capybara-dommy 0.10.0 → 0.12.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: ce693099e9f737e726914acb5cf9129ad87a96978209f5b1d9cc27132925b652
4
- data.tar.gz: a69e70e2f0878025fd8d8c5a9411aaa990d6ebd7f812bd623cd3f27c4614e3bd
3
+ metadata.gz: '09b82196b4134a59d541a83732013edf23a017a47776dd35b326ebc573747789'
4
+ data.tar.gz: 282eb35b9f51404a80602537ad3fe6f29a7af768da2b30210c483fc8a841f86b
5
5
  SHA512:
6
- metadata.gz: 1f351cf3f7989d3bfa1a984dc425cf895790350ed699431f1e6fb8a7d6708c8b1abc05ab20df20f129839a0c926074d6b64774b9885a97b593ad41b8354b0b2a
7
- data.tar.gz: 4246e91a681b995f8cc2fb2633eac92a1d6e5b1fc8dad67954d099124cc999055575597a59a94965ea6d37b201ed04d9ec2271a6dbd0146d7eede6b634a416cc
6
+ metadata.gz: 6f9e7b3ca7e5bb3bde8c5c465bbdf783ba7497c4a40f032927fb6d34a1416c53cb73e321ee5902e1a9c215028e0b1e7665dbe7dc3ad92842a64fa001a3844d0a
7
+ data.tar.gz: 9aed884b51865fd48c233f9d7c4c8b63b50798f1cf18850701048d42671c8b0e5c071657850db0e49f9487b2b519b50ab8b74ae3f58cc58913646f53b6171cee
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0 — 2026-09-22
4
+
5
+ Versioned in lockstep with [`dommy`](https://github.com/takahashim/dommy) 0.12.0.
6
+ No functional changes to capybara-dommy itself.
7
+
8
+ ## 0.11.0 — 2026-09-11
9
+
10
+ ### Added
11
+ - **Native dialog helpers in JS mode.** `accept_confirm`, `dismiss_confirm`, `accept_alert`, `accept_prompt` (with `with:`) and `dismiss_prompt` work against the page's real `confirm` / `alert` / `prompt`, including a `text:` string or Regexp to say which dialog is expected. Nested helper blocks answer the dialogs in the order the page opens them, and a dialog nobody is waiting for falls back to the headless default, so a stray `confirm` cannot silently accept. A block that opens no matching dialog raises `Capybara::ModalNotFound`, naming what turned up instead.
12
+
13
+ ### Fixed
14
+ - Turbo-driven navigation in JS apps is followed, so `have_current_path` and the matchers after a Turbo visit see the page the app actually moved to.
15
+ - `current_url` advances the virtual clock like the other queries do, so a `have_current_path` poll converges when the navigation settles in a scheduled task rather than a microtask.
16
+
3
17
  ## 0.10.0 — 2026-07-13
4
18
 
5
19
  ### Added
data/README.md CHANGED
@@ -19,15 +19,24 @@ speed and simplicity of a Rack-style driver.
19
19
  - Preserves session state such as cookies, follows redirects by default, and
20
20
  supports browser-like back, forward, and refresh navigation.
21
21
  - Implements HTML-level visibility through `dommy-rack`.
22
+ - In JavaScript-enabled mode, supports Capybara's `accept_alert`,
23
+ `accept_confirm`, `dismiss_confirm`, `accept_prompt`, and `dismiss_prompt`
24
+ helpers with deterministic responses.
22
25
  - Provides a Rails convenience require for `driven_by :dommy`.
23
26
 
24
27
  ## Limitations
25
28
 
26
29
  `capybara-dommy` is intentionally not a browser automation driver.
27
30
 
28
- - JavaScript is not executed.
29
- - Screenshots, browser windows, alerts, confirms, prompts, and other real
30
- browser features are not supported.
31
+ - The default driver does not execute JavaScript. Use the JavaScript-enabled
32
+ variant for embedded QuickJS execution.
33
+ - Screenshots and browser windows are not supported. Native alerts, confirms,
34
+ and prompts are supported by the JavaScript-enabled variant through
35
+ Capybara's modal helpers.
36
+ - Constructable stylesheets can be built (`new CSSStyleSheet()`), but
37
+ `adoptedStyleSheets` is not implemented — assigning one applies no style.
38
+ Component libraries that feature-detect (`'adoptedStyleSheets' in
39
+ Document.prototype`) fall back to injecting a `<style>`, which is handled.
31
40
  - CSS layout is not calculated. Visibility is based on HTML-level rules such as
32
41
  `hidden`, `type="hidden"`, and inline `display: none` handling provided by
33
42
  `dommy-rack`.
@@ -4,9 +4,10 @@ module Capybara
4
4
  module Dommy
5
5
  # A Capybara driver backed by Dommy::Rack::Session. Implements the
6
6
  # navigation / query / reset! parts of the Capybara::Driver::Base contract;
7
- # element interaction lives in Capybara::Dommy::Node. JavaScript, screenshot,
8
- # window, and modal methods are left to Driver::Base (which raises
9
- # Capybara::NotSupportedByDriverError).
7
+ # element interaction lives in Capybara::Dommy::Node. The JS-enabled mode
8
+ # additionally supplies deterministic native dialog responses for Capybara's
9
+ # alert/confirm/prompt helpers. Screenshot and window methods remain with
10
+ # Driver::Base (which raises Capybara::NotSupportedByDriverError).
10
11
  class Driver < Capybara::Driver::Base
11
12
  VISIBILITY_MODES = %i[all html none].freeze
12
13
 
@@ -94,6 +95,12 @@ module Capybara
94
95
  end
95
96
 
96
97
  def current_url
98
+ # Capybara polls current_url for have_current_path. In JS mode that poll
99
+ # must advance the virtual clock too: Turbo/fetch continuations often
100
+ # settle in a scheduled task rather than the interaction's microtask
101
+ # drain. The Rack session's History hook then reflects pushState in its
102
+ # current URL.
103
+ pump!
97
104
  rack_session.current_url.to_s
98
105
  end
99
106
 
@@ -240,6 +247,22 @@ module Capybara
240
247
  unsupported_js!("evaluate_async_script")
241
248
  end
242
249
 
250
+ # --- Native dialogs ---
251
+ #
252
+ # A native dialog is synchronous in Dommy, so installing the expected
253
+ # answer before the triggering block runs is sufficient. What is expected,
254
+ # what it answers and what it saw instead live in ModalExpectation; the
255
+ # stack they sit on, which is the session's dialog handler while a helper
256
+ # block is running, is ModalStack.
257
+
258
+ def accept_modal(type, **options, &block)
259
+ respond_to_modal(type, accept: true, **options, &block)
260
+ end
261
+
262
+ def dismiss_modal(type, **options, &block)
263
+ respond_to_modal(type, accept: false, **options, &block)
264
+ end
265
+
243
266
  # Visibility decision used by Node#visible?. :all / :none treat every
244
267
  # element as visible; :html defers to dommy-rack's HTML-level check.
245
268
  def visible?(element)
@@ -323,6 +346,26 @@ module Capybara
323
346
  raise Capybara::NotSupportedByDriverError,
324
347
  "capybara-dommy does not support JavaScript (#{name})"
325
348
  end
349
+
350
+ def respond_to_modal(type, accept:, text: nil, with: nil, **_options)
351
+ expectation = ModalExpectation.new(type: type, accept: accept, text: text, with: with)
352
+ modals.push(expectation)
353
+ rack_session.dialog_handler = modals
354
+ yield if block_given?
355
+
356
+ raise Capybara::ModalNotFound, expectation.not_found_message unless expectation.answered?
357
+
358
+ expectation.message
359
+ ensure
360
+ if expectation
361
+ modals.delete(expectation)
362
+ rack_session.dialog_handler = nil if modals.empty? && @rack_session
363
+ end
364
+ end
365
+
366
+ def modals
367
+ @modals ||= ModalStack.new
368
+ end
326
369
  end
327
370
  end
328
371
  end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Dommy
5
+ # What one `accept_modal` / `dismiss_modal` block is waiting for, and what
6
+ # it saw. A native dialog is synchronous in Dommy, so the expectation is
7
+ # installed before the triggering block runs and answers the dialog the
8
+ # block opens.
9
+ class ModalExpectation
10
+ attr_reader :type, :text
11
+ # The message of the dialog this expectation answered — nil while it is
12
+ # still waiting, which is what makes it "not found" at the end of the
13
+ # block.
14
+ attr_reader :message
15
+
16
+ def initialize(type:, accept:, text: nil, with: nil)
17
+ @type = type.to_sym
18
+ @accept = accept
19
+ @text = text
20
+ @with = with
21
+ @message = nil
22
+ @actual = nil
23
+ end
24
+
25
+ def matches?(type, message)
26
+ @type == type && text_matches?(message)
27
+ end
28
+
29
+ def answered? = !@message.nil?
30
+
31
+ # Record that this dialog is the one, and answer it: a confirm with the
32
+ # accept/dismiss decision, a prompt with the text to type (`with:`, else
33
+ # what the page offered) or nil when dismissed. An alert has no answer.
34
+ def answer(message, default_value)
35
+ @message = message
36
+ case @type
37
+ when :confirm then @accept
38
+ when :prompt then @accept ? (@with || default_value) : nil
39
+ end
40
+ end
41
+
42
+ # A dialog this expectation did not want. Remembered for the error at the
43
+ # end of the block, which says what turned up instead.
44
+ def saw(type, message)
45
+ @actual = {type: type, message: message}
46
+ nil
47
+ end
48
+
49
+ def not_found_message
50
+ if @actual
51
+ "Unable to find #{@type} dialog with #{@text.inspect} - found " \
52
+ "#{@actual[:type]} dialog with #{@actual[:message].inspect} instead."
53
+ else
54
+ "Unable to find #{@type} dialog#{@text ? " with #{@text.inspect}" : ""}"
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def text_matches?(message)
61
+ return true if @text.nil?
62
+
63
+ pattern = @text.is_a?(Regexp) ? @text : Regexp.new(Regexp.escape(@text.to_s))
64
+ pattern.match?(message)
65
+ end
66
+ end
67
+
68
+ # The expectations currently in scope, innermost first, as the dialog
69
+ # handler a Dommy Window asks (it calls `#call`).
70
+ #
71
+ # A stack, because Capybara expresses nested confirms as nested helper
72
+ # blocks while the page opens them sequentially in one JS call stack:
73
+ # consuming the inner expectation exposes the outer answer to the next
74
+ # confirm immediately.
75
+ class ModalStack
76
+ def initialize
77
+ @expectations = []
78
+ end
79
+
80
+ def empty? = @expectations.empty?
81
+
82
+ def push(expectation)
83
+ @expectations << expectation
84
+ expectation
85
+ end
86
+
87
+ # By identity: two helper blocks with the same arguments build EQUAL
88
+ # expectations, and removing by value would drop the caller's alongside
89
+ # this one.
90
+ def delete(expectation)
91
+ @expectations.delete_if { |e| e.equal?(expectation) }
92
+ nil
93
+ end
94
+
95
+ # The dialog handler protocol: answer the dialog, or return
96
+ # DIALOG_UNANSWERED to leave it to the Window's headless default. An
97
+ # expectation that does not want this dialog remembers it (so the block
98
+ # can say what turned up instead) and declines rather than answering.
99
+ def call(type, message, default_value)
100
+ expectation = @expectations.last
101
+ return ::Dommy::Window::DIALOG_UNANSWERED if expectation.nil?
102
+
103
+ unless expectation.matches?(type, message)
104
+ expectation.saw(type, message)
105
+ return ::Dommy::Window::DIALOG_UNANSWERED
106
+ end
107
+
108
+ answer = expectation.answer(message, default_value)
109
+ delete(expectation)
110
+ answer
111
+ end
112
+ end
113
+ end
114
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Dommy
5
- VERSION = "0.10.0"
5
+ VERSION = "0.12.0"
6
6
  end
7
7
  end
@@ -8,6 +8,7 @@ require_relative "dommy/version"
8
8
  require_relative "dommy/errors"
9
9
  require_relative "dommy/configuration"
10
10
  require_relative "dommy/text_extractor"
11
+ require_relative "dommy/modal"
11
12
  require_relative "dommy/node"
12
13
  require_relative "dommy/driver"
13
14
 
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.10.0
4
+ version: 0.12.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.10.0
32
+ version: 0.12.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.10.0
39
+ version: 0.12.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.10.0
46
+ version: 0.12.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.10.0
53
+ version: 0.12.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,
@@ -69,6 +69,7 @@ files:
69
69
  - lib/capybara/dommy/configuration.rb
70
70
  - lib/capybara/dommy/driver.rb
71
71
  - lib/capybara/dommy/errors.rb
72
+ - lib/capybara/dommy/modal.rb
72
73
  - lib/capybara/dommy/node.rb
73
74
  - lib/capybara/dommy/rails.rb
74
75
  - lib/capybara/dommy/text_extractor.rb
@@ -94,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
95
  - !ruby/object:Gem::Version
95
96
  version: '0'
96
97
  requirements: []
97
- rubygems_version: 4.0.10
98
+ rubygems_version: 3.6.9
98
99
  specification_version: 4
99
100
  summary: A Dommy-backed Capybara driver
100
101
  test_files: []