rails-jsdom 0.1.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/CHANGELOG.md +13 -0
- data/LICENSE +21 -0
- data/README.md +351 -0
- data/lib/generators/rails_jsdom/install/install_generator.rb +108 -0
- data/lib/rails_jsdom/bridge.rb +80 -0
- data/lib/rails_jsdom/element.rb +186 -0
- data/lib/rails_jsdom/page.rb +33 -0
- data/lib/rails_jsdom/query.rb +76 -0
- data/lib/rails_jsdom/runner.js +3 -0
- data/lib/rails_jsdom/runtime.js +926 -0
- data/lib/rails_jsdom/test_case.rb +29 -0
- data/lib/rails_jsdom/test_helper.rb +767 -0
- data/lib/rails_jsdom/turbo_stream_deliveries.rb +25 -0
- data/lib/rails_jsdom/version.rb +4 -0
- data/lib/rails_jsdom.rb +83 -0
- metadata +158 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
module RailsJsdom
|
|
2
|
+
# An element on the page, returned by TestHelper#find, TestHelper#all,
|
|
3
|
+
# TestHelper#first, TestHelper#find_field, TestHelper#find_button and
|
|
4
|
+
# TestHelper#find_link.
|
|
5
|
+
#
|
|
6
|
+
# An element refers to a node in the live page. Actions on it settle the
|
|
7
|
+
# page like the TestHelper actions. Once the node leaves the page, for
|
|
8
|
+
# example after a navigation, using the element raises RailsJsdom::Error.
|
|
9
|
+
#
|
|
10
|
+
# item = find("#cart li", text: "Mug")
|
|
11
|
+
# item.find_button("Remove").click
|
|
12
|
+
# assert_equal "2", find_field("Quantity").value
|
|
13
|
+
class Element
|
|
14
|
+
def self.action(name) # :nodoc:
|
|
15
|
+
define_method(name) { perform(name) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.read(name, from: name.to_s.delete_suffix("?")) # :nodoc:
|
|
19
|
+
define_method(name) { description.fetch(from) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.scoped_helper(name) # :nodoc:
|
|
23
|
+
define_method(name) do |*arguments, **options|
|
|
24
|
+
@test.within(self) { @test.public_send(name, *arguments, **options) }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private_class_method :action, :read, :scoped_helper
|
|
29
|
+
|
|
30
|
+
attr_reader :handle # :nodoc:
|
|
31
|
+
|
|
32
|
+
def initialize(test, handle) # :nodoc:
|
|
33
|
+
@test = test
|
|
34
|
+
@handle = handle
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Clicks the element.
|
|
39
|
+
action :click
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# Moves the pointer over the element. See TestHelper#hover.
|
|
43
|
+
action :hover
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Double-clicks the element. See TestHelper#double_click.
|
|
47
|
+
action :double_click
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# Right-clicks the element. See TestHelper#right_click.
|
|
51
|
+
action :right_click
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# Selects this `option` in its select.
|
|
55
|
+
action :select_option
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Deselects this `option` in a select that allows multiple options.
|
|
59
|
+
action :unselect_option
|
|
60
|
+
|
|
61
|
+
# Sets the element's value: text for a text-like field or a
|
|
62
|
+
# `contenteditable` element, `true` or `false` for a checkbox, `true` to
|
|
63
|
+
# select a radio button.
|
|
64
|
+
def set(value)
|
|
65
|
+
perform(:set, value: value)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Focuses the element and types `keys` into it. See TestHelper#send_keys.
|
|
69
|
+
def send_keys(*keys)
|
|
70
|
+
perform(:send_keys, keys: RailsJsdom.keys(keys))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# The visible text of the element.
|
|
75
|
+
read :text
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# The current value of a field, or `nil` for an element without one.
|
|
79
|
+
read :value
|
|
80
|
+
|
|
81
|
+
##
|
|
82
|
+
# The element's tag name, such as `"button"`.
|
|
83
|
+
read :tag_name, from: "tag"
|
|
84
|
+
|
|
85
|
+
##
|
|
86
|
+
# Whether a checkbox or radio button is checked.
|
|
87
|
+
read :checked?
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Whether an `option` is selected.
|
|
91
|
+
read :selected?
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
# Whether the element is disabled.
|
|
95
|
+
read :disabled?
|
|
96
|
+
|
|
97
|
+
##
|
|
98
|
+
# Whether the element is visible: neither it nor an ancestor is hidden.
|
|
99
|
+
read :visible?
|
|
100
|
+
|
|
101
|
+
# The value of the attribute `name`, or the current value for `:value`.
|
|
102
|
+
def [](name)
|
|
103
|
+
name.to_s == "value" ? value : description.fetch("attributes")[name.to_s]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
# Like TestHelper#find, searching inside this element.
|
|
108
|
+
scoped_helper :find
|
|
109
|
+
|
|
110
|
+
##
|
|
111
|
+
# Like TestHelper#all, searching inside this element.
|
|
112
|
+
scoped_helper :all
|
|
113
|
+
|
|
114
|
+
##
|
|
115
|
+
# Like TestHelper#first, searching inside this element.
|
|
116
|
+
scoped_helper :first
|
|
117
|
+
|
|
118
|
+
##
|
|
119
|
+
# Like TestHelper#find_field, searching inside this element.
|
|
120
|
+
scoped_helper :find_field
|
|
121
|
+
|
|
122
|
+
##
|
|
123
|
+
# Like TestHelper#find_button, searching inside this element.
|
|
124
|
+
scoped_helper :find_button
|
|
125
|
+
|
|
126
|
+
##
|
|
127
|
+
# Like TestHelper#find_link, searching inside this element.
|
|
128
|
+
scoped_helper :find_link
|
|
129
|
+
|
|
130
|
+
##
|
|
131
|
+
# Like TestHelper#assert_text, for the text inside this element.
|
|
132
|
+
scoped_helper :assert_text
|
|
133
|
+
|
|
134
|
+
##
|
|
135
|
+
# Like TestHelper#assert_no_text, for the text inside this element.
|
|
136
|
+
scoped_helper :assert_no_text
|
|
137
|
+
|
|
138
|
+
##
|
|
139
|
+
# Like TestHelper#assert_selector, inside this element.
|
|
140
|
+
scoped_helper :assert_selector
|
|
141
|
+
|
|
142
|
+
##
|
|
143
|
+
# Like TestHelper#assert_no_selector, inside this element.
|
|
144
|
+
scoped_helper :assert_no_selector
|
|
145
|
+
|
|
146
|
+
##
|
|
147
|
+
# Like TestHelper#has_text?, for the text inside this element.
|
|
148
|
+
scoped_helper :has_text?
|
|
149
|
+
|
|
150
|
+
##
|
|
151
|
+
# Like TestHelper#has_no_text?, for the text inside this element.
|
|
152
|
+
scoped_helper :has_no_text?
|
|
153
|
+
|
|
154
|
+
##
|
|
155
|
+
# Like TestHelper#has_selector?, inside this element.
|
|
156
|
+
scoped_helper :has_selector?
|
|
157
|
+
|
|
158
|
+
##
|
|
159
|
+
# Like TestHelper#has_no_selector?, inside this element.
|
|
160
|
+
scoped_helper :has_no_selector?
|
|
161
|
+
|
|
162
|
+
# Two elements are equal when they refer to the same node.
|
|
163
|
+
def ==(other)
|
|
164
|
+
other.is_a?(Element) && other.handle == handle
|
|
165
|
+
end
|
|
166
|
+
alias_method :eql?, :==
|
|
167
|
+
|
|
168
|
+
def hash # :nodoc:
|
|
169
|
+
handle.hash
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def inspect # :nodoc:
|
|
173
|
+
"#<RailsJsdom::Element #{handle}>"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
private
|
|
177
|
+
|
|
178
|
+
def perform(action, **options)
|
|
179
|
+
@test.rails_jsdom_element(handle, action.to_s, **options)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def description
|
|
183
|
+
@test.rails_jsdom_read(handle)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require "active_support/core_ext/module/delegation"
|
|
2
|
+
|
|
3
|
+
module RailsJsdom
|
|
4
|
+
# The object behind TestHelper#page, for tests written as
|
|
5
|
+
# `page.has_css?(...)` or `page.find(...)`. Every TestHelper method can be
|
|
6
|
+
# called on it.
|
|
7
|
+
#
|
|
8
|
+
# assert page.has_css?(".cart-item", count: 2)
|
|
9
|
+
# page.find("#menu").click
|
|
10
|
+
class Page
|
|
11
|
+
delegate(*TestHelper.public_instance_methods - [ :page ], to: :@test)
|
|
12
|
+
|
|
13
|
+
def initialize(test) # :nodoc:
|
|
14
|
+
@test = test
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# The HTML of the whole page.
|
|
18
|
+
def body
|
|
19
|
+
@test.rails_jsdom_page_snapshot.fetch("html")
|
|
20
|
+
end
|
|
21
|
+
alias_method :html, :body
|
|
22
|
+
|
|
23
|
+
# The page title.
|
|
24
|
+
def title
|
|
25
|
+
@test.rails_jsdom_page_snapshot.fetch("title")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The visible text of the page, or of the current TestHelper#within scope.
|
|
29
|
+
def text
|
|
30
|
+
@test.page_text
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module RailsJsdom
|
|
2
|
+
class Query # :nodoc:
|
|
3
|
+
DISABLED_BY_DEFAULT = %w[field button].freeze
|
|
4
|
+
|
|
5
|
+
attr_reader :strategy, :locator
|
|
6
|
+
|
|
7
|
+
def self.like?(actual, expected)
|
|
8
|
+
expected.is_a?(Regexp) ? actual.match?(expected) : actual.include?(expected.to_s)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.same?(actual, expected)
|
|
12
|
+
expected.is_a?(Regexp) ? actual.match?(expected) : actual == expected.to_s
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(strategy, locator, text: nil, exact_text: nil, visible: true, with: nil, checked: nil, unchecked: nil, disabled: nil, href: nil, type: nil)
|
|
16
|
+
@strategy = strategy
|
|
17
|
+
@locator = locator
|
|
18
|
+
@visible = visible
|
|
19
|
+
disabled = false if disabled.nil? && DISABLED_BY_DEFAULT.include?(strategy)
|
|
20
|
+
@filters = { text: text, exact_text: exact_text, with: with, checked: checked, unchecked: unchecked, disabled: disabled, href: href, type: type }.compact
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def message
|
|
24
|
+
{ command: "query", strategy: strategy, locator: locator }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def filter(descriptors)
|
|
28
|
+
descriptors.select { |descriptor| visible?(descriptor) && matches?(comparable_values(descriptor)) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def description
|
|
32
|
+
[ "#{strategy} #{locator.inspect}", *@filters.map { |name, expected| "#{name}: #{expected.inspect}" } ].join(", ")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def visible?(descriptor)
|
|
38
|
+
case @visible
|
|
39
|
+
when true
|
|
40
|
+
descriptor.fetch("visible")
|
|
41
|
+
when :hidden
|
|
42
|
+
!descriptor.fetch("visible")
|
|
43
|
+
else
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def matches?(values)
|
|
49
|
+
@filters.all? { |name, expected| satisfies?(name, expected, values.fetch(name)) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def satisfies?(name, expected, actual)
|
|
53
|
+
case name
|
|
54
|
+
when :text
|
|
55
|
+
Query.like?(actual, expected)
|
|
56
|
+
when :checked, :unchecked, :disabled
|
|
57
|
+
expected == :all || actual == expected
|
|
58
|
+
else
|
|
59
|
+
Query.same?(actual, expected)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def comparable_values(descriptor)
|
|
64
|
+
{
|
|
65
|
+
text: descriptor.fetch(@visible == true ? "text" : "all_text"),
|
|
66
|
+
exact_text: descriptor.fetch("text"),
|
|
67
|
+
with: descriptor.fetch("value").to_s,
|
|
68
|
+
checked: descriptor.fetch("checked"),
|
|
69
|
+
unchecked: !descriptor.fetch("checked"),
|
|
70
|
+
disabled: descriptor.fetch("disabled"),
|
|
71
|
+
href: descriptor.fetch("attributes")["href"].to_s,
|
|
72
|
+
type: descriptor.fetch("type")
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|