assert_xpath 0.3.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.
@@ -0,0 +1,594 @@
1
+ require 'assert_xpath'
2
+ require 'tempfile'
3
+ require 'cgi'
4
+
5
+ # These assertions soak in Javascript::PurePerl goodness
6
+
7
+ #:stopdoc:
8
+ # ERGO this module should use search
9
+ # ERGO assert_javascript should only use assert_rexml not assert_xml
10
+ # ERGO tell crew to set eblogs' background color correctly...
11
+ # ERGO return value on each method
12
+ # ERGO assert_js_remote_function should take url_for args
13
+ # ERGO move got_pure_perl? rdoc to AssertJavaScript page
14
+ # ERGO clicking in Konsole navigates in Kate
15
+ # ERGO any matcher in an assert_js should search
16
+ #:startdoc:
17
+
18
+ def temporarily(obj, member, new_value)
19
+ old_value = obj.send(member)
20
+
21
+ begin
22
+ obj.send(member.to_s+'=', new_value) # CONSIDER look up the assign thinger?
23
+ yield
24
+ ensure
25
+ obj.send(member.to_s+'=', old_value)
26
+ end
27
+ end # ERGO document!
28
+
29
+ =begin rdoc
30
+ See: http://assertxpath.rubyforge.org/
31
+ %html <pre>
32
+ #\ #.:# +------------------------------------------+
33
+ #\ #.:# ^^^^^ ___/ these assertions use Javascript::PurePerl \
34
+ #\ #.:# }OvO{ <___ to convert JS into XML. assert_xpath can |
35
+ ##.:# {| |} | query this to find important details, |
36
+ #..# \| |/ \ and safely skip over unimportant details! /
37
+ #..# \ _ / +------------------------------------------+
38
+ #..# | |
39
+ #..#>=======d=b=======
40
+ #..#
41
+ %html </pre>
42
+ =end
43
+
44
+ module AssertJavaScript
45
+ include AssertXPath
46
+
47
+ # %html <a name='assert_javascript'></a>
48
+ # Wraps <tt>Javascript::PurePerl</tt> to convert JavaScript into XML describing
49
+ # each lexeme. This allows subsequent +assert_xpath+ calls to read the JavaScript.
50
+ #
51
+ # See {Javascript::PurePerl for Ruby Enthusiasts}[http://phlip.eblogs.com/2007/07/28/javascriptpureperl-for-ruby-enthusiasts/]
52
+ # to learn to install Javascript::PurePerl
53
+ # * +source+ - optional string containing JavaScript. The default is
54
+ # <tt>@response.body</tt>
55
+ # * +diagnostic+ - optional string to add to failure message
56
+ # Other +assert_js_+* methods call +assert_javascript+ if the secret
57
+ # <tt>@xdoc</tt> instance variable is +nil+
58
+ #
59
+ # To extract JS from an XHTML page, place the +assert_js+ inside the +assert_xpath+
60
+ # which located your JavaScript. For example:
61
+ # assert_xpath './/input[ @type = "image" ]' do |input|
62
+ # assert_javascript input.onclick
63
+ # assert_xpath './/Statement[2]' do
64
+ # assert_js_remote_function '/user/inventory/' do
65
+ # json = assert_js_argument(2)
66
+ # params = assert_params('uri?' + json[:parameters]).last
67
+ # assert_equal @user.id.to_s, params[:user_id]
68
+ # assert_equal @user.inventory.first.id.to_s, params[:inventory_id]
69
+ # end
70
+ # end
71
+ # end
72
+ # assert_tag_id :div, :inventory_bar # <-- applies to original XML
73
+ #
74
+ # See: AssertJavaScriptTest#test_assert_javascript
75
+ #
76
+ def assert_javascript(source = nil, diagnostic = nil)
77
+ javascript_to_xml(source, diagnostic) do |tmp_error|
78
+ @xdoc = assert_xpath(
79
+ '/AST/Program/SourceElements',
80
+ build_message(
81
+ diagnostic,
82
+ "JavaScript <#{source}> contained errors:\n#{File.read(tmp_error) rescue nil}"
83
+ )
84
+ )
85
+ end # ERGO if any diagnostic is a lambda, lazily evaluate it
86
+ end
87
+ alias assert_js assert_javascript
88
+
89
+ def assert_javascript_too(&block) #:nodoc: # ERGO merge with assert_javascript!!!
90
+ @xdoc or assert_javascript
91
+
92
+ sit_and_spin = './/VariableDeclaration/' +
93
+ 'Identifier[ @name = "Identifier" ]/../' +
94
+ 'Initializer[ @name = "Initializer" ]/' +
95
+ 'Number[ @name = "AssignmentExpression" ]/../..'
96
+ stuff = {}
97
+
98
+ @xdoc.each_element(sit_and_spin) do |node|
99
+ name = REXML::XPath.first(node, './/Identifier').text
100
+ number = REXML::XPath.first(node, './/Number').text
101
+ stuff[name.to_sym] = number.to_f
102
+ # ERGO is to_f best?
103
+ # ERGO simpler way to add an ostruct member?
104
+ # ERGO cleaner XPath...
105
+ end
106
+
107
+ js = OpenStruct.new(stuff)
108
+ block.call(js)
109
+ end
110
+
111
+ # ERGO what is "id('content')/div[1]/" ? Can we do that?
112
+ # ERGO "put the subtle to the metal..."
113
+
114
+ # Negates +assert_javascript+. Inexplicably passes if a string does not contain
115
+ # anything which satisfies <tt>Javascript::PurePerl</tt>'s narrow definition of
116
+ # JavaScript. Depends on +assert_javascript+
117
+ #
118
+ # * +source+ - optional string that should not contain JavaScript. The default is @response.body
119
+ # * +diagnostic+ - optional string to add to failure message
120
+ #
121
+ # Note that "var = 2" will pass, (and fail in +assert_javascript+)
122
+ # because <tt>Javascript::PurePerl</tt> requires a trailing <tt>;</tt>
123
+ #
124
+ # Example:
125
+ # The author would be interested to hear if anyone finds a use for this
126
+ #
127
+ def deny_javascript(source = nil, diagnostic = nil)
128
+ javascript_to_xml(source, diagnostic) do
129
+ stash_xdoc do
130
+ deny_xpath( '/AST/Program/SourceElements',
131
+ build_message(
132
+ diagnostic,
133
+ "string <#{_esc source}> should not be well-formed JavaScript"
134
+ )
135
+ )
136
+ end
137
+ end
138
+ end
139
+ alias deny_js deny_javascript
140
+
141
+ unless respond_to? :returning
142
+ def returning(value) #:nodoc:
143
+ yield(value)
144
+ return value
145
+ end
146
+ end
147
+
148
+ # ERGO "if that Congress still can't budge
149
+ # daddy's in tight with a Supreme Court judge"
150
+
151
+ # %html <a name='assert_json'></a>
152
+ # Converts a REXML::Element into a Hash containing named elements. Currently
153
+ # we only support Booleans or Strings
154
+ #
155
+ # This depend on methods like assert_js_argument to locate a JSON argument.
156
+ # Otherwise, the method searches for the first JSON it finds in
157
+ # the current context
158
+ # * +jsonic+ - the input REXML::Element node - defaults to the secret <tt>@xdoc</tt>
159
+ # variable set by +assert_javascript+.
160
+ #
161
+ # Example:
162
+ #
163
+ # %transclude AssertJavaScriptTest#test_assert_json
164
+ #
165
+ def assert_json(jsonic = @xdoc || assert_javascript, diagnostic = nil)
166
+ @xdoc or assert_javascript
167
+
168
+ returning Hash.new do |json|
169
+ stash_xdoc do
170
+ @xdoc = jsonic
171
+ assert_any_xpath 'descendant-or-self::PropertyNameAndValueList/PropertyPair', diagnostic
172
+ end # ERGO use assert_any_xpath
173
+
174
+ jsonic.each_element('descendant-or-self::PropertyNameAndValueList/PropertyPair') do |node|
175
+ name = REXML::XPath.first(node, 'PropertyName/*')
176
+ name = name.text.to_sym
177
+
178
+ json[name] =
179
+ case
180
+ when b = REXML::XPath.first(node, 'ObjectLiteral/PropertyNameAndValueList')
181
+ assert_json(b)
182
+
183
+ when b = REXML::XPath.first(node, 'Boolean')
184
+ b.text == '1'
185
+
186
+ when b = REXML::XPath.first(node, 'Number')
187
+ b.text
188
+
189
+ # ERGO how to do an or in an XPath??
190
+
191
+ when b = REXML::XPath.first(node, 'String')
192
+ b.text
193
+
194
+ when b = REXML::XPath.first(node, 'Identifier')
195
+ b.text # ERGO test me!
196
+
197
+ when b = REXML::XPath.first(node, 'ArrayLiteral/ElementList')
198
+ # ERGO recurse here
199
+ returning [] do |list|
200
+ b.each_element('*') do |item|
201
+ list << item.text # ERGO more accurate
202
+ end
203
+ end
204
+
205
+ else
206
+ # ERGO handle embedded JS expressons!
207
+ #puts indent_xml(node)
208
+ # ERGO flunk "Add type #{node.children.last.name} to assert_json!"
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ # Explores a function call's argument list
215
+ # * +index+ - 1-based index into the arguments
216
+ # * +diagnostic+ - optional string to add to failure message
217
+ #
218
+ # Example: AssertJavaScriptTest#test_assert_js_argument
219
+ #
220
+ def assert_js_argument(index = 1, diagnostic = nil)
221
+ @xdoc or assert_javascript
222
+ # ERGO rdoc: what do we depend on?
223
+ # ERGO propagate xpathic 'descendant-or-self'
224
+
225
+ assert_xpath xpath_argument(index), diagnostic do |node|
226
+ return node.text if %w(String Identifier).include?(node.name)
227
+ return assert_json if node.name == 'ObjectLiteral'
228
+ end # ERGO test we slip not into a nested argument list
229
+ end
230
+
231
+ def deny_js_argument(index = 1, diagnostic = nil)
232
+ @xdoc or assert_javascript
233
+ # ERGO rdoc: what do we depend on?
234
+ deny_xpath xpath_argument(index), diagnostic
235
+ end
236
+
237
+ # Not ready for public use yet!
238
+ #
239
+ def assert_params(path_query)
240
+ path, query = path_query.split('?')
241
+ params = {}
242
+
243
+ if query
244
+ query.split('&').each do |item|
245
+ key, value = item.split('=')
246
+ params[key.to_sym] = CGI::unescape(value)
247
+ end
248
+ end
249
+
250
+ return [path, params]
251
+ end # ERGO get this working on incomplete URLs
252
+
253
+ # ERGO sick local tests for all this stuff that the MMORPG tests
254
+ # ERGO links out to all this stuff
255
+ # ERGO tag_id -> element_id
256
+
257
+ # Detect the JavaScriptGenerator method +replace_html+
258
+ # * +element_id+ - first argument to +replace_html+ -
259
+ # the target HTML Element's +id+
260
+ # * +matcher+ - optional regular expression to match
261
+ # the raw contents of the second argument to +replace_html+
262
+ # * +diagnostic+ - optional string to add to failure message
263
+ # * +block+ - optional; permits +assert_xpath+ calls
264
+ # into the HTML contents of the replacement
265
+ #
266
+ # Example: AssertJavaScriptTest#test_assert_js_replace_html
267
+ #
268
+ def assert_js_replace_html(element_id, matcher = nil, diagnostic = nil, &block)
269
+ assert_xpath object_method_xpath('Element', 'update', element_id), diagnostic do
270
+ assert_equal element_id.to_s, assert_js_argument(1)
271
+ assert_match matcher, assert_js_argument(2), diagnostic if matcher
272
+ # ERGO fetch argument 2 as yielded xml and match its inner text
273
+ if block
274
+ assert_js_xml(assert_js_argument(2)) # ERGO do this even without the if
275
+ block.call
276
+ end
277
+ end
278
+ end
279
+
280
+ # Negates assert_js_replace_html
281
+ # * +element_id+ - first argument to +replace_html+ -
282
+ # the target HTML Element's +id+
283
+ # * +matcher+ - optional regular expression to not match
284
+ # the raw contents of the second argument to +replace_html+
285
+ # * +diagnostic+ - optional string to add to failure message
286
+ # If the +matcher+ is +nil+ or not provided, the +element_id+
287
+ # must /not/ match any +Element.update+ call in its context.
288
+ # If the +matcher+ /is/ provided, the +element_id+ /must/
289
+ # match, and the +matcher+ must /not/ agree with its contents
290
+ #
291
+ # Example: AssertJavaScriptTest#test_deny_js_replace_html
292
+ #
293
+ def deny_js_replace_html(element_id, matcher = nil, diagnostic = nil)
294
+ path = object_method_xpath('Element', 'update', element_id)
295
+
296
+ # ERGO don't let subsequent updates with the same element_id confuse the matcher!
297
+
298
+ if matcher and node = REXML::XPath.first(@xdoc, path)
299
+ stash_xdoc do
300
+ @xdoc = node
301
+ assert_no_match matcher, assert_js_argument(2), diagnostic
302
+ return # ERGO pass node for 1st arg to assert_js_argument
303
+ end
304
+ end
305
+
306
+ deny_xpath path, diagnostic
307
+ end
308
+
309
+ # ERGO this line should not crash on bad page:
310
+ # yar = YarWiki.new(params[:id].to_s)
311
+ # ERGO provide link for Flash if you have none
312
+
313
+ # Detects an Insertion attack.
314
+ # * +orientation+ - <tt>:top</tt>, <tt>:bottom</tt>, etc...
315
+ # Remaining arguments similar to assert_js_replace_html
316
+ #
317
+ # Example:
318
+ # %transclude AssertJavaScriptTest#test_assert_js_insert_html
319
+ #
320
+ def assert_js_insert_html(orientation, element_id, matcher = //, diagnostic = nil)
321
+ orientation = orientation.to_s.capitalize
322
+
323
+ # ERGO the matcher should work the same as assert_js_replace_html
324
+
325
+ assert_any_xpath object_method_xpath('Insertion', orientation, element_id), matcher, diagnostic do
326
+ assert_equal element_id.to_s, assert_js_argument(1), diagnostic # note: shouldn't happen!
327
+ assert_js_xml(assert_js_argument(2)) # ERGO good error diagnostic if _this_ fails!
328
+ yield if block_given? # ERGO yield something?
329
+ return @xdoc # ERGO everyone should return their guts like this
330
+ end
331
+ end # ERGO local test for me
332
+
333
+ # Detects an +Ajax.Update+ or +Ajax.Request+ call
334
+ # * +action+ - optional - the HTTP Post action - the first
335
+ # argument to +Ajax.Update+ or +Ajax.Request+
336
+ # * +tag_id+ - any of the following
337
+ # * - <tt>:request</tt> -
338
+ # * - <tt>:update</tt> -
339
+ # * +diagnostic+ - optional string to add to failure message
340
+ #
341
+ # Example:
342
+ #
343
+ def assert_js_remote_function( action = nil,
344
+ tag_id = nil,
345
+ diagnostic = nil )
346
+ matcher = // # ERGO
347
+
348
+ method = if tag_id.class === String or
349
+ tag_id.class == Regexp # ERGO test regexp
350
+ 'Update'
351
+ elsif tag_id == :request # ERGO test this branch
352
+ 'Request'
353
+ elsif tag_id == :update # ERGO test this branch
354
+ 'Update'
355
+ else
356
+ "Request' or . = 'Update"
357
+ end
358
+
359
+ xpath = object_method_xpath('Ajax', method, action)
360
+
361
+ # ERGO search via tag_id!!!
362
+
363
+ assert_any_xpath xpath, //, diagnostic do
364
+ if (tag_id.class != String and tag_id.class != Regexp) or
365
+ /#{tag_id}/ =~ assert_js_argument(2)
366
+ if action
367
+ assert_equal action.to_s.gsub('&amp;', '&'),
368
+ assert_js_argument(1),
369
+ diagnostic
370
+ end
371
+ # ERGO a better system to extract GET parameters
372
+ yield if block_given? # ERGO test this block
373
+ return @xdoc # ERGO test that return
374
+ end
375
+ end # ERGO less confusing error diagnostic if this fails
376
+
377
+ flunk build_message(diagnostic, "expected id <#{tag_id}>")
378
+ end
379
+
380
+ # ERGO test via a javascript-like dsl:
381
+ # assert_javascript do
382
+ # js_if, js_replace_html, etc...
383
+ # var(:x) = 42
384
+ # object(:Render).update :div_name etc.
385
+ # end
386
+
387
+ # ERGO diagnostic
388
+
389
+ # Not ready for public use!
390
+ #
391
+ def assert_js_xml(q) # ERGO explain this beast; hide inside assert_js_*
392
+ xml = eval('"' + q + '"')
393
+ assert_xml xml # ERGO what are the errors if these fail?
394
+ end # ERGO test the error recoverer in assert_xml
395
+
396
+ # Not ready for public use!
397
+ #
398
+ # Example:
399
+ #
400
+ def assert_js_call(*args, &block)
401
+ path, arg1, arg2, diagnostic = *calling_js_path(args)
402
+ assert_xpath path, diagnostic, &block
403
+ end # ERGO local tests for all these
404
+
405
+ #
406
+ # * ++ -
407
+ # * +diagnostic+ - optional string to add to failure message
408
+ #
409
+ # Example:
410
+ #
411
+ def deny_js_call(*args)
412
+ path, arg1, arg2, diagnostic = calling_js_path(args)
413
+ # ERGO do something with arg1 and arg2
414
+ deny_xpath path, diagnostic
415
+ end
416
+
417
+ # %html <a name='assert_js_show'></a>
418
+ # Detects the Element.show or Element.hide commands
419
+ # * +element_id+ - HTML Element +id+ to target
420
+ # * +visibility+ - defaults to :show; optionally :hide
421
+ #
422
+ def assert_js_show(element_id, visibility = :show) # ERGO also deny, also diagnostic
423
+ assert_xpath object_method_xpath(:Element, visibility, element_id) do
424
+ assert_equal element_id.to_s, assert_js_argument(1)
425
+ end
426
+ # ERGO what if the visibility is wrong?
427
+ end # ERGO deny for every assert
428
+
429
+ # Alias for assert_js_show(element_id, :hide)
430
+ #
431
+ def assert_js_hide(element_id) assert_js_show(element_id, :hide) end
432
+
433
+ # Find a JavaScript if statement, or one of its elements
434
+ # * +*args+ - ERGO!
435
+ # * +diagnostic+ - optional string to add to failure message
436
+ #
437
+ # Example:
438
+ #
439
+ def assert_js_if(*args, &block)
440
+ return assert_any_xpath(*_re_arg_if(*args), &block)
441
+ end
442
+
443
+ # Fails if a JavaScript +if+ expression or block has the given characteristics
444
+ # * +*args+ - ERGO!
445
+ # * +diagnostic+ - optional string to add to failure message
446
+ #
447
+ # Example:
448
+ #
449
+ def deny_js_if(condition = :all, matcher = nil, diagnostic = nil)
450
+ xpath, matcher, diagnostic = _re_arg_if(condition, matcher, diagnostic)
451
+ return deny_xpath(xpath, diagnostic) unless matcher
452
+ return deny_any_xpath(xpath, matcher, diagnostic)
453
+ # ERGO flunk here - with param explanation (if humanly possible!)
454
+ end # ERGO assert_any_xpath should take an explicit block
455
+
456
+ # ERGO extract-method the x[@name=y] stuff!
457
+
458
+ # ERGO test nesting if statements to require this temporarily
459
+ # ERGO rdoc should provide external-able links for important methods
460
+
461
+ private # ERGO assert_js.js_like_ruby_statements_as_dsl
462
+
463
+ def javascript_to_xml(source, diagnostic)
464
+ source ||= default_js_source(diagnostic)
465
+ here = File.dirname(__FILE__)
466
+ jsToXml_pl = File.join(here, 'jsToXml.pl')
467
+
468
+ Tempfile.open('assert_javascript_sample') do |sample|
469
+ Tempfile.open('assert_javascript_error') do |error|
470
+ sample.write(source)
471
+ sample.flush
472
+ got = `perl "#{jsToXml_pl}" "#{sample.path}" 2>#{error.path}`
473
+ assert_xml got
474
+ yield(error.path)
475
+ end
476
+ end
477
+ end # ERGO use @xdoc = assert_xpath in certain other places
478
+
479
+ def default_js_source(diagnostic = nil)
480
+ @response.respond_to?(:body) || flunk(build_message(diagnostic, "no @response.body found - use assert_js(my_js)"))
481
+ return @response.body.relevance || flunk(build_message(diagnostic, "no JavaScript found in @response.body"))
482
+ end # ERGO publish relevance
483
+
484
+ # CONSIDER permit symbols for both element_id and matcher
485
+
486
+ def _re_arg_if(condition = :all, matcher = nil, diagnostic = nil)
487
+ @xdoc or assert_javascript
488
+
489
+ matcher = condition.respond_to?(:match) ? condition : matcher
490
+ aspect = :all
491
+ aspect = condition if [:condition, :all, :true, :false].include?(condition)
492
+
493
+ xpath = './/IfStatement[ @name = "IfStatement" ]' +
494
+ if aspect == :condition
495
+ '/Expression[ @name = "Expression" ]'
496
+ elsif aspect == :all
497
+ '/..' # CONSIDER why is this here??
498
+ else
499
+ "/Statement[ @name='#{aspect}' ]"
500
+ end
501
+
502
+ # ERGO flunk here - with param explanation (if humanly possible!)
503
+
504
+ return [xpath, matcher, diagnostic]
505
+ end
506
+
507
+ def yield_xdoc_block(xdoc, aspect, matcher, diagnostic, &block)
508
+ @if_condition = @xdoc.parent.parent if aspect == :condition
509
+ yield(@xdoc = xdoc) if block_given?
510
+ assert_match matcher, @xdoc.inner_text, diagnostic if matcher
511
+ return @xdoc
512
+ end # ERGO better name and/or tighter abstraction
513
+
514
+ def xpath_argument(index)
515
+ return "descendant-or-self::ArgumentList/*[position() = #{index}]"
516
+ end
517
+
518
+ # ERGO patch test:recent to run tests with same names as
519
+ # edited code - hook SVN diff to do it!
520
+
521
+ def object_method_xpath(object, method, element_id)
522
+ @xdoc or assert_javascript
523
+ # ERGO: element_id may be nil. Propagate this
524
+ # ERGO: element_id -> first_argument
525
+
526
+ return ".//ArgumentList[ @name = 'Arguments' ]/" +
527
+ (element_id ? "String[ . = '#{element_id}' ]/../" : '') +
528
+ '../' + member_expression(object, method)
529
+ end # ERGO quanti-friably
530
+
531
+ def member_expression(object, method)
532
+ return "MemberMemberExpression[ @name = 'Callee' or
533
+ @name = 'ClassExpression' ]" + # ERGO productively reconcile them two
534
+ "/Identifier[ position() = 1 and
535
+ @name = 'lhs' and
536
+ . = '#{ object }' ]" +
537
+ "/../Identifier[ position() = 2 and
538
+ @name = 'member' and
539
+ . = '#{ method }' ]" +
540
+ "/../../ArgumentList[ @name = 'Arguments' ]"
541
+ end
542
+
543
+ def calling_js_path(args)
544
+ @xdoc or assert_javascript
545
+ object, method = args.first.to_s.split('.')
546
+ element_id, diagnostic = [args[1], args[2]]
547
+
548
+ # ERGO this should also work without element_id
549
+ # ERGO local test that this calls its blocks correctly
550
+
551
+ if object and method and element_id
552
+ return object_method_xpath(object, method, element_id), *args
553
+ elsif object and method # ERGO document - pass nil for element_id if you need a diagnostic
554
+ # ERGO descendent or self?
555
+ return './/' + member_expression(object, method), *args
556
+ else
557
+ return ".//Identifier[ @name = 'Callee' and . = '#{args.shift}' ]" +
558
+ '/following-sibling::ArgumentList', *args
559
+ end
560
+ end
561
+
562
+ end
563
+
564
+ # ERGO: Don't put all your eggs in one basket before they're hatched.
565
+
566
+ # Detect if your kit is complete, and detect if you have <tt>Javascript::PurePerl</tt>.
567
+ # If you don't, we warn one time. Use this method to defend portable tests that
568
+ # should not break on computers without Javascript::PurePerl
569
+ #
570
+ # Example:
571
+ # if RAILS_ENV == 'test' and got_pure_perl?
572
+ # class AssertJavaScriptTest < Test::Unit::TestCase
573
+ # # ...
574
+ # end
575
+ # end
576
+ #
577
+ def got_pure_perl?
578
+ perl_version = `perl -v 2>&1` rescue 'perl, v0'
579
+ perl_version =~ /perl, v(\d+)/
580
+
581
+ if $1.to_i < 5
582
+ puts "\ninsufficient Perl for assert_js!" unless $already_warned_about_missing_pure_perl
583
+ $already_warned_about_missing_pure_perl = true
584
+ return false
585
+ end
586
+
587
+ unless system('perl -e "use Javascript::PurePerl" 2>/dev/null')
588
+ puts "\ninstall Javascript::PurePerl for best results!" unless $already_warned_about_missing_pure_perl
589
+ $already_warned_about_missing_pure_perl = true
590
+ return false
591
+ end
592
+
593
+ return true
594
+ end