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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +213 -0
  3. data/lib/dommy/attr.rb +200 -0
  4. data/lib/dommy/blob.rb +182 -0
  5. data/lib/dommy/bridge.rb +141 -0
  6. data/lib/dommy/css.rb +283 -0
  7. data/lib/dommy/custom_elements.rb +125 -0
  8. data/lib/dommy/data_transfer.rb +98 -0
  9. data/lib/dommy/document.rb +674 -0
  10. data/lib/dommy/dom_exception.rb +258 -0
  11. data/lib/dommy/dom_parser.rb +88 -0
  12. data/lib/dommy/element.rb +1975 -0
  13. data/lib/dommy/event.rb +589 -0
  14. data/lib/dommy/fetch.rb +241 -0
  15. data/lib/dommy/form_data.rb +208 -0
  16. data/lib/dommy/html_collection.rb +207 -0
  17. data/lib/dommy/html_elements.rb +4455 -0
  18. data/lib/dommy/internal/cookie_jar.rb +27 -0
  19. data/lib/dommy/internal/dom_matching.rb +141 -0
  20. data/lib/dommy/internal/mutation_coordinator.rb +172 -0
  21. data/lib/dommy/internal/node_traversal.rb +36 -0
  22. data/lib/dommy/internal/node_wrapper_cache.rb +179 -0
  23. data/lib/dommy/internal/observer_manager.rb +31 -0
  24. data/lib/dommy/internal/observer_matcher.rb +31 -0
  25. data/lib/dommy/internal/scope_resolution.rb +27 -0
  26. data/lib/dommy/internal/shadow_root_registry.rb +35 -0
  27. data/lib/dommy/internal/template_content_registry.rb +97 -0
  28. data/lib/dommy/minitest/assertions.rb +105 -0
  29. data/lib/dommy/minitest.rb +17 -0
  30. data/lib/dommy/navigator.rb +271 -0
  31. data/lib/dommy/node.rb +218 -0
  32. data/lib/dommy/observer.rb +199 -0
  33. data/lib/dommy/parser.rb +29 -0
  34. data/lib/dommy/promise.rb +199 -0
  35. data/lib/dommy/router.rb +275 -0
  36. data/lib/dommy/rspec/capy_style_matchers.rb +356 -0
  37. data/lib/dommy/rspec/matchers.rb +230 -0
  38. data/lib/dommy/rspec.rb +18 -0
  39. data/lib/dommy/scheduler.rb +135 -0
  40. data/lib/dommy/shadow_root.rb +255 -0
  41. data/lib/dommy/storage.rb +112 -0
  42. data/lib/dommy/test_helpers.rb +78 -0
  43. data/lib/dommy/tree_walker.rb +425 -0
  44. data/lib/dommy/url.rb +479 -0
  45. data/lib/dommy/version.rb +5 -0
  46. data/lib/dommy/world.rb +209 -0
  47. data/lib/dommy.rb +119 -0
  48. 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