Module: Phlex::Reactive::TestHelpers::System

Defined in:
lib/phlex/reactive/test_helpers/system.rb

Defined Under Namespace

Classes: ReactiveValueMatcher

Constant Summary collapse

ACTIVE_MARKER =

The marker the client sets while the reactive layer is busy. Kept in lockstep with reactive_controller.js's ACTIVE_ATTR — the ONE selector every wait keys off. A [data-reactive-active] presence check is the system twin of wait_for_turbo watching the Turbo progress bar.

"data-reactive-active"

Instance Method Summary collapse

Instance Method Details

#have_reactive_text(id, value) ⇒ Object

Assert (waiting) that the node with DOM id id has TEXT value, re-resolving by id each poll — the mirror/recap twin of have_reactive_value for a text sink (a reactive_compute text:/mirror target, a recap node) rather than a form field.

expect(page).to have_reactive_text("recap", "6 items")


92
93
94
# File 'lib/phlex/reactive/test_helpers/system.rb', line 92

def have_reactive_text(id, value, **)
  have_css("##{id}", text: value, **)
end

#have_reactive_value(id, value, timeout: nil, wait: nil) ⇒ Object

Assert (waiting) that the field with DOM id id has value value, RE-RESOLVING the field by its id on every poll and reading its live .value PROPERTY (issue #204) — NOT the value attribute. A reactive_compute reducer paints a computed output with el.value = … (the property); for a DISABLED / read-only output the value attribute never reflects that, so an attribute-based matcher reads "" and fails. Reading the property covers enabled AND disabled/computed fields — the exact case this matcher was built for — and keeps the morph-immunity (each poll re-finds the node by id, so a re-seed/morph that replaces the input can't surface a stale node or a transient blank).

expect(page).to have_reactive_value("total", "6")

timeout: (or wait:) overrides Capybara's default max wait. The have_ prefix is Capybara-matcher convention (have_field/have_css), NOT a predicate — hence the PredicatePrefix disable, mirroring matchers.rb. rubocop:disable Naming/PredicatePrefix



82
83
84
# File 'lib/phlex/reactive/test_helpers/system.rb', line 82

def have_reactive_value(id, value, timeout: nil, wait: nil)
  ReactiveValueMatcher.new(id, value, wait: timeout || wait)
end

#wait_for_reactive(timeout: nil) ⇒ Object

Block until the reactive layer is IDLE — every dispatch round trip and deferred render has settled and the marker is gone. The system-test twin of wait_for_turbo (which watches the Turbo progress bar, NOT a reactive morph/seed, so it can't cover this).

Implemented as a Capybara WAITING assertion (have_no_css on the document element with the default max wait), so it re-checks the live DOM each poll and raises a readable Capybara::ElementNotFound-style error if the layer never settles inside timeout — never a bare sleep, never a stale read.

timeout: overrides Capybara.default_max_wait_time for a slow operation (a deferred render behind a real job). Returns nil; call it as a barrier BEFORE asserting a settled value if you are not already using one of the waiting matchers below.



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/phlex/reactive/test_helpers/system.rb', line 53

def wait_for_reactive(timeout: nil)
  # Scope the check to <html> via the :xpath "/html" so the marker is read
  # on the document element the client writes it to — not a descendant.
  # assert_no_selector WAITS (retries) until the marker clears or the wait
  # budget elapses; a persistent marker fails LOUDLY with Capybara's own
  # timeout error rather than a silent pass. Called on `page` (the current
  # session) so it works regardless of whether the example group mixed in
  # Capybara::DSL.
  page.assert_no_selector(:xpath, "/html[@#{ACTIVE_MARKER}]", **wait_option(timeout))
  nil
end