packages feed

mmark-ext-0.3.0.0: tests/Text/MMark/Extension/HeadingSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Text.MMark.Extension.HeadingSpec (spec) where

import Data.Text (Text)
import Data.Text qualified as T
import Test.Hspec
import Text.MMark qualified as MMark
import Text.MMark.Extension.Heading
import Text.MMark.Extension.TestUtils

spec :: Spec
spec = do
  describe "checkHeadings" $ do
    it "reports a heading that skips a level" $
      headingErrors "# A\n\n### B"
        `shouldReturn` [ "3:1: this heading is of level 3, but the one before it is of level 1, so the outline of the document skips a level"
                       ]
    it "reports a second level 1 heading" $
      headingErrors "# A\n\n# B"
        `shouldReturn` ["3:1: there is more than one level 1 heading in this document"]
    it "reports two headings that get the same id" $
      headingErrors "# A\n\n## A"
        `shouldReturn` ["3:1: another heading is already given the id \"a\""]
    it "accepts a well formed outline" $
      headingErrors "# A\n\n## B\n\n### C\n\n## D" `shouldReturn` []
    it "reports each problem exactly once" $
      headingErrors "# A\n\nSome text.\n\nMore text.\n\n### B"
        `shouldReturn` [ "7:1: this heading is of level 3, but the one before it is of level 1, so the outline of the document skips a level"
                       ]

  describe "headingProblems" $ do
    it "finds nothing in a document with no headings" $
      problems "just some text" `shouldBe` []
    it "finds nothing in a well formed outline" $
      problems "# A\n\n## B\n\n### C\n\n## D" `shouldBe` []
    it "accepts an outline that comes back up several levels at once" $
      problems "# A\n\n## B\n\n### C\n\n## D\n\n# E" `shouldBe` ["title"]
    it "accepts a document that starts below level 1" $
      problems "## A\n\n### B" `shouldBe` []
    it "names the level a heading skips to and from" $
      problems "## A\n\n##### B" `shouldBe` ["skip"]
    it "finds a problem of each kind at once" $
      problems "# A\n\n### B\n\n# A" `shouldBe` ["skip", "title", "collision"]
    it "reports the problems in the order they appear" $
      problems "# A\n\n# B\n\n### C"
        `shouldBe` ["title", "skip"]
    it "finds a collision between headings of different levels" $
      problems "# Same\n\n## Same" `shouldBe` ["collision"]
    it "reports every heading after the first that shares an id" $
      problems "## A\n\n## A\n\n## A" `shouldBe` ["collision", "collision"]
    it "sees headings inside a block quote as part of the outline" $
      problems "# A\n\n> ### B" `shouldBe` []

-- | Scan a document for its headings and check them.
headingErrors :: Text -> IO [Text]
headingErrors input = do
  Right doc <- pure (MMark.parse "" input)
  checkErrors (checkHeadings (MMark.runScanner headingScanner doc)) input

-- | The problems of a document, each named by its kind so that a test does
-- not have to repeat the whole message.
problems :: Text -> [Text]
problems input = kindOf . snd <$> headingProblems (scan input)
  where
    scan t = case MMark.parse "" t of
      Left _ -> error "the test input does not parse"
      Right doc -> MMark.runScanner headingScanner doc
    kindOf msg
      | "skips a level" `T.isSuffixOf` msg = "skip"
      | "more than one level 1" `T.isInfixOf` msg = "title"
      | "already given the id" `T.isInfixOf` msg = "collision"
      | otherwise = msg