capybara 3.11.1 → 3.12.0

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