346 lines
9.3 KiB
JavaScript
346 lines
9.3 KiB
JavaScript
getJasmineRequireObj().Suite = function(j$) {
|
|
class Suite {
|
|
#reportedParentSuiteId;
|
|
#throwOnExpectationFailure;
|
|
#autoCleanClosures;
|
|
#timer;
|
|
|
|
constructor(attrs) {
|
|
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.onLateError = attrs.onLateError || function() {};
|
|
this.#reportedParentSuiteId = attrs.reportedParentSuiteId;
|
|
this.#throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
|
|
this.#autoCleanClosures =
|
|
attrs.autoCleanClosures === undefined
|
|
? true
|
|
: !!attrs.autoCleanClosures;
|
|
this.#timer = attrs.timer || new j$.Timer();
|
|
|
|
this.beforeFns = [];
|
|
this.afterFns = [];
|
|
this.beforeAllFns = [];
|
|
this.afterAllFns = [];
|
|
this.children = [];
|
|
|
|
this.reset();
|
|
}
|
|
|
|
setSuiteProperty(key, value) {
|
|
this.result.properties = this.result.properties || {};
|
|
this.result.properties[key] = value;
|
|
}
|
|
|
|
getFullName() {
|
|
const fullName = [];
|
|
for (
|
|
let parentSuite = this;
|
|
parentSuite;
|
|
parentSuite = parentSuite.parentSuite
|
|
) {
|
|
if (parentSuite.parentSuite) {
|
|
fullName.unshift(parentSuite.description);
|
|
}
|
|
}
|
|
return fullName.join(' ');
|
|
}
|
|
|
|
// Mark the suite with "pending" status
|
|
pend() {
|
|
this.markedPending = true;
|
|
}
|
|
|
|
// Like pend(), but pending state will survive reset().
|
|
// Useful for fdescribe, xdescribe, where pending state should remain.
|
|
exclude() {
|
|
this.pend();
|
|
this.markedExcluding = true;
|
|
}
|
|
|
|
beforeEach(fn) {
|
|
this.beforeFns.unshift({ ...fn, suite: this });
|
|
}
|
|
|
|
beforeAll(fn) {
|
|
this.beforeAllFns.push({ ...fn, type: 'beforeAll', suite: this });
|
|
}
|
|
|
|
afterEach(fn) {
|
|
this.afterFns.unshift({ ...fn, suite: this, type: 'afterEach' });
|
|
}
|
|
|
|
afterAll(fn) {
|
|
this.afterAllFns.unshift({ ...fn, type: 'afterAll' });
|
|
}
|
|
|
|
startTimer() {
|
|
this.#timer.start();
|
|
}
|
|
|
|
endTimer() {
|
|
this.result.duration = this.#timer.elapsed();
|
|
}
|
|
|
|
cleanupBeforeAfter() {
|
|
if (this.#autoCleanClosures) {
|
|
removeFns(this.beforeAllFns);
|
|
removeFns(this.afterAllFns);
|
|
removeFns(this.beforeFns);
|
|
removeFns(this.afterFns);
|
|
}
|
|
}
|
|
|
|
reset() {
|
|
/**
|
|
* @typedef SuiteResult
|
|
* @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 - Deprecated. The name of the file the suite was defined in.
|
|
* Note: The value may be incorrect if zone.js is installed or
|
|
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions that
|
|
* don't maintain the same call stack height as the originals. This property
|
|
* may be removed in a future version unless there is enough user interest
|
|
* in keeping it. See {@link https://github.com/jasmine/jasmine/issues/2065}.
|
|
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
|
|
* @property {ExpectationResult[]} 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.
|
|
* @property {number} duration - The time in ms for Suite execution, including any before/afterAll, before/afterEach.
|
|
* @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSuiteProperty}
|
|
* @since 2.0.0
|
|
*/
|
|
this.result = {
|
|
id: this.id,
|
|
description: this.description,
|
|
fullName: this.getFullName(),
|
|
parentSuiteId: this.#reportedParentSuiteId,
|
|
filename: this.filename,
|
|
failedExpectations: [],
|
|
deprecationWarnings: [],
|
|
duration: null,
|
|
properties: null
|
|
};
|
|
this.markedPending = this.markedExcluding;
|
|
this.children.forEach(function(child) {
|
|
child.reset();
|
|
});
|
|
this.reportedDone = false;
|
|
}
|
|
|
|
removeChildren() {
|
|
this.children = [];
|
|
}
|
|
|
|
addChild(child) {
|
|
this.children.push(child);
|
|
}
|
|
|
|
#status() {
|
|
if (this.markedPending) {
|
|
return 'pending';
|
|
}
|
|
|
|
if (this.result.failedExpectations.length > 0) {
|
|
return 'failed';
|
|
} else {
|
|
return 'passed';
|
|
}
|
|
}
|
|
|
|
getResult() {
|
|
this.result.status = this.#status();
|
|
return this.result;
|
|
}
|
|
|
|
canBeReentered() {
|
|
return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
|
|
}
|
|
|
|
sharedUserContext() {
|
|
if (!this.sharedContext) {
|
|
this.sharedContext = this.parentSuite
|
|
? this.parentSuite.clonedSharedUserContext()
|
|
: new j$.UserContext();
|
|
}
|
|
|
|
return this.sharedContext;
|
|
}
|
|
|
|
clonedSharedUserContext() {
|
|
return j$.UserContext.fromExisting(this.sharedUserContext());
|
|
}
|
|
|
|
handleException() {
|
|
if (arguments[0] instanceof j$.errors.ExpectationFailed) {
|
|
return;
|
|
}
|
|
|
|
const data = {
|
|
matcherName: '',
|
|
passed: false,
|
|
expected: '',
|
|
actual: '',
|
|
error: arguments[0]
|
|
};
|
|
const failedExpectation = j$.buildExpectationResult(data);
|
|
|
|
if (!this.parentSuite) {
|
|
failedExpectation.globalErrorType = 'afterAll';
|
|
}
|
|
|
|
if (this.reportedDone) {
|
|
this.onLateError(failedExpectation);
|
|
} else {
|
|
this.result.failedExpectations.push(failedExpectation);
|
|
}
|
|
}
|
|
|
|
onMultipleDone() {
|
|
let msg;
|
|
|
|
// Issue an error. Include the context ourselves and pass
|
|
// ignoreRunnable: true, since getting here always means that we've already
|
|
// moved on and the current runnable isn't the one that caused the problem.
|
|
if (this.parentSuite) {
|
|
msg =
|
|
"An asynchronous beforeAll or afterAll function called its 'done' " +
|
|
'callback more than once.\n' +
|
|
'(in suite: ' +
|
|
this.getFullName() +
|
|
')';
|
|
} else {
|
|
msg =
|
|
'A top-level beforeAll or afterAll function called its ' +
|
|
"'done' callback more than once.";
|
|
}
|
|
|
|
this.onLateError(new Error(msg));
|
|
}
|
|
|
|
addExpectationResult() {
|
|
if (isFailure(arguments)) {
|
|
const data = arguments[1];
|
|
const expectationResult = j$.buildExpectationResult(data);
|
|
|
|
if (this.reportedDone) {
|
|
this.onLateError(expectationResult);
|
|
} else {
|
|
this.result.failedExpectations.push(expectationResult);
|
|
|
|
// TODO: refactor so that we don't need to override cached status
|
|
if (this.result.status) {
|
|
this.result.status = 'failed';
|
|
}
|
|
}
|
|
|
|
if (this.#throwOnExpectationFailure) {
|
|
throw new j$.errors.ExpectationFailed();
|
|
}
|
|
}
|
|
}
|
|
|
|
addDeprecationWarning(deprecation) {
|
|
if (typeof deprecation === 'string') {
|
|
deprecation = { message: deprecation };
|
|
}
|
|
this.result.deprecationWarnings.push(
|
|
j$.buildExpectationResult(deprecation)
|
|
);
|
|
}
|
|
|
|
hasChildWithDescription(description) {
|
|
for (const child of this.children) {
|
|
if (child.description === description) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
get metadata() {
|
|
if (!this.metadata_) {
|
|
this.metadata_ = new SuiteMetadata(this);
|
|
}
|
|
|
|
return this.metadata_;
|
|
}
|
|
}
|
|
|
|
function removeFns(queueableFns) {
|
|
for (const qf of queueableFns) {
|
|
qf.fn = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @interface Suite
|
|
* @see Env#topSuite
|
|
* @since 2.0.0
|
|
*/
|
|
class SuiteMetadata {
|
|
#suite;
|
|
|
|
constructor(suite) {
|
|
this.#suite = suite;
|
|
/**
|
|
* The unique ID of this suite.
|
|
* @name Suite#id
|
|
* @readonly
|
|
* @type {string}
|
|
* @since 2.0.0
|
|
*/
|
|
this.id = suite.id;
|
|
|
|
/**
|
|
* The parent of this suite, or null if this is the top suite.
|
|
* @name Suite#parentSuite
|
|
* @readonly
|
|
* @type {Suite}
|
|
*/
|
|
this.parentSuite = suite.parentSuite ? suite.parentSuite.metadata : null;
|
|
|
|
/**
|
|
* The description passed to the {@link describe} that created this suite.
|
|
* @name Suite#description
|
|
* @readonly
|
|
* @type {string}
|
|
* @since 2.0.0
|
|
*/
|
|
this.description = suite.description;
|
|
}
|
|
|
|
/**
|
|
* The full description including all ancestors of this suite.
|
|
* @name Suite#getFullName
|
|
* @function
|
|
* @returns {string}
|
|
* @since 2.0.0
|
|
*/
|
|
getFullName() {
|
|
return this.#suite.getFullName();
|
|
}
|
|
|
|
/**
|
|
* The suite's children.
|
|
* @name Suite#children
|
|
* @type {Array.<(Spec|Suite)>}
|
|
* @since 2.0.0
|
|
*/
|
|
get children() {
|
|
return this.#suite.children.map(child => child.metadata);
|
|
}
|
|
}
|
|
|
|
function isFailure(args) {
|
|
return !args[0];
|
|
}
|
|
|
|
return Suite;
|
|
};
|