konacha 1.2.2 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/History.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # master
2
2
 
3
+ # 1.2.3
4
+
5
+ * Update mocha (1.1.0) and chai (1.0.4)
6
+ * Traverse symlinked directories when discovering specs
7
+
3
8
  # 1.2.2
4
9
 
5
10
  * Update chai (1.0.3)
@@ -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.2.2"
20
+ gem.version = "1.2.3"
21
21
 
22
22
  gem.add_dependency "rails", "~> 3.1"
23
23
  gem.add_dependency "capybara"
@@ -33,7 +33,7 @@ module Konacha
33
33
  end
34
34
 
35
35
  def spec_paths
36
- Dir[File.join(spec_root, "**/*{_spec,_test}.*")].map do |path|
36
+ Dir[File.join(spec_root, "**{,/*/**}/*{_spec,_test}.*")].map do |path|
37
37
  path.gsub(File.join(spec_root, ''), '')
38
38
  end
39
39
  end
@@ -23,6 +23,26 @@ describe Konacha do
23
23
  subject.should include("subdirectory/subdirectory_spec.js")
24
24
  end
25
25
 
26
+ it "traverses symlinked directories" do
27
+ begin
28
+ # Create a directory with specs outside of 'spec/javascripts'.
29
+ Dir.mkdir "spec/dummy/app/external_specs"
30
+ File.new "spec/dummy/app/external_specs/my_spec.js", "w"
31
+
32
+ # Symlink it into 'spec/javascripts'.
33
+ File.symlink "../../app/external_specs/", "spec/dummy/spec/javascripts/external_specs"
34
+
35
+ subject.should include("external_specs/my_spec.js")
36
+
37
+ File.unlink "spec/dummy/spec/javascripts/external_specs"
38
+ rescue NotImplementedError
39
+ # Don't test this on platforms that don't support symlinking.
40
+ end
41
+
42
+ File.unlink "spec/dummy/app/external_specs/my_spec.js"
43
+ Dir.unlink "spec/dummy/app/external_specs"
44
+ end
45
+
26
46
  it "does not include spec_helper" do
27
47
  subject.should_not include("spec_helper.js")
28
48
  end
@@ -305,11 +305,11 @@ Assertion.addChainableMethod('a', an);
305
305
  * @api public
306
306
  */
307
307
 
308
- function includeChainingBehavior() {
308
+ function includeChainingBehavior () {
309
309
  flag(this, 'contains', true);
310
310
  }
311
311
 
312
- function include(val) {
312
+ function include (val) {
313
313
  var obj = flag(this, 'object')
314
314
  this.assert(
315
315
  ~obj.indexOf(val)
@@ -527,24 +527,22 @@ Object.defineProperty(Assertion.prototype, 'empty',
527
527
  * }
528
528
  *
529
529
  * @name arguments
530
+ * @alias Arguments
530
531
  * @api public
531
532
  */
532
533
 
533
- Object.defineProperty(Assertion.prototype, 'arguments',
534
- { get: function () {
535
- var obj = flag(this, 'object');
536
- this.assert(
537
- '[object Arguments]' == Object.prototype.toString.call(obj)
538
- , 'expected #{this} to be arguments'
539
- , 'expected #{this} to not be arguments'
540
- , '[object Arguments]'
541
- , Object.prototype.toString.call(obj)
542
- );
534
+ function checkArguments () {
535
+ var obj = flag(this, 'object')
536
+ , type = Object.prototype.toString.call(obj);
537
+ this.assert(
538
+ '[object Arguments]' === type
539
+ , 'expected #{this} to be arguments but got ' + type
540
+ , 'expected #{this} to not be arguments'
541
+ );
542
+ }
543
543
 
544
- return this;
545
- }
546
- , configurable: true
547
- });
544
+ Assertion.addProperty('arguments', checkArguments);
545
+ Assertion.addProperty('Arguments', checkArguments);
548
546
 
549
547
  /**
550
548
  * ### .equal(value)
@@ -1042,9 +1040,16 @@ Assertion.prototype.Throw = function (constructor, msg) {
1042
1040
  *
1043
1041
  * Asserts that the object or class target will respond to a method.
1044
1042
  *
1043
+ * Klass.prototype.bar = function(){};
1045
1044
  * expect(Klass).to.respondTo('bar');
1046
1045
  * expect(obj).to.respondTo('bar');
1047
1046
  *
1047
+ * To check if a constructor will respond to a static function,
1048
+ * set the `itself` flag.
1049
+ *
1050
+ * Klass.baz = function(){};
1051
+ * expect(Klass).itself.to.respondTo('baz');
1052
+ *
1048
1053
  * @name respondTo
1049
1054
  * @param {String} method
1050
1055
  * @api public
@@ -1052,7 +1057,8 @@ Assertion.prototype.Throw = function (constructor, msg) {
1052
1057
 
1053
1058
  Assertion.prototype.respondTo = function (method) {
1054
1059
  var obj = flag(this, 'object')
1055
- , context = ('function' === typeof obj)
1060
+ , itself = flag(this, 'itself')
1061
+ , context = ('function' === typeof obj && !itself)
1056
1062
  ? obj.prototype[method]
1057
1063
  : obj[method];
1058
1064
 
@@ -1067,6 +1073,29 @@ Assertion.prototype.respondTo = function (method) {
1067
1073
  return this;
1068
1074
  };
1069
1075
 
1076
+ /**
1077
+ * ### .itself
1078
+ *
1079
+ * Sets the `itself` flag, later used by the `respondTo` assertion.
1080
+ *
1081
+ * function Foo() {}
1082
+ * Foo.bar = function() {}
1083
+ * Foo.prototype.baz = function() {}
1084
+ *
1085
+ * expect(Foo).itself.to.respondTo('bar');
1086
+ * expect(Foo).itself.not.to.respondTo('baz');
1087
+ *
1088
+ * @name itself
1089
+ * @api public
1090
+ */
1091
+ Object.defineProperty(Assertion.prototype, 'itself',
1092
+ { get: function () {
1093
+ flag(this, 'itself', true);
1094
+ return this;
1095
+ }
1096
+ , configurable: true
1097
+ });
1098
+
1070
1099
  /**
1071
1100
  * ### .satisfy(method)
1072
1101
  *
@@ -1183,7 +1212,7 @@ var used = []
1183
1212
  * Chai version
1184
1213
  */
1185
1214
 
1186
- exports.version = '1.0.3';
1215
+ exports.version = '1.0.4';
1187
1216
 
1188
1217
  /*!
1189
1218
  * Primary `Assertion` prototype
@@ -2336,9 +2365,8 @@ var transferFlags = require('./transferFlags');
2336
2365
  */
2337
2366
 
2338
2367
  module.exports = function (ctx, name, method, chainingBehavior) {
2339
- if (typeof chainingBehavior !== 'function') {
2368
+ if (typeof chainingBehavior !== 'function')
2340
2369
  chainingBehavior = function () { };
2341
- }
2342
2370
 
2343
2371
  Object.defineProperty(ctx, name,
2344
2372
  { get: function () {
@@ -2352,20 +2380,15 @@ module.exports = function (ctx, name, method, chainingBehavior) {
2352
2380
  // Re-enumerate every time to better accomodate plugins.
2353
2381
  var asserterNames = Object.getOwnPropertyNames(ctx);
2354
2382
  asserterNames.forEach(function (asserterName) {
2355
- var pd = Object.getOwnPropertyDescriptor(ctx, asserterName);
2356
-
2357
- // Avoid trying to overwrite things that we can't, like `length`
2358
- // and `arguments`.
2359
- var functionProtoPD = Object.getOwnPropertyDescriptor(Function.prototype, asserterName);
2360
- if (functionProtoPD && !functionProtoPD.configurable) {
2361
- return;
2362
- }
2363
-
2383
+ var pd = Object.getOwnPropertyDescriptor(ctx, asserterName)
2384
+ , functionProtoPD = Object.getOwnPropertyDescriptor(Function.prototype, asserterName);
2385
+ // Avoid trying to overwrite things that we can't, like `length` and `arguments`.
2386
+ if (functionProtoPD && !functionProtoPD.configurable) return;
2387
+ if (asserterName === 'arguments') return; // @see chaijs/chai/issues/69
2364
2388
  Object.defineProperty(assert, asserterName, pd);
2365
2389
  });
2366
2390
 
2367
2391
  transferFlags(this, assert);
2368
-
2369
2392
  return assert;
2370
2393
  }
2371
2394
  , configurable: true
@@ -3000,7 +3023,7 @@ function formatValue(ctx, value, recurseTimes) {
3000
3023
 
3001
3024
  // Make error with message first say the error
3002
3025
  if (isError(value)) {
3003
- base = ' ' + formatError(value);
3026
+ return formatError(value);
3004
3027
  }
3005
3028
 
3006
3029
  if (keys.length === 0 && (!array || value.length == 0)) {
@@ -3181,6 +3204,7 @@ function isError(e) {
3181
3204
  function objectToString(o) {
3182
3205
  return Object.prototype.toString.call(o);
3183
3206
  }
3207
+
3184
3208
  }); // module: utils/inspect.js
3185
3209
 
3186
3210
  require.register("utils/overwriteMethod.js", function(module, exports, require){
@@ -80,7 +80,7 @@ function isArray(obj) {
80
80
  /**
81
81
  * Event emitter constructor.
82
82
  *
83
- * @api public.
83
+ * @api public
84
84
  */
85
85
 
86
86
  function EventEmitter(){};
@@ -188,7 +188,7 @@ EventEmitter.prototype.removeAllListeners = function (name) {
188
188
  /**
189
189
  * Gets all listeners for a certain event.
190
190
  *
191
- * @api publci
191
+ * @api public
192
192
  */
193
193
 
194
194
  EventEmitter.prototype.listeners = function (name) {
@@ -560,7 +560,7 @@ module.exports = function(suite){
560
560
  * and/or tests.
561
561
  */
562
562
 
563
- context.describe = function(title, fn){
563
+ context.describe = context.context = function(title, fn){
564
564
  var suite = Suite.create(suites[0], title);
565
565
  suites.unshift(suite);
566
566
  fn();
@@ -870,7 +870,7 @@ exports = module.exports = Mocha;
870
870
  * Library version.
871
871
  */
872
872
 
873
- exports.version = '1.0.3';
873
+ exports.version = '1.1.0';
874
874
 
875
875
  /**
876
876
  * Expose internals.
@@ -908,6 +908,7 @@ function image(name) {
908
908
  * - `globals` array of accepted globals
909
909
  * - `timeout` timeout in milliseconds
910
910
  * - `ignoreLeaks` ignore global leaks
911
+ * - `grep` string or regexp to filter tests with
911
912
  *
912
913
  * @param {Object} options
913
914
  * @api public
@@ -917,6 +918,7 @@ function Mocha(options) {
917
918
  options = options || {};
918
919
  this.files = [];
919
920
  this.options = options;
921
+ this.grep(options.grep);
920
922
  this.suite = new exports.Suite('', new exports.Context);
921
923
  this.ui(options.ui);
922
924
  this.reporter(options.reporter);
@@ -1003,6 +1005,21 @@ Mocha.prototype.growl = function(runner, reporter) {
1003
1005
  });
1004
1006
  };
1005
1007
 
1008
+ /**
1009
+ * Add regexp to grep for to the options object
1010
+ *
1011
+ * @param {RegExp} or {String} re
1012
+ * @return {Mocha}
1013
+ * @api public
1014
+ */
1015
+
1016
+ Mocha.prototype.grep = function(re){
1017
+ this.options.grep = 'string' == typeof re
1018
+ ? new RegExp(re)
1019
+ : re;
1020
+ return this;
1021
+ };
1022
+
1006
1023
  /**
1007
1024
  * Run tests and invoke `fn()` when complete.
1008
1025
  *
@@ -1023,7 +1040,6 @@ Mocha.prototype.run = function(fn){
1023
1040
  if (options.growl) this.growl(runner, reporter);
1024
1041
  return runner.run(fn);
1025
1042
  };
1026
-
1027
1043
  }); // module: mocha.js
1028
1044
 
1029
1045
  require.register("reporters/base.js", function(module, exports, require){
@@ -1035,6 +1051,16 @@ require.register("reporters/base.js", function(module, exports, require){
1035
1051
  var tty = require('browser/tty')
1036
1052
  , diff = require('browser/diff');
1037
1053
 
1054
+ /**
1055
+ * Save timer references to avoid Sinon interfering (see GH-237).
1056
+ */
1057
+
1058
+ var Date = global.Date
1059
+ , setTimeout = global.setTimeout
1060
+ , setInterval = global.setInterval
1061
+ , clearTimeout = global.clearTimeout
1062
+ , clearInterval = global.clearInterval;
1063
+
1038
1064
  /**
1039
1065
  * Check if both stdio streams are associated with a tty.
1040
1066
  */
@@ -1351,7 +1377,7 @@ function pad(str, len) {
1351
1377
 
1352
1378
  function errorDiff(err, type) {
1353
1379
  return diff['diff' + type](err.actual, err.expected).map(function(str){
1354
- if (str.value == '\n') str.value = '<newline>\n';
1380
+ if (/^(\n+)$/.test(str.value)) str.value = Array(++RegExp.$1.length).join('<newline>');
1355
1381
  if (str.added) return colorLines('diff added', str.value);
1356
1382
  if (str.removed) return colorLines('diff removed', str.value);
1357
1383
  return str.value;
@@ -1577,6 +1603,16 @@ var Base = require('./base')
1577
1603
  , Progress = require('../browser/progress')
1578
1604
  , escape = utils.escape;
1579
1605
 
1606
+ /**
1607
+ * Save timer references to avoid Sinon interfering (see GH-237).
1608
+ */
1609
+
1610
+ var Date = global.Date
1611
+ , setTimeout = global.setTimeout
1612
+ , setInterval = global.setInterval
1613
+ , clearTimeout = global.clearTimeout
1614
+ , clearInterval = global.clearInterval;
1615
+
1580
1616
  /**
1581
1617
  * Expose `Doc`.
1582
1618
  */
@@ -1614,7 +1650,8 @@ function HTML(runner) {
1614
1650
  , failures = items[2].getElementsByTagName('em')[0]
1615
1651
  , duration = items[3].getElementsByTagName('em')[0]
1616
1652
  , canvas = stat.getElementsByTagName('canvas')[0]
1617
- , stack = [root]
1653
+ , report = fragment('<ul id="report"></ul>')
1654
+ , stack = [report]
1618
1655
  , progress
1619
1656
  , ctx
1620
1657
 
@@ -1626,6 +1663,7 @@ function HTML(runner) {
1626
1663
  if (!root) return error('#mocha div missing, add it to your document');
1627
1664
 
1628
1665
  root.appendChild(stat);
1666
+ root.appendChild(report);
1629
1667
 
1630
1668
  if (progress) progress.size(40);
1631
1669
 
@@ -1633,11 +1671,12 @@ function HTML(runner) {
1633
1671
  if (suite.root) return;
1634
1672
 
1635
1673
  // suite
1636
- var el = fragment('<div class="suite"><h1>%s</h1></div>', suite.title);
1674
+ var url = location.origin + location.pathname + '?grep=^' + utils.escapeRegexp(suite.fullTitle());
1675
+ var el = fragment('<li class="suite"><h1><a href="%s">%s</a></h1></li>', url, suite.title);
1637
1676
 
1638
1677
  // container
1639
1678
  stack[0].appendChild(el);
1640
- stack.unshift(document.createElement('div'));
1679
+ stack.unshift(document.createElement('ul'));
1641
1680
  el.appendChild(stack[0]);
1642
1681
  });
1643
1682
 
@@ -1663,11 +1702,11 @@ function HTML(runner) {
1663
1702
 
1664
1703
  // test
1665
1704
  if ('passed' == test.state) {
1666
- var el = fragment('<div class="test pass %e"><h2>%e<span class="duration">%ems</span></h2></div>', test.speed, test.title, test.duration);
1705
+ var el = fragment('<li class="test pass %e"><h2>%e<span class="duration">%ems</span></h2></li>', test.speed, test.title, test.duration);
1667
1706
  } else if (test.pending) {
1668
- var el = fragment('<div class="test pass pending"><h2>%e</h2></div>', test.title);
1707
+ var el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title);
1669
1708
  } else {
1670
- var el = fragment('<div class="test fail"><h2>%e</h2></div>', test.title);
1709
+ var el = fragment('<li class="test fail"><h2>%e</h2></li>', test.title);
1671
1710
  var str = test.err.stack || test.err.toString();
1672
1711
 
1673
1712
  // FF / Opera do not add the message
@@ -2702,23 +2741,23 @@ function Teamcity(runner) {
2702
2741
  });
2703
2742
 
2704
2743
  runner.on('test', function(test) {
2705
- console.log("##teamcity[testStarted name='%s']", escape(test.fullTitle()));
2744
+ console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']");
2706
2745
  });
2707
2746
 
2708
2747
  runner.on('fail', function(test, err) {
2709
- console.log("##teamcity[testFailed name='%s' message='%s']", escape(test.fullTitle()), escape(err.message));
2748
+ console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']");
2710
2749
  });
2711
2750
 
2712
2751
  runner.on('pending', function(test) {
2713
- console.log("##teamcity[testIgnored name='%s' message='pending']", escape(test.fullTitle()));
2752
+ console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']");
2714
2753
  });
2715
2754
 
2716
2755
  runner.on('test end', function(test) {
2717
- console.log("##teamcity[testFinished name='%s' duration='%s']", escape(test.fullTitle()), test.duration);
2756
+ console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']");
2718
2757
  });
2719
2758
 
2720
2759
  runner.on('end', function() {
2721
- console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='%s']", stats.duration);
2760
+ console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']");
2722
2761
  });
2723
2762
  }
2724
2763
 
@@ -2727,8 +2766,18 @@ function Teamcity(runner) {
2727
2766
  */
2728
2767
 
2729
2768
  function escape(str) {
2730
- return str.replace(/'/g, "|'");
2769
+ return str
2770
+ .replace(/\|/g, "||")
2771
+ .replace(/\n/g, "|n")
2772
+ .replace(/\r/g, "|r")
2773
+ .replace(/\[/g, "|[")
2774
+ .replace(/\]/g, "|]")
2775
+ .replace(/\u0085/g, "|x")
2776
+ .replace(/\u2028/g, "|l")
2777
+ .replace(/\u2029/g, "|p")
2778
+ .replace(/'/g, "|'");
2731
2779
  }
2780
+
2732
2781
  }); // module: reporters/teamcity.js
2733
2782
 
2734
2783
  require.register("reporters/xunit.js", function(module, exports, require){
@@ -2741,6 +2790,16 @@ var Base = require('./base')
2741
2790
  , utils = require('../utils')
2742
2791
  , escape = utils.escape;
2743
2792
 
2793
+ /**
2794
+ * Save timer references to avoid Sinon interfering (see GH-237).
2795
+ */
2796
+
2797
+ var Date = global.Date
2798
+ , setTimeout = global.setTimeout
2799
+ , setInterval = global.setInterval
2800
+ , clearTimeout = global.clearTimeout
2801
+ , clearInterval = global.clearInterval;
2802
+
2744
2803
  /**
2745
2804
  * Expose `XUnit`.
2746
2805
  */
@@ -2847,6 +2906,16 @@ require.register("runnable.js", function(module, exports, require){
2847
2906
  var EventEmitter = require('browser/events').EventEmitter
2848
2907
  , debug = require('browser/debug')('runnable');
2849
2908
 
2909
+ /**
2910
+ * Save timer references to avoid Sinon interfering (see GH-237).
2911
+ */
2912
+
2913
+ var Date = global.Date
2914
+ , setTimeout = global.setTimeout
2915
+ , setInterval = global.setInterval
2916
+ , clearTimeout = global.clearTimeout
2917
+ , clearInterval = global.clearInterval;
2918
+
2850
2919
  /**
2851
2920
  * Expose `Runnable`.
2852
2921
  */
@@ -3151,6 +3220,9 @@ Runner.prototype.checkGlobals = function(test){
3151
3220
  Runner.prototype.fail = function(test, err){
3152
3221
  ++this.failures;
3153
3222
  test.state = 'failed';
3223
+ if ('string' == typeof err) {
3224
+ err = new Error('the string "' + err + '" was thrown, throw an Error :)');
3225
+ }
3154
3226
  this.emit('fail', test, err);
3155
3227
  };
3156
3228
 
@@ -3978,6 +4050,7 @@ exports.files = function(dir, ret){
3978
4050
  *
3979
4051
  * @param {String} str
3980
4052
  * @return {String}
4053
+ * @api private
3981
4054
  */
3982
4055
 
3983
4056
  exports.slug = function(str){
@@ -3986,6 +4059,18 @@ exports.slug = function(str){
3986
4059
  .replace(/ +/g, '-')
3987
4060
  .replace(/[^-\w]/g, '');
3988
4061
  };
4062
+
4063
+ /**
4064
+ * Escape regular expression characters in `str`.
4065
+ *
4066
+ * @param {String} str
4067
+ * @return {String}
4068
+ * @api private
4069
+ */
4070
+
4071
+ exports.escapeRegexp = function(str){
4072
+ return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
4073
+ };
3989
4074
  }); // module: utils.js
3990
4075
  /**
3991
4076
  * Node shims.
@@ -4,7 +4,16 @@ body {
4
4
  padding: 60px 50px;
5
5
  }
6
6
 
7
- #mocha h1, h2 {
7
+ #mocha ul, #mocha li {
8
+ margin: 0;
9
+ padding: 0;
10
+ }
11
+
12
+ #mocha ul {
13
+ list-style: none;
14
+ }
15
+
16
+ #mocha h1, #mocha h2 {
8
17
  margin: 0;
9
18
  }
10
19
 
@@ -14,6 +23,15 @@ body {
14
23
  font-weight: 200;
15
24
  }
16
25
 
26
+ #mocha h1 a {
27
+ text-decoration: none;
28
+ color: inherit;
29
+ }
30
+
31
+ #mocha h1 a:hover {
32
+ text-decoration: underline;
33
+ }
34
+
17
35
  #mocha .suite .suite h1 {
18
36
  margin-top: 0;
19
37
  font-size: .8em;
@@ -62,6 +80,7 @@ body {
62
80
  display: block;
63
81
  float: left;
64
82
  margin-right: 5px;
83
+ color: #00d6b2;
65
84
  }
66
85
 
67
86
  #mocha .test.pass .duration {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: konacha
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.2.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-29 00:00:00.000000000 Z
12
+ date: 2012-06-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -224,15 +224,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
224
224
  - - ! '>='
225
225
  - !ruby/object:Gem::Version
226
226
  version: '0'
227
+ segments:
228
+ - 0
229
+ hash: 2642027270440006025
227
230
  required_rubygems_version: !ruby/object:Gem::Requirement
228
231
  none: false
229
232
  requirements:
230
233
  - - ! '>='
231
234
  - !ruby/object:Gem::Version
232
235
  version: '0'
236
+ segments:
237
+ - 0
238
+ hash: 2642027270440006025
233
239
  requirements: []
234
240
  rubyforge_project:
235
- rubygems_version: 1.8.23
241
+ rubygems_version: 1.8.24
236
242
  signing_key:
237
243
  specification_version: 3
238
244
  summary: Unit-test your Rails JavaScript with the mocha test framework and chai assertion