content: module FileMappingSpec where
  
  import GhcMod.FileMapping
  import GhcMod.Utils (withMappedFile)
  import Test.Hspec
  import TestUtils
  import qualified Data.Map as M
  import Dir
  import System.IO.Temp
  import System.Directory
  
  import GhcMod
  
  spec :: Spec
  spec = do
        describe "loadMappedFile" $ do
  
          it "inserts a given FilePath FileMapping into state with canonicalized path" $ do
            withDirectory_ "test/data/file-mapping" $ do
              mappedFiles <- runD $ do
                loadMappedFile "File.hs" "File.hs"
                getMMappedFiles
              dir <- getCurrentDirectory
              show mappedFiles `shouldBe` show (M.fromList [(dir </> "File.hs", FileMapping "File.hs" False)])
  
          it "should try to guess a canonical name if file doesn't exist" $ do
            withDirectory_ "test/data/file-mapping" $ do
              mappedFiles <- runD $ do
                loadMappedFile "NonExistantFile.hs" "File.hs"
                getMMappedFiles
              dir <- getCurrentDirectory
              show mappedFiles `shouldBe` show (M.fromList [(dir </> "NonExistantFile.hs", FileMapping "File.hs" False)])
  
        describe "loadMappedFileSource" $ do
  
          it "inserts a given FilePath FileMapping into state with canonicalized path" $ do
            withDirectory_ "test/data/file-mapping" $ do
              mappedFiles <- runD $ do
                loadMappedFileSource "File.hs" "main :: IO ()"
                getMMappedFiles
              dir <- getCurrentDirectory
              -- TODO
              M.toList mappedFiles `shouldSatisfy` \[(fn, FileMapping _to True)] ->
                fn == dir </> "File.hs"
  
          it "should try to guess a canonical name if file doesn't exist" $ do
            withDirectory_ "test/data/file-mapping" $ do
              mappedFiles <- runD $ do
                loadMappedFileSource "NonExistantFile.hs" "main :: IO ()"
                getMMappedFiles
              dir <- getCurrentDirectory
              -- TODO
              M.toList mappedFiles `shouldSatisfy` \[(fn, FileMapping _to True)] ->
                fn == dir </> "NonExistantFile.hs"
  
        describe "unloadMappedFile" $ do
  
          it "removes a given FilePath from state" $ do
            withDirectory_ "test/data/file-mapping" $ do
              mappedFiles <- runD $ do
                loadMappedFile "File.hs" "File2.hs"
                unloadMappedFile "File.hs"
                getMMappedFiles
              show mappedFiles `shouldBe` show (M.fromList ([] :: [(FilePath, FileMapping)]))
  
          it "should work even if file does not exist" $ do
            withDirectory_ "test/data/file-mapping" $ do
              mappedFiles <- runD $ do
                loadMappedFile "NonExistantFile.hs" "File2.hs"
                unloadMappedFile "NonExistantFile.hs"
                getMMappedFiles
              show mappedFiles `shouldBe` show (M.fromList ([] :: [(FilePath, FileMapping)]))
  
          it "should remove created temporary files" $ do
            withDirectory_ "test/data/file-mapping" $ do
              dir <- getCurrentDirectory
              fileExists <- runD $ do
                loadMappedFileSource "NonExistantFile.hs" "main :: IO ()"
                fp <- maybe undefined fmPath `fmap` lookupMMappedFile (dir </> "NonExistantFile.hs")
                unloadMappedFile "NonExistantFile.hs"
                liftIO $ doesFileExist fp
              not fileExists `shouldBe` True
  
        describe "withMappedFile" $ do
  
          it "checks if there is a redirected file and calls and action with its FilePath" $ do
            withDirectory_ "test/data/file-mapping" $ do
              res <- runD $ do
                loadMappedFile "File.hs" "File_Redir.hs"
                withMappedFile "File.hs" return
              res `shouldBe` "File_Redir.hs"
  
          it "checks if there is an in-memory file and calls and action with temporary file" $ do
            withDirectory_ "test/data/file-mapping" $ do
              (fn, src) <- runD $ do
                loadMappedFileSource "File.hs" "main = test"
                withMappedFile "File.hs" $ \fn -> do
                  src <- liftIO $ readFile fn
                  return (fn, src)
              fn `shouldSatisfy` (/="File.hs")
              src `shouldBe` "main = test"
  
          it "runs action with original filename if there is no mapping" $ do
            withDirectory_ "test/data/file-mapping" $ do
              fn <- runD $ do
                withMappedFile "File.hs" return
              fn `shouldBe` "File.hs"
  
        describe "integration tests" $ do
  
          it "checks redirected file if one is specified and outputs original filename" $ do
            withDirectory_ "test/data/file-mapping" $ do
              let fm = [("File.hs", "File_Redir.hs")]
              res <- run defaultOptions $ do
                mapM_ (uncurry loadMappedFile) fm
                checkSyntax ["File.hs"]
              res `shouldBe` "File.hs:1:1:Warning: Top-level binding with no type signature: main :: IO ()\n"
  
          it "works in a project with multiple executables (multiple Main modules)" $ do
            let fm = [ ("Main.hs", "Main_Redir.hs")
                     , ("OtherMain.hs", "OtherMain_Redir.hs")
                     ]
            res <- runD'  "test/data/file-mapping/duplicate-main" $ do
              mapM_ (uncurry loadMappedFile) fm
              checkSyntax ["Main.hs"]
            res `shouldBe` "" -- check it doesn't error
  
          it "checks in-memory file if one is specified and outputs original filename" $ do
            withDirectory_ "test/data/file-mapping" $ do
              let fm = [("File.hs", "main = putStrLn \"Hello World!\"\n")]
              res <- run defaultOptions $ do
                mapM_ (uncurry loadMappedFileSource) fm
                checkSyntax ["File.hs"]
              res `shouldBe` "File.hs:1:1:Warning: Top-level binding with no type signature: main :: IO ()\n"
  
          it "should work even if file doesn't exist" $ do
            withDirectory_ "test/data/file-mapping" $ do
              let fm = [("Nonexistent.hs", "main = putStrLn \"Hello World!\"\n")]
              res <- run defaultOptions $ do
                mapM_ (uncurry loadMappedFileSource) fm
                checkSyntax ["Nonexistent.hs"]
              res `shouldBe` "Nonexistent.hs:1:1:Warning: Top-level binding with no type signature: main :: IO ()\n"
  
          it "lints redirected file if one is specified and outputs original filename" $ do
            withDirectory_ "test/data/file-mapping" $ do
              res <- runD $ do
                loadMappedFile "File.hs" "File_Redir_Lint.hs"
                lint lintOpts "File.hs"
              res `shouldBe` "File.hs:4:1: Warning: Eta reduce\NULFound:\NUL  func a b = (*) a b\NULWhy not:\NUL  func = (*)\n"
  
          it "lints in-memory file if one is specified and outputs original filename" $ do
            withDirectory_ "test/data/file-mapping" $ do
              res <- runD $ do
                loadMappedFileSource "File.hs" "func a b = (++) a b\n"
                lint lintOpts "File.hs"
              res `shouldBe` "File.hs:1:1: Warning: Eta reduce\NULFound:\NUL  func a b = (++) a b\NULWhy not:\NUL  func = (++)\n"
  
          it "shows types of the expression for redirected files" $ do
              let tdir = "test/data/file-mapping"
              res <- runD' tdir $ do
                loadMappedFile "File.hs" "File_Redir_Lint.hs"
                types False "File.hs" 4 12
              res `shouldBe` "4 12 4 15 \"a -> a -> a\"\n4 12 4 17 \"a -> a\"\n4 12 4 19 \"a\"\n4 1 4 19 \"a -> a -> a\"\n"
  
          it "shows types of the expression with constraints for redirected files" $ do --
              let tdir = "test/data/file-mapping"
              res <- runD' tdir $ do
                loadMappedFile "File.hs" "File_Redir_Lint.hs"
                types True "File.hs" 4 12
              res `shouldBe` "4 12 4 15 \"a -> a -> a\"\n4 12 4 17 \"a -> a\"\n4 12 4 19 \"a\"\n4 1 4 19 \"Num a => a -> a -> a\"\n"
  
          it "shows types of the expression for in-memory files" $ do
              let tdir = "test/data/file-mapping"
              res <- runD' tdir $ do
                loadMappedFileSource "File.hs" "main = putStrLn \"Hello!\""
                types False "File.hs" 1 14
              res `shouldBe` "1 8 1 16 \"String -> IO ()\"\n1 8 1 25 \"IO ()\"\n1 1 1 25 \"IO ()\"\n"
  
          it "shows info for the expression for redirected files" $ do
              let tdir = "test/data/file-mapping"
              res <- runD' tdir $ do
                loadMappedFile "File.hs" "File_Redir_Lint.hs"
                info "File.hs" $ Expression "func"
              res `shouldBe` "func :: Num a => a -> a -> a \t-- Defined at File.hs:4:1\n"
  
          it "shows info for the expression for in-memory files" $ do
              let tdir = "test/data/file-mapping"
              res <- runD' tdir $ do
                loadMappedFileSource "File.hs" "module File where\n\ntestfun = putStrLn \"Hello!\""
                info "File.hs" $ Expression "testfun"
              res `shouldBe` "testfun :: IO () \t-- Defined at File.hs:3:1\n"
  
        describe "preprocessor tests" $ do
  
          it "checks redirected file if one is specified and outputs original filename" $ do
            withDirectory_ "test/data/file-mapping/preprocessor" $ do
              let fm = [("File.hs", "File_Redir.hs")]
              res <- run defaultOptions $ do
                mapM_ (uncurry loadMappedFile) fm
                checkSyntax ["File.hs"]
              res `shouldBe` "File.hs:3:1:Warning: Top-level binding with no type signature: main :: IO ()\n"
  
          it "works with full path as well" $ do
            withDirectory_ "test/data/file-mapping/preprocessor" $ do
              cwd <- getCurrentDirectory
              let fm = [("File.hs", cwd </> "File_Redir.hs")]
              res <- run defaultOptions $ do
                mapM_ (uncurry loadMappedFile) fm
                checkSyntax ["File.hs"]
              res `shouldBe` "File.hs:3:1:Warning: Top-level binding with no type signature: main :: IO ()\n"
  
          it "checks in-memory file" $ do
            withDirectory_ "test/data/file-mapping/preprocessor" $ do
              src <- readFile "File_Redir.hs"
              let fm = [("File.hs", src)]
              res <- run defaultOptions $ do
                mapM_ (uncurry loadMappedFileSource) fm
                checkSyntax ["File.hs"]
              res `shouldBe` "File.hs:3:1:Warning: Top-level binding with no type signature: main :: IO ()\n"
  
          it "lints redirected file if one is specified and outputs original filename" $ do
            withDirectory_ "test/data/file-mapping/preprocessor" $ do
              res <- runD $ do
                loadMappedFile "File.hs" "File_Redir_Lint.hs"
                lint lintOpts "File.hs"
              res `shouldBe` "File.hs:6:1: Warning: Eta reduce\NULFound:\NUL  func a b = (*) a b\NULWhy not:\NUL  func = (*)\n"
  
          it "lints in-memory file if one is specified and outputs original filename" $ do
            withDirectory_ "test/data/file-mapping/preprocessor" $ do
              src <- readFile "File_Redir_Lint.hs"
              res <- runD $ do
                loadMappedFileSource "File.hs" src
                lint lintOpts "File.hs"
              res `shouldBe` "File.hs:6:1: Warning: Eta reduce\NULFound:\NUL  func a b = (*) a b\NULWhy not:\NUL  func = (*)\n"
  
  
        describe "literate haskell tests" $ do
  
          it "checks redirected file if one is specified and outputs original filename" $ do
            withDirectory_ "test/data/file-mapping/lhs" $ do
              let fm = [("File.lhs", "File_Redir.lhs")]
              res <- run defaultOptions $ do
                mapM_ (uncurry loadMappedFile) fm
                checkSyntax ["File.lhs"]
              res `shouldBe` "File.lhs:1:3:Warning: Top-level binding with no type signature: main :: IO ()\n"
  
          it "checks in-memory file if one is specified and outputs original filename" $ do
            withDirectory_ "test/data/file-mapping/lhs" $ do
              src <- readFile "File_Redir.lhs"
              let fm = [("File.lhs", src)]
              res <- run defaultOptions $ do
                mapM_ (uncurry loadMappedFileSource) fm
                checkSyntax ["File.lhs"]
              res `shouldBe` "File.lhs:1:3:Warning: Top-level binding with no type signature: main :: IO ()\n"
  
          -- NOTE: There is a bug in hlint that prevents it from linting lhs files.
          -- it "lints redirected file if one is specified and outputs original filename" $ do
          --   withDirectory_ "test/data/file-mapping/lhs" $ do
          --     res <- runD $ do
          --       loadMappedFile "File.lhs" (RedirectedMapping "File_Redir_Lint.lhs")
          --       lint "File.lhs"
          --     res `shouldBe` "File.lhs:6:1: Error: Eta reduce\NULFound:\NUL  func a b = (*) a b\NULWhy not:\NUL  func = (*)\n"
  
          -- it "lints in-memory file if one is specified and outputs original filename" $ do
          --   withDirectory_ "test/data/file-mapping/lhs" $ do
          --     src <- readFile "File_Redir_Lint.lhs"
          --     res <- runD $ do
          --       loadMappedFile "File.lhs" (MemoryMapping $ Just src)
          --       lint "File.lhs"
          --     res `shouldBe` "File.lhs:6:1: Error: Eta reduce\NULFound:\NUL  func a b = (*) a b\NULWhy not:\NUL  func = (*)\n"
  
        describe "template haskell" $ do
  
          it "works with a redirected module using TemplateHaskell" $ do
            withSystemTempDirectory "ghc-mod-test" $ \tmpdir -> do
              srcFoo <- readFile "test/data/template-haskell/Foo.hs"
              srcBar <- readFile "test/data/template-haskell/Bar.hs"
              withDirectory_ "test/data/file-mapping" $ do
                writeFile (tmpdir </> "Foo_Redir.hs") srcFoo
                writeFile (tmpdir </> "Bar_Redir.hs") srcBar
                let fm = [("Foo.hs", tmpdir </> "Foo_Redir.hs")
                         ,("Bar.hs", tmpdir </> "Bar_Redir.hs")]
                res <- run defaultOptions $ do
                  mapM_ (uncurry loadMappedFile) fm
                  types False "Bar.hs" 5 1
                res `shouldBe` unlines ["5 1 5 20 \"[Char]\""]
  
          it "works with a memory module using TemplateHaskell" $ do
            srcFoo <- readFile "test/data/template-haskell/Foo.hs"
            srcBar <- readFile "test/data/template-haskell/Bar.hs"
            withDirectory_ "test/data/file-mapping" $ do
              let fm = [("Foo.hs", srcFoo)
                       ,("Bar.hs", srcBar)]
              res <- run defaultOptions $ do
                mapM_ (uncurry loadMappedFileSource) fm
                types False "Bar.hs" 5 1
              res `shouldBe` unlines ["5 1 5 20 \"[Char]\""]
  
  
  lintOpts :: LintOpts
  lintOpts =
      defaultLintOpts { optLintHlintOpts = ["--ignore=Use module export list"] }
  