From 385b39107fcd85c33cc1ad7b0ee84410263075a7 Mon Sep 17 00:00:00 2001 From: Ariel Mashraki Date: Wed, 9 Mar 2016 15:15:56 +0200 Subject: [PATCH] feat(runner): fix '.only()' exclusive feature, #1481 This PR fix #1481, and also extends the .only() behaviour. (i.e: it's not use grep anymore, support suite, test-case or both, add the ability to run multiple .only) --- Makefile | 18 ++- lib/interfaces/bdd.js | 10 +- lib/interfaces/common.js | 31 +++++ lib/interfaces/qunit.js | 8 +- lib/interfaces/tdd.js | 8 +- lib/mocha.js | 1 + lib/runner.js | 25 ++++ test/acceptance/misc/only/bdd.js | 125 +++++++++++++++++- test/acceptance/misc/only/global/bdd.js | 12 ++ test/acceptance/misc/only/global/qunit.js | 12 ++ test/acceptance/misc/only/global/tdd.js | 12 ++ test/acceptance/misc/only/qunit.js | 72 +++++++++- test/acceptance/misc/only/tdd.js | 125 +++++++++++++++++- test/integration/fixtures/options/only/bdd.js | 21 +++ .../fixtures/options/only/qunit.js | 26 ++++ test/integration/fixtures/options/only/tdd.js | 23 ++++ test/integration/only.js | 39 ++++++ 17 files changed, 526 insertions(+), 42 deletions(-) create mode 100644 test/acceptance/misc/only/global/bdd.js create mode 100644 test/acceptance/misc/only/global/qunit.js create mode 100644 test/acceptance/misc/only/global/tdd.js create mode 100644 test/integration/fixtures/options/only/bdd.js create mode 100644 test/integration/fixtures/options/only/qunit.js create mode 100644 test/integration/fixtures/options/only/tdd.js create mode 100644 test/integration/only.js diff --git a/Makefile b/Makefile index 53cb5657a1..70650bfc0e 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ lint: test: lint test-unit -test-all: lint test-bdd test-tdd test-qunit test-exports test-unit test-integration test-jsapi test-compilers test-glob test-requires test-reporters test-only +test-all: lint test-bdd test-tdd test-qunit test-exports test-unit test-integration test-jsapi test-compilers test-glob test-requires test-reporters test-only test-global-only test-jsapi: @node test/jsapi @@ -117,6 +117,22 @@ test-only: --ui qunit \ test/acceptance/misc/only/qunit +test-global-only: + @./bin/mocha \ + --reporter $(REPORTER) \ + --ui tdd \ + test/acceptance/misc/only/global/tdd + + @./bin/mocha \ + --reporter $(REPORTER) \ + --ui bdd \ + test/acceptance/misc/only/global/bdd + + @./bin/mocha \ + --reporter $(REPORTER) \ + --ui qunit \ + test/acceptance/misc/only/global/qunit + test-mocha: @./bin/mocha \ --reporter $(REPORTER) \ diff --git a/lib/interfaces/bdd.js b/lib/interfaces/bdd.js index 253f24e488..691394ce9e 100644 --- a/lib/interfaces/bdd.js +++ b/lib/interfaces/bdd.js @@ -4,7 +4,6 @@ var Suite = require('../suite'); var Test = require('../test'); -var escapeRe = require('escape-string-regexp'); /** * BDD-style interface: @@ -66,9 +65,7 @@ module.exports = function(suite) { */ context.describe.only = function(title, fn) { - var suite = context.describe(title, fn); - mocha.grep(suite.fullTitle()); - return suite; + return common.suite.only(mocha, context.describe(title, fn)); }; /** @@ -93,10 +90,7 @@ 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)); - return test; + return common.test.only(mocha, context.it(title, fn)); }; /** diff --git a/lib/interfaces/common.js b/lib/interfaces/common.js index 4a21c16c69..d7b085f651 100644 --- a/lib/interfaces/common.js +++ b/lib/interfaces/common.js @@ -62,7 +62,38 @@ module.exports = function(suites, context) { suites[0].afterEach(name, fn); }, + suite: { + /** + * Exclusive suite. + * + * @param {Object} mocha + * @param {Function} suite + */ + + only: function(mocha, suite) { + suite.isOnly = true; + mocha.options.hasOnly = true; + return suite; + } + }, + test: { + + /** + * Exclusive test-case. + * + * @param {Object} mocha + * @param {Function} test + * @returns {*} + */ + only: function(mocha, test) { + var suite = test.parent; + suite.isOnly = true; + suite.onlyTests = (suite.onlyTests || []).concat(test); + mocha.options.hasOnly = true; + return test; + }, + /** * Pending test case. * diff --git a/lib/interfaces/qunit.js b/lib/interfaces/qunit.js index be7f50fb40..c2e3b13436 100644 --- a/lib/interfaces/qunit.js +++ b/lib/interfaces/qunit.js @@ -4,7 +4,6 @@ var Suite = require('../suite'); var Test = require('../test'); -var escapeRe = require('escape-string-regexp'); /** * QUnit-style interface: @@ -61,8 +60,7 @@ module.exports = function(suite) { */ context.suite.only = function(title, fn) { - var suite = context.suite(title, fn); - mocha.grep(suite.fullTitle()); + return common.suite.only(mocha, context.suite(title, fn)); }; /** @@ -83,9 +81,7 @@ 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)); + return common.test.only(mocha, context.test(title, fn)); }; context.test.skip = common.test.skip; diff --git a/lib/interfaces/tdd.js b/lib/interfaces/tdd.js index fb22a79190..04f4c49372 100644 --- a/lib/interfaces/tdd.js +++ b/lib/interfaces/tdd.js @@ -4,7 +4,6 @@ var Suite = require('../suite'); var Test = require('../test'); -var escapeRe = require('escape-string-regexp'); /** * TDD-style interface: @@ -71,8 +70,7 @@ module.exports = function(suite) { * Exclusive test-case. */ context.suite.only = function(title, fn) { - var suite = context.suite(title, fn); - mocha.grep(suite.fullTitle()); + return common.suite.only(mocha, context.suite(title, fn)); }; /** @@ -95,9 +93,7 @@ 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)); + return common.test.only(mocha, context.test(title, fn)); }; context.test.skip = common.test.skip; diff --git a/lib/mocha.js b/lib/mocha.js index 1817ccf2c6..879a94f4ac 100644 --- a/lib/mocha.js +++ b/lib/mocha.js @@ -433,6 +433,7 @@ Mocha.prototype.run = function(fn) { var reporter = new this._reporter(runner, options); runner.ignoreLeaks = options.ignoreLeaks !== false; runner.fullStackTrace = options.fullStackTrace; + runner.hasOnly = options.hasOnly; runner.asyncOnly = options.asyncOnly; if (options.grep) { runner.grep(options.grep, options.invert); diff --git a/lib/runner.js b/lib/runner.js index f6a8fc2437..23d136ce91 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -635,6 +635,11 @@ Runner.prototype.run = function(fn) { var self = this; var rootSuite = this.suite; + // If there is an `only` filter + if (this.hasOnly) { + filterOnly(rootSuite); + } + fn = fn || function() {}; function uncaught(err) { @@ -686,6 +691,26 @@ Runner.prototype.abort = function() { return this; }; +/** + * 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/only/bdd.js b/test/acceptance/misc/only/bdd.js index ff14dcdfe3..b0f8a7aa7a 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'); + it.only('should run this test', function() { + (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/global/bdd.js b/test/acceptance/misc/only/global/bdd.js new file mode 100644 index 0000000000..e923876f84 --- /dev/null +++ b/test/acceptance/misc/only/global/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/only/global/qunit.js b/test/acceptance/misc/only/global/qunit.js new file mode 100644 index 0000000000..59ad72c3bf --- /dev/null +++ b/test/acceptance/misc/only/global/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); +}); \ No newline at end of file diff --git a/test/acceptance/misc/only/global/tdd.js b/test/acceptance/misc/only/global/tdd.js new file mode 100644 index 0000000000..08d456848b --- /dev/null +++ b/test/acceptance/misc/only/global/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); +}); \ No newline at end of file diff --git a/test/acceptance/misc/only/qunit.js b/test/acceptance/misc/only/qunit.js index 07c240f617..1248adeba7 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'); +test.only('should run this test', function() { + (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..3e1716180f 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'); + test.only('should run this test', function() { + (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 diff --git a/test/integration/fixtures/options/only/bdd.js b/test/integration/fixtures/options/only/bdd.js new file mode 100644 index 0000000000..f2fc4fd40b --- /dev/null +++ b/test/integration/fixtures/options/only/bdd.js @@ -0,0 +1,21 @@ +describe.only('should run this suite', function() { + it('should run this test', function() {}); + + it('should run this test', function() {}); + + it('should run this test', function() {}); +}); + +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); + }); +}); \ No newline at end of file diff --git a/test/integration/fixtures/options/only/qunit.js b/test/integration/fixtures/options/only/qunit.js new file mode 100644 index 0000000000..9fa95f440b --- /dev/null +++ b/test/integration/fixtures/options/only/qunit.js @@ -0,0 +1,26 @@ +suite.only('should run all tests in this suite'); + +test('should run this test #1', function() {}); + +test('should run this test #2', function() {}); + +test('should run this test #3', function() {}); + +test('should run this test #4', function() {}); + +test('should run this test #5', function() {}); + + +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); +}); \ No newline at end of file diff --git a/test/integration/fixtures/options/only/tdd.js b/test/integration/fixtures/options/only/tdd.js new file mode 100644 index 0000000000..6e1e37becf --- /dev/null +++ b/test/integration/fixtures/options/only/tdd.js @@ -0,0 +1,23 @@ +suite.only('should run all tests in this tdd suite', function() { + test('should run this test #1', function() {}); + + test('should run this test #2', function() {}); + + test('should run this test #3', function() {}); + + test('should run this test #4', function() {}); +}); + +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); + }); +}); \ No newline at end of file diff --git a/test/integration/only.js b/test/integration/only.js new file mode 100644 index 0000000000..52c11a3a0b --- /dev/null +++ b/test/integration/only.js @@ -0,0 +1,39 @@ +var run = require('./helpers').runMochaJSON; +var assert = require('assert'); + +describe('.only()', function() { + this.timeout(2000); + + it('should run only tests that marked as `only`', function(done) { + run('options/only/bdd.js', ['--ui', 'bdd'], function(err, res) { + assert(!err); + assert.equal(res.stats.pending, 0); + assert.equal(res.stats.passes, 3); + assert.equal(res.stats.failures, 0); + assert.equal(res.code, 0); + done(); + }); + }); + + it('should run only tests that marked as `only`', function(done) { + run('options/only/tdd.js', ['--ui', 'tdd'], function(err, res) { + assert(!err); + assert.equal(res.stats.pending, 0); + assert.equal(res.stats.passes, 4); + assert.equal(res.stats.failures, 0); + assert.equal(res.code, 0); + done(); + }); + }); + + it('should run only tests that marked as `only`', function(done) { + run('options/only/qunit.js', ['--ui', 'qunit'], function(err, res) { + assert(!err); + assert.equal(res.stats.pending, 0); + assert.equal(res.stats.passes, 5); + assert.equal(res.stats.failures, 0); + assert.equal(res.code, 0); + done(); + }); + }); +}); \ No newline at end of file