You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
716 lines
19 KiB
716 lines
19 KiB
'use strict'; |
|
|
|
Object.defineProperty(exports, '__esModule', { |
|
value: true |
|
}); |
|
exports.default = jasmineEnv; |
|
|
|
var _assert = require('assert'); |
|
|
|
var _jestUtil = require('jest-util'); |
|
|
|
var _assertionErrorMessage = _interopRequireDefault( |
|
require('../assertionErrorMessage') |
|
); |
|
|
|
var _isError = _interopRequireDefault(require('../isError')); |
|
|
|
var _queueRunner = _interopRequireDefault(require('../queueRunner')); |
|
|
|
var _treeProcessor = _interopRequireDefault(require('../treeProcessor')); |
|
|
|
function _interopRequireDefault(obj) { |
|
return obj && obj.__esModule ? obj : {default: obj}; |
|
} |
|
|
|
function _defineProperty(obj, key, value) { |
|
if (key in obj) { |
|
Object.defineProperty(obj, key, { |
|
value: value, |
|
enumerable: true, |
|
configurable: true, |
|
writable: true |
|
}); |
|
} else { |
|
obj[key] = value; |
|
} |
|
return obj; |
|
} |
|
|
|
function jasmineEnv(j$) { |
|
return class Env { |
|
constructor() { |
|
_defineProperty(this, 'specFilter', void 0); |
|
|
|
_defineProperty(this, 'catchExceptions', void 0); |
|
|
|
_defineProperty(this, 'throwOnExpectationFailure', void 0); |
|
|
|
_defineProperty(this, 'catchingExceptions', void 0); |
|
|
|
_defineProperty(this, 'topSuite', void 0); |
|
|
|
_defineProperty(this, 'fail', void 0); |
|
|
|
_defineProperty(this, 'pending', void 0); |
|
|
|
_defineProperty(this, 'afterAll', void 0); |
|
|
|
_defineProperty(this, 'fit', void 0); |
|
|
|
_defineProperty(this, 'throwingExpectationFailures', void 0); |
|
|
|
_defineProperty(this, 'randomizeTests', void 0); |
|
|
|
_defineProperty(this, 'randomTests', void 0); |
|
|
|
_defineProperty(this, 'seed', void 0); |
|
|
|
_defineProperty(this, 'execute', void 0); |
|
|
|
_defineProperty(this, 'fdescribe', void 0); |
|
|
|
_defineProperty(this, 'spyOn', void 0); |
|
|
|
_defineProperty(this, 'beforeEach', void 0); |
|
|
|
_defineProperty(this, 'afterEach', void 0); |
|
|
|
_defineProperty(this, 'clearReporters', void 0); |
|
|
|
_defineProperty(this, 'addReporter', void 0); |
|
|
|
_defineProperty(this, 'it', void 0); |
|
|
|
_defineProperty(this, 'xdescribe', void 0); |
|
|
|
_defineProperty(this, 'xit', void 0); |
|
|
|
_defineProperty(this, 'beforeAll', void 0); |
|
|
|
_defineProperty(this, 'todo', void 0); |
|
|
|
_defineProperty(this, 'provideFallbackReporter', void 0); |
|
|
|
_defineProperty(this, 'allowRespy', void 0); |
|
|
|
_defineProperty(this, 'describe', void 0); |
|
|
|
let totalSpecsDefined = 0; |
|
let catchExceptions = true; |
|
const realSetTimeout = global.setTimeout; |
|
const realClearTimeout = global.clearTimeout; |
|
const runnableResources = {}; |
|
const currentlyExecutingSuites = []; |
|
let currentSpec = null; |
|
let throwOnExpectationFailure = false; |
|
let random = false; |
|
let seed = null; |
|
let nextSpecId = 0; |
|
let nextSuiteId = 0; |
|
|
|
const getNextSpecId = function () { |
|
return 'spec' + nextSpecId++; |
|
}; |
|
|
|
const getNextSuiteId = function () { |
|
return 'suite' + nextSuiteId++; |
|
}; |
|
|
|
const topSuite = new j$.Suite({ |
|
id: getNextSuiteId(), |
|
description: '', |
|
|
|
getTestPath() { |
|
return j$.testPath; |
|
} |
|
}); |
|
let currentDeclarationSuite = topSuite; |
|
|
|
const currentSuite = function () { |
|
return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; |
|
}; |
|
|
|
const currentRunnable = function () { |
|
return currentSpec || currentSuite(); |
|
}; |
|
|
|
const reporter = new j$.ReportDispatcher([ |
|
'jasmineStarted', |
|
'jasmineDone', |
|
'suiteStarted', |
|
'suiteDone', |
|
'specStarted', |
|
'specDone' |
|
]); |
|
|
|
this.specFilter = function () { |
|
return true; |
|
}; |
|
|
|
const defaultResourcesForRunnable = function (id, _parentRunnableId) { |
|
const resources = { |
|
spies: [] |
|
}; |
|
runnableResources[id] = resources; |
|
}; |
|
|
|
const clearResourcesForRunnable = function (id) { |
|
spyRegistry.clearSpies(); |
|
delete runnableResources[id]; |
|
}; |
|
|
|
const beforeAndAfterFns = function (suite) { |
|
return function () { |
|
let afters = []; |
|
let befores = []; |
|
|
|
while (suite) { |
|
befores = befores.concat(suite.beforeFns); |
|
afters = afters.concat(suite.afterFns); |
|
suite = suite.parentSuite; |
|
} |
|
|
|
return { |
|
befores: befores.reverse(), |
|
afters |
|
}; |
|
}; |
|
}; |
|
|
|
const getSpecName = function (spec, suite) { |
|
const fullName = [spec.description]; |
|
const suiteFullName = suite.getFullName(); |
|
|
|
if (suiteFullName !== '') { |
|
fullName.unshift(suiteFullName); |
|
} |
|
|
|
return fullName.join(' '); |
|
}; |
|
|
|
this.catchExceptions = function (value) { |
|
catchExceptions = !!value; |
|
return catchExceptions; |
|
}; |
|
|
|
this.catchingExceptions = function () { |
|
return catchExceptions; |
|
}; |
|
|
|
this.throwOnExpectationFailure = function (value) { |
|
throwOnExpectationFailure = !!value; |
|
}; |
|
|
|
this.throwingExpectationFailures = function () { |
|
return throwOnExpectationFailure; |
|
}; |
|
|
|
this.randomizeTests = function (value) { |
|
random = !!value; |
|
}; |
|
|
|
this.randomTests = function () { |
|
return random; |
|
}; |
|
|
|
this.seed = function (value) { |
|
if (value) { |
|
seed = value; |
|
} |
|
|
|
return seed; |
|
}; |
|
|
|
const queueRunnerFactory = options => { |
|
options.clearTimeout = realClearTimeout; |
|
options.fail = this.fail; |
|
options.setTimeout = realSetTimeout; |
|
return (0, _queueRunner.default)(options); |
|
}; |
|
|
|
this.topSuite = function () { |
|
return topSuite; |
|
}; |
|
|
|
const uncaught = err => { |
|
if (currentSpec) { |
|
currentSpec.onException(err); |
|
currentSpec.cancel(); |
|
} else { |
|
console.error('Unhandled error'); |
|
console.error(err.stack); |
|
} |
|
}; |
|
|
|
let oldListenersException; |
|
let oldListenersRejection; |
|
|
|
const executionSetup = function () { |
|
// Need to ensure we are the only ones handling these exceptions. |
|
oldListenersException = process.listeners('uncaughtException').slice(); |
|
oldListenersRejection = process.listeners('unhandledRejection').slice(); |
|
j$.process.removeAllListeners('uncaughtException'); |
|
j$.process.removeAllListeners('unhandledRejection'); |
|
j$.process.on('uncaughtException', uncaught); |
|
j$.process.on('unhandledRejection', uncaught); |
|
}; |
|
|
|
const executionTeardown = function () { |
|
j$.process.removeListener('uncaughtException', uncaught); |
|
j$.process.removeListener('unhandledRejection', uncaught); // restore previous exception handlers |
|
|
|
oldListenersException.forEach(listener => { |
|
j$.process.on('uncaughtException', listener); |
|
}); |
|
oldListenersRejection.forEach(listener => { |
|
j$.process.on('unhandledRejection', listener); |
|
}); |
|
}; |
|
|
|
this.execute = async function (runnablesToRun, suiteTree = topSuite) { |
|
if (!runnablesToRun) { |
|
if (focusedRunnables.length) { |
|
runnablesToRun = focusedRunnables; |
|
} else { |
|
runnablesToRun = [suiteTree.id]; |
|
} |
|
} |
|
|
|
if (currentlyExecutingSuites.length === 0) { |
|
executionSetup(); |
|
} |
|
|
|
const lastDeclarationSuite = currentDeclarationSuite; |
|
await (0, _treeProcessor.default)({ |
|
nodeComplete(suite) { |
|
if (!suite.disabled) { |
|
clearResourcesForRunnable(suite.id); |
|
} |
|
|
|
currentlyExecutingSuites.pop(); |
|
|
|
if (suite === topSuite) { |
|
reporter.jasmineDone({ |
|
failedExpectations: topSuite.result.failedExpectations |
|
}); |
|
} else { |
|
reporter.suiteDone(suite.getResult()); |
|
} |
|
}, |
|
|
|
nodeStart(suite) { |
|
currentlyExecutingSuites.push(suite); |
|
defaultResourcesForRunnable( |
|
suite.id, |
|
suite.parentSuite && suite.parentSuite.id |
|
); |
|
|
|
if (suite === topSuite) { |
|
reporter.jasmineStarted({ |
|
totalSpecsDefined |
|
}); |
|
} else { |
|
reporter.suiteStarted(suite.result); |
|
} |
|
}, |
|
|
|
queueRunnerFactory, |
|
runnableIds: runnablesToRun, |
|
tree: suiteTree |
|
}); |
|
currentDeclarationSuite = lastDeclarationSuite; |
|
|
|
if (currentlyExecutingSuites.length === 0) { |
|
executionTeardown(); |
|
} |
|
}; |
|
|
|
this.addReporter = function (reporterToAdd) { |
|
reporter.addReporter(reporterToAdd); |
|
}; |
|
|
|
this.provideFallbackReporter = function (reporterToAdd) { |
|
reporter.provideFallbackReporter(reporterToAdd); |
|
}; |
|
|
|
this.clearReporters = function () { |
|
reporter.clearReporters(); |
|
}; |
|
|
|
const spyRegistry = new j$.SpyRegistry({ |
|
currentSpies() { |
|
if (!currentRunnable()) { |
|
throw new Error( |
|
'Spies must be created in a before function or a spec' |
|
); |
|
} |
|
|
|
return runnableResources[currentRunnable().id].spies; |
|
} |
|
}); |
|
|
|
this.allowRespy = function (allow) { |
|
spyRegistry.allowRespy(allow); |
|
}; |
|
|
|
this.spyOn = function (...args) { |
|
return spyRegistry.spyOn.apply(spyRegistry, args); |
|
}; |
|
|
|
const suiteFactory = function (description) { |
|
const suite = new j$.Suite({ |
|
id: getNextSuiteId(), |
|
description, |
|
parentSuite: currentDeclarationSuite, |
|
throwOnExpectationFailure, |
|
|
|
getTestPath() { |
|
return j$.testPath; |
|
} |
|
}); |
|
return suite; |
|
}; |
|
|
|
this.describe = function (description, specDefinitions) { |
|
const suite = suiteFactory(description); |
|
|
|
if (specDefinitions === undefined) { |
|
throw new Error( |
|
'Missing second argument. It must be a callback function.' |
|
); |
|
} |
|
|
|
if (typeof specDefinitions !== 'function') { |
|
throw new Error( |
|
`Invalid second argument, ${specDefinitions}. It must be a callback function.` |
|
); |
|
} |
|
|
|
if (specDefinitions.length > 0) { |
|
throw new Error('describe does not expect any arguments'); |
|
} |
|
|
|
if (currentDeclarationSuite.markedPending) { |
|
suite.pend(); |
|
} |
|
|
|
if (currentDeclarationSuite.markedTodo) { |
|
// @ts-expect-error TODO Possible error: Suite does not have todo method |
|
suite.todo(); |
|
} |
|
|
|
addSpecsToSuite(suite, specDefinitions); |
|
return suite; |
|
}; |
|
|
|
this.xdescribe = function (description, specDefinitions) { |
|
const suite = suiteFactory(description); |
|
suite.pend(); |
|
addSpecsToSuite(suite, specDefinitions); |
|
return suite; |
|
}; |
|
|
|
const focusedRunnables = []; |
|
|
|
this.fdescribe = function (description, specDefinitions) { |
|
const suite = suiteFactory(description); |
|
suite.isFocused = true; |
|
focusedRunnables.push(suite.id); |
|
unfocusAncestor(); |
|
addSpecsToSuite(suite, specDefinitions); |
|
return suite; |
|
}; |
|
|
|
const addSpecsToSuite = (suite, specDefinitions) => { |
|
const parentSuite = currentDeclarationSuite; |
|
parentSuite.addChild(suite); |
|
currentDeclarationSuite = suite; |
|
let declarationError = undefined; |
|
let describeReturnValue; |
|
|
|
try { |
|
describeReturnValue = specDefinitions.call(suite); |
|
} catch (e) { |
|
declarationError = e; |
|
} |
|
|
|
if ((0, _jestUtil.isPromise)(describeReturnValue)) { |
|
declarationError = new Error( |
|
'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.' |
|
); |
|
} else if (describeReturnValue !== undefined) { |
|
declarationError = new Error( |
|
'A "describe" callback must not return a value.' |
|
); |
|
} |
|
|
|
if (declarationError) { |
|
this.it('encountered a declaration exception', () => { |
|
throw declarationError; |
|
}); |
|
} |
|
|
|
currentDeclarationSuite = parentSuite; |
|
}; |
|
|
|
function findFocusedAncestor(suite) { |
|
while (suite) { |
|
if (suite.isFocused) { |
|
return suite.id; |
|
} |
|
|
|
suite = suite.parentSuite; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function unfocusAncestor() { |
|
const focusedAncestor = findFocusedAncestor(currentDeclarationSuite); |
|
|
|
if (focusedAncestor) { |
|
for (let i = 0; i < focusedRunnables.length; i++) { |
|
if (focusedRunnables[i] === focusedAncestor) { |
|
focusedRunnables.splice(i, 1); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
const specFactory = (description, fn, suite, timeout) => { |
|
totalSpecsDefined++; |
|
const spec = new j$.Spec({ |
|
id: getNextSpecId(), |
|
beforeAndAfterFns: beforeAndAfterFns(suite), |
|
resultCallback: specResultCallback, |
|
|
|
getSpecName(spec) { |
|
return getSpecName(spec, suite); |
|
}, |
|
|
|
getTestPath() { |
|
return j$.testPath; |
|
}, |
|
|
|
onStart: specStarted, |
|
description, |
|
queueRunnerFactory, |
|
|
|
userContext() { |
|
return suite.clonedSharedUserContext(); |
|
}, |
|
|
|
queueableFn: { |
|
fn, |
|
|
|
timeout() { |
|
return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; |
|
} |
|
}, |
|
throwOnExpectationFailure |
|
}); |
|
|
|
if (!this.specFilter(spec)) { |
|
spec.disable(); |
|
} |
|
|
|
return spec; |
|
|
|
function specResultCallback(result) { |
|
clearResourcesForRunnable(spec.id); |
|
currentSpec = null; |
|
reporter.specDone(result); |
|
} |
|
|
|
function specStarted(spec) { |
|
currentSpec = spec; |
|
defaultResourcesForRunnable(spec.id, suite.id); |
|
reporter.specStarted(spec.result); |
|
} |
|
}; |
|
|
|
this.it = function (description, fn, timeout) { |
|
if (typeof description !== 'string') { |
|
throw new Error( |
|
`Invalid first argument, ${description}. It must be a string.` |
|
); |
|
} |
|
|
|
if (fn === undefined) { |
|
throw new Error( |
|
'Missing second argument. It must be a callback function. Perhaps you want to use `test.todo` for a test placeholder.' |
|
); |
|
} |
|
|
|
if (typeof fn !== 'function') { |
|
throw new Error( |
|
`Invalid second argument, ${fn}. It must be a callback function.` |
|
); |
|
} |
|
|
|
const spec = specFactory( |
|
description, |
|
fn, |
|
currentDeclarationSuite, |
|
timeout |
|
); |
|
|
|
if (currentDeclarationSuite.markedPending) { |
|
spec.pend(); |
|
} // When a test is defined inside another, jasmine will not run it. |
|
// This check throws an error to warn the user about the edge-case. |
|
|
|
if (currentSpec !== null) { |
|
throw new Error( |
|
`Tests cannot be nested. Test "${spec.description}" cannot run because it is nested within "${currentSpec.description}".` |
|
); |
|
} |
|
|
|
currentDeclarationSuite.addChild(spec); |
|
return spec; |
|
}; |
|
|
|
this.xit = function (...args) { |
|
const spec = this.it.apply(this, args); |
|
spec.pend('Temporarily disabled with xit'); |
|
return spec; |
|
}; |
|
|
|
this.todo = function () { |
|
const description = arguments[0]; |
|
|
|
if (arguments.length !== 1 || typeof description !== 'string') { |
|
throw new _jestUtil.ErrorWithStack( |
|
'Todo must be called with only a description.', |
|
this.todo |
|
); |
|
} |
|
|
|
const spec = specFactory( |
|
description, |
|
() => {}, |
|
currentDeclarationSuite |
|
); |
|
|
|
if (currentDeclarationSuite.markedPending) { |
|
spec.pend(); |
|
} else { |
|
spec.todo(); |
|
} |
|
|
|
currentDeclarationSuite.addChild(spec); |
|
return spec; |
|
}; |
|
|
|
this.fit = function (description, fn, timeout) { |
|
const spec = specFactory( |
|
description, |
|
fn, |
|
currentDeclarationSuite, |
|
timeout |
|
); |
|
currentDeclarationSuite.addChild(spec); |
|
|
|
if (currentDeclarationSuite.markedPending) { |
|
spec.pend(); |
|
} else { |
|
focusedRunnables.push(spec.id); |
|
} |
|
|
|
unfocusAncestor(); |
|
return spec; |
|
}; |
|
|
|
this.beforeEach = function (beforeEachFunction, timeout) { |
|
currentDeclarationSuite.beforeEach({ |
|
fn: beforeEachFunction, |
|
|
|
timeout() { |
|
return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; |
|
} |
|
}); |
|
}; |
|
|
|
this.beforeAll = function (beforeAllFunction, timeout) { |
|
currentDeclarationSuite.beforeAll({ |
|
fn: beforeAllFunction, |
|
|
|
timeout() { |
|
return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; |
|
} |
|
}); |
|
}; |
|
|
|
this.afterEach = function (afterEachFunction, timeout) { |
|
currentDeclarationSuite.afterEach({ |
|
fn: afterEachFunction, |
|
|
|
timeout() { |
|
return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; |
|
} |
|
}); |
|
}; |
|
|
|
this.afterAll = function (afterAllFunction, timeout) { |
|
currentDeclarationSuite.afterAll({ |
|
fn: afterAllFunction, |
|
|
|
timeout() { |
|
return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; |
|
} |
|
}); |
|
}; |
|
|
|
this.pending = function (message) { |
|
let fullMessage = j$.Spec.pendingSpecExceptionMessage; |
|
|
|
if (message) { |
|
fullMessage += message; |
|
} |
|
|
|
throw fullMessage; |
|
}; |
|
|
|
this.fail = function (error) { |
|
let checkIsError; |
|
let message; |
|
|
|
if ( |
|
error instanceof _assert.AssertionError || |
|
(error && error.name === _assert.AssertionError.name) |
|
) { |
|
checkIsError = false; // @ts-expect-error TODO Possible error: j$.Spec does not have expand property |
|
|
|
message = (0, _assertionErrorMessage.default)(error, { |
|
expand: j$.Spec.expand |
|
}); |
|
} else { |
|
const check = (0, _isError.default)(error); |
|
checkIsError = check.isError; |
|
message = check.message; |
|
} |
|
|
|
const errorAsErrorObject = checkIsError ? error : new Error(message); |
|
const runnable = currentRunnable(); |
|
|
|
if (!runnable) { |
|
errorAsErrorObject.message = |
|
'Caught error after test environment was torn down\n\n' + |
|
errorAsErrorObject.message; |
|
throw errorAsErrorObject; |
|
} |
|
|
|
runnable.addExpectationResult(false, { |
|
matcherName: '', |
|
passed: false, |
|
expected: '', |
|
actual: '', |
|
message, |
|
error: errorAsErrorObject |
|
}); |
|
}; |
|
} |
|
}; |
|
}
|
|
|