From 1ae9d44c53301e97d8e21f421a5947429b25b915 Mon Sep 17 00:00:00 2001 From: Ariel Mashraki Date: Fri, 20 Mar 2015 16:06:03 +0200 Subject: [PATCH] feat(runner): fix '.only()' exclusive feature, #1481 --- Makefile | 18 +++- lib/interfaces/bdd.js | 13 ++- lib/interfaces/qunit.js | 13 ++- lib/interfaces/tdd.js | 13 ++- lib/mocha.js | 2 +- lib/runner.js | 22 ++++ test/acceptance/misc/global-only/bdd.js | 12 +++ test/acceptance/misc/global-only/qunit.js | 12 +++ test/acceptance/misc/global-only/tdd.js | 12 +++ test/acceptance/misc/only/bdd.js | 123 ++++++++++++++++++++-- test/acceptance/misc/only/qunit.js | 70 ++++++++++-- test/acceptance/misc/only/tdd.js | 123 ++++++++++++++++++++-- 12 files changed, 400 insertions(+), 33 deletions(-) create mode 100644 test/acceptance/misc/global-only/bdd.js create mode 100644 test/acceptance/misc/global-only/qunit.js create mode 100644 test/acceptance/misc/global-only/tdd.js diff --git a/Makefile b/Makefile index 6afdbac32c..71d48af232 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ lib-cov: test: test-unit -test-all: test-bdd test-tdd test-qunit test-exports test-unit test-grep test-jsapi test-compilers test-sort test-glob test-requires test-reporters test-only test-failing test-regression +test-all: test-bdd test-tdd test-qunit test-exports test-unit test-grep test-jsapi test-compilers test-sort test-glob test-requires test-reporters test-global-only test-only test-failing test-regression test-jsapi: @node test/jsapi @@ -164,6 +164,22 @@ test-only: --ui qunit \ test/acceptance/misc/only/qunit +test-global-only: + @./bin/mocha \ + --reporter $(REPORTER) \ + --ui tdd \ + test/acceptance/misc/global-only/tdd + + @./bin/mocha \ + --reporter $(REPORTER) \ + --ui bdd \ + test/acceptance/misc/global-only/bdd + + @./bin/mocha \ + --reporter $(REPORTER) \ + --ui qunit \ + test/acceptance/misc/global-only/qunit + test-sort: @./bin/mocha \ --reporter $(REPORTER) \ diff --git a/lib/interfaces/bdd.js b/lib/interfaces/bdd.js index f9f08a3ed0..86d68b20c5 100644 --- a/lib/interfaces/bdd.js +++ b/lib/interfaces/bdd.js @@ -69,9 +69,10 @@ module.exports = function(suite){ * Exclusive suite. */ - context.describe.only = function(title, fn){ + context.describe.only = function(title, fn) { var suite = context.describe(title, fn); - mocha.grep(suite.fullTitle()); + mocha.options.hasOnly = true; + suite.isOnly = true; return suite; }; @@ -95,9 +96,11 @@ module.exports = function(suite){ */ context.it.only = function(title, fn){ - var test = context.it(title, fn); - var reString = '^' + escapeRe(test.fullTitle()) + '$'; - mocha.grep(new RegExp(reString)); + var test = context.it(title, fn) + , suite = test.parent; + mocha.options.hasOnly = true; + suite.isOnly = true; + suite.onlyTests = (suite.onlyTests || []).concat(test); return test; }; diff --git a/lib/interfaces/qunit.js b/lib/interfaces/qunit.js index 6668967ca3..27559c1677 100644 --- a/lib/interfaces/qunit.js +++ b/lib/interfaces/qunit.js @@ -62,7 +62,9 @@ module.exports = function(suite){ context.suite.only = function(title, fn){ var suite = context.suite(title, fn); - mocha.grep(suite.fullTitle()); + mocha.options.hasOnly = true; + suite.isOnly = true; + return suite; }; /** @@ -83,9 +85,12 @@ module.exports = function(suite){ */ context.test.only = function(title, fn){ - var test = context.test(title, fn); - var reString = '^' + escapeRe(test.fullTitle()) + '$'; - mocha.grep(new RegExp(reString)); + var test = context.test(title, fn) + , suite = test.parent; + mocha.options.hasOnly = true; + suite.isOnly = true; + suite.onlyTests = (suite.onlyTests || []).concat(test); + return test; }; context.test.skip = common.test.skip; diff --git a/lib/interfaces/tdd.js b/lib/interfaces/tdd.js index 13bc2a33be..f6c199093b 100644 --- a/lib/interfaces/tdd.js +++ b/lib/interfaces/tdd.js @@ -76,7 +76,9 @@ module.exports = function(suite){ context.suite.only = function(title, fn){ var suite = context.suite(title, fn); - mocha.grep(suite.fullTitle()); + mocha.options.hasOnly = true; + suite.isOnly = true; + return suite; }; /** @@ -99,9 +101,12 @@ module.exports = function(suite){ */ context.test.only = function(title, fn){ - var test = context.test(title, fn); - var reString = '^' + escapeRe(test.fullTitle()) + '$'; - mocha.grep(new RegExp(reString)); + var test = context.test(title, fn) + , suite = test.parent; + mocha.options.hasOnly = true; + suite.isOnly = true; + suite.onlyTests = (suite.onlyTests || []).concat(test); + return test; }; context.test.skip = common.test.skip; diff --git a/lib/mocha.js b/lib/mocha.js index 8a11124cb1..8e0504eaf3 100644 --- a/lib/mocha.js +++ b/lib/mocha.js @@ -427,6 +427,7 @@ Mocha.prototype.run = function(fn){ var reporter = new this._reporter(runner, options); runner.ignoreLeaks = false !== options.ignoreLeaks; runner.fullStackTrace = options.fullStackTrace; + runner.hasOnly = options.hasOnly; runner.asyncOnly = options.asyncOnly; if (options.grep) runner.grep(options.grep, options.invert); if (options.globals) runner.globals(options.globals); @@ -441,6 +442,5 @@ Mocha.prototype.run = function(fn){ reporter.done(failures, fn); } else fn && fn(failures); } - return runner.run(done); }; diff --git a/lib/runner.js b/lib/runner.js index a985ce9736..0a0f9e4f0c 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -514,6 +514,7 @@ Runner.prototype.runSuite = function(suite, fn){ , self = this , i = 0; + debug('run suite %s', suite.fullTitle()); if (!total) return fn(); @@ -606,6 +607,9 @@ Runner.prototype.run = function(fn){ var self = this, rootSuite = this.suite; + // If there is an `only` filter + if (this.hasOnly) filterOnly(rootSuite); + fn = fn || function(){}; function uncaught(err){ @@ -656,6 +660,24 @@ Runner.prototype.abort = function(){ this._abort = true; }; +/** + * Filter suites based on `isOnly` logic. + * + * @param {Array} suite + * @returns {Boolean} + * @api private + */ +function filterOnly(suite) { + // If it has `only` tests, run only those + if (suite.onlyTests) suite.tests = suite.onlyTests; + // Filter the nested suites + suite.suites = filter(suite.suites, filterOnly); + // Don't run tests from suites that are not marked as `only` + suite.tests = suite.isOnly ? suite.tests : []; + // Keep the suite only if there is something to run + return suite.suites.length || suite.tests.length; +} + /** * Filter leaks with the given globals flagged as `ok`. * diff --git a/test/acceptance/misc/global-only/bdd.js b/test/acceptance/misc/global-only/bdd.js new file mode 100644 index 0000000000..e923876f84 --- /dev/null +++ b/test/acceptance/misc/global-only/bdd.js @@ -0,0 +1,12 @@ +// Root-only test cases +it.only('#Root-Suite, should run this bdd test-case #1', function() { + (true).should.equal(true); +}); + +it('#Root-Suite, should not run this bdd test-case #2', function() { + (false).should.equal(true); +}); + +it('#Root-Suite, should not run this bdd test-case #3', function() { + (false).should.equal(true); +}); \ No newline at end of file diff --git a/test/acceptance/misc/global-only/qunit.js b/test/acceptance/misc/global-only/qunit.js new file mode 100644 index 0000000000..9c2f69de59 --- /dev/null +++ b/test/acceptance/misc/global-only/qunit.js @@ -0,0 +1,12 @@ +// Root-only test cases +test.only('#Root-Suite, should run this qunit test-case #1', function() { + (true).should.equal(true); +}); + +test('#Root-Suite, should not run this qunit test-case #2', function() { + (false).should.equal(true); +}); + +test('#Root-Suite, should not run this qunit test-case #3', function() { + (false).should.equal(true); +}); diff --git a/test/acceptance/misc/global-only/tdd.js b/test/acceptance/misc/global-only/tdd.js new file mode 100644 index 0000000000..8635cb09a2 --- /dev/null +++ b/test/acceptance/misc/global-only/tdd.js @@ -0,0 +1,12 @@ +// Root-only test cases +test.only('#Root-Suite, should run this tdd test-case #1', function() { + (true).should.equal(true); +}); + +test('#Root-Suite, should not run this tdd test-case #2', function() { + (false).should.equal(true); +}); + +test('#Root-Suite, should not run this tdd test-case #3', function() { + (false).should.equal(true); +}); diff --git a/test/acceptance/misc/only/bdd.js b/test/acceptance/misc/only/bdd.js index ff14dcdfe3..6dc3757aec 100644 --- a/test/acceptance/misc/only/bdd.js +++ b/test/acceptance/misc/only/bdd.js @@ -1,14 +1,125 @@ describe('should only run .only test in this bdd suite', function() { it('should not run this test', function() { - var zero = 0; - zero.should.equal(1, 'this test should have been skipped'); + (0).should.equal(1, 'this test should have been skipped'); }); it.only('should run this test', function() { - var zero = 0; - zero.should.equal(0, 'this .only test should run'); + (0).should.equal(0, 'this .only test should run'); }); it('should run this test, not (includes the title of the .only test)', function() { - var zero = 0; - zero.should.equal(1, 'this test should have been skipped'); + (0).should.equal(1, 'this test should have been skipped'); }); }); + +describe('should not run this suite', function() { + it('should not run this test', function() { + (true).should.equal(false); + }); + + it('should not run this test', function() { + (true).should.equal(false); + }); + + it('should not run this test', function() { + (true).should.equal(false); + }); +}); + +describe.only('should run all tests in this bdd suite', function() { + it('should run this test #1', function() { + (true).should.equal(true); + }); + + it('should run this test #2', function() { + (1).should.equal(1); + }); + + it('should run this test #3', function() { + ('foo').should.equal('foo'); + }); +}); + +describe('should run only suites that marked as `only`', function() { + describe.only('should run all this tdd suite', function() { + it('should run this test #1', function() { + (true).should.equal(true); + }); + + it('should run this test #2', function() { + (true).should.equal(true); + }); + }); + + describe('should not run this suite', function() { + it('should run this test', function() { + (true).should.equal(false); + }); + }); +}); + +// Nested situation +describe('should not run parent tests', function() { + it('should not run this test', function() { + (true).should.equal(false); + }); + describe('and not the child tests too', function() { + it('should not run this test', function() { + (true).should.equal(false); + }); + describe.only('but run all the tests in this suite', function() { + it('should run this test #1', function() { + (true).should.equal(true); + }); + it('should run this test #2', function() { + (true).should.equal(true); + }); + }); + }); +}); + +// mark test as `only` override the suite behavior +describe.only('should run only tests that marked as `only`', function() { + it('should not run this test #1', function() { + (false).should.equal(true); + }); + + it.only('should run this test #2', function() { + (true).should.equal(true); + }); + + it('should not run this test #3', function() { + (false).should.equal(true); + }); + + it.only('should run this test #4', function() { + (true).should.equal(true); + }); +}); + +describe.only('Should run only test cases that mark as only', function() { + it.only('should runt his test', function() { + (true).should.equal(true); + }); + + it('should not run this test', function() { + (false).should.equal(true); + }); + + describe('should not run this suite', function() { + it('should not run this test', function() { + (false).should.equal(true); + }); + }); +}); + +// Root Suite +it.only('#Root-Suite, should run this test-case #1', function() { + (true).should.equal(true); +}); + +it.only('#Root-Suite, should run this test-case #2', function() { + (true).should.equal(true); +}); + +it('#Root-Suite, should not run this test', function() { + (false).should.equal(true); +}); \ No newline at end of file diff --git a/test/acceptance/misc/only/qunit.js b/test/acceptance/misc/only/qunit.js index 07c240f617..cd65e7fc76 100644 --- a/test/acceptance/misc/only/qunit.js +++ b/test/acceptance/misc/only/qunit.js @@ -1,15 +1,73 @@ -function ok(expr, msg) { - if (!expr) throw new Error(msg); -} +// Root Suite +test.only('#Root-Suite, should run this test-case #1', function() { + (true).should.equal(true); +}); + +test.only('#Root-Suite, should run this test-case #2', function() { + (true).should.equal(true); +}); + +test('#Root-Suite, should not run this test', function() { + (false).should.equal(true); +}); suite('should only run .only test in this qunit suite'); test('should not run this test', function() { - ok(0 === 1, 'this test should have been skipped'); + (0).should.equal(1, 'this test should have been skipped'); }); test.only('should run this test', function() { - ok(0 === 0, 'this .only test should run'); + (0).should.equal(0, 'this .only test should run'); }); test('should run this test, not (includes the title of the .only test)', function() { - ok(0 === 1, 'this test should have been skipped'); + (0).should.equal(1, 'this test should have been skipped'); +}); + +// Mark suite +suite.only('should run all tests in this suite'); + +test('should run this test #1', function() { + (true).should.equal(true); +}); + +test('should run this test #2', function() { + (true).should.equal(true); }); + +test('should run this test #3', function() { + (true).should.equal(true); +}); + +// Unmark this suite +suite('should not run any of this suite\'s tests'); + +test('should not run this test', function() { + (false).should.equal(true); +}); + +test('should not run this test', function() { + (false).should.equal(true); +}); + +test('should not run this test', function() { + (false).should.equal(true); +}); + +// Mark test as `only` override the suite behavior +suite.only('should run only tests that marked as `only`'); + +test('should not run this test #1', function() { + (false).should.equal(true); +}); + +test.only('should not run this test #2', function() { + (true).should.equal(true); +}); + +test('should not run this test #3', function() { + (false).should.equal(true); +}); + +test.only('should not run this test #4', function() { + (true).should.equal(true); +}); \ No newline at end of file diff --git a/test/acceptance/misc/only/tdd.js b/test/acceptance/misc/only/tdd.js index cb6429a3d6..f08dbd51a0 100644 --- a/test/acceptance/misc/only/tdd.js +++ b/test/acceptance/misc/only/tdd.js @@ -1,14 +1,125 @@ suite('should only run .only test in this tdd suite', function() { test('should not run this test', function() { - var zero = 0; - zero.should.equal(1, 'this test should have been skipped'); + (0).should.equal(1, 'this test should have been skipped'); }); test.only('should run this test', function() { - var zero = 0; - zero.should.equal(0, 'this .only test should run'); + (0).should.equal(0, 'this .only test should run'); }); test('should run this test, not (includes the title of the .only test)', function() { - var zero = 0; - zero.should.equal(1, 'this test should have been skipped'); + (0).should.equal(1, 'this test should have been skipped'); }); }); + +suite('should not run this suite', function() { + test('should not run this test', function() { + (true).should.equal(false); + }); + + test('should not run this test', function() { + (true).should.equal(false); + }); + + test('should not run this test', function() { + (true).should.equal(false); + }); +}); + +suite.only('should run all tests in this tdd suite', function() { + test('should run this test #1', function() { + (true).should.equal(true); + }); + + test('should run this test #2', function() { + (1).should.equal(1); + }); + + test('should run this test #3', function() { + ('foo').should.equal('foo'); + }); +}); + +suite('should run only suites that marked as `only`', function() { + suite.only('should run all this tdd suite', function() { + test('should run this test #1', function() { + (true).should.equal(true); + }); + + test('should run this test #2', function() { + (true).should.equal(true); + }); + }); + + suite('should not run this suite', function() { + test('should not run this test', function() { + (true).should.equal(false); + }); + }); +}); + +// Nested situation +suite('should not run parent tests', function() { + test('should not run this test', function() { + (true).should.equal(false); + }); + suite('and not the child tests too', function() { + test('should not run this test', function() { + (true).should.equal(false); + }); + suite.only('but run all the tests in this suite', function() { + test('should run this test #1', function() { + (true).should.equal(true); + }); + test('should run this test #2', function() { + (true).should.equal(true); + }); + }); + }); +}); + +// mark test as `only` override the suite behavior +suite.only('should run only tests that marked as `only`', function() { + test('should not run this test #1', function() { + (false).should.equal(true); + }); + + test.only('should run this test #2', function() { + (true).should.equal(true); + }); + + test('should not run this test #3', function() { + (false).should.equal(true); + }); + + test.only('should run this test #4', function() { + (true).should.equal(true); + }); +}); + +suite.only('Should run only test cases that mark as only', function() { + test.only('should runt his test', function() { + (true).should.equal(true); + }); + + test('should not run this test', function() { + (false).should.equal(true); + }); + + suite('should not run this suite', function() { + test('should not run this test', function() { + (false).should.equal(true); + }); + }); +}); + +// Root Suite +test.only('#Root-Suite, should run this test-case #1', function() { + (true).should.equal(true); +}); + +test.only('#Root-Suite, should run this test-case #2', function() { + (true).should.equal(true); +}); + +test('#Root-Suite, should not run this test', function() { + (false).should.equal(true); +}); \ No newline at end of file