diff --git a/lib/block-manager.js b/lib/block-manager.js index caf2924..0197323 100644 --- a/lib/block-manager.js +++ b/lib/block-manager.js @@ -16,7 +16,8 @@ var BlockManager = { whens: [], thens: [], invariants: [], - ands: [] + ands: [], + cleanups: [] }) }, exit: function() { this._blockStack.pop() }, @@ -26,6 +27,7 @@ var BlockManager = { addThenToCurrent: function(fn) { this.current().thens.push(fn) }, addInvariantToCurrent: function(fn) { this.current().invariants.push(fn) }, addAndToCurrent: function(fn) { this.current().ands.push(fn) }, + addCleanupToCurrent: function(fn) { this.current().cleanups.push(fn) }, allGivens: function() { return this._collectAllClausesBy(function(blk){ return blk.givens }) @@ -42,6 +44,14 @@ var BlockManager = { currentAnds: function() { return this.current().ands }, + snapshot: function() { + var cleanupRefs = this._blockStack.toArray().map(function(blk){ return blk.cleanups }) + return { + allCleanups: function() { + return cleanupRefs.reduceRight(concat) + } + } + }, _collectAllClausesBy: function(fn) { return this._blockStack.toArray().map(fn).reduceRight(concat) } diff --git a/lib/core.js b/lib/core.js index 9c0fed6..e13e8cc 100644 --- a/lib/core.js +++ b/lib/core.js @@ -197,7 +197,8 @@ function createCore(opts, blockManager) { givens = blockManager.allGivens(), whens = blockManager.allWhens(), invariants = blockManager.allInvariants(), - ands = blockManager.currentAnds() // keep reference at this moment. `ands` is a empty array at this moment + ands = blockManager.currentAnds(), // keep reference at this moment. `ands` is a empty array at this moment + snapshot = blockManager.snapshot() blockManager.addThenToCurrent(thenFn) @@ -212,6 +213,7 @@ function createCore(opts, blockManager) { .then(runExpectations) .then(function(res){ done() }) .catch(done) + .then(runCleanups) function runPreparations() { return sequencialExecute(givens.concat(whens), function(fn) { @@ -231,6 +233,18 @@ function createCore(opts, blockManager) { }) } + function runCleanups() { + var cleanups = snapshot.allCleanups() + return sequencialExecute(cleanups, function(fn) { + function noop() {} + if (fn.length > 0) + return toPromise(fn.bind(ctx)).catch(noop) + return Promise.resolve().then(function(){ + return fn.call(ctx) + }).catch(noop) + }) + } + function goodHabitGuard(fn) { if (isGoodHabitModeEnabled() && fn.length > 0) throw new GoodHabitViolationError('async ' + fn._keyword + ' is discouraged') @@ -303,6 +317,10 @@ function createCore(opts, blockManager) { blockManager.addAndToCurrent(fn) } + function Cleanup(fn) { + blockManager.addCleanupToCurrent(fn) + } + function createFnFromArgs(args, opts) { opts = opts || {} var fn = u.findFirstThatIsFunction(args) @@ -326,6 +344,7 @@ function createCore(opts, blockManager) { Then: Then, Invariant: Invariant, And: And, + Cleanup: Cleanup, ThenError: ThenError, ThenFail: ThenFail, diff --git a/mocha/register-interface.js b/mocha/register-interface.js index f446f5a..d76ac11 100644 --- a/mocha/register-interface.js +++ b/mocha/register-interface.js @@ -44,6 +44,7 @@ module.exports = function(Mocha, Suite, Test) { context.Then = core.Then context.Invariant = core.Invariant context.And = core.And + context.Cleanup = core.Cleanup context.Then.skip = context.xit context.Failure = core.Failure diff --git a/test/1SAFE/cleanup_spec.coffee b/test/1SAFE/cleanup_spec.coffee new file mode 100644 index 0000000..3eb5ec1 --- /dev/null +++ b/test/1SAFE/cleanup_spec.coffee @@ -0,0 +1,44 @@ +expect = require 'expect.js' + +describe "Cleanup(fn)", -> + + describe "cleanup is run", -> + + context 'when Then pass', -> + info = [] + Then -> 'pass' + And -> info.push 'A' + Cleanup -> info.push "C" + + describe "verify", -> + Then -> expect(["A", "C"]).to.eql info + + context 'when Then fail', -> + info = [] + ThenFail -> false + And -> info.push 'A' + Cleanup -> info.push "C" + + describe "verify", -> + Then -> expect(["C"]).to.eql info + + context 'when other Cleanup fail', -> + info = [] + ThenFail -> false + Cleanup -> throw new Error 'oops!' + Cleanup -> info.push "C" + + describe "verify", -> + Then -> expect(["C"]).to.eql info + + describe "with nested cleanups", -> + info = [] + Cleanup -> info.push "C-OUTER" + + context "inner", -> + Then -> info.push "T-INNER" + And -> info.push "A-INNER" + Cleanup -> info.push "C-INNER" + + context "verify", -> + Then -> expect(info).to.eql ["T-INNER", "A-INNER", "C-OUTER", "C-INNER"]