rspec-html 0.3.3 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.ruby-version +1 -1
  4. data/Gemfile +7 -5
  5. data/Gemfile.lock +69 -25
  6. data/LICENSE.txt +1 -1
  7. data/Makefile +9 -0
  8. data/README.md +6 -200
  9. data/lib/rspec/html/version.rb +1 -1
  10. data/lib/rspec_html/element.rb +2 -1
  11. data/lib/rspec_html/search.rb +7 -1
  12. data/rspec-documentation/pages/000-Introduction.md +38 -0
  13. data/rspec-documentation/pages/010-Usage/010-The document Object.md +13 -0
  14. data/rspec-documentation/pages/010-Usage/020-Selectors.md +45 -0
  15. data/rspec-documentation/pages/010-Usage/030-Matchers/010-match_text.md +27 -0
  16. data/rspec-documentation/pages/010-Usage/030-Matchers/020-contain_tag.md +15 -0
  17. data/rspec-documentation/pages/010-Usage/030-Matchers/030-exist.md +13 -0
  18. data/rspec-documentation/pages/010-Usage/030-Matchers/040-be_checked.md +13 -0
  19. data/rspec-documentation/pages/010-Usage/030-Matchers.md +8 -0
  20. data/rspec-documentation/pages/010-Usage/035-Counters.md +55 -0
  21. data/rspec-documentation/pages/010-Usage/040-Attributes.md +24 -0
  22. data/rspec-documentation/pages/010-Usage/050-Enumerating Elements.md +70 -0
  23. data/rspec-documentation/pages/010-Usage/050-XPath.md +13 -0
  24. data/rspec-documentation/pages/010-Usage.md +25 -0
  25. data/rspec-documentation/pages/020-Request Specs.md +21 -0
  26. data/rspec-documentation/pages/030-Response Inspection.md +23 -0
  27. data/rspec-documentation/pages/400-Alternatives.md +3 -0
  28. data/rspec-documentation/pages/500-License.md +11 -0
  29. data/rspec-documentation/spec_helper.rb +33 -0
  30. data/rspec-html.gemspec +1 -1
  31. metadata +22 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7fe3c7e19e3501479829d1b3999be456b05f0ac309c2400fe4b7aeafc379e2f
4
- data.tar.gz: e4e7d1f167678a432a23b0e6a987027974ec048cf3da0e336d01b585f5127000
3
+ metadata.gz: e46ebedf887629f4f11c117ca0e3183e2966abf9148120c675b980e34ea8eb3d
4
+ data.tar.gz: 1aefb7b84a37692de24edd83fd8cdb4127ceb0c385bb0d81526c425300f25c9c
5
5
  SHA512:
6
- metadata.gz: 8cb1be8484a9b0d230284a890e35149a06e361c2196c0b8e96b47a6e20eb1b112e543c943c0a205ba0f9f5fa5c72e97d82e281f63325c0b7db8c0fce3aa031b5
7
- data.tar.gz: 9b6952bdfcf65162b3446c7ae7a9b6bdfc5dbe570b917acfed740f07dbc2ac60aafb5a4c0262bf1213e79230450ed49de32eff9ab2582fd498061520f67e7729
6
+ metadata.gz: a2e614b5b7064514d4dd27f2fe85b5e9940863abe0ebb3067af0c8f650f8da2245cb1829cea9eac8f1c992f23b1f72bb7b4111c646ef78ada24c7396ccccefbf
7
+ data.tar.gz: 78a0dea60b6260879e8f731cfa3b838c9523ef758b89ce998a5e82a86f86d52f7c65dbf9b2aa7bba6b488501504c33e5b37b48627a54e5f7c6f4da4c1c03421e
data/.gitignore CHANGED
@@ -14,3 +14,4 @@
14
14
  *.swo
15
15
  .byebug_history
16
16
  rspec-html-*.gem
17
+ /rspec-documentation/bundle/
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.9
1
+ 2.7.8
data/Gemfile CHANGED
@@ -6,12 +6,14 @@ source 'https://rubygems.org'
6
6
  gemspec
7
7
 
8
8
  gem 'activesupport', '~> 6.1'
9
- gem 'bundler', '~> 2.0'
10
- gem 'devpack', '~> 0.4.0'
11
- gem 'i18n', '~> 1.7'
9
+ gem 'devpack', '~> 0.4.1'
10
+ gem 'i18n', '~> 1.14'
11
+ gem 'mail', '~> 2.8'
12
12
  gem 'rake', '~> 13.0'
13
+ gem 'rspec-documentation', '~> 0.0.7'
14
+ gem 'rspec-file_fixtures', '~> 0.1.6'
13
15
  gem 'rspec-its', '~> 1.3'
14
- gem 'rubocop', '~> 1.29'
16
+ gem 'rubocop', '~> 1.64'
15
17
  gem 'rubocop-rake', '~> 0.6.0'
16
- gem 'rubocop-rspec', '~> 2.10'
18
+ gem 'rubocop-rspec', '~> 2.22'
17
19
  gem 'strong_versions', '~> 0.4.5'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rspec-html (0.3.3)
4
+ rspec-html (0.3.5)
5
5
  nokogiri (~> 1.10)
6
6
  rspec (~> 3.0)
7
7
 
@@ -16,34 +16,70 @@ GEM
16
16
  zeitwerk (~> 2.3)
17
17
  ast (2.4.2)
18
18
  concurrent-ruby (1.2.2)
19
+ date (3.3.3)
19
20
  devpack (0.4.1)
20
21
  diff-lcs (1.5.0)
21
- i18n (1.13.0)
22
+ htmlbeautifier (1.4.2)
23
+ i18n (1.14.1)
22
24
  concurrent-ruby (~> 1.0)
23
- json (2.6.3)
24
- mini_portile2 (2.8.2)
25
+ json (2.7.2)
26
+ kramdown (2.4.0)
27
+ rexml
28
+ kramdown-parser-gfm (1.1.0)
29
+ kramdown (~> 2.0)
30
+ language_server-protocol (3.17.0.3)
31
+ mail (2.8.1)
32
+ mini_mime (>= 0.1.1)
33
+ net-imap
34
+ net-pop
35
+ net-smtp
36
+ mini_mime (1.1.2)
25
37
  minitest (5.18.0)
26
- nokogiri (1.13.10)
27
- mini_portile2 (~> 2.8.0)
38
+ net-imap (0.3.4)
39
+ date
40
+ net-protocol
41
+ net-pop (0.1.2)
42
+ net-protocol
43
+ net-protocol (0.2.1)
44
+ timeout
45
+ net-smtp (0.3.3)
46
+ net-protocol
47
+ nokogiri (1.15.2-x86_64-linux)
28
48
  racc (~> 1.4)
29
49
  paint (2.3.0)
30
- parallel (1.23.0)
31
- parser (3.2.2.1)
50
+ paintbrush (0.1.3)
51
+ parallel (1.25.1)
52
+ parser (3.3.3.0)
32
53
  ast (~> 2.4.1)
33
- racc (1.6.2)
54
+ racc
55
+ racc (1.8.0)
34
56
  rainbow (3.1.1)
35
57
  rake (13.0.6)
36
- regexp_parser (2.8.0)
37
- rexml (3.2.5)
58
+ redcarpet (3.6.0)
59
+ regexp_parser (2.9.2)
60
+ rexml (3.3.1)
61
+ strscan
62
+ rouge (4.1.2)
38
63
  rspec (3.12.0)
39
64
  rspec-core (~> 3.12.0)
40
65
  rspec-expectations (~> 3.12.0)
41
66
  rspec-mocks (~> 3.12.0)
42
67
  rspec-core (3.12.2)
43
68
  rspec-support (~> 3.12.0)
69
+ rspec-documentation (0.0.7)
70
+ htmlbeautifier (~> 1.4)
71
+ kramdown (~> 2.4)
72
+ kramdown-parser-gfm (~> 1.1)
73
+ nokogiri (~> 1.15)
74
+ paintbrush (~> 0.1.3)
75
+ redcarpet (~> 3.6)
76
+ rouge (~> 4.1)
77
+ rspec (~> 3.12)
44
78
  rspec-expectations (3.12.3)
45
79
  diff-lcs (>= 1.2.0, < 2.0)
46
80
  rspec-support (~> 3.12.0)
81
+ rspec-file_fixtures (0.1.6)
82
+ rspec (~> 3.0)
47
83
  rspec-its (1.3.0)
48
84
  rspec-core (>= 3.0.0)
49
85
  rspec-expectations (>= 3.0.0)
@@ -51,49 +87,57 @@ GEM
51
87
  diff-lcs (>= 1.2.0, < 2.0)
52
88
  rspec-support (~> 3.12.0)
53
89
  rspec-support (3.12.0)
54
- rubocop (1.50.2)
90
+ rubocop (1.64.1)
55
91
  json (~> 2.3)
92
+ language_server-protocol (>= 3.17.0)
56
93
  parallel (~> 1.10)
57
- parser (>= 3.2.0.0)
94
+ parser (>= 3.3.0.2)
58
95
  rainbow (>= 2.2.2, < 4.0)
59
96
  regexp_parser (>= 1.8, < 3.0)
60
97
  rexml (>= 3.2.5, < 4.0)
61
- rubocop-ast (>= 1.28.0, < 2.0)
98
+ rubocop-ast (>= 1.31.1, < 2.0)
62
99
  ruby-progressbar (~> 1.7)
63
100
  unicode-display_width (>= 2.4.0, < 3.0)
64
- rubocop-ast (1.28.0)
65
- parser (>= 3.2.1.0)
101
+ rubocop-ast (1.31.3)
102
+ parser (>= 3.3.1.0)
66
103
  rubocop-capybara (2.18.0)
67
104
  rubocop (~> 1.41)
105
+ rubocop-factory_bot (2.23.1)
106
+ rubocop (~> 1.33)
68
107
  rubocop-rake (0.6.0)
69
108
  rubocop (~> 1.0)
70
- rubocop-rspec (2.20.0)
109
+ rubocop-rspec (2.22.0)
71
110
  rubocop (~> 1.33)
72
111
  rubocop-capybara (~> 2.17)
112
+ rubocop-factory_bot (~> 2.22)
73
113
  ruby-progressbar (1.13.0)
74
114
  strong_versions (0.4.5)
75
115
  i18n (>= 0.5)
76
116
  paint (~> 2.0)
117
+ strscan (3.1.0)
118
+ timeout (0.3.2)
77
119
  tzinfo (2.0.6)
78
120
  concurrent-ruby (~> 1.0)
79
- unicode-display_width (2.4.2)
121
+ unicode-display_width (2.5.0)
80
122
  zeitwerk (2.6.8)
81
123
 
82
124
  PLATFORMS
83
- ruby
125
+ x86_64-linux
84
126
 
85
127
  DEPENDENCIES
86
128
  activesupport (~> 6.1)
87
- bundler (~> 2.0)
88
- devpack (~> 0.4.0)
89
- i18n (~> 1.7)
129
+ devpack (~> 0.4.1)
130
+ i18n (~> 1.14)
131
+ mail (~> 2.8)
90
132
  rake (~> 13.0)
133
+ rspec-documentation (~> 0.0.7)
134
+ rspec-file_fixtures (~> 0.1.6)
91
135
  rspec-html!
92
136
  rspec-its (~> 1.3)
93
- rubocop (~> 1.29)
137
+ rubocop (~> 1.64)
94
138
  rubocop-rake (~> 0.6.0)
95
- rubocop-rspec (~> 2.10)
139
+ rubocop-rspec (~> 2.22)
96
140
  strong_versions (~> 0.4.5)
97
141
 
98
142
  BUNDLED WITH
99
- 2.3.11
143
+ 2.4.22
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2019 Bob Farrell
3
+ Copyright (c) 2019-2023 Robert Farrell
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/Makefile CHANGED
@@ -1,5 +1,14 @@
1
+ project=rspec-html
2
+
1
3
  .PHONY:
2
4
  test:
3
5
  bundle exec rspec
4
6
  bundle exec rubocop
5
7
  bundle exec strong_versions
8
+ bundle exec rspec-documentation
9
+
10
+ .PHONY: publish
11
+ publish:
12
+ @RSPEC_DOCUMENTATION_URL_ROOT='/$(project)' bundle exec rspec-documentation
13
+ @rsync --delete -r rspec-documentation/bundle/ docs.bob.frl:/var/www/html/$(project)/
14
+ @echo 'Published.'
data/README.md CHANGED
@@ -1,13 +1,15 @@
1
1
  # RSpec::HTML
2
2
 
3
- _RSpec::HTML_ provides a simple object interface to HTML responses from [_RSpec Rails_ request specs](https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec).
3
+ _RSpec::HTML_ provides a simple object interface to _HTML_ content.
4
+
5
+ You can use _RSpec::HTML_ to test any _HTML_ produced by your library or application and it works out of the box with [_RSpec Rails_ request specs](https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec).
4
6
 
5
7
  ## Installation
6
8
 
7
9
  Add the gem to your `Gemfile`:
8
10
 
9
11
  ```ruby
10
- gem 'rspec-html', '~> 0.3.0'
12
+ gem 'rspec-html', '~> 0.3.4'
11
13
  ```
12
14
 
13
15
  And rebuild your bundle:
@@ -16,205 +18,9 @@ And rebuild your bundle:
16
18
  $ bundle install
17
19
  ```
18
20
 
19
- ## Usage
20
-
21
- Require the gem in your `spec_helper.rb`:
22
-
23
- ```
24
- # spec/spec_helper.rb
25
- require 'rspec/html'
26
- ```
27
-
28
- Several [matchers](#matchers) are provided to identify text and _HTML_ elements within the _DOM_. These matchers can only be used with the provided [object interface](#object-interface).
29
-
30
- ### Browser Inspection
31
-
32
- To open the current document in your operating system's default browser, call `document.open`. Use this to inspect _HTML_ content while debugging.
33
-
34
- ```ruby
35
- it 'has complex HTML' do
36
- get '/my/path'
37
- document.open
38
- end
39
- ```
40
-
41
- Alternatively `document.html_path` writes the current document to a temporary file and returns its path.
42
-
43
- ### Object Interface
44
- <a name="object-interface"></a>
45
-
46
- The top-level object `document` is available in all tests which reflects the current response body (e.g. in request specs).
47
-
48
- If you need to parse _HTML_ manually you can use the provided `parse_html` helper and then access `document` as normal:
49
-
50
- ```ruby
51
- let(:document) { parse_html('<html><body>hello</body></html>') }
52
-
53
- it 'says hello' do
54
- expect(document.body).to match_text 'hello'
55
- end
56
- ```
57
-
58
- This method can also be used for _ActionMailer_ emails:
59
- ```ruby
60
- let(:document) { parse_html(ActionMailer::Base.deliveries.last.body.decoded) }
61
- ```
62
-
63
- **Changed in 0.3.0**: `parse_html` no longer assigns `document` automatically, you must use a `let` block to assign it yourself.
64
-
65
- To navigate the _DOM_ by a sequence of tag names use chained method calls on the `document` object:
66
-
67
- #### Tag Traversal
68
- ```ruby
69
- expect(document.body.div.span).to match_text 'some text'
70
- ```
71
-
72
- #### Attribute Matching
73
- To select an element matching certain attributes pass a hash to any of the chained methods:
74
- ```ruby
75
- expect(document.body.div(id: 'my-div').span(align: 'left')).to match_text 'some text'
76
- ```
77
-
78
- Special attributes like `checked` can be found using the `#attributes` method:
79
- ```ruby
80
- expect(document.input(type: 'checkbox', name: 'my-name')).attributes).to include 'checked'
81
- ```
82
-
83
- #### Class Matching
84
- _CSS_ classes are treated as a special case: to select an element matching a set of classes pass the `class` parameter:
85
- ```ruby
86
- expect(document.body.div(id: 'my-div').span(class: 'my-class')).to match_text 'some text'
87
- expect(document.body.div(id: 'my-div').span(class: 'my-class my-other-class')).to match_text 'some text'
88
- ```
89
-
90
- Classes can be provided in any order, i.e. `'my-class my-other-class'` is equivalent to `'my-other-class my-class'`.
91
-
92
- #### Simple CSS Matching
93
- To use a simple CSS selector when no other attributes are needed, pass a string to the tag method:
94
- ```ruby
95
- expect(document.body.div('#my-id.my-class1.my-class2')).to match_text 'some text'
96
- ```
97
-
98
- This is effectively shorthand for:
99
- ```ruby
100
- expect(document.body.div(id: 'my-id', class: 'my-class1 my-class2')).to match_text 'some text'
101
- ```
102
-
103
- #### Counting Matchers
104
- Use `once`, `twice`, `times`, `at_least`, and `at_most` to match element counts:
105
- ```ruby
106
- expect(document.div('.my-class')).to match_text('some text').once
107
- expect(document.div('.my-class')).to match_text('some other text').twice
108
- expect(document.div('.my-class')).to match_text('some').times(3)
109
- expect(document.div('.my-class')).to match_text('some').at_least(:twice)
110
- expect(document.div('.my-class')).to match_text('some').at_least.times(3)
111
- expect(document.div('.my-class')).to match_text('some text').at_most.once
112
- expect(document.div('.my-class')).to match_text('some other text').at_most.twice
113
- expect(document.div('.my-class')).to match_text('text').at_most.times(3)
114
- ```
115
-
116
- #### Text Matching
117
- To select an element that includes a given text string (i.e. excluding mark-up) use the `text` option:
118
- ```ruby
119
- expect(document.body.div(text: 'some text').input[:value]).to eql 'some-value'
120
- ```
121
-
122
- #### Attribute Retrieval
123
- To select an attribute from an element use the hash-style interface:
124
- ```ruby
125
- expect(document.body.div.span[:class]).to match_text 'my-class'
126
- expect(document.body.div.span['data-content']).to match_text 'my content'
127
- ```
128
-
129
- #### Retrieve all matching elements
130
- To select all matching elements as an array, use `#all`:
131
- ```ruby
132
- expect(document.span.all.map(&:text)).to eql ['foo', 'bar', 'baz']
133
- ```
134
-
135
- #### Indexing a Matching Set
136
- To select an index from a set of matched elements use the array-style interface (the first matching element is `1`, not `0`):
137
- ```ruby
138
- expect(document.body.div[1].span[1][:class]).to match_text 'my-class'
139
- ```
140
-
141
- Alternatively, use `#first`, `#last` or, when using _ActiveSupport_, `#second`, `#third`, etc. are also available:
142
-
143
- ```ruby
144
- expect(document.body.div.first.span.last[:class]).to match_text 'my-class'
145
- ```
146
-
147
-
148
- #### Element Existence
149
- To test if a matching element was found use the `exist` matcher:
150
- ```ruby
151
- expect(document.body.div[1]).to exist
152
- expect(document.body.div[4]).to_not exist
153
- ```
154
-
155
- #### Length of matched attributes
156
- To test the length of matched elements use the `#size` or `#length` method:
157
- ```ruby
158
- expect(document.body.div.size).to eql 3
159
- expect(document.body.div.length).to eql 3
160
- ```
161
-
162
- #### XPath / CSS Selectors
163
- If you need something more specific you can always use the _Nokogiri_ `#xpath` and `#css` methods on any element:
164
- ```ruby
165
- expect(document.body.xpath('//span[@class="my-class"]')).to match_text 'some text'
166
- expect(document.body.css('span.my-class')).to match_text 'some text'
167
- ```
168
-
169
- To simply check that an _XPath_ or _CSS_ selector exists use `have_xpath` and `have_css`:
170
- ```ruby
171
- expect(document.body).to have_css 'html body div.myclass'
172
- expect(document.body).to have_xpath '//html/body/div[@class="myclass"]'
173
- ```
174
-
175
- #### Checkboxes
176
-
177
- Use `be_checked` to test if a checkbox is checked:
178
- ```ruby
179
- expect(document.input(type: 'checkbox')).to be_checked
180
- ```
181
-
182
- ### Custom Matchers
183
- <a name="matchers"></a>
184
-
185
- **Changed in 0.3.0**: The `contain_text` matcher has been removed. Use `match_text` instead.
186
-
187
- #### match_text
188
-
189
- Use the `match_text` matcher to locate text within a _DOM_ element. All mark-up elements are stripped when using this matcher.
190
-
191
- This matcher receives either a string or a regular expression.
192
-
193
- ```ruby
194
- expect(document.body.form).to match_text 'Please enter your password'
195
- expect(document.body.form).to match_text /Please enter your [a-z]+/
196
- ```
197
-
198
- #### contain_tag
199
-
200
- Use the `contain_tag` matcher to locate _DOM_ elements within any given element. This matcher accepts two arguments:
201
-
202
- * The tag name of the element you want to match (e.g. `:div`);
203
- * _(Optional)_ A hash of options. All options supported by the [object interface](#object-interface) can be used here.
204
-
205
- Without options:
206
- ```ruby
207
- expect(document.div(class: 'my-class')).to contain_tag :span
208
- ```
209
-
210
- With options:
211
- ```ruby
212
- expect(document.form(class: 'my-form')).to contain_tag :input, name: 'email', class: 'email-input'
213
- ```
214
-
215
- ## Contributing
21
+ ## Documentation
216
22
 
217
- Feel free to make a pull request.
23
+ See the [documentation](https://docs.bob.frl/rspec_html) for full usage guide.
218
24
 
219
25
  ## License
220
26
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RSpec
4
4
  module HTML
5
- VERSION = '0.3.3'
5
+ VERSION = '0.3.5'
6
6
  end
7
7
  end
@@ -9,7 +9,7 @@ module RSpecHTML
9
9
 
10
10
  def_delegators :@search,
11
11
  :has_css?, :has_xpath?, :include?,
12
- :all, :siblings, :text, :truncated_text, :attributes, :to_a,
12
+ :all, :siblings, :children, :text, :truncated_text, :attributes, :to_a,
13
13
  :size, :length, :[],
14
14
  :css, :xpath, :checked?,
15
15
  :first, :last, :second, :third, :fourth, :fifth
@@ -49,6 +49,7 @@ module RSpecHTML
49
49
 
50
50
  Tags.each do |tag|
51
51
  define_method tag.downcase do |*args|
52
+ args[0] = " #{args[0]}" if args.first.is_a?(String) && args.first&.match?(/^[a-zA-Z]/)
52
53
  options = args.first
53
54
  return @search.new_from_find(tag.downcase, options) if options.nil?
54
55
 
@@ -83,7 +83,7 @@ module RSpecHTML
83
83
  end
84
84
 
85
85
  def attributes
86
- @element&.attributes&.to_h { |key, val| [key.to_sym, val.to_s] } || {}
86
+ @element&.attributes.to_h { |key, val| [key.to_sym, val.to_s] }
87
87
  end
88
88
 
89
89
  def size
@@ -93,6 +93,12 @@ module RSpecHTML
93
93
  end
94
94
  alias length size
95
95
 
96
+ def children(text: false) # rubocop:disable Metrics/CyclomaticComplexity
97
+ @element&.children
98
+ &.map { |child| Element.new(child, child.name, siblings: @element.children) }
99
+ &.reject { |child| text ? false : child.name == 'text' } || []
100
+ end
101
+
96
102
  def new_from_find(tag, options)
97
103
  Element.new(
98
104
  find(tag),
@@ -0,0 +1,38 @@
1
+ # Introduction
2
+
3
+ _RSpec::HTML_ provides a simple object interface to _HTML_ content.
4
+
5
+ You can use _RSpec::HTML_ to test any _HTML_ produced by your library or application and it works out of the box with [_RSpec Rails_ request specs](https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec).
6
+
7
+ ## Setup
8
+
9
+ Load `rspec-html` in your `spec_helper.rb`:
10
+
11
+ ```ruby
12
+ # spec/spec_helper.rb
13
+
14
+ require 'rspec/html'
15
+ ```
16
+
17
+ ## Quick Example
18
+
19
+ ```rspec:html
20
+ subject(:document) { parse_html(html) }
21
+
22
+ let(:html) do
23
+ <<~HTML
24
+ <html>
25
+ <body>
26
+ <div>Some other content</div>
27
+ <div class="my-div">
28
+ My div content
29
+ </div>
30
+ </body>
31
+ </html>
32
+ HTML
33
+ end
34
+
35
+ it 'matches "My div content"' do
36
+ expect(document.html.body.div('.my-div')).to match_text 'My div content'
37
+ end
38
+ ```
@@ -0,0 +1,13 @@
1
+ # The `document` Object
2
+
3
+ For [_RSpec Rails_ `request` specs](request-specs.html) the `document` object is already defined by _RSpec::HTML_ as the parsed response body.
4
+
5
+ If you are testing _HTML_ in any other context, e.g. for parsing _ActionMailer_ emails, simply define `document` with a `let` or `subject` block that calls the provided `parse_html` helper:
6
+
7
+ ```rspec:html
8
+ subject(:document) { parse_html(ActionMailer::Base.deliveries.last.body.decoded) }
9
+
10
+ it 'contains a welcome message' do
11
+ expect(document.div('.welcome-message')).to match_text 'Welcome to our website!'
12
+ end
13
+ ```
@@ -0,0 +1,45 @@
1
+ # Selectors
2
+
3
+ Each method in the chain can receive arguments to specify selectors on attributes of each element.
4
+
5
+ To select an `<input>` element with a `name` attribute whose value is `email`, pass `name: 'email'` to the `input` method:
6
+
7
+ ## Attribute Selectors
8
+
9
+ ```rspec:html
10
+ it 'renders a name field' do
11
+ get '/users/new'
12
+ expect(document.form.input(name: 'user[email]')).to exist
13
+ end
14
+ ```
15
+
16
+ ## CSS Selectors
17
+
18
+ Since `class` is an attribute just like `name` you can specify the exact `class` string to match elements as `document.table(class: 'table users')`:
19
+
20
+ However, this test is very fragile - any change to the `class` attribute will cause the test to break, even if the order of the classes changes.
21
+
22
+ Instead, you can match using _CSS_ selectors by passing a string directly to each element method:
23
+
24
+ ```rspec:html
25
+ it 'renders a users table' do
26
+ get '/users'
27
+ expect(document.table('.users')).to exist
28
+ end
29
+ ```
30
+
31
+ Any valid _CSS_ selector is permitted:
32
+
33
+ ```rspec:html
34
+ it 'renders a users table' do
35
+ get '/users'
36
+ expect(document.table('#users-table')).to exist
37
+ end
38
+ ```
39
+
40
+ ```rspec:html
41
+ it 'renders a users table' do
42
+ get '/users'
43
+ expect(document.table('tr td[class="name"]')).to exist
44
+ end
45
+ ```
@@ -0,0 +1,27 @@
1
+ # `match_text`
2
+
3
+ The `match_text` matcher filters out any tags and compacts whitespace to make matching strings within your _HTML_ more straightforward.
4
+
5
+ ## Strings
6
+
7
+ ```rspec:html
8
+ subject(:document) do
9
+ parse_html '<div>Some <span>text</span> with <span>whitespace</span></div>'
10
+ end
11
+
12
+ it 'matches simple strings' do
13
+ expect(document.div).to match_text 'Some text with whitespace'
14
+ end
15
+ ```
16
+
17
+ ## Regular Expressions
18
+
19
+ ```rspec:html
20
+ subject(:document) do
21
+ parse_html '<div>Some <span>text</span> with <span>whitespace</span></div>'
22
+ end
23
+
24
+ it 'matches simple strings' do
25
+ expect(document.div).to match_text /text.*whitespace/
26
+ end
27
+ ```
@@ -0,0 +1,15 @@
1
+ # `contain_tag`
2
+
3
+ The `contain_tag` matcher verifies that an element exists within your document.
4
+
5
+ The matcher receives a tag name as a `Symbol` and optionally receives a set of keyword arguments that refine the element definition.
6
+
7
+ ```rspec:html
8
+ subject(:document) do
9
+ parse_html('<html><body><table><tr><td align="center">td tag</td><tr></table></body></html>')
10
+ end
11
+
12
+ it 'matches an element' do
13
+ expect(document.table).to contain_tag :td, align: 'center'
14
+ end
15
+ ```
@@ -0,0 +1,13 @@
1
+ # `exist`
2
+
3
+ The `exist` matcher verifies that a provided element specification exists in the document.
4
+
5
+ ```rspec:html
6
+ subject(:document) do
7
+ parse_html('<html><body><div><span class="my-span">my text</span></div></body></html>')
8
+ end
9
+
10
+ it 'verifies that an element exists' do
11
+ expect(document.body.div.span('.my-span')).to exist
12
+ end
13
+ ```
@@ -0,0 +1,13 @@
1
+ # `be_checked`
2
+
3
+ Use the `be_checked` matcher to verify that a checkbox input is checked.
4
+
5
+ ```rspec:html
6
+ subject(:document) do
7
+ parse_html('<form><input type="checkbox" checked></form>')
8
+ end
9
+
10
+ it 'renders a checked checkbox input' do
11
+ expect(document.form.input(type: 'checkbox')).to be_checked
12
+ end
13
+ ```
@@ -0,0 +1,8 @@
1
+ # Matchers
2
+
3
+ _RSpec::HTML_ provides a selection of matchers to help you verify your _HTML_ content.
4
+
5
+ * [`match_text`](matchers/match_text.html)
6
+ * [`contain_tag`](matchers/contain_tag.html)
7
+ * [`exist`](matchers/exist.html)
8
+ * [`be_checked`](matchers/be_checked.html)
@@ -0,0 +1,55 @@
1
+ # Counters
2
+
3
+ The [`match_text`](matchers/match_text.html) and [`contain_tag`](matchers/contain_tag.html) matchers support a counting interface to verify the number of occurrences of a given element specification within a document.
4
+
5
+ The following methods can be added to a `match_text` or `contain_tag` call:
6
+
7
+ * `#once`
8
+ * `#twice`
9
+ * `#times(n)`
10
+ * `#at_least(:once)`, `#at_least(:twice)`, `#at_least.times(n)`
11
+ * `#at_most(:once)`, `#at_most(:twice)`, `#at_most.times(n)`
12
+
13
+ ## Exact Counts
14
+
15
+ ```rspec:html
16
+ subject(:document) { parse_html('<div>my div</div>') }
17
+
18
+ it 'has one div' do
19
+ expect(document.div).to match_text('my div').once
20
+ end
21
+ ```
22
+
23
+ ```rspec:html
24
+ subject(:document) { parse_html('<div>my div</div><div>my div</div>') }
25
+
26
+ it 'has one div' do
27
+ expect(document.div).to match_text('my div').twice
28
+ end
29
+ ```
30
+
31
+ ```rspec:html
32
+ subject(:document) { parse_html('<div>my div</div><div>my div</div><div>my div</div>') }
33
+
34
+ it 'has one div' do
35
+ expect(document.div).to match_text('my div').times(3)
36
+ end
37
+ ```
38
+
39
+ ## Minimum/Maximum Counts
40
+
41
+ ```rspec:html
42
+ subject(:document) { parse_html('<div>my div</div><div>my div</div><div>my div</div>') }
43
+
44
+ it 'has at least two divs' do
45
+ expect(document.div).to match_text('my div').at_least(:twice)
46
+ end
47
+ ```
48
+
49
+ ```rspec:html
50
+ subject(:document) { parse_html('<div>my div</div><div>my div</div><div>my div</div>') }
51
+
52
+ it 'has at most three divs' do
53
+ expect(document.div).to match_text('my div').at_most.times(3)
54
+ end
55
+ ```
@@ -0,0 +1,24 @@
1
+ # Attributes
2
+
3
+ If you need to verify the exact value of an attribute, use `Hash` key lookup syntax by calling `#[]`.
4
+
5
+ ```rspec:html
6
+ subject(:document) do
7
+ parse_html('<div><input type="text" value="my-value"></div>')
8
+ end
9
+
10
+ it 'has an input whose value is "my-value"' do
11
+ expect(document.div.input[:value]).to eql 'my-value'
12
+ end
13
+ ```
14
+
15
+ One particular use case that makes this especially useful is retrieving an anti-CSRF token from a form before submitting a `POST` or `PATCH` request:
16
+
17
+ ```rspec:html
18
+ it 'submits a user form' do
19
+ get '/users/new'
20
+ token = document.form.input(name: 'authenticity_token')[:value]
21
+ post '/users', params: { user: { email: 'user@example.com' }, authenticity_token: token }
22
+ expect(document.div('.flash.notice')).to match_text 'User successfully created'
23
+ end
24
+ ```
@@ -0,0 +1,70 @@
1
+ # Enumerating Elements
2
+
3
+ Several interfaces exist to assist you if you need to test specifics about the number of elements that match a given element specification.
4
+
5
+ ## `#all`
6
+
7
+ Use `#all` to retrieve all matched elements:
8
+
9
+ ```rspec:html
10
+ subject(:document) { parse_html('<div>div #1</div><div>div #2</div><div>div #3</div>') }
11
+
12
+ it 'retrieves all matching elements' do
13
+ expect(document.div.all).to all(match_text /div #[0-9]/)
14
+ end
15
+ ```
16
+
17
+ ## `#children`
18
+
19
+ Use `#children` to access immediate child elements of the current element. Return an empty array if no children present.
20
+
21
+ ```rspec:html
22
+ subject(:document) { parse_html('<div>text<span>span #1</span><span>span #2</span></div>') }
23
+
24
+ it 'retrieves all matching elements' do
25
+ expect(document.div.children.size).to eql 2
26
+ end
27
+ ```
28
+
29
+ Pass `text: true` to also include dangling text (default: `false`):
30
+
31
+ ```rspec:html
32
+ subject(:document) { parse_html('<div>text<span>span #1</span><span>span #2</span></div>') }
33
+
34
+ it 'retrieves all matching elements' do
35
+ expect(document.div.children(text: true).size).to eql 3
36
+ end
37
+ ```
38
+ ## `#[]`
39
+
40
+ Use `#[]` to index a specific element from a matching set. Note that indexing starts at `1`, not `0`, so the first element in a set is `[1]`.
41
+
42
+ ```rspec:html
43
+ subject(:document) { parse_html('<div>div #1</div><div>div #2</div><div>div #3</div>') }
44
+
45
+ it 'retrieves all matching elements' do
46
+ expect(document.div[2]).to match_text 'div #2'
47
+ end
48
+ ```
49
+
50
+ You can also use `#first` or `#last` if you prefer:
51
+
52
+ ```rspec:html
53
+ subject(:document) { parse_html('<div>div #1</div><div>div #2</div><div>div #3</div>') }
54
+
55
+ it 'retrieves all matching elements' do
56
+ expect(document.div.last).to match_text 'div #3'
57
+ end
58
+ ```
59
+
60
+ ## `#size`
61
+
62
+ Use `#size` to verify the length of a set of matched elements:
63
+
64
+ ```rspec:html
65
+ subject(:document) { parse_html('<div>div #1</div><div>div #2</div><div>div #3</div>') }
66
+
67
+ it 'retrieves all matching elements' do
68
+ expect(document.div.size).to eql 3
69
+ end
70
+ ```
@@ -0,0 +1,13 @@
1
+ # XPath
2
+
3
+ If the provided _DOM_ navigation syntax does not provide a way for you to definitively select the required element, [_XPath_](https://developer.mozilla.org/en-US/docs/Web/XPath) is available via the `#xpath` method which delegates directly to the underlying [`Nokogiri::HTML::Document`](https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/HTML/Document) object.
4
+
5
+ ```rspec:html
6
+ subject(:document) do
7
+ parse_html('<table><tr><td>My content</td></table>')
8
+ end
9
+
10
+ it 'matches using xpath' do
11
+ expect(document.xpath('//table/tr/td').text).to eql 'My content'
12
+ end
13
+ ```
@@ -0,0 +1,25 @@
1
+ # Usage
2
+
3
+ _RSpec::HTML_ uses method chaining to traverse the Document Object Model (_DOM_).
4
+
5
+ ## Implicit Navigation
6
+
7
+ Access a `<td>` tag anywhere in the document:
8
+
9
+ ```rspec:html
10
+ it 'renders a user name' do
11
+ get '/users'
12
+ expect(document.td('.name')).to match_text 'Example User #1'
13
+ end
14
+ ```
15
+
16
+ ## Explicit Navigation
17
+
18
+ Or specify the full node tree explicitly to ensure your document's structure:
19
+
20
+ ```rspec:html
21
+ it 'renders a user name' do
22
+ get '/users'
23
+ expect(document.body.table.tbody.tr.td('.name')).to match_text 'Example User #1'
24
+ end
25
+ ```
@@ -0,0 +1,21 @@
1
+ # Request Specs
2
+
3
+ If you're using _RSpec_ `request` specs with _Rails_, a `document` object is available in all specs, wrapping the `response.body` of each request.
4
+
5
+ ```rspec:html
6
+ it 'generates a users table' do
7
+ get '/users'
8
+ expect(document.table('.users').td('.email')).to match_text 'user@example.com'
9
+ end
10
+ ```
11
+
12
+ This is equivalent to:
13
+
14
+ ```rspec:html
15
+ let(:document) { parse_html(response.body) }
16
+
17
+ it 'generates a users table' do
18
+ get '/users'
19
+ expect(document.table('.users').td('.email')).to match_text 'user@example.com'
20
+ end
21
+ ```
@@ -0,0 +1,23 @@
1
+ # Response Inspection
2
+
3
+ ## Browser Inspection
4
+
5
+ To make debugging a little easier, the output of the parsed document can be loaded into your operating system's default web browser by calling `document.open`.
6
+
7
+ ```ruby
8
+ it 'has complex HTML' do
9
+ get '/my/path'
10
+ document.open
11
+ end
12
+ ```
13
+
14
+ ## String Inspection
15
+
16
+ Alternatively, the document can be printed to `stdout` by calling `puts document`.
17
+
18
+ ```ruby
19
+ it 'has complex HTML' do
20
+ get '/my/path'
21
+ puts document
22
+ end
23
+ ```
@@ -0,0 +1,3 @@
1
+ # Alternatives
2
+
3
+ * [rspec-html-matchers](https://github.com/kucaahbe/rspec-html-matchers)
@@ -0,0 +1,11 @@
1
+ # License
2
+
3
+ ## MIT
4
+
5
+ Copyright 2019-2023 Robert Farrell
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/html'
4
+ require 'rspec/file_fixtures'
5
+
6
+ require 'mail'
7
+
8
+ module ActionMailer
9
+ # Stub for ActionMailer::Base to return an example delivered email.
10
+ class Base
11
+ def self.deliveries
12
+ [Mail.new { body '<div class="welcome-message">Welcome to our website!</div>' }]
13
+ end
14
+ end
15
+ end
16
+
17
+ RSpec::Documentation.configure do |config|
18
+ config.context do
19
+ def get(path)
20
+ @response_body = fixture("html#{path}.html").read
21
+ end
22
+
23
+ def post(path, params:) # rubocop:disable Lint/UnusedMethodArgument
24
+ @response_body = fixture("html#{path}/create.html").read
25
+ end
26
+
27
+ def response
28
+ double(body: @response_body)
29
+ end
30
+
31
+ subject { @response_body }
32
+ end
33
+ end
data/rspec-html.gemspec CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.executables = []
29
29
  spec.require_paths = ['lib']
30
30
 
31
- spec.required_ruby_version = '>= 2.6'
31
+ spec.required_ruby_version = '>= 2.7'
32
32
  spec.add_dependency 'nokogiri', '~> 1.10'
33
33
  spec.add_dependency 'rspec', '~> 3.0'
34
34
  spec.metadata['rubygems_mfa_required'] = 'true'
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.3.3
4
+ version: 0.3.5
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-27 00:00:00.000000000 Z
11
+ date: 2024-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -71,6 +71,24 @@ files:
71
71
  - lib/rspec_html/reconstituted_element.rb
72
72
  - lib/rspec_html/search.rb
73
73
  - lib/rspec_html/tags.rb
74
+ - rspec-documentation/pages/000-Introduction.md
75
+ - rspec-documentation/pages/010-Usage.md
76
+ - rspec-documentation/pages/010-Usage/010-The document Object.md
77
+ - rspec-documentation/pages/010-Usage/020-Selectors.md
78
+ - rspec-documentation/pages/010-Usage/030-Matchers.md
79
+ - rspec-documentation/pages/010-Usage/030-Matchers/010-match_text.md
80
+ - rspec-documentation/pages/010-Usage/030-Matchers/020-contain_tag.md
81
+ - rspec-documentation/pages/010-Usage/030-Matchers/030-exist.md
82
+ - rspec-documentation/pages/010-Usage/030-Matchers/040-be_checked.md
83
+ - rspec-documentation/pages/010-Usage/035-Counters.md
84
+ - rspec-documentation/pages/010-Usage/040-Attributes.md
85
+ - rspec-documentation/pages/010-Usage/050-Enumerating Elements.md
86
+ - rspec-documentation/pages/010-Usage/050-XPath.md
87
+ - rspec-documentation/pages/020-Request Specs.md
88
+ - rspec-documentation/pages/030-Response Inspection.md
89
+ - rspec-documentation/pages/400-Alternatives.md
90
+ - rspec-documentation/pages/500-License.md
91
+ - rspec-documentation/spec_helper.rb
74
92
  - rspec-html.gemspec
75
93
  - templates/description/contain_tag.erb
76
94
  - templates/description/match_text.erb
@@ -92,14 +110,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
92
110
  requirements:
93
111
  - - ">="
94
112
  - !ruby/object:Gem::Version
95
- version: '2.6'
113
+ version: '2.7'
96
114
  required_rubygems_version: !ruby/object:Gem::Requirement
97
115
  requirements:
98
116
  - - ">="
99
117
  - !ruby/object:Gem::Version
100
118
  version: '0'
101
119
  requirements: []
102
- rubygems_version: 3.0.3.1
120
+ rubygems_version: 3.1.6
103
121
  signing_key:
104
122
  specification_version: 4
105
123
  summary: RSpec HTML