mirror of
https://github.com/hanzoai/jest.git
synced 2026-09-24 16:59:57 +00:00
chore: hoist functions higher if possible (#14812)
This commit is contained in:
+3
-1
@@ -236,6 +236,7 @@ module.exports = {
|
||||
'sort-keys': 'off',
|
||||
'unicorn/prefer-number-properties': 'off',
|
||||
'unicorn/no-static-only-class': 'off',
|
||||
'unicorn/consistent-function-scoping': 'off',
|
||||
},
|
||||
},
|
||||
// demonstration of matchers usage
|
||||
@@ -344,6 +345,7 @@ module.exports = {
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'unicorn/consistent-function-scoping': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -378,6 +380,7 @@ module.exports = {
|
||||
],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'unicorn/consistent-function-scoping': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -706,7 +709,6 @@ module.exports = {
|
||||
|
||||
// TODO: turn on at some point
|
||||
'unicorn/catch-error-name': 'off',
|
||||
'unicorn/consistent-function-scoping': 'off',
|
||||
'unicorn/error-message': 'off',
|
||||
'unicorn/no-await-expression-member': 'off',
|
||||
'unicorn/no-console-spaces': 'off',
|
||||
|
||||
@@ -46,7 +46,7 @@ FAIL __tests__/worksWithConcurrentMode.test.js
|
||||
15 | });
|
||||
16 |
|
||||
|
||||
at Function.failing (../../packages/jest-jasmine2/build/index.js:305:17)
|
||||
at Function.failing (../../packages/jest-jasmine2/build/index.js:307:17)
|
||||
at Suite.failing (__tests__/worksWithConcurrentMode.test.js:13:17)
|
||||
at Object.describe (__tests__/worksWithConcurrentMode.test.js:8:1)
|
||||
|
||||
@@ -80,7 +80,7 @@ FAIL __tests__/worksWithConcurrentOnlyMode.test.js
|
||||
15 | });
|
||||
16 |
|
||||
|
||||
at Function.failing (../../packages/jest-jasmine2/build/index.js:305:17)
|
||||
at Function.failing (../../packages/jest-jasmine2/build/index.js:307:17)
|
||||
at Suite.failing (__tests__/worksWithConcurrentOnlyMode.test.js:13:22)
|
||||
at Object.describe (__tests__/worksWithConcurrentOnlyMode.test.js:8:1)
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"experimentalDecorators": true,
|
||||
"strict": true
|
||||
},
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["./**/*"]
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"noEmit": true,
|
||||
"module": "commonjs",
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
},
|
||||
"include": ["./**/*"]
|
||||
}
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@
|
||||
"test-types": "yarn tstyche && yarn test-ts --selectProjects type-tests",
|
||||
"test": "yarn lint && yarn jest",
|
||||
"typecheck": "yarn typecheck:examples && yarn typecheck:tests",
|
||||
"typecheck:examples": "tsc -p examples/angular --noEmit && tsc -p examples/expect-extend --noEmit && tsc -p examples/typescript --noEmit",
|
||||
"typecheck:examples": "tsc -p examples/angular && tsc -p examples/expect-extend && tsc -p examples/typescript",
|
||||
"typecheck:tests": "tsc -b packages/{babel-jest,babel-plugin-jest-hoist,create-jest,diff-sequences,expect,expect-utils,jest-circus,jest-cli,jest-config,jest-console,jest-snapshot,jest-util,jest-validate,jest-watcher,jest-worker,pretty-format}/**/__tests__",
|
||||
"verify-old-ts": "node ./scripts/verifyOldTs.mjs",
|
||||
"verify-pnp": "node ./scripts/verifyPnP.mjs",
|
||||
|
||||
@@ -313,6 +313,52 @@ const extractJestObjExprIfHoistable = (expr: NodePath): JestObjInfo | null => {
|
||||
return null;
|
||||
};
|
||||
|
||||
function visitBlock(block: NodePath<BlockStatement> | NodePath<Program>) {
|
||||
// use a temporary empty statement instead of the real first statement, which may itself be hoisted
|
||||
const [varsHoistPoint, callsHoistPoint] = block.unshiftContainer('body', [
|
||||
emptyStatement(),
|
||||
emptyStatement(),
|
||||
]);
|
||||
block.traverse({
|
||||
CallExpression: visitCallExpr,
|
||||
VariableDeclarator: visitVariableDeclarator,
|
||||
// do not traverse into nested blocks, or we'll hoist calls in there out to this block
|
||||
denylist: ['BlockStatement'],
|
||||
});
|
||||
callsHoistPoint.remove();
|
||||
varsHoistPoint.remove();
|
||||
|
||||
function visitCallExpr(callExpr: NodePath<CallExpression>) {
|
||||
if (hoistedJestGetters.has(callExpr.node)) {
|
||||
const mockStmt = callExpr.getStatementParent();
|
||||
|
||||
if (mockStmt) {
|
||||
const mockStmtParent = mockStmt.parentPath;
|
||||
if (mockStmtParent.isBlock()) {
|
||||
const mockStmtNode = mockStmt.node;
|
||||
mockStmt.remove();
|
||||
callsHoistPoint.insertBefore(mockStmtNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function visitVariableDeclarator(varDecl: NodePath<VariableDeclarator>) {
|
||||
if (hoistedVariables.has(varDecl.node)) {
|
||||
// should be assert function, but it's not. So let's cast below
|
||||
varDecl.parentPath.assertVariableDeclaration();
|
||||
|
||||
const {kind, declarations} = varDecl.parent as VariableDeclaration;
|
||||
if (declarations.length === 1) {
|
||||
varDecl.parentPath.remove();
|
||||
} else {
|
||||
varDecl.remove();
|
||||
}
|
||||
varsHoistPoint.insertBefore(variableDeclaration(kind, [varDecl.node]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-disable sort-keys */
|
||||
export default function jestHoist(): PluginObj<{
|
||||
declareJestObjGetterIdentifier: () => Identifier;
|
||||
@@ -360,56 +406,6 @@ export default function jestHoist(): PluginObj<{
|
||||
post({path: program}) {
|
||||
visitBlock(program);
|
||||
program.traverse({BlockStatement: visitBlock});
|
||||
|
||||
function visitBlock(block: NodePath<BlockStatement> | NodePath<Program>) {
|
||||
// use a temporary empty statement instead of the real first statement, which may itself be hoisted
|
||||
const [varsHoistPoint, callsHoistPoint] = block.unshiftContainer(
|
||||
'body',
|
||||
[emptyStatement(), emptyStatement()],
|
||||
);
|
||||
block.traverse({
|
||||
CallExpression: visitCallExpr,
|
||||
VariableDeclarator: visitVariableDeclarator,
|
||||
// do not traverse into nested blocks, or we'll hoist calls in there out to this block
|
||||
denylist: ['BlockStatement'],
|
||||
});
|
||||
callsHoistPoint.remove();
|
||||
varsHoistPoint.remove();
|
||||
|
||||
function visitCallExpr(callExpr: NodePath<CallExpression>) {
|
||||
if (hoistedJestGetters.has(callExpr.node)) {
|
||||
const mockStmt = callExpr.getStatementParent();
|
||||
|
||||
if (mockStmt) {
|
||||
const mockStmtParent = mockStmt.parentPath;
|
||||
if (mockStmtParent.isBlock()) {
|
||||
const mockStmtNode = mockStmt.node;
|
||||
mockStmt.remove();
|
||||
callsHoistPoint.insertBefore(mockStmtNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function visitVariableDeclarator(
|
||||
varDecl: NodePath<VariableDeclarator>,
|
||||
) {
|
||||
if (hoistedVariables.has(varDecl.node)) {
|
||||
// should be assert function, but it's not. So let's cast below
|
||||
varDecl.parentPath.assertVariableDeclaration();
|
||||
|
||||
const {kind, declarations} = varDecl.parent as VariableDeclaration;
|
||||
if (declarations.length === 1) {
|
||||
varDecl.parentPath.remove();
|
||||
} else {
|
||||
varDecl.remove();
|
||||
}
|
||||
varsHoistPoint.insertBefore(
|
||||
variableDeclaration(kind, [varDecl.node]),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/* eslint-disable unicorn/consistent-function-scoping */
|
||||
|
||||
import {equals, iterableEquality} from '@jest/expect-utils';
|
||||
import {getType, isPrimitive} from 'jest-get-type';
|
||||
import {
|
||||
|
||||
@@ -426,6 +426,12 @@ const buildTestPathPatterns = (
|
||||
return testPathPatterns;
|
||||
};
|
||||
|
||||
function printConfig(opts: Array<string>) {
|
||||
const string = opts.map(ext => `'${ext}'`).join(', ');
|
||||
|
||||
return chalk.bold(`extensionsToTreatAsEsm: [${string}]`);
|
||||
}
|
||||
|
||||
function validateExtensionsToTreatAsEsm(
|
||||
extensionsToTreatAsEsm: Config.InitialOptions['extensionsToTreatAsEsm'],
|
||||
) {
|
||||
@@ -433,12 +439,6 @@ function validateExtensionsToTreatAsEsm(
|
||||
return;
|
||||
}
|
||||
|
||||
function printConfig(opts: Array<string>) {
|
||||
const string = opts.map(ext => `'${ext}'`).join(', ');
|
||||
|
||||
return chalk.bold(`extensionsToTreatAsEsm: [${string}]`);
|
||||
}
|
||||
|
||||
const extensionWithoutDot = extensionsToTreatAsEsm.some(
|
||||
ext => !ext.startsWith('.'),
|
||||
);
|
||||
|
||||
@@ -54,6 +54,10 @@ const hasSCM = (changedFilesInfo: ChangedFiles) => {
|
||||
return !noSCM;
|
||||
};
|
||||
|
||||
function normalizePosix(filePath: string) {
|
||||
return filePath.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
export default class SearchSource {
|
||||
private readonly _context: TestContext;
|
||||
private _dependencyResolver: DependencyResolver | null;
|
||||
@@ -299,10 +303,6 @@ export default class SearchSource {
|
||||
const allFiles = this._context.hasteFS.getAllFiles();
|
||||
const options = {nocase: true, windows: false};
|
||||
|
||||
function normalizePosix(filePath: string) {
|
||||
return filePath.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
paths = paths
|
||||
.map(p => {
|
||||
// micromatch works with forward slashes: https://github.com/micromatch/micromatch#backslashes
|
||||
|
||||
@@ -22,14 +22,14 @@ export default function getConfigsOfProjectsToRun(
|
||||
});
|
||||
}
|
||||
|
||||
const always = () => true;
|
||||
|
||||
function createProjectFilter(opts: {
|
||||
ignoreProjects: Array<string> | undefined;
|
||||
selectProjects: Array<string> | undefined;
|
||||
}) {
|
||||
const {selectProjects, ignoreProjects} = opts;
|
||||
|
||||
const always = () => true;
|
||||
|
||||
const selected = selectProjects
|
||||
? (name: string | undefined) => name && selectProjects.includes(name)
|
||||
: always;
|
||||
|
||||
@@ -60,6 +60,18 @@ function isString(value: unknown): value is string {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
|
||||
const timerIdToRef = (id: number) => ({
|
||||
id,
|
||||
ref() {
|
||||
return this;
|
||||
},
|
||||
unref() {
|
||||
return this;
|
||||
},
|
||||
});
|
||||
|
||||
const timerRefToId = (timer: Timer): number | undefined => timer?.id;
|
||||
|
||||
export default class NodeEnvironment implements JestEnvironment<Timer> {
|
||||
context: Context | null;
|
||||
fakeTimers: LegacyFakeTimers<Timer> | null;
|
||||
@@ -159,18 +171,6 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {
|
||||
|
||||
this.moduleMocker = new ModuleMocker(global);
|
||||
|
||||
const timerIdToRef = (id: number) => ({
|
||||
id,
|
||||
ref() {
|
||||
return this;
|
||||
},
|
||||
unref() {
|
||||
return this;
|
||||
},
|
||||
});
|
||||
|
||||
const timerRefToId = (timer: Timer): number | undefined => timer?.id;
|
||||
|
||||
this.fakeTimers = new LegacyFakeTimers({
|
||||
config: projectConfig,
|
||||
global,
|
||||
|
||||
@@ -431,11 +431,16 @@ export default class FakeTimers<TimerRef = unknown> {
|
||||
}
|
||||
}
|
||||
|
||||
private _createMocks() {
|
||||
const fn = <T extends FunctionLike = UnknownFunction>(implementation?: T) =>
|
||||
this._moduleMocker.fn(implementation);
|
||||
#createMockFunction<T extends FunctionLike = UnknownFunction>(
|
||||
implementation: T,
|
||||
) {
|
||||
return this._moduleMocker.fn(implementation.bind(this));
|
||||
}
|
||||
|
||||
const promisifiableFakeSetTimeout = fn(this._fakeSetTimeout.bind(this));
|
||||
private _createMocks() {
|
||||
const promisifiableFakeSetTimeout = this.#createMockFunction(
|
||||
this._fakeSetTimeout,
|
||||
);
|
||||
// @ts-expect-error: no index
|
||||
promisifiableFakeSetTimeout[promisify.custom] = (
|
||||
delay?: number,
|
||||
@@ -444,14 +449,16 @@ export default class FakeTimers<TimerRef = unknown> {
|
||||
new Promise(resolve => promisifiableFakeSetTimeout(resolve, delay, arg));
|
||||
|
||||
this._fakeTimerAPIs = {
|
||||
cancelAnimationFrame: fn(this._fakeClearTimer.bind(this)),
|
||||
clearImmediate: fn(this._fakeClearImmediate.bind(this)),
|
||||
clearInterval: fn(this._fakeClearTimer.bind(this)),
|
||||
clearTimeout: fn(this._fakeClearTimer.bind(this)),
|
||||
nextTick: fn(this._fakeNextTick.bind(this)),
|
||||
requestAnimationFrame: fn(this._fakeRequestAnimationFrame.bind(this)),
|
||||
setImmediate: fn(this._fakeSetImmediate.bind(this)),
|
||||
setInterval: fn(this._fakeSetInterval.bind(this)),
|
||||
cancelAnimationFrame: this.#createMockFunction(this._fakeClearTimer),
|
||||
clearImmediate: this.#createMockFunction(this._fakeClearImmediate),
|
||||
clearInterval: this.#createMockFunction(this._fakeClearTimer),
|
||||
clearTimeout: this.#createMockFunction(this._fakeClearTimer),
|
||||
nextTick: this.#createMockFunction(this._fakeNextTick),
|
||||
requestAnimationFrame: this.#createMockFunction(
|
||||
this._fakeRequestAnimationFrame,
|
||||
),
|
||||
setImmediate: this.#createMockFunction(this._fakeSetImmediate),
|
||||
setInterval: this.#createMockFunction(this._fakeSetInterval),
|
||||
setTimeout: promisifiableFakeSetTimeout,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -230,6 +230,7 @@ function makeConcurrent(
|
||||
return spec;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
const failing = () => {
|
||||
throw new Error(
|
||||
'Jest: `failing` tests are only supported in `jest-circus`.',
|
||||
@@ -242,7 +243,7 @@ function makeConcurrent(
|
||||
);
|
||||
};
|
||||
// each is bound after the function is made concurrent, so for now it is made noop
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function,unicorn/consistent-function-scoping
|
||||
concurrentFn.each = () => () => {};
|
||||
concurrentFn.failing = failing;
|
||||
|
||||
|
||||
@@ -28,6 +28,20 @@ export type TreeNode = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const noop = () => {};
|
||||
|
||||
function getNodeWithoutChildrenHandler(node: TreeNode, enabled: boolean) {
|
||||
return function fn(done: (error?: unknown) => void = noop) {
|
||||
node.execute(done, enabled);
|
||||
};
|
||||
}
|
||||
|
||||
function hasNoEnabledTest(node: TreeNode): boolean {
|
||||
return (
|
||||
node.disabled ||
|
||||
node.markedPending ||
|
||||
(node.children?.every(hasNoEnabledTest) ?? false)
|
||||
);
|
||||
}
|
||||
|
||||
export default function treeProcessor(options: Options): void {
|
||||
const {nodeComplete, nodeStart, queueRunnerFactory, runnableIds, tree} =
|
||||
options;
|
||||
@@ -43,12 +57,6 @@ export default function treeProcessor(options: Options): void {
|
||||
: getNodeWithoutChildrenHandler(node, enabled);
|
||||
}
|
||||
|
||||
function getNodeWithoutChildrenHandler(node: TreeNode, enabled: boolean) {
|
||||
return function fn(done: (error?: unknown) => void = noop) {
|
||||
node.execute(done, enabled);
|
||||
};
|
||||
}
|
||||
|
||||
function getNodeWithChildrenHandler(node: TreeNode, enabled: boolean) {
|
||||
return async function fn(done: (error?: unknown) => void = noop) {
|
||||
nodeStart(node);
|
||||
@@ -62,14 +70,6 @@ export default function treeProcessor(options: Options): void {
|
||||
};
|
||||
}
|
||||
|
||||
function hasNoEnabledTest(node: TreeNode): boolean {
|
||||
return (
|
||||
node.disabled ||
|
||||
node.markedPending ||
|
||||
(node.children?.every(hasNoEnabledTest) ?? false)
|
||||
);
|
||||
}
|
||||
|
||||
function wrapChildren(node: TreeNode, enabled: boolean) {
|
||||
if (!node.children) {
|
||||
throw new Error('`node.children` is not defined.');
|
||||
|
||||
@@ -72,13 +72,6 @@ export default class TestRunner extends EmittingTestRunner {
|
||||
throw new CancelRun();
|
||||
}
|
||||
|
||||
// `deepCyclicCopy` used here to avoid mem-leak
|
||||
const sendMessageToJest: TestFileEvent = (eventName, args) =>
|
||||
this.#eventEmitter.emit(
|
||||
eventName,
|
||||
deepCyclicCopy(args, {keepPrototype: false}),
|
||||
);
|
||||
|
||||
await this.#eventEmitter.emit('test-file-start', [test]);
|
||||
|
||||
return runTest(
|
||||
@@ -87,7 +80,7 @@ export default class TestRunner extends EmittingTestRunner {
|
||||
test.context.config,
|
||||
test.context.resolver,
|
||||
this._context,
|
||||
sendMessageToJest,
|
||||
this.#sendMessageToJest,
|
||||
);
|
||||
})
|
||||
.then(
|
||||
@@ -208,6 +201,14 @@ export default class TestRunner extends EmittingTestRunner {
|
||||
): UnsubscribeFn {
|
||||
return this.#eventEmitter.on(eventName, listener);
|
||||
}
|
||||
|
||||
#sendMessageToJest: TestFileEvent = async (eventName, args) => {
|
||||
await this.#eventEmitter.emit(
|
||||
eventName,
|
||||
// `deepCyclicCopy` used here to avoid mem-leak
|
||||
deepCyclicCopy(args, {keepPrototype: false}),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
class CancelRun extends Error {
|
||||
|
||||
@@ -1819,6 +1819,7 @@ export default class Runtime {
|
||||
return this._moduleImplementation;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
const createRequire = (modulePath: string | URL) => {
|
||||
const filename =
|
||||
typeof modulePath === 'string'
|
||||
@@ -2195,6 +2196,7 @@ export default class Runtime {
|
||||
this.restoreAllMocks();
|
||||
return jestObject;
|
||||
};
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
const _getFakeTimers = () => {
|
||||
if (
|
||||
this.isTornDown ||
|
||||
@@ -2238,9 +2240,7 @@ export default class Runtime {
|
||||
this.isolateModules(fn);
|
||||
return jestObject;
|
||||
};
|
||||
const isolateModulesAsync = (fn: () => Promise<void>): Promise<void> => {
|
||||
return this.isolateModulesAsync(fn);
|
||||
};
|
||||
const isolateModulesAsync = this.isolateModulesAsync.bind(this);
|
||||
const fn = this._moduleMocker.fn.bind(this._moduleMocker);
|
||||
const spyOn = this._moduleMocker.spyOn.bind(this._moduleMocker);
|
||||
const mocked =
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
function cardTransform(offset, handWidth) {
|
||||
const transform = `rotate(${offset * 4}deg) translateX(${
|
||||
(offset - (Math.abs(offset) * offset) / 7) * Math.min(140, handWidth / 8)
|
||||
}px)`;
|
||||
return transform;
|
||||
}
|
||||
|
||||
// Docusaurus v1 animation, reworked a bit for the Docusaurus v2 migration
|
||||
// TODO maybe we can use React code instead of Vanilla JS now?
|
||||
export function setupLandingAnimation() {
|
||||
@@ -17,13 +24,6 @@ export function setupLandingAnimation() {
|
||||
const hand = document.querySelector('.jest-hand');
|
||||
const cards = hand.querySelectorAll('.jest-card');
|
||||
|
||||
function cardTransform(offset, handWidth) {
|
||||
const transform = `rotate(${offset * 4}deg) translateX(${
|
||||
(offset - (Math.abs(offset) * offset) / 7) * Math.min(140, handWidth / 8)
|
||||
}px)`;
|
||||
return transform;
|
||||
}
|
||||
|
||||
function positionCards() {
|
||||
const handWidth = hand.offsetWidth;
|
||||
for (const card of cards) {
|
||||
|
||||
Reference in New Issue
Block a user