nano-ui-0.1.0.0: test/integration/Cases/Demo.hs
module Cases.Demo
( runControlsTabHeightTest
, runBoundedRadioTest
, runColorPickerCommitTest
, runColorPickerChangeOnceTest
, runColorPickerBarKeysTest
, runColorPickerRgbaTest
, runColorPickerEditTest
, runColorPickerDragAfterFieldTest
) where
import Control.Monad (void)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.Text qualified as T
import NanoUI
import NanoUI.Testing
import NanoUI.Testing.Assert (assert, assertEq, withInput)
import NanoUI.Testing.Harness (held, keyInp, pressAt, releaseAt, runClick, spanCenter, tabInp, warmup2, withInputOff)
data DemoTab
= Controls
| List
| Diagnostics
deriving (Bounded, Enum, Eq, Ord, Read, Show)
data DemoTheme
= Light
| Dark
| System
deriving (Bounded, Enum, Eq, Ord, Read, Show)
newtype OffsetChoice = OffsetChoice Int
deriving (Eq, Show)
instance Bounded OffsetChoice where
minBound = OffsetChoice 10
maxBound = OffsetChoice 12
instance Enum OffsetChoice where
fromEnum (OffsetChoice n) = n
toEnum = OffsetChoice
runBoundedRadioTest :: Context -> IORef Int -> IO ()
runBoundedRadioTest ctx failed = do
let inp = withInputOff 300 160
ui = boundedRadio' (T.pack . show)
(_, initial) <- warmup2 ctx inp (ui (OffsetChoice 11))
assertEq failed initial (OffsetChoice 11)
spans <- collectTextSpans ctx
case [r | (r, txt, _, _, _) <- spans, "OffsetChoice 12" `T.isInfixOf` txt] of
r : _ -> do
(_, selected) <- runClick ctx inp (ui (OffsetChoice 11)) (spanCenter r)
assertEq failed selected (OffsetChoice 12)
((_, retained), _, _, _) <- runFrame ctx inp (ui selected)
assertEq failed retained selected
((_, reset), _, _, _) <- runFrame ctx inp (ui (OffsetChoice 10))
assertEq failed reset (OffsetChoice 10)
[] -> assert failed False
runControlsTabHeightTest :: Context -> IORef Int -> IO ()
runControlsTabHeightTest ctx failed = do
let
inp0 =
withInputOff 1280 800
controlsBody dumpRef = do
heading "Controls"
(cb, _) <- checkbox' "Feature" False
_ <- slider 0 100 50
_ <- select ["Low", "Medium", "High"] 1
(cp, _) <- colorPicker' (colorRGBA 204 102 102 255)
_ <- boundedRadio (T.pack . show) Dark
(ti, _) <- textInput' ""
separator
uiIO $ writeIORef dumpRef (Just (cb, cp, ti))
demoPage dumpRef =
scrollWith (tight . grow) $
columnWith (padAll 8 . gap 8 . fillW) $
rowWith (tight . gap 8 . fillW) $ do
columnWith (tight . gap 8 . fillW) $ do
card $ do
heading "State"
kv "Feature" "off"
kv "Volume" "50"
kv "Quality" "Medium"
kv "Theme" (T.pack (show Dark))
kv "Name" "-"
kv "Clicked" "-"
card $ do
heading "Gallery"
mapM_ (\i -> void (label (T.pack ("thumb line " <> show (i :: Int))))) [1 .. 8]
card $ do
(demoTab, setDemoTab) <- useEnum Controls
setDemoTab
=<< tabs
demoTab
[ tab t (T.pack (show t)) $ case t of
Controls -> controlsBody dumpRef
List -> heading "Tree"
Diagnostics -> heading "Diagnostics"
| t <- [minBound ..]
]
-- Heights of the checkbox, colour picker and text input, in that order.
heights c dumpRef = do
m <- readIORef dumpRef
case m of
Just (cb, cp, ti) -> mapM (fmap (fmap rectH) . getPrevRect c . respId) [cb, cp, ti]
Nothing -> pure [Nothing, Nothing, Nothing]
spanOf lbls spans =
let
match txt =
any
(\lbl -> txt == lbl || T.drop 1 txt == lbl || T.isSuffixOf lbl txt)
lbls
ys =
[ (y, y + h)
| (Rect _ y _ h, txt, _, _, _) <- spans
, match txt
]
in
case ys of
[] -> 0
_ -> maximum (map snd ys) - minimum (map fst ys)
dumpLone <- newIORef Nothing
ctxLone <- newPixelContext
_ <- runFrame ctxLone inp0 (columnWith (tight . fillW) (controlsBody dumpLone))
lone <- heights ctxLone dumpLone
dumpPage <- newIORef Nothing
let page = demoPage dumpPage
_ <- runFrame ctx inp0 page
_ <- runFrame ctx inp0 page
page0 <- heights ctx dumpPage
spans0 <- collectTextSpans ctx
dumped <- readIORef dumpPage
cbRect <- maybe (pure Nothing) (\(cb, _, _) -> getPrevRect ctx (respId cb)) dumped
let hover = maybe inp0 (\r -> inp0 {inputMousePos = spanCenter r}) cbRect
_ <- runFrame ctx hover page
pageHover <- heights ctx dumpPage
spansH <- collectTextSpans ctx
let
left0 = spanOf ["State", "Clicked", "Gallery"] spans0
body0 = spanOf ["Controls"] spans0
leftH = spanOf ["State", "Clicked", "Gallery"] spansH
bodyH = spanOf ["Controls"] spansH
tooTall pageH loneH = case (pageH, loneH) of
(Just p, Just l) -> l >= 8 && p > l * 1.35
_ -> True
jumped a b = case (a, b) of
(Just x, Just y) -> abs (x - y) > 1
_ -> True
assert failed (not (or (zipWith tooTall page0 lone)))
assert failed (not (or (zipWith tooTall pageHover lone)))
assert failed (not (or (zipWith jumped page0 pageHover)))
-- Body must not fill the wrap-line height of the left column.
assert failed (not (left0 > 80 && body0 > left0 * 0.92))
assert failed (not (leftH > 80 && bodyH > leftH * 0.92))
-- A press on the SV field previews without committing, a held press keeps
-- sampling it without blanking the field or resetting it to white, and the
-- release commits.
runColorPickerCommitTest :: Context -> IORef Int -> IO ()
runColorPickerCommitTest ctx failed = do
let initial = colorRGBA 204 102 102 255
colorRef <- newIORef initial
let inp0 = withInput 400 420
packed c = colorToWord32 c
ui = held colorRef colorPicker'
(resp, _) <- warmup2 ctx inp0 ui
let wid = respId resp
sv = colorPickerSvSquare (respRect resp)
pt = V2 (rectX sv + rectW sv * 0.9) (rectY sv + 2)
press = pressAt inp0 pt
release = releaseAt press
_ <- runFrame ctx press ui
storeDrag <- getStore ctx
assertEq failed (packed (widgetStoreBaseColor storeDrag wid initial)) (packed initial)
assert failed (packed (widgetStoreColor storeDrag wid initial) /= packed initial)
_ <- runFrame ctx press {inputMousePressed = False} ui
storeHold <- getStore ctx
assertEq
failed
(packed (widgetStoreColor storeHold wid initial))
(packed (widgetStoreColor storeDrag wid initial))
_ <- runFrame ctx release ui
storeDone <- getStore ctx
assertEq
failed
(packed (widgetStoreBaseColor storeDone wid initial))
(packed (widgetStoreColor storeDone wid initial))
runColorPickerRgbaTest :: Context -> IORef Int -> IO ()
runColorPickerRgbaTest ctx failed = do
let inp0 = withInputOff 400 460
ui = void (colorPickerRGBA (colorRGBA 204 102 102 128))
_ <- runFrame ctx inp0 ui
_ <- runFrame ctx inp0 ui
spans <- collectTextSpans ctx
let has needle = any (\(_, t, _, _, _) -> needle `T.isInfixOf` t) spans
assert failed (has "#cc666680")
assert failed (has "128")
assert failed (has "Current")
assert failed (has "New")
-- Typing in a channel field must recolour on the same frame (live edits). The
-- fields are numeric: Up steps the focused one, and letters are dropped.
runColorPickerEditTest :: Context -> IORef Int -> IO ()
runColorPickerEditTest ctx failed = do
let inp0 = withInput 400 460
initial = colorRGBA 204 102 102 255
ui = colorPicker' initial
_ <- warmup2 ctx inp0 ui
-- Tab past the field and the hue bar to the R field.
_ <- runFrame ctx (tabInp inp0) ui
_ <- runFrame ctx (tabInp inp0) ui
_ <- runFrame ctx (tabInp inp0) ui
_ <- runFrame ctx (inp0 {inputKeys = inputKeysFromList [KeyBackspace, KeyBackspace, KeyBackspace]}) ui
((_, col), _, _, _) <- runFrame ctx (inp0 {inputChars = "10"}) ui
assertEq failed (colorR col) 10
assertEq failed (colorG col) 102
((_, stepped), _, _, _) <- runFrame ctx (keyInp KeyUp inp0) ui
assertEq failed (colorR stepped) 11
((_, lettered), _, _, _) <- runFrame ctx (inp0 {inputChars = "x"}) ui
assertEq failed (colorR lettered) 11
-- A channel field that had focus must not pull the colour back while the
-- canvas is dragged: pressing the canvas takes focus away from the field.
runColorPickerDragAfterFieldTest :: Context -> IORef Int -> IO ()
runColorPickerDragAfterFieldTest ctx failed = do
let initial = colorRGBA 204 102 102 255
colorRef <- newIORef initial
let inp0 = withInput 400 460
ui = held colorRef colorPicker'
tabKey = tabInp inp0
(resp, _) <- warmup2 ctx inp0 ui
-- Tab past the field and the hue bar to the R field.
_ <- runFrame ctx tabKey ui
_ <- runFrame ctx tabKey ui
_ <- runFrame ctx tabKey ui
let sv = colorPickerSvSquare (respRect resp)
press = pressAt inp0 (V2 (rectX sv + 2) (rectY sv + 2))
drag =
press
{ inputMousePressed = False
, inputMousePos = V2 (rectX sv + rectW sv * 0.9) (rectY sv + rectH sv * 0.9)
}
_ <- runFrame ctx press ui
_ <- runFrame ctx drag ui
((_, col), _, _, _) <- runFrame ctx drag ui
-- Low value keeps every channel dark; a field still writing R would leave
-- it at 204.
assert failed (colorR col < 60)
-- respChanged fires on the frame the colour moves and not on later frames
-- (regression: it compared the colour against the initial one). A key step
-- commits at once: the base colour follows the live one.
runColorPickerChangeOnceTest :: Context -> IORef Int -> IO ()
runColorPickerChangeOnceTest ctx failed = do
let initial = colorRGBA 204 102 102 255
colorRef <- newIORef initial
let inp0 = withInput 400 420
ui = held colorRef colorPicker'
changed inp = (\((resp, _), _, _, _) -> respChanged resp) <$> runFrame ctx inp ui
(resp, _) <- warmup2 ctx inp0 ui
_ <- runFrame ctx (tabInp inp0) ui
moved <- changed (keyInp KeyRight inp0)
assert failed moved
store <- getStore ctx
let wid = respId resp
base = colorToWord32 (widgetStoreBaseColor store wid initial)
neu = colorToWord32 (widgetStoreColor store wid initial)
assert failed (neu /= colorToWord32 initial)
assertEq failed base neu
idle <- mapM changed [inp0, inp0]
assertEq failed idle [False, False]
-- The hue and alpha bars are focus stops after the field. An arrow moves a
-- bar's handle the way it is drawn (down raises the hue, up lowers the alpha),
-- Shift steps ten times as far, and Home / End jump to the bar's ends.
runColorPickerBarKeysTest :: Context -> IORef Int -> IO ()
runColorPickerBarKeysTest ctx failed = do
let initial = colorRGBA 204 102 102 200
colorRef <- newIORef initial
let inp0 = withInput 440 460
ui = held colorRef colorPickerRGBA'
frame inp = (\((_, c), _, _, _) -> c) <$> runFrame ctx inp ui
key k = inp0 {inputKeys = inputKeysFromList [k]}
_ <- warmup2 ctx inp0 ui
_ <- frame (key KeyTab)
_ <- frame (key KeyTab)
shifted <- frame ((key KeyDown) {inputModifiers = Modifiers True False False})
assert failed (colorG shifted > colorG initial + 10)
home <- frame (key KeyHome)
assert failed (colorG home <= colorG initial + 1)
_ <- frame (key KeyTab)
opaque <- frame (key KeyEnd)
assertEq failed (colorA opaque) 255
lowered <- frame (key KeyUp)
assertEq failed (colorA lowered) 254