packages feed

gi-gtk-declarative-0.6.3: test/GI/Gtk/Declarative/CustomWidgetTest.hs

{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE OverloadedLabels    #-}
{-# LANGUAGE OverloadedLists     #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE RecordWildCards     #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell     #-}

{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
module GI.Gtk.Declarative.CustomWidgetTest where

import           Control.Concurrent
import           Control.Concurrent.STM
import           Control.Exception.Safe
import           Control.Monad                  ( replicateM_ )
import           Control.Monad.IO.Class
import           Data.Function                  ( (&) )
import qualified Data.HashSet                  as HashSet
import qualified Data.Text                     as Text
import           Data.Vector                    ( Vector )
import qualified GI.GObject                    as GI
import qualified GI.Gtk                        as Gtk

import           Hedgehog
import qualified Hedgehog.Gen                  as Gen
import qualified Hedgehog.Range                as Range

import           GI.Gtk.Declarative
import           GI.Gtk.Declarative.EventSource
import           GI.Gtk.Declarative.State

import           GI.Gtk.Declarative.TestUtils

prop_sets_the_button_label = property $ do
  start       <- forAll (Gen.int (Range.linear 0 10))
  clicks      <- forAll (Gen.int (Range.linear 0 10))

  buttonLabel <- runUI . bracket (Gtk.new Gtk.Window []) #destroy $ \window ->
    do
      let markup = testWidget [] start
      first <- create markup
      btn   <- someStateWidget first >>= Gtk.unsafeCastTo Gtk.Button & liftIO
      #add window btn
      sub <- subscribe markup first (const (pure ()))
      Gtk.widgetShowAll window
      replicateM_ clicks (Gtk.buttonClicked btn)
      cancel sub
      Gtk.get btn #label

  let expectedLabel = Text.pack (show (start + clicks))
  expectedLabel === buttonLabel

prop_emits_correct_number_of_click_events = property $ do
  start  <- forAll (Gen.int (Range.linear 0 10))
  clicks <- forAll (Gen.int (Range.linear 0 10))

  values <- liftIO (newTBQueueIO (fromIntegral clicks))
  runUI . bracket (Gtk.new Gtk.Window []) #destroy $ \window -> do
    let markup = testWidget [] start
    first <- create markup
    btn   <- someStateWidget first >>= Gtk.unsafeCastTo Gtk.Button & liftIO
    #add window btn
    sub <- subscribe markup first (atomically . writeTBQueue values)
    Gtk.widgetShowAll window
    replicateM_ clicks (Gtk.buttonClicked btn)
    cancel sub

  let expectedValues = take clicks [succ start ..]
  actualValues <- liftIO (atomically (flushTBQueue values))
  expectedValues === actualValues

prop_sets_classes = property $ do
  let genClasses =
        Gen.list (Range.linear 0 5) (Gen.text (Range.linear 1 5) Gen.alphaNum)
  initialClasses                <- forAll genClasses
  finalClasses                  <- forAll genClasses

  (classesBefore, classesAfter) <-
    runUI . bracket (Gtk.new Gtk.Window []) #destroy $ \window -> do
      let markup1 = testWidget [classes initialClasses] 0
          markup2 = testWidget [classes finalClasses] 0
      first <- create markup1
      btn   <- liftIO (someStateWidget first >>= Gtk.unsafeCastTo Gtk.Button)
      #add window btn
      Gtk.widgetShowAll window
      sc           <- #getStyleContext btn
      beforeUpdate <- #listClasses sc
      _second      <- patch' first markup1 markup2
      afterUpdate  <- #listClasses sc
      pure (beforeUpdate, afterUpdate)

  HashSet.fromList ("text-button" : initialClasses)
    === HashSet.fromList classesBefore
  HashSet.fromList ("text-button" : finalClasses)
    === HashSet.fromList classesAfter

-- * Test widget and helpers

testWidget :: Vector (Attribute Gtk.Button Int) -> Int -> Widget Int
testWidget customAttributes customParams = Widget (CustomWidget { .. })
 where
  customWidget = Gtk.Button
  customCreate start = do
    clicks <- newMVar start
    btn    <- Gtk.new Gtk.Button [#label Gtk.:= Text.pack (show start)]
    return (btn, clicks)

  customPatch :: Int -> Int -> MVar Int -> CustomPatch Gtk.Button (MVar Int)
  customPatch _ new clicks = CustomModify $ \btn -> do
    -- putMVar clicks new
    Gtk.set btn [#label Gtk.:= Text.pack (show new)]
    return clicks

  customSubscribe
    :: Int -> MVar Int -> Gtk.Button -> (Int -> IO ()) -> IO Subscription
  customSubscribe _params clicks btn cb = do
    h <- Gtk.on btn #clicked $ do
      current <- modifyMVar clicks $ \x -> pure (succ x, succ x)
      cb current
      Gtk.set btn [#label Gtk.:= Text.pack (show current)]
    return (fromCancellation (GI.signalHandlerDisconnect btn h))

-- * Test collection

tests :: IO Bool
tests = checkParallel $$(discover)