packages feed

gibberish-3.0.0.0: test/Data/Gibberish/Gen/TrigraphSpec.hs

{-# LANGUAGE OverloadedLists #-}

module Data.Gibberish.Gen.TrigraphSpec (spec) where

import Data.Gibberish.Errors (isTrigraphNotFound)
import Data.Gibberish.Gen.Trigraph
import Data.Gibberish.Types
import Data.Gibberish.Utils (qwertyKeys)
import Paths_gibberish (getDataFileName)
import Test.Gibberish.Gen qualified as Gen

import Control.Monad (void)
import Data.List qualified as List
import Data.Map qualified as Map
import Data.Set (Set ())
import Data.Set qualified as Set
import Data.Text (Text ())
import Data.Text qualified as Text
import Hedgehog
import Hedgehog.Gen qualified as Gen hiding (word)
import Hedgehog.Range qualified as Range
import System.FilePath ((</>))
import Test.Hspec
import Test.Hspec.Hedgehog

spec :: Spec
spec = do
  describe "genTrigraph" $ do
    it "length trigraph <= length word - 2" $ hedgehog $ do
      word <- forAll Gen.word
      let trigrams' = List.sort $ trigrams word

      cover 10 "with duplicates" $ hasDuplicates trigrams'
      cover 10 "no duplicates" $ not (hasDuplicates trigrams')

      assert $
        length (unTrigraph $ genTrigraph [word]) <= max 0 (Text.length word - 2)

    it "sum frequencies = length trigrams" $ hedgehog $ do
      word <- forAll Gen.word
      let trigrams' = List.sort $ trigrams word

      cover 10 "with duplicates" $ hasDuplicates trigrams'
      cover 10 "no duplicates" $ not (hasDuplicates trigrams')

      let totalTrigrams =
            sum
              . concatMap (Map.elems . unFrequencies)
              . Map.elems
              . unTrigraph
              . genTrigraph
              $ [word]
      length trigrams' === fromIntegral totalTrigrams

    it "allTrigrams trigraphs = nub trigrams" $ hedgehog $ do
      words' <- forAll $ Gen.list (Range.linear 0 10) Gen.word
      let trigraphs = genTrigraph words'
          trigrams' = map (List.sort . trigrams) words'

      cover 10 "with duplicates" $ List.any hasDuplicates trigrams'
      cover 10 "no duplicates" $ not (List.all hasDuplicates trigrams')

      concatNub trigrams' === List.sort (allTrigrams trigraphs)

  describe "loadTrigraph" $ do
    it "loads english" $
      void $
        loadTrigraph English

    it "loads spanish" $
      void $
        loadTrigraph Spanish

    it "loads custom" $ do
      tri <- getDataFileName ("data" </> "trigraphs" </> "wamerican.json")
      void $
        loadTrigraph (CustomTrigraph $ TrigraphConfig tri)

    it "handles load failure" $ do
      loadTrigraph (CustomTrigraph $ TrigraphConfig "doesnotexist.json")
        `shouldThrow` isTrigraphNotFound

    it "contains only qwerty keys" $ do
      -- concatMap of Sets
      let concatMap' f = foldr (\x xs -> f x `Set.union` xs) Set.empty
      -- Find all trigraphs
      trigraphs <- sequence [loadTrigraph English, loadTrigraph Spanish] :: IO [Trigraph]
      -- Neither trigraph should have non-qwertys
      concatMap' allChars trigraphs `shouldSatisfy` all (`elem` qwertyKeys)

trigrams :: Text -> [Trigram]
trigrams ts = case Text.take 3 ts of
  [a, b, c] -> Trigram a b c : trigrams (Text.tail ts)
  _ -> []

allTrigrams :: Trigraph -> [Trigram]
allTrigrams (Trigraph tris) =
  concatMap (uncurry mapFrequencies) (Map.toList tris)
  where
    mapFrequencies (Digram c1 c2) (Frequencies freqs) =
      map (\(Unigram c3) -> Trigram c1 c2 c3) $ Map.keys freqs

allChars :: Trigraph -> Set Char
allChars = Map.foldrWithKey foldTrigraph Set.empty . unTrigraph
  where
    foldTrigraph (Digram c1 c2) (Frequencies freqs) cs =
      Set.insert c1 $
        Set.insert c2 $
          foldFrequencies freqs `Set.union` cs

    foldFrequencies = Map.foldrWithKey (const . Set.insert . unUnigram) Set.empty

hasDuplicates :: Ord a => [a] -> Bool
hasDuplicates ls = ls /= List.nub ls

concatNub :: Ord a => [[a]] -> [a]
concatNub = List.nub . List.sort . List.concat