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,258 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dommy
4
+ # `DOMException` — base class for DOM-spec errors. Subclasses match
5
+ # the well-known names from the WebIDL spec; each carries the
6
+ # legacy `code` integer (0 for names introduced after the legacy
7
+ # code table) and the canonical `name`.
8
+ #
9
+ # Use:
10
+ # raise Dommy::DOMException::IndexSizeError, "index 5 out of range"
11
+ # rescue Dommy::DOMException => e
12
+ # e.name # => "IndexSizeError"
13
+ # e.code # => 1
14
+ # e.message # => "index 5 out of range"
15
+ # e.to_s # => "IndexSizeError: index 5 out of range"
16
+ #
17
+ # The 2-arg form mirrors JS `new DOMException(message, name)`:
18
+ # Dommy::DOMException.new("bad input", "SyntaxError")
19
+ # which constructs a base DOMException carrying the supplied name —
20
+ # useful when the name is dynamic and you don't have a subclass.
21
+ #
22
+ # Inherits from StandardError so generic `rescue => e` catches them.
23
+ class DOMException < StandardError
24
+ NAME = "Error"
25
+ CODE = 0
26
+
27
+ # Legacy-name → numeric-code map. Used only by the 2-arg
28
+ # `new DOMException(msg, name)` form when the supplied name doesn't
29
+ # match the current subclass. Subclass-direct construction reads
30
+ # `self.class::CODE` and ignores this map.
31
+ LEGACY_CODES = {
32
+ "IndexSizeError" => 1,
33
+ "HierarchyRequestError" => 3,
34
+ "WrongDocumentError" => 4,
35
+ "InvalidCharacterError" => 5,
36
+ "NoModificationAllowedError" => 7,
37
+ "NotFoundError" => 8,
38
+ "NotSupportedError" => 9,
39
+ "InUseAttributeError" => 10,
40
+ "InvalidStateError" => 11,
41
+ "SyntaxError" => 12,
42
+ "InvalidModificationError" => 13,
43
+ "NamespaceError" => 14,
44
+ "InvalidAccessError" => 15,
45
+ "TypeMismatchError" => 17,
46
+ "SecurityError" => 18,
47
+ "NetworkError" => 19,
48
+ "AbortError" => 20,
49
+ "URLMismatchError" => 21,
50
+ "QuotaExceededError" => 22,
51
+ "TimeoutError" => 23,
52
+ "InvalidNodeTypeError" => 24,
53
+ "DataCloneError" => 25
54
+ }.freeze
55
+
56
+ def initialize(message = "", name = nil)
57
+ @__message = message.to_s
58
+ super(@__message)
59
+ @explicit_name = name&.to_s
60
+ end
61
+
62
+ def message
63
+ @__message.to_s
64
+ end
65
+
66
+ def name
67
+ @explicit_name || self.class::NAME
68
+ end
69
+
70
+ # When constructed on the base class with a dynamic name, derive
71
+ # the code from LEGACY_CODES (0 for unknown). When constructed via
72
+ # a subclass, always return that subclass's CODE so the legacy
73
+ # number is preserved even if the caller passed a different name.
74
+ def code
75
+ return self.class::CODE if self.class != DOMException || @explicit_name.nil?
76
+
77
+ LEGACY_CODES[@explicit_name] || 0
78
+ end
79
+
80
+ # JS exposes `e.name`, `e.code`, `e.message`.
81
+ def __js_get__(key)
82
+ case key
83
+ when "name"
84
+ name
85
+ when "code"
86
+ code
87
+ when "message"
88
+ message
89
+ end
90
+ end
91
+
92
+ def to_s
93
+ "#{name}: #{@__message}"
94
+ end
95
+
96
+ class IndexSizeError < self
97
+ NAME = "IndexSizeError"
98
+ CODE = 1
99
+ end
100
+
101
+ class HierarchyRequestError < self
102
+ NAME = "HierarchyRequestError"
103
+ CODE = 3
104
+ end
105
+
106
+ class WrongDocumentError < self
107
+ NAME = "WrongDocumentError"
108
+ CODE = 4
109
+ end
110
+
111
+ class InvalidCharacterError < self
112
+ NAME = "InvalidCharacterError"
113
+ CODE = 5
114
+ end
115
+
116
+ class NoModificationAllowedError < self
117
+ NAME = "NoModificationAllowedError"
118
+ CODE = 7
119
+ end
120
+
121
+ class NotFoundError < self
122
+ NAME = "NotFoundError"
123
+ CODE = 8
124
+ end
125
+
126
+ class NotSupportedError < self
127
+ NAME = "NotSupportedError"
128
+ CODE = 9
129
+ end
130
+
131
+ class InUseAttributeError < self
132
+ NAME = "InUseAttributeError"
133
+ CODE = 10
134
+ end
135
+
136
+ class InvalidStateError < self
137
+ NAME = "InvalidStateError"
138
+ CODE = 11
139
+ end
140
+
141
+ class SyntaxError < self
142
+ NAME = "SyntaxError"
143
+ CODE = 12
144
+ end
145
+
146
+ class InvalidModificationError < self
147
+ NAME = "InvalidModificationError"
148
+ CODE = 13
149
+ end
150
+
151
+ class NamespaceError < self
152
+ NAME = "NamespaceError"
153
+ CODE = 14
154
+ end
155
+
156
+ class InvalidAccessError < self
157
+ NAME = "InvalidAccessError"
158
+ CODE = 15
159
+ end
160
+
161
+ class TypeMismatchError < self
162
+ NAME = "TypeMismatchError"
163
+ CODE = 17
164
+ end
165
+
166
+ class SecurityError < self
167
+ NAME = "SecurityError"
168
+ CODE = 18
169
+ end
170
+
171
+ class NetworkError < self
172
+ NAME = "NetworkError"
173
+ CODE = 19
174
+ end
175
+
176
+ class AbortError < self
177
+ NAME = "AbortError"
178
+ CODE = 20
179
+ end
180
+
181
+ class URLMismatchError < self
182
+ NAME = "URLMismatchError"
183
+ CODE = 21
184
+ end
185
+
186
+ class QuotaExceededError < self
187
+ NAME = "QuotaExceededError"
188
+ CODE = 22
189
+ end
190
+
191
+ class TimeoutError < self
192
+ NAME = "TimeoutError"
193
+ CODE = 23
194
+ end
195
+
196
+ class InvalidNodeTypeError < self
197
+ NAME = "InvalidNodeTypeError"
198
+ CODE = 24
199
+ end
200
+
201
+ class DataCloneError < self
202
+ NAME = "DataCloneError"
203
+ CODE = 25
204
+ end
205
+
206
+ # Modern names without legacy codes (code is 0). Listed here so
207
+ # `rescue Dommy::DOMException::EncodingError` works.
208
+ class EncodingError < self
209
+ NAME = "EncodingError"
210
+ CODE = 0
211
+ end
212
+
213
+ class NotReadableError < self
214
+ NAME = "NotReadableError"
215
+ CODE = 0
216
+ end
217
+
218
+ class UnknownError < self
219
+ NAME = "UnknownError"
220
+ CODE = 0
221
+ end
222
+
223
+ class ConstraintError < self
224
+ NAME = "ConstraintError"
225
+ CODE = 0
226
+ end
227
+
228
+ class DataError < self
229
+ NAME = "DataError"
230
+ CODE = 0
231
+ end
232
+
233
+ class TransactionInactiveError < self
234
+ NAME = "TransactionInactiveError"
235
+ CODE = 0
236
+ end
237
+
238
+ class ReadOnlyError < self
239
+ NAME = "ReadOnlyError"
240
+ CODE = 0
241
+ end
242
+
243
+ class VersionError < self
244
+ NAME = "VersionError"
245
+ CODE = 0
246
+ end
247
+
248
+ class OperationError < self
249
+ NAME = "OperationError"
250
+ CODE = 0
251
+ end
252
+
253
+ class NotAllowedError < self
254
+ NAME = "NotAllowedError"
255
+ CODE = 0
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dommy
4
+ # `DOMParser` — public-facing parser entry point. Parses an HTML or
5
+ # XML string into a fresh `Dommy::Document`. Per spec, JS code
6
+ # often does:
7
+ #
8
+ # const doc = new DOMParser().parseFromString(html, "text/html");
9
+ #
10
+ # Supported mime types:
11
+ # - `text/html` (full HTML page)
12
+ # - `application/xhtml+xml`/`application/xml`/`text/xml`/`image/svg+xml`
13
+ # all delegate to Nokogiri's XML parser
14
+ #
15
+ # The returned Document has no `defaultView` (not attached to a
16
+ # Window). Useful for fragment parsing where you want a Document
17
+ # without spinning up a Window.
18
+ class DOMParser
19
+ def parse_from_string(string, mime_type = "text/html")
20
+ str = string.to_s
21
+ case mime_type.to_s.downcase
22
+ when "text/html", ""
23
+ parse_html(str)
24
+ when "application/xhtml+xml", "application/xml", "text/xml", "image/svg+xml"
25
+ parse_xml(str)
26
+ else
27
+ raise DOMException::TypeMismatchError, "Unsupported mime type: #{mime_type}"
28
+ end
29
+ end
30
+
31
+ alias parseFromString parse_from_string
32
+
33
+ def __js_get__(_key)
34
+ nil
35
+ end
36
+
37
+ def __js_call__(method, args)
38
+ case method
39
+ when "parseFromString"
40
+ parse_from_string(args[0], args[1])
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def parse_html(str)
47
+ nokogiri_doc = Nokogiri::HTML5(str.empty? ? "<html><body></body></html>" : str, max_errors: 0)
48
+ Document.new(nil, nokogiri_doc: nokogiri_doc)
49
+ end
50
+
51
+ def parse_xml(str)
52
+ nokogiri_doc = Nokogiri::XML(str)
53
+ Document.new(nil, nokogiri_doc: nokogiri_doc)
54
+ end
55
+ end
56
+
57
+ # `XMLSerializer` — round-trip a node tree to a string. Used for
58
+ # XML output, SVG inlining, and "serialize this Element" patterns.
59
+ # For HTML, prefer `Element#outer_html` directly.
60
+ class XMLSerializer
61
+ def serialize_to_string(node)
62
+ return "" unless node
63
+
64
+ if node.respond_to?(:outer_html)
65
+ node.outer_html
66
+ elsif node.respond_to?(:__node__)
67
+ node.__node__.to_xml
68
+ elsif node.respond_to?(:to_xml)
69
+ node.to_xml
70
+ else
71
+ node.to_s
72
+ end
73
+ end
74
+
75
+ alias serializeToString serialize_to_string
76
+
77
+ def __js_get__(_key)
78
+ nil
79
+ end
80
+
81
+ def __js_call__(method, args)
82
+ case method
83
+ when "serializeToString"
84
+ serialize_to_string(args[0])
85
+ end
86
+ end
87
+ end
88
+ end