capybara 2.1.0 → 2.2.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/History.md +48 -1
- data/README.md +22 -18
- data/lib/capybara.rb +28 -3
- data/lib/capybara/cucumber.rb +0 -2
- data/lib/capybara/driver/base.rb +9 -1
- data/lib/capybara/empty.html +4 -0
- data/lib/capybara/node/actions.rb +1 -1
- data/lib/capybara/node/finders.rb +3 -3
- data/lib/capybara/node/matchers.rb +22 -18
- data/lib/capybara/node/simple.rb +5 -3
- data/lib/capybara/query.rb +1 -0
- data/lib/capybara/rack_test/browser.rb +5 -4
- data/lib/capybara/rack_test/form.rb +5 -2
- data/lib/capybara/rack_test/node.rb +2 -1
- data/lib/capybara/result.rb +2 -1
- data/lib/capybara/rspec.rb +7 -0
- data/lib/capybara/rspec/features.rb +2 -0
- data/lib/capybara/rspec/matchers.rb +6 -6
- data/lib/capybara/selector.rb +3 -1
- data/lib/capybara/selenium/driver.rb +14 -2
- data/lib/capybara/selenium/node.rb +18 -5
- data/lib/capybara/server.rb +3 -7
- data/lib/capybara/session.rb +59 -17
- data/lib/capybara/spec/public/test.js +1 -1
- data/lib/capybara/spec/session/assert_selector.rb +18 -0
- data/lib/capybara/spec/session/attach_file_spec.rb +5 -0
- data/lib/capybara/spec/session/check_spec.rb +14 -0
- data/lib/capybara/spec/session/choose_spec.rb +14 -0
- data/lib/capybara/spec/session/click_button_spec.rb +20 -1
- data/lib/capybara/spec/session/fill_in_spec.rb +18 -2
- data/lib/capybara/spec/session/go_back_spec.rb +10 -0
- data/lib/capybara/spec/session/go_forward_spec.rb +12 -0
- data/lib/capybara/spec/session/has_button_spec.rb +24 -0
- data/lib/capybara/spec/session/has_field_spec.rb +48 -0
- data/lib/capybara/spec/session/has_text_spec.rb +18 -0
- data/lib/capybara/spec/session/node_spec.rb +36 -1
- data/lib/capybara/spec/session/reset_session_spec.rb +17 -2
- data/lib/capybara/spec/session/save_page_spec.rb +10 -4
- data/lib/capybara/spec/session/visit_spec.rb +16 -0
- data/lib/capybara/spec/session/within_frame_spec.rb +7 -0
- data/lib/capybara/spec/session/within_window_spec.rb +7 -0
- data/lib/capybara/spec/test_app.rb +1 -0
- data/lib/capybara/spec/views/form.erb +40 -0
- data/lib/capybara/spec/views/with_html.erb +5 -0
- data/lib/capybara/spec/views/with_js.erb +8 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/capybara_spec.rb +1 -1
- data/spec/result_spec.rb +14 -0
- data/spec/rspec/features_spec.rb +15 -0
- data/spec/rspec/matchers_spec.rb +24 -1
- data/spec/selenium_spec.rb +8 -1
- data/spec/server_spec.rb +13 -5
- metadata +78 -98
- metadata.gz.sig +0 -0
data/lib/capybara/query.rb
CHANGED
@@ -46,6 +46,7 @@ class Capybara::RackTest::Browser
|
|
46
46
|
method.downcase! unless method.is_a? Symbol
|
47
47
|
|
48
48
|
new_uri.path = request_path if path.start_with?("?")
|
49
|
+
new_uri.path = "/" if new_uri.path.empty?
|
49
50
|
new_uri.path = request_path.sub(%r(/[^/]*$), '/') + new_uri.path unless new_uri.path.start_with?('/')
|
50
51
|
new_uri.scheme ||= @current_scheme
|
51
52
|
new_uri.host ||= @current_host
|
@@ -77,7 +78,7 @@ class Capybara::RackTest::Browser
|
|
77
78
|
end
|
78
79
|
|
79
80
|
def dom
|
80
|
-
@dom ||=
|
81
|
+
@dom ||= Capybara::HTML(html)
|
81
82
|
end
|
82
83
|
|
83
84
|
def find(format, selector)
|
@@ -93,11 +94,11 @@ class Capybara::RackTest::Browser
|
|
93
94
|
rescue Rack::Test::Error
|
94
95
|
""
|
95
96
|
end
|
96
|
-
|
97
|
+
|
97
98
|
def title
|
98
99
|
dom.xpath("//title").text
|
99
100
|
end
|
100
|
-
|
101
|
+
|
101
102
|
protected
|
102
103
|
|
103
104
|
def build_rack_mock_session
|
@@ -108,6 +109,6 @@ protected
|
|
108
109
|
def request_path
|
109
110
|
last_request.path
|
110
111
|
rescue Rack::Test::Error
|
111
|
-
""
|
112
|
+
"/"
|
112
113
|
end
|
113
114
|
end
|
@@ -28,7 +28,10 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
|
|
28
28
|
case field.name
|
29
29
|
when 'input'
|
30
30
|
if %w(radio checkbox).include? field['type']
|
31
|
-
|
31
|
+
if field['checked']
|
32
|
+
node=Capybara::RackTest::Node.new(self.driver, field)
|
33
|
+
merge_param!(params, field['name'].to_s, node.value.to_s)
|
34
|
+
end
|
32
35
|
elsif %w(submit image).include? field['type']
|
33
36
|
# TO DO identify the click button here (in document order, rather
|
34
37
|
# than leaving until the end of the params)
|
@@ -60,7 +63,7 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
|
|
60
63
|
merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s) if option
|
61
64
|
end
|
62
65
|
when 'textarea'
|
63
|
-
merge_param!(params, field['name'].to_s, field.text.to_s)
|
66
|
+
merge_param!(params, field['name'].to_s, field.text.to_s.gsub(/\n/, "\r\n"))
|
64
67
|
end
|
65
68
|
end
|
66
69
|
merge_param!(params, button[:name], button[:value] || "") if button[:name]
|
@@ -52,7 +52,8 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
52
52
|
driver.follow(method, self[:href].to_s)
|
53
53
|
elsif (tag_name == 'input' and %w(submit image).include?(type)) or
|
54
54
|
((tag_name == 'button') and type.nil? or type == "submit")
|
55
|
-
|
55
|
+
associated_form = form
|
56
|
+
Capybara::RackTest::Form.new(driver, associated_form).submit(self) if associated_form
|
56
57
|
end
|
57
58
|
end
|
58
59
|
|
data/lib/capybara/result.rb
CHANGED
@@ -29,7 +29,8 @@ module Capybara
|
|
29
29
|
@query = query
|
30
30
|
end
|
31
31
|
|
32
|
-
def_delegators :@result, :each, :[], :at, :size, :count, :length,
|
32
|
+
def_delegators :@result, :each, :[], :at, :size, :count, :length,
|
33
|
+
:first, :last, :values_at, :empty?, :inspect, :sample, :index
|
33
34
|
|
34
35
|
def matches_count?
|
35
36
|
Capybara::Helpers.matches_count?(@result.size, @query.options)
|
data/lib/capybara/rspec.rb
CHANGED
@@ -7,6 +7,12 @@ require 'capybara/rspec/features'
|
|
7
7
|
RSpec.configure do |config|
|
8
8
|
config.include Capybara::DSL, :type => :feature
|
9
9
|
config.include Capybara::RSpecMatchers, :type => :feature
|
10
|
+
|
11
|
+
# A work-around to support accessing the current example that works in both
|
12
|
+
# RSpec 2 and RSpec 3.
|
13
|
+
fetch_current_example = RSpec.respond_to?(:current_example) ?
|
14
|
+
proc { RSpec.current_example } : proc { |context| context.example }
|
15
|
+
|
10
16
|
# The before and after blocks must run instantaneously, because Capybara
|
11
17
|
# might not actually be used in all examples where it's included.
|
12
18
|
config.after do
|
@@ -17,6 +23,7 @@ RSpec.configure do |config|
|
|
17
23
|
end
|
18
24
|
config.before do
|
19
25
|
if self.class.include?(Capybara::DSL)
|
26
|
+
example = fetch_current_example.call(self)
|
20
27
|
Capybara.current_driver = Capybara.javascript_driver if example.metadata[:js]
|
21
28
|
Capybara.current_driver = example.metadata[:driver] if example.metadata[:driver]
|
22
29
|
end
|
@@ -7,11 +7,13 @@ module Capybara
|
|
7
7
|
alias :xscenario :xit
|
8
8
|
alias :given :let
|
9
9
|
alias :given! :let!
|
10
|
+
alias :feature :describe
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
16
|
+
|
15
17
|
def self.feature(*args, &block)
|
16
18
|
options = if args.last.is_a?(Hash) then args.pop else {} end
|
17
19
|
options[:capybara_feature] = true
|
@@ -126,20 +126,20 @@ module Capybara
|
|
126
126
|
HaveSelector.new(:link, locator, options)
|
127
127
|
end
|
128
128
|
|
129
|
-
def have_button(locator)
|
130
|
-
HaveSelector.new(:button, locator)
|
129
|
+
def have_button(locator, options={})
|
130
|
+
HaveSelector.new(:button, locator, options)
|
131
131
|
end
|
132
132
|
|
133
133
|
def have_field(locator, options={})
|
134
134
|
HaveSelector.new(:field, locator, options)
|
135
135
|
end
|
136
136
|
|
137
|
-
def have_checked_field(locator)
|
138
|
-
HaveSelector.new(:field, locator, :checked => true)
|
137
|
+
def have_checked_field(locator, options={})
|
138
|
+
HaveSelector.new(:field, locator, options.merge(:checked => true))
|
139
139
|
end
|
140
140
|
|
141
|
-
def have_unchecked_field(locator)
|
142
|
-
HaveSelector.new(:field, locator, :unchecked => true)
|
141
|
+
def have_unchecked_field(locator, options={})
|
142
|
+
HaveSelector.new(:field, locator, options.merge(:unchecked => true))
|
143
143
|
end
|
144
144
|
|
145
145
|
def have_select(locator, options={})
|
data/lib/capybara/selector.rb
CHANGED
@@ -103,7 +103,7 @@ Capybara.add_selector(:field) do
|
|
103
103
|
filter(:checked) { |node, value| not(value ^ node.checked?) }
|
104
104
|
filter(:unchecked) { |node, value| (value ^ node.checked?) }
|
105
105
|
filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
|
106
|
-
filter(:with) { |node, with| node.value == with }
|
106
|
+
filter(:with) { |node, with| node.value == with.to_s }
|
107
107
|
filter(:type) do |node, type|
|
108
108
|
if ['textarea', 'select'].include?(type)
|
109
109
|
node.tag_name == type
|
@@ -146,6 +146,7 @@ Capybara.add_selector(:radio_button) do
|
|
146
146
|
xpath { |locator| XPath::HTML.radio_button(locator) }
|
147
147
|
filter(:checked) { |node, value| not(value ^ node.checked?) }
|
148
148
|
filter(:unchecked) { |node, value| (value ^ node.checked?) }
|
149
|
+
filter(:option) { |node, value| node.value == value.to_s }
|
149
150
|
filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
|
150
151
|
end
|
151
152
|
|
@@ -153,6 +154,7 @@ Capybara.add_selector(:checkbox) do
|
|
153
154
|
xpath { |locator| XPath::HTML.checkbox(locator) }
|
154
155
|
filter(:checked) { |node, value| not(value ^ node.checked?) }
|
155
156
|
filter(:unchecked) { |node, value| (value ^ node.checked?) }
|
157
|
+
filter(:option) { |node, value| node.value == value.to_s }
|
156
158
|
filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
|
157
159
|
end
|
158
160
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
1
3
|
class Capybara::Selenium::Driver < Capybara::Driver::Base
|
2
4
|
DEFAULT_OPTIONS = {
|
3
5
|
:browser => :firefox
|
@@ -43,6 +45,14 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
43
45
|
browser.navigate.to(path)
|
44
46
|
end
|
45
47
|
|
48
|
+
def go_back
|
49
|
+
browser.navigate.back
|
50
|
+
end
|
51
|
+
|
52
|
+
def go_forward
|
53
|
+
browser.navigate.forward
|
54
|
+
end
|
55
|
+
|
46
56
|
def html
|
47
57
|
browser.page_source
|
48
58
|
end
|
@@ -87,7 +97,9 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
87
97
|
# to about:blank, so we rescue this error and do nothing
|
88
98
|
# instead.
|
89
99
|
end
|
90
|
-
|
100
|
+
uri = URI(Capybara::EMPTY_HTML_FILE_PATH)
|
101
|
+
uri.scheme = "file"
|
102
|
+
@browser.navigate.to(uri.to_s)
|
91
103
|
end
|
92
104
|
end
|
93
105
|
|
@@ -137,7 +149,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
137
149
|
end
|
138
150
|
|
139
151
|
def quit
|
140
|
-
@browser.quit
|
152
|
+
@browser.quit if @browser
|
141
153
|
rescue Errno::ECONNREFUSED
|
142
154
|
# Browser must have already gone
|
143
155
|
end
|
@@ -37,8 +37,21 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
37
37
|
path_names = value.to_s.empty? ? [] : value
|
38
38
|
native.send_keys(*path_names)
|
39
39
|
elsif tag_name == 'textarea' or tag_name == 'input'
|
40
|
-
|
41
|
-
|
40
|
+
if value.to_s.empty?
|
41
|
+
native.clear
|
42
|
+
else
|
43
|
+
#script can change a readonly element which user input cannot, so dont execute if readonly
|
44
|
+
driver.browser.execute_script "arguments[0].value = ''", native unless self[:readonly]
|
45
|
+
native.send_keys(value.to_s)
|
46
|
+
end
|
47
|
+
elsif native.attribute('isContentEditable')
|
48
|
+
#ensure we are focused on the element
|
49
|
+
script = <<-JS
|
50
|
+
var range = document.createRange();
|
51
|
+
range.selectNodeContents(arguments[0]);
|
52
|
+
window.getSelection().addRange(range);
|
53
|
+
JS
|
54
|
+
driver.browser.execute_script script, native
|
42
55
|
native.send_keys(value.to_s)
|
43
56
|
end
|
44
57
|
end
|
@@ -61,7 +74,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
61
74
|
def hover
|
62
75
|
driver.browser.action.move_to(native).perform
|
63
76
|
end
|
64
|
-
|
77
|
+
|
65
78
|
def drag_to(element)
|
66
79
|
driver.browser.action.drag_and_drop(native, element.native).perform
|
67
80
|
end
|
@@ -89,11 +102,11 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
89
102
|
def find_xpath(locator)
|
90
103
|
native.find_elements(:xpath, locator).map { |n| self.class.new(driver, n) }
|
91
104
|
end
|
92
|
-
|
105
|
+
|
93
106
|
def find_css(locator)
|
94
107
|
native.find_elements(:css, locator).map { |n| self.class.new(driver, n) }
|
95
108
|
end
|
96
|
-
|
109
|
+
|
97
110
|
def ==(other)
|
98
111
|
native == other.native
|
99
112
|
end
|
data/lib/capybara/server.rb
CHANGED
@@ -31,13 +31,13 @@ module Capybara
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
attr_reader :app, :port
|
34
|
+
attr_reader :app, :port, :host
|
35
35
|
|
36
|
-
def initialize(app, port=Capybara.server_port)
|
36
|
+
def initialize(app, port=Capybara.server_port, host=Capybara.server_host)
|
37
37
|
@app = app
|
38
38
|
@middleware = Middleware.new(@app)
|
39
39
|
@server_thread = nil # supress warnings
|
40
|
-
@port = port
|
40
|
+
@host, @port = host, port
|
41
41
|
@port ||= Capybara::Server.ports[@app.object_id]
|
42
42
|
@port ||= find_available_port
|
43
43
|
end
|
@@ -50,10 +50,6 @@ module Capybara
|
|
50
50
|
@middleware.error
|
51
51
|
end
|
52
52
|
|
53
|
-
def host
|
54
|
-
Capybara.server_host || "127.0.0.1"
|
55
|
-
end
|
56
|
-
|
57
53
|
def responsive?
|
58
54
|
return false if @server_thread && @server_thread.join(0)
|
59
55
|
|
data/lib/capybara/session.rb
CHANGED
@@ -37,10 +37,11 @@ module Capybara
|
|
37
37
|
:has_no_unchecked_field?, :query, :assert_selector, :assert_no_selector
|
38
38
|
]
|
39
39
|
SESSION_METHODS = [
|
40
|
-
:body, :html, :
|
41
|
-
:
|
42
|
-
:
|
43
|
-
:
|
40
|
+
:body, :html, :source, :current_url, :current_host, :current_path,
|
41
|
+
:execute_script, :evaluate_script, :visit, :go_back, :go_forward,
|
42
|
+
:within, :within_fieldset, :within_table, :within_frame, :within_window,
|
43
|
+
:save_page, :save_and_open_page, :save_screenshot,
|
44
|
+
:reset_session!, :response_headers, :status_code,
|
44
45
|
:title, :has_title?, :has_no_title?, :current_scope
|
45
46
|
]
|
46
47
|
DSL_METHODS = NODE_METHODS + SESSION_METHODS
|
@@ -74,8 +75,11 @@ module Capybara
|
|
74
75
|
# Reset the session, removing all cookies.
|
75
76
|
#
|
76
77
|
def reset!
|
77
|
-
|
78
|
-
|
78
|
+
if @touched
|
79
|
+
driver.reset!
|
80
|
+
@touched = false
|
81
|
+
assert_no_selector :xpath, "/html/body/*"
|
82
|
+
end
|
79
83
|
raise @server.error if Capybara.raise_server_errors and @server and @server.error
|
80
84
|
ensure
|
81
85
|
@server.reset_error! if @server
|
@@ -195,27 +199,54 @@ module Capybara
|
|
195
199
|
|
196
200
|
##
|
197
201
|
#
|
198
|
-
#
|
199
|
-
#
|
202
|
+
# Move back a single entry in the browser's history.
|
203
|
+
#
|
204
|
+
def go_back
|
205
|
+
driver.go_back
|
206
|
+
end
|
207
|
+
|
208
|
+
##
|
209
|
+
#
|
210
|
+
# Move forward a single entry in the browser's history.
|
211
|
+
#
|
212
|
+
def go_forward
|
213
|
+
driver.go_forward
|
214
|
+
end
|
215
|
+
|
216
|
+
##
|
217
|
+
#
|
218
|
+
# Executes the given block within the context of a node. `within` takes the
|
219
|
+
# same options as `find`, as well as a block. For the duration of the
|
220
|
+
# block, any command to Capybara will be handled as though it were scoped
|
221
|
+
# to the given element.
|
200
222
|
#
|
201
223
|
# within(:xpath, '//div[@id="delivery-address"]') do
|
202
224
|
# fill_in('Street', :with => '12 Main Street')
|
203
225
|
# end
|
204
226
|
#
|
205
|
-
#
|
206
|
-
#
|
227
|
+
# Just as with `find`, if multiple elements match the selector given to
|
228
|
+
# `within`, an error will be raised, and just as with `find`, this
|
229
|
+
# behaviour can be controlled through the `:match` and `:exact` options.
|
230
|
+
#
|
231
|
+
# It is possible to omit the first parameter, in that case, the selector is
|
232
|
+
# assumed to be of the type set in Capybara.default_selector.
|
207
233
|
#
|
208
234
|
# within('div#delivery-address') do
|
209
235
|
# fill_in('Street', :with => '12 Main Street')
|
210
236
|
# end
|
211
237
|
#
|
238
|
+
# Note that a lot of uses of `within` can be replaced more succinctly with
|
239
|
+
# chaining:
|
240
|
+
#
|
241
|
+
# find('div#delivery-address').fill_in('Street', :with => '12 Main Street')
|
242
|
+
#
|
212
243
|
# @overload within(*find_args)
|
213
244
|
# @param (see Capybara::Node::Finders#all)
|
214
245
|
#
|
215
246
|
# @overload within(a_node)
|
216
247
|
# @param [Capybara::Node::Base] a_node The node in whose scope the block should be evaluated
|
217
248
|
#
|
218
|
-
# @raise [Capybara::ElementNotFound]
|
249
|
+
# @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
|
219
250
|
#
|
220
251
|
def within(*args)
|
221
252
|
new_scope = if args.first.is_a?(Capybara::Node::Base) then args.first else find(*args) end
|
@@ -262,9 +293,12 @@ module Capybara
|
|
262
293
|
# @param [String] name name of a frame
|
263
294
|
#
|
264
295
|
def within_frame(frame_handle)
|
296
|
+
scopes.push(nil)
|
265
297
|
driver.within_frame(frame_handle) do
|
266
298
|
yield
|
267
299
|
end
|
300
|
+
ensure
|
301
|
+
scopes.pop
|
268
302
|
end
|
269
303
|
|
270
304
|
##
|
@@ -275,7 +309,10 @@ module Capybara
|
|
275
309
|
# @param [String] handle of the window
|
276
310
|
#
|
277
311
|
def within_window(handle, &blk)
|
312
|
+
scopes.push(nil)
|
278
313
|
driver.within_window(handle, &blk)
|
314
|
+
ensure
|
315
|
+
scopes.pop
|
279
316
|
end
|
280
317
|
|
281
318
|
##
|
@@ -313,7 +350,7 @@ module Capybara
|
|
313
350
|
#
|
314
351
|
def save_page(path=nil)
|
315
352
|
path ||= "capybara-#{Time.new.strftime("%Y%m%d%H%M%S")}#{rand(10**10)}.html"
|
316
|
-
path = File.expand_path(path, Capybara.save_and_open_page_path)
|
353
|
+
path = File.expand_path(path, Capybara.save_and_open_page_path)
|
317
354
|
|
318
355
|
FileUtils.mkdir_p(File.dirname(path))
|
319
356
|
|
@@ -328,10 +365,15 @@ module Capybara
|
|
328
365
|
# @param [String] file_name The path to where it should be saved [optional]
|
329
366
|
#
|
330
367
|
def save_and_open_page(file_name=nil)
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
368
|
+
file_name = save_page(file_name)
|
369
|
+
|
370
|
+
begin
|
371
|
+
require "launchy"
|
372
|
+
Launchy.open(file_name)
|
373
|
+
rescue LoadError
|
374
|
+
warn "Page saved to #{file_name} with save_and_open_page."
|
375
|
+
warn "Please install the launchy gem to open page automatically."
|
376
|
+
end
|
335
377
|
end
|
336
378
|
|
337
379
|
##
|
@@ -382,7 +424,7 @@ module Capybara
|
|
382
424
|
end
|
383
425
|
|
384
426
|
def current_scope
|
385
|
-
scopes.last
|
427
|
+
scopes.last || document
|
386
428
|
end
|
387
429
|
|
388
430
|
private
|
@@ -35,7 +35,7 @@ $(function() {
|
|
35
35
|
$('body').append('<p id="focus_event_triggered">Focus Event triggered</p>');
|
36
36
|
});
|
37
37
|
$('#with_change_event').change(function() {
|
38
|
-
|
38
|
+
$('body').append($('<p class="change_event_triggered"></p>').text(this.value));
|
39
39
|
});
|
40
40
|
$('#checkbox_with_event').click(function() {
|
41
41
|
$('body').append('<p id="checkbox_event_triggered">Checkbox event triggered</p>');
|