Selkie--Test--Store

NAME

Selkie::Test::Store - Conveniences for testing handlers and subscriptions

SYNOPSIS

use Test;
use Selkie::Test::Store;

my $store = mock-store(state => {
    user => { name => 'Alice', roles => <admin> },
    count => 0,
});

$store.register-handler('inc', -> $st, % {
    (db => { count => ($st.get-in('count') // 0) + 1 },);
});

dispatch-and-tick($store, 'inc');
dispatch-and-tick($store, 'inc');

is state-at($store, 'count'), 2, 'count incremented twice';
is state-at($store, 'user', 'name'), 'Alice', 'nested state accessible';

done-testing;

DESCRIPTION

Four helpers for the common test patterns against Selkie::Store:

  • mock-store — build a fresh store, optionally pre-populated with nested initial state

  • dispatch-and-tick — the dispatch + tick boilerplate in one call

  • state-at — splat-style read that's more readable than $store.get-in(|@path)

  • tick-until — tick repeatedly until a condition holds, for asynchronous completion flows

These aren't assertions themselves — use plain is, is-deeply, etc. around the returned values. That keeps the helpers composable and lets you use whatever Test:: style fits.

EXAMPLES

Handler testing without a live App

my $store = mock-store;
my $app-handlers = My::App::StoreHandlers.new(:$db);
$app-handlers.register($store);

dispatch-and-tick($store, 'app/init');
is state-at($store, 'active-tab'), 'servers', 'init picks a default tab';

dispatch-and-tick($store, 'tab/select', name => 'logs');
is state-at($store, 'active-tab'), 'logs', 'dispatch changed tab';

Waiting for an asynchronous completion

Handlers that hand work to another thread (a start block, an async effect, a database writer's .then) dispatch their completion event from that thread; it lands on a later tick. tick-until ticks and polls until the observable state change arrives:

dispatch-and-tick($store, 'thing/save-requested', thing => $thing);
ok tick-until($store, {
    !(state-at($store, 'ui', 'thing', 'saving') // False)
}), 'save completed asynchronously';

Subscribing a widget in a test

my $text = Selkie::Widget::Text.new(text => '-', sizing => Sizing.fixed(1));
my $store = mock-store;

$store.subscribe-with-callback(
    'mirror',
    -> $s { $s.get-in('count') // 0 },
    -> $n { $text.set-text("count: $n") },
    $text,
);

dispatch-and-tick($store, 'inc');   # (assuming handler registered elsewhere)
is $text.text, 'count: 1', 'subscription callback fired';

SEE ALSO

sub mock-store

sub mock-store(
    :%state
) returns Selkie::Store

Create a fresh store, optionally pre-populated with nested state. The :state hash is deep-merged into the store's db in one shot — nested hashes become nested paths. my $store = mock-store(state => { app => { count => 0, user => { name => 'Alice' } }, flag => True, });

sub dispatch-and-tick

sub dispatch-and-tick(
    Selkie::Store $store,
    Str:D $event,
    *%payload
) returns Mu

Dispatch an event and immediately tick the store, replacing the common two-liner in tests. dispatch-and-tick(store, 'user/set', name => 'Bob'); Equivalent to: event, |%payload); $store.tick;

sub state-at

sub state-at(
    Selkie::Store $store,
    *@path
) returns Mu

Splat-style deep read. Same semantics as $store.get-in(|@path) but easier on the eyes in tests: is state-at(store, 'counter'), 0, 'reset';

sub tick-until

sub tick-until(
    Selkie::Store $store,
    &done,
    Int :$tries = 80,
    Real :$delay = 0.025
) returns Bool

Tick the store until &done returns true or :tries runs out. For flows whose completion event arrives from another thread (a start block, an async effect, a DB writer's .then): each round ticks the store — picking up any cross-thread dispatches — checks the condition, then sleeps :delay seconds. Returns the final truth of &done, so a timeout reports as a false assertion at the call site rather than a throw: dispatch-and-tick(store, { !(state-at($store, 'ui', 'thing', 'saving') // False) }), 'save completed';

Selkie v0.10.0

High-level TUI framework built on Notcurses

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

Notcurses::Native:ver<0.4.0+>:auth<zef:apogee>

Test Dependencies

Provides

  • Selkie
  • Selkie::App
  • Selkie::App::Internal::Dispatch
  • Selkie::App::Internal::ErrorLog
  • Selkie::App::Internal::FocusTree
  • Selkie::App::Internal::HitTest
  • Selkie::App::Internal::IdleBudget
  • Selkie::App::Internal::OverlayTree
  • Selkie::App::Internal::RenderLoop
  • Selkie::App::Internal::ScreenModalLifecycle
  • Selkie::App::Internal::Terminal
  • Selkie::App::Internal::TerminalSequences
  • Selkie::Container
  • Selkie::EffectiveBounds
  • Selkie::Event
  • Selkie::Layout::Allocate
  • Selkie::Layout::HBox
  • Selkie::Layout::Split
  • Selkie::Layout::VBox
  • Selkie::Plot::Palette
  • Selkie::Plot::Scaler
  • Selkie::Plot::Ticks
  • Selkie::ScreenManager
  • Selkie::Sizing
  • Selkie::Store
  • Selkie::Style
  • Selkie::Test::Focus
  • Selkie::Test::Keys
  • Selkie::Test::Snapshot
  • Selkie::Test::Snapshot::Harness
  • Selkie::Test::Store
  • Selkie::Test::Supply
  • Selkie::Test::Tree
  • Selkie::Theme
  • Selkie::Trace
  • Selkie::Tree
  • Selkie::Widget
  • Selkie::Widget::Axis
  • Selkie::Widget::BarChart
  • Selkie::Widget::Border
  • Selkie::Widget::Button
  • Selkie::Widget::CardList
  • Selkie::Widget::Checkbox
  • Selkie::Widget::CommandPalette
  • Selkie::Widget::ConfirmModal
  • Selkie::Widget::FileBrowser
  • Selkie::Widget::FocusableByDefault
  • Selkie::Widget::Heatmap
  • Selkie::Widget::HelpOverlay
  • Selkie::Widget::Histogram
  • Selkie::Widget::Image
  • Selkie::Widget::Legend
  • Selkie::Widget::LineChart
  • Selkie::Widget::ListView
  • Selkie::Widget::Modal
  • Selkie::Widget::MultiLineInput
  • Selkie::Widget::PasswordStrength
  • Selkie::Widget::Plot
  • Selkie::Widget::ProgressBar
  • Selkie::Widget::RadioGroup
  • Selkie::Widget::RichText
  • Selkie::Widget::RichText::Span
  • Selkie::Widget::ScatterPlot
  • Selkie::Widget::ScrollView
  • Selkie::Widget::Select
  • Selkie::Widget::Sparkline
  • Selkie::Widget::Spinner
  • Selkie::Widget::TabBar
  • Selkie::Widget::Table
  • Selkie::Widget::Text
  • Selkie::Widget::TextInput
  • Selkie::Widget::TextInput::HighlightSpan
  • Selkie::Widget::TextStream
  • Selkie::Widget::Toast
  • Selkie::Widget::ViewportedCardList

Documentation

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.