{-# 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