Add test harness extensions: reactive signals + web/DOM mocking
web/harness-reactive.sx — signal testing (no DOM dependency): assert-signal-value, assert-signal-has-subscribers, assert-signal-subscriber-count, assert-computed-dep-count, assert-computed-depends-on, simulate-signal-set!/swap!, make-test-signal (signal + history tracking), assert-batch-coalesces web/harness-web.sx — web platform testing (mock DOM, no browser): mock-element, mock-set-text!, mock-append-child!, mock-set-attr!, mock-add-listener!, simulate-click, simulate-input, simulate-event, assert-text, assert-attr, assert-class, assert-no-class, assert-child-count, assert-event-fired, assert-no-event, event-fire-count, make-web-harness Both extend spec/harness.sx. The reactive harness uses spec/signals.sx directly — works on any host. The web harness provides lightweight DOM stubs that record operations for assertion, no real browser needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
115
web/harness-reactive.sx
Normal file
115
web/harness-reactive.sx
Normal file
@@ -0,0 +1,115 @@
|
||||
;; ==========================================================================
|
||||
;; 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)))))
|
||||
Reference in New Issue
Block a user