packages feed

hstratus-notes-0.1.0.0: test/HStratus/Notes/Arbitraries.hs

{-# LANGUAGE NamedFieldPuns #-}
{-# OPTIONS_GHC -Wno-orphans #-}

{- |
Module      : HStratus.Notes.Arbitraries
Copyright   : (c) 2026 Tim Emiola
Maintainer  : Tim Emiola <adetokunbo@emio.la>
SPDX-License-Identifier: BSD-3-Clause

Orphan 'Arbitrary' instances for proto and domain types used in property tests.
Import this module to bring the instances into scope; nothing else is exported.
-}
module HStratus.Notes.Arbitraries () where

import qualified Data.Text as T
import Network.HStratus.Internal.Notes.Note
  ( NoteRun (..)
  , NoteStyle (..)
  , NoteText (..)
  )
import Network.HStratus.Internal.Notes.Proto (ProtoParagraphStyle (..))
import Test.QuickCheck


{- | Generates 'ProtoParagraphStyle' values that survive a proto3 encode/decode
roundtrip.  Two constraints apply:

  * 'ppsListStart' is 'Nothing' or 'Just n' with n >= 1, because proto3
    cannot distinguish an absent int32 from a zero int32 on the wire.

  * 'ppsChecked' is 'Just _' only when 'ppsStyleType' == 103 (checklist),
    mirroring what 'parseParagraphStyle' produces for real notes.
-}
instance Arbitrary ProtoParagraphStyle where
  arbitrary = do
    ppsStyleType <- elements [0, 1, 2, 4, 100, 101, 102, 103]
    ppsIndent <- choose (0, 4)
    ppsChecked <- case ppsStyleType of
      103 -> Just <$> arbitrary
      _ -> pure Nothing
    ppsListStart <- case ppsStyleType of
      102 -> oneof [pure Nothing, Just <$> choose (1, 9)]
      _ -> pure Nothing
    ppsBlockQuote <- case ppsStyleType of
      0 -> arbitrary
      _ -> pure False
    pure ProtoParagraphStyle{ppsStyleType, ppsIndent, ppsChecked, ppsListStart, ppsBlockQuote}


{- | Generates 'NoteStyle' values that are in the image of 'toNoteStyle'.
'StyleBody False' is excluded: 'toNoteStyle' maps both style_type=0 with
block_quote=False and style_type=0 with block_quote absent to 'StyleTitle',
never to 'StyleBody False'.
-}
instance Arbitrary NoteStyle where
  arbitrary =
    oneof
      [ pure StyleTitle
      , pure StyleHeading
      , pure StyleSubheading
      , pure StyleMonospaced
      , pure (StyleBody True)
      , StyleBullet <$> choose (0, 4)
      , StyleDash <$> choose (0, 4)
      , StyleNumbered <$> choose (0, 4) <*> oneof [pure Nothing, Just <$> choose (1, 9)]
      , StyleChecklist <$> choose (0, 4) <*> arbitrary
      ]


{- | Generates 'NoteRun' values.  'nrLength' is kept in [1, 20] to bound the
total text length generated by 'Arbitrary NoteText'.  'nrLink' is always
'Nothing' (deferred; encoding links requires additional TestHelper support).
-}
instance Arbitrary NoteRun where
  arbitrary =
    NoteRun
      <$> choose (1, 20)
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> pure Nothing -- nrAttachmentId (deferred)
      <*> pure Nothing -- nrLink (deferred)


{- | Generates 'NoteText' values whose 'ntText' length equals the sum of all
'nrLength' fields.  Runs are generated first; the text is filled with
lowercase ASCII to match the exact character count.
-}
instance Arbitrary NoteText where
  arbitrary = do
    runs <- listOf arbitrary
    let totalLen = fromIntegral (sum (map nrLength runs)) :: Int
    chars <- vectorOf totalLen (elements ['a' .. 'z'])
    pure NoteText{ntText = T.pack chars, ntRuns = runs}