diff --git a/lib/console/console.js b/lib/console/console.js index 78b56150..96026ab6 100644 --- a/lib/console/console.js +++ b/lib/console/console.js @@ -54,7 +54,8 @@ getJasmineRequireObj().ConsoleReporter = function() { red: '\x1B[31m', yellow: '\x1B[33m', none: '\x1B[0m' - }; + }, + failedSuites = []; this.jasmineStarted = function() { specCount = 0; @@ -89,9 +90,12 @@ getJasmineRequireObj().ConsoleReporter = function() { printNewline(); var seconds = timer.elapsed() / 1000; print('Finished in ' + seconds + ' ' + plural('second', seconds)); - printNewline(); + for(i = 0; i < failedSuites.length; i++) { + suiteFailureDetails(failedSuites[i]); + } + onComplete(failureCount === 0); }; @@ -116,6 +120,13 @@ getJasmineRequireObj().ConsoleReporter = function() { } }; + this.suiteDone = function(result) { + if (result.failedExpectations && result.failedExpectations.length > 0) { + failureCount++; + failedSuites.push(result); + } + }; + return this; function printNewline() { @@ -160,6 +171,17 @@ getJasmineRequireObj().ConsoleReporter = function() { printNewline(); } + + function suiteFailureDetails(result) { + for (var i = 0; i < result.failedExpectations.length; i++) { + printNewline(); + print(colored('red', 'An error was thrown in an afterAll')); + printNewline(); + print(colored('red', 'AfterAll ' + result.failedExpectations[i].message)); + + } + printNewline(); + } } return ConsoleReporter; diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 9d959032..2874fe4c 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -46,7 +46,8 @@ jasmineRequire.HtmlReporter = function(j$) { failureCount = 0, pendingSpecCount = 0, htmlReporterMain, - symbols; + symbols, + failedSuites = []; this.initialize = function() { clearPrior(); @@ -83,6 +84,10 @@ jasmineRequire.HtmlReporter = function(j$) { }; this.suiteDone = function(result) { + if (result.failedExpectations && result.failedExpectations.length > 0) { + failedSuites.push(result); + } + if (currentParent == topResults) { return; } @@ -178,6 +183,15 @@ jasmineRequire.HtmlReporter = function(j$) { alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); + for(i = 0; i < failedSuites.length; i++) { + var failedSuite = failedSuites[i]; + for(var j = 0; j < failedSuite.failedExpectations.length; j++) { + var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message; + var errorBarClassName = 'bar errored'; + alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage)); + } + } + var results = find('.results'); results.appendChild(summary); diff --git a/lib/jasmine-core/jasmine.css b/lib/jasmine-core/jasmine.css index fb2afd13..7ae58348 100644 --- a/lib/jasmine-core/jasmine.css +++ b/lib/jasmine-core/jasmine.css @@ -30,6 +30,7 @@ body { overflow-y: scroll; } .jasmine_html-reporter .bar.failed { background-color: #ca3a11; } .jasmine_html-reporter .bar.passed { background-color: #007069; } .jasmine_html-reporter .bar.skipped { background-color: #bababa; } +.jasmine_html-reporter .bar.errored { background-color: #ca3a11; } .jasmine_html-reporter .bar.menu { background-color: #fff; color: #aaaaaa; } .jasmine_html-reporter .bar.menu a { color: #333333; } .jasmine_html-reporter .bar a { color: white; } diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 1685cf04..5fafa405 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -55,6 +55,7 @@ getJasmineRequireObj = (function (jasmineGlobal) { j$.QueueRunner = jRequire.QueueRunner(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(); j$.Spec = jRequire.Spec(j$); + j$.SpyRegistry = jRequire.SpyRegistry(j$); j$.SpyStrategy = jRequire.SpyStrategy(); j$.Suite = jRequire.Suite(); j$.Timer = jRequire.Timer(); @@ -242,6 +243,21 @@ getJasmineRequireObj().util = function() { return false; }; + util.clone = function(obj) { + if (Object.prototype.toString.apply(obj) === '[object Array]') { + return obj.slice(); + } + + var cloned = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + cloned[prop] = obj[prop]; + } + } + + return cloned; + }; + return util; }; @@ -251,17 +267,16 @@ getJasmineRequireObj().Spec = function(j$) { this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.description = attrs.description || ''; - this.fn = attrs.fn; - this.beforeFns = attrs.beforeFns || function() { return []; }; - this.afterFns = attrs.afterFns || function() { return []; }; + this.queueableFn = attrs.queueableFn; + this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; + this.userContext = attrs.userContext || function() { return {}; }; this.onStart = attrs.onStart || function() {}; - this.exceptionFormatter = attrs.exceptionFormatter || function() {}; this.getSpecName = attrs.getSpecName || function() { return ''; }; this.expectationResultFactory = attrs.expectationResultFactory || function() { }; this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; - if (!this.fn) { + if (!this.queueableFn.fn) { this.pend(); } @@ -297,30 +312,16 @@ getJasmineRequireObj().Spec = function(j$) { return; } - var allFns = this.beforeFns().concat(this.fn).concat(this.afterFns()); + var fns = this.beforeAndAfterFns(); + var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); this.queueRunnerFactory({ - fns: allFns, - onException: onException, + queueableFns: allFns, + onException: function() { self.onException.apply(self, arguments); }, onComplete: complete, - enforceTimeout: function() { return true; } + userContext: this.userContext() }); - function onException(e) { - if (Spec.isPendingSpecException(e)) { - self.pend(); - return; - } - - self.addExpectationResult(false, { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: e - }); - } - function complete() { self.result.status = self.status(); self.resultCallback(self.result); @@ -331,6 +332,21 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.onException = function onException(e) { + if (Spec.isPendingSpecException(e)) { + this.pend(); + return; + } + + this.addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: e + }); + }; + Spec.prototype.disable = function() { this.disabled = true; }; @@ -355,6 +371,10 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.isExecutable = function() { + return !this.disabled && !this.markedPending; + }; + Spec.prototype.getFullName = function() { return this.getSpecName(this); }; @@ -388,11 +408,19 @@ getJasmineRequireObj().Env = function(j$) { this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler(), new j$.MockDate(global)); var runnableLookupTable = {}; - - var spies = []; + var runnableResources = {}; var currentSpec = null; - var currentSuite = null; + var currentlyExecutingSuites = []; + var currentDeclarationSuite = null; + + var currentSuite = function() { + return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; + }; + + var currentRunnable = function() { + return currentSpec || currentSuite(); + }; var reporter = new j$.ReportDispatcher([ 'jasmineStarted', @@ -407,11 +435,21 @@ getJasmineRequireObj().Env = function(j$) { return true; }; - var equalityTesters = []; - - var customEqualityTesters = []; this.addCustomEqualityTester = function(tester) { - customEqualityTesters.push(tester); + if(!currentRunnable()) { + throw new Error('Custom Equalities must be added in a before function or a spec'); + } + runnableResources[currentRunnable().id].customEqualityTesters.push(tester); + }; + + this.addMatchers = function(matchersToAdd) { + if(!currentRunnable()) { + throw new Error('Matchers must be added in a before function or a spec'); + } + var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { + customMatchers[matcherName] = matchersToAdd[matcherName]; + } }; j$.Expectation.addCoreMatchers(j$.matchers); @@ -429,7 +467,8 @@ getJasmineRequireObj().Env = function(j$) { var expectationFactory = function(actual, spec) { return j$.Expectation.Factory({ util: j$.matchersUtil, - customEqualityTesters: customEqualityTesters, + customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult }); @@ -439,30 +478,44 @@ getJasmineRequireObj().Env = function(j$) { } }; - var specStarted = function(spec) { - currentSpec = spec; - reporter.specStarted(spec.result); + var defaultResourcesForRunnable = function(id, parentRunnableId) { + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + + if(runnableResources[parentRunnableId]){ + resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); + resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); + } + + runnableResources[id] = resources; }; - var beforeFns = function(suite) { + var clearResourcesForRunnable = function(id) { + spyRegistry.clearSpies(); + delete runnableResources[id]; + }; + + var beforeAndAfterFns = function(suite, runnablesExplictlySet) { return function() { - var befores = []; + var befores = [], + afters = [], + beforeAlls = [], + afterAlls = []; + while(suite) { befores = befores.concat(suite.beforeFns); - suite = suite.parentSuite; - } - return befores.reverse(); - }; - }; - - var afterFns = function(suite) { - return function() { - var afters = []; - while(suite) { afters = afters.concat(suite.afterFns); + + if (runnablesExplictlySet()) { + beforeAlls = beforeAlls.concat(suite.beforeAllFns); + afterAlls = afterAlls.concat(suite.afterAllFns); + } + suite = suite.parentSuite; } - return afters; + return { + befores: beforeAlls.reverse().concat(befores.reverse()), + afters: afters.concat(afterAlls) + }; }; }; @@ -520,65 +573,54 @@ getJasmineRequireObj().Env = function(j$) { id: getNextSuiteId(), description: 'Jasmine__TopLevel__Suite', queueRunner: queueRunnerFactory, - resultCallback: function() {} // TODO - hook this up + resultCallback: function(attrs) { + reporter.suiteDone(attrs); + } }); runnableLookupTable[topSuite.id] = topSuite; - currentSuite = topSuite; + defaultResourcesForRunnable(topSuite.id); + currentDeclarationSuite = topSuite; this.topSuite = function() { return topSuite; }; this.execute = function(runnablesToRun) { - runnablesToRun = runnablesToRun || [topSuite.id]; + if(runnablesToRun) { + runnablesExplictlySet = true; + } else if (focusedRunnables.length) { + runnablesExplictlySet = true; + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; + } var allFns = []; for(var i = 0; i < runnablesToRun.length; i++) { var runnable = runnableLookupTable[runnablesToRun[i]]; - allFns.push((function(runnable) { return function(done) { runnable.execute(done); }; })(runnable)); + allFns.push((function(runnable) { return { fn: function(done) { runnable.execute(done); } }; })(runnable)); } reporter.jasmineStarted({ totalSpecsDefined: totalSpecsDefined }); - queueRunnerFactory({fns: allFns, onComplete: reporter.jasmineDone}); + queueRunnerFactory({queueableFns: allFns, onComplete: reporter.jasmineDone}); }; this.addReporter = function(reporterToAdd) { reporter.addReporter(reporterToAdd); }; - this.addMatchers = function(matchersToAdd) { - j$.Expectation.addMatchers(matchersToAdd); - }; - - this.spyOn = function(obj, methodName) { - if (j$.util.isUndefined(obj)) { - throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); + var spyRegistry = new j$.SpyRegistry({currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); } + return runnableResources[currentRunnable().id].spies; + }}); - if (j$.util.isUndefined(obj[methodName])) { - throw new Error(methodName + '() method does not exist'); - } - - if (obj[methodName] && j$.isSpy(obj[methodName])) { - //TODO?: should this return the current spy? Downside: may cause user confusion about spy state - throw new Error(methodName + ' has already been spied upon'); - } - - var spy = j$.createSpy(methodName, obj[methodName]); - - spies.push({ - spy: spy, - baseObj: obj, - methodName: methodName, - originalValue: obj[methodName] - }); - - obj[methodName] = spy; - - return spy; + this.spyOn = function() { + return spyRegistry.spyOn.apply(spyRegistry, arguments); }; var suiteFactory = function(description) { @@ -586,40 +628,33 @@ getJasmineRequireObj().Env = function(j$) { env: self, id: getNextSuiteId(), description: description, - parentSuite: currentSuite, + parentSuite: currentDeclarationSuite, queueRunner: queueRunnerFactory, onStart: suiteStarted, + expectationFactory: expectationFactory, + expectationResultFactory: expectationResultFactory, resultCallback: function(attrs) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + currentlyExecutingSuites.pop(); + } reporter.suiteDone(attrs); } }); runnableLookupTable[suite.id] = suite; return suite; + + function suiteStarted(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + } }; this.describe = function(description, specDefinitions) { var suite = suiteFactory(description); - - var parentSuite = currentSuite; - parentSuite.addChild(suite); - currentSuite = suite; - - var declarationError = null; - try { - specDefinitions.call(suite); - } catch (e) { - declarationError = e; - } - - if (declarationError) { - this.it('encountered a declaration exception', function() { - throw declarationError; - }); - } - - currentSuite = parentSuite; - + addSpecsToSuite(suite, specDefinitions); return suite; }; @@ -629,15 +664,75 @@ getJasmineRequireObj().Env = function(j$) { return suite; }; + var focusedRunnables = []; + + this.fdescribe = function(description, specDefinitions) { + var suite = suiteFactory(description); + suite.isFocused = true; + + focusedRunnables.push(suite.id); + unfocusAncestor(); + addSpecsToSuite(suite, specDefinitions); + + return suite; + }; + + function addSpecsToSuite(suite, specDefinitions) { + var parentSuite = currentDeclarationSuite; + parentSuite.addChild(suite); + currentDeclarationSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + if (declarationError) { + self.it('encountered a declaration exception', function() { + throw declarationError; + }); + } + + currentDeclarationSuite = parentSuite; + } + + function findFocusedAncestor(suite) { + while (suite) { + if (suite.isFocused) { + return suite.id; + } + suite = suite.parentSuite; + } + + return null; + } + + function unfocusAncestor() { + var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); + if (focusedAncestor) { + for (var i = 0; i < focusedRunnables.length; i++) { + if (focusedRunnables[i] === focusedAncestor) { + focusedRunnables.splice(i, 1); + break; + } + } + } + } + + var runnablesExplictlySet = false; + + var runnablesExplictlySetGetter = function(){ + return runnablesExplictlySet; + }; + var specFactory = function(description, fn, suite) { totalSpecsDefined++; - var spec = new j$.Spec({ id: getNextSpecId(), - beforeFns: beforeFns(suite), - afterFns: afterFns(suite), + beforeAndAfterFns: beforeAndAfterFns(suite, runnablesExplictlySetGetter), expectationFactory: expectationFactory, - exceptionFormatter: exceptionFormatter, resultCallback: specResultCallback, getSpecName: function(spec) { return getSpecName(spec, suite); @@ -646,7 +741,8 @@ getJasmineRequireObj().Env = function(j$) { description: description, expectationResultFactory: expectationResultFactory, queueRunnerFactory: queueRunnerFactory, - fn: fn + userContext: function() { return suite.clonedSharedUserContext(); }, + queueableFn: { fn: fn, type: 'it', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } } }); runnableLookupTable[spec.id] = spec; @@ -657,30 +753,22 @@ getJasmineRequireObj().Env = function(j$) { return spec; - function removeAllSpies() { - for (var i = 0; i < spies.length; i++) { - var spyEntry = spies[i]; - spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; - } - spies = []; - } - function specResultCallback(result) { - removeAllSpies(); - j$.Expectation.resetMatchers(); - customEqualityTesters = []; + clearResourcesForRunnable(spec.id); currentSpec = null; reporter.specDone(result); } - }; - var suiteStarted = function(suite) { - reporter.suiteStarted(suite.result); + function specStarted(spec) { + currentSpec = spec; + defaultResourcesForRunnable(spec.id, suite.id); + reporter.specStarted(spec.result); + } }; this.it = function(description, fn) { - var spec = specFactory(description, fn, currentSuite); - currentSuite.addChild(spec); + var spec = specFactory(description, fn, currentDeclarationSuite); + currentDeclarationSuite.addChild(spec); return spec; }; @@ -690,20 +778,36 @@ getJasmineRequireObj().Env = function(j$) { return spec; }; + this.fit = function(description, fn ){ + var spec = this.it(description, fn); + + focusedRunnables.push(spec.id); + unfocusAncestor(); + return spec; + }; + this.expect = function(actual) { - if (!currentSpec) { + if (!currentRunnable()) { throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); } - return currentSpec.expect(actual); + return currentRunnable().expect(actual); }; this.beforeEach = function(beforeEachFunction) { - currentSuite.beforeEach(beforeEachFunction); + currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, type: 'beforeEach', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + }; + + this.beforeAll = function(beforeAllFunction) { + currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, type: 'beforeAll', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.afterEach = function(afterEachFunction) { - currentSuite.afterEach(afterEachFunction); + currentDeclarationSuite.afterEach({ fn: afterEachFunction, type: 'afterEach', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + }; + + this.afterAll = function(afterAllFunction) { + currentDeclarationSuite.afterAll({ fn: afterAllFunction, type: 'afterAll', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.pending = function() { @@ -1185,8 +1289,6 @@ getJasmineRequireObj().ExceptionFormatter = function() { getJasmineRequireObj().Expectation = function() { - var matchers = {}; - function Expectation(options) { this.util = options.util || { buildFailureMessage: function() {} }; this.customEqualityTesters = options.customEqualityTesters || []; @@ -1194,8 +1296,9 @@ getJasmineRequireObj().Expectation = function() { this.addExpectationResult = options.addExpectationResult || function(){}; this.isNot = options.isNot; - for (var matcherName in matchers) { - this[matcherName] = matchers[matcherName]; + var customMatchers = options.customMatchers || {}; + for (var matcherName in customMatchers) { + this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); } } @@ -1262,19 +1365,6 @@ getJasmineRequireObj().Expectation = function() { } }; - Expectation.addMatchers = function(matchersToAdd) { - for (var name in matchersToAdd) { - var matcher = matchersToAdd[name]; - matchers[name] = Expectation.prototype.wrapCompare(name, matcher); - } - }; - - Expectation.resetMatchers = function() { - for (var name in matchers) { - delete matchers[name]; - } - }; - Expectation.Factory = function(options) { options = options || {}; @@ -1605,31 +1695,31 @@ getJasmineRequireObj().QueueRunner = function(j$) { } function QueueRunner(attrs) { - this.fns = attrs.fns || []; + this.queueableFns = attrs.queueableFns || []; this.onComplete = attrs.onComplete || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; this.onException = attrs.onException || function() {}; this.catchException = attrs.catchException || function() { return true; }; - this.enforceTimeout = attrs.enforceTimeout || function() { return false; }; - this.userContext = {}; + this.userContext = attrs.userContext || {}; this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; } QueueRunner.prototype.execute = function() { - this.run(this.fns, 0); + this.run(this.queueableFns, 0); }; - QueueRunner.prototype.run = function(fns, recursiveIndex) { - var length = fns.length, - self = this, - iterativeIndex; + QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { + var length = queueableFns.length, + self = this, + iterativeIndex; + for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { - var fn = fns[iterativeIndex]; - if (fn.length > 0) { - return attemptAsync(fn); + var queueableFn = queueableFns[iterativeIndex]; + if (queueableFn.fn.length > 0) { + return attemptAsync(queueableFn); } else { - attemptSync(fn); + attemptSync(queueableFn); } } @@ -1639,41 +1729,46 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.clearStack(this.onComplete); } - function attemptSync(fn) { + function attemptSync(queueableFn) { try { - fn.call(self.userContext); + queueableFn.fn.call(self.userContext); } catch (e) { - handleException(e); + handleException(e, queueableFn); } } - function attemptAsync(fn) { + function attemptAsync(queueableFn) { var clearTimeout = function () { Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]); }, next = once(function () { clearTimeout(timeoutId); - self.run(fns, iterativeIndex + 1); + self.run(queueableFns, iterativeIndex + 1); }), timeoutId; - if (self.enforceTimeout()) { + if (queueableFn.timeout) { timeoutId = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() { - self.onException(new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.')); + var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); + onException(error, queueableFn); next(); - }, j$.DEFAULT_TIMEOUT_INTERVAL]]); + }, queueableFn.timeout()]]); } try { - fn.call(self.userContext, next); + queueableFn.fn.call(self.userContext, next); } catch (e) { - handleException(e); + handleException(e, queueableFn); next(); } } - function handleException(e) { + function onException(e, queueableFn) { self.onException(e); + } + + function handleException(e, queueableFn) { + onException(e, queueableFn); if (!self.catchException(e)) { //TODO: set a var when we catch an exception and //use a finally block to close the loop in a nice way.. @@ -1721,6 +1816,52 @@ getJasmineRequireObj().ReportDispatcher = function() { }; +getJasmineRequireObj().SpyRegistry = function(j$) { + + function SpyRegistry(options) { + options = options || {}; + var currentSpies = options.currentSpies || function() { return []; }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); + } + + if (j$.util.isUndefined(obj[methodName])) { + throw new Error(methodName + '() method does not exist'); + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw new Error(methodName + ' has already been spied upon'); + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + currentSpies().push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; + + this.clearSpies = function() { + var spies = currentSpies(); + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + }; + } + + return SpyRegistry; +}; + getJasmineRequireObj().SpyStrategy = function() { function SpyStrategy(options) { @@ -1790,9 +1931,13 @@ getJasmineRequireObj().Suite = function() { this.onStart = attrs.onStart || function() {}; this.resultCallback = attrs.resultCallback || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; + this.expectationFactory = attrs.expectationFactory; + this.expectationResultFactory = attrs.expectationResultFactory; this.beforeFns = []; this.afterFns = []; + this.beforeAllFns = []; + this.afterAllFns = []; this.queueRunner = attrs.queueRunner || function() {}; this.disabled = false; @@ -1800,12 +1945,16 @@ getJasmineRequireObj().Suite = function() { this.result = { id: this.id, - status: this.disabled ? 'disabled' : '', description: this.description, - fullName: this.getFullName() + fullName: this.getFullName(), + failedExpectations: [] }; } + Suite.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + Suite.prototype.getFullName = function() { var fullName = this.description; for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { @@ -1825,10 +1974,18 @@ getJasmineRequireObj().Suite = function() { this.beforeFns.unshift(fn); }; + Suite.prototype.beforeAll = function(fn) { + this.beforeAllFns.push(fn); + }; + Suite.prototype.afterEach = function(fn) { this.afterFns.unshift(fn); }; + Suite.prototype.afterAll = function(fn) { + this.afterAllFns.push(fn); + }; + Suite.prototype.addChild = function(child) { this.children.push(child); }; @@ -1845,16 +2002,25 @@ getJasmineRequireObj().Suite = function() { var allFns = []; - for (var i = 0; i < this.children.length; i++) { - allFns.push(wrapChildAsAsync(this.children[i])); + if (this.isExecutable()) { + allFns = allFns.concat(this.beforeAllFns); + + for (var i = 0; i < this.children.length; i++) { + allFns.push(wrapChildAsAsync(this.children[i])); + } + + allFns = allFns.concat(this.afterAllFns); } this.queueRunner({ - fns: allFns, - onComplete: complete + queueableFns: allFns, + onComplete: complete, + userContext: this.sharedUserContext(), + onException: function() { self.onException.apply(self, arguments); } }); function complete() { + self.result.status = self.disabled ? 'disabled' : 'finished'; self.resultCallback(self.result); if (onComplete) { @@ -1863,10 +2029,82 @@ getJasmineRequireObj().Suite = function() { } function wrapChildAsAsync(child) { - return function(done) { child.execute(done); }; + return { fn: function(done) { child.execute(done); } }; } }; + Suite.prototype.isExecutable = function() { + var foundActive = false; + for(var i = 0; i < this.children.length; i++) { + if(this.children[i].isExecutable()) { + foundActive = true; + break; + } + } + return foundActive; + }; + + Suite.prototype.sharedUserContext = function() { + if (!this.sharedContext) { + this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; + } + + return this.sharedContext; + }; + + Suite.prototype.clonedSharedUserContext = function() { + return clone(this.sharedUserContext()); + }; + + Suite.prototype.onException = function() { + if(isAfterAll(this.children)) { + var data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0] + }; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.onException.apply(child, arguments); + } + } + }; + + Suite.prototype.addExpectationResult = function () { + if(isAfterAll(this.children) && isFailure(arguments)){ + var data = arguments[1]; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.addExpectationResult.apply(child, arguments); + } + } + }; + + function isAfterAll(children) { + return children && children[0].result.status; + } + + function isFailure(args) { + return !args[0]; + } + + function clone(obj) { + var clonedObj = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + clonedObj[prop] = obj[prop]; + } + } + + return clonedObj; + } + return Suite; }; @@ -2568,6 +2806,14 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.afterEach(afterEachFunction); }, + beforeAll: function(beforeAllFunction) { + return env.beforeAll(beforeAllFunction); + }, + + afterAll: function(afterAllFunction) { + return env.afterAll(afterAllFunction); + }, + expect: function(actual) { return env.expect(actual); }, diff --git a/spec/console/ConsoleReporterSpec.js b/spec/console/ConsoleReporterSpec.js index b19f51c6..df6b8b50 100644 --- a/spec/console/ConsoleReporterSpec.js +++ b/spec/console/ConsoleReporterSpec.js @@ -171,27 +171,44 @@ describe("ConsoleReporter", function() { out.clear(); - reporter.jasmineDone({}); + reporter.jasmineDone(); expect(out.getOutput()).toMatch(/true to be false/); expect(out.getOutput()).toMatch(/foo bar baz/); }); - it("calls the onComplete callback when the suite is done", function() { - var onComplete = jasmine.createSpy('onComplete'), + describe('onComplete callback', function(){ + var onComplete, reporter; + + beforeEach(function() { + onComplete = jasmine.createSpy('onComplete'); reporter = new j$.ConsoleReporter({ print: out.print, onComplete: onComplete }); + reporter.jasmineStarted(); + }); - reporter.jasmineDone({}); + it("is called when the suite is done", function() { + reporter.jasmineDone(); + expect(onComplete).toHaveBeenCalledWith(true); + }); - expect(onComplete).toHaveBeenCalled(); + it('calls it with false if there are spec failures', function() { + reporter.specDone({status: "failed", failedExpectations: []}); + reporter.jasmineDone(); + expect(onComplete).toHaveBeenCalledWith(false); + }); + + it('calls it with false if there are suite failures', function() { + reporter.specDone({status: "passed"}); + reporter.suiteDone({failedExpectations: [{ message: 'bananas' }] }); + reporter.jasmineDone(); + expect(onComplete).toHaveBeenCalledWith(false); + }); }); - describe("with color", function() { - it("reports that the suite has started to the console", function() { var reporter = new j$.ConsoleReporter({ print: out.print, @@ -235,5 +252,19 @@ describe("ConsoleReporter", function() { expect(out.getOutput()).toEqual("\x1B[31mF\x1B[0m"); }); + + it("displays all afterAll exceptions", function() { + var reporter = new j$.ConsoleReporter({ + print: out.print, + showColors: true + }); + + reporter.suiteDone({ failedExpectations: [{ message: 'After All Exception' }] }); + reporter.suiteDone({ failedExpectations: [{ message: 'Some Other Exception' }] }); + reporter.jasmineDone(); + + expect(out.getOutput()).toMatch(/After All Exception/); + expect(out.getOutput()).toMatch(/Some Other Exception/); + }); }); }); diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index 86898f56..f8867885 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -5,68 +5,6 @@ describe("Env", function() { env = new j$.Env(); }); - it('removes all spies when env is executed', function(done) { - var originalFoo = function() {}, - testObj = { - foo: originalFoo - }, - firstSpec = jasmine.createSpy('firstSpec').and.callFake(function() { - env.spyOn(testObj, 'foo'); - }), - secondSpec = jasmine.createSpy('secondSpec').and.callFake(function() { - expect(testObj.foo).toBe(originalFoo); - }); - env.describe('test suite', function() { - env.it('spec 0', firstSpec); - env.it('spec 1', secondSpec); - }); - - var assertions = function() { - expect(firstSpec).toHaveBeenCalled(); - expect(secondSpec).toHaveBeenCalled(); - done(); - }; - - env.addReporter({ jasmineDone: assertions }); - - env.execute(); - }); - - describe("#spyOn", function() { - it("checks for the existence of the object", function() { - expect(function() { - env.spyOn(void 0, 'pants'); - }).toThrowError(/could not find an object/); - }); - - it("checks for the existence of the method", function() { - var subject = {}; - - expect(function() { - env.spyOn(subject, 'pants'); - }).toThrowError(/method does not exist/); - }); - - it("checks if it has already been spied upon", function() { - var subject = { spiedFunc: function() {} }; - - env.spyOn(subject, 'spiedFunc'); - - expect(function() { - env.spyOn(subject, 'spiedFunc'); - }).toThrowError(/has already been spied upon/); - }); - - it("overrides the method on the object and returns the spy", function() { - var originalFunctionWasCalled = false; - var subject = { spiedFunc: function() { originalFunctionWasCalled = true; } }; - - var spy = env.spyOn(subject, 'spiedFunc'); - - expect(subject.spiedFunc).toEqual(spy); - }); - }); - describe("#pending", function() { it("throws the Pending Spec exception", function() { expect(function() { @@ -82,4 +20,3 @@ describe("Env", function() { }); }); }); - diff --git a/spec/core/ExpectationSpec.js b/spec/core/ExpectationSpec.js index c5528aa1..b253672b 100644 --- a/spec/core/ExpectationSpec.js +++ b/spec/core/ExpectationSpec.js @@ -1,14 +1,14 @@ describe("Expectation", function() { - it(".addMatchers makes matchers available to any expectation", function() { + it("makes custom matchers available to this expectation", function() { var matchers = { toFoo: function() {}, toBar: function() {} }, expectation; - j$.Expectation.addMatchers(matchers); - - expectation = new j$.Expectation({}); + expectation = new j$.Expectation({ + customMatchers: matchers + }); expect(expectation.toFoo).toBeDefined(); expect(expectation.toBar).toBeDefined(); @@ -27,25 +27,6 @@ describe("Expectation", function() { expect(expectation.toQuux).toBeDefined(); }); - it(".resetMatchers should keep only core matchers", function() { - var matchers = { - toFoo: function() {} - }, - coreMatchers = { - toQuux: function() {} - }, - expectation; - - j$.Expectation.addCoreMatchers(coreMatchers); - j$.Expectation.addMatchers(matchers); - j$.Expectation.resetMatchers(); - - expectation = new j$.Expectation({}); - - expect(expectation.toQuux).toBeDefined(); - expect(expectation.toFoo).toBeUndefined(); - }); - it("Factory builds an expectation/negative expectation", function() { var builtExpectation = j$.Expectation.Factory(); @@ -65,10 +46,9 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ util: util, + customMatchers: matchers, customEqualityTesters: customEqualityTesters, actual: "an actual", addExpectationResult: addExpectationResult @@ -94,10 +74,9 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ util: util, + customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult }); @@ -121,10 +100,8 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, util: util, actual: "an actual", addExpectationResult: addExpectationResult @@ -155,10 +132,8 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, util: util, actual: "an actual", addExpectationResult: addExpectationResult @@ -191,10 +166,9 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ actual: "an actual", + customMatchers: matchers, addExpectationResult: addExpectationResult }); @@ -225,10 +199,8 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult }); @@ -259,10 +231,8 @@ describe("Expectation", function() { actual = "an actual", expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult, isNot: true @@ -294,10 +264,8 @@ describe("Expectation", function() { actual = "an actual", expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, actual: "an actual", util: util, addExpectationResult: addExpectationResult, @@ -332,10 +300,8 @@ describe("Expectation", function() { actual = "an actual", expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult, isNot: true @@ -365,10 +331,8 @@ describe("Expectation", function() { actual = "an actual", expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult, isNot: true @@ -403,10 +367,8 @@ describe("Expectation", function() { actual = "an actual", expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult, isNot: true diff --git a/spec/core/PrettyPrintSpec.js b/spec/core/PrettyPrintSpec.js index fba38473..b5f939bc 100644 --- a/spec/core/PrettyPrintSpec.js +++ b/spec/core/PrettyPrintSpec.js @@ -135,7 +135,9 @@ describe("j$.pp", function () { }, env = new j$.Env(); - env.spyOn(TestObject, 'someFunction'); + var spyRegistry = new j$.SpyRegistry({currentSpies: function() {return [];}}); + + spyRegistry.spyOn(TestObject, 'someFunction'); expect(j$.pp(TestObject.someFunction)).toEqual("spy on someFunction"); expect(j$.pp(j$.createSpy("something"))).toEqual("spy on something"); @@ -158,4 +160,3 @@ describe("j$.pp", function () { expect(j$.pp(obj)).toEqual("{ foo: 'bar' }"); }); }); - diff --git a/spec/core/QueueRunnerSpec.js b/spec/core/QueueRunnerSpec.js index 37e651ee..8c4eb9a5 100644 --- a/spec/core/QueueRunnerSpec.js +++ b/spec/core/QueueRunnerSpec.js @@ -2,15 +2,15 @@ describe("QueueRunner", function() { it("runs all the functions it's passed", function() { var calls = [], - fn1 = jasmine.createSpy('fn1'), - fn2 = jasmine.createSpy('fn2'), + queueableFn1 = { fn: jasmine.createSpy('fn1') }, + queueableFn2 = { fn: jasmine.createSpy('fn2') }, queueRunner = new j$.QueueRunner({ - fns: [fn1, fn2] + queueableFns: [queueableFn1, queueableFn2] }); - fn1.and.callFake(function() { + queueableFn1.fn.and.callFake(function() { calls.push('fn1'); }); - fn2.and.callFake(function() { + queueableFn2.fn.and.callFake(function() { calls.push('fn2'); }); @@ -20,19 +20,19 @@ describe("QueueRunner", function() { }); it("calls each function with a consistent 'this'-- an empty object", function() { - var fn1 = jasmine.createSpy('fn1'), - fn2 = jasmine.createSpy('fn2'), - fn3 = function(done) { asyncContext = this; done(); }, + var queueableFn1 = { fn: jasmine.createSpy('fn1') }, + queueableFn2 = { fn: jasmine.createSpy('fn2') }, + queueableFn3 = { fn: function(done) { asyncContext = this; done(); } }, queueRunner = new j$.QueueRunner({ - fns: [fn1, fn2, fn3] + queueableFns: [queueableFn1, queueableFn2, queueableFn3] }), asyncContext; queueRunner.execute(); - var context = fn1.calls.first().object; + var context = queueableFn1.fn.calls.first().object; expect(context).toEqual({}); - expect(fn2.calls.first().object).toBe(context); + expect(queueableFn2.fn.calls.first().object).toBe(context); expect(asyncContext).toBe(context); }); @@ -53,20 +53,20 @@ describe("QueueRunner", function() { beforeCallback = jasmine.createSpy('beforeCallback'), fnCallback = jasmine.createSpy('fnCallback'), afterCallback = jasmine.createSpy('afterCallback'), - fn1 = function(done) { + queueableFn1 = { fn: function(done) { beforeCallback(); setTimeout(done, 100); - }, - fn2 = function(done) { + } }, + queueableFn2 = { fn: function(done) { fnCallback(); setTimeout(done, 100); - }, - fn3 = function(done) { + } }, + queueableFn3 = { fn: function(done) { afterCallback(); setTimeout(done, 100); - }, + } }, queueRunner = new j$.QueueRunner({ - fns: [fn1, fn2, fn3], + queueableFns: [queueableFn1, queueableFn2, queueableFn3], onComplete: onComplete }); @@ -94,54 +94,54 @@ describe("QueueRunner", function() { }); it("sets a timeout if requested for asynchronous functions so they don't go on forever", function() { - var beforeFn = function(done) { }, - fn = jasmine.createSpy('fn'), + var timeout = 3, + beforeFn = { fn: function(done) { }, type: 'before', timeout: function() { return timeout; } }, + queueableFn = { fn: jasmine.createSpy('fn'), type: 'queueable' }, onComplete = jasmine.createSpy('onComplete'), onException = jasmine.createSpy('onException'), queueRunner = new j$.QueueRunner({ - fns: [beforeFn, fn], + queueableFns: [beforeFn, queueableFn], onComplete: onComplete, - onException: onException, - enforceTimeout: function() { return true; } + onException: onException }); queueRunner.execute(); - expect(fn).not.toHaveBeenCalled(); + expect(queueableFn.fn).not.toHaveBeenCalled(); - jasmine.clock().tick(j$.DEFAULT_TIMEOUT_INTERVAL); + jasmine.clock().tick(timeout); expect(onException).toHaveBeenCalledWith(jasmine.any(Error)); - expect(fn).toHaveBeenCalled(); + expect(queueableFn.fn).toHaveBeenCalled(); expect(onComplete).toHaveBeenCalled(); }); it("by default does not set a timeout for asynchronous functions", function() { - var beforeFn = function(done) { }, - fn = jasmine.createSpy('fn'), + var beforeFn = { fn: function(done) { } }, + queueableFn = { fn: jasmine.createSpy('fn') }, onComplete = jasmine.createSpy('onComplete'), onException = jasmine.createSpy('onException'), queueRunner = new j$.QueueRunner({ - fns: [beforeFn, fn], + queueableFns: [beforeFn, queueableFn], onComplete: onComplete, onException: onException, }); queueRunner.execute(); - expect(fn).not.toHaveBeenCalled(); + expect(queueableFn.fn).not.toHaveBeenCalled(); jasmine.clock().tick(j$.DEFAULT_TIMEOUT_INTERVAL); expect(onException).not.toHaveBeenCalled(); - expect(fn).not.toHaveBeenCalled(); + expect(queueableFn.fn).not.toHaveBeenCalled(); expect(onComplete).not.toHaveBeenCalled(); }); - it("clears the timeout when an async function throws an exception, to prevent additional onException calls", function() { - var fn = function(done) { throw new Error("error!"); }, + it("clears the timeout when an async function throws an exception, to prevent additional exception reporting", function() { + var queueableFn = { fn: function(done) { throw new Error("error!"); } }, onComplete = jasmine.createSpy('onComplete'), onException = jasmine.createSpy('onException'), queueRunner = new j$.QueueRunner({ - fns: [fn], + queueableFns: [queueableFn], onComplete: onComplete, onException: onException }); @@ -156,11 +156,11 @@ describe("QueueRunner", function() { }); it("clears the timeout when the done callback is called", function() { - var fn = function(done) { done(); }, + var queueableFn = { fn: function(done) { done(); } }, onComplete = jasmine.createSpy('onComplete'), onException = jasmine.createSpy('onException'), queueRunner = new j$.QueueRunner({ - fns: [fn], + queueableFns: [queueableFn], onComplete: onComplete, onException: onException }); @@ -174,75 +174,78 @@ describe("QueueRunner", function() { }); it("only moves to the next spec the first time you call done", function() { - var fn = function(done) {done(); done();}, - nextFn = jasmine.createSpy('nextFn'); + var queueableFn = { fn: function(done) {done(); done();} }, + nextQueueableFn = { fn: jasmine.createSpy('nextFn') }; queueRunner = new j$.QueueRunner({ - fns: [fn, nextFn] + queueableFns: [queueableFn, nextQueueableFn] }); queueRunner.execute(); - expect(nextFn.calls.count()).toEqual(1); + expect(nextQueueableFn.fn.calls.count()).toEqual(1); }); it("does not move to the next spec if done is called after an exception has ended the spec", function() { - var fn = function(done) { + var queueableFn = { fn: function(done) { setTimeout(done, 1); throw new Error('error!'); - }, - nextFn = jasmine.createSpy('nextFn'); + } }, + nextQueueableFn = { fn: jasmine.createSpy('nextFn') }; queueRunner = new j$.QueueRunner({ - fns: [fn, nextFn] + queueableFns: [queueableFn, nextQueueableFn] }); queueRunner.execute(); jasmine.clock().tick(1); - expect(nextFn.calls.count()).toEqual(1); + expect(nextQueueableFn.fn.calls.count()).toEqual(1); }); }); - it("calls an exception handler when an exception is thrown in a fn", function() { - var fn = function() { + it("calls exception handlers when an exception is thrown in a fn", function() { + var queueableFn = { type: 'queueable', + fn: function() { throw new Error('fake error'); - }, - exceptionCallback = jasmine.createSpy('exception callback'), + } }, + onExceptionCallback = jasmine.createSpy('on exception callback'), queueRunner = new j$.QueueRunner({ - fns: [fn], - onException: exceptionCallback + queueableFns: [queueableFn], + onException: onExceptionCallback }); queueRunner.execute(); - expect(exceptionCallback).toHaveBeenCalledWith(jasmine.any(Error)); + expect(onExceptionCallback).toHaveBeenCalledWith(jasmine.any(Error)); }); it("rethrows an exception if told to", function() { - var fn = function() { + var queueableFn = { fn: function() { throw new Error('fake error'); - }, + } }, queueRunner = new j$.QueueRunner({ - fns: [fn], + queueableFns: [queueableFn], catchException: function(e) { return false; } }); - expect(queueRunner.execute).toThrow(); + expect(function() { + queueRunner.execute(); + }).toThrowError('fake error'); }); it("continues running the functions even after an exception is thrown in an async spec", function() { - var fn = function(done) { throw new Error("error"); }, - nextFn = jasmine.createSpy("nextFunction"), + var queueableFn = { fn: function(done) { throw new Error("error"); } }, + nextQueueableFn = { fn: jasmine.createSpy("nextFunction") }, queueRunner = new j$.QueueRunner({ - fns: [fn, nextFn] + queueableFns: [queueableFn, nextQueueableFn] }); queueRunner.execute(); - expect(nextFn).toHaveBeenCalled(); + expect(nextQueueableFn.fn).toHaveBeenCalled(); }); it("calls a provided complete callback when done", function() { - var fn = jasmine.createSpy('fn'), + var queueableFn = { fn: jasmine.createSpy('fn') }, completeCallback = jasmine.createSpy('completeCallback'), queueRunner = new j$.QueueRunner({ - fns: [fn], + queueableFns: [queueableFn], onComplete: completeCallback }); @@ -252,12 +255,12 @@ describe("QueueRunner", function() { }); it("calls a provided stack clearing function when done", function() { - var asyncFn = function(done) { done() }, - afterFn = jasmine.createSpy('afterFn'), + var asyncFn = { fn: function(done) { done() } }, + afterFn = { fn: jasmine.createSpy('afterFn') }, completeCallback = jasmine.createSpy('completeCallback'), clearStack = jasmine.createSpy('clearStack'), queueRunner = new j$.QueueRunner({ - fns: [asyncFn, afterFn], + queueableFns: [asyncFn, afterFn], clearStack: clearStack, onComplete: completeCallback }); @@ -265,7 +268,7 @@ describe("QueueRunner", function() { clearStack.and.callFake(function(fn) { fn(); }); queueRunner.execute(); - expect(afterFn).toHaveBeenCalled(); + expect(afterFn.fn).toHaveBeenCalled(); expect(clearStack).toHaveBeenCalledWith(completeCallback); }); }); diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index 77229dfc..f59eb1db 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -29,7 +29,7 @@ describe("Spec", function() { spec = new j$.Spec({ description: 'my test', id: 'some-id', - fn: function() {}, + queueableFn: { fn: function() {} }, queueRunnerFactory: fakeQueueRunner }); @@ -44,7 +44,7 @@ describe("Spec", function() { spec = new j$.Spec({ id: 123, description: 'foo bar', - fn: function() {}, + queueableFn: { fn: function() {} }, onStart: startCallback, queueRunnerFactory: fakeQueueRunner }); @@ -66,7 +66,7 @@ describe("Spec", function() { expect(beforesWereCalled).toBe(false); }), spec = new j$.Spec({ - fn: function() {}, + queueableFn: { fn: function() {} }, beforeFns: function() { return [function() { beforesWereCalled = true @@ -85,25 +85,22 @@ describe("Spec", function() { var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'), before = jasmine.createSpy('before'), after = jasmine.createSpy('after'), - fn = jasmine.createSpy('test body').and.callFake(function() { + queueableFn = { fn: jasmine.createSpy('test body').and.callFake(function() { expect(before).toHaveBeenCalled(); expect(after).not.toHaveBeenCalled(); - }), + }) }, spec = new j$.Spec({ - fn: fn, - beforeFns: function() { - return [before] - }, - afterFns: function() { - return [after] + queueableFn: queueableFn, + beforeAndAfterFns: function() { + return {befores: [before], afters: [after]} }, queueRunnerFactory: fakeQueueRunner }); spec.execute(); - var allSpecFns = fakeQueueRunner.calls.mostRecent().args[0].fns; - expect(allSpecFns).toEqual([before, fn, after]); + var allSpecFns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns; + expect(allSpecFns).toEqual([before, queueableFn, after]); }); it("is marked pending if created without a function body", function() { @@ -113,7 +110,7 @@ describe("Spec", function() { resultCallback = jasmine.createSpy('resultCallback'), spec = new j$.Spec({ onStart: startCallback, - fn: null, + queueableFn: { fn: null }, resultCallback: resultCallback, queueRunnerFactory: fakeQueueRunner }); @@ -129,7 +126,7 @@ describe("Spec", function() { resultCallback = jasmine.createSpy('resultCallback'), spec = new j$.Spec({ onStart:startCallback, - fn: specBody, + queueableFn: { fn: specBody }, resultCallback: resultCallback, queueRunnerFactory: fakeQueueRunner }); @@ -158,7 +155,8 @@ describe("Spec", function() { getSpecName: function() { return "a suite with a spec" }, - queueRunnerFactory: fakeQueueRunner + queueRunnerFactory: fakeQueueRunner, + queueableFn: { fn: null } }); spec.pend(); @@ -183,7 +181,7 @@ describe("Spec", function() { it("should call the done callback on execution complete", function() { var done = jasmine.createSpy('done callback'), spec = new j$.Spec({ - fn: function() {}, + queueableFn: { fn: function() {} }, catchExceptions: function() { return false; }, resultCallback: function() {}, queueRunnerFactory: function(attrs) { attrs.onComplete(); } @@ -194,19 +192,19 @@ describe("Spec", function() { expect(done).toHaveBeenCalled(); }); - it("#status returns passing by default", function(){ - var spec = new j$.Spec({ fn: function () {} }); - expect(spec.status()).toBe("passed"); + it("#status returns passing by default", function() { + var spec = new j$.Spec({queueableFn: { fn: jasmine.createSpy("spec body")} }); + expect(spec.status()).toBe('passed'); }); it("#status returns passed if all expectations in the spec have passed", function() { - var spec = new j$.Spec({fn: jasmine.createSpy("spec body")}); + var spec = new j$.Spec({queueableFn: { fn: jasmine.createSpy("spec body")} }); spec.addExpectationResult(true); expect(spec.status()).toBe('passed'); }); it("#status returns failed if any expectations in the spec have failed", function() { - var spec = new j$.Spec({ fn: jasmine.createSpy("spec body") }); + var spec = new j$.Spec({queueableFn: { fn: jasmine.createSpy("spec body") } }); spec.addExpectationResult(true); spec.addExpectationResult(false); expect(spec.status()).toBe('failed'); @@ -215,7 +213,7 @@ describe("Spec", function() { it("keeps track of passed and failed expectations", function() { var resultCallback = jasmine.createSpy('resultCallback'), spec = new j$.Spec({ - fn: jasmine.createSpy("spec body"), + queueableFn: { fn: jasmine.createSpy("spec body") }, expectationResultFactory: function (data) { return data; }, queueRunnerFactory: function(attrs) { attrs.onComplete(); }, resultCallback: resultCallback @@ -233,7 +231,8 @@ describe("Spec", function() { var specNameSpy = jasmine.createSpy('specNameSpy').and.returnValue('expected val'); var spec = new j$.Spec({ - getSpecName: specNameSpy + getSpecName: specNameSpy, + queueableFn: { fn: null } }); expect(spec.getFullName()).toBe('expected val'); @@ -248,7 +247,7 @@ describe("Spec", function() { spec = new j$.Spec({ description: 'my test', id: 'some-id', - fn: function() { }, + queueableFn: { fn: function() { } }, queueRunnerFactory: fakeQueueRunner }); diff --git a/spec/core/SpyRegistrySpec.js b/spec/core/SpyRegistrySpec.js new file mode 100644 index 00000000..36dcf68c --- /dev/null +++ b/spec/core/SpyRegistrySpec.js @@ -0,0 +1,55 @@ +describe("SpyRegistry", function() { + describe("#spyOn", function() { + it("checks for the existence of the object", function() { + var spyRegistry = new j$.SpyRegistry(); + expect(function() { + spyRegistry.spyOn(void 0, 'pants'); + }).toThrowError(/could not find an object/); + }); + + it("checks for the existence of the method", function() { + var spyRegistry = new j$.SpyRegistry(), + subject = {}; + + expect(function() { + spyRegistry.spyOn(subject, 'pants'); + }).toThrowError(/method does not exist/); + }); + + it("checks if it has already been spied upon", function() { + var spies = [], + spyRegistry = new j$.SpyRegistry({currentSpies: function() { return spies; }}), + subject = { spiedFunc: function() {} }; + + spyRegistry.spyOn(subject, 'spiedFunc'); + + expect(function() { + spyRegistry.spyOn(subject, 'spiedFunc'); + }).toThrowError(/has already been spied upon/); + }); + + it("overrides the method on the object and returns the spy", function() { + var originalFunctionWasCalled = false, + spyRegistry = new j$.SpyRegistry(), + subject = { spiedFunc: function() { originalFunctionWasCalled = true; } }; + + var spy = spyRegistry.spyOn(subject, 'spiedFunc'); + + expect(subject.spiedFunc).toEqual(spy); + }); + }); + + describe("#clearSpies", function() { + it("restores the original functions on the spied-upon objects", function() { + var spies = [], + spyRegistry = new j$.SpyRegistry({currentSpies: function() { return spies; }}), + originalFunction = function() {}, + subject = { spiedFunc: originalFunction }; + + spyRegistry.spyOn(subject, 'spiedFunc'); + spyRegistry.clearSpies(); + + expect(subject.spiedFunc).toBe(originalFunction); + }); + }); +}); diff --git a/spec/core/SuiteSpec.js b/spec/core/SuiteSpec.js index c29d2f4a..93b5ec33 100644 --- a/spec/core/SuiteSpec.js +++ b/spec/core/SuiteSpec.js @@ -52,6 +52,31 @@ describe("Suite", function() { expect(suite.beforeFns).toEqual([innerBefore, outerBefore]); }); + it("runs beforeAll functions in order of needed execution", function() { + var env = new j$.Env(), + fakeQueueRunner = jasmine.createSpy('fake queue runner'), + suite = new j$.Suite({ + env: env, + description: "I am a suite", + queueRunner: fakeQueueRunner + }), + firstBefore = jasmine.createSpy('outerBeforeAll'), + lastBefore = jasmine.createSpy('insideBeforeAll'), + fakeIt = {execute: jasmine.createSpy('it'), isExecutable: function() { return true; } }; + + suite.beforeAll(firstBefore); + suite.beforeAll(lastBefore); + suite.addChild(fakeIt); + + suite.execute(); + var suiteFns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns; + + suiteFns[0](); + expect(firstBefore).toHaveBeenCalled(); + suiteFns[1](); + expect(lastBefore).toHaveBeenCalled(); + }); + it("adds after functions in order of needed execution", function() { var env = new j$.Env(), suite = new j$.Suite({ @@ -67,6 +92,31 @@ describe("Suite", function() { expect(suite.afterFns).toEqual([innerAfter, outerAfter]); }); + it("runs afterAll functions in order of needed execution", function() { + var env = new j$.Env(), + fakeQueueRunner = jasmine.createSpy('fake queue runner'), + suite = new j$.Suite({ + env: env, + description: "I am a suite", + queueRunner: fakeQueueRunner + }), + firstAfter = jasmine.createSpy('outerAfterAll'), + lastAfter = jasmine.createSpy('insideAfterAll'), + fakeIt = {execute: jasmine.createSpy('it'), isExecutable: function() { return true; } }; + + suite.afterAll(firstAfter); + suite.afterAll(lastAfter); + suite.addChild(fakeIt); + + suite.execute(); + var suiteFns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns; + + suiteFns[1](); + expect(firstAfter).toHaveBeenCalled(); + suiteFns[2](); + expect(lastAfter).toHaveBeenCalled(); + }); + it("can be disabled, but still calls callbacks", function() { var env = new j$.Env(), fakeQueueRunner = jasmine.createSpy('fake queue runner'), @@ -93,7 +143,7 @@ describe("Suite", function() { expect(onComplete).toHaveBeenCalled(); }); - it("delegates execution of its specs and suites", function() { + it("delegates execution of its specs, suites, beforeAlls, and afterAlls", function() { var env = new j$.Env(), parentSuiteDone = jasmine.createSpy('parent suite done'), fakeQueueRunnerForParent = jasmine.createSpy('fake parent queue runner'), @@ -109,22 +159,57 @@ describe("Suite", function() { queueRunner: fakeQueueRunner }), fakeSpec1 = { - execute: jasmine.createSpy('fakeSpec1') - }; + execute: jasmine.createSpy('fakeSpec1'), + isExecutable: function() { return true; } + }, + beforeAllFn = { fn: jasmine.createSpy('beforeAll') }, + afterAllFn = { fn: jasmine.createSpy('afterAll') }; spyOn(suite, "execute"); parentSuite.addChild(fakeSpec1); parentSuite.addChild(suite); + parentSuite.beforeAll(beforeAllFn); + parentSuite.afterAll(afterAllFn); parentSuite.execute(parentSuiteDone); - var parentSuiteFns = fakeQueueRunnerForParent.calls.mostRecent().args[0].fns; + var parentSuiteFns = fakeQueueRunnerForParent.calls.mostRecent().args[0].queueableFns; - parentSuiteFns[0](); + parentSuiteFns[0].fn(); + expect(beforeAllFn.fn).toHaveBeenCalled(); + parentSuiteFns[1].fn(); expect(fakeSpec1.execute).toHaveBeenCalled(); - parentSuiteFns[1](); + parentSuiteFns[2].fn(); expect(suite.execute).toHaveBeenCalled(); + parentSuiteFns[3].fn(); + expect(afterAllFn.fn).toHaveBeenCalled(); + }); + + it("does not run beforeAll or afterAll if there are no child specs to run", function() { + var env = new j$.Env(), + fakeQueueRunnerForParent = jasmine.createSpy('fake parent queue runner'), + fakeQueueRunnerForChild = jasmine.createSpy('fake child queue runner'), + parentSuite = new j$.Suite({ + env: env, + description: "I am a suite", + queueRunner: fakeQueueRunnerForParent + }), + childSuite = new j$.Suite({ + env: env, + description: "I am a suite", + queueRunner: fakeQueueRunnerForChild, + parentSuite: parentSuite + }), + beforeAllFn = jasmine.createSpy('beforeAll'), + afterAllFn = jasmine.createSpy('afterAll'); + + parentSuite.addChild(childSuite); + parentSuite.beforeAll(beforeAllFn); + parentSuite.afterAll(afterAllFn); + + parentSuite.execute(); + expect(fakeQueueRunnerForParent).toHaveBeenCalledWith(jasmine.objectContaining({queueableFns: []})); }); it("calls a provided onStart callback when starting", function() { @@ -138,7 +223,8 @@ describe("Suite", function() { queueRunner: fakeQueueRunner }), fakeSpec1 = { - execute: jasmine.createSpy('fakeSpec1') + execute: jasmine.createSpy('fakeSpec1'), + isExecutable: function() { return true; } }; suite.execute(); @@ -182,9 +268,10 @@ describe("Suite", function() { expect(suiteResultsCallback).toHaveBeenCalledWith({ id: suite.id, - status: '', + status: 'finished', description: "with a child suite", - fullName: "with a child suite" + fullName: "with a child suite", + failedExpectations: [] }); }); @@ -210,7 +297,8 @@ describe("Suite", function() { id: suite.id, status: 'disabled', description: "with a child suite", - fullName: "with a child suite" + fullName: "with a child suite", + failedExpectations: [] }); }); }); diff --git a/spec/core/integration/CustomMatchersSpec.js b/spec/core/integration/CustomMatchersSpec.js index ee3e49a0..6c03d3d5 100644 --- a/spec/core/integration/CustomMatchersSpec.js +++ b/spec/core/integration/CustomMatchersSpec.js @@ -38,13 +38,13 @@ describe("Custom Matchers (Integration)", function() { }); it("passes the spec if the custom matcher passes", function(done) { - env.addMatchers({ - toBeReal: function() { - return { compare: function() { return { pass: true }; } }; - } - }); - env.it("spec using custom matcher", function() { + env.addMatchers({ + toBeReal: function() { + return { compare: function() { return { pass: true }; } }; + } + }); + env.expect(true).toBeReal(); }); @@ -57,16 +57,16 @@ describe("Custom Matchers (Integration)", function() { }); it("uses the negative compare function for a negative comparison, if provided", function(done) { - env.addMatchers({ - toBeReal: function() { - return { - compare: function() { return { pass: true }; }, - negativeCompare: function() { return { pass: true }; } - }; - } - }); - env.it("spec with custom negative comparison matcher", function() { + env.addMatchers({ + toBeReal: function() { + return { + compare: function() { return { pass: true }; }, + negativeCompare: function() { return { pass: true }; } + }; + } + }); + env.expect(true).not.toBeReal(); }); @@ -79,17 +79,17 @@ describe("Custom Matchers (Integration)", function() { }); it("generates messages with the same rules as built in matchers absent a custom message", function(done) { - env.addMatchers({ - toBeReal: function() { - return { - compare: function() { - return { pass: false }; + env.it('spec with an expectation', function() { + env.addMatchers({ + toBeReal: function() { + return { + compare: function() { + return { pass: false }; + } } } - } - }); + }); - env.it('spec with an expectation', function() { env.expect("a").toBeReal(); }); @@ -103,13 +103,14 @@ describe("Custom Matchers (Integration)", function() { it("passes the expected and actual arguments to the comparison function", function(done) { var argumentSpy = jasmine.createSpy("argument spy").and.returnValue({ pass: true }); - env.addMatchers({ - toBeReal: function() { - return { compare: argumentSpy }; - } - }); env.it('spec with an expectation', function () { + env.addMatchers({ + toBeReal: function() { + return { compare: argumentSpy }; + } + }); + env.expect(true).toBeReal(); env.expect(true).toBeReal("arg"); env.expect(true).toBeReal("arg1", "arg2"); @@ -130,12 +131,13 @@ describe("Custom Matchers (Integration)", function() { argumentSpy = jasmine.createSpy("argument spy").and.returnValue(matcherFactory), customEqualityFn = function() { return true; }; - env.addCustomEqualityTester(customEqualityFn); - env.addMatchers({ - toBeReal: argumentSpy - }); env.it("spec with expectation", function() { + env.addCustomEqualityTester(customEqualityFn); + env.addMatchers({ + toBeReal: argumentSpy + }); + env.expect(true).toBeReal(); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index d566bc65..7ce52921 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1,4 +1,44 @@ describe("Env integration", function() { + beforeEach(function() { + jasmine.addMatchers({ + toHaveFailedExpecationsForSuite: function(util, customeEqualityTesters) { + return { + compare: function(actual, suiteName, expectedFailures) { + var foundSuite = false, expectations = true, foundFailures = []; + for (var i = 0; i < actual.calls.count(); i++) { + var args = actual.calls.argsFor(i)[0]; + + if (args.description === suiteName) { + foundSuite = true; + + for (var j = 0; j < args.failedExpectations.length; j++) { + foundFailures.push(args.failedExpectations[j].message); + } + + for (var j = 0; j < expectedFailures.length; j++) { + var failure = foundFailures[j]; + var expectedFailure = expectedFailures[j]; + + if (Object.prototype.toString.call(expectedFailure) === '[object RegExp]') { + expectations = expectations && expectedFailure.test(failure); + } else { + expectations = expectations && failure === expectedFailure; + } + } + break; + } + } + + return { + pass: foundSuite && expectations, + message: !foundSuite ? 'The suite "' + suiteName + '" never finished' : + 'Expected suite "' + suiteName + '" to have failures ' + jasmine.pp(expectedFailures) + ' but it had ' + jasmine.pp(foundFailures) + }; + } + }; + } + }); + }); it("Suites execute as expected (no nesting)", function(done) { var env = new j$.Env(), @@ -80,7 +120,7 @@ describe("Env integration", function() { env.describe("Outer suite", function() { env.it("an outer spec", function() { - calls.push('an outer spec') + calls.push('an outer spec'); }); env.describe("Inner suite", function() { env.it("an inner spec", function() { @@ -212,6 +252,305 @@ describe("Env integration", function() { env.execute(); }); + it("calls associated beforeAlls/afterAlls only once per suite", function(done) { + var env = new j$.Env(), + before = jasmine.createSpy('beforeAll'), + after = jasmine.createSpy('afterAll'); + + env.addReporter({ + jasmineDone: function() { + expect(after).toHaveBeenCalled(); + expect(after.calls.count()).toBe(1); + expect(before.calls.count()).toBe(1); + done(); + } + }); + + env.describe("with beforeAll and afterAll", function() { + env.it("spec", function() { + expect(before).toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + }); + + env.it("another spec", function() { + expect(before).toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + }); + + env.beforeAll(before); + env.afterAll(after); + }); + + env.execute(); + }); + + it("calls associated beforeAlls/afterAlls only once per suite for async", function(done) { + var env = new j$.Env(), + before = jasmine.createSpy('beforeAll'), + after = jasmine.createSpy('afterAll'); + + env.addReporter({ + jasmineDone: function() { + expect(after).toHaveBeenCalled(); + expect(after.calls.count()).toBe(1); + expect(before.calls.count()).toBe(1); + done(); + } + }); + + env.describe("with beforeAll and afterAll", function() { + env.it("spec", function() { + expect(before).toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + }); + + env.it("another spec", function() { + expect(before).toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + }); + + env.beforeAll(function(beforeCallbackUnderTest) { + before(); + beforeCallbackUnderTest(); + }); + + env.afterAll(function(afterCallbackUnderTest) { + after(); + afterCallbackUnderTest(); + }); + }); + + env.execute(); + }); + + it("calls associated beforeAlls/afterAlls with the cascaded 'this'", function(done) { + var env = new j$.Env(); + + env.addReporter({jasmineDone: done}); + + env.describe("with beforeAll and afterAll", function() { + env.beforeAll(function() { + this.x = 1; + }); + + env.it("has an x at the root", function() { + expect(this.x).toBe(1); + }); + + env.describe("child that deletes", function() { + env.beforeAll(function() { + expect(this.x).toBe(1); + delete this.x; + }); + + env.it("has no x", function() { + expect(this.x).not.toBeDefined(); + }); + }); + + env.describe("child should still have x", function() { + env.beforeAll(function(innerDone) { + expect(this.x).toBe(1); + innerDone(); + }); + + env.it("has an x", function() { + expect(this.x).toBe(1); + delete this.x; + }); + + env.it("still has an x", function() { + expect(this.x).toBe(1); + }); + + env.it("adds a y", function() { + this.y = 2; + expect(this.y).toBe(2); + }); + + env.it("doesn't have y that was added in sibling", function() { + expect(this.y).not.toBeDefined(); + }); + }); + }); + + env.execute(); + }); + + it("fails all underlying specs when the beforeAll fails", function (done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.specDone.calls.count()).toEqual(2); + + expect(reporter.specDone.calls.argsFor(0)[0]) + .toEqual(jasmine.objectContaining({status: 'failed'})); + expect(reporter.specDone.calls.argsFor(0)[0].failedExpectations[0].message) + .toEqual("Expected 1 to be 2."); + + expect(reporter.specDone.calls.argsFor(1)[0]) + .toEqual(jasmine.objectContaining({status: 'failed'})); + expect(reporter.specDone.calls.argsFor(1)[0].failedExpectations[0].message) + .toEqual("Expected 1 to be 2."); + done(); + }); + + env.addReporter(reporter); + + env.describe('A suite', function(){ + env.beforeAll(function() { + env.expect(1).toBe(2); + }); + + env.it("spec that will be failed", function() { + }); + + env.describe("nesting", function() { + env.it("another spec to fail", function() { + }); + }); + }); + + env.execute(); + }); + + describe('suiteDone reporting', function(){ + it("reports when an afterAll fails an expectation", function(done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + 'Expected 1 to equal 2.', + 'Expected 2 to equal 3.' + ]); + done(); + }); + + env.addReporter(reporter); + + env.describe('my suite', function() { + env.it('my spec', function() { + }); + + env.afterAll(function() { + env.expect(1).toEqual(2); + env.expect(2).toEqual(3); + }); + }); + + env.execute(); + }); + + it("if there are no specs, it still reports correctly", function(done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('outer suite', [ + 'Expected 1 to equal 2.', + 'Expected 2 to equal 3.' + ]); + done(); + }); + + env.addReporter(reporter); + + env.describe('outer suite', function() { + env.describe('inner suite', function() { + env.it('spec', function(){ }); + }); + + env.afterAll(function() { + env.expect(1).toEqual(2); + env.expect(2).toEqual(3); + }); + }); + + env.execute(); + }); + + it("reports when afterAll throws an exception", function(done) { + var env = new j$.Env(), + error = new Error('After All Exception'), + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + (/^Error: After All Exception/) + ]); + done(); + }); + + env.addReporter(reporter); + + env.describe('my suite', function() { + env.it('my spec', function() { + }); + + env.afterAll(function() { + throw error; + }); + }); + + env.execute(); + }); + + it("reports when an async afterAll fails an expectation", function(done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + 'Expected 1 to equal 2.' + ]); + done(); + }); + + env.addReporter(reporter); + + env.describe('my suite', function() { + env.it('my spec', function() { + }); + + env.afterAll(function(afterAllDone) { + env.expect(1).toEqual(2); + afterAllDone(); + }); + }); + + env.execute(); + }); + + it("reports when an async afterAll throws an exception", function(done) { + var env = new j$.Env(), + error = new Error('After All Exception'), + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); + + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + (/^Error: After All Exception/) + ]); + done(); + }); + + env.addReporter(reporter); + + env.describe('my suite', function() { + env.it('my spec', function() { + }); + + env.afterAll(function(afterAllDone) { + throw error; + }); + }); + + env.execute(); + }); + }); + it("Allows specifying which specs and suites to run", function(done) { var env = new j$.Env(), calls = [], @@ -248,21 +587,61 @@ describe("Env integration", function() { env.execute([secondSuite.id, firstSpec.id]); }); - it("Functions can be spied on and have their calls tracked", function () { - var env = new j$.Env(); + it('runs before and after all functions for runnables provided to .execute()', function(done) { + var env = new j$.Env(), + calls = [], + first_spec, + second_spec; - var originalFunctionWasCalled = false; - var subject = { - spiedFunc: function() { - originalFunctionWasCalled = true; - return "original result"; - } - }; + var assertions = function() { + expect(calls).toEqual([ + "before", + "first spec", + "after", + "before", + "second spec", + "after" + ]); + done(); + }; + env.addReporter({jasmineDone: assertions}); + + env.describe("first suite", function() { + env.beforeAll(function() { + calls.push("before"); + }); + env.afterAll(function() { + calls.push("after") + }); + first_spec = env.it("spec", function() { + calls.push('first spec'); + }); + second_spec = env.it("spec 2", function() { + calls.push("second spec"); + }); + }); + + env.execute([first_spec.id, second_spec.id]); + }); + + it("Functions can be spied on and have their calls tracked", function (done) { + var env = new j$.Env(); + + var originalFunctionWasCalled = false; + var subject = { + spiedFunc: function() { + originalFunctionWasCalled = true; + return "original result"; + } + }; + + env.addReporter({jasmineDone: done}); + + env.it("works with spies", function() { var spy = env.spyOn(subject, 'spiedFunc').and.returnValue("stubbed result"); expect(subject.spiedFunc).toEqual(spy); - expect(subject.spiedFunc.calls.any()).toEqual(false); expect(subject.spiedFunc.calls.count()).toEqual(0); @@ -281,6 +660,67 @@ describe("Env integration", function() { expect(subject.spiedFunc.calls.mostRecent().args).toEqual(['bar']); expect(subject.spiedFunc.calls.mostRecent().returnValue).toEqual("original result"); expect(originalFunctionWasCalled).toEqual(true); + }); + + env.execute(); + }); + + it('removes all spies added in a spec after the spec is complete', function(done) { + var env = new j$.Env(), + originalFoo = function() {}, + testObj = { + foo: originalFoo + }, + firstSpec = jasmine.createSpy('firstSpec').and.callFake(function() { + env.spyOn(testObj, 'foo'); + }), + secondSpec = jasmine.createSpy('secondSpec').and.callFake(function() { + expect(testObj.foo).toBe(originalFoo); + }); + env.describe('test suite', function() { + env.it('spec 0', firstSpec); + env.it('spec 1', secondSpec); + }); + + var assertions = function() { + expect(firstSpec).toHaveBeenCalled(); + expect(secondSpec).toHaveBeenCalled(); + done(); + }; + + env.addReporter({ jasmineDone: assertions }); + + env.execute(); + }); + + it('removes all spies added in a suite after the suite is complete', function(done) { + var env = new j$.Env(), + originalFoo = function() {}, + testObj = { + foo: originalFoo + }; + + env.describe('test suite', function() { + env.beforeAll(function() { env.spyOn(testObj, 'foo');}) + + env.it('spec 0', function() { + expect(j$.isSpy(testObj.foo)).toBe(true); + }); + + env.it('spec 1', function() { + expect(j$.isSpy(testObj.foo)).toBe(true); + }); + }); + + env.describe('another suite', function() { + env.it('spec 2', function() { + expect(j$.isSpy(testObj.foo)).toBe(false); + }); + }); + + env.addReporter({ jasmineDone: done }); + + env.execute(); }); it("Mock clock can be installed and used in tests", function(done) { @@ -345,11 +785,11 @@ describe("Env integration", function() { beforeEach(function() { originalTimeout = j$.DEFAULT_TIMEOUT_INTERVAL; - jasmine.getEnv().clock.install(); + jasmine.clock().install(); }); afterEach(function() { - jasmine.getEnv().clock.uninstall(); + jasmine.clock().uninstall(); j$.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; }); @@ -371,7 +811,63 @@ describe("Env integration", function() { env.it("async spec that doesn't call done", function(underTestCallback) { env.expect(true).toBeTruthy(); - jasmine.getEnv().clock.tick(8416); + jasmine.clock().tick(8416); + }); + + env.execute(); + }); + + it("should wait a specified interval before failing beforeAll's and their associated specs that haven't called done", function(done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.specDone.calls.count()).toEqual(2); + expect(reporter.specDone.calls.argsFor(0)[0]).toEqual(jasmine.objectContaining({status: 'failed'})); + expect(reporter.specDone.calls.argsFor(1)[0]).toEqual(jasmine.objectContaining({status: 'failed'})); + done(); + }); + + env.addReporter(reporter); + j$.DEFAULT_TIMEOUT_INTERVAL = 1290; + + env.beforeAll(function(done) { + jasmine.clock().tick(1290); + }); + + env.it("spec that will be failed", function() { + }); + + env.describe("nesting", function() { + env.it("another spec to fail", function() { + }); + }); + + env.execute(); + }); + + it("should wait the specified interval before reporting an afterAll that fails to call done", function(done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + (/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./) + ]); + done(); + }); + + env.addReporter(reporter); + j$.DEFAULT_TIMEOUT_INTERVAL = 3000; + + env.describe('my suite', function() { + env.it('my spec', function() { + }); + + env.afterAll(function(innerDone) { + jasmine.clock().tick(3001); + innerDone(); + }); }); env.execute(); @@ -436,7 +932,58 @@ describe("Env integration", function() { }); }); - // TODO: something is wrong with this spec + describe('focused tests', function() { + it('should only run the focused tests', function(done) { + var env = new j$.Env(), + calls = []; + + var assertions = function() { + expect(calls).toEqual(['focused']); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.describe('a suite', function() { + env.fit('is focused', function() { + calls.push('focused'); + }); + + env.it('is not focused', function() { + calls.push('freakout'); + }) + }); + + env.execute(); + }); + + it('should only run focused suites', function(){ + var env = new j$.Env(), + calls = []; + + var assertions = function() { + expect(calls).toEqual(['focused']); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.fdescribe('a focused suite', function() { + env.it('is focused', function() { + calls.push('focused'); + }); + }); + + env.describe('a regular suite', function() { + env.it('is not focused', function() { + calls.push('freakout'); + }) + }); + + env.execute(); + }); + }); + it("should report as expected", function(done) { var env = new j$.Env(), reporter = jasmine.createSpyObj('fakeReporter', [ @@ -502,11 +1049,7 @@ describe("Env integration", function() { it("Custom equality testers should be per spec", function(done) { var env = new j$.Env({global: { setTimeout: setTimeout }}), reporter = jasmine.createSpyObj('fakeReporter', [ - "jasmineStarted", "jasmineDone", - "suiteStarted", - "suiteDone", - "specStarted", "specDone" ]); @@ -536,30 +1079,42 @@ describe("Env integration", function() { env.execute(); }); - it("Custom matchers should be per spec", function() { + it("Custom equality testers should be per suite", function(done) { var env = new j$.Env({global: { setTimeout: setTimeout }}), - matchers = { - toFoo: function() {} - }, reporter = jasmine.createSpyObj('fakeReporter', [ - "jasmineStarted", "jasmineDone", - "suiteStarted", - "suiteDone", - "specStarted", "specDone" ]); + reporter.jasmineDone.and.callFake(function() { + var firstSpecResult = reporter.specDone.calls.first().args[0], + secondSpecResult = reporter.specDone.calls.argsFor(0)[0], + thirdSpecResult = reporter.specDone.calls.mostRecent().args[0]; + + expect(firstSpecResult.status).toEqual("passed"); + expect(secondSpecResult.status).toEqual("passed"); + expect(thirdSpecResult.status).toEqual("failed"); + + done(); + }); + env.addReporter(reporter); - env.describe("testing custom matchers", function() { - env.it("with a custom matcher", function() { - env.addMatchers(matchers); - expect(env.expect().toFoo).toBeDefined(); + env.describe("testing custom equality testers", function() { + env.beforeAll(function() { env.addCustomEqualityTester(function(a, b) { return true; }); }); + + env.it("with a custom tester", function() { + env.expect("a").toEqual("b"); }); - env.it("without a custom matcher", function() { - expect(env.expect().toFoo).toBeUndefined(); + env.it("with the same custom tester", function() { + env.expect("a").toEqual("b"); + }); + }); + + env.describe("another suite", function() { + env.it("without the custom tester", function(){ + env.expect("a").toEqual("b"); }); }); @@ -569,11 +1124,7 @@ describe("Env integration", function() { it("Custom equality testers for toContain should be per spec", function(done) { var env = new j$.Env({global: { setTimeout: setTimeout }}), reporter = jasmine.createSpyObj('fakeReporter', [ - "jasmineStarted", "jasmineDone", - "suiteStarted", - "suiteDone", - "specStarted", "specDone" ]); @@ -596,7 +1147,7 @@ describe("Env integration", function() { }); env.it("without a custom tester", function() { - env.expect("a").toContain("b"); + env.expect(["a"]).toContain("b"); }); }); @@ -616,5 +1167,166 @@ describe("Env integration", function() { env.execute(); }); -}); + it("Custom equality testers for toContain should be per suite", function(done) { + var env = new j$.Env({global: { setTimeout: setTimeout }}), + reporter = jasmine.createSpyObj('fakeReporter', [ + "jasmineDone", + "specDone" + ]); + + reporter.jasmineDone.and.callFake(function() { + var firstSpecResult = reporter.specDone.calls.first().args[0], + secondSpecResult = reporter.specDone.calls.argsFor(1)[0], + thirdSpecResult = reporter.specDone.calls.mostRecent().args[0]; + + expect(firstSpecResult.status).toEqual("passed"); + expect(secondSpecResult.status).toEqual("passed"); + expect(thirdSpecResult.status).toEqual("failed"); + + done(); + }); + + env.addReporter(reporter); + + env.describe("testing custom equality testers", function() { + env.beforeAll(function() { env.addCustomEqualityTester(function(a, b) { return true; })}); + + env.it("with a custom tester", function() { + env.expect(["a"]).toContain("b"); + }); + + env.it("also with the custom tester", function() { + env.expect(["a"]).toContain("b"); + }); + }); + + env.describe("another suite", function() { + env.it("without the custom tester", function() { + env.expect(["a"]).toContain("b"); + }); + }); + + env.execute(); + }); + + it("Custom matchers should be per spec", function(done) { + var env = new j$.Env({global: { setTimeout: setTimeout }}), + matchers = { + toFoo: function() {} + }; + + env.describe("testing custom matchers", function() { + env.it("with a custom matcher", function() { + env.addMatchers(matchers); + expect(env.expect().toFoo).toBeDefined(); + }); + + env.it("without a custom matcher", function() { + expect(env.expect().toFoo).toBeUndefined(); + }); + }); + + env.addReporter({jasmineDone: done}); + + env.execute(); + }); + + it("Custom matchers should be per suite", function(done) { + var env = new j$.Env({global: { setTimeout: setTimeout }}), + matchers = { + toFoo: function() {} + }; + + env.describe("testing custom matchers", function() { + env.beforeAll(function() { env.addMatchers(matchers); }); + + env.it("with a custom matcher", function() { + expect(env.expect().toFoo).toBeDefined(); + }); + + env.it("with the same custom matcher", function() { + expect(env.expect().toFoo).toBeDefined(); + }); + }); + + env.describe("another suite", function() { + env.it("no longer has the custom matcher", function() { + expect(env.expect().toFoo).not.toBeDefined(); + }); + }); + + env.addReporter({jasmineDone: done}); + + env.execute(); + }); + + it('throws an exception if you try to create a spy outside of a runnable', function (done) { + var env = new j$.Env(), + obj = {fn: function () {}}, + exception; + + env.describe("a suite", function () { + try { + env.spyOn(obj, 'fn'); + } catch(e) { + exception = e; + } + }); + + var assertions = function() { + expect(exception.message).toBe('Spies must be created in a before function or a spec'); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.execute(); + }); + + it('throws an exception if you try to add a matcher outside of a runnable', function (done) { + var env = new j$.Env(), + obj = {fn: function () {}}, + exception; + + env.describe("a suite", function () { + try { + env.addMatchers({myMatcher: function(actual,expected){return false;}}); + } catch(e) { + exception = e; + } + }); + + var assertions = function() { + expect(exception.message).toBe('Matchers must be added in a before function or a spec'); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.execute(); + }); + + it('throws an exception if you try to add a custom equality outside of a runnable', function (done) { + var env = new j$.Env(), + obj = {fn: function () {}}, + exception; + + env.describe("a suite", function () { + try { + env.addCustomEqualityTester(function(first, second) {return true;}); + } catch(e) { + exception = e; + } + }); + + var assertions = function() { + expect(exception.message).toBe('Custom Equalities must be added in a before function or a spec'); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.execute(); + }); +}); diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index 3adf8d06..e1675d00 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -159,7 +159,7 @@ describe("jasmine spec running", function () { ]; expect(actions).toEqual(expected); done(); - } + }; env.addReporter({jasmineDone: assertions}); @@ -228,6 +228,257 @@ describe("jasmine spec running", function () { env.execute(); }); + it('should run beforeAlls before beforeEachs and afterAlls after afterEachs', function() { + var actions = []; + + env.beforeAll(function() { + actions.push('runner beforeAll'); + }); + + env.afterAll(function() { + actions.push('runner afterAll'); + }); + + env.beforeEach(function () { + actions.push('runner beforeEach'); + }); + + env.afterEach(function () { + actions.push('runner afterEach'); + }); + + env.describe('Something', function() { + env.beforeEach(function() { + actions.push('inner beforeEach'); + }); + + env.afterEach(function() { + actions.push('inner afterEach'); + }); + + env.beforeAll(function() { + actions.push('inner beforeAll'); + }); + + env.afterAll(function() { + actions.push('inner afterAll'); + }); + + env.it('does something or other', function() { + actions.push('it'); + }); + }); + + var assertions = function() { + var expected = [ + "runner beforeAll", + "inner beforeAll", + "runner beforeEach", + "inner beforeEach", + "it", + "inner afterEach", + "runner afterEach", + "inner afterAll", + "runner afterAll" + ]; + expect(actions).toEqual(expected); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + env.execute(); + }); + + it('should run beforeAlls and afterAlls as beforeEachs and afterEachs in the order declared when runnablesToRun is provided', function() { + var actions = [], + spec, + spec2; + + env.beforeAll(function() { + actions.push('runner beforeAll'); + }); + + env.afterAll(function() { + actions.push('runner afterAll'); + }); + + env.beforeEach(function () { + actions.push('runner beforeEach'); + }); + + env.afterEach(function () { + actions.push('runner afterEach'); + }); + + env.describe('Something', function() { + env.beforeEach(function() { + actions.push('inner beforeEach'); + }); + + env.afterEach(function() { + actions.push('inner afterEach'); + }); + + env.beforeAll(function() { + actions.push('inner beforeAll'); + }); + + env.afterAll(function() { + actions.push('inner afterAll'); + }); + + spec = env.it('does something', function() { + actions.push('it'); + }); + + spec2 = env.it('does something or other', function() { + actions.push('it2'); + }); + }); + + var assertions = function() { + var expected = [ + "runner beforeAll", + "inner beforeAll", + "runner beforeEach", + "inner beforeEach", + "it", + "inner afterEach", + "runner afterEach", + "inner afterAll", + "runner afterAll", + + "runner beforeAll", + "inner beforeAll", + "runner beforeEach", + "inner beforeEach", + "it2", + "inner afterEach", + "runner afterEach", + "inner afterAll", + "runner afterAll" + ]; + expect(actions).toEqual(expected); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + env.execute([spec.id, spec2.id]); + }); + + describe('focused runnables', function() { + it('runs the relevant alls and eachs for each runnable', function(done) { + var actions = []; + env.beforeAll(function() {actions.push('beforeAll')}); + env.afterAll(function() {actions.push('afterAll')}); + env.beforeEach(function() {actions.push('beforeEach')}); + env.afterEach(function() {actions.push('afterEach')}); + + env.fdescribe('a focused suite', function() { + env.it('is run', function() { + actions.push('spec in fdescribe') + }); + }); + + env.describe('an unfocused suite', function() { + env.fit('has a focused spec', function() { + actions.push('focused spec') + }); + }); + + var assertions = function() { + var expected = [ + 'beforeAll', + 'beforeEach', + 'spec in fdescribe', + 'afterEach', + 'afterAll', + + 'beforeAll', + 'beforeEach', + 'focused spec', + 'afterEach', + 'afterAll' + ]; + expect(actions).toEqual(expected); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + env.execute(); + }); + + it('focused specs in focused suites cause non-focused siblings to not run', function(done){ + var actions = []; + + env.fdescribe('focused suite', function() { + env.it('unfocused spec', function() { + actions.push('unfocused spec') + }); + env.fit('focused spec', function() { + actions.push('focused spec') + }); + }); + + var assertions = function() { + var expected = ['focused spec']; + expect(actions).toEqual(expected); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + env.execute(); + }); + + it('focused suites in focused suites cause non-focused siblings to not run', function(done){ + var actions = []; + + env.fdescribe('focused suite', function() { + env.it('unfocused spec', function() { + actions.push('unfocused spec') + }); + env.fdescribe('inner focused suite', function() { + env.it('inner spec', function() { + actions.push('inner spec'); + }); + }); + }); + + var assertions = function() { + var expected = ['inner spec']; + expect(actions).toEqual(expected); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + env.execute(); + }); + + it('focused runnables unfocus ancestor focused suites', function() { + var actions = []; + + env.fdescribe('focused suite', function() { + env.it('unfocused spec', function() { + actions.push('unfocused spec') + }); + env.describe('inner focused suite', function() { + env.fit('focused spec', function() { + actions.push('focused spec'); + }); + }); + }); + + var assertions = function() { + var expected = ['focused spec']; + expect(actions).toEqual(expected); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + env.execute(); + }); + }); + it("shouldn't run disabled suites", function(done) { var specInADisabledSuite = jasmine.createSpy("specInADisabledSuite"), suite = env.describe('A Suite', function() { @@ -236,10 +487,36 @@ describe("jasmine spec running", function () { }); }); - suite.execute(function() { + var assertions = function() { expect(specInADisabledSuite).not.toHaveBeenCalled(); done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.execute(); + }); + + it("should allow top level suites to be disabled", function() { + var specInADisabledSuite = jasmine.createSpy("specInADisabledSuite"), + otherSpec = jasmine.createSpy("otherSpec"); + + env.xdescribe('A disabled suite', function() { + env.it('spec inside a disabled suite', specInADisabledSuite); }); + env.describe('Another suite', function() { + env.it('another spec', otherSpec); + }); + + var assertions = function() { + expect(specInADisabledSuite).not.toHaveBeenCalled(); + expect(otherSpec).toHaveBeenCalled(); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.execute(); }); it("should set all pending specs to pending when a suite is run", function(done) { @@ -254,7 +531,6 @@ describe("jasmine spec running", function () { }); }); - // TODO: is this useful? It doesn't catch syntax errors xit("should recover gracefully when there are errors in describe functions", function() { var specs = []; diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 57b4872d..8c952e6a 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -179,6 +179,34 @@ describe("New HtmlReporter", function() { }); }); + describe("when there are suite failures", function () { + it("displays the exceptions in their own alert bars", function(){ + var env = new j$.Env(), + container = document.createElement("div"), + getContainer = function() { return container; }, + reporter = new j$.HtmlReporter({ + env: env, + getContainer: getContainer, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + + reporter.initialize(); + + reporter.jasmineStarted({}); + reporter.suiteDone({ failedExpectations: [{ message: 'My After All Exception' }] }); + reporter.suiteDone({ failedExpectations: [{ message: 'My Other Exception' }] }); + reporter.jasmineDone({}); + + var alertBars = container.querySelectorAll(".alert .bar"); + + expect(alertBars.length).toEqual(3); + expect(alertBars[1].innerHTML).toMatch(/My After All Exception/); + expect(alertBars[1].getAttribute("class")).toEqual('bar errored'); + expect(alertBars[2].innerHTML).toMatch(/My Other Exception/); + }); + }); + describe("when Jasmine is done", function() { it("adds EMPTY to the link title of specs that have no expectations", function() { if (!window.console) { diff --git a/spec/node_suite.js b/spec/node_suite.js new file mode 100644 index 00000000..54f09488 --- /dev/null +++ b/spec/node_suite.js @@ -0,0 +1,195 @@ +var fs = require('fs'); +var util = require('util'); +var path = require('path'); + +// boot code for jasmine +var jasmineRequire = require('../lib/jasmine-core/jasmine.js'); +var jasmine = jasmineRequire.core(jasmineRequire); + +var consoleFns = require('../lib/console/console.js'); +extend(jasmineRequire, consoleFns); +jasmineRequire.console(jasmineRequire, jasmine); + +var env = jasmine.getEnv(); + +var jasmineInterface = { + describe: function(description, specDefinitions) { + return env.describe(description, specDefinitions); + }, + + xdescribe: function(description, specDefinitions) { + return env.xdescribe(description, specDefinitions); + }, + + it: function(desc, func) { + return env.it(desc, func); + }, + + xit: function(desc, func) { + return env.xit(desc, func); + }, + + beforeEach: function(beforeEachFunction) { + return env.beforeEach(beforeEachFunction); + }, + + afterEach: function(afterEachFunction) { + return env.afterEach(afterEachFunction); + }, + + expect: function(actual) { + return env.expect(actual); + }, + + spyOn: function(obj, methodName) { + return env.spyOn(obj, methodName); + }, + + jsApiReporter: new jasmine.JsApiReporter({ + timer: new jasmine.Timer() + }), + + beforeAll: function(beforeAllFunction) { + return env.beforeAll(beforeAllFunction); + }, + + afterAll: function(afterAllFunction) { + return env.afterAll(afterAllFunction); + } +}; + +extend(global, jasmineInterface); + +function extend(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; +} + +jasmine.addCustomEqualityTester = function(tester) { + env.addCustomEqualityTester(tester); +}; + +jasmine.addMatchers = function(matchers) { + return env.addMatchers(matchers); +}; + +jasmine.clock = function() { + return env.clock; +}; + +// Jasmine "runner" +function executeSpecs(specs, done, isVerbose, showColors) { + global.jasmine = jasmine; + + for (var i = 0; i < specs.length; i++) { + var filename = specs[i]; + require(filename.replace(/\.\w+$/, "")); + } + + var env = jasmine.getEnv(); + var consoleReporter = new jasmine.ConsoleReporter({ + print: util.print, + onComplete: done, + showColors: showColors, + timer: new jasmine.Timer() + }); + + env.addReporter(consoleReporter); + env.execute(); +} + +function getFiles(dir, matcher) { + var allFiles = []; + + if (fs.statSync(dir).isFile() && dir.match(matcher)) { + allFiles.push(dir); + } else { + var files = fs.readdirSync(dir); + for (var i = 0, len = files.length; i < len; ++i) { + var filename = dir + '/' + files[i]; + if (fs.statSync(filename).isFile() && filename.match(matcher)) { + allFiles.push(filename); + } else if (fs.statSync(filename).isDirectory()) { + var subfiles = getFiles(filename); + subfiles.forEach(function(result) { + allFiles.push(result); + }); + } + } + } + return allFiles; +} + +function getSpecFiles(dir) { + return getFiles(dir, new RegExp("Spec.js$")); +} + +var j$require = (function() { + var exported = {}, + j$req; + + global.getJasmineRequireObj = getJasmineRequireObj; + + j$req = require(__dirname + "/../src/core/requireCore.js"); + extend(j$req, require(__dirname + "/../src/console/requireConsole.js")); + + var srcFiles = getFiles(__dirname + "/../src/core"); + srcFiles.push(__dirname + "/../src/version.js"); + srcFiles.push(__dirname + "/../src/console/ConsoleReporter.js"); + + for (var i = 0; i < srcFiles.length; i++) { + require(srcFiles[i]); + } + extend(j$req, exported); + + delete global.getJasmineRequireObj; + + return j$req; + + function getJasmineRequireObj() { + return exported; + } +}()); + +j$ = j$require.core(j$require); +j$require.console(j$require, j$); + +// options from command line +var isVerbose = false; +var showColors = true; +var perfSuite = false; + +process.argv.forEach(function(arg) { + switch (arg) { + case '--color': + showColors = true; + break; + case '--noColor': + showColors = false; + break; + case '--verbose': + isVerbose = true; + break; + case '--perf': + perfSuite = true; + break; + } +}); + +specs = []; + +if (perfSuite) { + specs = getFiles(__dirname + '/performance', new RegExp("test.js$")); +} else { + var consoleSpecs = getSpecFiles(__dirname + "/console"), + coreSpecs = getSpecFiles(__dirname + "/core"), + specs = consoleSpecs.concat(coreSpecs); +} + +executeSpecs(specs, function(passed) { + if (passed) { + process.exit(0); + } else { + process.exit(1); + } +}, isVerbose, showColors); diff --git a/src/console/ConsoleReporter.js b/src/console/ConsoleReporter.js index a3895f45..ba383466 100644 --- a/src/console/ConsoleReporter.js +++ b/src/console/ConsoleReporter.js @@ -19,7 +19,8 @@ getJasmineRequireObj().ConsoleReporter = function() { red: '\x1B[31m', yellow: '\x1B[33m', none: '\x1B[0m' - }; + }, + failedSuites = []; this.jasmineStarted = function() { specCount = 0; @@ -54,9 +55,12 @@ getJasmineRequireObj().ConsoleReporter = function() { printNewline(); var seconds = timer.elapsed() / 1000; print('Finished in ' + seconds + ' ' + plural('second', seconds)); - printNewline(); + for(i = 0; i < failedSuites.length; i++) { + suiteFailureDetails(failedSuites[i]); + } + onComplete(failureCount === 0); }; @@ -81,6 +85,13 @@ getJasmineRequireObj().ConsoleReporter = function() { } }; + this.suiteDone = function(result) { + if (result.failedExpectations && result.failedExpectations.length > 0) { + failureCount++; + failedSuites.push(result); + } + }; + return this; function printNewline() { @@ -125,6 +136,17 @@ getJasmineRequireObj().ConsoleReporter = function() { printNewline(); } + + function suiteFailureDetails(result) { + for (var i = 0; i < result.failedExpectations.length; i++) { + printNewline(); + print(colored('red', 'An error was thrown in an afterAll')); + printNewline(); + print(colored('red', 'AfterAll ' + result.failedExpectations[i].message)); + + } + printNewline(); + } } return ConsoleReporter; diff --git a/src/core/Env.js b/src/core/Env.js index 801251e5..30e0aed6 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -14,11 +14,19 @@ getJasmineRequireObj().Env = function(j$) { this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler(), new j$.MockDate(global)); var runnableLookupTable = {}; - - var spies = []; + var runnableResources = {}; var currentSpec = null; - var currentSuite = null; + var currentlyExecutingSuites = []; + var currentDeclarationSuite = null; + + var currentSuite = function() { + return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; + }; + + var currentRunnable = function() { + return currentSpec || currentSuite(); + }; var reporter = new j$.ReportDispatcher([ 'jasmineStarted', @@ -33,11 +41,21 @@ getJasmineRequireObj().Env = function(j$) { return true; }; - var equalityTesters = []; - - var customEqualityTesters = []; this.addCustomEqualityTester = function(tester) { - customEqualityTesters.push(tester); + if(!currentRunnable()) { + throw new Error('Custom Equalities must be added in a before function or a spec'); + } + runnableResources[currentRunnable().id].customEqualityTesters.push(tester); + }; + + this.addMatchers = function(matchersToAdd) { + if(!currentRunnable()) { + throw new Error('Matchers must be added in a before function or a spec'); + } + var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { + customMatchers[matcherName] = matchersToAdd[matcherName]; + } }; j$.Expectation.addCoreMatchers(j$.matchers); @@ -55,7 +73,8 @@ getJasmineRequireObj().Env = function(j$) { var expectationFactory = function(actual, spec) { return j$.Expectation.Factory({ util: j$.matchersUtil, - customEqualityTesters: customEqualityTesters, + customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult }); @@ -65,30 +84,44 @@ getJasmineRequireObj().Env = function(j$) { } }; - var specStarted = function(spec) { - currentSpec = spec; - reporter.specStarted(spec.result); + var defaultResourcesForRunnable = function(id, parentRunnableId) { + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + + if(runnableResources[parentRunnableId]){ + resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); + resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); + } + + runnableResources[id] = resources; }; - var beforeFns = function(suite) { + var clearResourcesForRunnable = function(id) { + spyRegistry.clearSpies(); + delete runnableResources[id]; + }; + + var beforeAndAfterFns = function(suite, runnablesExplictlySet) { return function() { - var befores = []; + var befores = [], + afters = [], + beforeAlls = [], + afterAlls = []; + while(suite) { befores = befores.concat(suite.beforeFns); - suite = suite.parentSuite; - } - return befores.reverse(); - }; - }; - - var afterFns = function(suite) { - return function() { - var afters = []; - while(suite) { afters = afters.concat(suite.afterFns); + + if (runnablesExplictlySet()) { + beforeAlls = beforeAlls.concat(suite.beforeAllFns); + afterAlls = afterAlls.concat(suite.afterAllFns); + } + suite = suite.parentSuite; } - return afters; + return { + befores: beforeAlls.reverse().concat(befores.reverse()), + afters: afters.concat(afterAlls) + }; }; }; @@ -146,65 +179,54 @@ getJasmineRequireObj().Env = function(j$) { id: getNextSuiteId(), description: 'Jasmine__TopLevel__Suite', queueRunner: queueRunnerFactory, - resultCallback: function() {} // TODO - hook this up + resultCallback: function(attrs) { + reporter.suiteDone(attrs); + } }); runnableLookupTable[topSuite.id] = topSuite; - currentSuite = topSuite; + defaultResourcesForRunnable(topSuite.id); + currentDeclarationSuite = topSuite; this.topSuite = function() { return topSuite; }; this.execute = function(runnablesToRun) { - runnablesToRun = runnablesToRun || [topSuite.id]; + if(runnablesToRun) { + runnablesExplictlySet = true; + } else if (focusedRunnables.length) { + runnablesExplictlySet = true; + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; + } var allFns = []; for(var i = 0; i < runnablesToRun.length; i++) { var runnable = runnableLookupTable[runnablesToRun[i]]; - allFns.push((function(runnable) { return function(done) { runnable.execute(done); }; })(runnable)); + allFns.push((function(runnable) { return { fn: function(done) { runnable.execute(done); } }; })(runnable)); } reporter.jasmineStarted({ totalSpecsDefined: totalSpecsDefined }); - queueRunnerFactory({fns: allFns, onComplete: reporter.jasmineDone}); + queueRunnerFactory({queueableFns: allFns, onComplete: reporter.jasmineDone}); }; this.addReporter = function(reporterToAdd) { reporter.addReporter(reporterToAdd); }; - this.addMatchers = function(matchersToAdd) { - j$.Expectation.addMatchers(matchersToAdd); - }; - - this.spyOn = function(obj, methodName) { - if (j$.util.isUndefined(obj)) { - throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); + var spyRegistry = new j$.SpyRegistry({currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); } + return runnableResources[currentRunnable().id].spies; + }}); - if (j$.util.isUndefined(obj[methodName])) { - throw new Error(methodName + '() method does not exist'); - } - - if (obj[methodName] && j$.isSpy(obj[methodName])) { - //TODO?: should this return the current spy? Downside: may cause user confusion about spy state - throw new Error(methodName + ' has already been spied upon'); - } - - var spy = j$.createSpy(methodName, obj[methodName]); - - spies.push({ - spy: spy, - baseObj: obj, - methodName: methodName, - originalValue: obj[methodName] - }); - - obj[methodName] = spy; - - return spy; + this.spyOn = function() { + return spyRegistry.spyOn.apply(spyRegistry, arguments); }; var suiteFactory = function(description) { @@ -212,40 +234,33 @@ getJasmineRequireObj().Env = function(j$) { env: self, id: getNextSuiteId(), description: description, - parentSuite: currentSuite, + parentSuite: currentDeclarationSuite, queueRunner: queueRunnerFactory, onStart: suiteStarted, + expectationFactory: expectationFactory, + expectationResultFactory: expectationResultFactory, resultCallback: function(attrs) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + currentlyExecutingSuites.pop(); + } reporter.suiteDone(attrs); } }); runnableLookupTable[suite.id] = suite; return suite; + + function suiteStarted(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + } }; this.describe = function(description, specDefinitions) { var suite = suiteFactory(description); - - var parentSuite = currentSuite; - parentSuite.addChild(suite); - currentSuite = suite; - - var declarationError = null; - try { - specDefinitions.call(suite); - } catch (e) { - declarationError = e; - } - - if (declarationError) { - this.it('encountered a declaration exception', function() { - throw declarationError; - }); - } - - currentSuite = parentSuite; - + addSpecsToSuite(suite, specDefinitions); return suite; }; @@ -255,15 +270,75 @@ getJasmineRequireObj().Env = function(j$) { return suite; }; + var focusedRunnables = []; + + this.fdescribe = function(description, specDefinitions) { + var suite = suiteFactory(description); + suite.isFocused = true; + + focusedRunnables.push(suite.id); + unfocusAncestor(); + addSpecsToSuite(suite, specDefinitions); + + return suite; + }; + + function addSpecsToSuite(suite, specDefinitions) { + var parentSuite = currentDeclarationSuite; + parentSuite.addChild(suite); + currentDeclarationSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + if (declarationError) { + self.it('encountered a declaration exception', function() { + throw declarationError; + }); + } + + currentDeclarationSuite = parentSuite; + } + + function findFocusedAncestor(suite) { + while (suite) { + if (suite.isFocused) { + return suite.id; + } + suite = suite.parentSuite; + } + + return null; + } + + function unfocusAncestor() { + var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); + if (focusedAncestor) { + for (var i = 0; i < focusedRunnables.length; i++) { + if (focusedRunnables[i] === focusedAncestor) { + focusedRunnables.splice(i, 1); + break; + } + } + } + } + + var runnablesExplictlySet = false; + + var runnablesExplictlySetGetter = function(){ + return runnablesExplictlySet; + }; + var specFactory = function(description, fn, suite) { totalSpecsDefined++; - var spec = new j$.Spec({ id: getNextSpecId(), - beforeFns: beforeFns(suite), - afterFns: afterFns(suite), + beforeAndAfterFns: beforeAndAfterFns(suite, runnablesExplictlySetGetter), expectationFactory: expectationFactory, - exceptionFormatter: exceptionFormatter, resultCallback: specResultCallback, getSpecName: function(spec) { return getSpecName(spec, suite); @@ -272,7 +347,8 @@ getJasmineRequireObj().Env = function(j$) { description: description, expectationResultFactory: expectationResultFactory, queueRunnerFactory: queueRunnerFactory, - fn: fn + userContext: function() { return suite.clonedSharedUserContext(); }, + queueableFn: { fn: fn, type: 'it', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } } }); runnableLookupTable[spec.id] = spec; @@ -283,30 +359,22 @@ getJasmineRequireObj().Env = function(j$) { return spec; - function removeAllSpies() { - for (var i = 0; i < spies.length; i++) { - var spyEntry = spies[i]; - spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; - } - spies = []; - } - function specResultCallback(result) { - removeAllSpies(); - j$.Expectation.resetMatchers(); - customEqualityTesters = []; + clearResourcesForRunnable(spec.id); currentSpec = null; reporter.specDone(result); } - }; - var suiteStarted = function(suite) { - reporter.suiteStarted(suite.result); + function specStarted(spec) { + currentSpec = spec; + defaultResourcesForRunnable(spec.id, suite.id); + reporter.specStarted(spec.result); + } }; this.it = function(description, fn) { - var spec = specFactory(description, fn, currentSuite); - currentSuite.addChild(spec); + var spec = specFactory(description, fn, currentDeclarationSuite); + currentDeclarationSuite.addChild(spec); return spec; }; @@ -316,20 +384,36 @@ getJasmineRequireObj().Env = function(j$) { return spec; }; + this.fit = function(description, fn ){ + var spec = this.it(description, fn); + + focusedRunnables.push(spec.id); + unfocusAncestor(); + return spec; + }; + this.expect = function(actual) { - if (!currentSpec) { + if (!currentRunnable()) { throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); } - return currentSpec.expect(actual); + return currentRunnable().expect(actual); }; this.beforeEach = function(beforeEachFunction) { - currentSuite.beforeEach(beforeEachFunction); + currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, type: 'beforeEach', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + }; + + this.beforeAll = function(beforeAllFunction) { + currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, type: 'beforeAll', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.afterEach = function(afterEachFunction) { - currentSuite.afterEach(afterEachFunction); + currentDeclarationSuite.afterEach({ fn: afterEachFunction, type: 'afterEach', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + }; + + this.afterAll = function(afterAllFunction) { + currentDeclarationSuite.afterAll({ fn: afterAllFunction, type: 'afterAll', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.pending = function() { @@ -343,7 +427,7 @@ getJasmineRequireObj().Env = function(j$) { message += error.message || error; } - currentSpec.addExpectationResult(false, { + currentRunnable().addExpectationResult(false, { matcherName: '', passed: false, expected: '', diff --git a/src/core/Expectation.js b/src/core/Expectation.js index 626a5cec..ca0695e4 100644 --- a/src/core/Expectation.js +++ b/src/core/Expectation.js @@ -1,7 +1,5 @@ getJasmineRequireObj().Expectation = function() { - var matchers = {}; - function Expectation(options) { this.util = options.util || { buildFailureMessage: function() {} }; this.customEqualityTesters = options.customEqualityTesters || []; @@ -9,8 +7,9 @@ getJasmineRequireObj().Expectation = function() { this.addExpectationResult = options.addExpectationResult || function(){}; this.isNot = options.isNot; - for (var matcherName in matchers) { - this[matcherName] = matchers[matcherName]; + var customMatchers = options.customMatchers || {}; + for (var matcherName in customMatchers) { + this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); } } @@ -77,19 +76,6 @@ getJasmineRequireObj().Expectation = function() { } }; - Expectation.addMatchers = function(matchersToAdd) { - for (var name in matchersToAdd) { - var matcher = matchersToAdd[name]; - matchers[name] = Expectation.prototype.wrapCompare(name, matcher); - } - }; - - Expectation.resetMatchers = function() { - for (var name in matchers) { - delete matchers[name]; - } - }; - Expectation.Factory = function(options) { options = options || {}; diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index 4a0e66f2..516c9e84 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -11,31 +11,31 @@ getJasmineRequireObj().QueueRunner = function(j$) { } function QueueRunner(attrs) { - this.fns = attrs.fns || []; + this.queueableFns = attrs.queueableFns || []; this.onComplete = attrs.onComplete || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; this.onException = attrs.onException || function() {}; this.catchException = attrs.catchException || function() { return true; }; - this.enforceTimeout = attrs.enforceTimeout || function() { return false; }; - this.userContext = {}; + this.userContext = attrs.userContext || {}; this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; } QueueRunner.prototype.execute = function() { - this.run(this.fns, 0); + this.run(this.queueableFns, 0); }; - QueueRunner.prototype.run = function(fns, recursiveIndex) { - var length = fns.length, - self = this, - iterativeIndex; + QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { + var length = queueableFns.length, + self = this, + iterativeIndex; + for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { - var fn = fns[iterativeIndex]; - if (fn.length > 0) { - return attemptAsync(fn); + var queueableFn = queueableFns[iterativeIndex]; + if (queueableFn.fn.length > 0) { + return attemptAsync(queueableFn); } else { - attemptSync(fn); + attemptSync(queueableFn); } } @@ -45,41 +45,46 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.clearStack(this.onComplete); } - function attemptSync(fn) { + function attemptSync(queueableFn) { try { - fn.call(self.userContext); + queueableFn.fn.call(self.userContext); } catch (e) { - handleException(e); + handleException(e, queueableFn); } } - function attemptAsync(fn) { + function attemptAsync(queueableFn) { var clearTimeout = function () { Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]); }, next = once(function () { clearTimeout(timeoutId); - self.run(fns, iterativeIndex + 1); + self.run(queueableFns, iterativeIndex + 1); }), timeoutId; - if (self.enforceTimeout()) { + if (queueableFn.timeout) { timeoutId = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() { - self.onException(new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.')); + var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); + onException(error, queueableFn); next(); - }, j$.DEFAULT_TIMEOUT_INTERVAL]]); + }, queueableFn.timeout()]]); } try { - fn.call(self.userContext, next); + queueableFn.fn.call(self.userContext, next); } catch (e) { - handleException(e); + handleException(e, queueableFn); next(); } } - function handleException(e) { + function onException(e, queueableFn) { self.onException(e); + } + + function handleException(e, queueableFn) { + onException(e, queueableFn); if (!self.catchException(e)) { //TODO: set a var when we catch an exception and //use a finally block to close the loop in a nice way.. diff --git a/src/core/Spec.js b/src/core/Spec.js index c85e67d1..056c4703 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -4,17 +4,16 @@ getJasmineRequireObj().Spec = function(j$) { this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.description = attrs.description || ''; - this.fn = attrs.fn; - this.beforeFns = attrs.beforeFns || function() { return []; }; - this.afterFns = attrs.afterFns || function() { return []; }; + this.queueableFn = attrs.queueableFn; + this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; + this.userContext = attrs.userContext || function() { return {}; }; this.onStart = attrs.onStart || function() {}; - this.exceptionFormatter = attrs.exceptionFormatter || function() {}; this.getSpecName = attrs.getSpecName || function() { return ''; }; this.expectationResultFactory = attrs.expectationResultFactory || function() { }; this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; - if (!this.fn) { + if (!this.queueableFn.fn) { this.pend(); } @@ -50,30 +49,16 @@ getJasmineRequireObj().Spec = function(j$) { return; } - var allFns = this.beforeFns().concat(this.fn).concat(this.afterFns()); + var fns = this.beforeAndAfterFns(); + var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); this.queueRunnerFactory({ - fns: allFns, - onException: onException, + queueableFns: allFns, + onException: function() { self.onException.apply(self, arguments); }, onComplete: complete, - enforceTimeout: function() { return true; } + userContext: this.userContext() }); - function onException(e) { - if (Spec.isPendingSpecException(e)) { - self.pend(); - return; - } - - self.addExpectationResult(false, { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: e - }); - } - function complete() { self.result.status = self.status(); self.resultCallback(self.result); @@ -84,6 +69,21 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.onException = function onException(e) { + if (Spec.isPendingSpecException(e)) { + this.pend(); + return; + } + + this.addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: e + }); + }; + Spec.prototype.disable = function() { this.disabled = true; }; @@ -108,6 +108,10 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.isExecutable = function() { + return !this.disabled && !this.markedPending; + }; + Spec.prototype.getFullName = function() { return this.getSpecName(this); }; diff --git a/src/core/SpyRegistry.js b/src/core/SpyRegistry.js new file mode 100644 index 00000000..776393ed --- /dev/null +++ b/src/core/SpyRegistry.js @@ -0,0 +1,45 @@ +getJasmineRequireObj().SpyRegistry = function(j$) { + + function SpyRegistry(options) { + options = options || {}; + var currentSpies = options.currentSpies || function() { return []; }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); + } + + if (j$.util.isUndefined(obj[methodName])) { + throw new Error(methodName + '() method does not exist'); + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw new Error(methodName + ' has already been spied upon'); + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + currentSpies().push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; + + this.clearSpies = function() { + var spies = currentSpies(); + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + }; + } + + return SpyRegistry; +}; diff --git a/src/core/Suite.js b/src/core/Suite.js index 4d5e3935..4b2642a2 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -7,9 +7,13 @@ getJasmineRequireObj().Suite = function() { this.onStart = attrs.onStart || function() {}; this.resultCallback = attrs.resultCallback || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; + this.expectationFactory = attrs.expectationFactory; + this.expectationResultFactory = attrs.expectationResultFactory; this.beforeFns = []; this.afterFns = []; + this.beforeAllFns = []; + this.afterAllFns = []; this.queueRunner = attrs.queueRunner || function() {}; this.disabled = false; @@ -17,12 +21,16 @@ getJasmineRequireObj().Suite = function() { this.result = { id: this.id, - status: this.disabled ? 'disabled' : '', description: this.description, - fullName: this.getFullName() + fullName: this.getFullName(), + failedExpectations: [] }; } + Suite.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + Suite.prototype.getFullName = function() { var fullName = this.description; for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { @@ -42,10 +50,18 @@ getJasmineRequireObj().Suite = function() { this.beforeFns.unshift(fn); }; + Suite.prototype.beforeAll = function(fn) { + this.beforeAllFns.push(fn); + }; + Suite.prototype.afterEach = function(fn) { this.afterFns.unshift(fn); }; + Suite.prototype.afterAll = function(fn) { + this.afterAllFns.push(fn); + }; + Suite.prototype.addChild = function(child) { this.children.push(child); }; @@ -62,16 +78,25 @@ getJasmineRequireObj().Suite = function() { var allFns = []; - for (var i = 0; i < this.children.length; i++) { - allFns.push(wrapChildAsAsync(this.children[i])); + if (this.isExecutable()) { + allFns = allFns.concat(this.beforeAllFns); + + for (var i = 0; i < this.children.length; i++) { + allFns.push(wrapChildAsAsync(this.children[i])); + } + + allFns = allFns.concat(this.afterAllFns); } this.queueRunner({ - fns: allFns, - onComplete: complete + queueableFns: allFns, + onComplete: complete, + userContext: this.sharedUserContext(), + onException: function() { self.onException.apply(self, arguments); } }); function complete() { + self.result.status = self.disabled ? 'disabled' : 'finished'; self.resultCallback(self.result); if (onComplete) { @@ -80,10 +105,82 @@ getJasmineRequireObj().Suite = function() { } function wrapChildAsAsync(child) { - return function(done) { child.execute(done); }; + return { fn: function(done) { child.execute(done); } }; } }; + Suite.prototype.isExecutable = function() { + var foundActive = false; + for(var i = 0; i < this.children.length; i++) { + if(this.children[i].isExecutable()) { + foundActive = true; + break; + } + } + return foundActive; + }; + + Suite.prototype.sharedUserContext = function() { + if (!this.sharedContext) { + this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; + } + + return this.sharedContext; + }; + + Suite.prototype.clonedSharedUserContext = function() { + return clone(this.sharedUserContext()); + }; + + Suite.prototype.onException = function() { + if(isAfterAll(this.children)) { + var data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0] + }; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.onException.apply(child, arguments); + } + } + }; + + Suite.prototype.addExpectationResult = function () { + if(isAfterAll(this.children) && isFailure(arguments)){ + var data = arguments[1]; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.addExpectationResult.apply(child, arguments); + } + } + }; + + function isAfterAll(children) { + return children && children[0].result.status; + } + + function isFailure(args) { + return !args[0]; + } + + function clone(obj) { + var clonedObj = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + clonedObj[prop] = obj[prop]; + } + } + + return clonedObj; + } + return Suite; }; diff --git a/src/core/requireCore.js b/src/core/requireCore.js index 64f831e0..7759bdc0 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -33,6 +33,7 @@ getJasmineRequireObj = (function (jasmineGlobal) { j$.QueueRunner = jRequire.QueueRunner(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(); j$.Spec = jRequire.Spec(j$); + j$.SpyRegistry = jRequire.SpyRegistry(j$); j$.SpyStrategy = jRequire.SpyStrategy(); j$.Suite = jRequire.Suite(); j$.Timer = jRequire.Timer(); diff --git a/src/core/requireInterface.js b/src/core/requireInterface.js index 69c06cf2..1d1fd466 100644 --- a/src/core/requireInterface.js +++ b/src/core/requireInterface.js @@ -24,6 +24,14 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.afterEach(afterEachFunction); }, + beforeAll: function(beforeAllFunction) { + return env.beforeAll(beforeAllFunction); + }, + + afterAll: function(afterAllFunction) { + return env.afterAll(afterAllFunction); + }, + expect: function(actual) { return env.expect(actual); }, diff --git a/src/core/util.js b/src/core/util.js index 141891aa..11d19347 100644 --- a/src/core/util.js +++ b/src/core/util.js @@ -40,5 +40,20 @@ getJasmineRequireObj().util = function() { return false; }; + util.clone = function(obj) { + if (Object.prototype.toString.apply(obj) === '[object Array]') { + return obj.slice(); + } + + var cloned = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + cloned[prop] = obj[prop]; + } + } + + return cloned; + }; + return util; }; diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index 3322a1d7..a5ff9960 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -17,7 +17,8 @@ jasmineRequire.HtmlReporter = function(j$) { failureCount = 0, pendingSpecCount = 0, htmlReporterMain, - symbols; + symbols, + failedSuites = []; this.initialize = function() { clearPrior(); @@ -54,6 +55,10 @@ jasmineRequire.HtmlReporter = function(j$) { }; this.suiteDone = function(result) { + if (result.failedExpectations && result.failedExpectations.length > 0) { + failedSuites.push(result); + } + if (currentParent == topResults) { return; } @@ -149,6 +154,15 @@ jasmineRequire.HtmlReporter = function(j$) { alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); + for(i = 0; i < failedSuites.length; i++) { + var failedSuite = failedSuites[i]; + for(var j = 0; j < failedSuite.failedExpectations.length; j++) { + var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message; + var errorBarClassName = 'bar errored'; + alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage)); + } + } + var results = find('.results'); results.appendChild(summary); diff --git a/src/html/_HTMLReporter.scss b/src/html/_HTMLReporter.scss index a481d7df..614addb5 100644 --- a/src/html/_HTMLReporter.scss +++ b/src/html/_HTMLReporter.scss @@ -194,6 +194,10 @@ body { background-color: $neutral-color; } + &.errored { + background-color: $failing-color; + } + &.menu { background-color: #fff; color: $faint-text-color;