rspec-html 0.2.0 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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