packages feed

sydtest-mutation-0.1.0.0: test/Test/Syd/MutationSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

module Test.Syd.MutationSpec (spec) where

import Data.List.NonEmpty (NonEmpty (..))
import Test.Syd
import Test.Syd.Mutation
import Test.Syd.Validity

spec :: Spec
spec = do
  genValidSpec @TestId

  describe "renderTestId" $ do
    it "dot-separates steps" $
      renderTestId (TestId (("foo", 0) :| [("bar", 0)]))
        `shouldBe` "foo.bar"
    it "appends :n when index is nonzero" $
      renderTestId (TestId (("foo", 0) :| [("bar", 2)]))
        `shouldBe` "foo.bar:2"
    it "shows all nonzero indices" $
      renderTestId (TestId (("foo", 1) :| [("bar", 2)]))
        `shouldBe` "foo:1.bar:2"
    it "handles a single step" $
      renderTestId (TestId (("works", 0) :| []))
        `shouldBe` "works"
    it "handles a single step with nonzero index" $
      renderTestId (TestId (("works", 3) :| []))
        `shouldBe` "works:3"
    it "escapes dots in descriptions" $
      renderTestId (TestId (("foo.bar", 0) :| []))
        `shouldBe` "foo\\.bar"
    it "escapes backslashes in descriptions" $
      renderTestId (TestId (("foo\\bar", 0) :| []))
        `shouldBe` "foo\\\\bar"
    it "escapes colons in descriptions" $
      renderTestId (TestId (("foo:bar", 0) :| []))
        `shouldBe` "foo\\:bar"
    it "escapes a trailing colon-digits suffix in descriptions" $
      renderTestId (TestId (("foo:42", 0) :| []))
        `shouldBe` "foo\\:42"

  describe "parseTestIdFilterArg" $ do
    it "returns Nothing on empty input" $
      parseTestIdFilterArg "" `shouldBe` Nothing
    it "parses a single step with no index" $
      parseTestIdFilterArg "foo"
        `shouldBe` Just (TestId (("foo", 0) :| []))
    it "parses a single step with an explicit zero index" $
      parseTestIdFilterArg "foo:0"
        `shouldBe` Just (TestId (("foo", 0) :| []))
    it "parses a single step with a nonzero index" $
      parseTestIdFilterArg "foo:2"
        `shouldBe` Just (TestId (("foo", 2) :| []))
    it "parses multiple steps" $
      parseTestIdFilterArg "foo.bar.baz"
        `shouldBe` Just (TestId (("foo", 0) :| [("bar", 0), ("baz", 0)]))
    it "parses multiple steps with mixed indices" $
      parseTestIdFilterArg "foo.bar:1.baz"
        `shouldBe` Just (TestId (("foo", 0) :| [("bar", 1), ("baz", 0)]))
    it "parses an index on the first step" $
      parseTestIdFilterArg "foo:3.bar.baz"
        `shouldBe` Just (TestId (("foo", 3) :| [("bar", 0), ("baz", 0)]))
    it "parses indices on all steps" $
      parseTestIdFilterArg "foo:1.bar:2.baz:3"
        `shouldBe` Just (TestId (("foo", 1) :| [("bar", 2), ("baz", 3)]))
    it "treats a colon followed by non-digits as part of the description" $
      parseTestIdFilterArg "foo:bar"
        `shouldBe` Just (TestId (("foo:bar", 0) :| []))
    it "parses an escaped dot as part of the description" $
      parseTestIdFilterArg "foo\\.bar"
        `shouldBe` Just (TestId (("foo.bar", 0) :| []))
    it "parses an escaped backslash as part of the description" $
      parseTestIdFilterArg "foo\\\\bar"
        `shouldBe` Just (TestId (("foo\\bar", 0) :| []))
    it "parses an escaped colon as part of the description" $
      parseTestIdFilterArg "foo\\:bar"
        `shouldBe` Just (TestId (("foo:bar", 0) :| []))
    it "returns Nothing on a trailing backslash" $
      parseTestIdFilterArg "foo\\" `shouldBe` Nothing
    -- renderTestId only ever emits @\\\\@, @\\.@, and @\\:@.  Any other
    -- @\\<c>@ in a parser input would have to come from somewhere other
    -- than 'renderTestId'; accept it and the parser becomes too
    -- permissive — 'parseTestIdFilterArg "foo\\x"' used to return
    -- @Just (TestId [("foox", 0)])@, which then re-renders to @"foox"@,
    -- a forward-roundtrip violation.
    it "returns Nothing on an unknown escape sequence" $
      parseTestIdFilterArg "foo\\x" `shouldBe` Nothing

  describe "renderTestId / parseTestIdFilterArg roundtrip" $ do
    it "roundtrips valid TestIds" $
      forAllValid $ \tid ->
        parseTestIdFilterArg (renderTestId tid) `shouldBe` Just tid
    -- Forward direction: if parseTestIdFilterArg accepts a string, it
    -- should be one that 'renderTestId' could have produced.
    it "parsed inputs round-trip back to the same string" $
      forAllValid $ \tid ->
        let s = renderTestId tid
         in case parseTestIdFilterArg s of
              Nothing -> expectationFailure ("parser rejected " ++ show s)
              Just tid' -> renderTestId tid' `shouldBe` s