2018-05-19 11:29:11 +01:00
|
|
|
'use strict';
|
|
|
|
|
|
2021-06-02 21:03:29 -04:00
|
|
|
const fs = require('fs');
|
2023-08-17 15:17:46 -07:00
|
|
|
const nodePath = require('path');
|
2018-05-19 11:29:11 +01:00
|
|
|
const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
|
|
|
|
|
|
2021-06-02 21:03:29 -04:00
|
|
|
function resolveEntryFork(resolvedEntry, isFBBundle) {
|
|
|
|
|
// Pick which entry point fork to use:
|
|
|
|
|
// .modern.fb.js
|
|
|
|
|
// .classic.fb.js
|
|
|
|
|
// .fb.js
|
|
|
|
|
// .stable.js
|
|
|
|
|
// .experimental.js
|
|
|
|
|
// .js
|
2024-06-19 14:19:48 -04:00
|
|
|
// or any of those plus .development.js
|
|
|
|
|
|
2021-06-02 21:03:29 -04:00
|
|
|
if (isFBBundle) {
|
2024-04-24 08:50:32 -07:00
|
|
|
// FB builds for react-dom need to alias both react-dom and react-dom/client to the same
|
|
|
|
|
// entrypoint since there is only a single build for them.
|
|
|
|
|
if (
|
|
|
|
|
resolvedEntry.endsWith('react-dom/index.js') ||
|
|
|
|
|
resolvedEntry.endsWith('react-dom/client.js') ||
|
|
|
|
|
resolvedEntry.endsWith('react-dom/unstable_testing.js')
|
|
|
|
|
) {
|
|
|
|
|
let specifier;
|
|
|
|
|
let entrypoint;
|
|
|
|
|
if (resolvedEntry.endsWith('index.js')) {
|
|
|
|
|
specifier = 'react-dom';
|
|
|
|
|
entrypoint = __EXPERIMENTAL__
|
|
|
|
|
? 'src/ReactDOMFB.modern.js'
|
|
|
|
|
: 'src/ReactDOMFB.js';
|
|
|
|
|
} else if (resolvedEntry.endsWith('client.js')) {
|
|
|
|
|
specifier = 'react-dom/client';
|
|
|
|
|
entrypoint = __EXPERIMENTAL__
|
|
|
|
|
? 'src/ReactDOMFB.modern.js'
|
|
|
|
|
: 'src/ReactDOMFB.js';
|
|
|
|
|
} else {
|
|
|
|
|
// must be unstable_testing
|
|
|
|
|
specifier = 'react-dom/unstable_testing';
|
|
|
|
|
entrypoint = __EXPERIMENTAL__
|
|
|
|
|
? 'src/ReactDOMTestingFB.modern.js'
|
|
|
|
|
: 'src/ReactDOMTestingFB.js';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resolvedEntry = nodePath.join(resolvedEntry, '..', entrypoint);
|
2025-02-13 18:26:36 +01:00
|
|
|
const devEntry = resolvedEntry.replace('.js', '.development.js');
|
|
|
|
|
if (__DEV__ && fs.existsSync(devEntry)) {
|
|
|
|
|
return devEntry;
|
2024-06-19 14:19:48 -04:00
|
|
|
}
|
2024-04-24 08:50:32 -07:00
|
|
|
if (fs.existsSync(resolvedEntry)) {
|
|
|
|
|
return resolvedEntry;
|
|
|
|
|
}
|
|
|
|
|
const fbReleaseChannel = __EXPERIMENTAL__ ? 'www-modern' : 'www-classic';
|
|
|
|
|
throw new Error(
|
|
|
|
|
`${fbReleaseChannel} tests are expected to alias ${specifier} to ${entrypoint} but this file was not found`
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-06-02 21:03:29 -04:00
|
|
|
const resolvedFBEntry = resolvedEntry.replace(
|
|
|
|
|
'.js',
|
|
|
|
|
__EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js'
|
|
|
|
|
);
|
2024-06-19 14:19:48 -04:00
|
|
|
const devFBEntry = resolvedFBEntry.replace('.js', '.development.js');
|
2025-02-13 18:26:36 +01:00
|
|
|
if (__DEV__ && fs.existsSync(devFBEntry)) {
|
2024-06-19 14:19:48 -04:00
|
|
|
return devFBEntry;
|
|
|
|
|
}
|
2021-06-02 21:03:29 -04:00
|
|
|
if (fs.existsSync(resolvedFBEntry)) {
|
|
|
|
|
return resolvedFBEntry;
|
|
|
|
|
}
|
|
|
|
|
const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js');
|
2025-02-13 18:26:36 +01:00
|
|
|
const devGenericFBEntry = resolvedGenericFBEntry.replace(
|
|
|
|
|
'.js',
|
|
|
|
|
'.development.js'
|
|
|
|
|
);
|
|
|
|
|
if (__DEV__ && fs.existsSync(devGenericFBEntry)) {
|
|
|
|
|
return devGenericFBEntry;
|
|
|
|
|
}
|
2021-06-02 21:03:29 -04:00
|
|
|
if (fs.existsSync(resolvedGenericFBEntry)) {
|
|
|
|
|
return resolvedGenericFBEntry;
|
|
|
|
|
}
|
|
|
|
|
// Even if it's a FB bundle we fallthrough to pick stable or experimental if we don't have an FB fork.
|
|
|
|
|
}
|
|
|
|
|
const resolvedForkedEntry = resolvedEntry.replace(
|
|
|
|
|
'.js',
|
|
|
|
|
__EXPERIMENTAL__ ? '.experimental.js' : '.stable.js'
|
|
|
|
|
);
|
2024-06-19 14:19:48 -04:00
|
|
|
const devForkedEntry = resolvedForkedEntry.replace('.js', '.development.js');
|
2025-02-13 18:26:36 +01:00
|
|
|
if (__DEV__ && fs.existsSync(devForkedEntry)) {
|
2024-06-19 14:19:48 -04:00
|
|
|
return devForkedEntry;
|
|
|
|
|
}
|
2021-06-02 21:03:29 -04:00
|
|
|
if (fs.existsSync(resolvedForkedEntry)) {
|
|
|
|
|
return resolvedForkedEntry;
|
|
|
|
|
}
|
2024-06-19 14:19:48 -04:00
|
|
|
const plainDevEntry = resolvedEntry.replace('.js', '.development.js');
|
2025-02-13 18:26:36 +01:00
|
|
|
if (__DEV__ && fs.existsSync(plainDevEntry)) {
|
2024-06-19 14:19:48 -04:00
|
|
|
return plainDevEntry;
|
|
|
|
|
}
|
2021-06-02 21:03:29 -04:00
|
|
|
// Just use the plain .js one.
|
|
|
|
|
return resolvedEntry;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-29 18:24:05 -04:00
|
|
|
function mockReact() {
|
|
|
|
|
jest.mock('react', () => {
|
|
|
|
|
const resolvedEntryPoint = resolveEntryFork(
|
|
|
|
|
require.resolve('react'),
|
2024-06-19 14:19:48 -04:00
|
|
|
global.__WWW__ || global.__XPLAT__,
|
|
|
|
|
global.__DEV__
|
2023-09-29 18:24:05 -04:00
|
|
|
);
|
|
|
|
|
return jest.requireActual(resolvedEntryPoint);
|
|
|
|
|
});
|
2024-04-08 19:23:23 -04:00
|
|
|
// Make it possible to import this module inside
|
|
|
|
|
// the React package itself.
|
|
|
|
|
jest.mock('shared/ReactSharedInternals', () => {
|
|
|
|
|
return jest.requireActual('react/src/ReactSharedInternalsClient');
|
|
|
|
|
});
|
2023-09-29 18:24:05 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// When we want to unmock React we really need to mock it again.
|
|
|
|
|
global.__unmockReact = mockReact;
|
|
|
|
|
|
|
|
|
|
mockReact();
|
2021-06-02 21:03:29 -04:00
|
|
|
|
2024-01-16 19:58:11 -05:00
|
|
|
jest.mock('react/react.react-server', () => {
|
2024-04-08 19:23:23 -04:00
|
|
|
// If we're requiring an RSC environment, use those internals instead.
|
|
|
|
|
jest.mock('shared/ReactSharedInternals', () => {
|
|
|
|
|
return jest.requireActual('react/src/ReactSharedInternalsServer');
|
|
|
|
|
});
|
2022-10-21 10:41:14 -04:00
|
|
|
const resolvedEntryPoint = resolveEntryFork(
|
2024-01-16 19:58:11 -05:00
|
|
|
require.resolve('react/src/ReactServer'),
|
2024-06-19 14:19:48 -04:00
|
|
|
global.__WWW__ || global.__XPLAT__,
|
|
|
|
|
global.__DEV__
|
2022-10-21 10:41:14 -04:00
|
|
|
);
|
|
|
|
|
return jest.requireActual(resolvedEntryPoint);
|
|
|
|
|
});
|
|
|
|
|
|
2018-05-19 11:29:11 +01:00
|
|
|
// When testing the custom renderer code path through `react-reconciler`,
|
|
|
|
|
// turn the export into a function, and use the argument as host config.
|
2023-04-10 14:58:44 -07:00
|
|
|
const shimHostConfigPath = 'react-reconciler/src/ReactFiberConfig';
|
2018-05-19 11:29:11 +01:00
|
|
|
jest.mock('react-reconciler', () => {
|
|
|
|
|
return config => {
|
|
|
|
|
jest.mock(shimHostConfigPath, () => config);
|
2021-05-27 17:33:57 +02:00
|
|
|
return jest.requireActual('react-reconciler');
|
2018-05-19 11:29:11 +01:00
|
|
|
};
|
|
|
|
|
});
|
2020-03-07 11:23:30 -08:00
|
|
|
const shimServerStreamConfigPath = 'react-server/src/ReactServerStreamConfig';
|
2023-04-10 14:54:26 -07:00
|
|
|
const shimServerConfigPath = 'react-server/src/ReactFizzConfig';
|
2020-03-10 14:55:04 -07:00
|
|
|
const shimFlightServerConfigPath = 'react-server/src/ReactFlightServerConfig';
|
2019-10-29 14:45:47 -07:00
|
|
|
jest.mock('react-server', () => {
|
2018-11-30 11:38:22 -08:00
|
|
|
return config => {
|
2020-03-07 11:23:30 -08:00
|
|
|
jest.mock(shimServerStreamConfigPath, () => config);
|
2023-04-10 14:54:26 -07:00
|
|
|
jest.mock(shimServerConfigPath, () => config);
|
2021-05-27 17:33:57 +02:00
|
|
|
return jest.requireActual('react-server');
|
2019-10-29 14:45:47 -07:00
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
jest.mock('react-server/flight', () => {
|
|
|
|
|
return config => {
|
2020-03-07 11:23:30 -08:00
|
|
|
jest.mock(shimServerStreamConfigPath, () => config);
|
2023-04-10 14:54:26 -07:00
|
|
|
jest.mock(shimServerConfigPath, () => config);
|
2023-04-10 14:47:23 -07:00
|
|
|
jest.mock('react-server/src/ReactFlightServerConfigBundlerCustom', () => ({
|
2023-01-27 20:08:26 -05:00
|
|
|
isClientReference: config.isClientReference,
|
2023-02-09 19:45:05 -05:00
|
|
|
isServerReference: config.isServerReference,
|
2023-01-27 20:08:26 -05:00
|
|
|
getClientReferenceKey: config.getClientReferenceKey,
|
2023-02-09 19:45:05 -05:00
|
|
|
resolveClientReferenceMetadata: config.resolveClientReferenceMetadata,
|
2020-03-18 12:18:34 -07:00
|
|
|
}));
|
2020-03-10 14:55:04 -07:00
|
|
|
jest.mock(shimFlightServerConfigPath, () =>
|
2021-05-27 17:33:57 +02:00
|
|
|
jest.requireActual(
|
2020-03-10 14:55:04 -07:00
|
|
|
'react-server/src/forks/ReactFlightServerConfig.custom'
|
|
|
|
|
)
|
|
|
|
|
);
|
2021-05-27 17:33:57 +02:00
|
|
|
return jest.requireActual('react-server/flight');
|
2019-10-29 14:45:47 -07:00
|
|
|
};
|
|
|
|
|
});
|
2023-04-10 14:47:23 -07:00
|
|
|
const shimFlightClientConfigPath = 'react-client/src/ReactFlightClientConfig';
|
2020-03-07 11:23:30 -08:00
|
|
|
jest.mock('react-client/flight', () => {
|
2019-10-29 14:45:47 -07:00
|
|
|
return config => {
|
2023-04-10 14:47:23 -07:00
|
|
|
jest.mock(shimFlightClientConfigPath, () => config);
|
2021-05-27 17:33:57 +02:00
|
|
|
return jest.requireActual('react-client/flight');
|
2018-11-30 11:38:22 -08:00
|
|
|
};
|
|
|
|
|
});
|
2018-05-19 11:29:11 +01:00
|
|
|
|
2020-03-06 16:20:42 -08:00
|
|
|
const configPaths = [
|
2023-04-10 14:58:44 -07:00
|
|
|
'react-reconciler/src/ReactFiberConfig',
|
2023-04-10 14:47:23 -07:00
|
|
|
'react-client/src/ReactFlightClientConfig',
|
2020-03-07 11:23:30 -08:00
|
|
|
'react-server/src/ReactServerStreamConfig',
|
2023-04-10 14:54:26 -07:00
|
|
|
'react-server/src/ReactFizzConfig',
|
2020-03-10 14:55:04 -07:00
|
|
|
'react-server/src/ReactFlightServerConfig',
|
2020-03-06 16:20:42 -08:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
function mockAllConfigs(rendererInfo) {
|
|
|
|
|
configPaths.forEach(path => {
|
|
|
|
|
// We want the reconciler to pick up the host config for this renderer.
|
|
|
|
|
jest.mock(path, () => {
|
|
|
|
|
let idx = path.lastIndexOf('/');
|
2023-04-19 14:26:01 -07:00
|
|
|
let forkPath = path.slice(0, idx) + '/forks' + path.slice(idx);
|
2023-08-17 15:17:46 -07:00
|
|
|
let parts = rendererInfo.shortName.split('-');
|
|
|
|
|
while (parts.length) {
|
|
|
|
|
try {
|
|
|
|
|
const candidate = `${forkPath}.${parts.join('-')}.js`;
|
|
|
|
|
fs.statSync(nodePath.join(process.cwd(), 'packages', candidate));
|
|
|
|
|
return jest.requireActual(candidate);
|
|
|
|
|
} catch (error) {
|
2023-09-27 09:53:31 -07:00
|
|
|
if (error.code !== 'ENOENT') {
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
2023-08-17 15:17:46 -07:00
|
|
|
// try without a part
|
|
|
|
|
}
|
|
|
|
|
parts.pop();
|
|
|
|
|
}
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Expected to find a fork for ${path} but did not find one.`
|
|
|
|
|
);
|
2020-03-06 16:20:42 -08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-19 11:29:11 +01:00
|
|
|
// But for inlined host configs (such as React DOM, Native, etc), we
|
|
|
|
|
// mock their named entry points to establish a host config mapping.
|
|
|
|
|
inlinedHostConfigs.forEach(rendererInfo => {
|
|
|
|
|
if (rendererInfo.shortName === 'custom') {
|
|
|
|
|
// There is no inline entry point for the custom renderers.
|
|
|
|
|
// Instead, it's handled by the generic `react-reconciler` entry point above.
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-03-06 16:20:42 -08:00
|
|
|
rendererInfo.entryPoints.forEach(entryPoint => {
|
|
|
|
|
jest.mock(entryPoint, () => {
|
|
|
|
|
mockAllConfigs(rendererInfo);
|
2021-06-02 21:03:29 -04:00
|
|
|
const resolvedEntryPoint = resolveEntryFork(
|
|
|
|
|
require.resolve(entryPoint),
|
2024-06-19 14:19:48 -04:00
|
|
|
global.__WWW__ || global.__XPLAT__,
|
|
|
|
|
global.__DEV__
|
2021-06-02 21:03:29 -04:00
|
|
|
);
|
|
|
|
|
return jest.requireActual(resolvedEntryPoint);
|
2018-05-21 15:38:35 +01:00
|
|
|
});
|
2018-05-19 11:29:11 +01:00
|
|
|
});
|
|
|
|
|
});
|
2018-07-13 02:45:37 +01:00
|
|
|
|
2024-04-08 19:23:23 -04:00
|
|
|
jest.mock('react-server/src/ReactFlightServer', () => {
|
|
|
|
|
// If we're requiring an RSC environment, use those internals instead.
|
|
|
|
|
jest.mock('shared/ReactSharedInternals', () => {
|
|
|
|
|
return jest.requireActual('react/src/ReactSharedInternalsServer');
|
|
|
|
|
});
|
|
|
|
|
return jest.requireActual('react-server/src/ReactFlightServer');
|
|
|
|
|
});
|
Add new mock build of Scheduler with flush, yield API (#14964)
* Add new mock build of Scheduler with flush, yield API
Test environments need a way to take control of the Scheduler queue and
incrementally flush work. Our current tests accomplish this either using
dynamic injection, or by using Jest's fake timers feature. Both of these
options are fragile and rely too much on implementation details.
In this new approach, we have a separate build of Scheduler that is
specifically designed for test environments. We mock the default
implementation like we would any other module; in our case, via Jest.
This special build has methods like `flushAll` and `yieldValue` that
control when work is flushed. These methods are based on equivalent
methods we've been using to write incremental React tests. Eventually
we may want to migrate the React tests to interact with the mock
Scheduler directly, instead of going through the host config like we
currently do.
For now, I'm using our custom static injection infrastructure to create
the two builds of Scheduler — a default build for DOM (which falls back
to a naive timer based implementation), and the new mock build. I did it
this way because it allows me to share most of the implementation, which
isn't specific to a host environment — e.g. everything related to the
priority queue. It may be better to duplicate the shared code instead,
especially considering that future environments (like React Native) may
have entirely forked implementations. I'd prefer to wait until the
implementation stabilizes before worrying about that, but I'm open to
changing this now if we decide it's important enough.
* Mock Scheduler in bundle tests, too
* Remove special case by making regex more restrictive
2019-02-26 20:51:17 -08:00
|
|
|
|
2022-09-15 15:31:31 -07:00
|
|
|
// Make it possible to import this module inside
|
|
|
|
|
// the ReactDOM package itself.
|
|
|
|
|
jest.mock('shared/ReactDOMSharedInternals', () =>
|
|
|
|
|
jest.requireActual('react-dom/src/ReactDOMSharedInternals')
|
|
|
|
|
);
|
|
|
|
|
|
2021-05-27 17:33:57 +02:00
|
|
|
jest.mock('scheduler', () => jest.requireActual('scheduler/unstable_mock'));
|