rspec-html 0.2.20 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/Gemfile.lock +2 -4
- data/README.md +34 -16
- data/lib/rspec/html/version.rb +1 -1
- data/lib/rspec/html.rb +1 -3
- data/lib/rspec_html/countable.rb +51 -0
- data/lib/rspec_html/matchers/base.rb +6 -0
- data/lib/rspec_html/matchers/contain_tag.rb +9 -3
- data/lib/rspec_html/matchers/match_text.rb +12 -3
- data/lib/rspec_html/matchers.rb +4 -3
- data/lib/rspec_html.rb +1 -0
- data/templates/failure/contain_tag.erb +8 -0
- data/templates/failure/match_text.erb +10 -2
- metadata +3 -5
- data/lib/rspec_html/matchers/contain_text.rb +0 -19
- data/templates/description/contain_text.erb +0 -1
- data/templates/failure/contain_text.erb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cfd83e0a3a67f3f946b6ce6cb841e1cc32b591a590c84f85152ebc15c0c9930
|
4
|
+
data.tar.gz: a75ffeb0933fa3fa30d80fa89a4eff6da2211420fb04ec2fddf6ea287573fcb2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54886d022c1df32ce95b291769248387ef8d3044cda07f7ff9d314e91118c99dfacbe246b878e35f5bb7f8e1d4ddca88e5ebe387143d0eff3b7c446364c4d33c
|
7
|
+
data.tar.gz: 2437216d2611a1751e86f01c98d19442dbe33d6b21eee654711200dae3528a5000b23832a6b3b30cb91865a697d69605732143f56ba7cb6cb88949d8f27ef202
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rspec-html (0.
|
4
|
+
rspec-html (0.3.0)
|
5
5
|
nokogiri (~> 1.10)
|
6
6
|
rspec (~> 3.0)
|
7
7
|
|
@@ -21,10 +21,8 @@ GEM
|
|
21
21
|
i18n (1.13.0)
|
22
22
|
concurrent-ruby (~> 1.0)
|
23
23
|
json (2.6.3)
|
24
|
-
mini_portile2 (2.8.2)
|
25
24
|
minitest (5.18.0)
|
26
|
-
nokogiri (1.13.10)
|
27
|
-
mini_portile2 (~> 2.8.0)
|
25
|
+
nokogiri (1.13.10-x86_64-linux)
|
28
26
|
racc (~> 1.4)
|
29
27
|
paint (2.3.0)
|
30
28
|
parallel (1.23.0)
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ _RSpec::HTML_ provides a simple object interface to HTML responses from [_RSpec
|
|
7
7
|
Add the gem to your `Gemfile`:
|
8
8
|
|
9
9
|
```ruby
|
10
|
-
gem 'rspec-html', '~> 0.
|
10
|
+
gem 'rspec-html', '~> 0.3.0'
|
11
11
|
```
|
12
12
|
|
13
13
|
And rebuild your bundle:
|
@@ -35,28 +35,31 @@ The top-level object `document` is available in all tests which reflects the cur
|
|
35
35
|
If you need to parse _HTML_ manually you can use the provided `parse_html` helper and then access `document` as normal:
|
36
36
|
|
37
37
|
```ruby
|
38
|
-
|
38
|
+
let(:document) { parse_html('<html><body>hello</body></html>') }
|
39
|
+
|
39
40
|
it 'says hello' do
|
40
|
-
expect(document.body).to
|
41
|
+
expect(document.body).to match_text 'hello'
|
41
42
|
end
|
42
43
|
```
|
43
44
|
|
44
45
|
This method can also be used for _ActionMailer_ emails:
|
45
46
|
```ruby
|
46
|
-
|
47
|
+
let(:document) { parse_html(ActionMailer::Base.deliveries.last.body.decoded) }
|
47
48
|
```
|
48
49
|
|
50
|
+
**Changed in 0.3.0**: `parse_html` no longer sets the `document` automatically, you must use a `let` block to assign it yourself.
|
51
|
+
|
49
52
|
To navigate the _DOM_ by a sequence of tag names use chained method calls on the `document` object:
|
50
53
|
|
51
54
|
#### Tag Traversal
|
52
55
|
```ruby
|
53
|
-
expect(document.body.div.span).to
|
56
|
+
expect(document.body.div.span).to match_text 'some text'
|
54
57
|
```
|
55
58
|
|
56
59
|
#### Attribute Matching
|
57
60
|
To select an element matching certain attributes pass a hash to any of the chained methods:
|
58
61
|
```ruby
|
59
|
-
expect(document.body.div(id: 'my-div').span(align: 'left')).to
|
62
|
+
expect(document.body.div(id: 'my-div').span(align: 'left')).to match_text 'some text'
|
60
63
|
```
|
61
64
|
|
62
65
|
Special attributes like `checked` can be found using the `#attributes` method:
|
@@ -67,8 +70,8 @@ expect(document.input(type: 'checkbox', name: 'my-name')).attributes).to include
|
|
67
70
|
#### Class Matching
|
68
71
|
_CSS_ classes are treated as a special case: to select an element matching a set of classes pass the `class` parameter:
|
69
72
|
```ruby
|
70
|
-
expect(document.body.div(id: 'my-div').span(class: 'my-class')).to
|
71
|
-
expect(document.body.div(id: 'my-div').span(class: 'my-class my-other-class')).to
|
73
|
+
expect(document.body.div(id: 'my-div').span(class: 'my-class')).to match_text 'some text'
|
74
|
+
expect(document.body.div(id: 'my-div').span(class: 'my-class my-other-class')).to match_text 'some text'
|
72
75
|
```
|
73
76
|
|
74
77
|
Classes can be provided in any order, i.e. `'my-class my-other-class'` is equivalent to `'my-other-class my-class'`.
|
@@ -76,12 +79,25 @@ Classes can be provided in any order, i.e. `'my-class my-other-class'` is equiva
|
|
76
79
|
#### Simple CSS Matching
|
77
80
|
To use a simple CSS selector when no other attributes are needed, pass a string to the tag method:
|
78
81
|
```ruby
|
79
|
-
expect(document.body.div('#my-id.my-class1.my-class2')).to
|
82
|
+
expect(document.body.div('#my-id.my-class1.my-class2')).to match_text 'some text'
|
80
83
|
```
|
81
84
|
|
82
85
|
This is effectively shorthand for:
|
83
86
|
```ruby
|
84
|
-
expect(document.body.div(id: 'my-id', class: 'my-class1 my-class2')).to
|
87
|
+
expect(document.body.div(id: 'my-id', class: 'my-class1 my-class2')).to match_text 'some text'
|
88
|
+
```
|
89
|
+
|
90
|
+
#### Counting Matchers
|
91
|
+
Use `once`, `twice`, `times`, `at_least`, and `at_most` to match element counts:
|
92
|
+
```ruby
|
93
|
+
expect(document.div('.my-class')).to match_text('some text').once
|
94
|
+
expect(document.div('.my-class')).to match_text('some other text').twice
|
95
|
+
expect(document.div('.my-class')).to match_text('some').times(3)
|
96
|
+
expect(document.div('.my-class')).to match_text('some').at_least(:twice)
|
97
|
+
expect(document.div('.my-class')).to match_text('some').at_least.times(3)
|
98
|
+
expect(document.div('.my-class')).to match_text('some text').at_most.once
|
99
|
+
expect(document.div('.my-class')).to match_text('some other text').at_most.twice
|
100
|
+
expect(document.div('.my-class')).to match_text('text').at_most.times(3)
|
85
101
|
```
|
86
102
|
|
87
103
|
#### Text Matching
|
@@ -93,8 +109,8 @@ expect(document.body.div(text: 'some text').input[:value]).to eql 'some-value'
|
|
93
109
|
#### Attribute Retrieval
|
94
110
|
To select an attribute from an element use the hash-style interface:
|
95
111
|
```ruby
|
96
|
-
expect(document.body.div.span[:class]).to
|
97
|
-
expect(document.body.div.span['data-content']).to
|
112
|
+
expect(document.body.div.span[:class]).to match_text 'my-class'
|
113
|
+
expect(document.body.div.span['data-content']).to match_text 'my content'
|
98
114
|
```
|
99
115
|
|
100
116
|
#### Retrieve all matching elements
|
@@ -106,13 +122,13 @@ expect(document.span.all.map(&:text)).to eql ['foo', 'bar', 'baz']
|
|
106
122
|
#### Indexing a Matching Set
|
107
123
|
To select an index from a set of matched elements use the array-style interface (the first matching element is `1`, not `0`):
|
108
124
|
```ruby
|
109
|
-
expect(document.body.div[1].span[1][:class]).to
|
125
|
+
expect(document.body.div[1].span[1][:class]).to match_text 'my-class'
|
110
126
|
```
|
111
127
|
|
112
128
|
Alternatively, use `#first`, `#last` or, when using _ActiveSupport_, `#second`, `#third`, etc. are also available:
|
113
129
|
|
114
130
|
```ruby
|
115
|
-
expect(document.body.div.first.span.last[:class]).to
|
131
|
+
expect(document.body.div.first.span.last[:class]).to match_text 'my-class'
|
116
132
|
```
|
117
133
|
|
118
134
|
|
@@ -133,8 +149,8 @@ expect(document.body.div.length).to eql 3
|
|
133
149
|
#### XPath / CSS Selectors
|
134
150
|
If you need something more specific you can always use the _Nokogiri_ `#xpath` and `#css` methods on any element:
|
135
151
|
```ruby
|
136
|
-
expect(document.body.xpath('//span[@class="my-class"]')).to
|
137
|
-
expect(document.body.css('span.my-class')).to
|
152
|
+
expect(document.body.xpath('//span[@class="my-class"]')).to match_text 'some text'
|
153
|
+
expect(document.body.css('span.my-class')).to match_text 'some text'
|
138
154
|
```
|
139
155
|
|
140
156
|
To simply check that an _XPath_ or _CSS_ selector exists use `have_xpath` and `have_css`:
|
@@ -153,6 +169,8 @@ expect(document.input(type: 'checkbox')).to be_checked
|
|
153
169
|
### Custom Matchers
|
154
170
|
<a name="matchers"></a>
|
155
171
|
|
172
|
+
**Changed in 0.3.0**: The `contain_text` matcher has been removed. Use `match_text` instead.
|
173
|
+
|
156
174
|
#### match_text
|
157
175
|
|
158
176
|
Use the `match_text` matcher to locate text within a _DOM_ element. All mark-up elements are stripped when using this matcher.
|
data/lib/rspec/html/version.rb
CHANGED
data/lib/rspec/html.rb
CHANGED
@@ -9,15 +9,13 @@ module RSpec
|
|
9
9
|
# Module extension for RSpec::SharedContext
|
10
10
|
module HTML
|
11
11
|
def document
|
12
|
-
return @document if @document
|
13
|
-
|
14
12
|
RSpecHTML::Element.new(Nokogiri::HTML.parse(response.body), :document)
|
15
13
|
rescue NameError
|
16
14
|
raise RSpecHTML::NoResponseError, 'No `response` object found. Make a request first.'
|
17
15
|
end
|
18
16
|
|
19
17
|
def parse_html(content)
|
20
|
-
|
18
|
+
RSpecHTML::Element.new(Nokogiri::HTML.parse(content), :document)
|
21
19
|
end
|
22
20
|
end
|
23
21
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecHTML
|
4
|
+
# DSL module for all matchers, provides element counting utilities.
|
5
|
+
module Countable
|
6
|
+
def once
|
7
|
+
@expected_count = 1
|
8
|
+
@expected_count_type ||= :exact
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def twice
|
13
|
+
@expected_count = 2
|
14
|
+
@expected_count_type ||= :exact
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def times(count)
|
19
|
+
@expected_count = count
|
20
|
+
@expected_count_type ||= :exact
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def at_least(count = nil)
|
25
|
+
@expected_count = { once: 1, twice: 2 }.fetch(count, nil)
|
26
|
+
@expected_count_type = :at_least
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def at_most(count = nil)
|
31
|
+
@expected_count = { once: 1, twice: 2 }.fetch(count, nil)
|
32
|
+
@expected_count_type = :at_most
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def count_match?
|
39
|
+
case @expected_count_type
|
40
|
+
when :exact
|
41
|
+
@actual_count == @expected_count
|
42
|
+
when :at_least
|
43
|
+
@actual_count >= @expected_count
|
44
|
+
when :at_most
|
45
|
+
@actual_count <= @expected_count
|
46
|
+
else
|
47
|
+
raise ArgumentError, 'Unknown count comparison type'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -42,6 +42,12 @@ module RSpecHTML
|
|
42
42
|
RSpecHTML::Element.reconstituted(element, options)
|
43
43
|
end
|
44
44
|
|
45
|
+
def pluralize(number, string)
|
46
|
+
return "#{number} #{string}" if number == 1
|
47
|
+
|
48
|
+
"#{number} #{string}s"
|
49
|
+
end
|
50
|
+
|
45
51
|
private
|
46
52
|
|
47
53
|
def template(type, options, expected, actual = nil)
|
@@ -5,10 +5,16 @@ module RSpecHTML
|
|
5
5
|
# Matches elements within a given DOM element.
|
6
6
|
class ContainTag
|
7
7
|
include Base
|
8
|
+
include Countable
|
8
9
|
|
9
|
-
def match(actual)
|
10
|
-
@actual = actual
|
11
|
-
|
10
|
+
def match(actual, expected_count:, expected_count_type:)
|
11
|
+
@actual = actual
|
12
|
+
@expected_count = expected_count
|
13
|
+
@expected_count_type = expected_count_type
|
14
|
+
return actual.public_send(@expected, @options).present? if @expected_count.nil?
|
15
|
+
|
16
|
+
@actual_count = actual.public_send(@expected, @options).size
|
17
|
+
count_match?
|
12
18
|
end
|
13
19
|
end
|
14
20
|
end
|
@@ -5,11 +5,14 @@ module RSpecHTML
|
|
5
5
|
# Matches text or regex within a given DOM element (strips HTML tags and compares text content only).
|
6
6
|
class MatchText
|
7
7
|
include Base
|
8
|
+
include Countable
|
8
9
|
|
9
10
|
diffable
|
10
11
|
|
11
|
-
def match(actual)
|
12
|
+
def match(actual, expected_count:, expected_count_type:)
|
12
13
|
raise_argument_error unless actual.is_a?(RSpecHTML::Element)
|
14
|
+
@expected_count = expected_count
|
15
|
+
@expected_count_type = expected_count_type
|
13
16
|
@rspec_actual = actual&.text
|
14
17
|
return regexp_match?(actual) || regexp_siblings_match?(actual) if @expected.is_a?(Regexp)
|
15
18
|
|
@@ -19,7 +22,10 @@ module RSpecHTML
|
|
19
22
|
private
|
20
23
|
|
21
24
|
def regexp_match?(actual)
|
22
|
-
@expected.match(actual&.text || '')
|
25
|
+
return @expected.match(actual&.text || '') if @expected_count.nil?
|
26
|
+
|
27
|
+
@actual_count = actual&.text&.scan(@expected)&.size
|
28
|
+
count_match?
|
23
29
|
end
|
24
30
|
|
25
31
|
def regexp_siblings_match?(actual)
|
@@ -27,7 +33,10 @@ module RSpecHTML
|
|
27
33
|
end
|
28
34
|
|
29
35
|
def string_match?(actual)
|
30
|
-
(actual&.text || '').include?(@expected.to_s)
|
36
|
+
return (actual&.text || '').include?(@expected.to_s) if @expected_count.nil?
|
37
|
+
|
38
|
+
@actual_count = actual&.text&.scan(@expected)&.size
|
39
|
+
count_match?
|
31
40
|
end
|
32
41
|
|
33
42
|
def string_siblings_match?(actual)
|
data/lib/rspec_html/matchers.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rspec_html/matchers/base'
|
4
|
-
require 'rspec_html/matchers/contain_text'
|
5
4
|
require 'rspec_html/matchers/contain_tag'
|
6
5
|
require 'rspec_html/matchers/match_text'
|
7
6
|
|
@@ -18,17 +17,19 @@ module RSpecHTML
|
|
18
17
|
match do |actual|
|
19
18
|
rspec_html_matcher
|
20
19
|
.save_actual(actual)
|
21
|
-
.match(actual)
|
20
|
+
.match(actual, expected_count: @expected_count, expected_count_type: @expected_count_type)
|
22
21
|
.tap { @actual = rspec_html_matcher.rspec_actual }
|
23
22
|
end
|
24
23
|
description { rspec_html_matcher.description }
|
24
|
+
|
25
|
+
include Countable
|
26
|
+
|
25
27
|
failure_message { rspec_html_matcher.failure_message }
|
26
28
|
diffable if class_.diffable?
|
27
29
|
end
|
28
30
|
end
|
29
31
|
# rubocop:enable Metrics/MethodLength
|
30
32
|
|
31
|
-
define_matcher(:contain_text, ContainText)
|
32
33
|
define_matcher(:contain_tag, ContainTag)
|
33
34
|
define_matcher(:match_text, MatchText)
|
34
35
|
end
|
data/lib/rspec_html.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
<% if actual.element.nil? %>
|
2
|
+
<% if @expected_count.nil? %>
|
2
3
|
Expected <%= reconstituted(actual, @options) %> to contain <%= reconstituted(expected, @options) %> but the element did not exist.
|
4
|
+
<% else %>
|
5
|
+
Expected <%= reconstituted(actual, @options) %> to contain <%= reconstituted(expected, @options) %> <%= @expected_count_type.to_s.tr('_', ' ') %> <%= pluralize(@expected_count, 'time') %> but the element did not exist.
|
6
|
+
<% end %>
|
3
7
|
<% else %>
|
8
|
+
<% if @expected_count.nil? %>
|
4
9
|
Expected <%= reconstituted(actual, @options) %> to contain <%= reconstituted(expected, @options) %> but it did not.
|
10
|
+
<% else %>
|
11
|
+
Expected <%= reconstituted(actual, @options) %> to contain <%= reconstituted(expected, @options) %> <%= @expected_count_type.to_s.tr('_', ' ') %> <%= pluralize(@expected_count, 'time') %> but it was found <%= pluralize(@actual_count, 'time') %>.
|
12
|
+
<% end %>
|
5
13
|
<% end %>
|
@@ -1,5 +1,13 @@
|
|
1
1
|
<% unless actual.exist? %>
|
2
|
-
|
2
|
+
<% if @expected_count.nil? %>
|
3
|
+
Expected <%= actual.reconstituted %> to match <%= expected.inspect %> but the element did not exist.
|
4
|
+
<% else %>
|
5
|
+
Expected <%= actual.reconstituted %> to match <%= expected.inspect %> <%= @expected_count_type.to_s.tr('_', ' ') %> <%= pluralize(@expected_count, 'time') %> but the element did not exist.
|
6
|
+
<% end %>
|
3
7
|
<% else %>
|
4
|
-
|
8
|
+
<% if @expected_count.nil? %>
|
9
|
+
Expected text in <%= actual.reconstituted %> <%= (actual.truncated_text).inspect %> to match <%= expected.inspect %> but it did not.
|
10
|
+
<% else %>
|
11
|
+
Expected text in <%= actual.reconstituted %> <%= (actual.truncated_text).inspect %> to match <%= @expected_count_type.to_s.tr('_', ' ') %> <%= expected.inspect %> exactly <%= pluralize(@expected_count, 'time') %> but it was matched <%= pluralize(@actual_count, 'time') %>.
|
12
|
+
<% end %>
|
5
13
|
<% end %>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-html
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bob Farrell
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -61,21 +61,19 @@ files:
|
|
61
61
|
- lib/rspec/html.rb
|
62
62
|
- lib/rspec/html/version.rb
|
63
63
|
- lib/rspec_html.rb
|
64
|
+
- lib/rspec_html/countable.rb
|
64
65
|
- lib/rspec_html/element.rb
|
65
66
|
- lib/rspec_html/matchers.rb
|
66
67
|
- lib/rspec_html/matchers/base.rb
|
67
68
|
- lib/rspec_html/matchers/contain_tag.rb
|
68
|
-
- lib/rspec_html/matchers/contain_text.rb
|
69
69
|
- lib/rspec_html/matchers/match_text.rb
|
70
70
|
- lib/rspec_html/reconstituted_element.rb
|
71
71
|
- lib/rspec_html/search.rb
|
72
72
|
- lib/rspec_html/tags.rb
|
73
73
|
- rspec-html.gemspec
|
74
74
|
- templates/description/contain_tag.erb
|
75
|
-
- templates/description/contain_text.erb
|
76
75
|
- templates/description/match_text.erb
|
77
76
|
- templates/failure/contain_tag.erb
|
78
|
-
- templates/failure/contain_text.erb
|
79
77
|
- templates/failure/match_text.erb
|
80
78
|
homepage: https://github.com/bobf/rspec-html
|
81
79
|
licenses:
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RSpecHTML
|
4
|
-
module Matchers
|
5
|
-
# Matches text within a given DOM element (strips HTML tags and compares text content only).
|
6
|
-
# (Deprecated in favour of `#match_text`.
|
7
|
-
class ContainText
|
8
|
-
include Base
|
9
|
-
|
10
|
-
diffable
|
11
|
-
|
12
|
-
def match(actual)
|
13
|
-
warn('[rspec-html] The `contain_text` matcher is deprecated. Use `match_text` instead.')
|
14
|
-
@rspec_actual = actual&.text
|
15
|
-
(actual&.text || '').include?(@expected.to_s)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
contain text <%= expected.inspect %>
|
@@ -1,5 +0,0 @@
|
|
1
|
-
<% unless actual.exist? %>
|
2
|
-
Expected <%= actual.reconstituted %> to contain <%= expected.inspect %> but the element did not exist.
|
3
|
-
<% else %>
|
4
|
-
Expected text in <%= actual.reconstituted %> <%= (actual.truncated_text).inspect %> to contain <%= expected.inspect %> but it did not.
|
5
|
-
<% end %>
|