Report the path/url of the file that the spec/suite was defined in

Fixes #1884
This commit is contained in:
Steve Gravrock
2023-02-15 21:26:28 -08:00
parent bc3a495160
commit 6ad8d20694
7 changed files with 216 additions and 105 deletions

View File

@@ -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
@@ -741,6 +741,7 @@ getJasmineRequireObj().Spec = function(j$) {
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
this.resultCallback = attrs.resultCallback || function() {};
this.id = attrs.id;
this.filename = attrs.filename;
this.description = attrs.description || '';
this.queueableFn = attrs.queueableFn;
this.beforeAndAfterFns =
@@ -774,35 +775,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 +885,31 @@ 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} 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(),
filename: this.filename,
failedExpectations: [],
passedExpectations: [],
deprecationWarnings: [],
pendingReason: this.excludeMessage,
pendingReason: this.excludeMessage || '',
duration: null,
properties: null,
debugLogs: null
@@ -1816,17 +1806,23 @@ 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');
return suiteBuilder.fdescribe(description, definitionFn).metadata;
const filename = callerCallerFilename();
return suiteBuilder.fdescribe(description, definitionFn, filename)
.metadata;
};
function specResultCallback(spec, result, next) {
@@ -1853,17 +1849,20 @@ 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');
return suiteBuilder.fit(description, fn, timeout).metadata;
const filename = callerCallerFilename();
return suiteBuilder.fit(description, fn, timeout, filename).metadata;
};
/**
@@ -2003,6 +2002,10 @@ getJasmineRequireObj().Env = function(j$) {
};
}
function callerCallerFilename() {
return new j$.StackTrace(new Error()).frames[3].file;
}
return Env;
};
@@ -9530,6 +9533,7 @@ getJasmineRequireObj().Suite = function(j$) {
this.id = attrs.id;
this.parentSuite = attrs.parentSuite;
this.description = attrs.description;
this.filename = attrs.filename;
this.expectationFactory = attrs.expectationFactory;
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
@@ -9635,6 +9639,7 @@ 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} 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.
@@ -9646,6 +9651,7 @@ getJasmineRequireObj().Suite = function(j$) {
id: this.id,
description: this.description,
fullName: this.getFullName(),
filename: this.filename,
failedExpectations: [],
deprecationWarnings: [],
duration: null,
@@ -9873,9 +9879,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');
}
@@ -9886,9 +9892,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);
@@ -9898,37 +9904,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');
@@ -9936,7 +9942,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_();
@@ -9996,12 +10002,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();
}
@@ -10010,11 +10016,12 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
return spec;
}
suiteFactory_(description) {
suiteFactory_(description, filename) {
const config = this.env_.configuration();
return new j$.Suite({
id: 'suite' + this.nextSuiteId_++,
description,
filename,
parentSuite: this.currentDeclarationSuite_,
timer: new j$.Timer(),
expectationFactory: this.expectationFactory_,
@@ -10047,12 +10054,13 @@ 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 spec = new j$.Spec({
id: 'spec' + this.nextSpecId_++,
filename,
beforeAndAfterFns: beforeAndAfterFns(suite),
expectationFactory: this.expectationFactory_,
asyncExpectationFactory: this.specAsyncExpectationFactory_,

View File

@@ -195,6 +195,7 @@ describe('Spec', function() {
onStart: startCallback,
resultCallback: resultCallback,
description: 'with a spec',
filename: 'someSpecFile.js',
getSpecName: function() {
return 'a suite with a spec';
},
@@ -219,6 +220,7 @@ describe('Spec', function() {
status: 'pending',
description: 'with a spec',
fullName: 'a suite with a spec',
filename: 'someSpecFile.js',
failedExpectations: [],
passedExpectations: [],
deprecationWarnings: [],

View File

@@ -4004,6 +4004,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 {
addEventListener() {},

View File

@@ -674,17 +674,23 @@ 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');
return suiteBuilder.fdescribe(description, definitionFn).metadata;
const filename = callerCallerFilename();
return suiteBuilder.fdescribe(description, definitionFn, filename)
.metadata;
};
function specResultCallback(spec, result, next) {
@@ -711,17 +717,20 @@ 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');
return suiteBuilder.fit(description, fn, timeout).metadata;
const filename = callerCallerFilename();
return suiteBuilder.fit(description, fn, timeout, filename).metadata;
};
/**
@@ -861,5 +870,9 @@ getJasmineRequireObj().Env = function(j$) {
};
}
function callerCallerFilename() {
return new j$.StackTrace(new Error()).frames[3].file;
}
return Env;
};

View File

@@ -4,6 +4,7 @@ getJasmineRequireObj().Spec = function(j$) {
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
this.resultCallback = attrs.resultCallback || function() {};
this.id = attrs.id;
this.filename = attrs.filename;
this.description = attrs.description || '';
this.queueableFn = attrs.queueableFn;
this.beforeAndAfterFns =
@@ -37,35 +38,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 +148,31 @@ 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} 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(),
filename: this.filename,
failedExpectations: [],
passedExpectations: [],
deprecationWarnings: [],
pendingReason: this.excludeMessage,
pendingReason: this.excludeMessage || '',
duration: null,
properties: null,
debugLogs: null

View File

@@ -4,6 +4,7 @@ getJasmineRequireObj().Suite = function(j$) {
this.id = attrs.id;
this.parentSuite = attrs.parentSuite;
this.description = attrs.description;
this.filename = attrs.filename;
this.expectationFactory = attrs.expectationFactory;
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
@@ -109,6 +110,7 @@ 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} 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 +122,7 @@ getJasmineRequireObj().Suite = function(j$) {
id: this.id,
description: this.description,
fullName: this.getFullName(),
filename: this.filename,
failedExpectations: [],
deprecationWarnings: [],
duration: null,

View File

@@ -22,9 +22,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');
}
@@ -35,9 +35,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);
@@ -47,37 +47,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');
@@ -85,7 +85,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_();
@@ -145,12 +145,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();
}
@@ -159,11 +159,12 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
return spec;
}
suiteFactory_(description) {
suiteFactory_(description, filename) {
const config = this.env_.configuration();
return new j$.Suite({
id: 'suite' + this.nextSuiteId_++,
description,
filename,
parentSuite: this.currentDeclarationSuite_,
timer: new j$.Timer(),
expectationFactory: this.expectationFactory_,
@@ -196,12 +197,13 @@ 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 spec = new j$.Spec({
id: 'spec' + this.nextSpecId_++,
filename,
beforeAndAfterFns: beforeAndAfterFns(suite),
expectationFactory: this.expectationFactory_,
asyncExpectationFactory: this.specAsyncExpectationFactory_,