packages feed

hq-json-0.1.1: test/HQ/OpticSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}

module HQ.OpticSpec (spec) where

import Data.Aeson (Value (..))
import HQ.Optic
import HQ.Optic.AST (inferFocusType, inferOpticType)
import HQ.Optic.OpticType (OpticType (..))
import HQ.Optic.Parser (parseOptic)
import HQ.Transformation hiding (Compose)
import HQ.Transformation.TransformationType (ValueType (..))
import Relude hiding (Compose, filter, id, not)
import Test.Syd

-- | The cardinality of an optic, inferred directly.
opticTypeOf :: Optic -> OpticType
opticTypeOf = inferOpticType

-- | The focused value type of an optic, inferred directly.
opticFocusOf :: Optic -> ValueType
opticFocusOf = inferFocusType

-- | Parse 'show' output back (replaces the former 'Read Optic' orphan,
-- which lived in the library only for this check).
readOptic :: String -> Maybe Optic
readOptic str = case parseOptic (fromString str) of
  Left _ -> Nothing
  Right v -> Just v

spec :: Spec
spec = describe "HQ.Optic" $ do
  it "show and read are inverses" $ do
    -- NOTE: 'field' is excluded: its 'Show' quotes the name (@"a")
    -- while the parser accepts bare identifiers (@a), so fields never
    -- round-tripped (pre-existing mismatch, unrelated to this change).
    let optics =
          [ Id,
            Each,
            Keys,
            Values,
            Ix 0,
            Ix 1,
            Ix 3,
            Prism PString,
            Compose Each Keys,
            Compose Keys (Prism PString),
            Compose Values Keys,
            Filter Each (Equal (Number 1)),
            Filter (Ix 0) Not,
            Filter (Compose Each (Ix 0)) (Equal (Number 1))
          ]
    forM_ optics $ \optic -> readOptic (show optic) `shouldBe` Just optic

  it "ix equality compares indices" $ do
    Ix 0 `shouldBe` Ix 0
    (Ix 0 == Ix 1) `shouldBe` False

  describe "inferOpticType classifications" $ do
    it "classifies optics" $ forM_ opticTypeCases $ \(optic, expected) ->
      opticTypeOf optic `shouldBe` expected

  describe "appendOptic monoid" $ do
    it "combines optics" $ forM_ appendCases $ \(l, r, expected) ->
      appendOptic l r `shouldBe` expected

    it "satisfies the monoid identity laws" $ do
      (mempty :: Optic) `shouldBe` Id
      (Each <> Field "a") `shouldBe` Compose Each (Field "a")
      (Field "a" <> Id) `shouldBe` Field "a"
      (Id <> Field "a") `shouldBe` Field "a"

    it "agrees on cardinality across reassociation" $ do
      let l = Field "a"
          c = Each
          r = Ix 0
      opticTypeOf ((l <> c) <> r) `shouldBe` opticTypeOf (l <> (c <> r))

  describe "inferFocusType classifications" $ do
    it "infers focus types" $ forM_ focusCases $ \(optic, expected) ->
      opticFocusOf optic `shouldBe` expected

opticTypeCases :: [(Optic, OpticType)]
opticTypeCases =
  [ (Id, OpticLens),
    (Field "a", OpticAffineTraversal),
    (Ix 0, OpticAffineTraversal),
    (Prism PString, OpticPrism),
    (Prism PNull, OpticPrism),
    (PrismJust, OpticAffineTraversal),
    (Each, OpticTraversal),
    (Keys, OpticTraversal),
    (Values, OpticTraversal),
    (Compose (Field "a") Each, OpticTraversal),
    (Compose (Field "a") (Ix 0), OpticAffineTraversal),
    (Compose (Prism PString) PrismJust, OpticAffineTraversal),
    (Filter (Field "a") (Equal (Number 1)), OpticAffineTraversal),
    (Compose Each (Filter (Field "a") (Equal (Number 1))), OpticTraversal)
  ]

appendCases :: [(Optic, Optic, Optic)]
appendCases =
  [ (Id, Field "a", Field "a"),
    (Field "a", Id, Field "a"),
    (Id, Id, Id),
    (Each, Field "a", Compose Each (Field "a"))
  ]

focusCases :: [(Optic, ValueType)]
focusCases =
  [ (Field "a", ValueAny),
    (Each, ValueAny),
    (Values, ValueAny),
    (Id, ValueAny),
    (Ix 0, ValueAny),
    (PrismJust, ValueAny),
    (Filter (Field "a") (Equal (Number 1)), ValueAny),
    (Prism PString, ValueString),
    (Prism PNumber, ValueNumber),
    (Prism PBool, ValueBool),
    (Prism PNull, ValueNull),
    (Prism PArray, ValueArray),
    (Prism PObject, ValueObject),
    (Keys, ValueString),
    (Compose Each (Prism PNumber), ValueNumber),
    (Compose (Field "a") (Field "b"), ValueAny)
  ]