capybara 3.11.1 → 3.12.0

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +18 -1
  3. data/README.md +4 -4
  4. data/lib/capybara.rb +7 -0
  5. data/lib/capybara/node/element.rb +7 -1
  6. data/lib/capybara/node/matchers.rb +0 -18
  7. data/lib/capybara/queries/base_query.rb +1 -1
  8. data/lib/capybara/queries/selector_query.rb +24 -39
  9. data/lib/capybara/result.rb +4 -4
  10. data/lib/capybara/selector.rb +7 -15
  11. data/lib/capybara/selector/builders/css_builder.rb +59 -27
  12. data/lib/capybara/selector/builders/xpath_builder.rb +50 -35
  13. data/lib/capybara/selector/css.rb +7 -7
  14. data/lib/capybara/selector/filter.rb +1 -0
  15. data/lib/capybara/selector/filter_set.rb +17 -15
  16. data/lib/capybara/selector/filters/locator_filter.rb +19 -0
  17. data/lib/capybara/selector/regexp_disassembler.rb +104 -61
  18. data/lib/capybara/selector/selector.rb +14 -5
  19. data/lib/capybara/selenium/driver.rb +14 -9
  20. data/lib/capybara/selenium/driver_specializations/{marionette_driver.rb → firefox_driver.rb} +3 -3
  21. data/lib/capybara/selenium/nodes/{marionette_node.rb → firefox_node.rb} +1 -1
  22. data/lib/capybara/spec/session/all_spec.rb +8 -1
  23. data/lib/capybara/spec/session/assert_selector_spec.rb +0 -10
  24. data/lib/capybara/spec/session/click_button_spec.rb +5 -3
  25. data/lib/capybara/spec/session/click_link_spec.rb +5 -5
  26. data/lib/capybara/spec/session/find_spec.rb +1 -1
  27. data/lib/capybara/spec/session/first_spec.rb +1 -1
  28. data/lib/capybara/spec/session/has_css_spec.rb +7 -0
  29. data/lib/capybara/spec/session/has_xpath_spec.rb +17 -0
  30. data/lib/capybara/spec/session/node_spec.rb +10 -3
  31. data/lib/capybara/spec/session/window/window_spec.rb +2 -2
  32. data/lib/capybara/spec/spec_helper.rb +1 -2
  33. data/lib/capybara/spec/views/obscured.erb +3 -0
  34. data/lib/capybara/version.rb +1 -1
  35. data/spec/css_builder_spec.rb +99 -0
  36. data/spec/result_spec.rb +6 -0
  37. data/spec/selector_spec.rb +26 -1
  38. data/spec/selenium_spec_chrome.rb +18 -16
  39. data/spec/selenium_spec_chrome_remote.rb +0 -2
  40. data/spec/{selenium_spec_marionette.rb → selenium_spec_firefox.rb} +31 -25
  41. data/spec/selenium_spec_firefox_remote.rb +4 -6
  42. data/spec/shared_selenium_session.rb +2 -2
  43. data/spec/spec_helper.rb +5 -5
  44. data/spec/xpath_builder_spec.rb +91 -0
  45. metadata +10 -7
@@ -6,10 +6,10 @@ require 'English'
6
6
  class Capybara::Selenium::Driver < Capybara::Driver::Base
7
7
  DEFAULT_OPTIONS = {
8
8
  browser: :firefox,
9
- clear_local_storage: false,
10
- clear_session_storage: false
9
+ clear_local_storage: nil,
10
+ clear_session_storage: nil
11
11
  }.freeze
12
- SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage].freeze
12
+ SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage timeout].freeze
13
13
  attr_reader :app, :options
14
14
 
15
15
  def self.load_selenium
@@ -23,6 +23,9 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
23
23
 
24
24
  def browser
25
25
  @browser ||= begin
26
+ if options[:timeout]
27
+ options[:http_client] ||= Selenium::WebDriver::Remote::Http::Default.new(read_timeout: options[:timeout])
28
+ end
26
29
  processed_options = options.reject { |key, _val| SPECIAL_OPTIONS.include?(key) }
27
30
  Selenium::WebDriver.for(options[:browser], processed_options).tap do |driver|
28
31
  specialize_driver(driver)
@@ -260,15 +263,17 @@ private
260
263
  end
261
264
 
262
265
  def clear_storage
263
- clear_session_storage if options[:clear_session_storage]
264
- clear_local_storage if options[:clear_local_storage]
266
+ clear_session_storage unless options[:clear_session_storage] == false
267
+ clear_local_storage unless options[:clear_local_storage] == false
268
+ rescue Selenium::WebDriver::Error::JavascriptError # rubocop:disable Lint/HandleExceptions
269
+ # session/local storage may not be available if on non-http pages (e.g. about:blank)
265
270
  end
266
271
 
267
272
  def clear_session_storage
268
273
  if @browser.respond_to? :session_storage
269
274
  @browser.session_storage.clear
270
275
  else
271
- warn 'sessionStorage clear requested but is not available for this driver'
276
+ warn 'sessionStorage clear requested but is not supported by this driver' unless options[:clear_session_storage].nil?
272
277
  end
273
278
  end
274
279
 
@@ -276,7 +281,7 @@ private
276
281
  if @browser.respond_to? :local_storage
277
282
  @browser.local_storage.clear
278
283
  else
279
- warn 'localStorage clear requested but is not available for this driver'
284
+ warn 'localStorage clear requested but is not supported by this driver' unless options[:clear_local_storage].nil?
280
285
  end
281
286
  end
282
287
 
@@ -353,7 +358,7 @@ private
353
358
  extend ChromeDriver
354
359
  when :firefox
355
360
  require 'capybara/selenium/patches/pause_duration_fix' if pause_broken?(sel_driver)
356
- extend MarionetteDriver if sel_driver.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
361
+ extend FirefoxDriver if sel_driver.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
357
362
  end
358
363
  end
359
364
 
@@ -396,4 +401,4 @@ private
396
401
  end
397
402
 
398
403
  require 'capybara/selenium/driver_specializations/chrome_driver'
399
- require 'capybara/selenium/driver_specializations/marionette_driver'
404
+ require 'capybara/selenium/driver_specializations/firefox_driver'
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'capybara/selenium/nodes/marionette_node'
3
+ require 'capybara/selenium/nodes/firefox_node'
4
4
 
5
- module Capybara::Selenium::Driver::MarionetteDriver
5
+ module Capybara::Selenium::Driver::FirefoxDriver
6
6
  def resize_window_to(handle, width, height)
7
7
  within_given_window(handle) do
8
8
  # Don't set the size if already set - See https://github.com/mozilla/geckodriver/issues/643
@@ -45,6 +45,6 @@ module Capybara::Selenium::Driver::MarionetteDriver
45
45
  private
46
46
 
47
47
  def build_node(native_node)
48
- ::Capybara::Selenium::MarionetteNode.new(self, native_node)
48
+ ::Capybara::Selenium::FirefoxNode.new(self, native_node)
49
49
  end
50
50
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'capybara/selenium/extensions/html5_drag'
4
4
 
5
- class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node
5
+ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
6
6
  include Html5Drag
7
7
 
8
8
  def click(keys = [], **options)
@@ -30,7 +30,7 @@ Capybara::SpecHelper.spec '#all' do
30
30
 
31
31
  it 'should accept an XPath instance', :exact_false do
32
32
  @session.visit('/form')
33
- @xpath = Capybara::Selector.all[:fillable_field].call('Name')
33
+ @xpath = Capybara::Selector[:fillable_field].call('Name')
34
34
  expect(@xpath).to be_a(::XPath::Union)
35
35
  @result = @session.all(@xpath).map(&:value)
36
36
  expect(@result).to include('Smith', 'John', 'John Smith')
@@ -135,6 +135,13 @@ Capybara::SpecHelper.spec '#all' do
135
135
  it 'should raise ExpectationNotMet when the number of elements founds does not match the expectation' do
136
136
  expect { @session.all(:css, 'h1, p', between: 0..3) }.to raise_error(Capybara::ExpectationNotMet)
137
137
  end
138
+
139
+ eval <<~TEST, binding, __FILE__, __LINE__ + 1 if RUBY_VERSION.to_f > 2.5
140
+ it'treats an endless range as minimum' do
141
+ expect { @session.all(:css, 'h1, p', between: 2..) }.not_to raise_error
142
+ expect { @session.all(:css, 'h1, p', between: 5..) }.to raise_error(Capybara::ExpectationNotMet)
143
+ end
144
+ TEST
138
145
  end
139
146
 
140
147
  context 'with multiple count filters' do
@@ -72,16 +72,6 @@ Capybara::SpecHelper.spec '#assert_selector' do
72
72
  end
73
73
  end
74
74
 
75
- Capybara::SpecHelper.spec '#refute_selector', requires: [:driver] do
76
- it 'should warn not to use' do
77
- @session.visit('/with_html')
78
- doc = @session.document
79
- allow(doc).to receive(:warn)
80
- doc.refute_selector(:xpath, '//abbr')
81
- expect(doc).to have_received(:warn)
82
- end
83
- end
84
-
85
75
  Capybara::SpecHelper.spec '#assert_no_selector' do
86
76
  before do
87
77
  @session.visit('/with_html')
@@ -5,10 +5,12 @@ Capybara::SpecHelper.spec '#click_button' do
5
5
  @session.visit('/form')
6
6
  end
7
7
 
8
- it 'should wait for asynchronous load', requires: [:js] do
8
+ it 'should wait for asynchronous load', :focus_, requires: [:js] do
9
9
  @session.visit('/with_js')
10
- @session.click_link('Click me')
11
- @session.click_button('New Here')
10
+ @session.using_wait_time(1.5) do
11
+ @session.click_link('Click me')
12
+ @session.click_button('New Here')
13
+ end
12
14
  end
13
15
 
14
16
  it 'casts to string' do
@@ -83,7 +83,7 @@ Capybara::SpecHelper.spec '#click_link' do
83
83
  end
84
84
 
85
85
  it "should raise error if link wasn't found" do
86
- expect { @session.click_link('labore', href: 'invalid_href') }.to raise_error(Capybara::ElementNotFound)
86
+ expect { @session.click_link('labore', href: 'invalid_href') }.to raise_error(Capybara::ElementNotFound, /with href "invalid_href/)
87
87
  end
88
88
  end
89
89
 
@@ -104,8 +104,8 @@ Capybara::SpecHelper.spec '#click_link' do
104
104
  end
105
105
 
106
106
  it "should raise an error if no link's href matched the pattern" do
107
- expect { @session.click_link('labore', href: /invalid_pattern/) }.to raise_error(Capybara::ElementNotFound)
108
- expect { @session.click_link('labore', href: /.+d+/) }.to raise_error(Capybara::ElementNotFound)
107
+ expect { @session.click_link('labore', href: /invalid_pattern/) }.to raise_error(Capybara::ElementNotFound, %r{with href matching /invalid_pattern/})
108
+ expect { @session.click_link('labore', href: /.+d+/) }.to raise_error(Capybara::ElementNotFound, /#{Regexp.quote "with href matching /.+d+/"}/)
109
109
  end
110
110
 
111
111
  context 'href: nil' do
@@ -114,8 +114,8 @@ Capybara::SpecHelper.spec '#click_link' do
114
114
  end
115
115
 
116
116
  it 'should raise an error if href attribute exists' do
117
- expect { @session.click_link('Blank Href', href: nil) }.to raise_error(Capybara::ElementNotFound)
118
- expect { @session.click_link('Normal Anchor', href: nil) }.to raise_error(Capybara::ElementNotFound)
117
+ expect { @session.click_link('Blank Href', href: nil) }.to raise_error(Capybara::ElementNotFound, /with no href attribute/)
118
+ expect { @session.click_link('Normal Anchor', href: nil) }.to raise_error(Capybara::ElementNotFound, /with no href attribute/)
119
119
  end
120
120
  end
121
121
  end
@@ -224,7 +224,7 @@ Capybara::SpecHelper.spec '#find' do
224
224
 
225
225
  it 'should accept an XPath instance' do
226
226
  @session.visit('/form')
227
- @xpath = Capybara::Selector.all[:fillable_field].call('First Name')
227
+ @xpath = Capybara::Selector[:fillable_field].call('First Name')
228
228
  expect(@xpath).to be_a(::XPath::Union)
229
229
  expect(@session.find(@xpath).value).to eq('John')
230
230
  end
@@ -24,7 +24,7 @@ Capybara::SpecHelper.spec '#first' do
24
24
 
25
25
  it 'should accept an XPath instance' do
26
26
  @session.visit('/form')
27
- @xpath = Capybara::Selector.all[:fillable_field].call('First Name')
27
+ @xpath = Capybara::Selector[:fillable_field].call('First Name')
28
28
  expect(@xpath).to be_a(::XPath::Union)
29
29
  expect(@session.first(@xpath).value).to eq('John')
30
30
  end
@@ -27,11 +27,15 @@ Capybara::SpecHelper.spec '#has_css?' do
27
27
  expect(@session).to have_css('h2', id: 'h2one')
28
28
  expect(@session).to have_css('h2')
29
29
  expect(@session).to have_css('h2', id: /h2o/)
30
+ expect(@session).to have_css('li', id: /john|paul/)
30
31
  end
31
32
 
32
33
  it 'should support :class option' do
33
34
  expect(@session).to have_css('li', class: 'guitarist')
34
35
  expect(@session).to have_css('li', class: /guitar/)
36
+ expect(@session).to have_css('li', class: /guitar|drummer/)
37
+ expect(@session).to have_css('li', class: %w[beatle guitarist])
38
+ expect(@session).to have_css('li', class: /.*/)
35
39
  end
36
40
 
37
41
  it 'should support case insensitive :class and :id options' do
@@ -111,6 +115,9 @@ Capybara::SpecHelper.spec '#has_css?' do
111
115
  expect(@session).to have_css('p', count: 3)
112
116
  expect(@session).to have_css('p a#foo', count: 1)
113
117
  expect(@session).to have_css('p a.doesnotexist', count: 0)
118
+ expect(@session).to have_css('li', class: /guitar|drummer/, count: 4)
119
+ expect(@session).to have_css('li', id: /john|paul/, class: /guitar|drummer/, count: 2)
120
+ expect(@session).to have_css('li', class: %w[beatle guitarist], count: 2)
114
121
  end
115
122
 
116
123
  it 'should be false if the content occurs a different number of times than the given' do
@@ -11,6 +11,20 @@ Capybara::SpecHelper.spec '#has_xpath?' do
11
11
  expect(@session).to have_xpath("//p[contains(.,'est')]")
12
12
  end
13
13
 
14
+ it 'should support :id option' do
15
+ expect(@session).to have_xpath('//h2', id: 'h2one')
16
+ expect(@session).to have_xpath('//h2')
17
+ expect(@session).to have_xpath('//h2', id: /h2o/)
18
+ end
19
+
20
+ it 'should support :class option' do
21
+ expect(@session).to have_xpath('//li', class: 'guitarist')
22
+ expect(@session).to have_xpath('//li', class: /guitar/)
23
+ expect(@session).to have_xpath('//li', class: /guitar|drummer/)
24
+ expect(@session).to have_xpath('//li', class: %w[beatle guitarist])
25
+ expect(@session).to have_xpath('//li', class: /.*/)
26
+ end
27
+
14
28
  it 'should be false if the given selector is not on the page' do
15
29
  expect(@session).not_to have_xpath('//abbr')
16
30
  expect(@session).not_to have_xpath("//p//a[@id='doesnotexist']")
@@ -43,6 +57,9 @@ Capybara::SpecHelper.spec '#has_xpath?' do
43
57
  expect(@session).to have_xpath("//p//a[@id='foo']", count: 1)
44
58
  expect(@session).to have_xpath("//p[contains(.,'est')]", count: 1)
45
59
  expect(@session).to have_xpath("//p//a[@id='doesnotexist']", count: 0)
60
+ expect(@session).to have_xpath('//li', class: /guitar|drummer/, count: 4)
61
+ expect(@session).to have_xpath('//li', id: /john|paul/, class: /guitar|drummer/, count: 2)
62
+ expect(@session).to have_xpath('//li', class: %w[beatle guitarist], count: 2)
46
63
  end
47
64
 
48
65
  it 'should be false if the content occurs a different number of times than the given' do
@@ -406,6 +406,13 @@ Capybara::SpecHelper.spec 'node' do
406
406
  expect(locations[:y].to_f).to be_within(1).of(5)
407
407
  end
408
408
 
409
+ it 'should raise error if both x and y values are not passed' do
410
+ @session.visit('with_js')
411
+ el = @session.find(:css, '#click-test')
412
+ expect { el.click(x: 5) }.to raise_error ArgumentError
413
+ expect { el.click(x: nil, y: 3) }.to raise_error ArgumentError
414
+ end
415
+
409
416
  it 'should be able to click a table row', requires: [:js] do
410
417
  @session.visit('/tables')
411
418
  tr = @session.find(:css, '#agent_table tr:first-child').click
@@ -416,7 +423,7 @@ Capybara::SpecHelper.spec 'node' do
416
423
  @session.visit('/obscured')
417
424
  obscured = @session.find(:css, '#obscured')
418
425
  @session.execute_script <<~JS
419
- setTimeout(function(){ $('#cover').hide(); }, 1000)
426
+ setTimeout(function(){ $('#cover').hide(); }, 700)
420
427
  JS
421
428
  expect { obscured.click }.not_to raise_error
422
429
  end
@@ -468,7 +475,7 @@ Capybara::SpecHelper.spec 'node' do
468
475
  @session.visit('/obscured')
469
476
  obscured = @session.find(:css, '#obscured')
470
477
  @session.execute_script <<~JS
471
- setTimeout(function(){ $('#cover').hide(); }, 1000)
478
+ setTimeout(function(){ $('#cover').hide(); }, 700)
472
479
  JS
473
480
  expect { obscured.double_click }.not_to raise_error
474
481
  end
@@ -502,7 +509,7 @@ Capybara::SpecHelper.spec 'node' do
502
509
  @session.visit('/obscured')
503
510
  obscured = @session.find(:css, '#obscured')
504
511
  @session.execute_script <<~JS
505
- setTimeout(function(){ $('#cover').hide(); }, 1000)
512
+ setTimeout(function(){ $('#cover').hide(); }, 700)
506
513
  JS
507
514
  expect { obscured.right_click }.not_to raise_error
508
515
  end
@@ -120,7 +120,7 @@ Capybara::SpecHelper.spec Capybara::Window, requires: [:windows] do
120
120
 
121
121
  after do
122
122
  @session.current_window.resize_to(*@initial_size)
123
- sleep 0.5
123
+ sleep 1
124
124
  end
125
125
 
126
126
  it 'should be able to resize window', requires: %i[windows js] do
@@ -194,7 +194,7 @@ Capybara::SpecHelper.spec Capybara::Window, requires: [:windows] do
194
194
 
195
195
  after do
196
196
  @session.current_window.resize_to(*@initial_size)
197
- sleep 0.5
197
+ sleep 1
198
198
  end
199
199
 
200
200
  it 'should be able to fullscreen the window' do
@@ -16,8 +16,7 @@ module Capybara
16
16
  config.filter_run_excluding requires: method(:filter).to_proc
17
17
  config.before { Capybara::SpecHelper.reset! }
18
18
  config.after { Capybara::SpecHelper.reset! }
19
- # Test in 3.5+ where metadata doesn't autotrigger shared context inclusion - will be only behavior in RSpec 4
20
- config.shared_context_metadata_behavior = :apply_to_host_groups if config.respond_to?(:shared_context_metadata_behavior=)
19
+ config.shared_context_metadata_behavior = :apply_to_host_groups
21
20
  end
22
21
 
23
22
  def reset!
@@ -3,6 +3,9 @@
3
3
  <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
4
4
  <title>with_animation</title>
5
5
  <script src="/jquery.js" type="text/javascript" charset="utf-8"></script>
6
+ <script>
7
+ $(document).on('contextmenu', function(e){ e.preventDefault(); })
8
+ </script>
6
9
  <style>
7
10
  div {
8
11
  width: 400px;
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Capybara
4
- VERSION = '3.11.1'
4
+ VERSION = '3.12.0'
5
5
  end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Capybara::Selector::CSSBuilder do
6
+ let :builder do
7
+ ::Capybara::Selector::CSSBuilder.new(@css)
8
+ end
9
+
10
+ context 'add_attribute_conditions' do
11
+ it 'adds a single string condition to a single selector' do
12
+ @css = 'div'
13
+ selector = builder.add_attribute_conditions(random: 'abc')
14
+ expect(selector).to eq %(div[random='abc'])
15
+ end
16
+
17
+ it 'adds multiple string conditions to a single selector' do
18
+ @css = 'div'
19
+ selector = builder.add_attribute_conditions(random: 'abc', other: 'def')
20
+ expect(selector).to eq %(div[random='abc'][other='def'])
21
+ end
22
+
23
+ it 'adds a single string condition to a multiple selector' do
24
+ @css = 'div, ul'
25
+ selector = builder.add_attribute_conditions(random: 'abc')
26
+ expect(selector).to eq %(div[random='abc'], ul[random='abc'])
27
+ end
28
+
29
+ it 'adds multiple string conditions to a multiple selector' do
30
+ @css = 'div, ul'
31
+ selector = builder.add_attribute_conditions(random: 'abc', other: 'def')
32
+ expect(selector).to eq %(div[random='abc'][other='def'], ul[random='abc'][other='def'])
33
+ end
34
+
35
+ it 'adds simple regexp conditions to a single selector' do
36
+ @css = 'div'
37
+ selector = builder.add_attribute_conditions(random: /abc/, other: /def/)
38
+ expect(selector).to eq %(div[random*='abc'][other*='def'])
39
+ end
40
+
41
+ it 'adds wildcard regexp conditions to a single selector' do
42
+ @css = 'div'
43
+ selector = builder.add_attribute_conditions(random: /abc.*def/, other: /def.*ghi/)
44
+ expect(selector).to eq %(div[random*='abc'][random*='def'][other*='def'][other*='ghi'])
45
+ end
46
+
47
+ it 'adds alternated regexp conditions to a single selector' do
48
+ @css = 'div'
49
+ selector = builder.add_attribute_conditions(random: /abc|def/, other: /def|ghi/)
50
+ expect(selector).to eq %(div[random*='abc'][other*='def'], div[random*='abc'][other*='ghi'], div[random*='def'][other*='def'], div[random*='def'][other*='ghi'])
51
+ end
52
+
53
+ it 'adds alternated regexp conditions to a multiple selector' do
54
+ @css = 'div,ul'
55
+ selector = builder.add_attribute_conditions(other: /def.*ghi|jkl/)
56
+ expect(selector).to eq %(div[other*='def'][other*='ghi'], div[other*='jkl'], ul[other*='def'][other*='ghi'], ul[other*='jkl'])
57
+ end
58
+
59
+ it "returns original selector when regexp can't be substringed" do
60
+ @css = 'div'
61
+ selector = builder.add_attribute_conditions(other: /.+/)
62
+ expect(selector).to eq 'div'
63
+ end
64
+
65
+ context ':class' do
66
+ it 'handles string with CSS .' do
67
+ @css = 'a'
68
+ selector = builder.add_attribute_conditions(class: 'my_class')
69
+ expect(selector).to eq 'a.my_class'
70
+ end
71
+
72
+ it 'handles negated string with CSS .' do
73
+ @css = 'a'
74
+ selector = builder.add_attribute_conditions(class: '!my_class')
75
+ expect(selector).to eq 'a:not(.my_class)'
76
+ end
77
+
78
+ it 'handles array of string with CSS .' do
79
+ @css = 'a'
80
+ selector = builder.add_attribute_conditions(class: %w[my_class my_other_class])
81
+ expect(selector).to eq 'a.my_class.my_other_class'
82
+ end
83
+
84
+ it 'handles array of string with CSS . when negated included' do
85
+ @css = 'a'
86
+ selector = builder.add_attribute_conditions(class: %w[my_class !my_other_class])
87
+ expect(selector).to eq 'a.my_class:not(.my_other_class)'
88
+ end
89
+ end
90
+
91
+ context ':id' do
92
+ it 'handles string with CSS #' do
93
+ @css = 'ul'
94
+ selector = builder.add_attribute_conditions(id: 'my_id')
95
+ expect(selector).to eq 'ul#my_id'
96
+ end
97
+ end
98
+ end
99
+ end