rspec-html 0.1.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: 8b6fbffdbd1506e148ae25b7900d508e3f50048cb3c2d27ed25ef2fd5a93d0ab
4
- data.tar.gz: 8868853f56675ca2d4487b155a8da72c668ed6f80ebf61885f718a653d3b58a5
3
+ metadata.gz: ffb46cc469517211466583fd590e8d77a39368eba859513771a43438508c3985
4
+ data.tar.gz: db46d8cc7a56d00e91517d8df9465d7c5915f82d4fc35b9314ba02f043921c1b
5
5
  SHA512:
6
- metadata.gz: 63b9fa7b4043a45485bb6423c26ab1812e8ab56283285ad6f3e92402fe4f47c5a20e5f62b1102816f5a24a25c04965310464e69eacfc0619e7500cf716bde298
7
- data.tar.gz: 1065cfee90afa2c3372c524abf7e6a7383c56f6e349d35f1f9bbe06b6e803665459c41f7360d9066baeccd5375d4a666cddd64f4921be85dd5b25faa668453dd
6
+ metadata.gz: d11b514814d2e1d3d1cbbe3dae17a36f2630863694cb6869accda703b873de201bbaa20b31ff01ff1368d7dae7906950c6d4cbfeb6e51700d05f8408e94f92c9
7
+ data.tar.gz: 16a6c1d7a286940171c8feafbd35f0732906e1adeb4b17737f38f0db393f47684fc3909ddacdcfc81297e90ed75fdfd7e6d18a3cdf8c68ed5f17972947715d73
@@ -0,0 +1,85 @@
1
+ Metrics/BlockLength:
2
+ Exclude:
3
+ - 'spec/**/*'
4
+ - 'rspec-html.gemspec'
5
+
6
+ Gemspec/RequiredRubyVersion:
7
+ Enabled: false
8
+
9
+ Layout/LineLength:
10
+ Max: 100
11
+
12
+ Layout/EmptyLinesAroundAttributeAccessor:
13
+ Enabled: true
14
+ Layout/SpaceAroundMethodCallOperator:
15
+ Enabled: true
16
+ Lint/DeprecatedOpenSSLConstant:
17
+ Enabled: true
18
+ Lint/DuplicateElsifCondition:
19
+ Enabled: true
20
+ Lint/MixedRegexpCaptureTypes:
21
+ Enabled: true
22
+ Lint/RaiseException:
23
+ Enabled: true
24
+ Lint/StructNewOverride:
25
+ Enabled: true
26
+ Style/AccessorGrouping:
27
+ Enabled: true
28
+ Style/ArrayCoercion:
29
+ Enabled: true
30
+ Style/BisectedAttrAccessor:
31
+ Enabled: true
32
+ Style/CaseLikeIf:
33
+ Enabled: true
34
+ Style/ExponentialNotation:
35
+ Enabled: true
36
+ Style/HashAsLastArrayItem:
37
+ Enabled: true
38
+ Style/HashEachMethods:
39
+ Enabled: true
40
+ Style/HashLikeCase:
41
+ Enabled: true
42
+ Style/HashTransformKeys:
43
+ Enabled: true
44
+ Style/HashTransformValues:
45
+ Enabled: true
46
+ Style/RedundantAssignment:
47
+ Enabled: true
48
+ Style/RedundantFetchBlock:
49
+ Enabled: true
50
+ Style/RedundantFileExtensionInRequire:
51
+ Enabled: true
52
+ Style/RedundantRegexpCharacterClass:
53
+ Enabled: true
54
+ Style/RedundantRegexpEscape:
55
+ Enabled: true
56
+ Style/SlicingWithRange:
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,76 +1,80 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rspec-html (0.1.0)
4
+ rspec-html (0.2.2)
5
5
  nokogiri (~> 1.10)
6
6
  rspec (~> 3.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- ast (2.4.0)
12
- betterp (0.1.3)
13
- paint (~> 2.0)
14
- byebug (11.0.1)
15
- concurrent-ruby (1.1.5)
16
- diff-lcs (1.3)
17
- i18n (1.7.0)
11
+ ast (2.4.1)
12
+ byebug (11.1.3)
13
+ concurrent-ruby (1.1.6)
14
+ devpack (0.1.2)
15
+ diff-lcs (1.4.4)
16
+ i18n (1.8.5)
18
17
  concurrent-ruby (~> 1.0)
19
- jaro_winkler (1.5.4)
20
18
  mini_portile2 (2.4.0)
21
- nokogiri (1.10.5)
19
+ nokogiri (1.10.10)
22
20
  mini_portile2 (~> 2.4.0)
23
- paint (2.1.1)
24
- parallel (1.19.1)
25
- parser (2.6.5.0)
26
- ast (~> 2.4.0)
21
+ paint (2.2.0)
22
+ parallel (1.19.2)
23
+ parser (2.7.1.4)
24
+ ast (~> 2.4.1)
27
25
  rainbow (3.0.0)
28
- rake (10.5.0)
26
+ rake (13.0.1)
27
+ regexp_parser (1.7.1)
28
+ rexml (3.2.4)
29
29
  rspec (3.9.0)
30
30
  rspec-core (~> 3.9.0)
31
31
  rspec-expectations (~> 3.9.0)
32
32
  rspec-mocks (~> 3.9.0)
33
- rspec-core (3.9.0)
34
- rspec-support (~> 3.9.0)
35
- rspec-expectations (3.9.0)
33
+ rspec-core (3.9.2)
34
+ rspec-support (~> 3.9.3)
35
+ rspec-expectations (3.9.2)
36
36
  diff-lcs (>= 1.2.0, < 2.0)
37
37
  rspec-support (~> 3.9.0)
38
38
  rspec-its (1.3.0)
39
39
  rspec-core (>= 3.0.0)
40
40
  rspec-expectations (>= 3.0.0)
41
- rspec-mocks (3.9.0)
41
+ rspec-mocks (3.9.1)
42
42
  diff-lcs (>= 1.2.0, < 2.0)
43
43
  rspec-support (~> 3.9.0)
44
- rspec-support (3.9.0)
45
- rubocop (0.76.0)
46
- jaro_winkler (~> 1.5.1)
44
+ rspec-support (3.9.3)
45
+ rubocop (0.89.1)
47
46
  parallel (~> 1.10)
48
- parser (>= 2.6)
47
+ parser (>= 2.7.1.1)
49
48
  rainbow (>= 2.2.2, < 4.0)
49
+ regexp_parser (>= 1.7)
50
+ rexml
51
+ rubocop-ast (>= 0.3.0, < 1.0)
50
52
  ruby-progressbar (~> 1.7)
51
- unicode-display_width (>= 1.4.0, < 1.7)
52
- rubocop-rspec (1.36.0)
53
- rubocop (>= 0.68.1)
53
+ unicode-display_width (>= 1.4.0, < 2.0)
54
+ rubocop-ast (0.3.0)
55
+ parser (>= 2.7.1.4)
56
+ rubocop-rspec (1.42.0)
57
+ rubocop (>= 0.87.0)
54
58
  ruby-progressbar (1.10.1)
55
- strong_versions (0.3.2)
56
- i18n (>= 0.5.0)
59
+ strong_versions (0.4.5)
60
+ i18n (>= 0.5)
57
61
  paint (~> 2.0)
58
- unicode-display_width (1.6.0)
62
+ unicode-display_width (1.7.0)
59
63
 
60
64
  PLATFORMS
61
65
  ruby
62
66
 
63
67
  DEPENDENCIES
64
- betterp (~> 0.1.3)
65
68
  bundler (~> 2.0)
66
69
  byebug (~> 11.0)
70
+ devpack (~> 0.1.2)
67
71
  i18n (~> 1.7)
68
- rake (~> 10.0)
72
+ rake (~> 13.0)
69
73
  rspec-html!
70
74
  rspec-its (~> 1.3)
71
- rubocop (~> 0.76.0)
75
+ rubocop (~> 0.89.1)
72
76
  rubocop-rspec (~> 1.36)
73
- strong_versions (~> 0.3.2)
77
+ strong_versions (~> 0.4.5)
74
78
 
75
79
  BUNDLED WITH
76
80
  2.0.2
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.1.1'
10
+ gem 'rspec-html', '~> 0.2.2'
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,17 +25,116 @@ 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 object interface:
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
+ ### 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:
30
36
 
31
37
  ```ruby
32
- RSpec.describe 'something', type: :request do
33
- it 'does something' do
34
- get '/'
35
- expect(document.body).to include 'something'
36
- end
38
+ before { parse_html('<html><body>hello</body></html>') }
39
+ it 'says hello' do
40
+ expect(document.body).to contain_text 'hello'
37
41
  end
38
42
  ```
39
43
 
44
+ To navigate the _DOM_ by a sequence of tag names use chained method calls on the `document` object:
45
+
46
+ #### Tag Traversal
47
+ ```ruby
48
+ expect(document.body.div.span).to contain_text 'some text'
49
+ ```
50
+
51
+ #### Attribute Matching
52
+ To select an element matching certain attributes pass a hash to any of the chained methods:
53
+ ```ruby
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 specific class (or array 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
+ #### 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'
68
+ ```
69
+
70
+ #### Attribute Retrieval
71
+ To select an attribute from an element use the hash-style interface:
72
+ ```ruby
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'
75
+ ```
76
+
77
+ #### Indexing a Matching Set
78
+ To select an index from a set of matched elements use the array-style interface (the first matching element is `1`, not `0`):
79
+ ```ruby
80
+ expect(document.body.div[1].span[1][:class]).to contain_text 'my-class'
81
+ ```
82
+
83
+ #### Element Existence
84
+ To test if a matching element was found use the `exist` matcher:
85
+ ```ruby
86
+ expect(document.body.div[1]).to exist
87
+ expect(document.body.div[4]).to_not exist
88
+ ```
89
+
90
+ #### Length of matched attributes
91
+ To test the length of matched elements use the `#size` or `#length` method:
92
+ ```ruby
93
+ expect(document.body.div.size).to eql 3
94
+ expect(document.body.div.length).to eql 3
95
+ ```
96
+
97
+ #### XPath / CSS Selectors
98
+ If you need something more specific you can always use the _Nokogiri_ `#xpath` and `#css` methods on any element:
99
+ ```ruby
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'
102
+ ```
103
+
104
+ To simply check that an _XPath_ or _CSS_ selector exists use `have_xpath` and `have_css`:
105
+ ```ruby
106
+ expect(document.body).to have_css 'html body div.myclass'
107
+ expect(document.body).to have_xpath '//html/body/div[@class="myclass"]'
108
+ ```
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
+
40
138
  ## Contributing
41
139
 
42
140
  Feel free to make a pull request.
@@ -9,7 +9,17 @@ module RSpec
9
9
  # Module extension for RSpec::SharedContext
10
10
  module HTML
11
11
  def document
12
- RSpecHTML::Document.new(response.body)
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)
19
+ end
20
+
21
+ def parse_html(content)
22
+ @document = RSpecHTML::Element.new(Nokogiri::HTML.parse(content), :document)
13
23
  end
14
24
  end
15
25
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RSpec
4
4
  module HTML
5
- VERSION = '0.1.1'
5
+ VERSION = '0.2.2'
6
6
  end
7
7
  end
@@ -3,16 +3,18 @@
3
3
  require 'nokogiri'
4
4
  require 'pathname'
5
5
 
6
- require 'rspec_html/nameable'
7
6
  require 'rspec_html/searchable'
8
- require 'rspec_html/body'
9
- require 'rspec_html/document'
10
- require 'rspec_html/head'
11
- require 'rspec_html/title'
7
+ require 'rspec_html/element'
8
+ require 'rspec_html/tags'
9
+ require 'rspec_html/matchers'
12
10
 
13
11
  # Support module for rspec/html
14
12
  module RSpecHTML
13
+ class Error < StandardError; end
14
+ class NoResponseError < Error; end
15
15
  def self.root
16
16
  Pathname.new(__dir__).parent
17
17
  end
18
18
  end
19
+
20
+ RSpec.configure { |config| config.include RSpecHTML::Matchers }
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecHTML
4
+ # HTML DOM element abstraction
5
+ class Element
6
+ include Searchable
7
+
8
+ attr_reader :name, :element
9
+
10
+ def initialize(element, name, siblings: [])
11
+ @name = name
12
+ @element = element
13
+ @siblings = siblings
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
37
+ end
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,15 +4,113 @@ 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
- @entity.text.include?(val)
7
+ text.include?(val)
8
8
  end
9
9
 
10
- def to_s
11
- @entity.text.strip
10
+ def css(*args)
11
+ self.class.new(@element&.css(*args), :css)
12
12
  end
13
13
 
14
- def inspect
15
- %("#{self}")
14
+ def xpath(*args)
15
+ self.class.new(@element&.xpath(*args), :xpath)
16
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 [](val)
34
+ return index(val) if val.is_a?(Integer)
35
+ return range(val) if val.is_a?(Range)
36
+
37
+ @element&.attr(val.to_s)
38
+ end
39
+
40
+ def text
41
+ @element&.text || ''
42
+ end
43
+
44
+ def size
45
+ return @element.size if @element.respond_to?(:size)
46
+
47
+ @siblings.size
48
+ end
49
+ alias length size
50
+
51
+ private
52
+
53
+ def method_missing(tag, *args)
54
+ return super unless Tags.include?(tag)
55
+ return self.class.new(find(tag), tag, siblings: find(tag, all: true)) if args.empty?
56
+
57
+ self.class.new(where(tag, args.first), tag, siblings: where(tag, args.first, all: true))
58
+ end
59
+
60
+ def index(val)
61
+ zero_index_error if val.zero?
62
+ self.class.new(@siblings[val - 1], name)
63
+ end
64
+
65
+ def range(val)
66
+ zero_index_error if val.first.zero?
67
+ self.class.new(@siblings[(val.first - 1)..(val.last - 1)], :range)
68
+ end
69
+
70
+ def zero_index_error
71
+ raise ArgumentError, 'Index for matched sets starts at 1, not 0.'
72
+ end
73
+
74
+ def where(tag, query, all: false)
75
+ matched = if query[:class]
76
+ where_class(tag, query[:class]) & where_xpath(tag, query.merge(class: nil))
77
+ else
78
+ where_xpath(tag, query)
79
+ end
80
+ return matched&.first unless all
81
+
82
+ matched
83
+ end
84
+
85
+ def where_xpath(tag, query)
86
+ conditions = "[#{where_conditions(query)}]" unless query.compact.empty?
87
+ @element&.xpath("//#{tag}#{conditions}")
88
+ end
89
+
90
+ def where_conditions(query)
91
+ query.compact.map do |key, value|
92
+ next if value.nil?
93
+ next %(@#{key}="#{value}") unless key == :text
94
+
95
+ %[contains(text(),"#{value}")]
96
+ end.join ' and '
97
+ end
98
+
99
+ def where_class(tag, class_or_classes)
100
+ selector = class_or_classes.is_a?(Array) ? class_or_classes.join('.') : class_or_classes
101
+ @element&.css("#{tag}.#{selector}")
102
+ end
103
+
104
+ def find(tag, all: false)
105
+ return @element&.css(tag.to_s)&.first unless all
106
+
107
+ @element&.css(tag.to_s)
108
+ end
109
+
110
+ # rubocop:disable Lint/MissingSuper
111
+ def respond_to_missing?(method_name, *_)
112
+ Tags.include?(method_name)
113
+ end
114
+ # rubocop:enable Lint/MissingSuper
17
115
  end
18
116
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecHTML
4
+ # HTML tag identification, used to determine viability for DOM traversal via chained methods.
5
+ class Tags
6
+ def self.include?(val)
7
+ tags.include?(val.to_s.upcase)
8
+ end
9
+
10
+ # rubocop:disable Metrics/MethodLength
11
+ def self.tags
12
+ %w[
13
+ A ABBR ACRONYM ADDRESS APPLET AREA ARTICLE ASIDE AUDIO B BASE BASEFONT BDI BDO BGSOUND
14
+ BIG BLINK BLOCKQUOTE BODY BR BUTTON CANVAS CAPTION CENTER CITE CODE COL COLGROUP COMMAND
15
+ CONTENT DATA DATALIST DD DEL DETAILS DFN DIALOG DIR DIV DL DT ELEMENT EM EMBED FIELDSET
16
+ FIGCAPTION FIGURE FONT FOOTER FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HEADER HGROUP HR
17
+ HTML I IFRAME IMAGE IMG INPUT INS ISINDEX KBD KEYGEN LABEL LEGEND LI LINK LISTING MAIN
18
+ MAIN MAP MARK MARQUEE MENU MENUITEM META METER MULTICOL NAV NEXTID NOBR NOEMBED NOFRAMES
19
+ NOSCRIPT OBJECT OL OPTGROUP OPTION OUTPUT P PARAM PICTURE PLAINTEXT PRE PROGRESS Q RB RP
20
+ RT RTC RUBY S SAMP SCRIPT SECTION SELECT SHADOW SLOT SMALL SOURCE SPACER SPAN STRIKE
21
+ STRONG STYLE SUB SUMMARY SUP TABLE TBODY TD TEMPLATE TEXTAREA TFOOT TH THEAD TIME TITLE
22
+ TR TRACK TT U UL VAR VIDEO WBR XMP
23
+ ]
24
+ end
25
+ # rubocop:enable Metrics/MethodLength
26
+ end
27
+ end
@@ -31,13 +31,13 @@ Gem::Specification.new do |spec|
31
31
  spec.add_dependency 'nokogiri', '~> 1.10'
32
32
  spec.add_dependency 'rspec', '~> 3.0'
33
33
 
34
- spec.add_development_dependency 'betterp', '~> 0.1.3'
35
34
  spec.add_development_dependency 'bundler', '~> 2.0'
36
35
  spec.add_development_dependency 'byebug', '~> 11.0'
36
+ spec.add_development_dependency 'devpack', '~> 0.1.2'
37
37
  spec.add_development_dependency 'i18n', '~> 1.7'
38
- spec.add_development_dependency 'rake', '~> 10.0'
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.76.0'
40
+ spec.add_development_dependency 'rubocop', '~> 0.89.1'
41
41
  spec.add_development_dependency 'rubocop-rspec', '~> 1.36'
42
- spec.add_development_dependency 'strong_versions', '~> 0.3.2'
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.1.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: 2019-12-21 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
@@ -39,47 +39,47 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: betterp
42
+ name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.1.3
47
+ version: '2.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.1.3
54
+ version: '2.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: bundler
56
+ name: byebug
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '2.0'
61
+ version: '11.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '2.0'
68
+ version: '11.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: byebug
70
+ name: devpack
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '11.0'
75
+ version: 0.1.2
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '11.0'
82
+ version: 0.1.2
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: i18n
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '10.0'
103
+ version: '13.0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '10.0'
110
+ version: '13.0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rspec-its
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: 0.76.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.76.0
138
+ version: 0.89.1
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: rubocop-rspec
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -156,14 +156,14 @@ dependencies:
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: 0.3.2
159
+ version: 0.4.5
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: 0.3.2
166
+ version: 0.4.5
167
167
  description: HTML document abstraction and matchers for RSpec
168
168
  email:
169
169
  - git@bob.frl
@@ -173,6 +173,7 @@ extra_rdoc_files: []
173
173
  files:
174
174
  - ".gitignore"
175
175
  - ".rspec"
176
+ - ".rubocop.yml"
176
177
  - ".ruby-version"
177
178
  - ".travis.yml"
178
179
  - Gemfile
@@ -186,13 +187,18 @@ files:
186
187
  - lib/rspec/html.rb
187
188
  - lib/rspec/html/version.rb
188
189
  - lib/rspec_html.rb
189
- - lib/rspec_html/body.rb
190
- - lib/rspec_html/document.rb
191
- - lib/rspec_html/head.rb
192
- - lib/rspec_html/nameable.rb
190
+ - lib/rspec_html/element.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
193
195
  - lib/rspec_html/searchable.rb
194
- - lib/rspec_html/title.rb
196
+ - lib/rspec_html/tags.rb
195
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
196
202
  homepage: https://github.com/bobf/rspec-html
197
203
  licenses:
198
204
  - MIT
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpecHTML
4
- # HTML/BODY abstraction
5
- class Body
6
- include Searchable
7
- include Nameable
8
-
9
- def initialize(parsed_html)
10
- @name = :body
11
- @entity = parsed_html.css('body')
12
- end
13
- end
14
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpecHTML
4
- # HTML Document representation
5
- class Document
6
- def initialize(html)
7
- @html = html
8
- end
9
-
10
- def body
11
- Body.new(parsed_html)
12
- end
13
-
14
- def head
15
- Head.new(parsed_html)
16
- end
17
-
18
- private
19
-
20
- def parsed_html
21
- @parsed_html ||= Nokogiri::HTML(@html)
22
- end
23
- end
24
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpecHTML
4
- # HTML/BODY abstraction
5
- class Head
6
- include Nameable
7
-
8
- def initialize(parsed_html)
9
- @parsed_html = parsed_html
10
- @entity = parsed_html.css('head')
11
- @name = :head
12
- end
13
-
14
- def title
15
- Title.new(@parsed_html)
16
- end
17
-
18
- def include?(val)
19
- title.include?(val)
20
- end
21
- end
22
- end
@@ -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,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpecHTML
4
- # HTML/HEAD/TITLE abstraction
5
- class Title
6
- include Searchable
7
- include Nameable
8
-
9
- def initialize(parsed_html)
10
- @name = :title
11
- @entity = parsed_html.css('head title')
12
- end
13
- end
14
- end