require-0.4.11: test-suite/Main.hs
import qualified Data.Text as Text
import Relude
import qualified Require.Error as Error
import qualified Require.File as File
import qualified Require.Transform as Require
import Require.Types
import qualified Test.Tasty
import Test.Tasty.Hspec
import Test.Hspec
main :: IO ()
main = do
test <- testSpec "require" spec
Test.Tasty.defaultMain test
spec :: Spec
spec = parallel $ do
let transformLines autoMode fileInput =
case Require.transform autoMode fileInput of
Left err -> do
-- sadly expectationFailure is not polymorphic in its return type
expectationFailure $ "Tranform failed: " ++ show err
pure $ error "expectationFailure should have thrown"
Right tr ->
pure tr
transformString autoMode =
fmap toString . transform autoMode
transform autoMode =
fmap unlines . transformLines autoMode
describe "the transformation" $ do
it "transforms the 'require' keyword into a properly qualified import" $ do
let input = "require Data.Text"
let expected = "import qualified Data.Text as Text"
actual <- transformString
AutorequireDisabled
(File.Input (File.Name "Foo.hs") input)
actual `shouldContain` expected
it "imports the type based on the module" $ do
let input = "require Data.Text"
let expected = "import Data.Text (Text)"
actual <- transformString
AutorequireDisabled
(File.Input (File.Name "Foo.hs") input)
actual `shouldContain` expected
it "keeps the rest of the content intact" $ do
let input = "module Foo where\nrequire Data.Text\nfoo = 42"
let expectedStart = "{-# LINE 1"
let expectedModule = "module Foo where"
let expectedTypeImport = "import Data.Text (Text)"
let expectedQualifiedImport = "import qualified Data.Text as Text"
let expectedContent = "foo = 42\n"
actual <- transformString
AutorequireDisabled
(File.Input (File.Name "Foo.hs") input)
actual `shouldStartWith` expectedStart
actual `shouldContain` expectedModule
actual `shouldContain` expectedTypeImport
actual `shouldContain` expectedQualifiedImport
actual `shouldEndWith` expectedContent
it "aliases the modules properly" $ do
let input = "require Data.Text as Foo"
let expectedTypeImport = "import Data.Text (Text)"
let expectedQualifiedImport = "import qualified Data.Text as Foo"
actual <- transformString
AutorequireDisabled
(File.Input (File.Name "Foo.hs") input)
actual `shouldContain` expectedTypeImport
actual `shouldContain` expectedQualifiedImport
it "imports the types properly" $ do
let input = "require Data.Text (Foo)"
let expectedTypeImport = "import Data.Text (Foo)"
let expectedQualifiedImport = "import qualified Data.Text as Text"
actual <- transformString
AutorequireDisabled
(File.Input (File.Name "Foo.hs") input)
actual `shouldContain` expectedTypeImport
actual `shouldContain` expectedQualifiedImport
it "imports the types and aliases the modules properly" $ do
let input = "require Data.Text as Quux (Foo)"
let expectedTypeImport = "import Data.Text (Foo)"
let expectedQualifiedImport = "import qualified Data.Text as Quux"
actual <- transformString
AutorequireDisabled
(File.Input (File.Name "Foo.hs") input)
actual `shouldContain` expectedTypeImport
actual `shouldContain` expectedQualifiedImport
it "skips comments" $ do
let input = "require Data.Text -- test of comments"
let expected = "import Data.Text (Text)"
actual <- transformString
AutorequireDisabled
(File.Input (File.Name "Foo.hs") input)
actual `shouldContain` expected
it "allows empty parentheses" $ do
let input = "require Data.Text ()"
let expected1 = "import Data.Text ()"
let expected2 = "import qualified Data.Text as Text"
actual <- transformLines
AutorequireDisabled
(File.Input (File.Name "Foo.hs") input)
actual `shouldSatisfy` elem expected1
actual `shouldSatisfy` elem expected2
describe "require-mode" $ do
it "keeps requires where the module is a substring of the filename" $ do
-- Test case for https://github.com/theam/require/issues/20
let fileInput = Text.unlines [ "module FooTest where", "require Foo" ]
let expected1 = "import Foo (Foo)"
let expected2 = "import qualified Foo as Foo"
actual <- transformLines
AutorequireDisabled
(File.Input (File.Name "FooTests.hs") fileInput)
actual `shouldSatisfy` elem expected1
actual `shouldSatisfy` elem expected2
describe "autorequire directive" $ do
it "respects them" $ do
let fileInput = Text.unlines [ "module Main where", "autorequire", "import B" ]
let requireInput = Text.unlines [ "import A" ]
actual <- transformLines
(AutorequireOnDirective $ Just $ File.Input (File.Name "Requires") requireInput)
(File.Input (File.Name "src/Foo/Bar.hs") fileInput)
actual `shouldSatisfy` elem "module Main where"
actual `shouldSatisfy` elem "import A"
actual `shouldSatisfy` elem "import B"
actual `shouldSatisfy` elemN 0 "autorequire"
it "ignores them after the first one" $ do
let fileInput = Text.unlines [ "autorequire", "autorequire" ]
let requireInput = Text.unlines [ "import A" ]
actual <- transformLines
(AutorequireOnDirective $ Just $ File.Input (File.Name "Requires") requireInput)
(File.Input (File.Name "src/Foo/Bar.hs") fileInput)
actual `shouldSatisfy` elemN 1 "import A"
actual `shouldSatisfy` elemN 0 "autorequire"
it "throws an error if no requires file is provided" $ do
let fileInput = "autorequire"
let actual = Require.transform
(AutorequireOnDirective Nothing)
(File.Input (File.Name "Foo.hs") fileInput)
actual `shouldBe` Left Error.MissingOptionalRequiresFile
describe "autorequire-mode" $ do
describe "inclusion after module directive" $ do
let checkInclusion n fileInput = do
let requireInput = "import A"
actual <- transformLines
(AutorequireEnabled $ File.Input (File.Name "Requires") requireInput)
(File.Input (File.Name "src/Foo/Bar.hs") fileInput)
actual `shouldSatisfy` elemN n requireInput
it "works for no export list" $ do
checkInclusion 1 "module Main where"
it "works for a one line export list" $ do
checkInclusion 1 "module Main (Datatype(..), (*+), foo, Bar) where"
it "works for a multi line export list" $ do
checkInclusion 1 $ Text.unlines
[ "module Main"
, " ( -- * Foo"
, " , Foo(..)"
, " , (*+)"
, " , -- ** Bar"
, " foo, Bar"
, " ) where"
]
it "doesn't add after data/class/instance declarations" $ do
let fileInput = unlines
[ "class Foo a where"
, "instance Foo x => Bar (Baz x) where"
, "data Vec n a where"
, " Nil :: Vec 0 a"
, " Cons :: a -> Vec n a -> Vec (n + 1) a"
]
let requireInput = unlines [ "import A" ]
let actual = Require.transform
(AutorequireEnabled $ File.Input (File.Name "Requires") requireInput)
(File.Input (File.Name "Foo.hs") fileInput)
actual `shouldBe` Left Error.AutorequireImpossible
it "doesn't add after data/class/instance declarations split to multiple lines" $ do
let fileInput = unlines
[ "class Foo a -- some explanation here"
, " where"
]
let requireInput = unlines [ "import A" ]
let actual = Require.transform
(AutorequireEnabled $ File.Input (File.Name "Requires") requireInput)
(File.Input (File.Name "Foo.hs") fileInput)
actual `shouldBe` Left Error.AutorequireImpossible
describe "triggered using the autorequire directive" $ do
it "can be triggered before without a module directive" $ do
let fileInput = unlines [ "autorequire", "main = return ()" ]
let requireInput = unlines [ "import A" ]
let expected = unlines
[ "{-# LINE 1 \"Requires\" #-}"
, "import A"
, "{-# LINE 2 \"Foo.hs\" #-}"
, "main = return ()"
]
actual <- transform
(AutorequireEnabled $ File.Input (File.Name "Requires") requireInput)
(File.Input (File.Name "Foo.hs") fileInput)
actual `shouldBe` expected
it "can't be retrigged after the module directive" $ do
let fileInput = unlines [ "module Main where", "autorequire" ]
let requireInput = unlines [ "import A" ]
let expected = unlines
[ "{-# LINE 1 \"Foo.hs\" #-}"
, "module Main where"
, "{-# LINE 1 \"Requires\" #-}"
, "import A"
]
actual <- transform
(AutorequireEnabled $ File.Input (File.Name "Requires") requireInput)
(File.Input (File.Name "Foo.hs") fileInput)
actual `shouldBe` expected
it "doesn't retrigger automatically after the module directive" $ do
let fileInput = unlines [ "autorequire", "module Main where" ]
let requireInput = unlines [ "import A" ]
let expected = unlines
[ "{-# LINE 1 \"Requires\" #-}"
, "import A"
, "{-# LINE 2 \"Foo.hs\" #-}"
, "module Main where"
]
actual <- transform
(AutorequireEnabled $ File.Input (File.Name "Requires") requireInput)
(File.Input (File.Name "Foo.hs") fileInput)
actual `shouldBe` expected
it "drops self-imports" $ do
let fileInput = "module Foo.Bar where"
let requireInput = "require Foo.Bar"
let notExpected = "import Foo.Bar"
actual <- transform
(AutorequireEnabled $ File.Input (File.Name "Requires") requireInput)
(File.Input (File.Name "src/Foo/Bar.hs") fileInput)
toString actual `shouldNotContain` notExpected
it "adds LINE pragmas around the Requires contents" $ do
let fileInput = Text.unlines [ "module Main where", "import B" ]
let requireInput = Text.unlines [ "import A" ]
let expected = Text.unlines
[ "{-# LINE 1 \"Foo.hs\" #-}"
, "module Main where"
, "{-# LINE 1 \"Requires\" #-}"
, "import A"
, "{-# LINE 2 \"Foo.hs\" #-}"
, "import B"
]
actual <- transform
(AutorequireEnabled $ File.Input (File.Name "Requires") requireInput)
(File.Input (File.Name "Foo.hs") fileInput)
actual `shouldBe` expected
-- | Checks if a given element is contained exactly @n@ times in the given list.
elemN :: Eq a => Int -> a -> [a] -> Bool
elemN n a = (n ==) . length . filter (a ==)