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,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
|