capybara-dommy 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 288082f09fb0e1151ba15dfa7bce080997855de9ac4cbced143238b8fbc6db6b
4
- data.tar.gz: 8c7c68b114eb0526071f15ae2675a94bc38492161fadbf29f3b5cf934d810f09
3
+ metadata.gz: ce693099e9f737e726914acb5cf9129ad87a96978209f5b1d9cc27132925b652
4
+ data.tar.gz: a69e70e2f0878025fd8d8c5a9411aaa990d6ebd7f812bd623cd3f27c4614e3bd
5
5
  SHA512:
6
- metadata.gz: a6544c835b5721f26d77ab97c8278dc7a722c859a86c32ca06b81c263c8cb5a2d9062b318b92e731dd646795cb634bc69c91bbb229da6de3dbaf719f1bec2887
7
- data.tar.gz: 707be8eeb45ba4a7ca871f1aef7160755bc0fb8a33f587b9bd37ea10fc69c9095c164684d765a5884ca7c1f79d69db650bc0fe758cdc5b752c3d61715abee610
6
+ metadata.gz: 1f351cf3f7989d3bfa1a984dc425cf895790350ed699431f1e6fb8a7d6708c8b1abc05ab20df20f129839a0c926074d6b64774b9885a97b593ad41b8354b0b2a
7
+ data.tar.gz: 4246e91a681b995f8cc2fb2633eac92a1d6e5b1fc8dad67954d099124cc999055575597a59a94965ea6d37b201ed04d9ec2271a6dbd0146d7eede6b634a416cc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0 — 2026-07-13
4
+
5
+ ### Added
6
+ - **JavaScript-enabled driver variant** (`:dommy_js`, or `Driver.new(app, javascript: true)` / `config.javascript`): pages run their real Turbo/Stimulus/React bundles in the embedded QuickJS runtime, no browser process. Node interactions behave like a browser instead of the HTML-only fast paths — `click` dispatches the full pointer/mouse/click sequence before the default action (Turbo can `preventDefault` and take over), `set` types with focus + `input` / `change` events, `select_option` fires `input` / `change`, and `send_keys` dispatches real keyboard events. `execute_script` / `evaluate_script` run for real, and a time pump advances the virtual clock inside Capybara's synchronize loop so waiting matchers converge. Requires `dommy-js-quickjs`.
7
+
8
+ ### Fixed
9
+ - The dommy-rack session is fully disposed on `reset!` and when the effective host changes, so JS runtimes and open WebSocket transports no longer leak across tests.
10
+
3
11
  ## 0.9.0 — 2026-06-22
4
12
 
5
13
  Versioned in lockstep with [`dommy`](https://github.com/takahashim/dommy) 0.9.0.
@@ -6,7 +6,7 @@ module Capybara
6
6
  # when a keyword argument is omitted.
7
7
  class Configuration
8
8
  attr_accessor :default_host, :follow_redirects, :max_redirects, :visibility,
9
- :raise_on_unsupported_js
9
+ :raise_on_unsupported_js, :javascript
10
10
 
11
11
  def initialize
12
12
  @default_host = "http://example.org"
@@ -14,6 +14,7 @@ module Capybara
14
14
  @max_redirects = 5
15
15
  @visibility = :html
16
16
  @raise_on_unsupported_js = true
17
+ @javascript = false
17
18
  end
18
19
  end
19
20
 
@@ -28,10 +28,12 @@ module Capybara
28
28
  default_host: nil,
29
29
  follow_redirects: nil,
30
30
  max_redirects: nil,
31
- visibility: nil)
31
+ visibility: nil,
32
+ javascript: nil)
32
33
  super()
33
34
  config = Capybara::Dommy.configuration
34
35
  @app = app
36
+ @javascript = javascript.nil? ? config.javascript : javascript
35
37
  @visibility = visibility || config.visibility
36
38
  unless VISIBILITY_MODES.include?(@visibility)
37
39
  raise ArgumentError,
@@ -46,6 +48,24 @@ module Capybara
46
48
  # hosts (e.g. app_host / multi-server specs), so don't enforce origin.
47
49
  enforce_same_origin: false
48
50
  }
51
+ @session_options[:javascript] = true if @javascript
52
+ # A JS session needs the virtual clock pumped inside Capybara's
53
+ # synchronize loop, so waiting expectations converge on timer/fetch
54
+ # driven updates. A host-installed pump (the documented seam) wins.
55
+ @time_pump ||= -> { @rack_session&.advance_time(16) } if @javascript
56
+ end
57
+
58
+ # Whether this driver runs page JavaScript (`javascript: true`, backed by
59
+ # a `Dommy::Rack::Session.new(app, javascript: true)`). Node interactions
60
+ # then dispatch real DOM events (Turbo/Stimulus handlers run) instead of
61
+ # the HTML-only fast paths.
62
+ def javascript? = @javascript
63
+
64
+ # Drain the JS runtime after an interaction's events (promise reactions
65
+ # settle before the next Capybara step). No-op without JavaScript.
66
+ def drain_js
67
+ rack_session.after_interaction if @javascript
68
+ nil
49
69
  end
50
70
 
51
71
  # The dommy-rack session. Named `rack_session` to avoid colliding with
@@ -55,6 +75,7 @@ module Capybara
55
75
  def rack_session
56
76
  host = effective_host
57
77
  if @rack_session.nil? || @rack_session_host != host
78
+ @rack_session&.dispose
58
79
  @rack_session = ::Dommy::Rack::Session.new(@app, **@session_options.merge(default_host: host))
59
80
  @rack_session_host = host
60
81
  end
@@ -179,6 +200,7 @@ module Capybara
179
200
  # --- Lifecycle ---
180
201
 
181
202
  def reset!
203
+ @rack_session&.dispose
182
204
  @rack_session = nil
183
205
  @frame_stack = []
184
206
  end
@@ -200,12 +222,18 @@ module Capybara
200
222
  # When raise_on_unsupported_js is false these become no-ops, so tests
201
223
  # that incidentally call them don't fail.
202
224
 
203
- def execute_script(_script, *_args)
204
- unsupported_js!("execute_script")
225
+ def execute_script(script, *args)
226
+ return unsupported_js!("execute_script") unless @javascript
227
+ raise ArgumentError, "script arguments are not supported" unless args.empty?
228
+
229
+ rack_session.execute_script(script)
205
230
  end
206
231
 
207
- def evaluate_script(_script, *_args)
208
- unsupported_js!("evaluate_script")
232
+ def evaluate_script(script, *args)
233
+ return unsupported_js!("evaluate_script") unless @javascript
234
+ raise ArgumentError, "script arguments are not supported" unless args.empty?
235
+
236
+ rack_session.evaluate_script(script)
209
237
  end
210
238
 
211
239
  def evaluate_async_script(_script, *_args)
@@ -107,6 +107,12 @@ module Capybara
107
107
  # Capybara's non-JS send_keys specs use. Key *events* are not dispatched
108
108
  # (nothing here can observe them without JavaScript).
109
109
  def send_keys(*args)
110
+ # Under JavaScript, dispatch real keyboard events (keydown/keypress/
111
+ # input/keyup with browser default actions) through the dommy driver,
112
+ # so keyboard handlers (arrow navigation, Enter selection) run.
113
+ # Key chords ([:shift, "o"]) are not supported on this path.
114
+ return driver.rack_session.send_keys_to(native, *args) if driver.javascript?
115
+
110
116
  return unless native.respond_to?(:value=)
111
117
 
112
118
  state = {chars: native.value.to_s.chars, caret: native.value.to_s.length, shift: false}
@@ -136,6 +142,8 @@ module Capybara
136
142
  # --- Interaction ---
137
143
 
138
144
  def click(_keys = [], **_options)
145
+ return js_click if driver.javascript?
146
+
139
147
  if link?
140
148
  click_link_node
141
149
  elsif submits?
@@ -171,6 +179,7 @@ module Capybara
171
179
  select_el = select_node
172
180
  deselect_all(select_el) unless select_el&.multiple
173
181
  native.selected = true
182
+ notify_select_changed(select_el)
174
183
  end
175
184
 
176
185
  def unselect_option
@@ -257,6 +266,40 @@ module Capybara
257
266
  # javascript: links are no-ops in Capybara (a policy decision); every
258
267
  # other link delegates to dommy-rack, which handles fragment / same-page
259
268
  # / blank-href semantics and raises on genuinely unsupported schemes.
269
+ # A JavaScript click: dispatch the full pointer/mouse/click sequence
270
+ # (Stimulus actions, Turbo's link/submit interception run like in a
271
+ # browser), then perform the un-prevented default action ourselves —
272
+ # link navigation, or the form submission algorithm (a cancelable
273
+ # submit event, then the real submission if nothing canceled it;
274
+ # Turbo cancels and takes over). Checkbox/radio toggling already ran
275
+ # as the click event's activation behavior.
276
+ def js_click
277
+ prevented = ::Dommy::Interaction::EventSynthesis.click(native)
278
+ unless prevented
279
+ # Link navigation and form submission are the elements' own activation
280
+ # behavior — run inside EventSynthesis.click above, routing through the
281
+ # session's navigation delegate (performed when driver.drain_js drains).
282
+ # Only the non-navigating default actions remain here.
283
+ if tag_name == "label"
284
+ click_label
285
+ elsif (details = native.closest("details"))
286
+ toggle_details(details)
287
+ end
288
+ end
289
+ driver.drain_js
290
+ nil
291
+ end
292
+
293
+ # Selecting an option through the UI fires input + change on the select
294
+ # (under JavaScript; nothing listens without it).
295
+ def notify_select_changed(select_el)
296
+ return unless driver.javascript? && select_el
297
+
298
+ ::Dommy::Interaction::EventSynthesis.input(select_el)
299
+ ::Dommy::Interaction::EventSynthesis.change(select_el)
300
+ driver.drain_js
301
+ end
302
+
260
303
  def click_link_node
261
304
  scheme = native.get_attribute("href").to_s.split(":", 2).first.to_s.downcase
262
305
  return if scheme == "javascript"
@@ -303,11 +346,28 @@ module Capybara
303
346
  # There is no submitter button in this case.
304
347
  form = single_field_form
305
348
  if input_field? && string.end_with?("\n") && form
306
- native.value = string.chomp
349
+ write_text(string.chomp)
307
350
  driver.submit_form(form, submitter: nil)
308
351
  else
352
+ write_text(string)
353
+ end
354
+ end
355
+
356
+ # Set a text field's value. Under JavaScript this types like a user:
357
+ # focus, value, then input + change events, so Stimulus/React handlers
358
+ # observe the edit; the JS-less driver keeps the bare value write
359
+ # (nothing listens).
360
+ def write_text(string)
361
+ unless driver.javascript?
309
362
  native.value = string
363
+ return
310
364
  end
365
+
366
+ ::Dommy::Interaction::EventSynthesis.focus(native)
367
+ native.value = string
368
+ ::Dommy::Interaction::EventSynthesis.input(native, string)
369
+ ::Dommy::Interaction::EventSynthesis.change(native)
370
+ driver.drain_js
311
371
  end
312
372
 
313
373
  def single_field_form
@@ -8,3 +8,10 @@ require "capybara/dommy"
8
8
  Capybara.register_driver(:dommy) do |app|
9
9
  Capybara::Dommy::Driver.new(app)
10
10
  end
11
+
12
+ # The JavaScript-enabled variant (`driven_by :dommy_js`): pages run their real
13
+ # Turbo/Stimulus/React bundles in the embedded QuickJS runtime, no browser
14
+ # process. Requires dommy-js-quickjs.
15
+ Capybara.register_driver(:dommy_js) do |app|
16
+ Capybara::Dommy::Driver.new(app, javascript: true)
17
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Dommy
5
- VERSION = "0.9.0"
5
+ VERSION = "0.10.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capybara-dommy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - takahashim
@@ -29,28 +29,28 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: 0.9.0
32
+ version: 0.10.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.9.0
39
+ version: 0.10.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: dommy-rack
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 0.9.0
46
+ version: 0.10.0
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: 0.9.0
53
+ version: 0.10.0
54
54
  description: |
55
55
  capybara-dommy is a Capybara driver backed by Dommy and dommy-rack. It drives
56
56
  Rack/Rails apps through the Capybara DSL without a real browser or JavaScript,
@@ -94,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  requirements: []
97
- rubygems_version: 3.6.9
97
+ rubygems_version: 4.0.10
98
98
  specification_version: 4
99
99
  summary: A Dommy-backed Capybara driver
100
100
  test_files: []