@mui/internal-test-utils 1.0.0-dev.20240529-082515-213b5e33ab
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/build/.tsbuildinfo +1 -0
- package/build/KarmaReporterReactProfiler.d.ts +51 -0
- package/build/KarmaReporterReactProfiler.d.ts.map +1 -0
- package/build/KarmaReporterReactProfiler.js +68 -0
- package/build/KarmaReporterReactProfiler.js.map +1 -0
- package/build/components.d.ts +36 -0
- package/build/components.d.ts.map +1 -0
- package/build/components.js +78 -0
- package/build/components.js.map +1 -0
- package/build/createDOM.d.ts +3 -0
- package/build/createDOM.d.ts.map +1 -0
- package/build/createDOM.js +60 -0
- package/build/createDOM.js.map +1 -0
- package/build/createDescribe.d.ts +8 -0
- package/build/createDescribe.d.ts.map +1 -0
- package/build/createDescribe.js +22 -0
- package/build/createDescribe.js.map +1 -0
- package/build/createMount.d.ts +9 -0
- package/build/createMount.d.ts.map +1 -0
- package/build/createMount.js +120 -0
- package/build/createMount.js.map +1 -0
- package/build/createRenderer.d.ts +231 -0
- package/build/createRenderer.d.ts.map +1 -0
- package/build/createRenderer.js +506 -0
- package/build/createRenderer.js.map +1 -0
- package/build/createRenderer.test.d.ts +2 -0
- package/build/createRenderer.test.d.ts.map +1 -0
- package/build/createRenderer.test.js +48 -0
- package/build/createRenderer.test.js.map +1 -0
- package/build/describeConformance.d.ts +152 -0
- package/build/describeConformance.d.ts.map +1 -0
- package/build/describeConformance.js +815 -0
- package/build/describeConformance.js.map +1 -0
- package/build/findOutermostIntrinsic.d.ts +14 -0
- package/build/findOutermostIntrinsic.d.ts.map +1 -0
- package/build/findOutermostIntrinsic.js +22 -0
- package/build/findOutermostIntrinsic.js.map +1 -0
- package/build/findOutermostIntrinsic.test.d.ts +2 -0
- package/build/findOutermostIntrinsic.test.d.ts.map +1 -0
- package/build/findOutermostIntrinsic.test.js +70 -0
- package/build/findOutermostIntrinsic.test.js.map +1 -0
- package/build/fireDiscreteEvent.d.ts +7 -0
- package/build/fireDiscreteEvent.d.ts.map +1 -0
- package/build/fireDiscreteEvent.js +98 -0
- package/build/fireDiscreteEvent.js.map +1 -0
- package/build/flushMicrotasks.d.ts +2 -0
- package/build/flushMicrotasks.d.ts.map +1 -0
- package/build/flushMicrotasks.js +11 -0
- package/build/flushMicrotasks.js.map +1 -0
- package/build/focusVisible.d.ts +7 -0
- package/build/focusVisible.d.ts.map +1 -0
- package/build/focusVisible.js +33 -0
- package/build/focusVisible.js.map +1 -0
- package/build/index.d.ts +18 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +59 -0
- package/build/index.js.map +1 -0
- package/build/init.d.ts +2 -0
- package/build/init.d.ts.map +1 -0
- package/build/init.js +42 -0
- package/build/init.js.map +1 -0
- package/build/initMatchers.d.ts +81 -0
- package/build/initMatchers.d.ts.map +1 -0
- package/build/initMatchers.js +431 -0
- package/build/initMatchers.js.map +1 -0
- package/build/initMatchers.test.d.ts +2 -0
- package/build/initMatchers.test.d.ts.map +1 -0
- package/build/initMatchers.test.js +103 -0
- package/build/initMatchers.test.js.map +1 -0
- package/build/initPlaywrightMatchers.d.ts +25 -0
- package/build/initPlaywrightMatchers.d.ts.map +1 -0
- package/build/initPlaywrightMatchers.js +63 -0
- package/build/initPlaywrightMatchers.js.map +1 -0
- package/build/mochaHooks.d.ts +25 -0
- package/build/mochaHooks.d.ts.map +1 -0
- package/build/mochaHooks.js +171 -0
- package/build/mochaHooks.js.map +1 -0
- package/build/mochaHooks.test.d.ts +2 -0
- package/build/mochaHooks.test.d.ts.map +1 -0
- package/build/mochaHooks.test.js +112 -0
- package/build/mochaHooks.test.js.map +1 -0
- package/build/setup.d.ts +2 -0
- package/build/setup.d.ts.map +1 -0
- package/build/setup.js +10 -0
- package/build/setup.js.map +1 -0
- package/build/setupBabel.d.ts +2 -0
- package/build/setupBabel.d.ts.map +1 -0
- package/build/setupBabel.js +5 -0
- package/build/setupBabel.js.map +1 -0
- package/build/setupBabelPlaywright.d.ts +2 -0
- package/build/setupBabelPlaywright.d.ts.map +1 -0
- package/build/setupBabelPlaywright.js +11 -0
- package/build/setupBabelPlaywright.js.map +1 -0
- package/build/setupJSDOM.d.ts +7 -0
- package/build/setupJSDOM.d.ts.map +1 -0
- package/build/setupJSDOM.js +17 -0
- package/build/setupJSDOM.js.map +1 -0
- package/build/setupKarma.d.ts +2 -0
- package/build/setupKarma.d.ts.map +1 -0
- package/build/setupKarma.js +56 -0
- package/build/setupKarma.js.map +1 -0
- package/build/until.d.ts +2 -0
- package/build/until.d.ts.map +1 -0
- package/build/until.js +26 -0
- package/build/until.js.map +1 -0
- package/build/until.test.d.ts +2 -0
- package/build/until.test.d.ts.map +1 -0
- package/build/until.test.js +118 -0
- package/build/until.test.js.map +1 -0
- package/build/userEvent.d.ts +8 -0
- package/build/userEvent.d.ts.map +1 -0
- package/build/userEvent.js +61 -0
- package/build/userEvent.js.map +1 -0
- package/package.json +76 -0
- package/src/KarmaReporterReactProfiler.js +81 -0
- package/src/components.tsx +61 -0
- package/src/createDOM.d.ts +9 -0
- package/src/createDOM.js +67 -0
- package/src/createDescribe.ts +31 -0
- package/src/createMount.tsx +136 -0
- package/src/createRenderer.test.js +31 -0
- package/src/createRenderer.tsx +737 -0
- package/src/describeConformance.tsx +1117 -0
- package/src/findOutermostIntrinsic.test.js +68 -0
- package/src/findOutermostIntrinsic.ts +19 -0
- package/src/fireDiscreteEvent.ts +76 -0
- package/src/flushMicrotasks.ts +8 -0
- package/src/focusVisible.ts +28 -0
- package/src/index.ts +24 -0
- package/src/init.js +15 -0
- package/src/initMatchers.test.js +126 -0
- package/src/initMatchers.ts +658 -0
- package/src/initPlaywrightMatchers.ts +96 -0
- package/src/mochaHooks.js +209 -0
- package/src/mochaHooks.test.js +113 -0
- package/src/setup.js +10 -0
- package/src/setupBabel.js +3 -0
- package/src/setupBabelPlaywright.js +10 -0
- package/src/setupJSDOM.js +20 -0
- package/src/setupKarma.js +65 -0
- package/src/until.js +28 -0
- package/src/until.test.js +120 -0
- package/src/userEvent.ts +35 -0
- package/tsconfig.build.json +17 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import chai, { AssertionError } from 'chai';
|
|
2
|
+
import * as DomTestingLibrary from '@testing-library/dom';
|
|
3
|
+
import type { ElementHandle } from 'playwright';
|
|
4
|
+
|
|
5
|
+
// https://stackoverflow.com/a/46755166/3406963
|
|
6
|
+
declare global {
|
|
7
|
+
namespace Chai {
|
|
8
|
+
interface Assertion {
|
|
9
|
+
/**
|
|
10
|
+
* Checks if the element handle is actually focused i.e. the element handle is pointing to `document.activeElement`.
|
|
11
|
+
*/
|
|
12
|
+
toHaveFocus(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Checks if the element handle has the given attribute.
|
|
15
|
+
* @example expect($element).toHaveAttribute('aria-expanded') is like `[aria-expanded]` CSS selector
|
|
16
|
+
* @example expect($element).toHaveAttribute('aria-expanded', 'true') is like `[aria-expanded="true"]` CSS selector
|
|
17
|
+
*/
|
|
18
|
+
toHaveAttribute(attributeName: string, attributeValue?: string): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface Window {
|
|
23
|
+
DomTestingLibrary: typeof DomTestingLibrary;
|
|
24
|
+
/**
|
|
25
|
+
* @example $element.evaluate(element => window.pageElementToString(element))
|
|
26
|
+
*/
|
|
27
|
+
elementToString(element: Node | null | undefined): string | false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
chai.use((chaiAPI, utils) => {
|
|
32
|
+
chai.Assertion.addMethod('toHaveFocus', async function elementHandleIsFocused() {
|
|
33
|
+
const $elementOrHandle: ElementHandle | Promise<ElementHandle> = utils.flag(this, 'object');
|
|
34
|
+
if ($elementOrHandle == null) {
|
|
35
|
+
throw new AssertionError(`Expected an element handle but got ${String($elementOrHandle)}.`);
|
|
36
|
+
}
|
|
37
|
+
const $element =
|
|
38
|
+
typeof ($elementOrHandle as Promise<any>).then === 'function'
|
|
39
|
+
? await ($elementOrHandle as Promise<ElementHandle>)
|
|
40
|
+
: ($elementOrHandle as ElementHandle);
|
|
41
|
+
|
|
42
|
+
const { isFocused, stringifiedActiveElement, stringifiedElement } = await $element.evaluate(
|
|
43
|
+
(element) => {
|
|
44
|
+
const activeElement =
|
|
45
|
+
element.ownerDocument !== null ? element.ownerDocument.activeElement : null;
|
|
46
|
+
return {
|
|
47
|
+
isFocused: activeElement === element,
|
|
48
|
+
stringifiedElement: window.elementToString(element),
|
|
49
|
+
stringifiedActiveElement: window.elementToString(activeElement),
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
this.assert(
|
|
55
|
+
isFocused,
|
|
56
|
+
`expected element to have focus`,
|
|
57
|
+
`expected element to NOT have focus \n${stringifiedElement}`,
|
|
58
|
+
stringifiedElement,
|
|
59
|
+
stringifiedActiveElement,
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
chai.Assertion.addMethod(
|
|
64
|
+
'toHaveAttribute',
|
|
65
|
+
async function elementHandleHasAttribute(attributeName: string, attributeValue?: string) {
|
|
66
|
+
const $elementOrHandle: ElementHandle | Promise<ElementHandle> = utils.flag(this, 'object');
|
|
67
|
+
if ($elementOrHandle == null) {
|
|
68
|
+
throw new AssertionError(`Expected an element handle but got ${String($elementOrHandle)}.`);
|
|
69
|
+
}
|
|
70
|
+
const $element =
|
|
71
|
+
typeof ($elementOrHandle as Promise<any>).then === 'function'
|
|
72
|
+
? await ($elementOrHandle as Promise<ElementHandle>)
|
|
73
|
+
: ($elementOrHandle as ElementHandle);
|
|
74
|
+
|
|
75
|
+
const actualAttributeValue = await $element.getAttribute(attributeName);
|
|
76
|
+
|
|
77
|
+
if (attributeValue === undefined) {
|
|
78
|
+
this.assert(
|
|
79
|
+
actualAttributeValue !== null,
|
|
80
|
+
`expected element to have attribute \`${attributeName}\``,
|
|
81
|
+
`expected element to NOT have attribute \`${attributeName}\``,
|
|
82
|
+
null,
|
|
83
|
+
null,
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
this.assert(
|
|
87
|
+
actualAttributeValue === attributeValue,
|
|
88
|
+
`expected element to have attribute \`${attributeName}="${attributeValue}"\``,
|
|
89
|
+
`expected element to NOT have attribute \`${attributeName}="${attributeValue}"\``,
|
|
90
|
+
attributeValue,
|
|
91
|
+
actualAttributeValue,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
const formatUtil = require('format-util');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {(this: import('mocha').Context) => void} MochaHook
|
|
6
|
+
*
|
|
7
|
+
* @typedef {object} MochaHooks
|
|
8
|
+
* @property {MochaHook[]} beforeAll
|
|
9
|
+
* @property {MochaHook[]} afterAll
|
|
10
|
+
* @property {MochaHook[]} beforeEach
|
|
11
|
+
* @property {MochaHook[]} afterEach
|
|
12
|
+
*
|
|
13
|
+
* @typedef {object} Mocha -- custom definition for `const mocha = require('mocha')`
|
|
14
|
+
* @property {import('mocha').utils} utils
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const isKarma = Boolean(process.env.KARMA);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Polyfills for https://github.com/facebook/react/issues/19416.
|
|
21
|
+
* @param {[stack: string, message: string][]} consoleCalls
|
|
22
|
+
* @returns {[stack: string, message: string][]}
|
|
23
|
+
*/
|
|
24
|
+
function dedupeActWarningsByComponent(consoleCalls) {
|
|
25
|
+
/**
|
|
26
|
+
* @type {[stack: string, message: string][]}
|
|
27
|
+
*/
|
|
28
|
+
const dedupedCalls = [];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @type {string | undefined}
|
|
32
|
+
*/
|
|
33
|
+
let updatingComponentOutsideAct;
|
|
34
|
+
|
|
35
|
+
consoleCalls.forEach(([stack, message]) => {
|
|
36
|
+
const componentName = message.match(
|
|
37
|
+
/An update to (.+?) ran an effect, but was not wrapped in act/,
|
|
38
|
+
)?.[1];
|
|
39
|
+
|
|
40
|
+
const duplicateMissingActWarning =
|
|
41
|
+
componentName !== undefined && componentName === updatingComponentOutsideAct;
|
|
42
|
+
if (!duplicateMissingActWarning) {
|
|
43
|
+
dedupedCalls.push([stack, message]);
|
|
44
|
+
}
|
|
45
|
+
updatingComponentOutsideAct = componentName;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return dedupedCalls;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {Mocha} Mocha
|
|
53
|
+
* @param {'warn' | 'error'} methodName
|
|
54
|
+
* @param {string} expectedMatcher
|
|
55
|
+
* @returns MochaHooks
|
|
56
|
+
*/
|
|
57
|
+
function createUnexpectedConsoleMessagesHooks(Mocha, methodName, expectedMatcher) {
|
|
58
|
+
/**
|
|
59
|
+
* @type {MochaHooks}
|
|
60
|
+
*/
|
|
61
|
+
const mochaHooks = {
|
|
62
|
+
beforeAll: [],
|
|
63
|
+
afterAll: [],
|
|
64
|
+
beforeEach: [],
|
|
65
|
+
afterEach: [],
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* @type {[stack: string, message: string][]}
|
|
69
|
+
*/
|
|
70
|
+
const unexpectedCalls = [];
|
|
71
|
+
const stackTraceFilter = Mocha.utils.stackTraceFilter();
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {string} format
|
|
75
|
+
* @param {...unknown} args
|
|
76
|
+
* @returns {void}
|
|
77
|
+
*/
|
|
78
|
+
function logUnexpectedConsoleCalls(format, ...args) {
|
|
79
|
+
const message = formatUtil(format, ...args);
|
|
80
|
+
|
|
81
|
+
// Safe stack so that test dev can track where the unexpected console message was created.
|
|
82
|
+
const { stack } = new Error();
|
|
83
|
+
if (stack === undefined) {
|
|
84
|
+
throw new TypeError(
|
|
85
|
+
`Unable to get stack. Logging unexpected console calls is only supported in environments where Error.prototype.stack is implemented.`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (process.env.NODE_ENV === 'production') {
|
|
90
|
+
// TODO: mock scheduler
|
|
91
|
+
if (message.indexOf('act(...) is not supported in production builds of React') !== -1) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Ignore legacy root deprecation warnings
|
|
97
|
+
// TODO: Remove once we no longer use legacy roots.
|
|
98
|
+
if (
|
|
99
|
+
message.indexOf('Use createRoot instead.') !== -1 ||
|
|
100
|
+
message.indexOf('Use hydrateRoot instead.') !== -1
|
|
101
|
+
) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (message.indexOf('Warning: useLayoutEffect does nothing on the server') !== -1) {
|
|
106
|
+
// Controversial warning that is commonly ignored by switching to `useEffect` on the server.
|
|
107
|
+
// https://github.com/facebook/react/issues/14927
|
|
108
|
+
// However, this switch doesn't work since it relies on environment sniffing and we test SSR in a browser environment.
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Unclear why this is an issue for the current occurrences of this warning.
|
|
113
|
+
// TODO: Revisit once https://github.com/facebook/react/issues/22796 is resolved
|
|
114
|
+
if (
|
|
115
|
+
message.indexOf(
|
|
116
|
+
'Detected multiple renderers concurrently rendering the same context provider.',
|
|
117
|
+
) !== -1
|
|
118
|
+
) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
unexpectedCalls.push([
|
|
123
|
+
// first line includes the (empty) error message
|
|
124
|
+
// i.e. Remove the `Error:` line
|
|
125
|
+
// second line is this frame
|
|
126
|
+
stackTraceFilter(stack.split('\n').slice(2).join('\n')),
|
|
127
|
+
message,
|
|
128
|
+
]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @type {Console['warn' | 'error']}
|
|
133
|
+
*/
|
|
134
|
+
let originalConsoleMethod;
|
|
135
|
+
mochaHooks.beforeAll.push(function registerConsoleStub() {
|
|
136
|
+
// eslint-disable-next-line no-console
|
|
137
|
+
originalConsoleMethod = console[methodName];
|
|
138
|
+
// eslint-disable-next-line no-console
|
|
139
|
+
console[methodName] = logUnexpectedConsoleCalls;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
mochaHooks.afterAll.push(function registerConsoleStub() {
|
|
143
|
+
// eslint-disable-next-line no-console
|
|
144
|
+
console[methodName] = originalConsoleMethod;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
mochaHooks.afterEach.push(function flushUnexpectedCalls() {
|
|
148
|
+
const actionableCalls = dedupeActWarningsByComponent(unexpectedCalls);
|
|
149
|
+
const actionableCallCount = actionableCalls.length;
|
|
150
|
+
const formattedCalls = actionableCalls.map(
|
|
151
|
+
([stack, message], index) =>
|
|
152
|
+
`console.${methodName} message #${index + 1}:\n ${message}\n\nStack:\n${stack}`,
|
|
153
|
+
);
|
|
154
|
+
unexpectedCalls.length = 0;
|
|
155
|
+
|
|
156
|
+
// eslint-disable-next-line no-console
|
|
157
|
+
if (console[methodName] !== logUnexpectedConsoleCalls) {
|
|
158
|
+
throw new Error(`Did not tear down spy or stub of console.${methodName} in your test.`);
|
|
159
|
+
}
|
|
160
|
+
if (actionableCallCount > 0) {
|
|
161
|
+
// In karma `file` is `null`.
|
|
162
|
+
// We still have the stacktrace though
|
|
163
|
+
// @ts-expect-error -- this.currentTest being undefined would be a bug
|
|
164
|
+
const location = this.currentTest.file ?? '(unknown file)';
|
|
165
|
+
const message =
|
|
166
|
+
`Expected test not to call console.${methodName}() but instead received ${actionableCallCount} calls.\n\n` +
|
|
167
|
+
'If the warning is expected, test for it explicitly by ' +
|
|
168
|
+
// Don't add any punctuation after the location.
|
|
169
|
+
// Otherwise it's not clickable in IDEs
|
|
170
|
+
`using the ${expectedMatcher}() matcher.\nTest location:\n ${location} `;
|
|
171
|
+
|
|
172
|
+
const error = new Error(`${message}\n\n${formattedCalls.join('\n\n')}`);
|
|
173
|
+
// The stack of `flushUnexpectedCalls` is irrelevant.
|
|
174
|
+
// It includes no clue where the test was triggered
|
|
175
|
+
error.stack = '';
|
|
176
|
+
|
|
177
|
+
if (isKarma) {
|
|
178
|
+
// @ts-expect-error -- this.currentTest being undefined would be a bug
|
|
179
|
+
const testPath = `"${this.currentTest.fullTitle()}"`;
|
|
180
|
+
|
|
181
|
+
error.message += `\n\nin ${testPath}`;
|
|
182
|
+
throw error;
|
|
183
|
+
} else {
|
|
184
|
+
// @ts-expect-error -- this.test being undefined would be a bug
|
|
185
|
+
this.test.error(error);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
return mochaHooks;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @param {Mocha} Mocha
|
|
195
|
+
* @returns MochaHooks
|
|
196
|
+
*/
|
|
197
|
+
function createMochaHooks(Mocha) {
|
|
198
|
+
const warnHooks = createUnexpectedConsoleMessagesHooks(Mocha, 'warn', 'toWarnDev');
|
|
199
|
+
const errorHooks = createUnexpectedConsoleMessagesHooks(Mocha, 'error', 'toErrorDev');
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
beforeAll: [...warnHooks.beforeAll, ...errorHooks.beforeAll],
|
|
203
|
+
afterAll: [...warnHooks.afterAll, ...errorHooks.afterAll],
|
|
204
|
+
beforeEach: [...warnHooks.beforeEach, ...errorHooks.beforeEach],
|
|
205
|
+
afterEach: [...warnHooks.afterEach, ...errorHooks.afterEach],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = { createMochaHooks };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as Mocha from 'mocha';
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { stub } from 'sinon';
|
|
5
|
+
import { createMochaHooks } from './mochaHooks';
|
|
6
|
+
import { createRenderer, act } from './createRenderer';
|
|
7
|
+
|
|
8
|
+
describe('mochaHooks', () => {
|
|
9
|
+
// one block per hook.
|
|
10
|
+
describe('afterEach', () => {
|
|
11
|
+
describe('on unexpected console.(warn|error) in afterEach', function suite() {
|
|
12
|
+
const mochaHooks = createMochaHooks(Mocha);
|
|
13
|
+
|
|
14
|
+
beforeEach(function beforeEachHook() {
|
|
15
|
+
mochaHooks.beforeAll.forEach((beforeAllMochaHook) => {
|
|
16
|
+
beforeAllMochaHook.call(this);
|
|
17
|
+
});
|
|
18
|
+
mochaHooks.beforeEach.forEach((beforeEachMochaHook) => {
|
|
19
|
+
beforeEachMochaHook.call(this);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('throws an error', () => {
|
|
24
|
+
console.warn('unexpected warning');
|
|
25
|
+
console.error('unexpected error');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(function afterEachHook() {
|
|
29
|
+
const errorStub = stub(this.test, 'error');
|
|
30
|
+
mochaHooks.afterEach.forEach((afterEachMochaHook) => {
|
|
31
|
+
afterEachMochaHook.call(this);
|
|
32
|
+
});
|
|
33
|
+
mochaHooks.afterAll.forEach((afterAllMochaHook) => {
|
|
34
|
+
afterAllMochaHook.call(this);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(errorStub.callCount).to.equal(2);
|
|
38
|
+
expect(String(errorStub.firstCall.args[0])).to.include(
|
|
39
|
+
'console.warn message #1:\n unexpected warning\n\nStack:',
|
|
40
|
+
);
|
|
41
|
+
expect(String(errorStub.secondCall.args[0])).to.include(
|
|
42
|
+
'console.error message #1:\n unexpected error\n\nStack:',
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// TODO: May not be relevant in React 18
|
|
48
|
+
describe('when having missing act() warnings by component', () => {
|
|
49
|
+
const mochaHooks = createMochaHooks(Mocha);
|
|
50
|
+
// missing act warnings only happen in StrictMode
|
|
51
|
+
const { render } = createRenderer({ strict: true });
|
|
52
|
+
|
|
53
|
+
beforeEach(function beforeEachHook() {
|
|
54
|
+
mochaHooks.beforeAll.forEach((beforeAllMochaHook) => {
|
|
55
|
+
beforeAllMochaHook.call(this);
|
|
56
|
+
});
|
|
57
|
+
mochaHooks.beforeEach.forEach((beforeEachMochaHook) => {
|
|
58
|
+
beforeEachMochaHook.call(this);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('dedupes them', () => {
|
|
63
|
+
const Child = React.forwardRef(function Child() {
|
|
64
|
+
React.useEffect(() => {});
|
|
65
|
+
React.useEffect(() => {});
|
|
66
|
+
return null;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
let unsafeSetState;
|
|
70
|
+
function Parent() {
|
|
71
|
+
const [state, setState] = React.useState(0);
|
|
72
|
+
unsafeSetState = setState;
|
|
73
|
+
|
|
74
|
+
React.useEffect(() => {});
|
|
75
|
+
React.useEffect(() => {});
|
|
76
|
+
|
|
77
|
+
return <Child rerender={state} />;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
render(<Parent />);
|
|
81
|
+
|
|
82
|
+
// not wrapped in act()
|
|
83
|
+
unsafeSetState(1);
|
|
84
|
+
// make sure effects are flushed
|
|
85
|
+
act(() => {});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
afterEach(function afterEachHook() {
|
|
89
|
+
const errorStub = stub(this.test, 'error');
|
|
90
|
+
mochaHooks.afterEach.forEach((afterEachMochaHook) => {
|
|
91
|
+
afterEachMochaHook.call(this);
|
|
92
|
+
});
|
|
93
|
+
mochaHooks.afterAll.forEach((afterAllMochaHook) => {
|
|
94
|
+
afterAllMochaHook.call(this);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(errorStub.callCount).to.equal(1);
|
|
98
|
+
const error = String(errorStub.firstCall.args[0]);
|
|
99
|
+
expect(
|
|
100
|
+
error.match(/An update to Parent inside a test was not wrapped in act/g),
|
|
101
|
+
).to.have.lengthOf(1);
|
|
102
|
+
expect(
|
|
103
|
+
error.match(/An update to Parent ran an effect, but was not wrapped in act/g) ?? [],
|
|
104
|
+
).to.have.lengthOf(React.startTransition !== undefined ? 0 : 1);
|
|
105
|
+
expect(
|
|
106
|
+
error.match(
|
|
107
|
+
/An update to ForwardRef\(Child\) ran an effect, but was not wrapped in act/g,
|
|
108
|
+
) ?? [],
|
|
109
|
+
).to.have.lengthOf(React.startTransition !== undefined ? 0 : 1);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
package/src/setup.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const testingLibrary = require('@testing-library/dom');
|
|
2
|
+
const createDOM = require('./createDOM');
|
|
3
|
+
|
|
4
|
+
createDOM();
|
|
5
|
+
require('./init');
|
|
6
|
+
|
|
7
|
+
testingLibrary.configure({
|
|
8
|
+
// JSDOM logs errors otherwise on `getComputedStyle(element, pseudoElement)` calls.
|
|
9
|
+
computedStyleSupportsPseudoElements: false,
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// In playwright we sometimes write functions that are executed in the browser environment.
|
|
2
|
+
// Since we can't tell babel which parts of the code are executed in which environment we can only disable it entirely.
|
|
3
|
+
// We're only stripping types and compiling ES6 to CommonJS modules which should be safe.
|
|
4
|
+
|
|
5
|
+
require('@babel/register')({
|
|
6
|
+
configFile: false,
|
|
7
|
+
extensions: ['.js', '.ts', '.tsx'],
|
|
8
|
+
presets: [require.resolve('@babel/preset-typescript')],
|
|
9
|
+
plugins: [require.resolve('@babel/plugin-transform-modules-commonjs')],
|
|
10
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const testingLibrary = require('@testing-library/dom');
|
|
2
|
+
const Mocha = require('mocha');
|
|
3
|
+
const createDOM = require('./createDOM');
|
|
4
|
+
const { createMochaHooks } = require('./mochaHooks');
|
|
5
|
+
|
|
6
|
+
// Enable missing act warnings: https://github.com/reactwg/react-18/discussions/102
|
|
7
|
+
global.jest = null;
|
|
8
|
+
global.IS_REACT_ACT_ENVIRONMENT = true;
|
|
9
|
+
|
|
10
|
+
createDOM();
|
|
11
|
+
require('./init');
|
|
12
|
+
|
|
13
|
+
testingLibrary.configure({
|
|
14
|
+
// JSDOM logs errors otherwise on `getComputedStyle(element, pseudoElement)` calls.
|
|
15
|
+
computedStyleSupportsPseudoElements: false,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const mochaHooks = createMochaHooks(Mocha);
|
|
19
|
+
|
|
20
|
+
module.exports = { mochaHooks };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/* eslint-env mocha */
|
|
2
|
+
import { createMochaHooks } from './mochaHooks';
|
|
3
|
+
|
|
4
|
+
const mochaHooks = createMochaHooks(window.Mocha);
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Emit events from `DispatchingProfiler` as `browser_info` karma events.
|
|
8
|
+
*/
|
|
9
|
+
function handleReactProfilerResults(event) {
|
|
10
|
+
// Only own properties are persisted when sending the object.
|
|
11
|
+
const info = { type: event.type, detail: event.detail };
|
|
12
|
+
// eslint-disable-next-line no-underscore-dangle -- public: http://karma-runner.github.io/5.2/dev/plugins.html
|
|
13
|
+
window.__karma__.info(info);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
before(function beforeAllHook() {
|
|
17
|
+
mochaHooks.beforeAll.forEach((mochaHook) => {
|
|
18
|
+
mochaHook.call(this);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
window.addEventListener('reactProfilerResults', handleReactProfilerResults);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
after(function afterAllHook() {
|
|
25
|
+
window.removeEventListener('reactProfilerResults', handleReactProfilerResults);
|
|
26
|
+
|
|
27
|
+
mochaHooks.afterAll.forEach((mochaHook) => {
|
|
28
|
+
mochaHook.call(this);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
beforeEach(function beforeEachHook() {
|
|
33
|
+
mochaHooks.beforeEach.forEach((mochaHook) => {
|
|
34
|
+
mochaHook.call(this);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(function afterEachHook() {
|
|
39
|
+
mochaHooks.afterEach.forEach((mochaHook) => {
|
|
40
|
+
mochaHook.call(this);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Ensure that uncaught exceptions between tests result in the tests failing.
|
|
45
|
+
// This works around an issue with mocha / karma-mocha, see
|
|
46
|
+
// https://github.com/karma-runner/karma-mocha/issues/227
|
|
47
|
+
let pendingError = null;
|
|
48
|
+
let pendingErrorNotice = null;
|
|
49
|
+
|
|
50
|
+
window.addEventListener('error', (event) => {
|
|
51
|
+
pendingError = event.error;
|
|
52
|
+
pendingErrorNotice = 'An uncaught exception was thrown between tests';
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
56
|
+
pendingError = event.reason;
|
|
57
|
+
pendingErrorNotice = 'An uncaught promise rejection occurred between tests';
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
if (pendingError) {
|
|
62
|
+
console.error(pendingErrorNotice);
|
|
63
|
+
throw pendingError;
|
|
64
|
+
}
|
|
65
|
+
});
|
package/src/until.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function shallowRecursively(wrapper, selector, { context, ...other }) {
|
|
2
|
+
if (wrapper.isEmptyRender() || typeof wrapper.getElement().type === 'string') {
|
|
3
|
+
return wrapper;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
let newContext = context;
|
|
7
|
+
|
|
8
|
+
const instance = wrapper.root().instance();
|
|
9
|
+
// The instance can be null with a stateless functional component and react >= 16.
|
|
10
|
+
if (instance && instance.getChildContext) {
|
|
11
|
+
newContext = {
|
|
12
|
+
...context,
|
|
13
|
+
...instance.getChildContext(),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const nextWrapper = wrapper.shallow({ context: newContext, ...other });
|
|
18
|
+
|
|
19
|
+
if (selector && wrapper.is(selector)) {
|
|
20
|
+
return nextWrapper;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return shallowRecursively(nextWrapper, selector, { context: newContext });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function until(selector, options = {}) {
|
|
27
|
+
return this.single('until', () => shallowRecursively(this, selector, options));
|
|
28
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import { shallow } from 'enzyme';
|
|
5
|
+
import until from './until';
|
|
6
|
+
|
|
7
|
+
function Div() {
|
|
8
|
+
return <div />;
|
|
9
|
+
}
|
|
10
|
+
const hoc = (Component) =>
|
|
11
|
+
function Wrapper() {
|
|
12
|
+
return <Component />;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe('until', () => {
|
|
16
|
+
it('shallow renders the current wrapper one level deep', () => {
|
|
17
|
+
const EnhancedDiv = hoc(Div);
|
|
18
|
+
const wrapper = until.call(shallow(<EnhancedDiv />), 'Div');
|
|
19
|
+
expect(wrapper.contains(<div />)).to.equal(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('shallow renders the current wrapper several levels deep', () => {
|
|
23
|
+
const EnhancedDiv = hoc(hoc(hoc(Div)));
|
|
24
|
+
const wrapper = until.call(shallow(<EnhancedDiv />), 'Div');
|
|
25
|
+
expect(wrapper.contains(<div />)).to.equal(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('stops shallow rendering when the wrapper is empty', () => {
|
|
29
|
+
const nullHoc = () => () => null;
|
|
30
|
+
const EnhancedDiv = nullHoc();
|
|
31
|
+
const wrapper = until.call(shallow(<EnhancedDiv />), 'Div');
|
|
32
|
+
expect(wrapper.html()).to.equal(null);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('shallow renders as much as possible when no selector is provided', () => {
|
|
36
|
+
const EnhancedDiv = hoc(hoc(Div));
|
|
37
|
+
const wrapper = until.call(shallow(<EnhancedDiv />));
|
|
38
|
+
expect(wrapper.contains(<div />)).to.equal(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('shallow renders the current wrapper even if the selector never matches', () => {
|
|
42
|
+
const EnhancedDiv = hoc(Div);
|
|
43
|
+
const wrapper = until.call(shallow(<EnhancedDiv />), 'NotDiv');
|
|
44
|
+
expect(wrapper.contains(<div />)).to.equal(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('stops shallow rendering when it encounters a HTML element', () => {
|
|
48
|
+
const wrapper = until.call(
|
|
49
|
+
shallow(
|
|
50
|
+
<div>
|
|
51
|
+
<Div />
|
|
52
|
+
</div>,
|
|
53
|
+
),
|
|
54
|
+
'Div',
|
|
55
|
+
);
|
|
56
|
+
expect(
|
|
57
|
+
wrapper.contains(
|
|
58
|
+
<div>
|
|
59
|
+
<Div />
|
|
60
|
+
</div>,
|
|
61
|
+
),
|
|
62
|
+
).to.equal(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('throws when until called on an empty wrapper', () => {
|
|
66
|
+
expect(() => {
|
|
67
|
+
until.call(shallow(<Div />).find('Foo'), 'div');
|
|
68
|
+
}).to.throw(Error);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('shallow renders non-root wrappers', () => {
|
|
72
|
+
function Container() {
|
|
73
|
+
return (
|
|
74
|
+
<div>
|
|
75
|
+
<Div />
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const wrapper = until.call(shallow(<Container />).find(Div));
|
|
80
|
+
expect(wrapper.contains(<div />)).to.equal(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// eslint-disable-next-line react/prefer-stateless-function
|
|
84
|
+
class Foo extends React.Component {
|
|
85
|
+
render() {
|
|
86
|
+
return <Div />;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Foo.contextTypes = {
|
|
91
|
+
quux: PropTypes.bool.isRequired,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
it('context propagation passes down context from the root component', () => {
|
|
95
|
+
const EnhancedFoo = hoc(Foo);
|
|
96
|
+
const options = { context: { quux: true } };
|
|
97
|
+
const wrapper = until.call(shallow(<EnhancedFoo />, options), 'Foo', options);
|
|
98
|
+
expect(wrapper.context('quux')).to.equal(true);
|
|
99
|
+
expect(wrapper.contains(<Div />)).to.equal(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
class Bar extends React.Component {
|
|
103
|
+
static childContextTypes = { quux: PropTypes.bool };
|
|
104
|
+
|
|
105
|
+
getChildContext() {
|
|
106
|
+
return { quux: true };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
render() {
|
|
110
|
+
return <Foo />;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
it('context propagation passes down context from an intermediary component', () => {
|
|
115
|
+
const EnhancedBar = hoc(Bar);
|
|
116
|
+
const wrapper = until.call(shallow(<EnhancedBar />), 'Foo');
|
|
117
|
+
expect(wrapper.context('quux')).to.equal(true);
|
|
118
|
+
expect(wrapper.contains(<Div />)).to.equal(true);
|
|
119
|
+
});
|
|
120
|
+
});
|