packages feed

language-elm-0.1.0.0: test/Spec.hs

{-# LANGUAGE OverloadedStrings #-}

import           Control.Monad.Writer
import           Elm.Classes
import           Elm.Decleration
import           Elm.Expression
import           Elm.GenError
import           Elm.Import
import           Elm.Program
import           Elm.Type
import           Renderer
import           Test.Hspec
import           Text.PrettyPrint     hiding (Str, render)

main = hspec $ do
    describe "Setup" $ do
        it "Should work" $ do
            5 `shouldBe` 5
    describe "Expression" $ do
        describe "Bool" $ do
            it "Should render true properly" $ do
                let expr = Bool True
                renderExpr expr `shouldBe` "True"
            it "Should render false properly" $ do
                let expr = Bool False
                renderExpr expr `shouldBe` "False"
        describe "Int" $ do
            it "Should work" $ do
                renderExpr (Int 5) `shouldBe` "5"
        describe "Float" $ do
            it "Should work" $ do
                renderExpr (Float 1.2) `shouldBe` "1.2"
        describe "String" $ do
            it "Should work" $ do
                renderExpr (Str "hello") `shouldBe` "\"hello\""
        describe "Tuple" $ do
            it "Should work for 0 tuples" $ do
                renderExpr (Tuple []) `shouldBe` "()"
            it "Should work for larger tuples" $ do
                renderExpr (Tuple [Int 5, Int 6]) `shouldBe` "( 5, 6 )"
                renderExpr (Tuple [Int 2, Float 2.5]) `shouldBe` "( 2, 2.5 )"
        describe "Var" $ do
            it "Should work" $ do
                renderExpr (Var "a") `shouldBe` "a"
                renderExpr (Var "abc") `shouldBe` "abc"
            it "Should work with overloadedstrings" $ do
                renderExpr "a" `shouldBe` "a"
                renderExpr "abc" `shouldBe` "abc"
            it "Should error on an empty string" $ do
                (runWriter . generate $ Var "") `shouldBe` (text "", Error "An empty string is not a valid variable name")
            it "Should wrap operators in parens" $ do
                pending
            it "Should only allow operators with valid characters" $ do
                pending
            it "Should only allow variables with valid characters" $ do
                pending
        describe "Under" $ do
            it "Does this I guess" $ do
                renderExpr Under `shouldBe` "_"
        describe "List" $ do
            it "Should work for empty lists" $ do
                renderExpr (List []) `shouldBe` "[]"
            it "Should work for single item lists" $ do
                renderExpr (List ["a"]) `shouldBe` "[a]"
            it "Should work for multi item lists" $ do
                renderExpr (List ["a", "b", "c"]) `shouldBe` "[a, b, c]"
            it "Should work with overloaded lists" $ do
                renderExpr (List ["a", "b", "c"]) `shouldBe` "[a, b, c]"
        describe "Function Application" $ do
            it "Should work for 0 params" $ do
                renderExpr (App ["a"]) `shouldBe` "a"
            it "Should work for multiple params" $ do
                renderExpr (App ["a", "b", "c"]) `shouldBe` "a b c"
                renderExpr (App ["a", Int 5, "b", Int 7]) `shouldBe` "a 5 b 7"
            it "Should nest properly" $ do
                renderExpr (App ["a", App ["b", "c"]]) `shouldBe` "a (b c)"
        describe "Inline operators" $ do
            it "Should work" $ do
                renderExpr (Op "+" "a" "b") `shouldBe` "a + b"
            it "Should error on invalid operator characters" $ do
                pending
        describe "Record" $ do
            it "Should work when empty" $ do
                renderExpr (Record Nothing []) `shouldBe` "{}"
            it "Should work with key value pairs" $ do
                renderExpr (Record Nothing [("a", Int 5), ("b", Int 6)])
                    `shouldBe` "{ a = 5, b = 6 }"
            it "Should allow updates" $ do
                renderExpr (Record (Just "a") [("b", Int 5)])
                    `shouldBe` "{ a | b = 5 }"
            it "Should warn here" $ do
                (runWriter . generate $ Record (Just "data") [])
                    `shouldBe` ("data", WarningList ["Trying to update record data with no changed fields"])
            it "Should error here" $ do
                (runWriter . generate $ Record (Just $ App ["f", Int 5]) [])
                    `shouldBe` ("", Error "You are unable to update a record with a non constant")
        describe "Let" $ do
            it "Should work with one dec" $ do
                text <- liftIO . readFile $ "test/spec/let1.txt"
                let
                    ast = Let "a" [("a", Int 5)]
                renderExpr ast ++ "\n" `shouldBe` text
            it "Should work with more than one dec" $ do
                text <- liftIO . readFile $ "test/spec/let2.txt"
                let
                    ast = Let (Op "+" "a" "b") [("a", Int 5), ("b", Int 6)]
                renderExpr ast ++ "\n" `shouldBe` text
            it "Should error with zero declerations" $ do
                (runWriter . generate $ Let "a" [])
                    `shouldBe` ("", Error "Unable to create let expression with 0 bindings")
        describe "Case" $ do
            it "Should work" $ do
                text <- liftIO . readFile $ "test/spec/case1.txt"
                let
                    ast =
                        Case "m"
                            [(App ["Just", "x"], "x")
                            ,("Nothing", Int 0)
                            ]
                renderExpr ast ++ "\n" `shouldBe` text
            it "Should generate an error with no cases" $ do
                let
                    ast = Case "m" []
                (runWriter . generate $ ast)
                    `shouldBe` ("", Error "Unable to create case expression with 0 cases")

    describe "Type" $ do
        describe "Params" $ do
            it "Should work for without params" $ do
                render (Params "a" [])
                    `shouldBe` "a"
            it "Should work with params" $ do
                render (Params "a" [Params "b" []])
                    `shouldBe` "a b"
            it "Should put params in parens if needed" $ do
                render (Params "a" [Params "b" [Params "c" []]])
                    `shouldBe` "a (b c)"

        describe "TApp" $ do
            it "Should work" $ do
                render (TApp [Params "a" [], Params "b" []])
                    `shouldBe` "a -> b"

            it "Should put nested tapps in parens" $ do
                render (TApp [Params "a" [], TApp [Params "a" [], Params "b" []]])
                    `shouldBe` "a -> (a -> b)"

        describe "TTuple" $ do
            it "Should work for unit" $ do
                render (TTuple []) `shouldBe` "()"

            it "Should return the value and warn with a one item tuple" $ do
                let
                    ast =
                        TTuple [Params "a" []]
                (runWriter . generate $ ast)
                    `shouldBe` ("(a)", WarningList ["Attempt to create a one item tuple"])
            it "Should work for multiple item tuples" $ do
                render (TTuple [Params "a" [], Params "b" []])
                    `shouldBe` "(a, b)"

        describe "TRecord" $ do
            it "Should error when passed nothing" $ do
                let
                    ast =
                        TRecord Nothing []
                (runWriter . generate $ ast)
                    `shouldBe` ("", Error "Unable to create a record type with no base and no constraints")

            it "Should warn when there are no constraints" $ do
                let
                    ast =
                        TRecord (Just "a") []
                (runWriter . generate $ ast)
                    `shouldBe` ("a", WarningList ["You are creating a record type from a with no constraints"])
            it "Should work with no base" $ do
                let
                    ast =
                        TRecord Nothing
                            [ ("a", Params "Int" [])
                            , ("b", Params "Int" [])
                            ]
                render ast `shouldBe` "{ a : Int, b : Int }"

            it "Should work with a base" $ do
                let
                    ast =
                        TRecord (Just "a")
                            [ ("b", Params "Int" [])
                            , ("c", Params "Int" [])
                            ]
                render ast `shouldBe` "{ a | b : Int, c : Int }"
    describe "Import" $ do
        describe "ImportItem" $ do
            it "Should work for single items" $ do
                render (Item "Maybe")
                    `shouldBe` "Maybe"
            it "Should work for sub importing" $ do
                render (ItemExposing "Maybe" ["Just", "Nothing"])
                    `shouldBe` "Maybe(Just, Nothing)"
            it "Should working for sub importing everything" $ do
                render (ItemEvery "Maybe")
                    `shouldBe` "Maybe(..)"
        describe "ImportType" $ do
            it "Should properly select everything" $ do
                render (Everything) `shouldBe` "(..)"
            it "Should properly select only types" $ do
                render (Select []) `shouldBe` "()"
            it "Should properly select items" $ do
                render (Select [Item "Just", Item "withDefault"])
                    `shouldBe` "(Just, withDefault)"
                render (Select [ItemEvery "Just", Item "withDefault"])
                    `shouldBe` "(Just(..), withDefault)"
        describe "Import" $ do
            it "Should import stuff" $ do
                render (Import "List" Nothing Nothing)
                    `shouldBe` "import List"
            it "Should render with an alias" $ do
                render (Import "Maybe" (Just "M") Nothing)
                    `shouldBe` "import Maybe as M"
            it "Should expose from the imports" $ do
                render (Import "Maybe" Nothing $ Just $ Select [Item "withDefault"])
                    `shouldBe` "import Maybe exposing (withDefault)"
        describe "Dec" $ do
            it "Should work for simple delcerations" $ do
                let
                    ast =
                        Dec "add5"
                            (TApp ["Int", "Int"])
                            [Var "x"]
                            (Op "+" (Var "x") (Int 5))
                file <- readFile "test/spec/dec1.txt"
                render ast ++ "\n" `shouldBe` file
            it "Should work for more complex declerations" $ do
                let
                    ast =
                        Dec "withDefault"
                            (TApp ["a", Params "Maybe" ["a"], "a"])
                            ["default", "m"]
                            (Case "m"
                                [ (App ["Just", "x"], "x")
                                , ("Nothing", "default")
                                ])
                file <- readFile "test/spec/dec2.txt"
                render ast ++ "\n" `shouldBe` file
            it "Should properly handle type declerations" $ do
                -- \_(*_*)_/
                let
                    ast =
                        DecType "Maybe"
                        ["a"]
                        [ ("Nothing", [])
                        , ("Just", ["a"])
                        ]
                file <- readFile "test/spec/dec3.txt"
                render ast ++ "\n" `shouldBe` file
            it "Should properly handle type aliases" $ do
                let
                    ast =
                        DecTypeAlias "Model" ["a"] (Params "Maybe" ["a"])
                file <- readFile "test/spec/dec4.txt"
                render ast ++ "\n" `shouldBe` file
    describe "Program" $ do
        it "Should work" $ do
            let
                ast =
                    Program "Maybe"
                        (Select
                            [ ItemExposing "Maybe" ["Just", "Nothing"]
                            , Item "andThen"
                            , Item "map"
                            , Item "map2"
                            , Item "map3"
                            , Item "map4"
                            , Item "map5"
                            , Item "withDefault"
                            ])
                        []
                        [ DecType "Maybe" ["a"]
                            [ ("Nothing", [])
                            , ("Just", ["a"])
                            ]
                        , Dec "withDefault"
                            (TApp ["a", Params "Maybe" ["a"], "a"])
                            ["default", "maybe"]
                            (Case "maybe"
                                [ (App ["Just", "value"], "value")
                                , ("Nothing", "default")
                                ])
                        , Dec "map"
                            (TApp [TApp ["a", "b"], Params "Maybe" ["a"], Params "Maybe" ["b"]])
                            ["f", "maybe"]
                            (Case "maybe"
                                [ (App ["Just", "value"], App ["Just", App ["f", "value"]])
                                , ("Nothing", "Nothing")
                                ])
                        , Dec "map2"
                            (TApp
                                [ TApp ["a", "b", "value"]
                                , Params "Maybe" ["a"]
                                , Params "Maybe" ["b"]
                                , Params "Maybe" ["value"]
                                ])
                            ["func", "ma", "mb"]
                            (Case (Tuple ["ma", "mb"])
                                [ (Tuple [App ["Just", "a"], App ["Just", "b"]],
                                    App ["Just", App ["func", "a", "b"]])
                                , (Under, "Nothing")
                                ])
                        , Dec "map3"
                            (TApp
                                [ TApp ["a", "b", "c", "value"]
                                , Params "Maybe" ["a"]
                                , Params "Maybe" ["b"]
                                , Params "Maybe" ["c"]
                                , Params "Maybe" ["value"]
                                ])
                            ["func", "ma", "mb", "mc"]
                            (Case (Tuple ["ma", "mb", "mc"])
                                [ (Tuple
                                    [ App ["Just", "a"]
                                    , App ["Just", "b"]
                                    , App ["Just", "c"]
                                    ],
                                    App ["Just", App ["func", "a", "b", "c"]])
                                , (Under, "Nothing")
                                ])
                        ]
            file <- readFile "test/program1.elm"
            let (str, err) = renderProgram ast
            err `shouldBe` WarningList []
            str `shouldBe` file