rspec-html 0.3.2 → 0.3.4

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 +58 -18
  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 +1 -0
  11. data/lib/rspec_html/search.rb +10 -4
  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 +49 -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: 654cc8f392f2bde8068c50baedce33357749605760fe00d46558d6aef072a21f
4
- data.tar.gz: 7d207122c82ceb3681ab65cd00c05871c73fbe2628855943fa6c6758ab957451
3
+ metadata.gz: c535d0ae9c4cf1a7f73cdae79bf0b09a6ad24e78fb7fe787ea098c9640456940
4
+ data.tar.gz: 335afe827f7526ed40446930aa6555f06a54c76b0da532c0097b435e0d622ec0
5
5
  SHA512:
6
- metadata.gz: a1295ec0d0106dcfc7dc4157be1a56527137b2c671d814e1a62226a062a8e83844bf2571cbd42a90e2317033b4f39a8b4c433b2733ae3e186aed9ce6af1d6e7e
7
- data.tar.gz: 8e5b3027c778ccbbf5ddc46b3b915ee87de9b3e0cbdbd022c196b6a81e4cb2912faccf8eebaab6b76e4ce517d3c957e91a79bb5f2c28c7f2a20193e5e0e1dd8c
6
+ metadata.gz: 161327678741ab16f3f010e8c4e3c30d9b098c63387d26e504f45cb54fd7115b2b21e6dd051a683312a489e6d007c3355e25ef3aed4a0f5451ec7fcdbff1d1e0
7
+ data.tar.gz: b662a85ff8fb34ca580ec8a883bd5241201823cd64decb531929f478f03f24490aaaba123bdad005e2e51d2c52265581063229d19c5cc0c1b9bf35294dd8ce57
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.52'
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.2)
4
+ rspec-html (0.3.4)
5
5
  nokogiri (~> 1.10)
6
6
  rspec (~> 3.0)
7
7
 
@@ -16,34 +16,68 @@ 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
25
  json (2.6.3)
24
- mini_portile2 (2.8.2)
26
+ kramdown (2.4.0)
27
+ rexml
28
+ kramdown-parser-gfm (1.1.0)
29
+ kramdown (~> 2.0)
30
+ mail (2.8.1)
31
+ mini_mime (>= 0.1.1)
32
+ net-imap
33
+ net-pop
34
+ net-smtp
35
+ mini_mime (1.1.2)
25
36
  minitest (5.18.0)
26
- nokogiri (1.13.10)
27
- mini_portile2 (~> 2.8.0)
37
+ net-imap (0.3.4)
38
+ date
39
+ net-protocol
40
+ net-pop (0.1.2)
41
+ net-protocol
42
+ net-protocol (0.2.1)
43
+ timeout
44
+ net-smtp (0.3.3)
45
+ net-protocol
46
+ nokogiri (1.15.2-x86_64-linux)
28
47
  racc (~> 1.4)
29
48
  paint (2.3.0)
49
+ paintbrush (0.1.3)
30
50
  parallel (1.23.0)
31
- parser (3.2.2.1)
51
+ parser (3.2.2.3)
32
52
  ast (~> 2.4.1)
33
- racc (1.6.2)
53
+ racc
54
+ racc (1.7.0)
34
55
  rainbow (3.1.1)
35
56
  rake (13.0.6)
36
- regexp_parser (2.8.0)
57
+ redcarpet (3.6.0)
58
+ regexp_parser (2.8.1)
37
59
  rexml (3.2.5)
60
+ rouge (4.1.2)
38
61
  rspec (3.12.0)
39
62
  rspec-core (~> 3.12.0)
40
63
  rspec-expectations (~> 3.12.0)
41
64
  rspec-mocks (~> 3.12.0)
42
65
  rspec-core (3.12.2)
43
66
  rspec-support (~> 3.12.0)
67
+ rspec-documentation (0.0.7)
68
+ htmlbeautifier (~> 1.4)
69
+ kramdown (~> 2.4)
70
+ kramdown-parser-gfm (~> 1.1)
71
+ nokogiri (~> 1.15)
72
+ paintbrush (~> 0.1.3)
73
+ redcarpet (~> 3.6)
74
+ rouge (~> 4.1)
75
+ rspec (~> 3.12)
44
76
  rspec-expectations (3.12.3)
45
77
  diff-lcs (>= 1.2.0, < 2.0)
46
78
  rspec-support (~> 3.12.0)
79
+ rspec-file_fixtures (0.1.6)
80
+ rspec (~> 3.0)
47
81
  rspec-its (1.3.0)
48
82
  rspec-core (>= 3.0.0)
49
83
  rspec-expectations (>= 3.0.0)
@@ -51,7 +85,7 @@ GEM
51
85
  diff-lcs (>= 1.2.0, < 2.0)
52
86
  rspec-support (~> 3.12.0)
53
87
  rspec-support (3.12.0)
54
- rubocop (1.50.2)
88
+ rubocop (1.52.0)
55
89
  json (~> 2.3)
56
90
  parallel (~> 1.10)
57
91
  parser (>= 3.2.0.0)
@@ -61,39 +95,45 @@ GEM
61
95
  rubocop-ast (>= 1.28.0, < 2.0)
62
96
  ruby-progressbar (~> 1.7)
63
97
  unicode-display_width (>= 2.4.0, < 3.0)
64
- rubocop-ast (1.28.0)
98
+ rubocop-ast (1.29.0)
65
99
  parser (>= 3.2.1.0)
66
100
  rubocop-capybara (2.18.0)
67
101
  rubocop (~> 1.41)
102
+ rubocop-factory_bot (2.23.1)
103
+ rubocop (~> 1.33)
68
104
  rubocop-rake (0.6.0)
69
105
  rubocop (~> 1.0)
70
- rubocop-rspec (2.20.0)
106
+ rubocop-rspec (2.22.0)
71
107
  rubocop (~> 1.33)
72
108
  rubocop-capybara (~> 2.17)
109
+ rubocop-factory_bot (~> 2.22)
73
110
  ruby-progressbar (1.13.0)
74
111
  strong_versions (0.4.5)
75
112
  i18n (>= 0.5)
76
113
  paint (~> 2.0)
114
+ timeout (0.3.2)
77
115
  tzinfo (2.0.6)
78
116
  concurrent-ruby (~> 1.0)
79
117
  unicode-display_width (2.4.2)
80
118
  zeitwerk (2.6.8)
81
119
 
82
120
  PLATFORMS
83
- ruby
121
+ x86_64-linux
84
122
 
85
123
  DEPENDENCIES
86
124
  activesupport (~> 6.1)
87
- bundler (~> 2.0)
88
- devpack (~> 0.4.0)
89
- i18n (~> 1.7)
125
+ devpack (~> 0.4.1)
126
+ i18n (~> 1.14)
127
+ mail (~> 2.8)
90
128
  rake (~> 13.0)
129
+ rspec-documentation (~> 0.0.7)
130
+ rspec-file_fixtures (~> 0.1.6)
91
131
  rspec-html!
92
132
  rspec-its (~> 1.3)
93
- rubocop (~> 1.29)
133
+ rubocop (~> 1.52)
94
134
  rubocop-rake (~> 0.6.0)
95
- rubocop-rspec (~> 2.10)
135
+ rubocop-rspec (~> 2.22)
96
136
  strong_versions (~> 0.4.5)
97
137
 
98
138
  BUNDLED WITH
99
- 2.3.11
139
+ 2.4.13
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/ docs01.bob.frl:/mnt/docs/$(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.2'
5
+ VERSION = '0.3.4'
6
6
  end
7
7
  end
@@ -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
 
@@ -56,11 +56,11 @@ module RSpecHTML
56
56
 
57
57
  # rubocop:disable Naming/PredicateName
58
58
  def has_css?(*args)
59
- !@element&.css(*args)&.empty?
59
+ !blank?(@element&.css(*args))
60
60
  end
61
61
 
62
62
  def has_xpath?(*args)
63
- !@element&.xpath(*args)&.empty?
63
+ !blank?(@element&.xpath(*args))
64
64
  end
65
65
  # rubocop:enable Naming/PredicateName
66
66
 
@@ -148,9 +148,9 @@ module RSpecHTML
148
148
  end
149
149
 
150
150
  def where_xpath(tag, query)
151
- conditions = "[#{where_conditions(query)}]" unless query.compact.empty?
151
+ conditions = "[#{where_conditions(query)}]" unless blank?(query.compact)
152
152
  result = @element&.xpath(".//#{tag}#{conditions}")
153
- return result unless @siblings.is_a?(Nokogiri::XML::NodeSet) && (result.nil? || result.empty?)
153
+ return result unless @siblings.is_a?(Nokogiri::XML::NodeSet) && blank?(result)
154
154
 
155
155
  @siblings.xpath(".//#{tag}#{conditions}")
156
156
  end
@@ -175,6 +175,12 @@ module RSpecHTML
175
175
 
176
176
  @element&.css(tag.to_s)
177
177
  end
178
+
179
+ def blank?(object)
180
+ return true unless object
181
+
182
+ object.empty?
183
+ end
178
184
  end
179
185
  # rubocop:enable Metrics/ClassLength
180
186
  end
@@ -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,49 @@
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
+ ## `#[]`
18
+
19
+ 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]`.
20
+
21
+ ```rspec:html
22
+ subject(:document) { parse_html('<div>div #1</div><div>div #2</div><div>div #3</div>') }
23
+
24
+ it 'retrieves all matching elements' do
25
+ expect(document.div[2]).to match_text 'div #2'
26
+ end
27
+ ```
28
+
29
+ You can also use `#first` or `#last` if you prefer:
30
+
31
+ ```rspec:html
32
+ subject(:document) { parse_html('<div>div #1</div><div>div #2</div><div>div #3</div>') }
33
+
34
+ it 'retrieves all matching elements' do
35
+ expect(document.div.last).to match_text 'div #3'
36
+ end
37
+ ```
38
+
39
+ ## `#size`
40
+
41
+ Use `#size` to verify the length of a set of matched elements:
42
+
43
+ ```rspec:html
44
+ subject(:document) { parse_html('<div>div #1</div><div>div #2</div><div>div #3</div>') }
45
+
46
+ it 'retrieves all matching elements' do
47
+ expect(document.div.size).to eql 3
48
+ end
49
+ ```
@@ -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.2
4
+ version: 0.3.4
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-18 00:00:00.000000000 Z
11
+ date: 2023-06-11 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