Module: Plushie::Test::Helpers

Included in:
Case
Defined in:
lib/plushie/test/helpers.rb,
lib/plushie/test/helpers.rb

Overview

Test helper methods for interacting with a Plushie test session.

Include this module in your test class to get click, find!, assert_text, and other helpers. The session is stored in Thread.current.

Examples:

With Minitest

class CounterTest < Plushie::Test::Case
  app Counter
  def test_increment
    click("#increment")
    assert_text "#count", "Count: 1"
  end
end

Constant Summary collapse

PLACEHOLDER_A11Y_WIDGETS =
%w[text_input text_editor combo_box pick_list].freeze
ALT_A11Y_WIDGETS =
%w[image svg qr_code].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.infer_a11y(type, props) ⇒ Object



355
356
357
358
359
360
361
362
363
364
# File 'lib/plushie/test/helpers.rb', line 355

def self.infer_a11y(type, props)
  if PLACEHOLDER_A11Y_WIDGETS.include?(type)
    ph = props[:placeholder] || props["placeholder"]
    return {description: ph} if ph.is_a?(String) && !ph.empty?
  elsif ALT_A11Y_WIDGETS.include?(type)
    alt = props[:alt] || props["alt"]
    return {label: alt} if alt.is_a?(String) && !alt.empty?
  end
  {}
end

.resolve_a11y_for_element(element) ⇒ Hash

Apply widget-sdk-equivalent a11y inference on top of the normalized a11y prop. Kept aligned with the Rust SDK's resolve_a11y_for_node so cross-SDK parity holds.

Parameters:

  • element (Hash)

    renderer node (string or symbol keyed)

Returns:

  • (Hash)

    resolved a11y map with symbol keys



347
348
349
350
351
352
353
# File 'lib/plushie/test/helpers.rb', line 347

def self.resolve_a11y_for_element(element)
  type = (element[:type] || element["type"]).to_s
  props = element[:props] || element["props"] || {}
  explicit = symbolize_keys(props[:a11y] || props["a11y"] || {})
  inferred = infer_a11y(type, props)
  inferred.merge(explicit)
end

.symbolize_keys(map) ⇒ Object



366
367
368
369
370
371
# File 'lib/plushie/test/helpers.rb', line 366

def self.symbolize_keys(map)
  return {} unless map.is_a?(Hash)
  out = {}
  map.each { |k, v| out[k.is_a?(String) ? k.to_sym : k] = v }
  out
end

Instance Method Details

#advance_frame(timestamp) ⇒ Object

Advance the renderer's animation clock to the given timestamp. Causes the renderer to evaluate all active animations at that point in time, potentially triggering transition_complete events.

Parameters:

  • timestamp (Integer)

    animation clock time in milliseconds



209
210
211
# File 'lib/plushie/test/helpers.rb', line 209

def advance_frame(timestamp)
  session.command(Command.advance_frame(timestamp))
end

#assert_a11y(selector, expected) ⇒ Object

Assert that a widget's resolved a11y matches expected values. Reads through #resolved_a11y so inferred defaults compose with the author's explicit overrides.

Parameters:

  • selector (String)
  • expected (Hash)

    expected key-value pairs



276
277
278
279
280
281
282
283
# File 'lib/plushie/test/helpers.rb', line 276

def assert_a11y(selector, expected)
  a11y = resolved_a11y(selector)
  expected.each do |key, value|
    actual = a11y[key] || a11y[key.to_s]
    plushie_assert_equal value, actual,
      "a11y #{key.inspect} mismatch for #{selector}\nFull a11y: #{a11y.inspect}"
  end
end

#assert_exists(selector) ⇒ Object

Assert that a widget exists.

Parameters:

  • selector (String)


236
237
238
239
# File 'lib/plushie/test/helpers.rb', line 236

def assert_exists(selector)
  result = find(selector)
  plushie_assert result, "Expected widget #{selector} to exist, but it was not found"
end

#assert_model(expected) ⇒ Object

Assert model equals expected value.

Parameters:

  • expected (Object)


250
251
252
# File 'lib/plushie/test/helpers.rb', line 250

def assert_model(expected)
  plushie_assert_equal expected, model
end

#assert_no_diagnosticsObject

Assert that no diagnostics have been emitted by the renderer. Clears the diagnostic list after checking.

Raises:

  • (Minitest::Assertion)

    if diagnostics are pending



161
162
163
164
165
166
167
# File 'lib/plushie/test/helpers.rb', line 161

def assert_no_diagnostics
  diagnostics = session.get_diagnostics
  return if diagnostics.empty?

  details = diagnostics.map { |d| "  - #{d.diagnostic.inspect}" }.join("\n")
  plushie_flunk "Expected no diagnostics, but found:\n#{details}"
end

#assert_not_exists(selector) ⇒ Object

Assert that a widget does NOT exist.

Parameters:

  • selector (String)


243
244
245
246
# File 'lib/plushie/test/helpers.rb', line 243

def assert_not_exists(selector)
  result = find(selector)
  plushie_assert_nil result, "Expected widget #{selector} not to exist, but it was found"
end

#assert_text(selector, expected) ⇒ Object

Assert that a widget contains the expected text.

Parameters:

  • selector (String)
  • expected (String)


227
228
229
230
231
232
# File 'lib/plushie/test/helpers.rb', line 227

def assert_text(selector, expected)
  element = find!(selector)
  actual = text(element)
  plushie_assert_equal expected, actual,
    "Expected text #{expected.inspect} for #{selector}, got #{actual.inspect}"
end

#await_async(tag, timeout = 5000) ⇒ :ok

Wait for a tagged async task to complete. Async commands run synchronously on the mock backend, so this returns immediately after warning that there is nothing to wait for. Other test backends also currently complete async work before this method is needed, but do not warn.

Parameters:

  • tag (Symbol)

    the async command tag

  • timeout (Integer) (defaults to: 5000)

    max wait in milliseconds (unused)

Returns:

  • (:ok)


132
133
134
135
136
137
138
# File 'lib/plushie/test/helpers.rb', line 132

def await_async(tag, timeout = 5000)
  if Plushie::Test.respond_to?(:backend) && Plushie::Test.backend == :mock
    warn "Plushie::Test#await_async is a no-op on the mock backend; async commands run synchronously."
  end

  :ok
end

#click(selector) ⇒ Object

Click a button widget.

Parameters:

  • selector (String)

    "#id" or "text content"



32
# File 'lib/plushie/test/helpers.rb', line 32

def click(selector) = session.click(selector)

#click_element(canvas_id, element_id) ⇒ Object

Click a canvas element by injecting a synthetic canvas_element_click event.

Parameters:

  • canvas_id (String)

    the canvas widget ID (e.g. "#chart")

  • element_id (String)

    the element ID within the canvas



77
# File 'lib/plushie/test/helpers.rb', line 77

def click_element(canvas_id, element_id) = session.interact("canvas_element_click", canvas_id, element_id: element_id)

#find(selector) ⇒ Hash?

Find a widget by selector. Returns the node hash or nil.

Parameters:

  • selector (String)

Returns:

  • (Hash, nil)


92
# File 'lib/plushie/test/helpers.rb', line 92

def find(selector) = session.find(selector)

#find!(selector) ⇒ Hash

Find a widget by selector. Raises if not found.

Parameters:

  • selector (String)

Returns:

  • (Hash)


97
# File 'lib/plushie/test/helpers.rb', line 97

def find!(selector) = session.find!(selector)

#find_by_label(label) ⇒ Hash?

Find a widget by accessibility label.

Parameters:

  • label (String)

Returns:

  • (Hash, nil)


179
180
181
# File 'lib/plushie/test/helpers.rb', line 179

def find_by_label(label)
  session.find({by: "label", value: label})
end

#find_by_role(role) ⇒ Hash?

Find a widget by accessibility role.

Parameters:

  • role (Symbol, String)

    e.g. :button, "textbox"

Returns:

  • (Hash, nil)


172
173
174
# File 'lib/plushie/test/helpers.rb', line 172

def find_by_role(role)
  session.find({by: "role", value: role.to_s})
end

#find_focusedHash?

Find the currently focused widget.

Returns:

  • (Hash, nil)


185
186
187
# File 'lib/plushie/test/helpers.rb', line 185

def find_focused
  session.find({by: "focused"})
end

#focus_element(canvas_id, element_id) ⇒ Object

Focus a canvas element via scoped path. Use Command.focus("canvas/element") for the same effect.

Parameters:

  • canvas_id (String)

    the canvas widget ID

  • element_id (String)

    the element ID within the canvas



83
84
85
# File 'lib/plushie/test/helpers.rb', line 83

def focus_element(canvas_id, element_id)
  session.command(Command.focus("#{canvas_id}/#{element_id}"))
end

#modelObject

Returns current app model.

Returns:

  • (Object)

    current app model



100
# File 'lib/plushie/test/helpers.rb', line 100

def model = session.model

#move_to(x, y) ⇒ Object

Move cursor to coordinates.

Parameters:

  • x (Numeric)
  • y (Numeric)


72
# File 'lib/plushie/test/helpers.rb', line 72

def move_to(x, y) = session.move_to(x, y)

#plushie_start(app_class, **opts) ⇒ Object

Start a test session manually.

Parameters:

  • app_class (Class)


289
290
291
292
293
# File 'lib/plushie/test/helpers.rb', line 289

def plushie_start(app_class, **opts)
  pool = Plushie::Test.pool
  session_id = pool.register
  Thread.current[:_plushie_test_session] = Session.new(app_class, pool: pool, session_id: session_id)
end

#plushie_stopObject

Stop the current test session.



296
297
298
299
# File 'lib/plushie/test/helpers.rb', line 296

def plushie_stop
  Thread.current[:_plushie_test_session]&.stop
  Thread.current[:_plushie_test_session] = nil
end

#press(key) ⇒ Object

Press a key (key down).

Parameters:

  • key (String)


59
# File 'lib/plushie/test/helpers.rb', line 59

def press(key) = session.press(key)

#register_effect_stub(kind, response) ⇒ Object

Register an effect stub with the renderer. The renderer will return the given response immediately for any effect of the given kind.

Parameters:

  • kind (Symbol, String)

    effect kind (e.g. :clipboard_read)

  • response (Object)

    the canned response to return



146
147
148
# File 'lib/plushie/test/helpers.rb', line 146

def register_effect_stub(kind, response)
  session.register_effect_stub(kind.to_s, response)
end

#release(key) ⇒ Object

Release a key (key up).

Parameters:

  • key (String)


63
# File 'lib/plushie/test/helpers.rb', line 63

def release(key) = session.release(key)

#resetObject

Reset the session to initial state.



121
# File 'lib/plushie/test/helpers.rb', line 121

def reset = session.reset

#resolved_a11y(selector) ⇒ Hash

Return the resolved a11y hash for a widget.

Layers render-pipeline inference (placeholder -> description for text-entry widgets, alt -> label for media widgets) on top of the normalized a11y prop so tests see what assistive technology will see. Normalizer-populated defaults (role, implicit radio_group, required/validation projections, tooltip described_by) are already carried on the tree.

Parameters:

  • selector (String)

Returns:

  • (Hash)

    symbol-keyed a11y map (empty if no state)



265
266
267
268
# File 'lib/plushie/test/helpers.rb', line 265

def resolved_a11y(selector)
  element = find!(selector)
  ::Plushie::Test::Helpers.resolve_a11y_for_element(element)
end

#save_screenshot(name, **opts) ⇒ Hash

Capture a screenshot and save as PNG to test/screenshots/.

Parameters:

  • name (String)

    screenshot name

Returns:

  • (Hash)

    screenshot response



192
193
194
195
196
197
198
199
200
# File 'lib/plushie/test/helpers.rb', line 192

def save_screenshot(name, **opts)
  result = screenshot(name, **opts)
  if result && (rgba = result[:rgba] || result["rgba"])
    dir = "test/screenshots"
    FileUtils.mkdir_p(dir)
    File.binwrite(File.join(dir, "#{name}.rgba"), rgba)
  end
  result
end

#screenshot(name, **opts) ⇒ Hash

Capture a screenshot.

Parameters:

  • name (String)

Returns:

  • (Hash)


118
# File 'lib/plushie/test/helpers.rb', line 118

def screenshot(name, **opts) = session.screenshot(name, **opts)

#select(selector, value) ⇒ Object

Select a value from pick_list, combo_box, or radio.

Parameters:

  • selector (String)
  • value (String)


50
# File 'lib/plushie/test/helpers.rb', line 50

def select(selector, value) = session.select(selector, value)

#sessionSession

Returns the current test session.

Returns:

  • (Session)

    the current test session



23
24
25
26
# File 'lib/plushie/test/helpers.rb', line 23

def session
  Thread.current[:_plushie_test_session] ||
    raise("No Plushie test session. Use Plushie::Test::Case or call plushie_start first.")
end

#skip_transitionsObject

Skip all active renderer-side transitions to completion.

Advances the animation clock far enough to complete any reasonable animation. Triggers transition_complete events for any animations with on_complete tags.



218
219
220
# File 'lib/plushie/test/helpers.rb', line 218

def skip_transitions
  advance_frame(10_000)
end

#slide(selector, value) ⇒ Object

Slide a slider to a value.

Parameters:

  • selector (String)
  • value (Numeric)


55
# File 'lib/plushie/test/helpers.rb', line 55

def slide(selector, value) = session.slide(selector, value)

#submit(selector) ⇒ Object

Submit a text_input (press Enter).

Parameters:

  • selector (String)


41
# File 'lib/plushie/test/helpers.rb', line 41

def submit(selector) = session.submit(selector)

#text(element) ⇒ String?

Extract text content from an element hash.

Parameters:

  • element (Hash)

Returns:

  • (String, nil)


108
# File 'lib/plushie/test/helpers.rb', line 108

def text(element) = session.element_text(element)

#toggle(selector) ⇒ Object

Toggle a checkbox or toggler.

Parameters:

  • selector (String)


45
# File 'lib/plushie/test/helpers.rb', line 45

def toggle(selector) = session.toggle(selector)

#treeHash

Returns current tree from the renderer.

Returns:

  • (Hash)

    current tree from the renderer



103
# File 'lib/plushie/test/helpers.rb', line 103

def tree = session.tree

#tree_hash(name) ⇒ Hash

Capture a structural tree hash.

Parameters:

  • name (String)

Returns:

  • (Hash)


113
# File 'lib/plushie/test/helpers.rb', line 113

def tree_hash(name) = session.tree_hash(name)

#type_key(key) ⇒ Object

Type a key (press + release).

Parameters:

  • key (String)


67
# File 'lib/plushie/test/helpers.rb', line 67

def type_key(key) = session.type_key(key)

#type_text(selector, text) ⇒ Object

Type text into a text_input or text_editor.

Parameters:

  • selector (String)
  • text (String)


37
# File 'lib/plushie/test/helpers.rb', line 37

def type_text(selector, text) = session.type_text(selector, text)

#unregister_effect_stub(kind) ⇒ Object

Remove a previously registered effect stub.

Parameters:

  • kind (Symbol, String)

    effect kind



153
154
155
# File 'lib/plushie/test/helpers.rb', line 153

def unregister_effect_stub(kind)
  session.unregister_effect_stub(kind.to_s)
end