packages feed

nbparts-0.1.0.0: tests/Tests/Util/TextSpec.hs

module Tests.Util.TextSpec where

import Data.Text qualified as Text
import Hedgehog (forAll, (===))
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import Nbparts.Util.Text (lineColToIndex, replaceSlices, splitKeepNewlines)
import Test.Hspec (Spec, context, describe, it, shouldBe)
import Test.Hspec.Hedgehog (hedgehog)

spec :: Spec
spec = do
  describe "lineColToIndex" $ do
    context "when given an empty list" $ do
      it "returns Nothing" $ hedgehog $ do
        let textLines = []
        line <- forAll $ Gen.int (Range.linear minBound maxBound)
        col <- forAll $ Gen.int (Range.linear minBound maxBound)
        lineColToIndex textLines line col === Nothing

    context "when given a single line with valid line and col" $ do
      it "returns `col - 1`" $ do
        textLine <- forAll $ Gen.text (Range.linear 1 50) Gen.unicode
        let textLines = [textLine]
        col <- forAll $ Gen.int (Range.linear 1 (Text.length textLine + 1))
        lineColToIndex textLines 1 col === Just (col - 1)

    context "when given multiple lines with valid line and col" $ do
      it "computes the correct index for a simple case" $ do
        -- "abc\ndefg\nhijkl"
        let textLines = ["abc", "defg", "hijkl"]
        lineColToIndex textLines 1 1 `shouldBe` Just 0
        lineColToIndex textLines 1 2 `shouldBe` Just 1
        lineColToIndex textLines 2 1 `shouldBe` Just 4
        lineColToIndex textLines 3 3 `shouldBe` Just 11
        lineColToIndex textLines 3 6 `shouldBe` Just 14

      it "returns an index whose letter is equal to the one retrieved using the line-col index" $ hedgehog $ do
        textLines <-
          forAll $
            Gen.list
              (Range.linear 1 5)
              (Gen.text (Range.linear 1 50) Gen.unicode)

        line <- forAll $ Gen.int (Range.linear 1 (length textLines))
        let textLine = textLines !! (line - 1)

        col <- forAll $ Gen.int (Range.linear 1 (Text.length textLine))
        let letter = Text.index textLine (col - 1)

        let text = Text.intercalate "\n" textLines
        let maybeIdx = lineColToIndex textLines line col
        let letter' = Text.index text <$> maybeIdx

        Just letter === letter'

    context "when given non-positive indices" $ do
      it "returns Nothing" $ hedgehog $ do
        textLines <-
          forAll $
            Gen.list
              (Range.linear 1 5)
              (Gen.text (Range.linear 1 50) Gen.unicode)
        line <- forAll $ Gen.int (Range.linear (-100) 0)
        col <- forAll $ Gen.int (Range.linear (-100) 0)
        lineColToIndex textLines line col === Nothing

    context "when given out-of-range line" $ do
      it "returns Nothing" $ hedgehog $ do
        textLines <-
          forAll $
            Gen.list
              (Range.linear 1 5)
              (Gen.text (Range.linear 1 50) Gen.unicode)
        line <- forAll $ Gen.int (Range.linear (length textLines + 1) 1000)
        lineColToIndex textLines line 1 === Nothing

    context "when given out-of-range col" $ do
      it "returns Nothing" $ hedgehog $ do
        textLines <-
          forAll $
            Gen.list
              (Range.linear 1 5)
              (Gen.text (Range.linear 1 50) Gen.unicode)
        line <- forAll $ Gen.int (Range.linear 1 (length textLines))
        let textLine = textLines !! (line - 1)
        col <- forAll $ Gen.int (Range.linear (Text.length textLine + 2) 1000)
        lineColToIndex textLines line col === Nothing

  describe "replaceSlices" $ do
    context "when given no replacements" $ do
      it "is identity" $ hedgehog $ do
        t <- forAll $ Gen.text (Range.linear 0 250) Gen.unicode
        replaceSlices t [] === Just t

    context "when given only one replacement" $ do
      it "behaves like manual splice" $ hedgehog $ do
        t <- forAll $ Gen.text (Range.linear 1 100) Gen.unicode
        start <- forAll $ Gen.int (Range.linear 0 (Text.length t))
        end <- forAll $ Gen.int (Range.linear start (Text.length t))
        repl <- forAll $ Gen.text (Range.linear 0 20) Gen.unicode

        let manual = Text.take start t <> repl <> Text.drop end t
            replaced = replaceSlices t [((start, end), repl)]
        replaced === Just manual

    context "when given only one replacement at the start" $ do
      it "behaves like prepending" $ hedgehog $ do
        t <- forAll $ Gen.text (Range.linear 1 100) Gen.unicode
        repl <- forAll $ Gen.text (Range.linear 0 20) Gen.unicode

        let replaced = replaceSlices t [((0, 0), repl)]
        replaced === Just (repl <> t)

    context "when given only one replacement at the end" $ do
      it "behaves like appending" $ hedgehog $ do
        t <- forAll $ Gen.text (Range.linear 1 100) Gen.unicode
        repl <- forAll $ Gen.text (Range.linear 0 20) Gen.unicode
        let len = Text.length t

        let replaced = replaceSlices t [((len, len), repl)]
        replaced === Just (t <> repl)

    context "when given non-overlapping slices" $ do
      it "correctly replaces" $ do
        replaceSlices "abcdef" [((1, 3), "XYZ")] `shouldBe` Just "aXYZdef"
        replaceSlices "abcdef" [((0, 1), "XYZ"), ((3, 6), "FOOBAR")] `shouldBe` Just "XYZbcFOOBAR"
        replaceSlices "abcdef" [((0, 1), "XYZ"), ((1, 3), "FOO"), ((3, 6), "BAR")] `shouldBe` Just "XYZFOOBAR"

    context "when given overlapping slices" $ do
      it "returns Nothing" $ do
        replaceSlices "abcdef" [((0, 4), "FOO"), ((1, 5), "BAR")] `shouldBe` Nothing

    context "when given negative indices" $ do
      it "returns Nothing" $ do
        replaceSlices "abcdef" [((-1, 3), "FOO")] `shouldBe` Nothing
        replaceSlices "abcdef" [((1, -1), "FOO")] `shouldBe` Nothing
        replaceSlices "abcdef" [((-3, -1), "FOO")] `shouldBe` Nothing

    context "when given out-of-range indices" $ do
      it "returns Nothing" $ do
        replaceSlices "abcdef" [((6, 8), "FOO")] `shouldBe` Nothing
        replaceSlices "abcdef" [((3, 8), "FOO")] `shouldBe` Nothing

  describe "splitKeepNewlines" $ do
    context "when given empty text" $ do
      it "returns an empty list" $ do
        splitKeepNewlines "" `shouldBe` []

    context "when given text intercalated with single newlines" $ do
      it "splits and keeps newlines" $ do
        splitKeepNewlines "abc\ndefg\nhijkl" `shouldBe` ["abc\n", "defg\n", "hijkl"]

    context "when given text intercalated with multiple newlines" $ do
      it "splits and keeps newlines" $ do
        splitKeepNewlines "abc\n\ndefg\n\n\nhijkl" `shouldBe` ["abc\n", "\n", "defg\n", "\n", "\n", "hijkl"]

    context "when given text with trailing newlines" $ do
      it "splits and keeps newlines" $ do
        splitKeepNewlines "abc\ndefg\n\nhijkl\n" `shouldBe` ["abc\n", "defg\n", "\n", "hijkl\n"]
        splitKeepNewlines "abc\ndefg\n\nhijkl\n\n\n" `shouldBe` ["abc\n", "defg\n", "\n", "hijkl\n", "\n", "\n"]

    it "concatenation of parts is the original text" $ hedgehog $ do
      ls <-
        forAll $
          Gen.list
            (Range.linear 0 10)
            (Gen.text (Range.linear 0 100) Gen.unicode)
      let txt = Text.intercalate "\n" ls
      mconcat (splitKeepNewlines txt) === txt