packages feed

Shpadoinkle-widgets-0.0.0.1: tests/Test.hs

{-# LANGUAGE AllowAmbiguousTypes  #-}
{-# LANGUAGE ConstraintKinds      #-}
{-# LANGUAGE DataKinds            #-}
{-# LANGUAGE DeriveGeneric        #-}
{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE KindSignatures       #-}
{-# LANGUAGE RankNTypes           #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE TypeApplications     #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}


module Main where


import           Data.Kind
import           Data.Set                          as Set
import           GHC.Generics

import           Test.Hspec
import           Test.QuickCheck


import           Shpadoinkle.Widgets.Form.Dropdown
import           Shpadoinkle.Widgets.Types


data Foo = Bar | Baz | Qux | Nerp
  deriving (Eq, Ord, Show, Enum, Bounded, Generic)


instance Semigroup Foo where (<>) = min
instance Monoid Foo where mempty = maxBound


instance Arbitrary Foo where
  arbitrary = arbitraryBoundedEnum

instance (Ord a, Arbitrary a, Arbitrary (Selected p a)) => Arbitrary (Choice p a) where
  arbitrary = Choice <$> arbitrary <*> arbitrary

instance (Ord a, Arbitrary a, Arbitrary (Selected p a), Arbitrary (Considered p a)) => Arbitrary (ConsideredChoice p a) where
  arbitrary = ConsideredChoice <$> arbitrary <*> arbitrary

instance (Ord a, Arbitrary a, Arbitrary (ConsideredChoice p a)) => Arbitrary (Dropdown p a) where
  arbitrary = Dropdown <$> arbitrary <*> arbitrary

instance Arbitrary Toggle where
  arbitrary = arbitraryBoundedEnum


type TestConstrant f p =
  ( Arbitrary (f p Foo)
  , Selection f p
  , Show (f p Foo)
  , Eq (f p Foo)
  )


type TestConstrants f =
  ( TestConstrant f 'One
  , TestConstrant f 'AtleastOne
  , TestConstrant f 'Many
  )


selectionProps :: forall (f :: Pick -> Type -> Type). TestConstrants f => Spec
selectionProps = do

  describe "selected is an option" $ do

    it "One"        . property $ \(c :: f 'One Foo) ->
      toSet (selected c) `isSubsetOf` toSet c
    it "AtleastOne" . property $ \(c :: f 'AtleastOne Foo) ->
      Set.singleton (selected c) `isSubsetOf` toSet c
    it "Many"       . property $ \(c :: f 'Many Foo) ->
      toSet (selected c) `isSubsetOf` toSet c


  describe "if we select something it's an option" $ do

    it "One'"        . property $ \(c :: f 'One Foo) x ->
      x `member` toSet (select' c x)
    it "AtleastOne'" . property $ \(c :: f 'AtleastOne Foo) x ->
      x `member` toSet (select' c x)
    it "Many'"       . property $ \(c :: f 'Many Foo) x ->
      x `member` toSet (select' c x)


  describe "selected and unselected are exclusive" $ do

    it "One"        . property $ \(c :: f 'One Foo) ->
      toSet (selected c) `disjoint` unselected c
    it "AtleastOne" . property $ \(c :: f 'AtleastOne Foo) ->
      Set.singleton (selected c) `disjoint` unselected c
    it "Many"       . property $ \(c :: f 'Many Foo) ->
      toSet (selected c) `disjoint` unselected c


  describe "idempotence select" $ do

    it "One"        . property $ \(c :: f 'One Foo) x ->
      select' (select' c x) x == select' c x
    it "AtleastOne" . property $ \(c :: f 'AtleastOne Foo) x ->
      select' (select' c x) x == select' c x
    it "Many"       . property $ \(c :: f 'Many Foo) x ->
      select' (select' c x) x == select' c x


  describe "select selected identity" $ do

    it "One"        . property $ \x ->
      selected (x `withOptions` Set.empty :: f 'One Foo) == x
    it "AtleastOne" . property $ \x ->
      selected (x `withOptions` Set.empty :: f 'AtleastOne Foo) == x
    it "Many"       . property $ \x ->
      selected (x `withOptions` Set.empty :: f 'Many Foo) == x


  describe "unselected withOptions identity" $ do

    it "One"        . property $ \x ->
      unselected (x `withOptions` Set.empty :: f 'One Foo) == mempty
    it "AtleastOne" . property $ \x ->
      unselected (x `withOptions` Set.empty :: f 'AtleastOne Foo) == mempty
    it "Many"       . property $ \x ->
      unselected (x `withOptions` Set.empty :: f 'Many Foo) == mempty


  describe "selected is not unselected" $ do

    it "One" . property $ \(c :: f 'One Foo) x ->
      not $ x `member` unselected (select' c x)
    it "AtleastOne" . property $ \(c :: f 'AtleastOne Foo) x ->
      not $ x `member` unselected (select' c x)
    it "Many" . property $ \(c :: f 'Many Foo) x ->
      not $ x `member` unselected (select' c x)


deselectionPropsOne :: forall (f :: Pick -> Type -> Type). TestConstrants f => Deselection f 'One => Spec
deselectionPropsOne = describe "One" $ do

    it "idempotence deselect" . property $ \(c :: f 'One Foo) ->
        deselect (deselect c) == deselect c

    it "deselect select selected identity" . property $ \(c :: f 'One Foo) x ->
        selected (select (deselect c) x) == x

    it "selected deselect annihliation" . property $ \(c :: f 'One Foo) ->
        selected (deselect c) == mempty

    it "deselect keeps" . property $ \(c :: f 'One Foo) x ->
        toSet x `isSubsetOf` toSet (deselect (select c x))

    it "unselected passes through deselect keeps" . property $ \(c :: f 'One Foo) x ->
        toSet x `isSubsetOf` unselected (deselect (select c x))

    it "deselect unselected is full set" . property $ \(c :: f 'One Foo) ->
        unselected (deselect c) == toSet c


deselectionPropsMany :: forall (f :: Pick -> Type -> Type). TestConstrants f => Deselection f 'Many => Spec
deselectionPropsMany = describe "Many" $ do

    it "idempotence deselect" . property $ \(c :: f 'Many Foo) ->
        deselect (deselect c) == deselect c

    it "deselect select selected identity" . property $ \(c :: f 'Many Foo) x ->
        selected (select (deselect c) x) == x

    it "selected deselect annihliation" . property $ \(c :: f 'Many Foo) ->
        selected (deselect c) == mempty

    it "deselect keeps" . property $ \(c :: f 'Many Foo) x ->
        toSet x `isSubsetOf` toSet (deselect (select c x))

    it "unselected passes through deselect keeps" . property $ \(c :: f 'Many Foo) x ->
        toSet x `isSubsetOf` unselected (deselect (select c x))

    it "deselect unselected is full set" . property $ \(c :: f 'Many Foo) ->
        unselected (deselect c) == toSet c


main :: IO ()
main = hspec $ do
  it "foooid" . property $ \(x :: Foo) y z ->
    x <> mempty == x && x == mempty <> x && (x <> y) <> z == x <> (y <> z)

  describe "Choice" $ do
    selectionProps       @Choice
    deselectionPropsOne  @Choice
    deselectionPropsMany @Choice


  describe "ConsideredChoice" $ do
    selectionProps       @ConsideredChoice
    deselectionPropsOne  @ConsideredChoice
    deselectionPropsMany @ConsideredChoice


  describe "Dropdown" $ do
    selectionProps      @Dropdown
    deselectionPropsOne @Dropdown