packages feed

tilia-0.0.1.0: tests/Tilia/Doc/InternalSpec.hs

{-# LANGUAGE OverloadedStrings #-}

-- | The span algebra and the rendering engine's primitives.
module Tilia.Doc.InternalSpec (spec) where

import Data.Text (Text)
import Test.Hspec
import Tilia.Doc
import Tilia.Doc.Combinators
import Tilia.Doc.Internal (groupLayout)
import Tilia.Span

spec :: Spec
spec = do
  describe "Span" $ do
    it "recognises a single-line span" $
      isSingleLine (mkSpan (3, 1) (3, 40)) `shouldBe` True
    it "recognises a multi-line span" $
      isSingleLine (mkSpan (3, 1) (4, 1)) `shouldBe` False
    it "unions to cover both operands" $
      mkSpan (1, 5) (1, 9) <> mkSpan (3, 2) (4, 1)
        `shouldBe` mkSpan (1, 5) (4, 1)
    it "takes the earlier start column when starts share a line" $
      mkSpan (1, 9) (1, 20) <> mkSpan (1, 3) (1, 5)
        `shouldBe` mkSpan (1, 3) (1, 20)

  describe "groupLayout" $ do
    it "goes flat with no span" $
      groupLayout Nothing `shouldBe` Flat
    it "goes flat for a single-line span" $
      groupLayout (Just (mkSpan (1, 1) (1, 9))) `shouldBe` Flat
    it "goes broken for a multi-line span" $
      groupLayout (Just (mkSpan (1, 1) (2, 9))) `shouldBe` Broken

  describe "atoms" $ do
    it "renders nothing for an empty document" $
      out mempty `shouldBe` ""
    it "terminates output with a newline" $
      out (txt "x") `shouldBe` "x\n"
    it "collapses repeated spaces" $
      out (txt "a" <> space <> space <> txt "b") `shouldBe` "a b\n"
    it "drops a space before a line break" $
      out (txt "a" <> space <> hardBreak <> txt "b") `shouldBe` "a\nb\n"
    it "drops a leading space" $
      out (space <> txt "a") `shouldBe` "a\n"
    it "ignores an empty fragment" $
      out (txt "a" <> txt "" <> txt "b") `shouldBe` "ab\n"

  describe "blank lines" $ do
    it "collapses runs" $
      out (txt "a" <> blankLine <> blankLine <> txt "b")
        `shouldBe` "a\n\nb\n"
    it "drops a leading blank line" $
      out (blankLine <> txt "a") `shouldBe` "a\n"
    it "drops a trailing blank line" $
      out (txt "a" <> blankLine) `shouldBe` "a\n"
    it "separates when there is content on both sides" $
      out (txt "a" <> blankLine <> txt "b") `shouldBe` "a\n\nb\n"
    it "caps a run of hard breaks at one blank line" $ do
      out (txt "a" <> hardBreak <> hardBreak <> txt "b")
        `shouldBe` "a\n\nb\n"
      out (txt "a" <> hardBreak <> hardBreak <> hardBreak <> txt "b")
        `shouldBe` "a\n\nb\n"
      out (txt "a" <> mconcat (replicate 8 hardBreak) <> txt "b")
        `shouldBe` "a\n\nb\n"
    it "caps a mixture of hard breaks and blank lines" $
      out (txt "a" <> hardBreak <> blankLine <> hardBreak <> blankLine <> txt "b")
        `shouldBe` "a\n\nb\n"
    it "still breaks once for a single hard break" $
      out (txt "a" <> hardBreak <> txt "b") `shouldBe` "a\nb\n"

  describe "indentation" $ do
    it "indents by one step" $
      out (broken (txt "a" <> indent (breakOrSpace <> txt "b")))
        `shouldBe` "a\n  b\n"
    it "nests relative to the enclosing level" $
      out (broken (txt "a" <> indent (breakOrSpace <> txt "b" <> indent (breakOrSpace <> txt "c"))))
        `shouldBe` "a\n  b\n    c\n"
    it "aligns to the current column" $
      out (broken (txt "ab" <> space <> align (txt "c" <> breakOrSpace <> txt "d")))
        `shouldBe` "ab c\n   d\n"
    it "leaves no trailing whitespace on an empty line" $
      out (broken (indent (txt "a" <> hardBreak <> hardBreak <> txt "b")))
        `shouldBe` "  a\n\n  b\n"
    it "does not indent a line with nothing on it" $
      out (broken (indent (txt "a" <> hardBreak)))
        `shouldBe` "  a\n"

out :: Doc -> Text
out = printDoc defaultRenderOptions