edgecase-jasmine 1.0.1.1
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.
- data/README.markdown +45 -0
- data/bin/jasmine +6 -0
- data/generators/jasmine/jasmine_generator.rb +30 -0
- data/generators/jasmine/templates/INSTALL +9 -0
- data/generators/jasmine/templates/jasmine-example/SpecRunner.html +27 -0
- data/generators/jasmine/templates/jasmine-example/spec/PlayerSpec.js +58 -0
- data/generators/jasmine/templates/jasmine-example/spec/SpecHelper.js +9 -0
- data/generators/jasmine/templates/jasmine-example/src/Player.js +22 -0
- data/generators/jasmine/templates/jasmine-example/src/Song.js +7 -0
- data/generators/jasmine/templates/lib/tasks/jasmine.rake +2 -0
- data/generators/jasmine/templates/spec/javascripts/support/jasmine-rails.yml +81 -0
- data/generators/jasmine/templates/spec/javascripts/support/jasmine.yml +73 -0
- data/generators/jasmine/templates/spec/javascripts/support/jasmine_runner.rb +21 -0
- data/jasmine/example/SpecRunner.html +27 -0
- data/jasmine/lib/jasmine-html.js +188 -0
- data/jasmine/lib/jasmine.css +166 -0
- data/jasmine/lib/jasmine.js +2421 -0
- data/jasmine/lib/json2.js +478 -0
- data/lib/jasmine.rb +8 -0
- data/lib/jasmine/base.rb +59 -0
- data/lib/jasmine/command_line_tool.rb +68 -0
- data/lib/jasmine/config.rb +186 -0
- data/lib/jasmine/run.html.erb +43 -0
- data/lib/jasmine/selenium_driver.rb +44 -0
- data/lib/jasmine/server.rb +120 -0
- data/lib/jasmine/spec_builder.rb +152 -0
- data/lib/jasmine/tasks/jasmine.rake +31 -0
- data/spec/config_spec.rb +259 -0
- data/spec/jasmine_command_line_tool_spec.rb +26 -0
- data/spec/jasmine_self_test_config.rb +15 -0
- data/spec/jasmine_self_test_spec.rb +18 -0
- data/spec/rails_generator_spec.rb +27 -0
- data/spec/server_spec.rb +82 -0
- data/spec/spec_helper.rb +40 -0
- metadata +204 -0
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
|
3
|
+
module Jasmine
|
4
|
+
class SpecBuilder
|
5
|
+
attr_accessor :suites
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
@spec_files = config.spec_files
|
10
|
+
@runner = config
|
11
|
+
@spec_ids = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
guess_example_locations
|
16
|
+
|
17
|
+
@runner.start
|
18
|
+
load_suite_info
|
19
|
+
wait_for_suites_to_finish_running
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop
|
23
|
+
@runner.stop
|
24
|
+
end
|
25
|
+
|
26
|
+
def script_path
|
27
|
+
File.expand_path(__FILE__)
|
28
|
+
end
|
29
|
+
|
30
|
+
def guess_example_locations
|
31
|
+
@example_locations = {}
|
32
|
+
|
33
|
+
example_name_parts = []
|
34
|
+
previous_indent_level = 0
|
35
|
+
@config.spec_files_full_paths.each do |filename|
|
36
|
+
line_number = 1
|
37
|
+
File.open(filename, "r") do |file|
|
38
|
+
file.readlines.each do |line|
|
39
|
+
match = /^(\s*)(describe|it)\s*\(\s*["'](.*)["']\s*,\s*function/.match(line)
|
40
|
+
if (match)
|
41
|
+
indent_level = match[1].length / 2
|
42
|
+
example_name = match[3]
|
43
|
+
example_name_parts[indent_level] = example_name
|
44
|
+
|
45
|
+
full_example_name = example_name_parts.slice(0, indent_level + 1).join(" ")
|
46
|
+
@example_locations[full_example_name] = "#{filename}:#{line_number}: in `it'"
|
47
|
+
end
|
48
|
+
line_number += 1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def load_suite_info
|
55
|
+
started = Time.now
|
56
|
+
while !eval_js('jsApiReporter && jsApiReporter.started') do
|
57
|
+
raise "couldn't connect to Jasmine after 60 seconds" if (started + 60 < Time.now)
|
58
|
+
sleep 0.1
|
59
|
+
end
|
60
|
+
|
61
|
+
@suites = eval_js("var result = jsApiReporter.suites(); if (window.Prototype && Object.toJSON) { Object.toJSON(result) } else { JSON.stringify(result) }")
|
62
|
+
end
|
63
|
+
|
64
|
+
def results_for(spec_id)
|
65
|
+
@spec_results ||= load_results
|
66
|
+
@spec_results[spec_id.to_s]
|
67
|
+
end
|
68
|
+
|
69
|
+
def load_results
|
70
|
+
@spec_results = {}
|
71
|
+
@spec_ids.each_slice(50) do |slice|
|
72
|
+
@spec_results.merge!(eval_js("var result = jsApiReporter.resultsForSpecs(#{MultiJson.encode(slice)}); if (window.Prototype && Object.toJSON) { Object.toJSON(result) } else { JSON.stringify(result) }"))
|
73
|
+
end
|
74
|
+
@spec_results
|
75
|
+
end
|
76
|
+
|
77
|
+
def wait_for_suites_to_finish_running
|
78
|
+
puts "Waiting for suite to finish in browser ..."
|
79
|
+
while !eval_js('jsApiReporter.finished') do
|
80
|
+
sleep 0.1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def declare_suites
|
85
|
+
me = self
|
86
|
+
suites.each do |suite|
|
87
|
+
declare_suite(self, suite)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def declare_suite(parent, suite)
|
92
|
+
me = self
|
93
|
+
parent.describe suite["name"] do
|
94
|
+
suite["children"].each do |suite_or_spec|
|
95
|
+
type = suite_or_spec["type"]
|
96
|
+
if type == "suite"
|
97
|
+
me.declare_suite(self, suite_or_spec)
|
98
|
+
elsif type == "spec"
|
99
|
+
me.declare_spec(self, suite_or_spec)
|
100
|
+
else
|
101
|
+
raise "unknown type #{type} for #{suite_or_spec.inspect}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def declare_spec(parent, spec)
|
108
|
+
me = self
|
109
|
+
example_name = spec["name"]
|
110
|
+
@spec_ids << spec["id"]
|
111
|
+
backtrace = @example_locations[parent.description + " " + example_name]
|
112
|
+
parent.it example_name, {}, backtrace do
|
113
|
+
me.report_spec(spec["id"])
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def report_spec(spec_id)
|
118
|
+
spec_results = results_for(spec_id)
|
119
|
+
out = ""
|
120
|
+
messages = spec_results['messages'].each do |message|
|
121
|
+
case
|
122
|
+
when message["type"] == "log"
|
123
|
+
puts message["text"]
|
124
|
+
puts "\n"
|
125
|
+
else
|
126
|
+
unless message["message"] =~ /^Passed.$/
|
127
|
+
STDERR << message["message"]
|
128
|
+
STDERR << "\n"
|
129
|
+
|
130
|
+
out << message["message"]
|
131
|
+
out << "\n"
|
132
|
+
end
|
133
|
+
|
134
|
+
if !message["passed"] && message["trace"]["stack"]
|
135
|
+
stack_trace = message["trace"]["stack"].gsub(/<br \/>/, "\n").gsub(/<\/?b>/, " ")
|
136
|
+
STDERR << stack_trace.gsub(/\(.*\)@http:\/\/localhost:[0-9]+\/specs\//, "/spec/")
|
137
|
+
STDERR << "\n"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
fail out unless spec_results['result'] == 'passed'
|
143
|
+
puts out unless out.empty?
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def eval_js(js)
|
149
|
+
@runner.eval_js(js)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
namespace :jasmine do
|
2
|
+
task :require do
|
3
|
+
require 'jasmine'
|
4
|
+
end
|
5
|
+
|
6
|
+
desc "Run continuous integration tests"
|
7
|
+
task :ci => "jasmine:require" do
|
8
|
+
require "spec"
|
9
|
+
require 'spec/rake/spectask'
|
10
|
+
|
11
|
+
Spec::Rake::SpecTask.new(:jasmine_continuous_integration_runner) do |t|
|
12
|
+
t.spec_opts = ["--color", "--format", "specdoc"]
|
13
|
+
t.verbose = true
|
14
|
+
t.spec_files = ['spec/javascripts/support/jasmine_runner.rb']
|
15
|
+
end
|
16
|
+
Rake::Task["jasmine_continuous_integration_runner"].invoke
|
17
|
+
end
|
18
|
+
|
19
|
+
task :server => "jasmine:require" do
|
20
|
+
jasmine_config_overrides = 'spec/javascripts/support/jasmine_config.rb'
|
21
|
+
require jasmine_config_overrides if File.exists?(jasmine_config_overrides)
|
22
|
+
|
23
|
+
puts "your tests are here:"
|
24
|
+
puts " http://localhost:8888/"
|
25
|
+
|
26
|
+
Jasmine::Config.new.start_server
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Run specs via server"
|
31
|
+
task :jasmine => ['jasmine:server']
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
|
2
|
+
|
3
|
+
describe Jasmine::Config do
|
4
|
+
|
5
|
+
describe "configuration" do
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
temp_dir_before
|
9
|
+
|
10
|
+
Dir::chdir @tmp
|
11
|
+
`rails rails-project`
|
12
|
+
Dir::chdir 'rails-project'
|
13
|
+
|
14
|
+
FileUtils.cp_r(File.join(@root, 'generators'), 'vendor')
|
15
|
+
|
16
|
+
`./script/generate jasmine`
|
17
|
+
|
18
|
+
Dir::chdir @old_dir
|
19
|
+
|
20
|
+
@rails_dir = "#{@tmp}/rails-project"
|
21
|
+
end
|
22
|
+
|
23
|
+
after(:all) do
|
24
|
+
temp_dir_after
|
25
|
+
end
|
26
|
+
|
27
|
+
before(:each) do
|
28
|
+
@template_dir = File.expand_path(File.join(File.dirname(__FILE__), "../generators/jasmine/templates"))
|
29
|
+
@config = Jasmine::Config.new
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "defaults" do
|
33
|
+
|
34
|
+
it "src_dir uses root when src dir is blank" do
|
35
|
+
@config.stub!(:project_root).and_return('some_project_root')
|
36
|
+
@config.stub!(:simple_config_file).and_return(File.join(@template_dir, 'spec/javascripts/support/jasmine.yml'))
|
37
|
+
YAML.stub!(:load).and_return({'src_dir' => nil})
|
38
|
+
@config.src_dir.should == 'some_project_root'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should use correct default yaml config" do
|
42
|
+
@config.stub!(:project_root).and_return('some_project_root')
|
43
|
+
@config.simple_config_file.should == (File.join('some_project_root', 'spec/javascripts/support/jasmine.yml'))
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
describe "simple_config" do
|
50
|
+
before(:each) do
|
51
|
+
@config.stub!(:src_dir).and_return(File.join(@rails_dir, "."))
|
52
|
+
@config.stub!(:spec_dir).and_return(File.join(@rails_dir, "spec/javascripts"))
|
53
|
+
end
|
54
|
+
|
55
|
+
shared_examples_for "simple_config defaults" do
|
56
|
+
it "should return the correct files and mappings" do
|
57
|
+
@config.src_files.should == []
|
58
|
+
@config.stylesheets.should == []
|
59
|
+
@config.spec_files.should == ['PlayerSpec.js']
|
60
|
+
@config.helpers.should == ['helpers/SpecHelper.js']
|
61
|
+
@config.js_files.should == [
|
62
|
+
'/__spec__/helpers/SpecHelper.js',
|
63
|
+
'/__spec__/PlayerSpec.js',
|
64
|
+
]
|
65
|
+
@config.js_files("PlayerSpec.js").should ==
|
66
|
+
['/__spec__/helpers/SpecHelper.js',
|
67
|
+
'/__spec__/PlayerSpec.js']
|
68
|
+
@config.spec_files_full_paths.should == [
|
69
|
+
File.join(@rails_dir, 'spec/javascripts/PlayerSpec.js'),
|
70
|
+
]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should parse ERB" do
|
75
|
+
@config.stub!(:simple_config_file).and_return(File.expand_path(File.join(File.dirname(__FILE__), 'fixture/jasmine.erb.yml')))
|
76
|
+
Dir.stub!(:glob).and_return do |glob_string|
|
77
|
+
glob_string
|
78
|
+
end
|
79
|
+
@config.src_files.should == [
|
80
|
+
'file0.js',
|
81
|
+
'file1.js',
|
82
|
+
'file2.js',
|
83
|
+
]
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
describe "if sources.yaml not found" do
|
88
|
+
before(:each) do
|
89
|
+
File.stub!(:exist?).and_return(false)
|
90
|
+
end
|
91
|
+
it_should_behave_like "simple_config defaults"
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "if jasmine.yml is empty" do
|
95
|
+
before(:each) do
|
96
|
+
@config.stub!(:simple_config_file).and_return(File.join(@template_dir, 'spec/javascripts/support/jasmine.yml'))
|
97
|
+
YAML.stub!(:load).and_return(false)
|
98
|
+
end
|
99
|
+
it_should_behave_like "simple_config defaults"
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
# describe "using default jasmine.yml" do
|
104
|
+
# before(:each) do
|
105
|
+
# @config.stub!(:simple_config_file).and_return(File.join(@template_dir, 'spec/javascripts/support/jasmine.yml'))
|
106
|
+
# end
|
107
|
+
# it_should_behave_like "simple_config defaults"
|
108
|
+
#
|
109
|
+
# end
|
110
|
+
|
111
|
+
describe "should use the first appearance of duplicate filenames" do
|
112
|
+
before(:each) do
|
113
|
+
Dir.stub!(:glob).and_return do |glob_string|
|
114
|
+
glob_string
|
115
|
+
end
|
116
|
+
fake_config = Hash.new.stub!(:[]).and_return(["file1.ext", "file2.ext", "file1.ext"])
|
117
|
+
@config.stub!(:simple_config).and_return(fake_config)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "src_files" do
|
121
|
+
@config.src_files.should == ['file1.ext', 'file2.ext']
|
122
|
+
end
|
123
|
+
|
124
|
+
it "stylesheets" do
|
125
|
+
@config.stylesheets.should == ['file1.ext', 'file2.ext']
|
126
|
+
end
|
127
|
+
|
128
|
+
it "spec_files" do
|
129
|
+
@config.spec_files.should == ['file1.ext', 'file2.ext']
|
130
|
+
end
|
131
|
+
|
132
|
+
it "helpers" do
|
133
|
+
@config.spec_files.should == ['file1.ext', 'file2.ext']
|
134
|
+
end
|
135
|
+
|
136
|
+
it "js_files" do
|
137
|
+
@config.js_files.should == ["/file1.ext",
|
138
|
+
"/file2.ext",
|
139
|
+
"/__spec__/file1.ext",
|
140
|
+
"/__spec__/file2.ext",
|
141
|
+
"/__spec__/file1.ext",
|
142
|
+
"/__spec__/file2.ext"]
|
143
|
+
end
|
144
|
+
|
145
|
+
it "spec_files_full_paths" do
|
146
|
+
@config.spec_files_full_paths.should == [
|
147
|
+
File.expand_path("spec/javascripts/file1.ext", @rails_dir),
|
148
|
+
File.expand_path("spec/javascripts/file2.ext", @rails_dir)
|
149
|
+
]
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
it "simple_config stylesheets" do
|
155
|
+
@config.stub!(:simple_config_file).and_return(File.join(@template_dir, 'spec/javascripts/support/jasmine.yml'))
|
156
|
+
YAML.stub!(:load).and_return({'stylesheets' => ['foo.css', 'bar.css']})
|
157
|
+
Dir.stub!(:glob).and_return do |glob_string|
|
158
|
+
glob_string
|
159
|
+
end
|
160
|
+
@config.stylesheets.should == ['foo.css', 'bar.css']
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
it "using rails jasmine.yml" do
|
165
|
+
@config.stub!(:simple_config_file).and_return(File.join(@template_dir, 'spec/javascripts/support/jasmine-rails.yml'))
|
166
|
+
@config.spec_files.should == ['PlayerSpec.js']
|
167
|
+
@config.helpers.should == ['helpers/SpecHelper.js']
|
168
|
+
@config.src_files.should == ['public/javascripts/prototype.js',
|
169
|
+
'public/javascripts/effects.js',
|
170
|
+
'public/javascripts/controls.js',
|
171
|
+
'public/javascripts/dragdrop.js',
|
172
|
+
'public/javascripts/application.js',
|
173
|
+
'public/javascripts/Player.js',
|
174
|
+
'public/javascripts/Song.js']
|
175
|
+
@config.js_files.should == [
|
176
|
+
'/public/javascripts/prototype.js',
|
177
|
+
'/public/javascripts/effects.js',
|
178
|
+
'/public/javascripts/controls.js',
|
179
|
+
'/public/javascripts/dragdrop.js',
|
180
|
+
'/public/javascripts/application.js',
|
181
|
+
'/public/javascripts/Player.js',
|
182
|
+
'/public/javascripts/Song.js',
|
183
|
+
'/__spec__/helpers/SpecHelper.js',
|
184
|
+
'/__spec__/PlayerSpec.js',
|
185
|
+
]
|
186
|
+
@config.js_files("PlayerSpec.js").should == [
|
187
|
+
'/public/javascripts/prototype.js',
|
188
|
+
'/public/javascripts/effects.js',
|
189
|
+
'/public/javascripts/controls.js',
|
190
|
+
'/public/javascripts/dragdrop.js',
|
191
|
+
'/public/javascripts/application.js',
|
192
|
+
'/public/javascripts/Player.js',
|
193
|
+
'/public/javascripts/Song.js',
|
194
|
+
'/__spec__/helpers/SpecHelper.js',
|
195
|
+
'/__spec__/PlayerSpec.js'
|
196
|
+
]
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "browser configuration" do
|
205
|
+
it "should use firefox by default" do
|
206
|
+
ENV.stub!(:[], "JASMINE_BROWSER").and_return(nil)
|
207
|
+
config = Jasmine::Config.new
|
208
|
+
config.stub!(:start_servers)
|
209
|
+
Jasmine::SeleniumDriver.should_receive(:new).
|
210
|
+
with(anything(), anything(), "*firefox", anything()).
|
211
|
+
and_return(mock(Jasmine::SeleniumDriver, :connect => true))
|
212
|
+
config.start
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should use ENV['JASMINE_BROWSER'] if set" do
|
216
|
+
ENV.stub!(:[], "JASMINE_BROWSER").and_return("mosaic")
|
217
|
+
config = Jasmine::Config.new
|
218
|
+
config.stub!(:start_servers)
|
219
|
+
Jasmine::SeleniumDriver.should_receive(:new).
|
220
|
+
with(anything(), anything(), "*mosaic", anything()).
|
221
|
+
and_return(mock(Jasmine::SeleniumDriver, :connect => true))
|
222
|
+
config.start
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe "jasmine host" do
|
227
|
+
it "should use http://localhost by default" do
|
228
|
+
config = Jasmine::Config.new
|
229
|
+
config.instance_variable_set(:@jasmine_server_port, '1234')
|
230
|
+
config.stub!(:start_servers)
|
231
|
+
|
232
|
+
Jasmine::SeleniumDriver.should_receive(:new).
|
233
|
+
with(anything(), anything(), anything(), "http://localhost:1234/").
|
234
|
+
and_return(mock(Jasmine::SeleniumDriver, :connect => true))
|
235
|
+
config.start
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should use ENV['JASMINE_HOST'] if set" do
|
239
|
+
ENV.stub!(:[], "JASMINE_HOST").and_return("http://some_host")
|
240
|
+
config = Jasmine::Config.new
|
241
|
+
config.instance_variable_set(:@jasmine_server_port, '1234')
|
242
|
+
config.stub!(:start_servers)
|
243
|
+
|
244
|
+
Jasmine::SeleniumDriver.should_receive(:new).
|
245
|
+
with(anything(), anything(), anything(), "http://some_host:1234/").
|
246
|
+
and_return(mock(Jasmine::SeleniumDriver, :connect => true))
|
247
|
+
config.start
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
describe "#start_selenium_server" do
|
252
|
+
it "should use an existing selenium server if SELENIUM_SERVER_PORT is set" do
|
253
|
+
config = Jasmine::Config.new
|
254
|
+
ENV.stub!(:[], "SELENIUM_SERVER_PORT").and_return(1234)
|
255
|
+
Jasmine.should_receive(:wait_for_listener).with(1234, "selenium server")
|
256
|
+
config.start_selenium_server
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
|
2
|
+
|
3
|
+
describe "Jasmine command line tool" do
|
4
|
+
before :each do
|
5
|
+
temp_dir_before
|
6
|
+
Dir::chdir @tmp
|
7
|
+
end
|
8
|
+
|
9
|
+
after :each do
|
10
|
+
temp_dir_after
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should create files on init" do
|
14
|
+
output = capture_stdout do
|
15
|
+
Jasmine::CommandLineTool.new.process ["init"]
|
16
|
+
end
|
17
|
+
output.should =~ /Jasmine has been installed with example specs./
|
18
|
+
|
19
|
+
my_jasmine_lib = File.expand_path(File.join(@root, "lib"))
|
20
|
+
bootstrap = "$:.unshift('#{my_jasmine_lib}')"
|
21
|
+
|
22
|
+
ENV['JASMINE_GEM_PATH'] = "#{@root}/lib"
|
23
|
+
ci_output = `rake -E "#{bootstrap}" --trace jasmine:ci`
|
24
|
+
ci_output.should =~ (/[1-9][0-9]* examples, 0 failures/)
|
25
|
+
end
|
26
|
+
end
|