packages feed

vcs-ignore-0.0.1.0: test/Data/VCS/Ignore/Repo/GitSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Data.VCS.Ignore.Repo.GitSpec where

import qualified Data.List                     as L
import qualified Data.Text                     as T
import           Data.VCS.Ignore.Repo           ( RepoError(..) )
import           Data.VCS.Ignore.Repo.Git
import           System.Directory               ( makeAbsolute )
import           System.FilePath                ( (</>) )
import           Test.Hspec


spec :: Spec
spec = do
  let repo = "test-data" </> "fake-git-repo"

  describe "compilePattern" $ do
    it "compiles negated pattern" $ do
      compilePattern "!/foo/bar"
        `shouldBe` Pattern ["/foo/bar", "/foo/bar/**"] "!/foo/bar" True

    it "compiles pattern matching files or directories inside path" $ do
      compilePattern ".hidden"
        `shouldBe` Pattern ["**/.hidden", "**/.hidden/**"] ".hidden" False
      compilePattern ".hidden/"
        `shouldBe` Pattern ["**/.hidden/**"] ".hidden/" False


  describe "matchesPattern" $ do
    it "matches all content in subdirectory" $ do
      matchesPattern (compilePattern "/foo/*") "/foo" `shouldBe` False
      matchesPattern (compilePattern "/foo/*") "/foo/bar" `shouldBe` True

    it "matches all content in all subdirectories" $ do
      matchesPattern (compilePattern "/foo/**") "/foo/bar" `shouldBe` True
      matchesPattern (compilePattern "/foo/**") "/foo/bar/c" `shouldBe` True

    it "matches negated pattern" $ do
      matchesPattern (compilePattern "!/foo/*") "/foo" `shouldBe` False
      matchesPattern (compilePattern "!/foo/*") "/foo/bar" `shouldBe` True

    it "matches dir pattern anywhere in the path" $ do
      matchesPattern (compilePattern "bar/") "/foo/bar/x" `shouldBe` True
      matchesPattern (compilePattern "bar/") "/foo/bar" `shouldBe` False

    it "matches dir or file pattern anywhere in the path" $ do
      matchesPattern (compilePattern "bar") "/foo/bar/x" `shouldBe` True
      matchesPattern (compilePattern "bar") "/foo/bar" `shouldBe` True


  describe "parsePatterns" $ do
    it "parses glob patterns from input text (pattern per line)" $ do
      let input    = T.unlines [".cabal-sandbox/", "## comment", ".DS_Store"]
          expected = [".cabal-sandbox/", ".DS_Store"]
      parsePatterns input `shouldBe` expected


  describe "loadPatterns" $ do
    it "loads and parses glob patterns from input file" $ do
      let source   = repo </> "a" </> ".gitignore"
          expected = ["**/*.xml"]
      loadPatterns source `shouldReturn` expected

    it "returns empty list if input cannot be read" $ do
      let source = repo </> "non-existing"
      loadPatterns source `shouldReturn` []


  describe "findGitIgnores" $ do
    it "finds all .gitignore files in repo" $ do
      let expected =
            [ repo </> "a" </> ".gitignore"
            , repo </> "a" </> "b" </> ".gitignore"
            , repo </> ".gitignore"
            ]
      L.sort <$> findGitIgnores repo `shouldReturn` L.sort expected


  describe "gitIgnorePatterns" $ do
    it "loads patterns for all .gitignore files in repo" $ do
      let expected =
            [("/a/", ["**/*.xml"]), ("/a/b/", ["*.txt"]), ("/", ["foo"])]
      sortFst <$> gitIgnorePatterns repo `shouldReturn` sortFst expected


  describe "scanRepo'" $ do
    it "scans repository for ignored patterns" $ do
      absRepo <- makeAbsolute repo
      let
        fn1      = pure []
        fn2      = const $ pure []
        fn3      = const $ pure True
        expected = Git
          { gitPatterns = sortFst
            [("/", ["foo"]), ("/a/", ["**/*.xml"]), ("/a/b/", ["*.txt"])]
          , gitRepoRoot = absRepo
          }
      result <- scanRepo' fn1 fn2 gitIgnorePatterns fn3 repo
      let result' = result { gitPatterns = sortFst (gitPatterns result) }
      result' `shouldBe` expected

    it "aborts scanning if given path is not valid GIT repo" $ do
      let fn1 = pure []
          fn2 = const $ pure []
          fn3 = const $ pure False
      let err (InvalidRepo _ _) = True
      scanRepo' fn1 fn2 gitIgnorePatterns fn3 repo `shouldThrow` err


  describe "isIgnored'" $ do
    it "checks whether given path is excluded" $ do
      absRepo <- makeAbsolute repo
      let git = Git
            { gitPatterns = [ ("/"    , ["!keep.xml", ".hidden/", ".hid"])
                            , ("/a/"  , ["**/*.xml"])
                            , ("/a/b/", ["*.txt"])
                            ]
            , gitRepoRoot = absRepo
            }
      isIgnored' git "foo/bar" `shouldReturn` False
      isIgnored' git "a/hello.txt" `shouldReturn` False
      isIgnored' git "a/hello.xml" `shouldReturn` True
      isIgnored' git "a/b/hello.xml" `shouldReturn` True
      isIgnored' git "a/b/keep.xml" `shouldReturn` False
      isIgnored' git "/foo/bar" `shouldReturn` False
      isIgnored' git "/a/hello.txt" `shouldReturn` False
      isIgnored' git "/a/hello.xml" `shouldReturn` True
      isIgnored' git "/a/b/hello.xml" `shouldReturn` True
      isIgnored' git "/a/b/../hello.txt" `shouldReturn` False
      isIgnored' git "/a/b/../hello.xml" `shouldReturn` True
      isIgnored' git "/a/b/.hidden" `shouldReturn` False
      isIgnored' git "/a/b/.hidden/foo" `shouldReturn` True
      isIgnored' git "/a/b/.hid" `shouldReturn` True
      isIgnored' git "/a/b/.hid/foo" `shouldReturn` True


sortFst :: Ord a => [(a, b)] -> [(a, b)]
sortFst = L.sortOn fst