rspec-html 0.1.1 → 0.2.2

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: 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