konacha 1.5.1 → 2.0.0.beta1

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 (36) hide show
  1. data/History.md +7 -0
  2. data/README.md +71 -57
  3. data/app/assets/javascripts/konacha/iframe.js +40 -0
  4. data/app/assets/javascripts/konacha/parent.js +14 -0
  5. data/app/assets/javascripts/konacha/runner.js +45 -0
  6. data/app/assets/stylesheets/konacha.css +75 -0
  7. data/app/controllers/konacha/specs_controller.rb +6 -1
  8. data/app/models/konacha/spec.rb +1 -1
  9. data/app/views/konacha/specs/iframe.html.erb +18 -0
  10. data/app/views/konacha/specs/parent.html.erb +16 -0
  11. data/config/routes.rb +4 -2
  12. data/images/frame-select.png +0 -0
  13. data/konacha.gemspec +1 -1
  14. data/lib/konacha/engine.rb +1 -0
  15. data/lib/konacha/runner.rb +22 -57
  16. data/spec/controllers/specs_controller_spec.rb +4 -4
  17. data/spec/dummy/config/application.rb +1 -0
  18. data/spec/dummy/spec/isolated/errors/failing_iframe_spec.js.coffee +1 -0
  19. data/spec/dummy/spec/javascripts/{test_element_spec.js.coffee → body_spec.js.coffee} +4 -7
  20. data/spec/dummy/spec/javascripts/isolation/a_spec.js +6 -0
  21. data/spec/dummy/spec/javascripts/isolation/b_spec.js +6 -0
  22. data/spec/dummy/spec/javascripts/templating_spec.js +4 -4
  23. data/spec/dummy/spec/javascripts/ui_spec.js +7 -0
  24. data/spec/error_handling_spec.rb +33 -0
  25. data/spec/models/spec_spec.rb +1 -1
  26. data/spec/spec_helper.rb +5 -0
  27. data/spec/views/specs/{specs.html.erb_spec.rb → iframe.html.erb_spec.rb} +12 -8
  28. data/vendor/assets/javascripts/mocha.js +297 -114
  29. metadata +195 -180
  30. data/app/views/konacha/specs/specs.html.erb +0 -16
  31. data/lib/assets/javascripts/konacha.js +0 -20
  32. data/lib/assets/javascripts/konacha/runner.js +0 -50
  33. data/lib/assets/javascripts/konacha/server.js +0 -6
  34. data/lib/assets/stylesheets/konacha.css +0 -5
  35. data/spec/dummy/spec/javascripts/konacha_config.js +0 -2
  36. data/spec/dummy/spec/javascripts/konacha_config_spec.js +0 -9
@@ -1,4 +1,6 @@
1
1
  Konacha::Engine.routes.draw do
2
- match "/" => "specs#specs"
3
- match "*path" => "specs#specs"
2
+ match '/iframe' => 'specs#iframe', :as => 'iframe'
3
+ match '/iframe/*path' => 'specs#iframe'
4
+ match '/' => 'specs#parent', :as => 'parent'
5
+ match '*path' => 'specs#parent'
4
6
  end
Binary file
@@ -17,7 +17,7 @@ the asset pipeline and engines.}
17
17
  gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  gem.name = "konacha"
19
19
  gem.require_paths = ["lib"]
20
- gem.version = "1.5.1"
20
+ gem.version = "2.0.0.beta1"
21
21
 
22
22
  gem.add_dependency "railties", "~> 3.1"
23
23
  gem.add_dependency "actionpack", "~> 3.1"
@@ -31,6 +31,7 @@ module Konacha
31
31
  options.port ||= 3500
32
32
  options.application ||= self.class.application(app)
33
33
  options.driver ||= :selenium
34
+ options.stylesheets ||= %w(application)
34
35
 
35
36
  app.config.assets.paths << app.root.join(options.spec_dir).to_s
36
37
  end
@@ -7,7 +7,7 @@ module Konacha
7
7
  new.run
8
8
  end
9
9
 
10
- attr_reader :io
10
+ attr_reader :io, :examples
11
11
 
12
12
  def initialize(options = {})
13
13
  @io = options[:output] || STDOUT
@@ -16,7 +16,27 @@ module Konacha
16
16
  def run
17
17
  before = Time.now
18
18
 
19
- spec_runners.each { |spec_runner| spec_runner.run } # prints dots
19
+ begin
20
+ session.visit("/")
21
+
22
+ dots_printed = 0
23
+ begin
24
+ sleep 0.1
25
+ done, dots = session.evaluate_script('[Konacha.done, Konacha.dots]')
26
+ if dots
27
+ io.write colorize_dots(dots[dots_printed..-1])
28
+ io.flush
29
+ dots_printed = dots.length
30
+ end
31
+ end until done
32
+
33
+ @examples = JSON.parse(session.evaluate_script('Konacha.getResults()')).map do |row|
34
+ Example.new(row)
35
+ end
36
+ rescue => e
37
+ raise e, "Error communicating with browser process: #{e}", e.backtrace
38
+ end
39
+
20
40
  io.puts ""
21
41
  io.puts ""
22
42
  failure_messages.each { |msg| io.write("#{msg}\n\n") }
@@ -27,10 +47,6 @@ module Konacha
27
47
  passed?
28
48
  end
29
49
 
30
- def examples
31
- spec_runners.map { |spec_runner| spec_runner.examples }.flatten
32
- end
33
-
34
50
  def pending_examples
35
51
  examples.select { |example| example.pending? }
36
52
  end
@@ -51,27 +67,6 @@ module Konacha
51
67
  @session ||= Capybara::Session.new(Konacha.driver, Konacha.application)
52
68
  end
53
69
 
54
- def spec_runners
55
- @spec_runners ||= Konacha::Spec.all.map { |spec| SpecRunner.new(self, spec) }
56
- end
57
- end
58
-
59
- class SpecRunner
60
- attr_reader :runner, :spec, :examples
61
-
62
- def initialize(runner, spec)
63
- @runner = runner
64
- @spec = spec
65
- end
66
-
67
- def session
68
- runner.session
69
- end
70
-
71
- def io
72
- runner.io
73
- end
74
-
75
70
  def colorize_dots(dots)
76
71
  dots = dots.chars.map do |d|
77
72
  case d
@@ -83,27 +78,6 @@ module Konacha
83
78
  end
84
79
  dots.join ''
85
80
  end
86
-
87
- def run
88
- session.visit(spec.url)
89
-
90
- dots_printed = 0
91
- begin
92
- sleep 0.1
93
- done, dots = session.evaluate_script('[Konacha.done, Konacha.dots]')
94
- if dots
95
- io.write colorize_dots(dots[dots_printed..-1])
96
- io.flush
97
- dots_printed = dots.length
98
- end
99
- end until done
100
-
101
- @examples = JSON.parse(session.evaluate_script('Konacha.getResults()')).map do |row|
102
- Example.new(row)
103
- end
104
- rescue => e
105
- raise e, "Error communicating with browser process: #{e}", e.backtrace
106
- end
107
81
  end
108
82
 
109
83
  class Example
@@ -135,13 +109,4 @@ module Konacha
135
109
  end
136
110
  end
137
111
  end
138
-
139
- class Error < StandardError
140
- attr_accessor :original
141
-
142
- def initialize(msg, original=nil);
143
- super(msg);
144
- @original = original;
145
- end
146
- end
147
112
  end
@@ -5,18 +5,18 @@ describe Konacha::SpecsController do
5
5
  @routes = Konacha::Engine.routes
6
6
  end
7
7
 
8
- describe "#specs" do
8
+ describe "#iframe" do
9
9
  it "assigns the result of Spec.find to @specs" do
10
10
  Konacha::Spec.should_receive(:find).with("spec_path") { :spec }
11
- get :specs, :path => "spec_path"
11
+ get :iframe, :path => "spec_path"
12
12
  assigns[:specs].should == :spec
13
13
  end
14
14
 
15
15
  it "404s if there is no match for the given path" do
16
16
  Konacha::Spec.should_receive(:find).with("array_spec") { raise Konacha::Spec::NotFound }
17
- get :specs, :path => "array_spec"
17
+ get :iframe, :path => "array_spec"
18
18
  response.status.should == 404
19
- response.should_not render_template("konacha/specs/show")
19
+ response.should_not render_template("konacha/specs/iframe")
20
20
  end
21
21
  end
22
22
  end
@@ -13,5 +13,6 @@ module Dummy
13
13
  config.encoding = "utf-8"
14
14
  config.assets.enabled = true
15
15
  config.assets.version = '1.0'
16
+ config.assets.paths << Rails.root.join("spec/isolated/errors").to_s
16
17
  end
17
18
  end
@@ -1,6 +1,9 @@
1
1
  #= require jquery
2
2
 
3
- describe "the #konacha element", ->
3
+ describe "the body#konacha element", ->
4
+ it "is empty", ->
5
+ $('#konacha').html().should.equal ''
6
+
4
7
  it "can have content added in one test...", ->
5
8
  $('#konacha').append('<h1 id="added">New Stuff</h1>')
6
9
  $('#konacha h1#added').length.should.equal(1)
@@ -14,11 +17,5 @@ describe "the #konacha element", ->
14
17
  it "... that is removed before the next one starts", ->
15
18
  $('#konacha').hasClass('test').should.be.false
16
19
 
17
- it "can be removed in one test...", ->
18
- $('#konacha').remove()
19
-
20
- it "... and is re-added before the next one starts", ->
21
- $('#konacha').length.should.equal(1)
22
-
23
20
  it "is visible", ->
24
21
  $('#konacha').is(':visible').should.be.true
@@ -0,0 +1,6 @@
1
+ describe("spec file isolation (A)", function () {
2
+ it("isolates globals in one spec file from those of other spec files", function () {
3
+ window.a_spec = true;
4
+ expect(window.b_spec).to.be.undefined;
5
+ });
6
+ });
@@ -0,0 +1,6 @@
1
+ describe("spec file isolation (B)", function () {
2
+ it("isolates globals in one spec file from those of other spec files", function () {
3
+ window.b_spec = true;
4
+ expect(window.a_spec).to.be.undefined;
5
+ });
6
+ });
@@ -1,9 +1,9 @@
1
1
  //= require spec_helper
2
2
  //= require jquery
3
3
 
4
- describe("templating", function(){
5
- it("is built in to Sprockets", function(){
6
- $('#konacha').html(JST['templates/hello']());
7
- $('#konacha h1').text().should.equal('Hello Konacha!');
4
+ describe("templating", function() {
5
+ it("is built in to Sprockets", function() {
6
+ $('body').html(JST['templates/hello']());
7
+ $('body h1').text().should.equal('Hello Konacha!');
8
8
  });
9
9
  });
@@ -0,0 +1,7 @@
1
+ mocha.ui('tdd');
2
+
3
+ suite('UI', function () {
4
+ test('supports alternate mocha UIs', function () {
5
+ (2 + 2).should.equal(4);
6
+ });
7
+ });
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Konacha, :type => :request do
4
+ before do
5
+ Konacha.mode = :server
6
+ end
7
+
8
+ around do |example|
9
+ begin
10
+ spec_dir = Konacha.config.spec_dir
11
+ Konacha.config.spec_dir = "spec/isolated/errors"
12
+ example.run
13
+ ensure
14
+ Konacha.config.spec_dir = spec_dir
15
+ end
16
+ end
17
+
18
+ it "inserts a failing test when an iframe fails to load" do
19
+ silencing_stderr do
20
+ visit "/failing_iframe_spec"
21
+ page.should have_content("failing_iframe_spec.js.coffee")
22
+ page.should have_css(".test.fail")
23
+ end
24
+ end
25
+
26
+ def silencing_stderr
27
+ stderr = $stderr
28
+ $stderr = StringIO.new
29
+ yield
30
+ ensure
31
+ $stderr = stderr
32
+ end
33
+ end
@@ -18,7 +18,7 @@ describe Konacha::Spec do
18
18
 
19
19
  describe "#url" do
20
20
  it "returns a URL path" do
21
- described_class.new("array_spec.js").url.should == "/array_spec"
21
+ described_class.new("array_spec.js").url.should include "array_spec"
22
22
  end
23
23
  end
24
24
 
@@ -32,3 +32,8 @@ end
32
32
  RSpec.configure do |config|
33
33
  config.include Konacha::RequestSpec, :type => :request
34
34
  end
35
+
36
+ Konacha.configure do |config|
37
+ # We don't have an application.css in our dummy app.
38
+ config.stylesheets = []
39
+ end
@@ -1,13 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "konacha/specs/specs" do
4
- it "includes konacha JS for given mode" do
5
- assign(:specs, [])
6
- Konacha.should_receive(:mode).any_number_of_times { :runner }
7
-
8
- render
9
-
10
- rendered.should have_css("script[src='/assets/konacha/runner.js']")
3
+ describe "konacha/specs/iframe" do
4
+ before do
5
+ assign(:stylesheets, [])
11
6
  end
12
7
 
13
8
  def asset_double(asset_name, dependencies = [])
@@ -64,4 +59,13 @@ describe "konacha/specs/specs" do
64
59
  rendered.should have_selector("script[src='/assets/dependency_a.js?body=1']", :count => 1)
65
60
  rendered.should have_selector("script[src='/assets/dependency_b.js?body=1']", :count => 1)
66
61
  end
62
+
63
+ it "render the stylesheets" do
64
+ assign(:stylesheets, %w(foo bar))
65
+
66
+ render
67
+
68
+ rendered.should have_selector("link[href='/assets/foo.css']")
69
+ rendered.should have_selector("link[href='/assets/bar.css']")
70
+ end
67
71
  end
@@ -433,6 +433,19 @@ Context.prototype.timeout = function(ms){
433
433
  return this;
434
434
  };
435
435
 
436
+ /**
437
+ * Set test slowness threshold `ms`.
438
+ *
439
+ * @param {Number} ms
440
+ * @return {Context} self
441
+ * @api private
442
+ */
443
+
444
+ Context.prototype.slow = function(ms){
445
+ this.runnable().slow(ms);
446
+ return this;
447
+ };
448
+
436
449
  /**
437
450
  * Inspect the context void of `._runnable`.
438
451
  *
@@ -984,6 +997,7 @@ function image(name) {
984
997
  * - `reporter` reporter instance, defaults to `mocha.reporters.Dot`
985
998
  * - `globals` array of accepted globals
986
999
  * - `timeout` timeout in milliseconds
1000
+ * - `slow` milliseconds to wait before considering a test slow
987
1001
  * - `ignoreLeaks` ignore global leaks
988
1002
  * - `grep` string or regexp to filter tests with
989
1003
  *
@@ -999,7 +1013,8 @@ function Mocha(options) {
999
1013
  this.suite = new exports.Suite('', new exports.Context);
1000
1014
  this.ui(options.ui);
1001
1015
  this.reporter(options.reporter);
1002
- if (options.timeout) this.suite.timeout(options.timeout);
1016
+ if (options.timeout) this.timeout(options.timeout);
1017
+ if (options.slow) this.slow(options.slow);
1003
1018
  }
1004
1019
 
1005
1020
  /**
@@ -1015,16 +1030,24 @@ Mocha.prototype.addFile = function(file){
1015
1030
  };
1016
1031
 
1017
1032
  /**
1018
- * Set reporter to `name`, defaults to "dot".
1033
+ * Set reporter to `reporter`, defaults to "dot".
1019
1034
  *
1020
- * @param {String} name
1035
+ * @param {String|Function} reporter name of a reporter or a reporter constructor
1021
1036
  * @api public
1022
1037
  */
1023
1038
 
1024
- Mocha.prototype.reporter = function(name){
1025
- name = name || 'dot';
1026
- this._reporter = require('./reporters/' + name);
1027
- if (!this._reporter) throw new Error('invalid reporter "' + name + '"');
1039
+ Mocha.prototype.reporter = function(reporter){
1040
+ if ('function' == typeof reporter) {
1041
+ this._reporter = reporter;
1042
+ } else {
1043
+ reporter = reporter || 'dot';
1044
+ try {
1045
+ this._reporter = require('./reporters/' + reporter);
1046
+ } catch (err) {
1047
+ this._reporter = require(reporter);
1048
+ }
1049
+ if (!this._reporter) throw new Error('invalid reporter "' + reporter + '"');
1050
+ }
1028
1051
  return this;
1029
1052
  };
1030
1053
 
@@ -1125,6 +1148,18 @@ Mocha.prototype.ignoreLeaks = function(){
1125
1148
  return this;
1126
1149
  };
1127
1150
 
1151
+ /**
1152
+ * Enable global leak checking.
1153
+ *
1154
+ * @return {Mocha}
1155
+ * @api public
1156
+ */
1157
+
1158
+ Mocha.prototype.checkLeaks = function(){
1159
+ this.options.ignoreLeaks = false;
1160
+ return this;
1161
+ };
1162
+
1128
1163
  /**
1129
1164
  * Enable growl support.
1130
1165
  *
@@ -1138,15 +1173,41 @@ Mocha.prototype.growl = function(){
1138
1173
  };
1139
1174
 
1140
1175
  /**
1141
- * Ignore `globals`.
1176
+ * Ignore `globals` array or string.
1142
1177
  *
1143
- * @param {Array} globals
1178
+ * @param {Array|String} globals
1144
1179
  * @return {Mocha}
1145
1180
  * @api public
1146
1181
  */
1147
1182
 
1148
1183
  Mocha.prototype.globals = function(globals){
1149
- this.options.globals = globals;
1184
+ this.options.globals = (this.options.globals || []).concat(globals);
1185
+ return this;
1186
+ };
1187
+
1188
+ /**
1189
+ * Set the timeout in milliseconds.
1190
+ *
1191
+ * @param {Number} timeout
1192
+ * @return {Mocha}
1193
+ * @api public
1194
+ */
1195
+
1196
+ Mocha.prototype.timeout = function(timeout){
1197
+ this.suite.timeout(timeout);
1198
+ return this;
1199
+ };
1200
+
1201
+ /**
1202
+ * Set slowness threshold in milliseconds.
1203
+ *
1204
+ * @param {Number} slow
1205
+ * @return {Mocha}
1206
+ * @api public
1207
+ */
1208
+
1209
+ Mocha.prototype.slow = function(slow){
1210
+ this.suite.slow(slow);
1150
1211
  return this;
1151
1212
  };
1152
1213
 
@@ -1159,7 +1220,7 @@ Mocha.prototype.globals = function(globals){
1159
1220
  */
1160
1221
 
1161
1222
  Mocha.prototype.run = function(fn){
1162
- this.loadFiles();
1223
+ if (this.files.length) this.loadFiles();
1163
1224
  var suite = this.suite;
1164
1225
  var options = this.options;
1165
1226
  var runner = new exports.Runner(suite);
@@ -1173,6 +1234,90 @@ Mocha.prototype.run = function(fn){
1173
1234
 
1174
1235
  }); // module: mocha.js
1175
1236
 
1237
+ require.register("ms.js", function(module, exports, require){
1238
+
1239
+ /**
1240
+ * Helpers.
1241
+ */
1242
+
1243
+ var s = 1000;
1244
+ var m = s * 60;
1245
+ var h = m * 60;
1246
+ var d = h * 24;
1247
+
1248
+ /**
1249
+ * Parse or format the given `val`.
1250
+ *
1251
+ * @param {String|Number} val
1252
+ * @return {String|Number}
1253
+ * @api public
1254
+ */
1255
+
1256
+ module.exports = function(val){
1257
+ if ('string' == typeof val) return parse(val);
1258
+ return format(val);
1259
+ }
1260
+
1261
+ /**
1262
+ * Parse the given `str` and return milliseconds.
1263
+ *
1264
+ * @param {String} str
1265
+ * @return {Number}
1266
+ * @api private
1267
+ */
1268
+
1269
+ function parse(str) {
1270
+ var m = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str);
1271
+ if (!m) return;
1272
+ var n = parseFloat(m[1]);
1273
+ var type = (m[2] || 'ms').toLowerCase();
1274
+ switch (type) {
1275
+ case 'years':
1276
+ case 'year':
1277
+ case 'y':
1278
+ return n * 31557600000;
1279
+ case 'days':
1280
+ case 'day':
1281
+ case 'd':
1282
+ return n * 86400000;
1283
+ case 'hours':
1284
+ case 'hour':
1285
+ case 'h':
1286
+ return n * 3600000;
1287
+ case 'minutes':
1288
+ case 'minute':
1289
+ case 'm':
1290
+ return n * 60000;
1291
+ case 'seconds':
1292
+ case 'second':
1293
+ case 's':
1294
+ return n * 1000;
1295
+ case 'ms':
1296
+ return n;
1297
+ }
1298
+ }
1299
+
1300
+ /**
1301
+ * Format the given `ms`.
1302
+ *
1303
+ * @param {Number} ms
1304
+ * @return {String}
1305
+ * @api public
1306
+ */
1307
+
1308
+ function format(ms) {
1309
+ if (ms == d) return (ms / d) + ' day';
1310
+ if (ms > d) return (ms / d) + ' days';
1311
+ if (ms == h) return (ms / h) + ' hour';
1312
+ if (ms > h) return (ms / h) + ' hours';
1313
+ if (ms == m) return (ms / m) + ' minute';
1314
+ if (ms > m) return (ms / m) + ' minutes';
1315
+ if (ms == s) return (ms / s) + ' second';
1316
+ if (ms > s) return (ms / s) + ' seconds';
1317
+ return ms + ' ms';
1318
+ }
1319
+ }); // module: ms.js
1320
+
1176
1321
  require.register("reporters/base.js", function(module, exports, require){
1177
1322
 
1178
1323
  /**
@@ -1180,7 +1325,8 @@ require.register("reporters/base.js", function(module, exports, require){
1180
1325
  */
1181
1326
 
1182
1327
  var tty = require('browser/tty')
1183
- , diff = require('browser/diff');
1328
+ , diff = require('browser/diff')
1329
+ , ms = require('../ms');
1184
1330
 
1185
1331
  /**
1186
1332
  * Save timer references to avoid Sinon interfering (see GH-237).
@@ -1294,13 +1440,6 @@ exports.cursor = {
1294
1440
  }
1295
1441
  };
1296
1442
 
1297
- /**
1298
- * A test is considered slow if it
1299
- * exceeds the following value in milliseconds.
1300
- */
1301
-
1302
- exports.slow = 75;
1303
-
1304
1443
  /**
1305
1444
  * Outut the given `failures` as a list.
1306
1445
  *
@@ -1402,8 +1541,8 @@ function Base(runner) {
1402
1541
  runner.on('pass', function(test){
1403
1542
  stats.passes = stats.passes || 0;
1404
1543
 
1405
- var medium = exports.slow / 2;
1406
- test.speed = test.duration > exports.slow
1544
+ var medium = test.slow() / 2;
1545
+ test.speed = test.duration > test.slow()
1407
1546
  ? 'slow'
1408
1547
  : test.duration > medium
1409
1548
  ? 'medium'
@@ -1466,12 +1605,12 @@ Base.prototype.epilogue = function(){
1466
1605
  // pass
1467
1606
  fmt = color('bright pass', ' ✔')
1468
1607
  + color('green', ' %d %s complete')
1469
- + color('light', ' (%dms)');
1608
+ + color('light', ' (%s)');
1470
1609
 
1471
1610
  console.log(fmt,
1472
1611
  stats.tests || 0,
1473
1612
  pluralize(stats.tests),
1474
- stats.duration);
1613
+ ms(stats.duration));
1475
1614
 
1476
1615
  // pending
1477
1616
  if (stats.pending) {
@@ -1508,7 +1647,10 @@ function pad(str, len) {
1508
1647
 
1509
1648
  function errorDiff(err, type) {
1510
1649
  return diff['diff' + type](err.actual, err.expected).map(function(str){
1511
- if (/^(\n+)$/.test(str.value)) str.value = Array(++RegExp.$1.length).join('<newline>');
1650
+ str.value = str.value
1651
+ .replace(/\t/g, '<tab>')
1652
+ .replace(/\r/g, '<CR>')
1653
+ .replace(/\n/g, '<LF>\n');
1512
1654
  if (str.added) return colorLines('diff added', str.value);
1513
1655
  if (str.removed) return colorLines('diff removed', str.value);
1514
1656
  return str.value;
@@ -1759,13 +1901,12 @@ var statsTemplate = '<ul id="stats">'
1759
1901
  * @api public
1760
1902
  */
1761
1903
 
1762
- function HTML(runner) {
1904
+ function HTML(runner, root) {
1763
1905
  Base.call(this, runner);
1764
1906
 
1765
1907
  var self = this
1766
1908
  , stats = this.stats
1767
1909
  , total = runner.total
1768
- , root = document.getElementById('mocha')
1769
1910
  , stat = fragment(statsTemplate)
1770
1911
  , items = stat.getElementsByTagName('li')
1771
1912
  , passes = items[1].getElementsByTagName('em')[0]
@@ -1779,6 +1920,8 @@ function HTML(runner) {
1779
1920
  , progress
1780
1921
  , ctx
1781
1922
 
1923
+ root = root || document.getElementById('mocha');
1924
+
1782
1925
  if (canvas.getContext) {
1783
1926
  var ratio = window.devicePixelRatio || 1;
1784
1927
  canvas.style.width = canvas.width;
@@ -1813,8 +1956,7 @@ function HTML(runner) {
1813
1956
  if (suite.root) return;
1814
1957
 
1815
1958
  // suite
1816
- var grep = '^' + encodeURIComponent(utils.escapeRegexp(suite.fullTitle()));
1817
- var url = location.protocol + '//' + location.host + location.pathname + '?grep=' + grep;
1959
+ var url = '?grep=' + encodeURIComponent(suite.fullTitle());
1818
1960
  var el = fragment('<li class="suite"><h1><a href="%s">%s</a></h1></li>', url, escape(suite.title));
1819
1961
 
1820
1962
  // container
@@ -3045,10 +3187,10 @@ function TAP(runner) {
3045
3187
 
3046
3188
  var self = this
3047
3189
  , stats = this.stats
3048
- , total = runner.total
3049
3190
  , n = 1;
3050
3191
 
3051
3192
  runner.on('start', function(){
3193
+ var total = runner.grepTotal(runner.suite);
3052
3194
  console.log('%d..%d', 1, total);
3053
3195
  });
3054
3196
 
@@ -3313,6 +3455,7 @@ function Runnable(title, fn) {
3313
3455
  this.async = fn && fn.length;
3314
3456
  this.sync = ! this.async;
3315
3457
  this._timeout = 2000;
3458
+ this._slow = 75;
3316
3459
  this.timedOut = false;
3317
3460
  }
3318
3461
 
@@ -3340,6 +3483,21 @@ Runnable.prototype.timeout = function(ms){
3340
3483
  return this;
3341
3484
  };
3342
3485
 
3486
+ /**
3487
+ * Set & get slow `ms`.
3488
+ *
3489
+ * @param {Number} ms
3490
+ * @return {Runnable|Number} ms or self
3491
+ * @api private
3492
+ */
3493
+
3494
+ Runnable.prototype.slow = function(ms){
3495
+ if (0 === arguments.length) return this._slow;
3496
+ debug('timeout %d', ms);
3497
+ this._slow = ms;
3498
+ return this;
3499
+ };
3500
+
3343
3501
  /**
3344
3502
  * Return the full title generated by recursively
3345
3503
  * concatenating the parent's full title.
@@ -3651,7 +3809,6 @@ Runner.prototype.failHook = function(hook, err){
3651
3809
  Runner.prototype.hook = function(name, fn){
3652
3810
  var suite = this.suite
3653
3811
  , hooks = suite['_' + name]
3654
- , ms = suite._timeout
3655
3812
  , self = this
3656
3813
  , timer;
3657
3814
 
@@ -3961,6 +4118,7 @@ function filterLeaks(ok) {
3961
4118
  return matched.length == 0 && (!global.navigator || 'onerror' !== key);
3962
4119
  });
3963
4120
  }
4121
+
3964
4122
  }); // module: runner.js
3965
4123
 
3966
4124
  require.register("suite.js", function(module, exports, require){
@@ -3971,6 +4129,7 @@ require.register("suite.js", function(module, exports, require){
3971
4129
 
3972
4130
  var EventEmitter = require('browser/events').EventEmitter
3973
4131
  , debug = require('browser/debug')('mocha:suite')
4132
+ , milliseconds = require('./ms')
3974
4133
  , utils = require('./utils')
3975
4134
  , Hook = require('./hook');
3976
4135
 
@@ -4023,6 +4182,7 @@ function Suite(title, ctx) {
4023
4182
  this._afterAll = [];
4024
4183
  this.root = !title;
4025
4184
  this._timeout = 2000;
4185
+ this._slow = 75;
4026
4186
  this._bail = false;
4027
4187
  }
4028
4188
 
@@ -4046,6 +4206,7 @@ Suite.prototype.clone = function(){
4046
4206
  debug('clone');
4047
4207
  suite.ctx = this.ctx;
4048
4208
  suite.timeout(this.timeout());
4209
+ suite.slow(this.slow());
4049
4210
  suite.bail(this.bail());
4050
4211
  return suite;
4051
4212
  };
@@ -4060,12 +4221,28 @@ Suite.prototype.clone = function(){
4060
4221
 
4061
4222
  Suite.prototype.timeout = function(ms){
4062
4223
  if (0 == arguments.length) return this._timeout;
4063
- if (String(ms).match(/s$/)) ms = parseFloat(ms) * 1000;
4224
+ if ('string' == typeof ms) ms = milliseconds(ms);
4064
4225
  debug('timeout %d', ms);
4065
4226
  this._timeout = parseInt(ms, 10);
4066
4227
  return this;
4067
4228
  };
4068
4229
 
4230
+ /**
4231
+ * Set slow `ms` or short-hand such as "2s".
4232
+ *
4233
+ * @param {Number|String} ms
4234
+ * @return {Suite|Number} for chaining
4235
+ * @api private
4236
+ */
4237
+
4238
+ Suite.prototype.slow = function(ms){
4239
+ if (0 === arguments.length) return this._slow;
4240
+ if ('string' == typeof ms) ms = milliseconds(ms);
4241
+ debug('slow %d', ms);
4242
+ this._slow = ms;
4243
+ return this;
4244
+ };
4245
+
4069
4246
  /**
4070
4247
  * Sets whether to bail after first error.
4071
4248
  *
@@ -4094,6 +4271,7 @@ Suite.prototype.beforeAll = function(fn){
4094
4271
  var hook = new Hook('"before all" hook', fn);
4095
4272
  hook.parent = this;
4096
4273
  hook.timeout(this.timeout());
4274
+ hook.slow(this.slow());
4097
4275
  hook.ctx = this.ctx;
4098
4276
  this._beforeAll.push(hook);
4099
4277
  this.emit('beforeAll', hook);
@@ -4113,6 +4291,7 @@ Suite.prototype.afterAll = function(fn){
4113
4291
  var hook = new Hook('"after all" hook', fn);
4114
4292
  hook.parent = this;
4115
4293
  hook.timeout(this.timeout());
4294
+ hook.slow(this.slow());
4116
4295
  hook.ctx = this.ctx;
4117
4296
  this._afterAll.push(hook);
4118
4297
  this.emit('afterAll', hook);
@@ -4132,6 +4311,7 @@ Suite.prototype.beforeEach = function(fn){
4132
4311
  var hook = new Hook('"before each" hook', fn);
4133
4312
  hook.parent = this;
4134
4313
  hook.timeout(this.timeout());
4314
+ hook.slow(this.slow());
4135
4315
  hook.ctx = this.ctx;
4136
4316
  this._beforeEach.push(hook);
4137
4317
  this.emit('beforeEach', hook);
@@ -4151,6 +4331,7 @@ Suite.prototype.afterEach = function(fn){
4151
4331
  var hook = new Hook('"after each" hook', fn);
4152
4332
  hook.parent = this;
4153
4333
  hook.timeout(this.timeout());
4334
+ hook.slow(this.slow());
4154
4335
  hook.ctx = this.ctx;
4155
4336
  this._afterEach.push(hook);
4156
4337
  this.emit('afterEach', hook);
@@ -4168,6 +4349,7 @@ Suite.prototype.afterEach = function(fn){
4168
4349
  Suite.prototype.addSuite = function(suite){
4169
4350
  suite.parent = this;
4170
4351
  suite.timeout(this.timeout());
4352
+ suite.slow(this.slow());
4171
4353
  suite.bail(this.bail());
4172
4354
  this.suites.push(suite);
4173
4355
  this.emit('suite', suite);
@@ -4185,6 +4367,7 @@ Suite.prototype.addSuite = function(suite){
4185
4367
  Suite.prototype.addTest = function(test){
4186
4368
  test.parent = this;
4187
4369
  test.timeout(this.timeout());
4370
+ test.slow(this.slow());
4188
4371
  test.ctx = this.ctx;
4189
4372
  this.tests.push(test);
4190
4373
  this.emit('test', test);
@@ -4303,7 +4486,7 @@ var ignore = ['node_modules', '.git'];
4303
4486
  * @api private
4304
4487
  */
4305
4488
 
4306
- exports.escape = function(html) {
4489
+ exports.escape = function(html){
4307
4490
  return String(html)
4308
4491
  .replace(/&/g, '&amp;')
4309
4492
  .replace(/"/g, '&quot;')
@@ -4320,7 +4503,7 @@ exports.escape = function(html) {
4320
4503
  * @api private
4321
4504
  */
4322
4505
 
4323
- exports.forEach = function(arr, fn, scope) {
4506
+ exports.forEach = function(arr, fn, scope){
4324
4507
  for (var i = 0, l = arr.length; i < l; i++)
4325
4508
  fn.call(scope, arr[i], i);
4326
4509
  };
@@ -4334,7 +4517,7 @@ exports.forEach = function(arr, fn, scope) {
4334
4517
  * @api private
4335
4518
  */
4336
4519
 
4337
- exports.indexOf = function (arr, obj, start) {
4520
+ exports.indexOf = function(arr, obj, start){
4338
4521
  for (var i = start || 0, l = arr.length; i < l; i++) {
4339
4522
  if (arr[i] === obj)
4340
4523
  return i;
@@ -4348,15 +4531,14 @@ exports.indexOf = function (arr, obj, start) {
4348
4531
  * @param {Array} array
4349
4532
  * @param {Function} fn
4350
4533
  * @param {Object} initial value
4351
- * @param {Object} scope
4352
4534
  * @api private
4353
4535
  */
4354
4536
 
4355
- exports.reduce = function(arr, fn, val, scope) {
4537
+ exports.reduce = function(arr, fn, val){
4356
4538
  var rval = val;
4357
4539
 
4358
4540
  for (var i = 0, l = arr.length; i < l; i++) {
4359
- rval = fn.call(scope, rval, arr[i], i, arr);
4541
+ rval = fn(rval, arr[i], i, arr);
4360
4542
  }
4361
4543
 
4362
4544
  return rval;
@@ -4367,17 +4549,15 @@ exports.reduce = function(arr, fn, val, scope) {
4367
4549
  *
4368
4550
  * @param {Array} array
4369
4551
  * @param {Function} fn
4370
- * @param {Object} scope
4371
4552
  * @api private
4372
4553
  */
4373
4554
 
4374
- exports.filter = function(arr, fn, scope) {
4555
+ exports.filter = function(arr, fn){
4375
4556
  var ret = [];
4376
4557
 
4377
4558
  for (var i = 0, l = arr.length; i < l; i++) {
4378
4559
  var val = arr[i];
4379
- if (fn.call(scope, val, i, arr))
4380
- ret.push(val);
4560
+ if (fn(val, i, arr)) ret.push(val);
4381
4561
  }
4382
4562
 
4383
4563
  return ret;
@@ -4511,6 +4691,60 @@ exports.escapeRegexp = function(str){
4511
4691
  exports.trim = function(str){
4512
4692
  return str.replace(/^\s+|\s+$/g, '');
4513
4693
  };
4694
+
4695
+ /**
4696
+ * Parse the given `qs`.
4697
+ *
4698
+ * @param {String} qs
4699
+ * @return {Object}
4700
+ * @api private
4701
+ */
4702
+
4703
+ exports.parseQuery = function(qs){
4704
+ return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){
4705
+ var i = pair.indexOf('=')
4706
+ , key = pair.slice(0, i)
4707
+ , val = pair.slice(++i);
4708
+
4709
+ obj[key] = decodeURIComponent(val);
4710
+ return obj;
4711
+ }, {});
4712
+ };
4713
+
4714
+ /**
4715
+ * Highlight the given string of `js`.
4716
+ *
4717
+ * @param {String} js
4718
+ * @return {String}
4719
+ * @api private
4720
+ */
4721
+
4722
+ function highlight(js) {
4723
+ return js
4724
+ .replace(/</g, '&lt;')
4725
+ .replace(/>/g, '&gt;')
4726
+ .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>')
4727
+ .replace(/('.*?')/gm, '<span class="string">$1</span>')
4728
+ .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>')
4729
+ .replace(/(\d+)/gm, '<span class="number">$1</span>')
4730
+ .replace(/\bnew *(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>')
4731
+ .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>')
4732
+ }
4733
+
4734
+ /**
4735
+ * Highlight the contents of tag `name`.
4736
+ *
4737
+ * @param {String} name
4738
+ * @api private
4739
+ */
4740
+
4741
+ exports.highlightTags = function(name) {
4742
+ var code = document.getElementsByTagName(name);
4743
+ for (var i = 0, len = code.length; i < len; ++i) {
4744
+ code[i].innerHTML = highlight(code[i].innerHTML);
4745
+ }
4746
+ };
4747
+
4514
4748
  }); // module: utils.js
4515
4749
  /**
4516
4750
  * Node shims.
@@ -4574,69 +4808,25 @@ process.on = function(e, fn){
4574
4808
  }
4575
4809
  };
4576
4810
 
4577
- /**
4578
- * Expose mocha.
4579
- */
4580
-
4581
- window.mocha = require('mocha');
4582
-
4583
4811
  // boot
4584
4812
  ;(function(){
4585
- var utils = mocha.utils
4586
- , options = {}
4587
-
4588
- // TODO: use new Mocha here... not mocha.grep etc
4589
-
4590
- mocha.suite = new mocha.Suite('', new mocha.Context());
4591
-
4592
- /**
4593
- * Highlight the given string of `js`.
4594
- */
4595
-
4596
- function highlight(js) {
4597
- return js
4598
- .replace(/</g, '&lt;')
4599
- .replace(/>/g, '&gt;')
4600
- .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>')
4601
- .replace(/('.*?')/gm, '<span class="string">$1</span>')
4602
- .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>')
4603
- .replace(/(\d+)/gm, '<span class="number">$1</span>')
4604
- .replace(/\bnew *(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>')
4605
- .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>')
4606
- }
4607
4813
 
4608
4814
  /**
4609
- * Highlight code contents.
4815
+ * Expose mocha.
4610
4816
  */
4611
4817
 
4612
- function highlightCode() {
4613
- var code = document.getElementsByTagName('code');
4614
- for (var i = 0, len = code.length; i < len; ++i) {
4615
- code[i].innerHTML = highlight(code[i].innerHTML);
4616
- }
4617
- }
4818
+ var Mocha = window.Mocha = require('mocha'),
4819
+ mocha = window.mocha = new Mocha({ reporter: 'html' });
4618
4820
 
4619
4821
  /**
4620
- * Parse the given `qs`.
4822
+ * Override ui to ensure that the ui functions are initialized.
4823
+ * Normally this would happen in Mocha.prototype.loadFiles.
4621
4824
  */
4622
4825
 
4623
- function parse(qs) {
4624
- return utils.reduce(qs.replace('?', '').split('&'), function(obj, pair){
4625
- var i = pair.indexOf('=')
4626
- , key = pair.slice(0, i)
4627
- , val = pair.slice(++i);
4628
-
4629
- obj[key] = decodeURIComponent(val);
4630
- return obj;
4631
- }, {});
4632
- }
4633
-
4634
- /**
4635
- * Grep.
4636
- */
4637
-
4638
- mocha.grep = function(str){
4639
- options.grep = new RegExp(utils.escapeRegexp(str));
4826
+ mocha.ui = function(ui){
4827
+ Mocha.prototype.ui.call(this, ui);
4828
+ this.suite.emit('pre-require', window, null, this);
4829
+ return this;
4640
4830
  };
4641
4831
 
4642
4832
  /**
@@ -4644,14 +4834,9 @@ window.mocha = require('mocha');
4644
4834
  */
4645
4835
 
4646
4836
  mocha.setup = function(opts){
4647
- if ('string' === typeof opts) options.ui = opts;
4648
- else options = opts;
4649
-
4650
- ui = mocha.interfaces[options.ui];
4651
- if (!ui) throw new Error('invalid mocha interface "' + ui + '"');
4652
- if (options.timeout) mocha.suite.timeout(options.timeout);
4653
- ui(mocha.suite);
4654
- mocha.suite.emit('pre-require', window, null, mocha);
4837
+ if ('string' == typeof opts) opts = { ui: opts };
4838
+ for (var opt in opts) this[opt](opts[opt]);
4839
+ return this;
4655
4840
  };
4656
4841
 
4657
4842
  /**
@@ -4659,18 +4844,16 @@ window.mocha = require('mocha');
4659
4844
  */
4660
4845
 
4661
4846
  mocha.run = function(fn){
4662
- mocha.suite.emit('run');
4663
- var runner = new mocha.Runner(mocha.suite);
4664
- var Reporter = options.reporter || mocha.reporters.HTML;
4665
- var reporter = new Reporter(runner);
4666
- var query = parse(window.location.search || "");
4667
- if (query.grep) runner.grep(new RegExp(query.grep));
4668
- if (options.grep) runner.grep(options.grep);
4669
- if (options.ignoreLeaks) runner.ignoreLeaks = true;
4670
- if (options.globals) runner.globals(options.globals);
4671
- runner.globals(['location']);
4672
- runner.on('end', highlightCode);
4673
- return runner.run(fn);
4847
+ var options = mocha.options;
4848
+ mocha.globals('location');
4849
+
4850
+ var query = Mocha.utils.parseQuery(window.location.search || '');
4851
+ if (query.grep) mocha.grep(query.grep);
4852
+
4853
+ return Mocha.prototype.run.call(mocha, function(){
4854
+ Mocha.utils.highlightTags('code');
4855
+ if (fn) fn();
4856
+ });
4674
4857
  };
4675
4858
  })();
4676
4859
  })();