packages feed

text-rope-zipper-0.1.0.0: test/Data/Text/Lazy/ZipperSpec.hs

{-# OPTIONS_GHC -Wno-orphans #-}

module Data.Text.Lazy.ZipperSpec where

import Data.Text.Lazy qualified as Text
import Data.Text.Lazy.Zipper
import Data.Text.Lazy.Zipper qualified as Zipper
import Util

data Move = Backward | Forward | Start | End | Rel !Int | Abs !Position
    deriving stock (Eq, Show)

instance Arbitrary Move where
    arbitrary =
        oneof
            [ pure Backward
            , pure Forward
            , pure Start
            , pure End
            , Rel <$> arbitrary
            , Abs <$> arbitrary
            ]

moveZipper :: Move -> TextZipper -> TextZipper
moveZipper Forward = moveForward
moveZipper Backward = moveBackward
moveZipper Start = moveStart
moveZipper End = moveEnd
moveZipper (Rel i) = moveCursor $ boundedAdd i
moveZipper (Abs i) = setCursor i

spec :: Spec
spec = parallel $ modifyMaxSuccess (* 100) do
    it "fromText is a right inverse of toText" $ property $ \t -> do
        let zipper = fromText t
        toText zipper `shouldBe` t
        Zipper.length zipper `shouldBe` Text.length t

    it "concatenates" $ property $ \t1 t2 -> do
        fromText t1 <> fromText t2 `shouldBe` fromParts t1 t2

    it "inserts" $ property $ \t1 t2 t3 ->
        insert t2 (fromParts t1 t3) `shouldBe` fromParts (t1 <> t2) t3

    it "deletes before" $ property $ \t1 (Text.singleton -> c) t2 ->
        deleteBefore (fromParts (t1 <> c) t2) `shouldBe` fromParts t1 t2

    it "deletes after" $ property $ \t1 (Text.singleton -> c) t2 ->
        deleteAfter (fromParts t1 (c <> t2)) `shouldBe` fromParts t1 t2

    it "moves" $ property $ \t1 t2 move -> do
        let zipper = fromParts t1 t2
        let zipperLength = fromIntegral . Zipper.length $ zipper
        zipper.cursor `shouldBe` fromIntegral (Text.length t1)
        zipper.cursor `shouldBe` fromIntegral (Text.length zipper.beforeCursor)
        let moveCursor' =
                clamp (0, zipperLength) . case move of
                    Backward -> boundedAdd (-1)
                    Forward -> boundedAdd 1
                    Start -> const minBound
                    End -> const maxBound
                    Rel i -> boundedAdd i
                    Abs i -> const i
        let zipper' = moveZipper move zipper
        zipper'.cursor `shouldBe` moveCursor' zipper.cursor
        zipper'.cursor `shouldBe` fromIntegral (Text.length zipper'.beforeCursor)
        toText zipper `shouldBe` toText zipper'