packages feed

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 ==)