;; ========================================================================== ;; web/harness-reactive.sx — Signal and reactive testing extensions ;; ;; Extends spec/harness.sx with assertions for the reactive signal system. ;; Depends on spec/signals.sx (core reactive primitives). ;; No DOM dependency — works on any host. ;; ========================================================================== ;; -------------------------------------------------------------------------- ;; Signal assertions ;; -------------------------------------------------------------------------- ;; Assert a signal has a specific value (define assert-signal-value :effects [] (fn ((sig :as any) expected) (let ((actual (signal-value sig))) (assert= actual expected (str "Expected signal value " expected ", got " actual))))) ;; Assert a signal has subscribers (i.e., something is watching it) (define assert-signal-has-subscribers :effects [] (fn ((sig :as any)) (assert (> (len (signal-subscribers sig)) 0) "Expected signal to have subscribers"))) ;; Assert a signal has no subscribers (define assert-signal-no-subscribers :effects [] (fn ((sig :as any)) (assert (= (len (signal-subscribers sig)) 0) "Expected signal to have no subscribers"))) ;; Assert a signal has exactly N subscribers (define assert-signal-subscriber-count :effects [] (fn ((sig :as any) (n :as number)) (let ((actual (len (signal-subscribers sig)))) (assert= actual n (str "Expected " n " subscribers, got " actual))))) ;; -------------------------------------------------------------------------- ;; Signal simulation ;; -------------------------------------------------------------------------- ;; Set a signal value directly (like a user action would) (define simulate-signal-set! :effects [mutation] (fn ((sig :as any) value) (reset! sig value))) ;; Swap a signal value (like a button click handler would) (define simulate-signal-swap! :effects [mutation] (fn ((sig :as any) (f :as lambda) &rest args) (apply swap! (cons sig (cons f args))))) ;; -------------------------------------------------------------------------- ;; Computed assertions ;; -------------------------------------------------------------------------- ;; Assert a computed signal tracks the expected number of dependencies (define assert-computed-dep-count :effects [] (fn ((sig :as any) (n :as number)) (let ((actual (len (signal-deps sig)))) (assert= actual n (str "Expected " n " deps, got " actual))))) ;; Assert a computed signal depends on a specific signal (define assert-computed-depends-on :effects [] (fn ((computed-sig :as any) (dep-sig :as any)) (assert (contains? (signal-deps computed-sig) dep-sig) "Expected computed to depend on the given signal"))) ;; -------------------------------------------------------------------------- ;; Effect tracking ;; -------------------------------------------------------------------------- ;; Run a function and count how many times an effect fires (define count-effect-runs :effects [mutation] (fn ((thunk :as lambda)) (let ((count (signal 0))) (effect (fn () (deref count))) ;; subscribe to count changes (let ((run-count 0) (tracker (effect (fn () (set! run-count (+ run-count 1)) (cek-call thunk nil))))) run-count)))) ;; Create a signal + effect pair for testing, returns dict with :signal, :history (define make-test-signal :effects [mutation] (fn (initial-value) (let ((sig (signal initial-value)) (history (list))) (effect (fn () (append! history (deref sig)))) {:signal sig :history history}))) ;; -------------------------------------------------------------------------- ;; Batch assertions ;; -------------------------------------------------------------------------- ;; Assert that a batch of signal writes only triggers N subscriber notifications (define assert-batch-coalesces :effects [mutation] (fn ((thunk :as lambda) (expected-notify-count :as number)) (let ((notify-count 0) (sig (signal 0))) (effect (fn () (deref sig) (set! notify-count (+ notify-count 1)))) ;; Initial effect run counts as 1 (set! notify-count 0) (batch thunk) (assert= notify-count expected-notify-count (str "Expected " expected-notify-count " notifications, got " notify-count)))))