dommy 0.5.0 → 0.7.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -13
  3. data/lib/dommy/animation.rb +288 -0
  4. data/lib/dommy/attr.rb +23 -11
  5. data/lib/dommy/backend/nokogiri_adapter.rb +51 -0
  6. data/lib/dommy/backend/nokolexbor_adapter.rb +80 -0
  7. data/lib/dommy/backend.rb +129 -0
  8. data/lib/dommy/blob.rb +2 -2
  9. data/lib/dommy/compression_streams.rb +147 -0
  10. data/lib/dommy/cookie_store.rb +128 -0
  11. data/lib/dommy/crypto.rb +396 -0
  12. data/lib/dommy/css.rb +7 -7
  13. data/lib/dommy/custom_elements.rb +6 -6
  14. data/lib/dommy/document.rb +190 -32
  15. data/lib/dommy/dom_parser.rb +5 -4
  16. data/lib/dommy/element.rb +356 -53
  17. data/lib/dommy/event.rb +431 -25
  18. data/lib/dommy/event_source.rb +131 -0
  19. data/lib/dommy/fetch.rb +76 -6
  20. data/lib/dommy/file_reader.rb +176 -0
  21. data/lib/dommy/form_data.rb +1 -3
  22. data/lib/dommy/history.rb +82 -0
  23. data/lib/dommy/html_collection.rb +4 -4
  24. data/lib/dommy/html_elements.rb +130 -67
  25. data/lib/dommy/internal/cookie_jar.rb +2 -0
  26. data/lib/dommy/internal/css_pseudo_handlers.rb +28 -0
  27. data/lib/dommy/internal/dom_matching.rb +4 -4
  28. data/lib/dommy/internal/idna.rb +443 -0
  29. data/lib/dommy/internal/idna_data.rb +10379 -0
  30. data/lib/dommy/internal/ipv4_parser.rb +78 -0
  31. data/lib/dommy/internal/node_traversal.rb +1 -1
  32. data/lib/dommy/internal/node_wrapper_cache.rb +23 -12
  33. data/lib/dommy/internal/observable_callback.rb +25 -0
  34. data/lib/dommy/internal/punycode.rb +202 -0
  35. data/lib/dommy/internal/range_text_serializer.rb +72 -0
  36. data/lib/dommy/internal/reflected_attributes.rb +45 -0
  37. data/lib/dommy/internal/template_content_registry.rb +6 -6
  38. data/lib/dommy/intersection_observer.rb +82 -0
  39. data/lib/dommy/{router.rb → location.rb} +8 -142
  40. data/lib/dommy/media_query_list.rb +118 -0
  41. data/lib/dommy/message_channel.rb +249 -0
  42. data/lib/dommy/{observer.rb → mutation_observer.rb} +21 -11
  43. data/lib/dommy/navigator.rb +365 -5
  44. data/lib/dommy/node.rb +12 -0
  45. data/lib/dommy/notification.rb +89 -0
  46. data/lib/dommy/parser.rb +13 -13
  47. data/lib/dommy/performance.rb +146 -0
  48. data/lib/dommy/performance_observer.rb +55 -0
  49. data/lib/dommy/range.rb +597 -0
  50. data/lib/dommy/resize_observer.rb +53 -0
  51. data/lib/dommy/shadow_root.rb +10 -8
  52. data/lib/dommy/streams.rb +386 -0
  53. data/lib/dommy/svg_elements.rb +3863 -0
  54. data/lib/dommy/text_codec.rb +175 -0
  55. data/lib/dommy/tree_walker.rb +21 -21
  56. data/lib/dommy/url.rb +274 -29
  57. data/lib/dommy/url_pattern.rb +144 -0
  58. data/lib/dommy/version.rb +1 -1
  59. data/lib/dommy/web_socket.rb +209 -0
  60. data/lib/dommy/window.rb +369 -0
  61. data/lib/dommy/worker.rb +143 -0
  62. data/lib/dommy/xml_http_request.rb +438 -0
  63. data/lib/dommy.rb +43 -5
  64. metadata +44 -29
  65. data/lib/dommy/world.rb +0 -209
metadata CHANGED
@@ -1,38 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dommy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - takahashim
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-05-21 00:00:00.000000000 Z
11
- dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: nokogiri
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: '1.15'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: '1.15'
26
- description: |
27
- A pure-Ruby DOM polyfill on top of Nokogiri::HTML5, a Ruby-side
28
- analogue to JavaScript DOM libraries like happy-dom and jsdom.
29
- It exposes browser-like DOM semantics — events, MutationObserver,
30
- Custom Elements, Shadow DOM, the File API (Blob / File / FormData /
31
- DataTransfer), URL, Promise, timers, and Storage — without spinning
32
- up a real browser.
10
+ date: 2026-05-29 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: 'A pure Ruby DOM polyfill built on Nokogiri::HTML5, inspired by happy-dom
13
+ and jsdom. It gives Ruby tests a browser style DOM with events, MutationObserver,
14
+ Custom Elements, Shadow DOM, the File API, timers, and Storage, without requiring
15
+ a real browser.
33
16
 
34
- Aimed at testing Ruby code that emits or consumes HTML. Includes
35
- drop-in RSpec matchers and Minitest assertions.
17
+ '
36
18
  email:
37
19
  - takahashimm@gmail.com
38
20
  executables: []
@@ -41,9 +23,16 @@ extra_rdoc_files: []
41
23
  files:
42
24
  - README.md
43
25
  - lib/dommy.rb
26
+ - lib/dommy/animation.rb
44
27
  - lib/dommy/attr.rb
28
+ - lib/dommy/backend.rb
29
+ - lib/dommy/backend/nokogiri_adapter.rb
30
+ - lib/dommy/backend/nokolexbor_adapter.rb
45
31
  - lib/dommy/blob.rb
46
32
  - lib/dommy/bridge.rb
33
+ - lib/dommy/compression_streams.rb
34
+ - lib/dommy/cookie_store.rb
35
+ - lib/dommy/crypto.rb
47
36
  - lib/dommy/css.rb
48
37
  - lib/dommy/custom_elements.rb
49
38
  - lib/dommy/data_transfer.rb
@@ -52,39 +41,65 @@ files:
52
41
  - lib/dommy/dom_parser.rb
53
42
  - lib/dommy/element.rb
54
43
  - lib/dommy/event.rb
44
+ - lib/dommy/event_source.rb
55
45
  - lib/dommy/fetch.rb
46
+ - lib/dommy/file_reader.rb
56
47
  - lib/dommy/form_data.rb
48
+ - lib/dommy/history.rb
57
49
  - lib/dommy/html_collection.rb
58
50
  - lib/dommy/html_elements.rb
59
51
  - lib/dommy/internal/cookie_jar.rb
52
+ - lib/dommy/internal/css_pseudo_handlers.rb
60
53
  - lib/dommy/internal/dom_matching.rb
54
+ - lib/dommy/internal/idna.rb
55
+ - lib/dommy/internal/idna_data.rb
56
+ - lib/dommy/internal/ipv4_parser.rb
61
57
  - lib/dommy/internal/mutation_coordinator.rb
62
58
  - lib/dommy/internal/node_traversal.rb
63
59
  - lib/dommy/internal/node_wrapper_cache.rb
60
+ - lib/dommy/internal/observable_callback.rb
64
61
  - lib/dommy/internal/observer_manager.rb
65
62
  - lib/dommy/internal/observer_matcher.rb
63
+ - lib/dommy/internal/punycode.rb
64
+ - lib/dommy/internal/range_text_serializer.rb
65
+ - lib/dommy/internal/reflected_attributes.rb
66
66
  - lib/dommy/internal/scope_resolution.rb
67
67
  - lib/dommy/internal/shadow_root_registry.rb
68
68
  - lib/dommy/internal/template_content_registry.rb
69
+ - lib/dommy/intersection_observer.rb
70
+ - lib/dommy/location.rb
71
+ - lib/dommy/media_query_list.rb
72
+ - lib/dommy/message_channel.rb
69
73
  - lib/dommy/minitest.rb
70
74
  - lib/dommy/minitest/assertions.rb
75
+ - lib/dommy/mutation_observer.rb
71
76
  - lib/dommy/navigator.rb
72
77
  - lib/dommy/node.rb
73
- - lib/dommy/observer.rb
78
+ - lib/dommy/notification.rb
74
79
  - lib/dommy/parser.rb
80
+ - lib/dommy/performance.rb
81
+ - lib/dommy/performance_observer.rb
75
82
  - lib/dommy/promise.rb
76
- - lib/dommy/router.rb
83
+ - lib/dommy/range.rb
84
+ - lib/dommy/resize_observer.rb
77
85
  - lib/dommy/rspec.rb
78
86
  - lib/dommy/rspec/capy_style_matchers.rb
79
87
  - lib/dommy/rspec/matchers.rb
80
88
  - lib/dommy/scheduler.rb
81
89
  - lib/dommy/shadow_root.rb
82
90
  - lib/dommy/storage.rb
91
+ - lib/dommy/streams.rb
92
+ - lib/dommy/svg_elements.rb
83
93
  - lib/dommy/test_helpers.rb
94
+ - lib/dommy/text_codec.rb
84
95
  - lib/dommy/tree_walker.rb
85
96
  - lib/dommy/url.rb
97
+ - lib/dommy/url_pattern.rb
86
98
  - lib/dommy/version.rb
87
- - lib/dommy/world.rb
99
+ - lib/dommy/web_socket.rb
100
+ - lib/dommy/window.rb
101
+ - lib/dommy/worker.rb
102
+ - lib/dommy/xml_http_request.rb
88
103
  homepage: https://github.com/takahashim/dommy
89
104
  licenses:
90
105
  - MIT
data/lib/dommy/world.rb DELETED
@@ -1,209 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "cgi"
4
- require "erb"
5
-
6
- # Dommy — a happy-dom-style DOM polyfill in pure Ruby. Backbone is
7
- # Nokogiri::HTML5 plus a small scheduler/event-loop layer.
8
- #
9
- # Two views into the same objects:
10
- # - Public Ruby API (snake_case methods like `text_content`,
11
- # `append_child`) for CRuby users writing tests against rendered
12
- # HTML.
13
- # - `__js_get__` / `__js_set__` / `__js_call__` / `__js_new__`
14
- # bridge protocol for JS bridge embedders — dispatches into the
15
- # same underlying Ruby methods.
16
- module Dommy
17
- # The browser global. `JS.global` from inside wasm resolves to this.
18
- # Property access (`JS.global[:document]`, `JS.global[:console]`) is
19
- # routed through `#__js_get__`. Method calls (`JS.global.call(:foo)`)
20
- # are routed through `#__js_call__`.
21
- class Window
22
- include EventTarget
23
-
24
- attr_reader :document, :scheduler, :location, :globals, :custom_elements, :navigator
25
-
26
- def initialize(host = nil, nokogiri_doc: nil)
27
- @host = host
28
- @scheduler = Scheduler.new
29
- @event_ctor = Bridge::Constructor.new { |args| Event.new(args[0], args[1]) }
30
- @custom_event_ctor = Bridge::Constructor.new { |args| CustomEvent.new(args[0], args[1]) }
31
- @mouse_event_ctor = Bridge::Constructor.new { |args| MouseEvent.new(args[0], args[1]) }
32
- @keyboard_event_ctor = Bridge::Constructor.new { |args| KeyboardEvent.new(args[0], args[1]) }
33
- @event_target_ctor = Bridge::Constructor.new { |_args| StandaloneEventTarget.new }
34
- @error_ctor = Bridge::Constructor.new { |args| ErrorValue.new(args[0]) }
35
- @promise_ctor = Bridge::PromiseConstructor.new(self)
36
- @mutation_observer_ctor = Bridge::Constructor.new { |args| MutationObserver.new(self, args[0]) }
37
- @abort_controller_ctor = Bridge::Constructor.new { |_args| AbortController.new }
38
- @blob_ctor = Bridge::Constructor.new { |args| Blob.new(args[0] || [], args[1] || {}) }
39
- @file_ctor = Bridge::Constructor.new { |args| File.new(args[0] || [], args[1].to_s, args[2] || {}) }
40
- @file_list_ctor = Bridge::Constructor.new { |args| FileList.new(args[0] || []) }
41
- @data_transfer_ctor = Bridge::Constructor.new { |args|
42
- opts = args[0] || {}
43
- DataTransfer.new(
44
- files: opts["files"] || opts[:files] || [],
45
- data: opts["data"] || opts[:data] || {}
46
- )
47
- }
48
- @drag_event_ctor = Bridge::Constructor.new { |args| DragEvent.new(args[0], args[1]) }
49
- @local_storage = Storage.new
50
- @session_storage = Storage.new
51
- @location = Location.new(self)
52
- @history = History.new(self, @location)
53
- @url_ctor = Bridge::Constructor.new { |args| Url.new(args[0], args[1]) }
54
- @url_ctor.define_class_method("createObjectURL") { |args| URL.create_object_url(args[0]) }
55
- @url_ctor.define_class_method("revokeObjectURL") { |args| URL.revoke_object_url(args[0]) }
56
- # `JS.global[:__some_key__] = ...` from user code lands here.
57
- # Test code uses this for stub installation (e.g. a custom
58
- # `__fetch_stub__`); production code stays on the typed
59
- # accessors above. We keep it last in the read fallback to
60
- # avoid shadowing intentional getters.
61
- @globals = {}
62
- @document = Document.new(host, nokogiri_doc: nokogiri_doc)
63
- @document.default_view = self
64
- @custom_elements = CustomElementRegistry.new(self)
65
- @navigator = Navigator.new(self)
66
- end
67
-
68
- # Bridge protocol: respond to a JS-style property read by name.
69
- # Returns either a Ruby primitive (Integer / String / true / false /
70
- # nil), a Hash/Array (for JS object/array literals), or a Dom::*
71
- # instance for live DOM/BOM objects.
72
- #
73
- # Anything outside the surface we've explicitly polyfilled returns
74
- # nil (= JS undefined). Spec failures here are the signal to widen
75
- # the surface in a future session.
76
- def __js_get__(key)
77
- case key
78
- when "document"
79
- @document
80
- when "Event"
81
- @event_ctor
82
- when "CustomEvent"
83
- @custom_event_ctor
84
- when "MouseEvent"
85
- @mouse_event_ctor
86
- when "KeyboardEvent"
87
- @keyboard_event_ctor
88
- when "EventTarget"
89
- @event_target_ctor
90
- when "Error"
91
- @error_ctor
92
- when "Promise"
93
- @promise_ctor
94
- when "MutationObserver"
95
- @mutation_observer_ctor
96
- when "AbortController"
97
- @abort_controller_ctor
98
- when "Blob"
99
- @blob_ctor
100
- when "File"
101
- @file_ctor
102
- when "FileList"
103
- @file_list_ctor
104
- when "DataTransfer"
105
- @data_transfer_ctor
106
- when "DragEvent"
107
- @drag_event_ctor
108
- # handled by Symbol sentinel
109
- when "console"
110
- :console
111
- # likewise
112
- when "Object"
113
- :object_ctor
114
- when "Array"
115
- :array_ctor
116
- when "JSON"
117
- :json_ctor
118
- when "performance"
119
- {"now" => @scheduler.now_ms.to_f}
120
- when "localStorage"
121
- @local_storage
122
- when "sessionStorage"
123
- @session_storage
124
- when "location"
125
- @location
126
- when "history"
127
- @history
128
- when "URL"
129
- @url_ctor
130
- when "fetch"
131
- FetchFn.new(self)
132
- when "customElements"
133
- @custom_elements
134
- when "navigator"
135
- @navigator
136
- else
137
- @globals[key]
138
- end
139
- end
140
-
141
- def __js_set__(key, value)
142
- # Stash arbitrary keys for later reads (e.g.
143
- # `JS.global[:__fetchy_stub__] = map`).
144
- @globals[key] = value
145
- # The Fetchy spec's `install_fetch_stub` resets `__fetch_count__`
146
- # to 0 inside its JS installer (`globalThis.__fetch_count__ = 0;
147
- # globalThis.fetch = ...`). Our polyfill ignores raw JS, so we
148
- # piggy-back on the stub assignment to perform the same reset
149
- # — without it the count accumulates across tests in one VM run.
150
- @globals["__fetch_count__"] = 0 if %w[__fetchy_stub__ __resource_fetch_stub__ __inject_fetch_stub__].include?(key)
151
- nil
152
- end
153
-
154
- def __js_call__(method, args)
155
- case method
156
- when "fetch"
157
- FetchFn.new(self).__js_call__("call", args)
158
- when "encodeURIComponent"
159
- # JS spec encoding: percent-encode anything except
160
- # `A-Za-z0-9 - _ . ! ~ * ' ( )`. Ruby's `CGI.escape` uses
161
- # `+` for space; ERB::Util.url_encode matches JS behavior.
162
- ERB::Util.url_encode(args[0].to_s)
163
- when "decodeURIComponent"
164
- CGI.unescape(args[0].to_s)
165
- when "addEventListener"
166
- add_event_listener(args[0], args[1], args[2])
167
- when "removeEventListener"
168
- remove_event_listener(args[0], args[1])
169
- when "dispatchEvent"
170
- dispatch_event(args[0])
171
- when "setTimeout"
172
- @scheduler.set_timeout(args[0], args[1] || 0)
173
- when "clearTimeout"
174
- @scheduler.clear_timeout(args[0])
175
- when "setInterval"
176
- @scheduler.set_interval(args[0], args[1] || 0)
177
- when "clearInterval"
178
- @scheduler.clear_interval(args[0])
179
- when "requestAnimationFrame"
180
- @scheduler.request_animation_frame(args[0])
181
- when "cancelAnimationFrame"
182
- @scheduler.cancel_animation_frame(args[0])
183
- when "queueMicrotask"
184
- @scheduler.queue_microtask(args[0])
185
- else
186
- # Additional window-level methods (fetch, location, history,
187
- # Promise, MutationObserver, etc.) arrive in later sessions.
188
- nil
189
- end
190
- end
191
-
192
- def __event_parent__
193
- nil
194
- end
195
-
196
- # Called by History#go and Location.href= to fire popstate /
197
- # hashchange events. Listeners registered on the Window via
198
- # `addEventListener("popstate"|"hashchange", cb)` receive them.
199
- def fire_popstate(state)
200
- event = CustomEvent.new("popstate", "detail" => state)
201
- dispatch_event(event)
202
- end
203
-
204
- def fire_hashchange(old_hash, new_hash)
205
- event = CustomEvent.new("hashchange", "detail" => {"oldURL" => old_hash, "newURL" => new_hash})
206
- dispatch_event(event)
207
- end
208
- end
209
- end