rspec-html 0.2.0 → 0.2.5

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: 0d6b33d4bbde8f992f361359fed2e7ca1fa5aea60031bfeb72fcc719fe61bb4a
4
- data.tar.gz: c8bbd8cae270839cba34034bdbd78a8d42085f01fe29891ef01eed6299805b1b
3
+ metadata.gz: a5241cc27dba3e36c05cd7595280908be306d659d6611e02b70d58984a2f8900
4
+ data.tar.gz: 18e5487083e27fe812565b15c0411026ba622cb82c0bbbeb36787167928dab48
5
5
  SHA512:
6
- metadata.gz: 810cc82b373e10c3ab4f137d7dc4648972f81654f07478b937b7eb38d43832273c5b0203ad8ebcc7fb6927efea78ac1d048663d9c4a5f95d8afa8459f8bdae97
7
- data.tar.gz: 0a9e83ccf82b023d0f4809935c354ef3d31b5bb6cc95161395bd8b4246ae839fcc71abfc933563d913eea8fcd9ee0935992c535a5455ef230a768e91ca48981d
6
+ metadata.gz: 39065b13e1a1d959fcebecc3004b20e2940b560c06a9789d39b1ffa99c249f4cb0062c3888ed6af69e44d2523ff8ed4ea66989c4e35e3e924764610c51900414
7
+ data.tar.gz: 4c275980c39e8f9506a03cbb8ceca6bfdf16304aaa37f4eba318ae0e085106cfc5502f34148d4d6dfe86d7a000d145e39015ab20471ee812447f16d7b09d60b3
@@ -3,6 +3,16 @@ 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
+
12
+ # Compatibility with older (< 2.6) Rubies.
13
+ Style/SlicingWithRange:
14
+ Enabled: false
15
+
6
16
  Layout/EmptyLinesAroundAttributeAccessor:
7
17
  Enabled: true
8
18
  Layout/SpaceAroundMethodCallOperator:
@@ -47,5 +57,31 @@ Style/RedundantRegexpCharacterClass:
47
57
  Enabled: true
48
58
  Style/RedundantRegexpEscape:
49
59
  Enabled: true
50
- Style/SlicingWithRange:
60
+ Lint/BinaryOperatorWithIdenticalOperands:
61
+ Enabled: true
62
+ Lint/DuplicateRescueException:
63
+ Enabled: true
64
+ Lint/EmptyConditionalBody:
65
+ Enabled: true
66
+ Lint/FloatComparison:
67
+ Enabled: true
68
+ Lint/MissingSuper:
69
+ Enabled: true
70
+ Lint/OutOfRangeRegexpRef:
71
+ Enabled: true
72
+ Lint/SelfAssignment:
73
+ Enabled: true
74
+ Lint/TopLevelReturnWithArgument:
75
+ Enabled: true
76
+ Lint/UnreachableLoop:
77
+ Enabled: true
78
+ Style/ExplicitBlockArgument:
79
+ Enabled: true
80
+ Style/GlobalStdStream:
81
+ Enabled: true
82
+ Style/OptionalBooleanParameter:
83
+ Enabled: true
84
+ Style/SingleArgumentDig:
85
+ Enabled: true
86
+ Style/StringConcatenation:
51
87
  Enabled: true
@@ -1 +1 @@
1
- 2.6.5
1
+ 2.7.1
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rspec-html (0.2.0)
4
+ rspec-html (0.2.5)
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,9 +72,9 @@ 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
 
79
79
  BUNDLED WITH
80
- 2.0.2
80
+ 2.1.4
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.0'
10
+ gem 'rspec-html', '~> 0.2.5'
9
11
  ```
10
12
 
11
- Bundle
12
- And then execute:
13
-
14
- $ bundle
13
+ And rebuild your bundle:
15
14
 
16
- Or install it yourself as:
17
-
18
- $ gem install rspec-html
15
+ ```bash
16
+ $ bundle install
17
+ ```
19
18
 
20
19
  ## Usage
21
20
 
@@ -26,34 +25,61 @@ Require the gem in your `spec_helper.rb`:
26
25
  require 'rspec/html'
27
26
  ```
28
27
 
29
- In request specs, access the HTML document through the provided `document` object.
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).
30
29
 
31
30
  ### Object Interface
31
+ <a name="object-interface"></a>
32
+
33
+ The top-level object `document` is available in all tests which reflects the current response body (e.g. in request specs).
34
+
35
+ If you need to parse _HTML_ manually you can use the provided `parse_html` helper and then access `document` as normal:
36
+
37
+ ```ruby
38
+ before { parse_html('<html><body>hello</body></html>') }
39
+ it 'says hello' do
40
+ expect(document.body).to contain_text 'hello'
41
+ end
42
+ ```
32
43
 
33
- To navigating the _DOM_ by a sequence of tag names use chained method calls on the `document` object:
44
+ To navigate the _DOM_ by a sequence of tag names use chained method calls on the `document` object:
34
45
 
35
46
  #### Tag Traversal
36
47
  ```ruby
37
- expect(document.body.div.span).to include 'some text'
48
+ expect(document.body.div.span).to contain_text 'some text'
38
49
  ```
39
50
 
40
51
  #### Attribute Matching
41
52
  To select an element matching certain attributes pass a hash to any of the chained methods:
42
53
  ```ruby
43
- expect(document.body.div(id: 'my-div').span(class: 'my-class')).to eql 'some text'
54
+ expect(document.body.div(id: 'my-div').span(align: 'left')).to contain_text 'some text'
55
+ ```
56
+
57
+ #### Class Matching
58
+ _CSS_ classes are treated as a special case: to select an element matching a set of classes pass the `class` parameter:
59
+ ```ruby
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
+ Classes can be provided in any order, i.e. `'my-class my-other-class'` is equivalent to `'my-other-class my-class'`.
65
+
66
+ #### Text Matching
67
+ To select an element that includes a given text string (i.e. excluding mark-up) use the `text` option:
68
+ ```ruby
69
+ expect(document.body.div(text: 'some text').input[:value]).to eql 'some-value'
44
70
  ```
45
71
 
46
72
  #### Attribute Retrieval
47
73
  To select an attribute from an element use the hash-style interface:
48
74
  ```ruby
49
- expect(document.body.div.span[:class]).to eql 'my-class'
50
- expect(document.body.div.span['data-content']).to eql 'my content'
75
+ expect(document.body.div.span[:class]).to contain_text 'my-class'
76
+ expect(document.body.div.span['data-content']).to contain_text 'my content'
51
77
  ```
52
78
 
53
79
  #### Indexing a Matching Set
54
80
  To select an index from a set of matched elements use the array-style interface (the first matching element is `1`, not `0`):
55
81
  ```ruby
56
- expect(document.body.div[1].span[1][:class]).to eql 'my-class'
82
+ expect(document.body.div[1].span[1][:class]).to contain_text 'my-class'
57
83
  ```
58
84
 
59
85
  #### Element Existence
@@ -73,8 +99,8 @@ expect(document.body.div.length).to eql 3
73
99
  #### XPath / CSS Selectors
74
100
  If you need something more specific you can always use the _Nokogiri_ `#xpath` and `#css` methods on any element:
75
101
  ```ruby
76
- expect(document.body.xpath('//span[@class="my-class"]')).to include 'some text'
77
- expect(document.body.css('span.my-class')).to include 'some text'
102
+ expect(document.body.xpath('//span[@class="my-class"]')).to contain_text 'some text'
103
+ expect(document.body.css('span.my-class')).to contain_text 'some text'
78
104
  ```
79
105
 
80
106
  To simply check that an _XPath_ or _CSS_ selector exists use `have_xpath` and `have_css`:
@@ -83,6 +109,34 @@ expect(document.body).to have_css 'html body div.myclass'
83
109
  expect(document.body).to have_xpath '//html/body/div[@class="myclass"]'
84
110
  ```
85
111
 
112
+ ### Custom Matchers
113
+ <a name="matchers"></a>
114
+
115
+ #### contain_text
116
+
117
+ Use the `contain_text` matcher to locate text within a _DOM_ element. All mark-up elements are stripped when using this matcher.
118
+
119
+ ```ruby
120
+ expect(document.body.form).to contain_text 'Please enter your password'
121
+ ```
122
+
123
+ #### contain_tag
124
+
125
+ Use the `contain_tag` matcher to locate _DOM_ elements within any given element. This matcher accepts two arguments:
126
+
127
+ * The tag name of the element you want to match (e.g. `:div`);
128
+ * _(Optional)_ A hash of options. All options supported by the [object interface](#object-interface) can be used here.
129
+
130
+ Without options:
131
+ ```ruby
132
+ expect(document.div(class: 'my-class')).to contain_tag :span
133
+ ```
134
+
135
+ With options:
136
+ ```ruby
137
+ expect(document.form(class: 'my-form')).to contain_tag :input, name: 'email', class: 'email-input'
138
+ ```
139
+
86
140
  ## Contributing
87
141
 
88
142
  Feel free to make a pull request.
@@ -9,8 +9,18 @@ module RSpec
9
9
  # Module extension for RSpec::SharedContext
10
10
  module HTML
11
11
  def 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
+
12
18
  RSpecHTML::Element.new(Nokogiri::HTML.parse(response.body), :document)
13
19
  end
20
+
21
+ def parse_html(content)
22
+ @document = RSpecHTML::Element.new(Nokogiri::HTML.parse(content), :document)
23
+ end
14
24
  end
15
25
  end
16
26
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RSpec
4
4
  module HTML
5
- VERSION = '0.2.0'
5
+ VERSION = '0.2.5'
6
6
  end
7
7
  end
@@ -1,16 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'nokogiri'
4
+
4
5
  require 'pathname'
6
+ require 'forwardable'
5
7
 
6
- require 'rspec_html/nameable'
7
- require 'rspec_html/searchable'
8
- require 'rspec_html/element'
9
8
  require 'rspec_html/tags'
9
+ require 'rspec_html/element'
10
+ require 'rspec_html/search'
11
+ require 'rspec_html/reconstituted_element'
12
+ require 'rspec_html/matchers'
10
13
 
11
14
  # Support module for rspec/html
12
15
  module RSpecHTML
16
+ class Error < StandardError; end
17
+ class NoResponseError < Error; end
13
18
  def self.root
14
19
  Pathname.new(__dir__).parent
15
20
  end
16
21
  end
22
+
23
+ RSpec.configure { |config| config.include RSpecHTML::Matchers }
@@ -3,13 +3,51 @@
3
3
  module RSpecHTML
4
4
  # HTML DOM element abstraction
5
5
  class Element
6
- include Searchable
7
- include Nameable
6
+ attr_reader :name, :element
8
7
 
9
- def initialize(element, name, siblings: [])
8
+ extend Forwardable
9
+
10
+ def_delegators :@search,
11
+ :has_css?, :has_xpath?, :include?, :text, :truncated_text, :size, :length, :[]
12
+
13
+ def initialize(element, name, options: {}, siblings: [])
10
14
  @name = name
11
15
  @element = element
16
+ @options = options
12
17
  @siblings = siblings
18
+ @search = Search.new(@element, @siblings)
19
+ end
20
+
21
+ def present?
22
+ return true if name == :document
23
+
24
+ @search.present?
25
+ end
26
+ alias exist? present?
27
+
28
+ def inspect
29
+ "<#{self.class}::#{name.to_s.capitalize}>"
30
+ end
31
+
32
+ def to_s
33
+ @element.to_s
34
+ end
35
+
36
+ Tags.each do |tag|
37
+ define_method tag.downcase do |*args|
38
+ options = args.first
39
+ return @search.new_from_find(tag.downcase, options) if options.nil?
40
+
41
+ @search.new_from_where(tag.downcase, options)
42
+ end
43
+ end
44
+
45
+ def reconstituted
46
+ self.class.reconstituted(name, @options)
47
+ end
48
+
49
+ def self.reconstituted(tag, options = {})
50
+ ReconstitutedElement.new(tag, options).to_s
13
51
  end
14
52
  end
15
53
  end
@@ -0,0 +1,33 @@
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
+ # rubocop:disable Metrics/MethodLength
14
+ def self.define_matcher(name, class_)
15
+ matcher name do |expected, options|
16
+ rspec_html_matcher = class_.new(expected, options || {})
17
+ match do |actual|
18
+ rspec_html_matcher
19
+ .save_actual(actual)
20
+ .match(actual)
21
+ .tap { @actual = rspec_html_matcher.rspec_actual }
22
+ end
23
+ description { rspec_html_matcher.description }
24
+ failure_message { rspec_html_matcher.failure_message }
25
+ diffable if class_.diffable?
26
+ end
27
+ end
28
+ # rubocop:enable Metrics/MethodLength
29
+
30
+ define_matcher(:contain_text, ContainText)
31
+ define_matcher(:contain_tag, ContainTag)
32
+ end
33
+ end
@@ -0,0 +1,61 @@
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 :rspec_actual
22
+
23
+ def initialize(expected, options)
24
+ @expected = expected
25
+ @options = options
26
+ end
27
+
28
+ def description
29
+ template(:description, @options, @expected)
30
+ end
31
+
32
+ def failure_message
33
+ template(:failure, @options, @expected, @actual)
34
+ end
35
+
36
+ def save_actual(actual)
37
+ @actual = actual
38
+ self
39
+ end
40
+
41
+ def reconstituted(element, options)
42
+ RSpecHTML::Element.reconstituted(element, options)
43
+ end
44
+
45
+ private
46
+
47
+ def template(type, options, expected, actual = nil)
48
+ ERB.new(template_path(type).read).result(binding)
49
+ end
50
+
51
+ def template_path(type)
52
+ RSpecHTML.root.join('templates', type.to_s, "#{filename}.erb")
53
+ end
54
+
55
+ def filename
56
+ _, _, name = self.class.name.rpartition('::')
57
+ (name[0] + name[1..-1].gsub(/(.)([A-Z])/, '\1_\2')).downcase
58
+ end
59
+ end
60
+ end
61
+ 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
+ @rspec_actual = actual&.text
13
+ (actual&.text || '').include?(@expected.to_s)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecHTML
4
+ # Reconstructs an HTML representation of an element from provided parameters.
5
+ class ReconstitutedElement
6
+ def initialize(tag, options)
7
+ @tag = tag
8
+ @options = options
9
+ end
10
+
11
+ def to_s
12
+ name = @tag.to_s.downcase
13
+ return '#document' if name == 'document'
14
+ return name if name == 'document'
15
+ return "<#{name}#{formatted_attributes} />" unless @options&.key?(:text)
16
+
17
+ "<#{name}#{formatted_attributes}>#{@options[:text]}</#{name}>"
18
+ end
19
+
20
+ private
21
+
22
+ def mapped_attributes
23
+ return [] if @options.nil?
24
+
25
+ @options.reject { |key| key.to_sym == :text }.map do |key, value|
26
+ next %(#{key}="#{value}") unless key.to_sym == :class && value.is_a?(Array)
27
+
28
+ %(#{key}="#{value.join(' ')}")
29
+ end
30
+ end
31
+
32
+ def formatted_attributes
33
+ mapped_attributes.empty? ? nil : " #{mapped_attributes.join(' ')}"
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecHTML
4
+ # Provides element/attribute/text searching for HTML entities
5
+ class Search
6
+ def initialize(element, siblings)
7
+ @element = element
8
+ @siblings = siblings
9
+ end
10
+
11
+ def include?(val)
12
+ text.include?(val)
13
+ end
14
+
15
+ def css(*args)
16
+ self.class.new(@element&.css(*args), :css)
17
+ end
18
+
19
+ def xpath(*args)
20
+ self.class.new(@element&.xpath(*args), :xpath)
21
+ end
22
+
23
+ def present?
24
+ !@element.nil?
25
+ end
26
+ alias exist? present?
27
+
28
+ # rubocop:disable Naming/PredicateName
29
+ def has_css?(*args)
30
+ !@element&.css(*args)&.empty?
31
+ end
32
+
33
+ def has_xpath?(*args)
34
+ !@element&.xpath(*args)&.empty?
35
+ end
36
+ # rubocop:enable Naming/PredicateName
37
+
38
+ def [](val)
39
+ return index(val) if val.is_a?(Integer)
40
+ return range(val) if val.is_a?(Range)
41
+
42
+ @element&.attr(val.to_s)
43
+ end
44
+
45
+ def text
46
+ @element&.text&.gsub(/\s+/, ' ')&.strip || ''
47
+ end
48
+
49
+ def truncated_text
50
+ max = RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length
51
+ return text if text.size <= max
52
+
53
+ "#{text[0..max]}...#{text[-max..-1]}"
54
+ end
55
+
56
+ def size
57
+ return @element.size if @element.respond_to?(:size)
58
+
59
+ @siblings.size
60
+ end
61
+ alias length size
62
+
63
+ def new_from_find(tag, options)
64
+ Element.new(
65
+ find(tag),
66
+ tag,
67
+ options: options,
68
+ siblings: find(tag, all: true)
69
+ )
70
+ end
71
+
72
+ def new_from_where(tag, options)
73
+ Element.new(
74
+ where(tag, options),
75
+ tag,
76
+ options: options,
77
+ siblings: where(tag, options, all: true)
78
+ )
79
+ end
80
+
81
+ private
82
+
83
+ def index(val)
84
+ zero_index_error if val.zero?
85
+ self.class.new(@siblings[val - 1], @element.name)
86
+ end
87
+
88
+ def range(val)
89
+ zero_index_error if val.first.zero?
90
+ self.class.new(@siblings[(val.first - 1)..(val.last - 1)], :range)
91
+ end
92
+
93
+ def zero_index_error
94
+ raise ArgumentError, 'Index for matched sets starts at 1, not 0.'
95
+ end
96
+
97
+ def where(tag, query, all: false)
98
+ matched = if query[:class]
99
+ where_class(tag, query[:class]) & where_xpath(tag, query.merge(class: nil))
100
+ else
101
+ where_xpath(tag, query)
102
+ end
103
+ return matched&.first unless all
104
+
105
+ matched
106
+ end
107
+
108
+ def where_xpath(tag, query)
109
+ conditions = "[#{where_conditions(query)}]" unless query.compact.empty?
110
+ @element&.xpath("//#{tag}#{conditions}")
111
+ end
112
+
113
+ def where_conditions(query)
114
+ query.compact.map do |key, value|
115
+ next if value.nil?
116
+ next %(@#{key}="#{value}") unless key == :text
117
+
118
+ %[contains(text(),"#{value}")]
119
+ end.join ' and '
120
+ end
121
+
122
+ def where_class(tag, class_or_classes)
123
+ classes = class_or_classes.is_a?(Array) ? class_or_classes : class_or_classes.to_s.split
124
+ selector = classes.map(&:to_s).join('.')
125
+ @element&.css("#{tag}.#{selector}")
126
+ end
127
+
128
+ def find(tag, all: false)
129
+ return @element&.css(tag.to_s)&.first unless all
130
+
131
+ @element&.css(tag.to_s)
132
+ end
133
+ end
134
+ end
@@ -7,6 +7,10 @@ module RSpecHTML
7
7
  tags.include?(val.to_s.upcase)
8
8
  end
9
9
 
10
+ def self.each(&block)
11
+ tags.each { |tag| block.call(tag) }
12
+ end
13
+
10
14
  # rubocop:disable Metrics/MethodLength
11
15
  def self.tags
12
16
  %w[
@@ -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 <%= reconstituted(actual, @options) %> to contain <%= reconstituted(expected, @options) %> but the element did not exist.
3
+ <% else %>
4
+ Expected <%= reconstituted(actual, @options) %> to contain <%= reconstituted(expected, @options) %> but it did not.
5
+ <% end %>
@@ -0,0 +1,5 @@
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 %>
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.0
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Farrell
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-28 00:00:00.000000000 Z
11
+ date: 2020-09-01 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,18 @@ 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
192
- - lib/rspec_html/searchable.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
195
+ - lib/rspec_html/reconstituted_element.rb
196
+ - lib/rspec_html/search.rb
193
197
  - lib/rspec_html/tags.rb
194
198
  - rspec-html.gemspec
199
+ - templates/description/contain_tag.erb
200
+ - templates/description/contain_text.erb
201
+ - templates/failure/contain_tag.erb
202
+ - templates/failure/contain_text.erb
195
203
  homepage: https://github.com/bobf/rspec-html
196
204
  licenses:
197
205
  - MIT
@@ -199,7 +207,7 @@ metadata:
199
207
  homepage_uri: https://github.com/bobf/rspec-html
200
208
  source_code_uri: https://github.com/bobf/rspec-html
201
209
  changelog_uri: https://github.com/bobf/rspec-html/blob/master/LICENSE
202
- post_install_message:
210
+ post_install_message:
203
211
  rdoc_options: []
204
212
  require_paths:
205
213
  - lib
@@ -214,8 +222,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
214
222
  - !ruby/object:Gem::Version
215
223
  version: '0'
216
224
  requirements: []
217
- rubygems_version: 3.0.3
218
- signing_key:
225
+ rubygems_version: 3.1.2
226
+ signing_key:
219
227
  specification_version: 4
220
228
  summary: RSpec HTML
221
229
  test_files: []
@@ -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
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpecHTML
4
- # Mixin module providing methods for searching text content of HTML entities
5
- module Searchable
6
- def include?(val)
7
- @element.text.include?(val)
8
- end
9
-
10
- def css(*args)
11
- self.class.new(@element&.css(*args), :css)
12
- end
13
-
14
- def xpath(*args)
15
- self.class.new(@element&.xpath(*args), :xpath)
16
- end
17
-
18
- def present?
19
- !@element.nil?
20
- end
21
- alias exist? present?
22
-
23
- # rubocop:disable Naming/PredicateName
24
- def has_css?(*args)
25
- !@element&.css(*args)&.empty?
26
- end
27
-
28
- def has_xpath?(*args)
29
- !@element&.xpath(*args)&.empty?
30
- end
31
- # rubocop:enable Naming/PredicateName
32
-
33
- def to_s
34
- @element&.text&.strip
35
- end
36
-
37
- def inspect
38
- %("#{@element}")
39
- end
40
-
41
- def [](val)
42
- return index(val) if val.is_a?(Integer)
43
- return range(val) if val.is_a?(Range)
44
-
45
- @element&.attr(val.to_s)
46
- end
47
-
48
- def size
49
- return @element.size if @element.respond_to?(:size)
50
-
51
- @siblings.size
52
- end
53
- alias length size
54
-
55
- private
56
-
57
- def method_missing(tag, *args)
58
- return super unless Tags.include?(tag)
59
- return self.class.new(find(tag), tag, siblings: find(tag, all: true)) if args.empty?
60
-
61
- self.class.new(where(tag, args.first), tag, siblings: where(tag, args.first, all: true))
62
- end
63
-
64
- def index(val)
65
- zero_index_error if val.zero?
66
- self.class.new(@siblings[val - 1], name)
67
- end
68
-
69
- def range(val)
70
- zero_index_error if val.first.zero?
71
- self.class.new(@siblings[(val.first - 1)..(val.last - 1)], :range)
72
- end
73
-
74
- def zero_index_error
75
- raise ArgumentError, 'Index for matched sets starts at 1, not 0.'
76
- end
77
-
78
- def where(tag, query, all: false)
79
- matched = @element&.xpath("//#{tag}[#{where_conditions(query)}]")
80
- return matched&.first unless all
81
-
82
- matched
83
- end
84
-
85
- def where_conditions(query)
86
- query.map do |key, value|
87
- %(@#{key}="#{value}")
88
- end.join ' and '
89
- end
90
-
91
- def find(tag, all: false)
92
- return @element&.css(tag.to_s)&.first unless all
93
-
94
- @element&.css(tag.to_s)
95
- end
96
-
97
- def respond_to_missing?(method_name, *_)
98
- Tags.include?(method_name)
99
- end
100
- end
101
- end