dommy-rails 0.9.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 +129 -0
- data/lib/dommy/rails/aria_snapshot_matching.rb +100 -0
- data/lib/dommy/rails/browser_spec.rb +148 -0
- data/lib/dommy/rails/dom_source.rb +35 -0
- data/lib/dommy/rails/form_inspector.rb +52 -0
- data/lib/dommy/rails/lint.rb +109 -0
- data/lib/dommy/rails/mail_part.rb +32 -0
- data/lib/dommy/rails/match_target.rb +23 -0
- data/lib/dommy/rails/minitest/assertions.rb +298 -0
- data/lib/dommy/rails/minitest/integration.rb +13 -0
- data/lib/dommy/rails/minitest.rb +4 -0
- data/lib/dommy/rails/page_inspector.rb +66 -0
- data/lib/dommy/rails/rspec/browser.rb +40 -0
- data/lib/dommy/rails/rspec/integration.rb +16 -0
- data/lib/dommy/rails/rspec/matchers.rb +793 -0
- data/lib/dommy/rails/rspec.rb +12 -0
- data/lib/dommy/rails/stimulus.rb +50 -0
- data/lib/dommy/rails/turbo_stream.rb +40 -0
- data/lib/dommy/rails/url_matcher.rb +26 -0
- data/lib/dommy/rails/url_normalizer.rb +45 -0
- data/lib/dommy/rails/version.rb +5 -0
- data/lib/dommy/rails.rb +18 -0
- metadata +78 -0
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dommy
|
|
4
|
+
module Rails
|
|
5
|
+
module RSpec
|
|
6
|
+
module Matchers
|
|
7
|
+
def have_form(action: nil, method: nil, model: nil)
|
|
8
|
+
HaveForm.new(action: action, method: method, model: model)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def have_xpath(expression, text: nil, count: nil)
|
|
12
|
+
HaveXPath.new(expression, text: text, count: count)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def have_title(expected)
|
|
16
|
+
HaveTitle.new(expected)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def match_aria_snapshot(expected)
|
|
20
|
+
MatchAriaSnapshot.new(expected)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def have_meta(name: nil, property: nil, content: nil)
|
|
24
|
+
HaveMeta.new(name: name, property: property, content: content)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def have_csrf_meta_tags
|
|
28
|
+
HaveCsrfMetaTags.new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def have_authenticity_token
|
|
32
|
+
HaveAuthenticityToken.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def have_link(text = nil, href: nil, count: nil)
|
|
36
|
+
HaveLink.new(text, href: href, count: count)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def have_turbo_frame(id = nil, text: nil, count: nil)
|
|
40
|
+
HaveTurboFrame.new(id, text: text, count: count)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def have_select(name = nil, label: nil, count: nil)
|
|
44
|
+
HaveSelect.new(name, label: label, count: count)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def have_checked_field(name = nil)
|
|
48
|
+
HaveCheckableField.new(name, checked: true)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def have_unchecked_field(name = nil)
|
|
52
|
+
HaveCheckableField.new(name, checked: false)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def have_turbo_stream(action:, target:)
|
|
56
|
+
HaveTurboStream.new(action: action, target: target)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def append_turbo_stream(target)
|
|
60
|
+
HaveTurboStream.new(action: "append", target: target)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def replace_turbo_stream(target)
|
|
64
|
+
HaveTurboStream.new(action: "replace", target: target)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def update_turbo_stream(target)
|
|
68
|
+
HaveTurboStream.new(action: "update", target: target)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def remove_turbo_stream(target)
|
|
72
|
+
HaveTurboStream.new(action: "remove", target: target)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def have_stimulus_controller(name)
|
|
76
|
+
HaveStimulusController.new(name)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def have_stimulus_action(action)
|
|
80
|
+
HaveStimulusAction.new(action)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def have_stimulus_target(controller, target)
|
|
84
|
+
HaveStimulusTarget.new(controller, target)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def have_stimulus_value(controller, key, value)
|
|
88
|
+
HaveStimulusValue.new(controller, key, value)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def have_no_duplicate_ids
|
|
92
|
+
HaveNoDuplicateIds.new
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def have_no_invalid_aria_references
|
|
96
|
+
HaveNoInvalidAriaReferences.new
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def have_no_missing_form_labels
|
|
100
|
+
HaveNoMissingFormLabels.new
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def have_no_empty_links
|
|
104
|
+
HaveNoEmptyLinks.new
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def have_no_nested_interactive_elements
|
|
108
|
+
HaveNoNestedInteractiveElements.new
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def have_html_link(text = nil, href: nil, count: nil)
|
|
112
|
+
HaveHtmlLink.new(text, href: href, count: count)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def have_html_text(text)
|
|
116
|
+
HaveHtmlText.new(text)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def have_plain_text(text)
|
|
120
|
+
HavePlainText.new(text)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class HaveForm
|
|
124
|
+
def initialize(action:, method:, model:)
|
|
125
|
+
@action = action
|
|
126
|
+
@method = method
|
|
127
|
+
@model = model
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def matches?(actual)
|
|
131
|
+
@document = MatchTarget.document(actual)
|
|
132
|
+
Dommy::Rails::FormInspector.matches?(@document, action: @action, method: @method, model: @model)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def does_not_match?(actual)
|
|
136
|
+
!matches?(actual)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def description
|
|
140
|
+
"have form #{criteria_description}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def failure_message
|
|
144
|
+
"expected to find form #{criteria_description}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def failure_message_when_negated
|
|
148
|
+
"expected not to find form #{criteria_description}"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def criteria_description
|
|
154
|
+
parts = []
|
|
155
|
+
parts << "action=#{@action.inspect}" if @action
|
|
156
|
+
parts << "method=#{@method.inspect}" if @method
|
|
157
|
+
parts << "model=#{@model.inspect}" if @model
|
|
158
|
+
parts.empty? ? "matching any criteria" : parts.join(" ")
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
class HaveXPath
|
|
163
|
+
def initialize(expression, text:, count:)
|
|
164
|
+
@expression = expression
|
|
165
|
+
@text = text
|
|
166
|
+
@count = count
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def matches?(actual)
|
|
170
|
+
@matched = Dommy::Rails::PageInspector.xpath_matches(MatchTarget.document(actual), @expression, text: @text)
|
|
171
|
+
Dommy::Internal::DomMatching.count_matches?(@matched.size, @count)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def does_not_match?(actual)
|
|
175
|
+
!matches?(actual)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def description
|
|
179
|
+
"have XPath #{@expression.inspect}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def failure_message
|
|
183
|
+
"expected to find XPath #{@expression.inspect}, found #{@matched.size}"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def failure_message_when_negated
|
|
187
|
+
"expected not to find XPath #{@expression.inspect}, found #{@matched.size}"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
class HaveTitle
|
|
192
|
+
def initialize(expected)
|
|
193
|
+
@expected = expected
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def matches?(actual)
|
|
197
|
+
@document = MatchTarget.document(actual)
|
|
198
|
+
Dommy::Rails::PageInspector.title_matches?(@document, @expected)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def does_not_match?(actual)
|
|
202
|
+
!matches?(actual)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def description
|
|
206
|
+
"have title #{@expected.inspect}"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def failure_message
|
|
210
|
+
"expected title to match #{@expected.inspect}, got #{@document.title.inspect}"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def failure_message_when_negated
|
|
214
|
+
"expected title not to match #{@expected.inspect}"
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Playwright-style ARIA snapshot subset match. `expected` is a template
|
|
219
|
+
# snapshot (its nodes must appear in the actual tree, in order); names
|
|
220
|
+
# may be `/regex/`.
|
|
221
|
+
class MatchAriaSnapshot
|
|
222
|
+
def initialize(expected)
|
|
223
|
+
@expected = expected
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def matches?(actual)
|
|
227
|
+
@actual = MatchTarget.document(actual).aria_snapshot
|
|
228
|
+
Dommy::Rails::AriaSnapshotMatching.matches?(@actual, @expected)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def does_not_match?(actual)
|
|
232
|
+
!matches?(actual)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def description
|
|
236
|
+
"match aria snapshot"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def failure_message
|
|
240
|
+
"expected aria snapshot to match:\n#{@expected}\ngot:\n#{@actual}"
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def failure_message_when_negated
|
|
244
|
+
"expected aria snapshot not to match:\n#{@expected}"
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
class HaveMeta
|
|
249
|
+
def initialize(name:, property:, content:)
|
|
250
|
+
@name = name
|
|
251
|
+
@property = property
|
|
252
|
+
@content = content
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def matches?(actual)
|
|
256
|
+
Dommy::Rails::PageInspector.meta_matches?(MatchTarget.document(actual), name: @name, property: @property, content: @content)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def does_not_match?(actual)
|
|
260
|
+
!matches?(actual)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def description
|
|
264
|
+
"have meta #{criteria_description}"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def failure_message
|
|
268
|
+
"expected to find meta #{criteria_description}"
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def failure_message_when_negated
|
|
272
|
+
"expected not to find meta #{criteria_description}"
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
private
|
|
276
|
+
|
|
277
|
+
def criteria_description
|
|
278
|
+
parts = []
|
|
279
|
+
parts << "name=#{@name.inspect}" if @name
|
|
280
|
+
parts << "property=#{@property.inspect}" if @property
|
|
281
|
+
parts << "content=#{@content.inspect}" if @content
|
|
282
|
+
parts.empty? ? "matching any criteria" : parts.join(" ")
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
class HaveCsrfMetaTags
|
|
287
|
+
def matches?(actual)
|
|
288
|
+
Dommy::Rails::PageInspector.csrf_meta_tags?(MatchTarget.document(actual))
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def does_not_match?(actual)
|
|
292
|
+
!matches?(actual)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def description
|
|
296
|
+
"have Rails CSRF meta tags"
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def failure_message
|
|
300
|
+
"expected to find Rails CSRF meta tags"
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def failure_message_when_negated
|
|
304
|
+
"expected not to find Rails CSRF meta tags"
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
class HaveAuthenticityToken
|
|
309
|
+
def matches?(actual)
|
|
310
|
+
Dommy::Rails::PageInspector.authenticity_token?(MatchTarget.document(actual))
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def does_not_match?(actual)
|
|
314
|
+
!matches?(actual)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def description
|
|
318
|
+
"have Rails authenticity token field"
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def failure_message
|
|
322
|
+
"expected to find Rails authenticity token field"
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def failure_message_when_negated
|
|
326
|
+
"expected not to find Rails authenticity token field"
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
class HaveLink
|
|
331
|
+
def initialize(text, href:, count:)
|
|
332
|
+
@text = text
|
|
333
|
+
@href = href
|
|
334
|
+
@count = count
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def matches?(actual)
|
|
338
|
+
@matched = Dommy::Rails::PageInspector.links(MatchTarget.document(actual), text: @text, href: @href)
|
|
339
|
+
Dommy::Internal::DomMatching.count_matches?(@matched.size, @count)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def does_not_match?(actual)
|
|
343
|
+
!matches?(actual)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def description
|
|
347
|
+
"have link"
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def failure_message
|
|
351
|
+
"expected to find link, found #{@matched.size}"
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def failure_message_when_negated
|
|
355
|
+
"expected not to find link, found #{@matched.size}"
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
class HaveTurboFrame
|
|
360
|
+
def initialize(id, text:, count:)
|
|
361
|
+
@id = id
|
|
362
|
+
@text = text
|
|
363
|
+
@count = count
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def matches?(actual, &block)
|
|
367
|
+
@matched = Dommy::Rails::PageInspector.turbo_frames(MatchTarget.document(actual), @id, text: @text)
|
|
368
|
+
ok = Dommy::Internal::DomMatching.count_matches?(@matched.size, @count)
|
|
369
|
+
block.call(@matched.first) if ok && block && @matched.any?
|
|
370
|
+
ok
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def does_not_match?(actual)
|
|
374
|
+
!matches?(actual)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def description
|
|
378
|
+
"have turbo-frame#{@id ? " ##{@id}" : ""}"
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def failure_message
|
|
382
|
+
"expected to find turbo-frame#{@id ? " ##{@id}" : ""}, found #{@matched.size}"
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def failure_message_when_negated
|
|
386
|
+
"expected not to find turbo-frame#{@id ? " ##{@id}" : ""}, found #{@matched.size}"
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
class HaveSelect
|
|
391
|
+
def initialize(name, label:, count:)
|
|
392
|
+
@name = name
|
|
393
|
+
@label = label
|
|
394
|
+
@count = count
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def matches?(actual)
|
|
398
|
+
@matched = Dommy::Rails::PageInspector.selects(MatchTarget.document(actual), name: @name, label: @label)
|
|
399
|
+
Dommy::Internal::DomMatching.count_matches?(@matched.size, @count)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def does_not_match?(actual)
|
|
403
|
+
!matches?(actual)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def description
|
|
407
|
+
"have select"
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def failure_message
|
|
411
|
+
"expected to find select, found #{@matched.size}"
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def failure_message_when_negated
|
|
415
|
+
"expected not to find select, found #{@matched.size}"
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
class HaveCheckableField
|
|
420
|
+
def initialize(name, checked:)
|
|
421
|
+
@name = name
|
|
422
|
+
@checked = checked
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def matches?(actual)
|
|
426
|
+
@matched = Dommy::Rails::PageInspector.checkable_fields(MatchTarget.document(actual), name: @name, checked: @checked)
|
|
427
|
+
@matched.any?
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def does_not_match?(actual)
|
|
431
|
+
!matches?(actual)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def description
|
|
435
|
+
"have #{@checked ? 'checked' : 'unchecked'} field"
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def failure_message
|
|
439
|
+
"expected to find #{@checked ? 'checked' : 'unchecked'} field"
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def failure_message_when_negated
|
|
443
|
+
"expected not to find #{@checked ? 'checked' : 'unchecked'} field"
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
class HaveTurboStream
|
|
448
|
+
def initialize(action:, target:)
|
|
449
|
+
@action = action
|
|
450
|
+
@target = target
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def matches?(actual, &block)
|
|
454
|
+
stream = Dommy::Rails::TurboStream.find(MatchTarget.body(actual), action: @action, target: @target)
|
|
455
|
+
block.call(Dommy::Rails::TurboStream.fragment_document(stream)) if stream && block
|
|
456
|
+
!stream.nil?
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def does_not_match?(actual)
|
|
460
|
+
!matches?(actual)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def description
|
|
464
|
+
"have turbo-stream action=#{@action.inspect} target=#{@target.inspect}"
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def failure_message
|
|
468
|
+
"expected to find turbo-stream action=#{@action.inspect} target=#{@target.inspect}"
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def failure_message_when_negated
|
|
472
|
+
"expected not to find turbo-stream action=#{@action.inspect} target=#{@target.inspect}"
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
class HaveStimulusController
|
|
477
|
+
def initialize(name)
|
|
478
|
+
@name = name
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def matches?(actual)
|
|
482
|
+
Dommy::Rails::Stimulus.controller?(MatchTarget.document(actual), @name)
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
def does_not_match?(actual)
|
|
486
|
+
!matches?(actual)
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def description
|
|
490
|
+
"have Stimulus controller #{@name.inspect}"
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def failure_message
|
|
494
|
+
"expected to find element with Stimulus controller #{@name.inspect}"
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def failure_message_when_negated
|
|
498
|
+
"expected not to find element with Stimulus controller #{@name.inspect}"
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
class HaveStimulusAction
|
|
503
|
+
def initialize(action)
|
|
504
|
+
@action = action
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def matches?(actual)
|
|
508
|
+
Dommy::Rails::Stimulus.action?(MatchTarget.document(actual), @action)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def does_not_match?(actual)
|
|
512
|
+
!matches?(actual)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def description
|
|
516
|
+
"have Stimulus action #{@action.inspect}"
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def failure_message
|
|
520
|
+
"expected to find element with Stimulus action #{@action.inspect}"
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def failure_message_when_negated
|
|
524
|
+
"expected not to find element with Stimulus action #{@action.inspect}"
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
class HaveStimulusTarget
|
|
529
|
+
def initialize(controller, target)
|
|
530
|
+
@controller = controller
|
|
531
|
+
@target = target
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def matches?(actual)
|
|
535
|
+
Dommy::Rails::Stimulus.target?(MatchTarget.document(actual), @controller, @target)
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def does_not_match?(actual)
|
|
539
|
+
!matches?(actual)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def description
|
|
543
|
+
"have Stimulus target #{@controller.inspect}.#{@target.inspect}"
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def failure_message
|
|
547
|
+
"expected to find element with Stimulus target #{@controller.inspect}.#{@target.inspect}"
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def failure_message_when_negated
|
|
551
|
+
"expected not to find element with Stimulus target #{@controller.inspect}.#{@target.inspect}"
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
class HaveStimulusValue
|
|
556
|
+
def initialize(controller, key, value)
|
|
557
|
+
@controller = controller
|
|
558
|
+
@key = key
|
|
559
|
+
@value = value
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def matches?(actual)
|
|
563
|
+
Dommy::Rails::Stimulus.value?(MatchTarget.document(actual), @controller, @key, @value)
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def does_not_match?(actual)
|
|
567
|
+
!matches?(actual)
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def description
|
|
571
|
+
"have Stimulus value #{@controller.inspect}.#{@key.inspect}"
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def failure_message
|
|
575
|
+
"expected to find element with Stimulus value #{@controller.inspect}.#{@key.inspect} = #{@value.inspect}"
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
def failure_message_when_negated
|
|
579
|
+
"expected not to find element with Stimulus value #{@controller.inspect}.#{@key.inspect} = #{@value.inspect}"
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
class HaveNoDuplicateIds
|
|
584
|
+
def matches?(actual)
|
|
585
|
+
@issues = Dommy::Rails::Lint.duplicate_ids(MatchTarget.document(actual))
|
|
586
|
+
@issues.empty?
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def does_not_match?(actual)
|
|
590
|
+
!matches?(actual)
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def description
|
|
594
|
+
"have no duplicate ids"
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def failure_message
|
|
598
|
+
"expected no duplicate IDs, found: #{@issues.join(', ')}"
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
def failure_message_when_negated
|
|
602
|
+
"expected duplicate IDs"
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
class HaveNoInvalidAriaReferences
|
|
607
|
+
def matches?(actual)
|
|
608
|
+
@issues = Dommy::Rails::Lint.invalid_aria_references(MatchTarget.document(actual))
|
|
609
|
+
@issues.empty?
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def does_not_match?(actual)
|
|
613
|
+
!matches?(actual)
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def description
|
|
617
|
+
"have no invalid ARIA references"
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def failure_message
|
|
621
|
+
"expected no invalid ARIA references, found #{@issues.size} issues"
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def failure_message_when_negated
|
|
625
|
+
"expected invalid ARIA references"
|
|
626
|
+
end
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
class HaveNoMissingFormLabels
|
|
630
|
+
def matches?(actual)
|
|
631
|
+
@issues = Dommy::Rails::Lint.missing_form_labels(MatchTarget.document(actual))
|
|
632
|
+
@issues.empty?
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
def does_not_match?(actual)
|
|
636
|
+
!matches?(actual)
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def description
|
|
640
|
+
"have no missing form labels"
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
def failure_message
|
|
644
|
+
"expected no missing form labels, found #{@issues.size} issues"
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
def failure_message_when_negated
|
|
648
|
+
"expected missing form labels"
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
class HaveNoEmptyLinks
|
|
653
|
+
def matches?(actual)
|
|
654
|
+
@issues = Dommy::Rails::Lint.empty_links(MatchTarget.document(actual))
|
|
655
|
+
@issues.empty?
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
def does_not_match?(actual)
|
|
659
|
+
!matches?(actual)
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
def description
|
|
663
|
+
"have no empty links"
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
def failure_message
|
|
667
|
+
"expected no empty links, found #{@issues.size} issues"
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
def failure_message_when_negated
|
|
671
|
+
"expected empty links"
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
class HaveNoNestedInteractiveElements
|
|
676
|
+
def matches?(actual)
|
|
677
|
+
@issues = Dommy::Rails::Lint.nested_interactive_elements(MatchTarget.document(actual))
|
|
678
|
+
@issues.empty?
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
def does_not_match?(actual)
|
|
682
|
+
!matches?(actual)
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def description
|
|
686
|
+
"have no nested interactive elements"
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def failure_message
|
|
690
|
+
"expected no nested interactive elements, found #{@issues.size} issues"
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
def failure_message_when_negated
|
|
694
|
+
"expected nested interactive elements"
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
class HaveHtmlLink
|
|
699
|
+
def initialize(text, href:, count:)
|
|
700
|
+
@text = text
|
|
701
|
+
@href = href
|
|
702
|
+
@count = count
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
def matches?(mail)
|
|
706
|
+
@document = Dommy::Rails::MailPart.html_document(mail)
|
|
707
|
+
return false unless @document
|
|
708
|
+
|
|
709
|
+
@matched = Dommy::Rails::PageInspector.links(@document, text: @text, href: @href)
|
|
710
|
+
Dommy::Internal::DomMatching.count_matches?(@matched.size, @count)
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
def does_not_match?(mail)
|
|
714
|
+
!matches?(mail)
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
def description
|
|
718
|
+
"have HTML link"
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
def failure_message
|
|
722
|
+
return "expected mail to have an HTML part" unless @document
|
|
723
|
+
|
|
724
|
+
"expected mail HTML to contain link, found #{@matched.size}"
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
def failure_message_when_negated
|
|
728
|
+
"expected mail HTML not to contain link"
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
class HaveHtmlText
|
|
733
|
+
def initialize(text)
|
|
734
|
+
@text = text
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
def matches?(mail)
|
|
738
|
+
@document = Dommy::Rails::MailPart.html_document(mail)
|
|
739
|
+
return false unless @document
|
|
740
|
+
|
|
741
|
+
@actual = Dommy::Internal::DomMatching.text_of(@document)
|
|
742
|
+
Dommy::Internal::DomMatching.text_matches?(@actual, @text)
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
def does_not_match?(mail)
|
|
746
|
+
!matches?(mail)
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
def description
|
|
750
|
+
"have HTML text #{@text.inspect}"
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
def failure_message
|
|
754
|
+
return "expected mail to have an HTML part" unless @document
|
|
755
|
+
|
|
756
|
+
"expected mail HTML to include #{@text.inspect}, got #{@actual.inspect}"
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
def failure_message_when_negated
|
|
760
|
+
"expected mail HTML not to include #{@text.inspect}"
|
|
761
|
+
end
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
class HavePlainText
|
|
765
|
+
def initialize(text)
|
|
766
|
+
@text = text
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
def matches?(mail)
|
|
770
|
+
@actual = Dommy::Rails::MailPart.plain_body(mail).to_s
|
|
771
|
+
Dommy::Internal::DomMatching.text_matches?(@actual, @text)
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
def does_not_match?(mail)
|
|
775
|
+
!matches?(mail)
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
def description
|
|
779
|
+
"have plain text #{@text.inspect}"
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
def failure_message
|
|
783
|
+
"expected mail plain text to include #{@text.inspect}, got #{@actual.inspect}"
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
def failure_message_when_negated
|
|
787
|
+
"expected mail plain text not to include #{@text.inspect}"
|
|
788
|
+
end
|
|
789
|
+
end
|
|
790
|
+
end
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
end
|