content: module ExampleSpec (
    spec,
  ) where
  
  import Data.IORef (IORef, modifyIORef', newIORef, readIORef)
  import Data.Text qualified as Text
  
  import Skeletest
  import Skeletest.Predicate qualified as P
  import Skeletest.Prop qualified as Prop
  import Skeletest.Prop.Gen qualified as Gen
  import Skeletest.Prop.Range qualified as Range
  
  spec :: Spec
  spec = do
    describe "predicates" $ do
      it "checks approximate equality" $ do
        let x = 0.1 + 0.2 :: Double
        x `shouldSatisfy` P.approx P.tol 0.3
        x `shouldSatisfy` P.approx P.tol{P.rel = Just 1e-8} 0.3
        x `shouldSatisfy` P.approx P.tol{P.abs = 1e-12} 0.3
        x `shouldSatisfy` P.approx P.tol{P.rel = Just 1e-8, P.abs = 1e-12} 0.3
        x `shouldSatisfy` P.approx P.tol{P.rel = Nothing} 0.3
        x `shouldSatisfy` P.approx P.tol{P.rel = Nothing, P.abs = 1e-12} 0.3
  
      it "matches constructors" $ do
        ConA `shouldSatisfy` P.con ConA
        ConB 1 2 `shouldSatisfy` P.con (ConB (P.eq 1) P.anything)
  
        -- record field order doesn't matter
        ConC{conC1 = 1, conC2 = 2} `shouldSatisfy` P.con ConC{conC1 = P.eq 1, conC2 = P.eq 2}
        ConC{conC1 = 1, conC2 = 2} `shouldSatisfy` P.con ConC{conC2 = P.eq 2, conC1 = P.eq 1}
        ConC{conC2 = 2, conC1 = 1} `shouldSatisfy` P.con ConC{conC1 = P.eq 1, conC2 = P.eq 2}
        ConC{conC2 = 2, conC1 = 1} `shouldSatisfy` P.con ConC{conC2 = P.eq 2, conC1 = P.eq 1}
  
        -- missing records always match
        ConC{conC1 = 1, conC2 = 2} `shouldSatisfy` P.con ConC{conC1 = P.eq 1}
  
        -- check some failures
        ConC{conC1 = 1, conC2 = 2} `shouldNotSatisfy` P.con ConA
        ConC{conC1 = 1, conC2 = 2} `shouldNotSatisfy` P.con ConC{conC1 = P.eq 123}
        ConA `shouldNotSatisfy` P.con ConC{conC1 = P.eq 2}
  
        -- works for type with one constructor
        UserNoShow{name = "user1", age = 18} `shouldSatisfy` P.con UserNoShow{age = P.gt 10}
  
      it "matches snapshots" $ do
        (1 :: Int) `shouldSatisfy` P.matchesSnapshot
        "a \"quoted\" string" `shouldSatisfy` P.matchesSnapshot
        Text.pack "a \"quoted\" text" `shouldSatisfy` P.matchesSnapshot
  
      it "matches snapshots without Show instance" $
        UserNoShow{name = "user1", age = 18} `shouldSatisfy` P.matchesSnapshot
  
    prop "reverse does not drop elements" $ do
      input <-
        forAll $
          Gen.list (Range.linear 0 10) $
            Gen.string (Range.linear 0 100) Gen.unicode
      Prop.label $ show $ length input
      Prop.classify "even" $ even $ length input
      Prop.classify "odd" $ odd $ length input
      length (reverse input) `shouldBe` length input
  
    prop "read . show === id" $
      (read . show) P.=== id `shouldSatisfy` P.isoWith (Gen.int $ Range.linear 0 100)
  
    describe "fixtures example" $ do
      it "allows using fixtures inside fixtures" $ do
        FixtureD <- getFixture
        TraceFixture traceRef <- getFixture
        readIORef traceRef `shouldSatisfy` P.returns (P.eq ["A", "B", "C", "D"])
  
  data MyType
    = ConA
    | ConB Int Int
    | ConC {conC1 :: Int, conC2 :: Int}
  
  -- Do not add a Show instance
  data UserNoShow = UserNoShow
    { name :: String
    , age :: Int
    }
  
  -- | A helper for tracing fixtures
  newtype TraceFixture = TraceFixture (IORef [String])
  
  instance Fixture TraceFixture where
    fixtureAction = do
      traceRef <- newIORef []
      pure . noCleanup $ TraceFixture traceRef
  
  {-----
  Fixtures example:
  Create a diamond fixture:
    A
   / \
  B   C
   \ /
    D
  -----}
  
  data FixtureA = FixtureA
  instance Fixture FixtureA where
    fixtureAction = do
      TraceFixture traceRef <- getFixture
      modifyIORef' traceRef (<> ["A"])
      pure . noCleanup $ FixtureA
  
  data FixtureB = FixtureB
  instance Fixture FixtureB where
    fixtureAction = do
      FixtureA <- getFixture
      TraceFixture traceRef <- getFixture
      modifyIORef' traceRef (<> ["B"])
      pure . noCleanup $ FixtureB
  
  data FixtureC = FixtureC
  instance Fixture FixtureC where
    fixtureAction = do
      FixtureA <- getFixture
      TraceFixture traceRef <- getFixture
      modifyIORef' traceRef (<> ["C"])
      pure . noCleanup $ FixtureC
  
  data FixtureD = FixtureD
  instance Fixture FixtureD where
    fixtureAction = do
      FixtureB <- getFixture
      FixtureC <- getFixture
      TraceFixture traceRef <- getFixture
      modifyIORef' traceRef (<> ["D"])
      pure . noCleanup $ FixtureD
  