diff --git a/lib/jasmine-core/boot0.js b/lib/jasmine-core/boot0.js index c773ba8e..b053af10 100644 --- a/lib/jasmine-core/boot0.js +++ b/lib/jasmine-core/boot0.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2022 Pivotal Labs +Copyright (c) 2008-2023 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/lib/jasmine-core/boot1.js b/lib/jasmine-core/boot1.js index 5fe49e41..b06f7d8c 100644 --- a/lib/jasmine-core/boot1.js +++ b/lib/jasmine-core/boot1.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2022 Pivotal Labs +Copyright (c) 2008-2023 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 2ebc6d0e..47035410 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2022 Pivotal Labs +Copyright (c) 2008-2023 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 9c4df766..ecbeda78 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -741,6 +741,8 @@ getJasmineRequireObj().Spec = function(j$) { this.asyncExpectationFactory = attrs.asyncExpectationFactory; this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; + this.filename = attrs.filename; + this.parentSuiteId = attrs.parentSuiteId; this.description = attrs.description || ''; this.queueableFn = attrs.queueableFn; this.beforeAndAfterFns = @@ -774,35 +776,7 @@ getJasmineRequireObj().Spec = function(j$) { this.exclude(); } - /** - * @typedef SpecResult - * @property {String} id - The unique id of this spec. - * @property {String} description - The description passed to the {@link it} that created this spec. - * @property {String} fullName - The full description including all ancestors of this spec. - * @property {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec. - * @property {Expectation[]} passedExpectations - The list of expectations that passed during execution of this spec. - * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec. - * @property {String} pendingReason - If the spec is {@link pending}, this will be the reason. - * @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec. - * @property {number} duration - The time in ms used by the spec execution, including any before/afterEach. - * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty} - * @property {DebugLogEntry[]|null} debugLogs - Messages, if any, that were logged using {@link jasmine.debugLog} during a failing spec. - * @since 2.0.0 - */ - this.result = { - id: this.id, - description: this.description, - fullName: this.getFullName(), - failedExpectations: [], - passedExpectations: [], - deprecationWarnings: [], - pendingReason: '', - duration: null, - properties: null, - debugLogs: null - }; - - this.reportedDone = false; + this.reset(); } Spec.prototype.addExpectationResult = function(passed, data, isError) { @@ -912,14 +886,33 @@ getJasmineRequireObj().Spec = function(j$) { }; Spec.prototype.reset = function() { + /** + * @typedef SpecResult + * @property {String} id - The unique id of this spec. + * @property {String} description - The description passed to the {@link it} that created this spec. + * @property {String} fullName - The full description including all ancestors of this spec. + * @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe(). + * @property {String} filename - The name of the file the spec was defined in. + * @property {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec. + * @property {Expectation[]} passedExpectations - The list of expectations that passed during execution of this spec. + * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec. + * @property {String} pendingReason - If the spec is {@link pending}, this will be the reason. + * @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec. + * @property {number} duration - The time in ms used by the spec execution, including any before/afterEach. + * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty} + * @property {DebugLogEntry[]|null} debugLogs - Messages, if any, that were logged using {@link jasmine.debugLog} during a failing spec. + * @since 2.0.0 + */ this.result = { id: this.id, description: this.description, fullName: this.getFullName(), + parentSuiteId: this.parentSuiteId, + filename: this.filename, failedExpectations: [], passedExpectations: [], deprecationWarnings: [], - pendingReason: this.excludeMessage, + pendingReason: this.excludeMessage || '', duration: null, properties: null, debugLogs: null @@ -1858,18 +1851,24 @@ getJasmineRequireObj().Env = function(j$) { this.describe = function(description, definitionFn) { ensureIsNotNested('describe'); - return suiteBuilder.describe(description, definitionFn).metadata; + const filename = callerCallerFilename(); + return suiteBuilder.describe(description, definitionFn, filename) + .metadata; }; this.xdescribe = function(description, definitionFn) { ensureIsNotNested('xdescribe'); - return suiteBuilder.xdescribe(description, definitionFn).metadata; + const filename = callerCallerFilename(); + return suiteBuilder.xdescribe(description, definitionFn, filename) + .metadata; }; this.fdescribe = function(description, definitionFn) { ensureIsNotNested('fdescribe'); ensureNonParallel('fdescribe'); - return suiteBuilder.fdescribe(description, definitionFn).metadata; + const filename = callerCallerFilename(); + return suiteBuilder.fdescribe(description, definitionFn, filename) + .metadata; }; function specResultCallback(spec, result, next) { @@ -1896,18 +1895,21 @@ getJasmineRequireObj().Env = function(j$) { this.it = function(description, fn, timeout) { ensureIsNotNested('it'); - return suiteBuilder.it(description, fn, timeout).metadata; + const filename = callerCallerFilename(); + return suiteBuilder.it(description, fn, timeout, filename).metadata; }; this.xit = function(description, fn, timeout) { ensureIsNotNested('xit'); - return suiteBuilder.xit(description, fn, timeout).metadata; + const filename = callerCallerFilename(); + return suiteBuilder.xit(description, fn, timeout, filename).metadata; }; this.fit = function(description, fn, timeout) { ensureIsNotNested('fit'); ensureNonParallel('fit'); - return suiteBuilder.fit(description, fn, timeout).metadata; + const filename = callerCallerFilename(); + return suiteBuilder.fit(description, fn, timeout, filename).metadata; }; /** @@ -2063,6 +2065,10 @@ getJasmineRequireObj().Env = function(j$) { }; } + function callerCallerFilename() { + return new j$.StackTrace(new Error()).frames[3].file; + } + return Env; }; @@ -9627,6 +9633,8 @@ getJasmineRequireObj().Suite = function(j$) { this.id = attrs.id; this.parentSuite = attrs.parentSuite; this.description = attrs.description; + this.reportedParentSuiteId = attrs.reportedParentSuiteId; + this.filename = attrs.filename; this.expectationFactory = attrs.expectationFactory; this.asyncExpectationFactory = attrs.asyncExpectationFactory; this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; @@ -9732,6 +9740,8 @@ getJasmineRequireObj().Suite = function(j$) { * @property {String} id - The unique id of this suite. * @property {String} description - The description text passed to the {@link describe} that made this suite. * @property {String} fullName - The full description including all ancestors of this suite. + * @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe(). + * @property {String} filename - The name of the file the suite was defined in. * @property {Expectation[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite. * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite. * @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite. @@ -9743,6 +9753,8 @@ getJasmineRequireObj().Suite = function(j$) { id: this.id, description: this.description, fullName: this.getFullName(), + parentSuiteId: this.reportedParentSuiteId, + filename: this.filename, failedExpectations: [], deprecationWarnings: [], duration: null, @@ -9985,9 +9997,9 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { this.focusedRunables = []; } - describe(description, definitionFn) { + describe(description, definitionFn, filename) { ensureIsFunction(definitionFn, 'describe'); - const suite = this.suiteFactory_(description); + const suite = this.suiteFactory_(description, filename); if (definitionFn.length > 0) { throw new Error('describe does not expect any arguments'); } @@ -9998,9 +10010,9 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { return suite; } - fdescribe(description, definitionFn) { + fdescribe(description, definitionFn, filename) { ensureIsFunction(definitionFn, 'fdescribe'); - const suite = this.suiteFactory_(description); + const suite = this.suiteFactory_(description, filename); suite.isFocused = true; this.focusedRunables.push(suite.id); @@ -10010,37 +10022,37 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { return suite; } - xdescribe(description, definitionFn) { + xdescribe(description, definitionFn, filename) { ensureIsFunction(definitionFn, 'xdescribe'); - const suite = this.suiteFactory_(description); + const suite = this.suiteFactory_(description, filename); suite.exclude(); this.addSpecsToSuite_(suite, definitionFn); return suite; } - it(description, fn, timeout) { + it(description, fn, timeout, filename) { // it() sometimes doesn't have a fn argument, so only check the type if // it's given. if (arguments.length > 1 && typeof fn !== 'undefined') { ensureIsFunctionOrAsync(fn, 'it'); } - return this.it_(description, fn, timeout); + return this.it_(description, fn, timeout, filename); } - xit(description, fn, timeout) { + xit(description, fn, timeout, filename) { // xit(), like it(), doesn't always have a fn argument, so only check the // type when needed. if (arguments.length > 1 && typeof fn !== 'undefined') { ensureIsFunctionOrAsync(fn, 'xit'); } - const spec = this.it_(description, fn, timeout); + const spec = this.it_(description, fn, timeout, filename); spec.exclude('Temporarily disabled with xit'); return spec; } - fit(description, fn, timeout) { + fit(description, fn, timeout, filename) { // Unlike it and xit, the function is required because it doesn't make // sense to focus on nothing. ensureIsFunctionOrAsync(fn, 'fit'); @@ -10048,7 +10060,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { if (timeout) { j$.util.validateTimeout(timeout); } - const spec = this.specFactory_(description, fn, timeout); + const spec = this.specFactory_(description, fn, timeout, filename); this.currentDeclarationSuite_.addChild(spec); this.focusedRunables.push(spec.id); this.unfocusAncestor_(); @@ -10108,12 +10120,12 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { }); } - it_(description, fn, timeout) { + it_(description, fn, timeout, filename) { if (timeout) { j$.util.validateTimeout(timeout); } - const spec = this.specFactory_(description, fn, timeout); + const spec = this.specFactory_(description, fn, timeout, filename); if (this.currentDeclarationSuite_.markedExcluding) { spec.exclude(); } @@ -10122,12 +10134,17 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { return spec; } - suiteFactory_(description) { + suiteFactory_(description, filename) { const config = this.env_.configuration(); + const parentSuite = this.currentDeclarationSuite_; + const reportedParentSuiteId = + parentSuite === this.topSuite ? null : parentSuite.id; return new j$.Suite({ id: 'suite' + this.nextSuiteId_++, description, - parentSuite: this.currentDeclarationSuite_, + filename, + parentSuite, + reportedParentSuiteId, timer: new j$.Timer(), expectationFactory: this.expectationFactory_, asyncExpectationFactory: this.suiteAsyncExpectationFactory_, @@ -10159,12 +10176,15 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { this.currentDeclarationSuite_ = parentSuite; } - specFactory_(description, fn, timeout) { + specFactory_(description, fn, timeout, filename) { this.totalSpecsDefined++; const config = this.env_.configuration(); const suite = this.currentDeclarationSuite_; + const parentSuiteId = suite === this.topSuite ? null : suite.id; const spec = new j$.Spec({ id: 'spec' + this.nextSpecId_++, + filename, + parentSuiteId, beforeAndAfterFns: beforeAndAfterFns(suite), expectationFactory: this.expectationFactory_, asyncExpectationFactory: this.specAsyncExpectationFactory_, diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index 33bea8b3..599760ed 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -195,6 +195,8 @@ describe('Spec', function() { onStart: startCallback, resultCallback: resultCallback, description: 'with a spec', + parentSuiteId: 'suite1', + filename: 'someSpecFile.js', getSpecName: function() { return 'a suite with a spec'; }, @@ -219,6 +221,8 @@ describe('Spec', function() { status: 'pending', description: 'with a spec', fullName: 'a suite with a spec', + parentSuiteId: 'suite1', + filename: 'someSpecFile.js', failedExpectations: [], passedExpectations: [], deprecationWarnings: [], diff --git a/spec/core/SuiteBuilderSpec.js b/spec/core/SuiteBuilderSpec.js index e664a1da..d625d29b 100644 --- a/spec/core/SuiteBuilderSpec.js +++ b/spec/core/SuiteBuilderSpec.js @@ -193,7 +193,9 @@ describe('SuiteBuilder', function() { failedExpectations: [], deprecationWarnings: [], duration: null, - properties: null + properties: null, + parentSuiteId: null, + filename: undefined, }); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index e96e8326..f7a90cf5 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1944,11 +1944,17 @@ describe('Env integration', function() { 'specStarted', 'specDone' ]); + const suiteFullNameToId = {}; + reporter.suiteStarted.and.callFake(function(e) { + suiteFullNameToId[e.fullName] = e.id; + }); env.addReporter(reporter); + env.it('a top level spec', function() {}); + env.describe('A Suite', function() { - env.it('with a top level spec', function() { + env.it('with a spec', function() { env.expect(true).toBe(true); }); env.describe('with a nested suite', function() { @@ -1971,38 +1977,110 @@ describe('Env integration', function() { await env.execute(); expect(reporter.jasmineStarted).toHaveBeenCalledWith({ - totalSpecsDefined: 5, + totalSpecsDefined: 6, order: jasmine.any(jasmineUnderTest.Order), parallel: false }); - expect(reporter.specDone.calls.count()).toBe(5); + expect(reporter.specStarted.calls.count()).toBe(6); + expect(reporter.specDone.calls.count()).toBe(6); - expect(reporter.specDone).toHaveBeenCalledWith( + expect(reporter.specStarted).toHaveBeenCalledWith( jasmine.objectContaining({ - description: 'with a top level spec', - status: 'passed' + description: 'a top level spec', + parentSuiteId: null }) ); - expect(reporter.specDone).toHaveBeenCalledWith( jasmine.objectContaining({ - description: "with an x'ed spec", - status: 'pending' + description: 'a top level spec', + status: 'passed', + parentSuiteId: null + }) + ); + expect(reporter.specStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'with a spec', + parentSuiteId: suiteFullNameToId['A Suite'] }) ); - expect(reporter.specDone).toHaveBeenCalledWith( jasmine.objectContaining({ description: 'with a spec', - status: 'failed' + status: 'passed', + parentSuiteId: suiteFullNameToId['A Suite'] }) ); + expect(reporter.specStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: "with an x'ed spec", + parentSuiteId: suiteFullNameToId['A Suite with a nested suite'] + }) + ); + expect(reporter.specDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: "with an x'ed spec", + status: 'pending', + parentSuiteId: suiteFullNameToId['A Suite with a nested suite'] + }) + ); + + expect(reporter.specStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'with a spec', + parentSuiteId: suiteFullNameToId['A Suite with a nested suite'] + }) + ); + expect(reporter.specDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'with a spec', + status: 'failed', + parentSuiteId: suiteFullNameToId['A Suite with a nested suite'] + }) + ); + + expect(reporter.specStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'is pending', + parentSuiteId: + suiteFullNameToId['A Suite with only non-executable specs'] + }) + ); expect(reporter.specDone).toHaveBeenCalledWith( jasmine.objectContaining({ description: 'is pending', - status: 'pending' + status: 'pending', + parentSuiteId: + suiteFullNameToId['A Suite with only non-executable specs'] + }) + ); + + expect(reporter.suiteStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'A Suite', + parentSuiteId: null + }) + ); + expect(reporter.suiteDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'A Suite', + status: 'passed', + parentSuiteId: null + }) + ); + + expect(reporter.suiteStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'with a nested suite', + parentSuiteId: suiteFullNameToId['A Suite'] + }) + ); + expect(reporter.suiteDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'with a nested suite', + status: 'passed', + parentSuiteId: suiteFullNameToId['A Suite'] }) ); @@ -2013,6 +2091,89 @@ describe('Env integration', function() { expect(suiteResult.description).toEqual('A Suite'); }); + it('reports focused specs and suites as expected', async function() { + const reporter = jasmine.createSpyObj('fakeReporter', [ + 'suiteStarted', + 'suiteDone', + 'specStarted', + 'specDone' + ]); + const suiteFullNameToId = {}; + reporter.suiteStarted.and.callFake(function(e) { + suiteFullNameToId[e.fullName] = e.id; + }); + + env.fit('a focused top level spec', function() {}); + + env.describe('a suite', function() { + env.fdescribe('a focused suite', function() { + env.fit('a focused spec', function() {}); + }); + }); + + env.addReporter(reporter); + await env.execute(); + + expect(reporter.specStarted).toHaveBeenCalledTimes(2); + expect(reporter.specDone).toHaveBeenCalledTimes(2); + + expect(reporter.specStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a focused top level spec', + parentSuiteId: null + }) + ); + expect(reporter.specDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a focused top level spec', + status: 'passed', + parentSuiteId: null + }) + ); + + expect(reporter.specStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a focused spec', + parentSuiteId: suiteFullNameToId['a suite a focused suite'] + }) + ); + expect(reporter.specDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a focused spec', + status: 'passed', + parentSuiteId: suiteFullNameToId['a suite a focused suite'] + }) + ); + + expect(reporter.suiteStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a suite', + parentSuiteId: null + }) + ); + expect(reporter.suiteDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a suite', + status: 'passed', + parentSuiteId: null + }) + ); + + expect(reporter.suiteStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a focused suite', + parentSuiteId: suiteFullNameToId['a suite'] + }) + ); + expect(reporter.suiteDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a focused suite', + status: 'passed', + parentSuiteId: suiteFullNameToId['a suite'] + }) + ); + }); + it('should report the random seed at the beginning and end of execution', async function() { const reporter = jasmine.createSpyObj('fakeReporter', [ 'jasmineStarted', @@ -4080,6 +4241,99 @@ describe('Env integration', function() { ); }); + it('reports suite and spec filenames', async function() { + const methods = ['suiteStarted', 'suiteDone', 'specStarted', 'specDone']; + const reporter = jasmine.createSpyObj('reporter', methods); + env.addReporter(reporter); + + // Simulate calling through global it and describe, + // which add another stack frame vs calling env methods directly + function describeShim(name, fn) { + env.describe(name, fn); + } + function itShim(name, fn) { + env.it(name, fn); + } + + describeShim('a suite', function() { + itShim('a spec', function() {}); + }); + + await env.execute(); + + for (const method of methods) { + expect(reporter[method]) + .withContext(method) + .toHaveBeenCalledWith( + jasmine.objectContaining({ + filename: jasmine.stringMatching(/EnvSpec\.js$/) + }) + ); + } + }); + + it('reports skipped suite and spec filenames', async function() { + const methods = ['suiteStarted', 'suiteDone', 'specStarted', 'specDone']; + const reporter = jasmine.createSpyObj('reporter', methods); + env.addReporter(reporter); + + // Simulate calling through global it and describe, + // which add another stack frame vs calling env methods directly + function xdescribeShim(name, fn) { + env.xdescribe(name, fn); + } + function xitShim(name, fn) { + env.xit(name, fn); + } + + xdescribeShim('a suite', function() { + xitShim('a spec', function() {}); + }); + + await env.execute(); + + for (const method of methods) { + expect(reporter[method]) + .withContext(method) + .toHaveBeenCalledWith( + jasmine.objectContaining({ + filename: jasmine.stringMatching(/EnvSpec\.js$/) + }) + ); + } + }); + + it('reports focused suite and spec filenames', async function() { + const methods = ['suiteStarted', 'suiteDone', 'specStarted', 'specDone']; + const reporter = jasmine.createSpyObj('reporter', methods); + env.addReporter(reporter); + + // Simulate calling through global it and describe, + // which add another stack frame vs calling env methods directly + function fdescribeShim(name, fn) { + env.fdescribe(name, fn); + } + function fitShim(name, fn) { + env.fit(name, fn); + } + + fdescribeShim('a suite', function() { + fitShim('a spec', function() {}); + }); + + await env.execute(); + + for (const method of methods) { + expect(reporter[method]) + .withContext(method) + .toHaveBeenCalledWith( + jasmine.objectContaining({ + filename: jasmine.stringMatching(/EnvSpec\.js$/) + }) + ); + } + }); + function browserEventMethods() { return { listeners_: { error: [], unhandledrejection: [] }, diff --git a/src/core/Env.js b/src/core/Env.js index 9a08c9f1..b7ed3e85 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -716,18 +716,24 @@ getJasmineRequireObj().Env = function(j$) { this.describe = function(description, definitionFn) { ensureIsNotNested('describe'); - return suiteBuilder.describe(description, definitionFn).metadata; + const filename = callerCallerFilename(); + return suiteBuilder.describe(description, definitionFn, filename) + .metadata; }; this.xdescribe = function(description, definitionFn) { ensureIsNotNested('xdescribe'); - return suiteBuilder.xdescribe(description, definitionFn).metadata; + const filename = callerCallerFilename(); + return suiteBuilder.xdescribe(description, definitionFn, filename) + .metadata; }; this.fdescribe = function(description, definitionFn) { ensureIsNotNested('fdescribe'); ensureNonParallel('fdescribe'); - return suiteBuilder.fdescribe(description, definitionFn).metadata; + const filename = callerCallerFilename(); + return suiteBuilder.fdescribe(description, definitionFn, filename) + .metadata; }; function specResultCallback(spec, result, next) { @@ -754,18 +760,21 @@ getJasmineRequireObj().Env = function(j$) { this.it = function(description, fn, timeout) { ensureIsNotNested('it'); - return suiteBuilder.it(description, fn, timeout).metadata; + const filename = callerCallerFilename(); + return suiteBuilder.it(description, fn, timeout, filename).metadata; }; this.xit = function(description, fn, timeout) { ensureIsNotNested('xit'); - return suiteBuilder.xit(description, fn, timeout).metadata; + const filename = callerCallerFilename(); + return suiteBuilder.xit(description, fn, timeout, filename).metadata; }; this.fit = function(description, fn, timeout) { ensureIsNotNested('fit'); ensureNonParallel('fit'); - return suiteBuilder.fit(description, fn, timeout).metadata; + const filename = callerCallerFilename(); + return suiteBuilder.fit(description, fn, timeout, filename).metadata; }; /** @@ -921,5 +930,9 @@ getJasmineRequireObj().Env = function(j$) { }; } + function callerCallerFilename() { + return new j$.StackTrace(new Error()).frames[3].file; + } + return Env; }; diff --git a/src/core/Spec.js b/src/core/Spec.js index f4149622..52a24a5d 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -4,6 +4,8 @@ getJasmineRequireObj().Spec = function(j$) { this.asyncExpectationFactory = attrs.asyncExpectationFactory; this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; + this.filename = attrs.filename; + this.parentSuiteId = attrs.parentSuiteId; this.description = attrs.description || ''; this.queueableFn = attrs.queueableFn; this.beforeAndAfterFns = @@ -37,35 +39,7 @@ getJasmineRequireObj().Spec = function(j$) { this.exclude(); } - /** - * @typedef SpecResult - * @property {String} id - The unique id of this spec. - * @property {String} description - The description passed to the {@link it} that created this spec. - * @property {String} fullName - The full description including all ancestors of this spec. - * @property {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec. - * @property {Expectation[]} passedExpectations - The list of expectations that passed during execution of this spec. - * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec. - * @property {String} pendingReason - If the spec is {@link pending}, this will be the reason. - * @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec. - * @property {number} duration - The time in ms used by the spec execution, including any before/afterEach. - * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty} - * @property {DebugLogEntry[]|null} debugLogs - Messages, if any, that were logged using {@link jasmine.debugLog} during a failing spec. - * @since 2.0.0 - */ - this.result = { - id: this.id, - description: this.description, - fullName: this.getFullName(), - failedExpectations: [], - passedExpectations: [], - deprecationWarnings: [], - pendingReason: '', - duration: null, - properties: null, - debugLogs: null - }; - - this.reportedDone = false; + this.reset(); } Spec.prototype.addExpectationResult = function(passed, data, isError) { @@ -175,14 +149,33 @@ getJasmineRequireObj().Spec = function(j$) { }; Spec.prototype.reset = function() { + /** + * @typedef SpecResult + * @property {String} id - The unique id of this spec. + * @property {String} description - The description passed to the {@link it} that created this spec. + * @property {String} fullName - The full description including all ancestors of this spec. + * @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe(). + * @property {String} filename - The name of the file the spec was defined in. + * @property {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec. + * @property {Expectation[]} passedExpectations - The list of expectations that passed during execution of this spec. + * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec. + * @property {String} pendingReason - If the spec is {@link pending}, this will be the reason. + * @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec. + * @property {number} duration - The time in ms used by the spec execution, including any before/afterEach. + * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty} + * @property {DebugLogEntry[]|null} debugLogs - Messages, if any, that were logged using {@link jasmine.debugLog} during a failing spec. + * @since 2.0.0 + */ this.result = { id: this.id, description: this.description, fullName: this.getFullName(), + parentSuiteId: this.parentSuiteId, + filename: this.filename, failedExpectations: [], passedExpectations: [], deprecationWarnings: [], - pendingReason: this.excludeMessage, + pendingReason: this.excludeMessage || '', duration: null, properties: null, debugLogs: null diff --git a/src/core/Suite.js b/src/core/Suite.js index 4b64fe82..553c176f 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -4,6 +4,8 @@ getJasmineRequireObj().Suite = function(j$) { this.id = attrs.id; this.parentSuite = attrs.parentSuite; this.description = attrs.description; + this.reportedParentSuiteId = attrs.reportedParentSuiteId; + this.filename = attrs.filename; this.expectationFactory = attrs.expectationFactory; this.asyncExpectationFactory = attrs.asyncExpectationFactory; this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; @@ -109,6 +111,8 @@ getJasmineRequireObj().Suite = function(j$) { * @property {String} id - The unique id of this suite. * @property {String} description - The description text passed to the {@link describe} that made this suite. * @property {String} fullName - The full description including all ancestors of this suite. + * @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe(). + * @property {String} filename - The name of the file the suite was defined in. * @property {Expectation[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite. * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite. * @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite. @@ -120,6 +124,8 @@ getJasmineRequireObj().Suite = function(j$) { id: this.id, description: this.description, fullName: this.getFullName(), + parentSuiteId: this.reportedParentSuiteId, + filename: this.filename, failedExpectations: [], deprecationWarnings: [], duration: null, diff --git a/src/core/SuiteBuilder.js b/src/core/SuiteBuilder.js index 4ed6f8ce..24fea487 100644 --- a/src/core/SuiteBuilder.js +++ b/src/core/SuiteBuilder.js @@ -33,9 +33,9 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { this.focusedRunables = []; } - describe(description, definitionFn) { + describe(description, definitionFn, filename) { ensureIsFunction(definitionFn, 'describe'); - const suite = this.suiteFactory_(description); + const suite = this.suiteFactory_(description, filename); if (definitionFn.length > 0) { throw new Error('describe does not expect any arguments'); } @@ -46,9 +46,9 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { return suite; } - fdescribe(description, definitionFn) { + fdescribe(description, definitionFn, filename) { ensureIsFunction(definitionFn, 'fdescribe'); - const suite = this.suiteFactory_(description); + const suite = this.suiteFactory_(description, filename); suite.isFocused = true; this.focusedRunables.push(suite.id); @@ -58,37 +58,37 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { return suite; } - xdescribe(description, definitionFn) { + xdescribe(description, definitionFn, filename) { ensureIsFunction(definitionFn, 'xdescribe'); - const suite = this.suiteFactory_(description); + const suite = this.suiteFactory_(description, filename); suite.exclude(); this.addSpecsToSuite_(suite, definitionFn); return suite; } - it(description, fn, timeout) { + it(description, fn, timeout, filename) { // it() sometimes doesn't have a fn argument, so only check the type if // it's given. if (arguments.length > 1 && typeof fn !== 'undefined') { ensureIsFunctionOrAsync(fn, 'it'); } - return this.it_(description, fn, timeout); + return this.it_(description, fn, timeout, filename); } - xit(description, fn, timeout) { + xit(description, fn, timeout, filename) { // xit(), like it(), doesn't always have a fn argument, so only check the // type when needed. if (arguments.length > 1 && typeof fn !== 'undefined') { ensureIsFunctionOrAsync(fn, 'xit'); } - const spec = this.it_(description, fn, timeout); + const spec = this.it_(description, fn, timeout, filename); spec.exclude('Temporarily disabled with xit'); return spec; } - fit(description, fn, timeout) { + fit(description, fn, timeout, filename) { // Unlike it and xit, the function is required because it doesn't make // sense to focus on nothing. ensureIsFunctionOrAsync(fn, 'fit'); @@ -96,7 +96,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { if (timeout) { j$.util.validateTimeout(timeout); } - const spec = this.specFactory_(description, fn, timeout); + const spec = this.specFactory_(description, fn, timeout, filename); this.currentDeclarationSuite_.addChild(spec); this.focusedRunables.push(spec.id); this.unfocusAncestor_(); @@ -156,12 +156,12 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { }); } - it_(description, fn, timeout) { + it_(description, fn, timeout, filename) { if (timeout) { j$.util.validateTimeout(timeout); } - const spec = this.specFactory_(description, fn, timeout); + const spec = this.specFactory_(description, fn, timeout, filename); if (this.currentDeclarationSuite_.markedExcluding) { spec.exclude(); } @@ -170,12 +170,17 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { return spec; } - suiteFactory_(description) { + suiteFactory_(description, filename) { const config = this.env_.configuration(); + const parentSuite = this.currentDeclarationSuite_; + const reportedParentSuiteId = + parentSuite === this.topSuite ? null : parentSuite.id; return new j$.Suite({ id: 'suite' + this.nextSuiteId_++, description, - parentSuite: this.currentDeclarationSuite_, + filename, + parentSuite, + reportedParentSuiteId, timer: new j$.Timer(), expectationFactory: this.expectationFactory_, asyncExpectationFactory: this.suiteAsyncExpectationFactory_, @@ -207,12 +212,15 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { this.currentDeclarationSuite_ = parentSuite; } - specFactory_(description, fn, timeout) { + specFactory_(description, fn, timeout, filename) { this.totalSpecsDefined++; const config = this.env_.configuration(); const suite = this.currentDeclarationSuite_; + const parentSuiteId = suite === this.topSuite ? null : suite.id; const spec = new j$.Spec({ id: 'spec' + this.nextSpecId_++, + filename, + parentSuiteId, beforeAndAfterFns: beforeAndAfterFns(suite), expectationFactory: this.expectationFactory_, asyncExpectationFactory: this.specAsyncExpectationFactory_,