dommy 0.5.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 +7 -0
- data/README.md +213 -0
- data/lib/dommy/attr.rb +200 -0
- data/lib/dommy/blob.rb +182 -0
- data/lib/dommy/bridge.rb +141 -0
- data/lib/dommy/css.rb +283 -0
- data/lib/dommy/custom_elements.rb +125 -0
- data/lib/dommy/data_transfer.rb +98 -0
- data/lib/dommy/document.rb +674 -0
- data/lib/dommy/dom_exception.rb +258 -0
- data/lib/dommy/dom_parser.rb +88 -0
- data/lib/dommy/element.rb +1975 -0
- data/lib/dommy/event.rb +589 -0
- data/lib/dommy/fetch.rb +241 -0
- data/lib/dommy/form_data.rb +208 -0
- data/lib/dommy/html_collection.rb +207 -0
- data/lib/dommy/html_elements.rb +4455 -0
- data/lib/dommy/internal/cookie_jar.rb +27 -0
- data/lib/dommy/internal/dom_matching.rb +141 -0
- data/lib/dommy/internal/mutation_coordinator.rb +172 -0
- data/lib/dommy/internal/node_traversal.rb +36 -0
- data/lib/dommy/internal/node_wrapper_cache.rb +179 -0
- data/lib/dommy/internal/observer_manager.rb +31 -0
- data/lib/dommy/internal/observer_matcher.rb +31 -0
- data/lib/dommy/internal/scope_resolution.rb +27 -0
- data/lib/dommy/internal/shadow_root_registry.rb +35 -0
- data/lib/dommy/internal/template_content_registry.rb +97 -0
- data/lib/dommy/minitest/assertions.rb +105 -0
- data/lib/dommy/minitest.rb +17 -0
- data/lib/dommy/navigator.rb +271 -0
- data/lib/dommy/node.rb +218 -0
- data/lib/dommy/observer.rb +199 -0
- data/lib/dommy/parser.rb +29 -0
- data/lib/dommy/promise.rb +199 -0
- data/lib/dommy/router.rb +275 -0
- data/lib/dommy/rspec/capy_style_matchers.rb +356 -0
- data/lib/dommy/rspec/matchers.rb +230 -0
- data/lib/dommy/rspec.rb +18 -0
- data/lib/dommy/scheduler.rb +135 -0
- data/lib/dommy/shadow_root.rb +255 -0
- data/lib/dommy/storage.rb +112 -0
- data/lib/dommy/test_helpers.rb +78 -0
- data/lib/dommy/tree_walker.rb +425 -0
- data/lib/dommy/url.rb +479 -0
- data/lib/dommy/version.rb +5 -0
- data/lib/dommy/world.rb +209 -0
- data/lib/dommy.rb +119 -0
- metadata +110 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dommy
|
|
4
|
+
# NodeFilter constants — bitmasks for `whatToShow` and return values
|
|
5
|
+
# for the optional filter callable. Standard DOM Level 2 Traversal.
|
|
6
|
+
module NodeFilter
|
|
7
|
+
SHOW_ALL = 0xFFFFFFFF
|
|
8
|
+
SHOW_ELEMENT = 0x1
|
|
9
|
+
SHOW_ATTRIBUTE = 0x2
|
|
10
|
+
SHOW_TEXT = 0x4
|
|
11
|
+
SHOW_CDATA_SECTION = 0x8
|
|
12
|
+
SHOW_PROCESSING_INSTRUCTION = 0x40
|
|
13
|
+
SHOW_COMMENT = 0x80
|
|
14
|
+
SHOW_DOCUMENT = 0x100
|
|
15
|
+
SHOW_DOCUMENT_TYPE = 0x200
|
|
16
|
+
SHOW_DOCUMENT_FRAGMENT = 0x400
|
|
17
|
+
|
|
18
|
+
FILTER_ACCEPT = 1
|
|
19
|
+
FILTER_REJECT = 2
|
|
20
|
+
FILTER_SKIP = 3
|
|
21
|
+
|
|
22
|
+
# Map a wrapped Dommy node to its NodeFilter bitmask. Returns 0
|
|
23
|
+
# for unknown node types (effectively "doesn't pass any filter").
|
|
24
|
+
def self.bitmask_for(node)
|
|
25
|
+
case node
|
|
26
|
+
when Element
|
|
27
|
+
SHOW_ELEMENT
|
|
28
|
+
when TextNode
|
|
29
|
+
SHOW_TEXT
|
|
30
|
+
when CommentNode
|
|
31
|
+
SHOW_COMMENT
|
|
32
|
+
when Fragment
|
|
33
|
+
SHOW_DOCUMENT_FRAGMENT
|
|
34
|
+
when Document
|
|
35
|
+
SHOW_DOCUMENT
|
|
36
|
+
when DocumentType
|
|
37
|
+
SHOW_DOCUMENT_TYPE
|
|
38
|
+
else
|
|
39
|
+
0
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Shared helpers between TreeWalker and NodeIterator. Both walk the
|
|
45
|
+
# tree rooted at `root` and filter by `whatToShow` + an optional
|
|
46
|
+
# filter callable (or object with `acceptNode`).
|
|
47
|
+
module TreeTraversalCore
|
|
48
|
+
# Returns FILTER_ACCEPT / FILTER_REJECT / FILTER_SKIP for the
|
|
49
|
+
# given wrapped node.
|
|
50
|
+
def __accept__(node)
|
|
51
|
+
return NodeFilter::FILTER_REJECT unless node
|
|
52
|
+
return NodeFilter::FILTER_SKIP if (NodeFilter.bitmask_for(node) & @what_to_show) == 0
|
|
53
|
+
|
|
54
|
+
result = invoke_filter(node)
|
|
55
|
+
result || NodeFilter::FILTER_ACCEPT
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def invoke_filter(node)
|
|
61
|
+
return NodeFilter::FILTER_ACCEPT if @filter.nil?
|
|
62
|
+
|
|
63
|
+
if @filter.respond_to?(:accept_node)
|
|
64
|
+
@filter.accept_node(node)
|
|
65
|
+
elsif @filter.respond_to?(:call)
|
|
66
|
+
@filter.call(node)
|
|
67
|
+
else
|
|
68
|
+
NodeFilter::FILTER_ACCEPT
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# TreeWalker — stateful traversal with `next_node` / `previous_node`
|
|
74
|
+
# / `parent_node` / `first_child` / `last_child` / `next_sibling` /
|
|
75
|
+
# `previous_sibling` and a mutable `current_node` cursor.
|
|
76
|
+
#
|
|
77
|
+
# Wraps Nokogiri descent; doesn't snapshot the tree, so mutations
|
|
78
|
+
# during traversal are visible (matches DOM spec).
|
|
79
|
+
class TreeWalker
|
|
80
|
+
include TreeTraversalCore
|
|
81
|
+
|
|
82
|
+
attr_reader :root, :what_to_show, :filter
|
|
83
|
+
attr_accessor :current_node
|
|
84
|
+
|
|
85
|
+
def initialize(root, what_to_show = NodeFilter::SHOW_ALL, filter = nil)
|
|
86
|
+
@root = root
|
|
87
|
+
@what_to_show = what_to_show.to_i
|
|
88
|
+
@filter = filter
|
|
89
|
+
@current_node = root
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def next_node
|
|
93
|
+
node = first_descendant_or_following(@current_node)
|
|
94
|
+
while node
|
|
95
|
+
verdict = __accept__(node)
|
|
96
|
+
if verdict == NodeFilter::FILTER_ACCEPT
|
|
97
|
+
@current_node = node
|
|
98
|
+
return node
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
node = (verdict == NodeFilter::FILTER_REJECT) ? following_skip_subtree(node) : first_descendant_or_following(
|
|
102
|
+
node
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def previous_node
|
|
110
|
+
node = preceding(@current_node)
|
|
111
|
+
while node && node != @root
|
|
112
|
+
verdict = __accept__(node)
|
|
113
|
+
if verdict == NodeFilter::FILTER_ACCEPT
|
|
114
|
+
@current_node = node
|
|
115
|
+
return node
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
node = preceding(node)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def parent_node
|
|
125
|
+
node = wrapped_parent(@current_node)
|
|
126
|
+
while node && reachable_from_root?(node)
|
|
127
|
+
return @current_node = node if __accept__(node) == NodeFilter::FILTER_ACCEPT
|
|
128
|
+
|
|
129
|
+
node = wrapped_parent(node)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def first_child
|
|
136
|
+
first = first_wrapped_child(@current_node)
|
|
137
|
+
walk_siblings(first, :next_sibling_wrapped)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def last_child
|
|
141
|
+
last = last_wrapped_child(@current_node)
|
|
142
|
+
walk_siblings(last, :previous_sibling_wrapped)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def next_sibling
|
|
146
|
+
walk_siblings(next_sibling_wrapped(@current_node), :next_sibling_wrapped)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def previous_sibling
|
|
150
|
+
walk_siblings(previous_sibling_wrapped(@current_node), :previous_sibling_wrapped)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def __js_get__(key)
|
|
154
|
+
case key
|
|
155
|
+
when "root"
|
|
156
|
+
@root
|
|
157
|
+
when "whatToShow"
|
|
158
|
+
@what_to_show
|
|
159
|
+
when "filter"
|
|
160
|
+
@filter
|
|
161
|
+
when "currentNode"
|
|
162
|
+
@current_node
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def __js_set__(key, value)
|
|
167
|
+
@current_node = value if key == "currentNode"
|
|
168
|
+
nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def __js_call__(method, _args)
|
|
172
|
+
case method
|
|
173
|
+
when "nextNode"
|
|
174
|
+
next_node
|
|
175
|
+
when "previousNode"
|
|
176
|
+
previous_node
|
|
177
|
+
when "parentNode"
|
|
178
|
+
parent_node
|
|
179
|
+
when "firstChild"
|
|
180
|
+
first_child
|
|
181
|
+
when "lastChild"
|
|
182
|
+
last_child
|
|
183
|
+
when "nextSibling"
|
|
184
|
+
next_sibling
|
|
185
|
+
when "previousSibling"
|
|
186
|
+
previous_sibling
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
|
|
192
|
+
def walk_siblings(start, direction)
|
|
193
|
+
node = start
|
|
194
|
+
while node
|
|
195
|
+
v = __accept__(node)
|
|
196
|
+
return @current_node = node if v == NodeFilter::FILTER_ACCEPT
|
|
197
|
+
|
|
198
|
+
node = (v == NodeFilter::FILTER_REJECT) ? nil : send(direction, node)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
nil
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def first_descendant_or_following(node)
|
|
205
|
+
child = first_wrapped_child(node)
|
|
206
|
+
return child if child
|
|
207
|
+
|
|
208
|
+
following_skip_subtree(node)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def following_skip_subtree(node)
|
|
212
|
+
current = node
|
|
213
|
+
while current && current != @root
|
|
214
|
+
sib = next_sibling_wrapped(current)
|
|
215
|
+
return sib if sib
|
|
216
|
+
|
|
217
|
+
current = wrapped_parent(current)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
nil
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def preceding(node)
|
|
224
|
+
sib = previous_sibling_wrapped(node)
|
|
225
|
+
if sib
|
|
226
|
+
node = sib
|
|
227
|
+
while (last = last_wrapped_child(node))
|
|
228
|
+
node = last
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
return node
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
wrapped_parent(node)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def reachable_from_root?(node)
|
|
238
|
+
current = node
|
|
239
|
+
while current
|
|
240
|
+
return true if current == @root
|
|
241
|
+
|
|
242
|
+
current = wrapped_parent(current)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
false
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def wrapped_parent(node)
|
|
249
|
+
parent_nk = node.respond_to?(:__node__) ? node.__node__.parent : nil
|
|
250
|
+
return nil unless parent_nk && !parent_nk.is_a?(Nokogiri::XML::Document)
|
|
251
|
+
|
|
252
|
+
doc = node.instance_variable_get(:@document) || (@root.respond_to?(:document) ? @root.document : @root)
|
|
253
|
+
doc.wrap_node(parent_nk)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def first_wrapped_child(node)
|
|
257
|
+
child_nk = node.respond_to?(:__node__) ? node.__node__.children.first : nil
|
|
258
|
+
child_nk && document_for(node).wrap_node(child_nk)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def last_wrapped_child(node)
|
|
262
|
+
child_nk = node.respond_to?(:__node__) ? node.__node__.children.last : nil
|
|
263
|
+
child_nk && document_for(node).wrap_node(child_nk)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def next_sibling_wrapped(node)
|
|
267
|
+
n = node.respond_to?(:__node__) ? node.__node__.next : nil
|
|
268
|
+
n && document_for(node).wrap_node(n)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def previous_sibling_wrapped(node)
|
|
272
|
+
n = node.respond_to?(:__node__) ? node.__node__.previous : nil
|
|
273
|
+
n && document_for(node).wrap_node(n)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def document_for(node)
|
|
277
|
+
node.instance_variable_get(:@document) || @root.instance_variable_get(:@document) || @root
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# NodeIterator — flat-list traversal. Same filter semantics as
|
|
282
|
+
# TreeWalker but no sibling/parent navigation, just `next_node` /
|
|
283
|
+
# `previous_node` over a depth-first sequence anchored to `root`.
|
|
284
|
+
class NodeIterator
|
|
285
|
+
include TreeTraversalCore
|
|
286
|
+
|
|
287
|
+
attr_reader :root, :what_to_show, :filter
|
|
288
|
+
|
|
289
|
+
def initialize(root, what_to_show = NodeFilter::SHOW_ALL, filter = nil)
|
|
290
|
+
@root = root
|
|
291
|
+
@what_to_show = what_to_show.to_i
|
|
292
|
+
@filter = filter
|
|
293
|
+
@reference_node = root
|
|
294
|
+
@pointer_before_reference = true
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def next_node
|
|
298
|
+
loop do
|
|
299
|
+
node = if @pointer_before_reference
|
|
300
|
+
@reference_node
|
|
301
|
+
else
|
|
302
|
+
next_in_document_order(@reference_node)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
return nil unless node
|
|
306
|
+
|
|
307
|
+
@reference_node = node
|
|
308
|
+
@pointer_before_reference = false
|
|
309
|
+
return node if __accept__(node) == NodeFilter::FILTER_ACCEPT
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def previous_node
|
|
314
|
+
loop do
|
|
315
|
+
node = if @pointer_before_reference
|
|
316
|
+
previous_in_document_order(@reference_node)
|
|
317
|
+
else
|
|
318
|
+
@reference_node
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
return nil unless node
|
|
322
|
+
|
|
323
|
+
@reference_node = node
|
|
324
|
+
@pointer_before_reference = true
|
|
325
|
+
return node if __accept__(node) == NodeFilter::FILTER_ACCEPT
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def detach
|
|
330
|
+
nil
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def __js_get__(key)
|
|
334
|
+
case key
|
|
335
|
+
when "root"
|
|
336
|
+
@root
|
|
337
|
+
when "whatToShow"
|
|
338
|
+
@what_to_show
|
|
339
|
+
when "filter"
|
|
340
|
+
@filter
|
|
341
|
+
when "referenceNode"
|
|
342
|
+
@reference_node
|
|
343
|
+
when "pointerBeforeReferenceNode"
|
|
344
|
+
@pointer_before_reference
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def __js_call__(method, _args)
|
|
349
|
+
case method
|
|
350
|
+
when "nextNode"
|
|
351
|
+
next_node
|
|
352
|
+
when "previousNode"
|
|
353
|
+
previous_node
|
|
354
|
+
when "detach"
|
|
355
|
+
detach
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
private
|
|
360
|
+
|
|
361
|
+
def next_in_document_order(node)
|
|
362
|
+
return @root if node.nil?
|
|
363
|
+
|
|
364
|
+
child = first_child_node(node)
|
|
365
|
+
return child if child
|
|
366
|
+
|
|
367
|
+
current = node
|
|
368
|
+
while current && current != @root
|
|
369
|
+
sib = next_sibling_node(current)
|
|
370
|
+
return sib if sib
|
|
371
|
+
|
|
372
|
+
current = parent_node_of(current)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
nil
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def previous_in_document_order(node)
|
|
379
|
+
return nil if node.nil? || node == @root
|
|
380
|
+
|
|
381
|
+
sib = previous_sibling_node(node)
|
|
382
|
+
if sib
|
|
383
|
+
node = sib
|
|
384
|
+
while (last = last_child_node(node))
|
|
385
|
+
node = last
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
return node
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
parent_node_of(node)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def first_child_node(node)
|
|
395
|
+
n = node.respond_to?(:__node__) ? node.__node__.children.first : nil
|
|
396
|
+
n && document_for(node).wrap_node(n)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def last_child_node(node)
|
|
400
|
+
n = node.respond_to?(:__node__) ? node.__node__.children.last : nil
|
|
401
|
+
n && document_for(node).wrap_node(n)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def next_sibling_node(node)
|
|
405
|
+
n = node.respond_to?(:__node__) ? node.__node__.next : nil
|
|
406
|
+
n && document_for(node).wrap_node(n)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def previous_sibling_node(node)
|
|
410
|
+
n = node.respond_to?(:__node__) ? node.__node__.previous : nil
|
|
411
|
+
n && document_for(node).wrap_node(n)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def parent_node_of(node)
|
|
415
|
+
parent_nk = node.respond_to?(:__node__) ? node.__node__.parent : nil
|
|
416
|
+
return nil unless parent_nk && !parent_nk.is_a?(Nokogiri::XML::Document)
|
|
417
|
+
|
|
418
|
+
document_for(node).wrap_node(parent_nk)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def document_for(node)
|
|
422
|
+
node.instance_variable_get(:@document) || @root.instance_variable_get(:@document) || @root
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
end
|