rspec-html 0.2.1 → 0.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d34bf3daf886cca4a7e1b50de449025cd6255629a1fb6a5f6d319c6353fedb0a
4
- data.tar.gz: 7f3ae650fa77a60700211ccda0cb3478e034d8b14f22a40375b89ed007ecb843
3
+ metadata.gz: ffb46cc469517211466583fd590e8d77a39368eba859513771a43438508c3985
4
+ data.tar.gz: db46d8cc7a56d00e91517d8df9465d7c5915f82d4fc35b9314ba02f043921c1b
5
5
  SHA512:
6
- metadata.gz: a0ae4675dfe2790a31f953e089ce97157f6b736967abc96281eb50adc04641e1725765085be03770686dbff3c8ccd8d2fa4d567fd366edaabb74c7f2348f5e27
7
- data.tar.gz: '0384ab293fe9397088d0c6356585d1c0657a443ba7a2be8ed1d6a8cdd05ada3b173adc7138091db29b4eedf0c3a267360231735fe1d9fa53144ee64f7367bf39'
6
+ metadata.gz: d11b514814d2e1d3d1cbbe3dae17a36f2630863694cb6869accda703b873de201bbaa20b31ff01ff1368d7dae7906950c6d4cbfeb6e51700d05f8408e94f92c9
7
+ data.tar.gz: 16a6c1d7a286940171c8feafbd35f0732906e1adeb4b17737f38f0db393f47684fc3909ddacdcfc81297e90ed75fdfd7e6d18a3cdf8c68ed5f17972947715d73
@@ -3,6 +3,12 @@ Metrics/BlockLength:
3
3
  - 'spec/**/*'
4
4
  - 'rspec-html.gemspec'
5
5
 
6
+ Gemspec/RequiredRubyVersion:
7
+ Enabled: false
8
+
9
+ Layout/LineLength:
10
+ Max: 100
11
+
6
12
  Layout/EmptyLinesAroundAttributeAccessor:
7
13
  Enabled: true
8
14
  Layout/SpaceAroundMethodCallOperator:
@@ -49,3 +55,31 @@ Style/RedundantRegexpEscape:
49
55
  Enabled: true
50
56
  Style/SlicingWithRange:
51
57
  Enabled: true
58
+ Lint/BinaryOperatorWithIdenticalOperands:
59
+ Enabled: true
60
+ Lint/DuplicateRescueException:
61
+ Enabled: true
62
+ Lint/EmptyConditionalBody:
63
+ Enabled: true
64
+ Lint/FloatComparison:
65
+ Enabled: true
66
+ Lint/MissingSuper:
67
+ Enabled: true
68
+ Lint/OutOfRangeRegexpRef:
69
+ Enabled: true
70
+ Lint/SelfAssignment:
71
+ Enabled: true
72
+ Lint/TopLevelReturnWithArgument:
73
+ Enabled: true
74
+ Lint/UnreachableLoop:
75
+ Enabled: true
76
+ Style/ExplicitBlockArgument:
77
+ Enabled: true
78
+ Style/GlobalStdStream:
79
+ Enabled: true
80
+ Style/OptionalBooleanParameter:
81
+ Enabled: true
82
+ Style/SingleArgumentDig:
83
+ Enabled: true
84
+ Style/StringConcatenation:
85
+ Enabled: true
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rspec-html (0.2.1)
4
+ rspec-html (0.2.2)
5
5
  nokogiri (~> 1.10)
6
6
  rspec (~> 3.0)
7
7
 
@@ -42,17 +42,17 @@ GEM
42
42
  diff-lcs (>= 1.2.0, < 2.0)
43
43
  rspec-support (~> 3.9.0)
44
44
  rspec-support (3.9.3)
45
- rubocop (0.88.0)
45
+ rubocop (0.89.1)
46
46
  parallel (~> 1.10)
47
47
  parser (>= 2.7.1.1)
48
48
  rainbow (>= 2.2.2, < 4.0)
49
49
  regexp_parser (>= 1.7)
50
50
  rexml
51
- rubocop-ast (>= 0.1.0, < 1.0)
51
+ rubocop-ast (>= 0.3.0, < 1.0)
52
52
  ruby-progressbar (~> 1.7)
53
53
  unicode-display_width (>= 1.4.0, < 2.0)
54
- rubocop-ast (0.2.0)
55
- parser (>= 2.7.0.1)
54
+ rubocop-ast (0.3.0)
55
+ parser (>= 2.7.1.4)
56
56
  rubocop-rspec (1.42.0)
57
57
  rubocop (>= 0.87.0)
58
58
  ruby-progressbar (1.10.1)
@@ -72,7 +72,7 @@ DEPENDENCIES
72
72
  rake (~> 13.0)
73
73
  rspec-html!
74
74
  rspec-its (~> 1.3)
75
- rubocop (~> 0.88.0)
75
+ rubocop (~> 0.89.1)
76
76
  rubocop-rspec (~> 1.36)
77
77
  strong_versions (~> 0.4.5)
78
78
 
data/README.md CHANGED
@@ -4,18 +4,17 @@ _RSpec::HTML_ provides a simple object interface to HTML responses from [_RSpec
4
4
 
5
5
  ## Installation
6
6
 
7
+ Add the gem to your `Gemfile`:
8
+
7
9
  ```ruby
8
- gem 'rspec-html', '~> 0.2.1'
10
+ gem 'rspec-html', '~> 0.2.2'
9
11
  ```
10
12
 
11
- Bundle
12
- And then execute:
13
-
14
- $ bundle
15
-
16
- Or install it yourself as:
13
+ And rebuild your bundle:
17
14
 
18
- $ gem install rspec-html
15
+ ```bash
16
+ $ bundle install
17
+ ```
19
18
 
20
19
  ## Usage
21
20
 
@@ -26,7 +25,10 @@ Require the gem in your `spec_helper.rb`:
26
25
  require 'rspec/html'
27
26
  ```
28
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
+
29
30
  ### Object Interface
31
+ <a name="object-interface"></a>
30
32
 
31
33
  The top-level object `document` is available in all tests which reflects the current response body (e.g. in request specs).
32
34
 
@@ -35,7 +37,7 @@ If you need to parse _HTML_ manually you can use the provided `parse_html` helpe
35
37
  ```ruby
36
38
  before { parse_html('<html><body>hello</body></html>') }
37
39
  it 'says hello' do
38
- expect(document.body).to include 'hello'
40
+ expect(document.body).to contain_text 'hello'
39
41
  end
40
42
  ```
41
43
 
@@ -43,33 +45,39 @@ To navigate the _DOM_ by a sequence of tag names use chained method calls on the
43
45
 
44
46
  #### Tag Traversal
45
47
  ```ruby
46
- expect(document.body.div.span).to include 'some text'
48
+ expect(document.body.div.span).to contain_text 'some text'
47
49
  ```
48
50
 
49
51
  #### Attribute Matching
50
52
  To select an element matching certain attributes pass a hash to any of the chained methods:
51
53
  ```ruby
52
- expect(document.body.div(id: 'my-div').span(align: 'left')).to include 'some text'
54
+ expect(document.body.div(id: 'my-div').span(align: 'left')).to contain_text 'some text'
53
55
  ```
54
56
 
55
57
  #### Class Matching
56
58
  CSS classes are treated as a special case: to select an element matching a specific class (or array of classes) pass the `class` parameter:
57
59
  ```ruby
58
- expect(document.body.div(id: 'my-div').span(class: 'my-class')).to include 'some text'
59
- expect(document.body.div(id: 'my-div').span(class: ['my-class', 'my-other-class'])).to include 'some text'
60
+ expect(document.body.div(id: 'my-div').span(class: 'my-class')).to contain_text 'some text'
61
+ expect(document.body.div(id: 'my-div').span(class: ['my-class', 'my-other-class'])).to contain_text 'some text'
62
+ ```
63
+
64
+ #### Text Matching
65
+ To select an element that includes a given text string (i.e. excluding mark-up) use the `text` option:
66
+ ```ruby
67
+ expect(document.body.div(text: 'some text').input[:value]).to eql 'some-value'
60
68
  ```
61
69
 
62
70
  #### Attribute Retrieval
63
71
  To select an attribute from an element use the hash-style interface:
64
72
  ```ruby
65
- expect(document.body.div.span[:class]).to include 'my-class'
66
- expect(document.body.div.span['data-content']).to include 'my content'
73
+ expect(document.body.div.span[:class]).to contain_text 'my-class'
74
+ expect(document.body.div.span['data-content']).to contain_text 'my content'
67
75
  ```
68
76
 
69
77
  #### Indexing a Matching Set
70
78
  To select an index from a set of matched elements use the array-style interface (the first matching element is `1`, not `0`):
71
79
  ```ruby
72
- expect(document.body.div[1].span[1][:class]).to include 'my-class'
80
+ expect(document.body.div[1].span[1][:class]).to contain_text 'my-class'
73
81
  ```
74
82
 
75
83
  #### Element Existence
@@ -89,8 +97,8 @@ expect(document.body.div.length).to eql 3
89
97
  #### XPath / CSS Selectors
90
98
  If you need something more specific you can always use the _Nokogiri_ `#xpath` and `#css` methods on any element:
91
99
  ```ruby
92
- expect(document.body.xpath('//span[@class="my-class"]')).to include 'some text'
93
- expect(document.body.css('span.my-class')).to include 'some text'
100
+ expect(document.body.xpath('//span[@class="my-class"]')).to contain_text 'some text'
101
+ expect(document.body.css('span.my-class')).to contain_text 'some text'
94
102
  ```
95
103
 
96
104
  To simply check that an _XPath_ or _CSS_ selector exists use `have_xpath` and `have_css`:
@@ -99,6 +107,34 @@ expect(document.body).to have_css 'html body div.myclass'
99
107
  expect(document.body).to have_xpath '//html/body/div[@class="myclass"]'
100
108
  ```
101
109
 
110
+ ### Custom Matchers
111
+ <a name="matchers"></a>
112
+
113
+ #### contain_text
114
+
115
+ Use the `contain_text` matcher to locate text within a _DOM_ element. All mark-up elements are stripped when using this matcher.
116
+
117
+ ```ruby
118
+ expect(document.body.form).to contain_text 'Please enter your password'
119
+ ```
120
+
121
+ #### contain_tag
122
+
123
+ Use the `contain_tag` matcher to locate _DOM_ elements within any given element. This matcher accepts two arguments:
124
+
125
+ * The tag name of the element you want to match (e.g. `:div`);
126
+ * _(Optional)_ A hash of options. All options supported by the [object interface](#object-interface) can be used here.
127
+
128
+ Without options:
129
+ ```ruby
130
+ expect(document.div(class: 'my-class')).to contain_tag :span
131
+ ```
132
+
133
+ With options:
134
+ ```
135
+ expect(document.form(class: 'my-form')).to contain_tag :input, name: 'email', class: 'email-input'
136
+ ```
137
+
102
138
  ## Contributing
103
139
 
104
140
  Feel free to make a pull request.
@@ -9,7 +9,13 @@ module RSpec
9
9
  # Module extension for RSpec::SharedContext
10
10
  module HTML
11
11
  def document
12
- @document || RSpecHTML::Element.new(Nokogiri::HTML.parse(response.body), :document)
12
+ return @document if @document
13
+
14
+ if !defined?(response) || response.nil?
15
+ raise RSpecHTML::NoResponseError, 'No `response` object found. Make a request first.'
16
+ end
17
+
18
+ RSpecHTML::Element.new(Nokogiri::HTML.parse(response.body), :document)
13
19
  end
14
20
 
15
21
  def parse_html(content)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RSpec
4
4
  module HTML
5
- VERSION = '0.2.1'
5
+ VERSION = '0.2.2'
6
6
  end
7
7
  end
@@ -3,14 +3,18 @@
3
3
  require 'nokogiri'
4
4
  require 'pathname'
5
5
 
6
- require 'rspec_html/nameable'
7
6
  require 'rspec_html/searchable'
8
7
  require 'rspec_html/element'
9
8
  require 'rspec_html/tags'
9
+ require 'rspec_html/matchers'
10
10
 
11
11
  # Support module for rspec/html
12
12
  module RSpecHTML
13
+ class Error < StandardError; end
14
+ class NoResponseError < Error; end
13
15
  def self.root
14
16
  Pathname.new(__dir__).parent
15
17
  end
16
18
  end
19
+
20
+ RSpec.configure { |config| config.include RSpecHTML::Matchers }
@@ -4,12 +4,35 @@ module RSpecHTML
4
4
  # HTML DOM element abstraction
5
5
  class Element
6
6
  include Searchable
7
- include Nameable
7
+
8
+ attr_reader :name, :element
8
9
 
9
10
  def initialize(element, name, siblings: [])
10
11
  @name = name
11
12
  @element = element
12
13
  @siblings = siblings
13
14
  end
15
+
16
+ def inspect
17
+ "<#{self.class}::#{name.to_s.capitalize}>"
18
+ end
19
+
20
+ def to_s
21
+ @element.to_s
22
+ end
23
+
24
+ def self.reconstituted(tag, options = {})
25
+ name = tag.to_s.downcase
26
+ mapped_attributes = options.reject { |key| key.to_sym == :text }.map do |key, value|
27
+ next %(#{key}="#{value}") unless key.to_sym == :class && value.is_a?(Array)
28
+
29
+ %(#{key}="#{value.join(' ')}")
30
+ end
31
+
32
+ attributes = mapped_attributes.empty? ? nil : " #{mapped_attributes.join(' ')}"
33
+ return "<#{name}#{attributes} />" unless options.key?(:text)
34
+
35
+ "<#{name}#{attributes}>#{options[:text]}</#{name}>"
36
+ end
14
37
  end
15
38
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec_html/matchers/base'
4
+ require 'rspec_html/matchers/contain_text'
5
+ require 'rspec_html/matchers/contain_tag'
6
+
7
+ module RSpecHTML
8
+ # Provides matchers for identifying elements and text within a DOM element.
9
+ module Matchers
10
+ extend RSpec::Matchers::DSL
11
+ extend RSpec::Matchers::DSL::Macros
12
+
13
+ def self.define_matcher(name, class_)
14
+ matcher name do |expected, options|
15
+ rspec_html_matcher = class_.new(expected, options)
16
+ match do |actual|
17
+ @actual = rspec_html_matcher.actual
18
+ rspec_html_matcher.match(actual)
19
+ end
20
+ description { rspec_html_matcher.description }
21
+ failure_message { |actual| rspec_html_matcher.failure_message(actual) }
22
+ diffable if class_.diffable?
23
+ end
24
+ end
25
+
26
+ define_matcher(:contain_text, ContainText)
27
+ define_matcher(:contain_tag, ContainTag)
28
+ end
29
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecHTML
4
+ module Matchers
5
+ # Mix-in class to provide a uniform interface and message templating for all matchers.
6
+ module Base
7
+ def self.included(base)
8
+ base.class_eval do
9
+ class << self
10
+ def diffable
11
+ @diffable = true
12
+ end
13
+
14
+ def diffable?
15
+ @diffable
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ attr_reader :actual
22
+
23
+ def initialize(expected, options = {})
24
+ @expected = expected
25
+ @options = options
26
+ end
27
+
28
+ def description
29
+ template(:description, @expected)
30
+ end
31
+
32
+ def failure_message(actual)
33
+ template(:failure, @expected, actual)
34
+ end
35
+
36
+ private
37
+
38
+ def template(type, expected, actual = nil)
39
+ ERB.new(template_path(type).read).result(binding)
40
+ end
41
+
42
+ def template_path(type)
43
+ RSpecHTML.root.join('templates', type.to_s, "#{filename}.erb")
44
+ end
45
+
46
+ def filename
47
+ _, _, name = self.class.name.rpartition('::')
48
+ (name[0] + name[1..].gsub(/(.)([A-Z])/, '\1_\2')).downcase
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecHTML
4
+ module Matchers
5
+ # Matches elements within a given DOM element.
6
+ class ContainTag
7
+ include Base
8
+
9
+ def match(actual)
10
+ @actual = actual.to_s
11
+ actual.public_send(@expected, @options).present?
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecHTML
4
+ module Matchers
5
+ # Matches text within a given DOM element.
6
+ class ContainText
7
+ include Base
8
+
9
+ diffable
10
+
11
+ def match(actual)
12
+ @actual = actual&.text&.strip
13
+ (actual&.text || '').include?(@expected.to_s)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -4,7 +4,7 @@ module RSpecHTML
4
4
  # Mixin module providing methods for searching text content of HTML entities
5
5
  module Searchable
6
6
  def include?(val)
7
- @element.text.include?(val)
7
+ text.include?(val)
8
8
  end
9
9
 
10
10
  def css(*args)
@@ -30,14 +30,6 @@ module RSpecHTML
30
30
  end
31
31
  # rubocop:enable Naming/PredicateName
32
32
 
33
- def to_s
34
- @element&.text&.strip
35
- end
36
-
37
- def inspect
38
- %("#{@element}")
39
- end
40
-
41
33
  def [](val)
42
34
  return index(val) if val.is_a?(Integer)
43
35
  return range(val) if val.is_a?(Range)
@@ -45,6 +37,10 @@ module RSpecHTML
45
37
  @element&.attr(val.to_s)
46
38
  end
47
39
 
40
+ def text
41
+ @element&.text || ''
42
+ end
43
+
48
44
  def size
49
45
  return @element.size if @element.respond_to?(:size)
50
46
 
@@ -94,8 +90,9 @@ module RSpecHTML
94
90
  def where_conditions(query)
95
91
  query.compact.map do |key, value|
96
92
  next if value.nil?
93
+ next %(@#{key}="#{value}") unless key == :text
97
94
 
98
- %(@#{key}="#{value}")
95
+ %[contains(text(),"#{value}")]
99
96
  end.join ' and '
100
97
  end
101
98
 
@@ -110,8 +107,10 @@ module RSpecHTML
110
107
  @element&.css(tag.to_s)
111
108
  end
112
109
 
110
+ # rubocop:disable Lint/MissingSuper
113
111
  def respond_to_missing?(method_name, *_)
114
112
  Tags.include?(method_name)
115
113
  end
114
+ # rubocop:enable Lint/MissingSuper
116
115
  end
117
116
  end
@@ -37,7 +37,7 @@ Gem::Specification.new do |spec|
37
37
  spec.add_development_dependency 'i18n', '~> 1.7'
38
38
  spec.add_development_dependency 'rake', '~> 13.0'
39
39
  spec.add_development_dependency 'rspec-its', '~> 1.3'
40
- spec.add_development_dependency 'rubocop', '~> 0.88.0'
40
+ spec.add_development_dependency 'rubocop', '~> 0.89.1'
41
41
  spec.add_development_dependency 'rubocop-rspec', '~> 1.36'
42
42
  spec.add_development_dependency 'strong_versions', '~> 0.4.5'
43
43
  end
@@ -0,0 +1 @@
1
+ contain tag <%= RSpecHTML::Element.reconstituted(expected, @options) %>
@@ -0,0 +1 @@
1
+ contain text <%= expected.inspect %>
@@ -0,0 +1,5 @@
1
+ <% if actual.element.nil? %>
2
+ Expected <%= actual.name %> to contain <%= RSpecHTML::Element.reconstituted(expected, @options) %> but the element did not exist.
3
+ <% else %>
4
+ Expected <<%= actual.name %>> to contain <%= RSpecHTML::Element.reconstituted(expected, @options) %> but it did not.
5
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <% if actual.element.nil? %>
2
+ Expected <%= actual.name %> to contain <%= expected.inspect %> but the element did not exist.
3
+ <% else %>
4
+ Expected text in <<%= actual.name %>> <%= (actual.text&.strip || '').inspect %> to contain <%= expected.inspect %> but it did not.
5
+ <% 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.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Farrell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-30 00:00:00.000000000 Z
11
+ date: 2020-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: 0.88.0
131
+ version: 0.89.1
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: 0.88.0
138
+ version: 0.89.1
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: rubocop-rspec
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -188,10 +188,17 @@ files:
188
188
  - lib/rspec/html/version.rb
189
189
  - lib/rspec_html.rb
190
190
  - lib/rspec_html/element.rb
191
- - lib/rspec_html/nameable.rb
191
+ - lib/rspec_html/matchers.rb
192
+ - lib/rspec_html/matchers/base.rb
193
+ - lib/rspec_html/matchers/contain_tag.rb
194
+ - lib/rspec_html/matchers/contain_text.rb
192
195
  - lib/rspec_html/searchable.rb
193
196
  - lib/rspec_html/tags.rb
194
197
  - rspec-html.gemspec
198
+ - templates/description/contain_tag.erb
199
+ - templates/description/contain_text.erb
200
+ - templates/failure/contain_tag.erb
201
+ - templates/failure/contain_text.erb
195
202
  homepage: https://github.com/bobf/rspec-html
196
203
  licenses:
197
204
  - MIT
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpecHTML
4
- # Mixin module providing methods allowing an entity to specify its name
5
- module Nameable
6
- attr_reader :name
7
- end
8
- end