@mui/internal-test-utils 2.0.15 → 2.0.18-canary.0

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.
Files changed (197) hide show
  1. package/CHANGELOG.md +11 -3
  2. package/LICENSE +1 -1
  3. package/README.md +52 -6
  4. package/chaiPlugin.d.ts +3 -0
  5. package/chaiPlugin.js +300 -0
  6. package/{src/chai.types.ts → chaiTypes.d.ts} +7 -40
  7. package/chaiTypes.js +5 -0
  8. package/{build/components.d.ts → components.d.ts} +18 -19
  9. package/components.js +65 -0
  10. package/configure.d.ts +9 -0
  11. package/configure.js +16 -0
  12. package/createDescribe.d.ts +14 -0
  13. package/createDescribe.js +33 -0
  14. package/createRenderer.d.ts +216 -0
  15. package/createRenderer.js +447 -0
  16. package/describeConformance.d.ts +196 -0
  17. package/describeConformance.js +1043 -0
  18. package/env.d.ts +9 -0
  19. package/env.js +21 -0
  20. package/esm/chaiPlugin.d.ts +3 -0
  21. package/esm/chaiPlugin.js +293 -0
  22. package/esm/chaiTypes.d.ts +74 -0
  23. package/esm/chaiTypes.js +3 -0
  24. package/esm/components.d.ts +35 -0
  25. package/esm/components.js +57 -0
  26. package/esm/configure.d.ts +9 -0
  27. package/esm/configure.js +9 -0
  28. package/esm/createDescribe.d.ts +14 -0
  29. package/esm/createDescribe.js +27 -0
  30. package/esm/createRenderer.d.ts +216 -0
  31. package/esm/createRenderer.js +396 -0
  32. package/esm/describeConformance.d.ts +196 -0
  33. package/esm/describeConformance.js +1029 -0
  34. package/esm/env.d.ts +9 -0
  35. package/esm/env.js +13 -0
  36. package/esm/flushMicrotasks.d.ts +1 -0
  37. package/esm/flushMicrotasks.js +4 -0
  38. package/{build → esm}/focusVisible.d.ts +1 -2
  39. package/{src/focusVisible.ts → esm/focusVisible.js} +10 -9
  40. package/esm/index.d.ts +10 -0
  41. package/esm/index.js +18 -0
  42. package/esm/initMatchers.d.ts +5 -0
  43. package/esm/initMatchers.js +9 -0
  44. package/esm/initPlaywrightMatchers.d.ts +24 -0
  45. package/esm/initPlaywrightMatchers.js +40 -0
  46. package/esm/package.json +1 -0
  47. package/esm/setupVitest.d.ts +8 -0
  48. package/esm/setupVitest.js +94 -0
  49. package/flushMicrotasks.d.ts +1 -0
  50. package/flushMicrotasks.js +10 -0
  51. package/focusVisible.d.ts +7 -0
  52. package/focusVisible.js +44 -0
  53. package/index.d.ts +10 -0
  54. package/index.js +125 -0
  55. package/initMatchers.d.ts +5 -0
  56. package/initMatchers.js +29 -0
  57. package/initPlaywrightMatchers.d.ts +24 -0
  58. package/initPlaywrightMatchers.js +42 -0
  59. package/package.json +106 -52
  60. package/setupVitest.d.ts +8 -0
  61. package/setupVitest.js +104 -0
  62. package/build/.tsbuildinfo +0 -1
  63. package/build/KarmaReporterReactProfiler.d.ts +0 -51
  64. package/build/KarmaReporterReactProfiler.d.ts.map +0 -1
  65. package/build/KarmaReporterReactProfiler.js +0 -66
  66. package/build/KarmaReporterReactProfiler.js.map +0 -1
  67. package/build/chai.types.d.ts +0 -75
  68. package/build/chai.types.d.ts.map +0 -1
  69. package/build/chai.types.js +0 -3
  70. package/build/chai.types.js.map +0 -1
  71. package/build/chaiPlugin.d.ts +0 -5
  72. package/build/chaiPlugin.d.ts.map +0 -1
  73. package/build/chaiPlugin.js +0 -416
  74. package/build/chaiPlugin.js.map +0 -1
  75. package/build/components.d.ts.map +0 -1
  76. package/build/components.js +0 -88
  77. package/build/components.js.map +0 -1
  78. package/build/createDOM.d.ts +0 -3
  79. package/build/createDOM.d.ts.map +0 -1
  80. package/build/createDOM.js +0 -60
  81. package/build/createDOM.js.map +0 -1
  82. package/build/createDescribe.d.ts +0 -8
  83. package/build/createDescribe.d.ts.map +0 -1
  84. package/build/createDescribe.js +0 -22
  85. package/build/createDescribe.js.map +0 -1
  86. package/build/createRenderer.d.ts +0 -215
  87. package/build/createRenderer.d.ts.map +0 -1
  88. package/build/createRenderer.js +0 -564
  89. package/build/createRenderer.js.map +0 -1
  90. package/build/createRenderer.test.d.ts +0 -2
  91. package/build/createRenderer.test.d.ts.map +0 -1
  92. package/build/createRenderer.test.js +0 -58
  93. package/build/createRenderer.test.js.map +0 -1
  94. package/build/describeConformance.d.ts +0 -201
  95. package/build/describeConformance.d.ts.map +0 -1
  96. package/build/describeConformance.js +0 -859
  97. package/build/describeConformance.js.map +0 -1
  98. package/build/describeSkipIf.d.ts +0 -4
  99. package/build/describeSkipIf.d.ts.map +0 -1
  100. package/build/describeSkipIf.js +0 -10
  101. package/build/describeSkipIf.js.map +0 -1
  102. package/build/fireDiscreteEvent.d.ts +0 -7
  103. package/build/fireDiscreteEvent.d.ts.map +0 -1
  104. package/build/fireDiscreteEvent.js +0 -77
  105. package/build/fireDiscreteEvent.js.map +0 -1
  106. package/build/flushMicrotasks.d.ts +0 -2
  107. package/build/flushMicrotasks.d.ts.map +0 -1
  108. package/build/flushMicrotasks.js +0 -8
  109. package/build/flushMicrotasks.js.map +0 -1
  110. package/build/focusVisible.d.ts.map +0 -1
  111. package/build/focusVisible.js +0 -38
  112. package/build/focusVisible.js.map +0 -1
  113. package/build/index.d.ts +0 -18
  114. package/build/index.d.ts.map +0 -1
  115. package/build/index.js +0 -68
  116. package/build/index.js.map +0 -1
  117. package/build/init.d.ts +0 -2
  118. package/build/init.d.ts.map +0 -1
  119. package/build/init.js +0 -46
  120. package/build/init.js.map +0 -1
  121. package/build/initMatchers.d.ts +0 -2
  122. package/build/initMatchers.d.ts.map +0 -1
  123. package/build/initMatchers.js +0 -45
  124. package/build/initMatchers.js.map +0 -1
  125. package/build/initMatchers.test.d.ts +0 -2
  126. package/build/initMatchers.test.d.ts.map +0 -1
  127. package/build/initMatchers.test.js +0 -101
  128. package/build/initMatchers.test.js.map +0 -1
  129. package/build/initPlaywrightMatchers.d.ts +0 -25
  130. package/build/initPlaywrightMatchers.d.ts.map +0 -1
  131. package/build/initPlaywrightMatchers.js +0 -73
  132. package/build/initPlaywrightMatchers.js.map +0 -1
  133. package/build/mochaHooks.d.ts +0 -24
  134. package/build/mochaHooks.d.ts.map +0 -1
  135. package/build/mochaHooks.js +0 -165
  136. package/build/mochaHooks.js.map +0 -1
  137. package/build/mochaHooks.test.d.ts +0 -2
  138. package/build/mochaHooks.test.d.ts.map +0 -1
  139. package/build/mochaHooks.test.js +0 -128
  140. package/build/mochaHooks.test.js.map +0 -1
  141. package/build/reactMajor.d.ts +0 -3
  142. package/build/reactMajor.d.ts.map +0 -1
  143. package/build/reactMajor.js +0 -38
  144. package/build/reactMajor.js.map +0 -1
  145. package/build/setup.d.ts +0 -2
  146. package/build/setup.d.ts.map +0 -1
  147. package/build/setup.js +0 -10
  148. package/build/setup.js.map +0 -1
  149. package/build/setupBabel.d.ts +0 -2
  150. package/build/setupBabel.d.ts.map +0 -1
  151. package/build/setupBabel.js +0 -5
  152. package/build/setupBabel.js.map +0 -1
  153. package/build/setupBabelPlaywright.d.ts +0 -2
  154. package/build/setupBabelPlaywright.d.ts.map +0 -1
  155. package/build/setupBabelPlaywright.js +0 -14
  156. package/build/setupBabelPlaywright.js.map +0 -1
  157. package/build/setupJSDOM.d.ts +0 -7
  158. package/build/setupJSDOM.d.ts.map +0 -1
  159. package/build/setupJSDOM.js +0 -17
  160. package/build/setupJSDOM.js.map +0 -1
  161. package/build/setupKarma.d.ts +0 -2
  162. package/build/setupKarma.d.ts.map +0 -1
  163. package/build/setupKarma.js +0 -56
  164. package/build/setupKarma.js.map +0 -1
  165. package/build/setupVitest.d.ts +0 -2
  166. package/build/setupVitest.d.ts.map +0 -1
  167. package/build/setupVitest.js +0 -131
  168. package/build/setupVitest.js.map +0 -1
  169. package/src/KarmaReporterReactProfiler.js +0 -82
  170. package/src/chai-augmentation.d.ts +0 -8
  171. package/src/chaiPlugin.ts +0 -515
  172. package/src/components.tsx +0 -61
  173. package/src/createDOM.d.ts +0 -9
  174. package/src/createDOM.js +0 -67
  175. package/src/createDescribe.ts +0 -31
  176. package/src/createRenderer.test.js +0 -31
  177. package/src/createRenderer.tsx +0 -808
  178. package/src/describeConformance.tsx +0 -1257
  179. package/src/describeSkipIf.tsx +0 -11
  180. package/src/fireDiscreteEvent.ts +0 -76
  181. package/src/flushMicrotasks.ts +0 -5
  182. package/src/index.ts +0 -25
  183. package/src/init.js +0 -11
  184. package/src/initMatchers.test.js +0 -124
  185. package/src/initMatchers.ts +0 -7
  186. package/src/initPlaywrightMatchers.ts +0 -101
  187. package/src/mochaHooks.js +0 -200
  188. package/src/mochaHooks.test.js +0 -116
  189. package/src/reactMajor.ts +0 -3
  190. package/src/setup.js +0 -10
  191. package/src/setupBabel.js +0 -3
  192. package/src/setupBabelPlaywright.js +0 -13
  193. package/src/setupJSDOM.js +0 -20
  194. package/src/setupKarma.js +0 -65
  195. package/src/setupVitest.ts +0 -117
  196. package/tsconfig.build.json +0 -16
  197. package/tsconfig.json +0 -17
@@ -0,0 +1,1029 @@
1
+ import { expect } from 'chai';
2
+ import * as React from 'react';
3
+ import { describe, it, afterAll, afterEach, beforeEach } from 'vitest';
4
+ import createDescribe from "./createDescribe.js";
5
+ import { isJsdom } from "./env.js";
6
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ function capitalize(string) {
8
+ return string.charAt(0).toUpperCase() + string.slice(1);
9
+ }
10
+ /**
11
+ * Glossary
12
+ * - root component:
13
+ * - renders the outermost host component
14
+ * - has the `root` class if the component has one
15
+ * - excess props are spread to this component
16
+ * - has the type of `inheritComponent`
17
+ */
18
+
19
+ export function randomStringValue() {
20
+ return `s${Math.random().toString(36).slice(2)}`;
21
+ }
22
+ function throwMissingPropError(field) {
23
+ throw new Error(`missing "${field}" in options
24
+
25
+ > describeConformance(element, () => options)
26
+ `);
27
+ }
28
+
29
+ /**
30
+ * MUI components have a `className` prop. The `className` is applied to
31
+ * the root component.
32
+ */
33
+ export function testClassName(element, getOptions) {
34
+ it('applies the className to the root component', async () => {
35
+ const {
36
+ render
37
+ } = getOptions();
38
+ if (!render) {
39
+ throwMissingPropError('render');
40
+ }
41
+ const className = randomStringValue();
42
+ const testId = randomStringValue();
43
+ const {
44
+ getByTestId
45
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
46
+ className,
47
+ 'data-testid': testId
48
+ }));
49
+ expect(getByTestId(testId)).to.have.class(className);
50
+ });
51
+ }
52
+
53
+ /**
54
+ * MUI components have a `component` prop that allows rendering a different
55
+ * Component from @inheritComponent
56
+ */
57
+ export function testComponentProp(element, getOptions) {
58
+ describe('prop: component', () => {
59
+ it('can render another root component with the `component` prop', async () => {
60
+ const {
61
+ render,
62
+ testComponentPropWith: component = 'em'
63
+ } = getOptions();
64
+ if (!render) {
65
+ throwMissingPropError('render');
66
+ }
67
+ const testId = randomStringValue();
68
+ if (typeof component === 'string') {
69
+ const {
70
+ getByTestId
71
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
72
+ component,
73
+ 'data-testid': testId
74
+ }));
75
+ expect(getByTestId(testId)).not.to.equal(null);
76
+ expect(getByTestId(testId).nodeName.toLowerCase()).to.eq(component);
77
+ } else {
78
+ const componentWithTestId = props => /*#__PURE__*/React.createElement(component, {
79
+ ...props,
80
+ 'data-testid': testId
81
+ });
82
+ const {
83
+ getByTestId
84
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
85
+ component: componentWithTestId
86
+ }));
87
+ expect(getByTestId(testId)).not.to.equal(null);
88
+ }
89
+ });
90
+ });
91
+ }
92
+
93
+ /**
94
+ * MUI components spread additional props to its root.
95
+ */
96
+ export function testPropsSpread(element, getOptions) {
97
+ it(`spreads props to the root component`, async () => {
98
+ // type def in ConformanceOptions
99
+ const {
100
+ render
101
+ } = getOptions();
102
+ if (!render) {
103
+ throwMissingPropError('render');
104
+ }
105
+ const testProp = 'data-test-props-spread';
106
+ const value = randomStringValue();
107
+ const testId = randomStringValue();
108
+ const {
109
+ getByTestId
110
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
111
+ [testProp]: value,
112
+ 'data-testid': testId
113
+ }));
114
+ expect(getByTestId(testId)).to.have.attribute(testProp, value);
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Tests that the `ref` of a component will return the correct instance
120
+ *
121
+ * This is determined by a given constructor i.e. a React.Component or HTMLElement for
122
+ * components that forward their ref and attach it to a host component.
123
+ */
124
+ export function describeRef(element, getOptions) {
125
+ describe('ref', () => {
126
+ it(`attaches the ref`, async () => {
127
+ // type def in ConformanceOptions
128
+ const {
129
+ render,
130
+ refInstanceof
131
+ } = getOptions();
132
+ if (!render) {
133
+ throwMissingPropError('render');
134
+ }
135
+ const ref = /*#__PURE__*/React.createRef();
136
+ await render(/*#__PURE__*/React.cloneElement(element, {
137
+ ref
138
+ }));
139
+ expect(ref.current).to.be.instanceof(refInstanceof);
140
+ });
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Tests that the root component has the root class
146
+ */
147
+ export function testRootClass(element, getOptions) {
148
+ it('applies the root class to the root component if it has this class', async () => {
149
+ const {
150
+ classes,
151
+ render,
152
+ skip
153
+ } = getOptions();
154
+ if (classes.root == null) {
155
+ return;
156
+ }
157
+ const className = randomStringValue();
158
+ const classesRootClassname = randomStringValue();
159
+ const {
160
+ container
161
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
162
+ className,
163
+ classes: {
164
+ ...classes,
165
+ root: `${classes.root} ${classesRootClassname}`
166
+ }
167
+ }));
168
+
169
+ // we established that the root component renders the outermost host previously. We immediately
170
+ // jump to the host component because some components pass the `root` class
171
+ // to the `classes` prop of the root component.
172
+ // https://github.com/mui/material-ui/blob/f9896bcd129a1209153106296b3d2487547ba205/packages/material-ui/src/OutlinedInput/OutlinedInput.js#L101
173
+ expect(container.firstChild).to.have.class(className);
174
+ expect(container.firstChild).to.have.class(classes.root);
175
+ expect(document.querySelectorAll(`.${classes.root}`).length).to.equal(1);
176
+
177
+ // classes test only for @mui/material
178
+ if (!skip || !skip.includes('classesRoot')) {
179
+ // Test that classes prop works
180
+ expect(container.firstChild).to.have.class(classesRootClassname);
181
+
182
+ // Test that `classes` does not spread to DOM
183
+ expect(document.querySelectorAll('[classes]').length).to.equal(0);
184
+ }
185
+ });
186
+ }
187
+ function forEachSlot(slots, callback) {
188
+ if (!slots) {
189
+ return;
190
+ }
191
+ const slotNames = Object.keys(slots);
192
+ slotNames.forEach(slotName => {
193
+ const slot = slots[slotName];
194
+ callback(slotName, slot);
195
+ });
196
+ }
197
+ function testSlotsProp(element, getOptions) {
198
+ const {
199
+ render,
200
+ slots,
201
+ testLegacyComponentsProp
202
+ } = getOptions();
203
+ const CustomComponent = /*#__PURE__*/React.forwardRef(({
204
+ className,
205
+ children
206
+ }, ref) => /*#__PURE__*/_jsx("i", {
207
+ className: className,
208
+ ref: ref,
209
+ "data-testid": "custom",
210
+ children: children
211
+ }));
212
+ if (process.env.NODE_ENV !== "production") CustomComponent.displayName = "CustomComponent";
213
+ forEachSlot(slots, (slotName, slotOptions) => {
214
+ it(`allows overriding the ${slotName} slot with a component using the slots.${slotName} prop`, async () => {
215
+ if (!render) {
216
+ throwMissingPropError('render');
217
+ }
218
+ const slotComponent = slotOptions.testWithComponent ?? CustomComponent;
219
+ const components = {
220
+ [slotName]: slotComponent
221
+ };
222
+ const {
223
+ queryByTestId
224
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
225
+ slots: components
226
+ }));
227
+ const renderedElement = queryByTestId('custom');
228
+ expect(renderedElement).not.to.equal(null);
229
+ if (slotOptions.expectedClassName) {
230
+ expect(renderedElement).to.have.class(slotOptions.expectedClassName);
231
+ }
232
+ });
233
+ if (slotOptions.testWithElement !== null) {
234
+ it(`allows overriding the ${slotName} slot with an element using the slots.${slotName} prop`, async () => {
235
+ if (!render) {
236
+ throwMissingPropError('render');
237
+ }
238
+ const slotElement = slotOptions.testWithElement ?? 'i';
239
+ const components = {
240
+ [slotName]: slotElement
241
+ };
242
+ const slotProps = {
243
+ [slotName]: {
244
+ 'data-testid': 'customized'
245
+ }
246
+ };
247
+ const {
248
+ queryByTestId
249
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
250
+ slots: components,
251
+ slotProps
252
+ }));
253
+ const renderedElement = queryByTestId('customized');
254
+ expect(renderedElement).not.to.equal(null);
255
+ if (typeof slotElement === 'string') {
256
+ expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement);
257
+ }
258
+ if (slotOptions.expectedClassName) {
259
+ expect(renderedElement).to.have.class(slotOptions.expectedClassName);
260
+ }
261
+ });
262
+ }
263
+
264
+ // For testing Material UI components v5, and v6. Likely to be removed in a future major release.
265
+ if (testLegacyComponentsProp === true || Array.isArray(testLegacyComponentsProp) && testLegacyComponentsProp.includes(slotName)) {
266
+ it(`allows overriding the ${slotName} slot with a component using the components.${capitalize(slotName)} prop`, async () => {
267
+ if (!render) {
268
+ throwMissingPropError('render');
269
+ }
270
+ const slotComponent = slotOptions.testWithComponent ?? CustomComponent;
271
+ const components = {
272
+ [capitalize(slotName)]: slotComponent
273
+ };
274
+ const {
275
+ queryByTestId
276
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
277
+ components
278
+ }));
279
+ const renderedElement = queryByTestId('custom');
280
+ expect(renderedElement).not.to.equal(null);
281
+ if (slotOptions.expectedClassName) {
282
+ expect(renderedElement).to.have.class(slotOptions.expectedClassName);
283
+ }
284
+ });
285
+ it(`prioritizes the 'slots.${slotName}' over components.${capitalize(slotName)} if both are defined`, async () => {
286
+ if (!render) {
287
+ throwMissingPropError('render');
288
+ }
289
+ const ComponentForComponentsProp = /*#__PURE__*/React.forwardRef(({
290
+ children
291
+ }, ref) => {
292
+ const SlotComponent = slotOptions.testWithComponent ?? 'div';
293
+ return /*#__PURE__*/_jsx(SlotComponent, {
294
+ ref: ref,
295
+ "data-testid": "from-components",
296
+ children: children
297
+ });
298
+ });
299
+ if (process.env.NODE_ENV !== "production") ComponentForComponentsProp.displayName = "ComponentForComponentsProp";
300
+ const ComponentForSlotsProp = /*#__PURE__*/React.forwardRef(({
301
+ children
302
+ }, ref) => {
303
+ const SlotComponent = slotOptions.testWithComponent ?? 'div';
304
+ return /*#__PURE__*/_jsx(SlotComponent, {
305
+ ref: ref,
306
+ "data-testid": "from-slots",
307
+ children: children
308
+ });
309
+ });
310
+ if (process.env.NODE_ENV !== "production") ComponentForSlotsProp.displayName = "ComponentForSlotsProp";
311
+ const components = {
312
+ [capitalize(slotName)]: ComponentForComponentsProp
313
+ };
314
+ const slotOverrides = {
315
+ [slotName]: ComponentForSlotsProp
316
+ };
317
+ const {
318
+ queryByTestId
319
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
320
+ components,
321
+ slots: slotOverrides
322
+ }));
323
+ expect(queryByTestId('from-slots')).not.to.equal(null);
324
+ expect(queryByTestId('from-components')).to.equal(null);
325
+ });
326
+ if (slotOptions.testWithElement !== null) {
327
+ it(`allows overriding the ${slotName} slot with an element using the components.${capitalize(slotName)} prop`, async () => {
328
+ if (!render) {
329
+ throwMissingPropError('render');
330
+ }
331
+ const slotElement = slotOptions.testWithElement ?? 'i';
332
+ const components = {
333
+ [capitalize(slotName)]: slotElement
334
+ };
335
+ const componentsProps = {
336
+ [slotName]: {
337
+ 'data-testid': 'customized'
338
+ }
339
+ };
340
+ const {
341
+ queryByTestId
342
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
343
+ components,
344
+ componentsProps
345
+ }));
346
+ const renderedElement = queryByTestId('customized');
347
+ expect(renderedElement).not.to.equal(null);
348
+ if (typeof slotElement === 'string') {
349
+ expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement);
350
+ }
351
+ if (slotOptions.expectedClassName) {
352
+ expect(renderedElement).to.have.class(slotOptions.expectedClassName);
353
+ }
354
+ });
355
+ }
356
+ }
357
+ });
358
+ }
359
+ function testSlotPropsProp(element, getOptions) {
360
+ const {
361
+ render,
362
+ slots,
363
+ testLegacyComponentsProp
364
+ } = getOptions();
365
+ if (!render) {
366
+ throwMissingPropError('render');
367
+ }
368
+ forEachSlot(slots, (slotName, slotOptions) => {
369
+ it(`sets custom properties on the ${slotName} slot's element with the slotProps.${slotName} prop`, async () => {
370
+ const slotProps = {
371
+ [slotName]: {
372
+ 'data-testid': 'custom'
373
+ }
374
+ };
375
+ const {
376
+ queryByTestId
377
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
378
+ slotProps
379
+ }));
380
+ const slotComponent = queryByTestId('custom');
381
+ expect(slotComponent).not.to.equal(null);
382
+ if (slotOptions.expectedClassName) {
383
+ expect(slotComponent).to.have.class(slotOptions.expectedClassName);
384
+ }
385
+ });
386
+ if (slotOptions.expectedClassName) {
387
+ it(`merges the class names provided in slotsProps.${slotName} with the built-in ones`, async () => {
388
+ const slotProps = {
389
+ [slotName]: {
390
+ 'data-testid': 'custom',
391
+ className: randomStringValue()
392
+ }
393
+ };
394
+ const {
395
+ getByTestId
396
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
397
+ slotProps
398
+ }));
399
+ expect(getByTestId('custom')).to.have.class(slotOptions.expectedClassName);
400
+ expect(getByTestId('custom')).to.have.class(slotProps[slotName].className);
401
+ });
402
+ }
403
+ if (testLegacyComponentsProp === true || Array.isArray(testLegacyComponentsProp) && testLegacyComponentsProp.includes(slotName)) {
404
+ it(`sets custom properties on the ${slotName} slot's element with the componentsProps.${slotName} prop`, async () => {
405
+ const componentsProps = {
406
+ [slotName]: {
407
+ 'data-testid': 'custom'
408
+ }
409
+ };
410
+ const {
411
+ queryByTestId
412
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
413
+ componentsProps
414
+ }));
415
+ const slotComponent = queryByTestId('custom');
416
+ expect(slotComponent).not.to.equal(null);
417
+ if (slotOptions.expectedClassName) {
418
+ expect(slotComponent).to.have.class(slotOptions.expectedClassName);
419
+ }
420
+ });
421
+ it(`prioritizes the 'slotProps.${slotName}' over componentsProps.${slotName} if both are defined`, async () => {
422
+ const componentsProps = {
423
+ [slotName]: {
424
+ 'data-testid': 'custom',
425
+ 'data-from-components-props': 'true'
426
+ }
427
+ };
428
+ const slotProps = {
429
+ [slotName]: {
430
+ 'data-testid': 'custom',
431
+ 'data-from-slot-props': 'true'
432
+ }
433
+ };
434
+ const {
435
+ queryByTestId
436
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
437
+ componentsProps,
438
+ slotProps
439
+ }));
440
+ const slotComponent = queryByTestId('custom');
441
+ expect(slotComponent).to.have.attribute('data-from-slot-props', 'true');
442
+ expect(slotComponent).not.to.have.attribute('data-from-components-props');
443
+ });
444
+ }
445
+ });
446
+ }
447
+ function testSlotPropsCallback(element, getOptions) {
448
+ const {
449
+ render,
450
+ slots
451
+ } = getOptions();
452
+ if (!render) {
453
+ throwMissingPropError('render');
454
+ }
455
+ forEachSlot(slots, slotName => {
456
+ it(`sets custom properties on the ${slotName} slot's element with the slotProps.${slotName} callback`, async () => {
457
+ const slotProps = {
458
+ [slotName]: () => ({
459
+ 'data-testid': 'custom'
460
+ })
461
+ };
462
+ const {
463
+ queryByTestId
464
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
465
+ slotProps,
466
+ className: 'custom'
467
+ }));
468
+ const slotComponent = queryByTestId('custom');
469
+ expect(slotComponent).not.to.equal(null);
470
+ });
471
+ });
472
+ }
473
+ function testSlotPropsCallbackWithPropsAsOwnerState(element, getOptions) {
474
+ const {
475
+ render,
476
+ slots
477
+ } = getOptions();
478
+ if (!render) {
479
+ throwMissingPropError('render');
480
+ }
481
+ forEachSlot(slots, slotName => {
482
+ it(`sets custom properties on the ${slotName} slot's element with the slotProps.${slotName} callback using the ownerState`, async () => {
483
+ const slotProps = {
484
+ [slotName]: ownerState => ({
485
+ 'data-testid': ownerState.className
486
+ })
487
+ };
488
+ const {
489
+ queryByTestId
490
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
491
+ slotProps,
492
+ className: 'custom'
493
+ }));
494
+ const slotComponent = queryByTestId('custom', {
495
+ exact: false
496
+ });
497
+ expect(slotComponent).not.to.equal(null);
498
+ });
499
+ });
500
+ }
501
+
502
+ /**
503
+ * MUI components have a `components` prop that allows rendering a different
504
+ * Components from @inheritComponent
505
+ */
506
+ function testComponentsProp(element, getOptions) {
507
+ describe('prop components:', () => {
508
+ it('can render another root component with the `components` prop', async () => {
509
+ const {
510
+ render,
511
+ testComponentsRootPropWith: component = 'em'
512
+ } = getOptions();
513
+ if (!render) {
514
+ throwMissingPropError('render');
515
+ }
516
+ const testId = randomStringValue();
517
+ const {
518
+ getByTestId
519
+ } = await render(/*#__PURE__*/React.cloneElement(element, {
520
+ components: {
521
+ Root: component
522
+ },
523
+ 'data-testid': testId
524
+ }));
525
+ expect(getByTestId(testId)).not.to.equal(null);
526
+ expect(getByTestId(testId).nodeName.toLowerCase()).to.eq(component);
527
+ });
528
+ });
529
+ }
530
+
531
+ /**
532
+ * MUI theme has a components section that allows specifying default props.
533
+ * Components from @inheritComponent
534
+ */
535
+ function testThemeDefaultProps(element, getOptions) {
536
+ describe('theme default components:', () => {
537
+ it("respect theme's defaultProps", async () => {
538
+ const testProp = 'data-id';
539
+ const {
540
+ muiName,
541
+ render,
542
+ ThemeProvider,
543
+ createTheme
544
+ } = getOptions();
545
+ if (!muiName) {
546
+ throwMissingPropError('muiName');
547
+ }
548
+ if (!render) {
549
+ throwMissingPropError('render');
550
+ }
551
+ if (!ThemeProvider) {
552
+ throwMissingPropError('ThemeProvider');
553
+ }
554
+ if (!createTheme) {
555
+ throwMissingPropError('createTheme');
556
+ }
557
+ const theme = createTheme({
558
+ components: {
559
+ [muiName]: {
560
+ defaultProps: {
561
+ [testProp]: 'testProp'
562
+ }
563
+ }
564
+ }
565
+ });
566
+ const {
567
+ container
568
+ } = await render(/*#__PURE__*/_jsx(ThemeProvider, {
569
+ theme: theme,
570
+ children: element
571
+ }));
572
+ expect(container.firstChild).to.have.attribute(testProp, 'testProp');
573
+ });
574
+ });
575
+ describe('default props provider:', () => {
576
+ const {
577
+ muiName,
578
+ render,
579
+ DefaultPropsProvider
580
+ } = getOptions();
581
+ it.skipIf(!DefaultPropsProvider)('respect custom default props', async function test() {
582
+ const testProp = 'data-id';
583
+ if (!muiName) {
584
+ throwMissingPropError('muiName');
585
+ }
586
+ if (!render) {
587
+ throwMissingPropError('render');
588
+ }
589
+ const {
590
+ container
591
+ } = await render(
592
+ /*#__PURE__*/
593
+ // @ts-expect-error we skip it above.
594
+ _jsx(DefaultPropsProvider, {
595
+ value: {
596
+ [muiName]: {
597
+ defaultProps: {
598
+ [testProp]: 'testProp'
599
+ }
600
+ }
601
+ },
602
+ children: element
603
+ }));
604
+ expect(container.firstChild).to.have.attribute(testProp, 'testProp');
605
+ });
606
+ });
607
+ }
608
+
609
+ /**
610
+ * MUI theme has a components section that allows specifying style overrides.
611
+ * Components from @inheritComponent
612
+ */
613
+ function testThemeStyleOverrides(element, getOptions) {
614
+ describe('theme style overrides:', () => {
615
+ it.skipIf(isJsdom())("respect theme's styleOverrides custom state", async function test() {
616
+ const {
617
+ muiName,
618
+ testStateOverrides,
619
+ render,
620
+ ThemeProvider,
621
+ createTheme
622
+ } = getOptions();
623
+ if (!testStateOverrides) {
624
+ return;
625
+ }
626
+ if (!muiName) {
627
+ throwMissingPropError('muiName');
628
+ }
629
+ if (!render) {
630
+ throwMissingPropError('render');
631
+ }
632
+ if (!ThemeProvider) {
633
+ throwMissingPropError('ThemeProvider');
634
+ }
635
+ if (!createTheme) {
636
+ throwMissingPropError('createTheme');
637
+ }
638
+ const testStyle = {
639
+ marginTop: '13px'
640
+ };
641
+ const theme = createTheme({
642
+ components: {
643
+ [muiName]: {
644
+ styleOverrides: {
645
+ [testStateOverrides.styleKey]: testStyle
646
+ }
647
+ }
648
+ }
649
+ });
650
+ if (!testStateOverrides.prop) {
651
+ return;
652
+ }
653
+ const {
654
+ container
655
+ } = await render(/*#__PURE__*/_jsx(ThemeProvider, {
656
+ theme: theme,
657
+ children: /*#__PURE__*/React.cloneElement(element, {
658
+ [testStateOverrides.prop]: testStateOverrides.value
659
+ })
660
+ }));
661
+ expect(container.firstChild).to.toHaveComputedStyle(testStyle);
662
+ });
663
+ it.skipIf(isJsdom())("respect theme's styleOverrides slots", async function test() {
664
+ const {
665
+ muiName,
666
+ testDeepOverrides,
667
+ testRootOverrides = {
668
+ slotName: 'root'
669
+ },
670
+ render,
671
+ ThemeProvider,
672
+ createTheme
673
+ } = getOptions();
674
+ if (!ThemeProvider) {
675
+ throwMissingPropError('ThemeProvider');
676
+ }
677
+ if (!createTheme) {
678
+ throwMissingPropError('createTheme');
679
+ }
680
+ const testStyle = {
681
+ mixBlendMode: 'darken'
682
+ };
683
+ function resolveDeepOverrides(callback) {
684
+ if (!testDeepOverrides) {
685
+ return {};
686
+ }
687
+ const styles = {};
688
+ if (Array.isArray(testDeepOverrides)) {
689
+ testDeepOverrides.forEach(slot => {
690
+ callback(styles, slot);
691
+ });
692
+ } else {
693
+ callback(styles, testDeepOverrides);
694
+ }
695
+ return styles;
696
+ }
697
+ const theme = createTheme({
698
+ components: {
699
+ [muiName]: {
700
+ styleOverrides: {
701
+ [testRootOverrides.slotName]: {
702
+ ...testStyle,
703
+ ...resolveDeepOverrides((styles, slot) => {
704
+ styles[`& .${slot.slotClassName}`] = {
705
+ fontVariantCaps: 'all-petite-caps'
706
+ };
707
+ })
708
+ },
709
+ ...resolveDeepOverrides((styles, slot) => {
710
+ styles[slot.slotName] = {
711
+ mixBlendMode: 'darken'
712
+ };
713
+ })
714
+ }
715
+ }
716
+ }
717
+ });
718
+ const {
719
+ container,
720
+ setProps
721
+ } = await render(/*#__PURE__*/_jsx(ThemeProvider, {
722
+ theme: theme,
723
+ children: element
724
+ }));
725
+ if (testRootOverrides.slotClassName) {
726
+ expect(document.querySelector(`.${testRootOverrides.slotClassName}`)).to.toHaveComputedStyle(testStyle);
727
+ } else {
728
+ expect(container.firstChild).to.toHaveComputedStyle(testStyle);
729
+ }
730
+ if (testDeepOverrides) {
731
+ (Array.isArray(testDeepOverrides) ? testDeepOverrides : [testDeepOverrides]).forEach(slot => {
732
+ expect(document.querySelector(`.${slot.slotClassName}`)).to.toHaveComputedStyle({
733
+ fontVariantCaps: 'all-petite-caps',
734
+ mixBlendMode: 'darken'
735
+ });
736
+ });
737
+ const themeWithoutRootOverrides = createTheme({
738
+ components: {
739
+ [muiName]: {
740
+ styleOverrides: {
741
+ ...resolveDeepOverrides((styles, slot) => {
742
+ styles[slot.slotName] = testStyle;
743
+ })
744
+ }
745
+ }
746
+ }
747
+ });
748
+ setProps({
749
+ theme: themeWithoutRootOverrides
750
+ });
751
+ (Array.isArray(testDeepOverrides) ? testDeepOverrides : [testDeepOverrides]).forEach(slot => {
752
+ expect(document.querySelector(`.${slot.slotClassName}`)).to.toHaveComputedStyle(testStyle);
753
+ });
754
+ }
755
+ });
756
+ it.skipIf(isJsdom())('overrideStyles does not replace each other in slots', async function test() {
757
+ const {
758
+ muiName,
759
+ classes,
760
+ testStateOverrides,
761
+ render,
762
+ ThemeProvider,
763
+ createTheme
764
+ } = getOptions();
765
+ if (!ThemeProvider) {
766
+ throwMissingPropError('ThemeProvider');
767
+ }
768
+ if (!createTheme) {
769
+ throwMissingPropError('createTheme');
770
+ }
771
+ const classKeys = Object.keys(classes);
772
+
773
+ // only test the component that has `root` and other classKey
774
+ if (!testStateOverrides || !classKeys.includes('root') || classKeys.length === 1) {
775
+ return;
776
+ }
777
+
778
+ // `styleKey` in some tests is `foo` or `bar`, so need to check if it is a valid classKey.
779
+ const isStyleKeyExists = classKeys.includes(testStateOverrides.styleKey);
780
+ if (!isStyleKeyExists) {
781
+ return;
782
+ }
783
+ const theme = createTheme({
784
+ components: {
785
+ [muiName]: {
786
+ styleOverrides: {
787
+ root: {
788
+ [`&.${classes.root}`]: {
789
+ filter: 'blur(1px)',
790
+ mixBlendMode: 'darken'
791
+ }
792
+ },
793
+ ...(testStateOverrides && {
794
+ [testStateOverrides.styleKey]: {
795
+ [`&.${classes.root}`]: {
796
+ mixBlendMode: 'color'
797
+ }
798
+ }
799
+ })
800
+ }
801
+ }
802
+ }
803
+ });
804
+ if (!testStateOverrides.prop) {
805
+ return;
806
+ }
807
+ await render(/*#__PURE__*/_jsx(ThemeProvider, {
808
+ theme: theme,
809
+ children: /*#__PURE__*/React.cloneElement(element, {
810
+ [testStateOverrides.prop]: testStateOverrides.value
811
+ })
812
+ }));
813
+ expect(document.querySelector(`.${classes.root}`)).toHaveComputedStyle({
814
+ filter: 'blur(1px)',
815
+ // still valid in root
816
+ mixBlendMode: 'color' // overridden by `styleKey`
817
+ });
818
+ });
819
+ });
820
+ }
821
+
822
+ /**
823
+ * MUI theme has a components section that allows specifying custom variants.
824
+ * Components from @inheritComponent
825
+ */
826
+ function testThemeVariants(element, getOptions) {
827
+ describe('theme variants:', () => {
828
+ it.skipIf(isJsdom())("respect theme's variants", async function test() {
829
+ const {
830
+ muiName,
831
+ testVariantProps,
832
+ render,
833
+ ThemeProvider,
834
+ createTheme
835
+ } = getOptions();
836
+ if (!testVariantProps) {
837
+ throw new Error('missing testVariantProps');
838
+ }
839
+ if (!muiName) {
840
+ throwMissingPropError('muiName');
841
+ }
842
+ if (!render) {
843
+ throwMissingPropError('render');
844
+ }
845
+ if (!ThemeProvider) {
846
+ throwMissingPropError('ThemeProvider');
847
+ }
848
+ if (!createTheme) {
849
+ throwMissingPropError('createTheme');
850
+ }
851
+ const testStyle = {
852
+ mixBlendMode: 'darken'
853
+ };
854
+ const theme = createTheme({
855
+ components: {
856
+ [muiName]: {
857
+ variants: [{
858
+ props: testVariantProps,
859
+ style: testStyle
860
+ }]
861
+ }
862
+ }
863
+ });
864
+ const {
865
+ getByTestId
866
+ } = await render(/*#__PURE__*/_jsxs(ThemeProvider, {
867
+ theme: theme,
868
+ children: [/*#__PURE__*/React.cloneElement(element, {
869
+ ...testVariantProps,
870
+ 'data-testid': 'with-props'
871
+ }), /*#__PURE__*/React.cloneElement(element, {
872
+ 'data-testid': 'without-props'
873
+ })]
874
+ }));
875
+ expect(getByTestId('with-props')).to.toHaveComputedStyle(testStyle);
876
+ expect(getByTestId('without-props')).not.to.toHaveComputedStyle(testStyle);
877
+ });
878
+ it.skipIf(isJsdom())('supports custom variant', async function test() {
879
+ const {
880
+ muiName,
881
+ testCustomVariant,
882
+ render,
883
+ ThemeProvider,
884
+ createTheme
885
+ } = getOptions();
886
+ if (!ThemeProvider) {
887
+ throwMissingPropError('ThemeProvider');
888
+ }
889
+ if (!createTheme) {
890
+ throwMissingPropError('createTheme');
891
+ }
892
+ if (!testCustomVariant) {
893
+ return;
894
+ }
895
+ const theme = createTheme({
896
+ components: {
897
+ [muiName]: {
898
+ styleOverrides: {
899
+ root: ({
900
+ ownerState
901
+ }) => ({
902
+ ...(ownerState.variant === 'unknown' && {
903
+ mixBlendMode: 'darken'
904
+ })
905
+ })
906
+ }
907
+ }
908
+ }
909
+ });
910
+ const {
911
+ getByTestId
912
+ } = await render(/*#__PURE__*/_jsx(ThemeProvider, {
913
+ theme: theme,
914
+ children: /*#__PURE__*/React.cloneElement(element, {
915
+ variant: 'unknown',
916
+ 'data-testid': 'custom-variant'
917
+ })
918
+ }));
919
+ expect(getByTestId('custom-variant')).toHaveComputedStyle({
920
+ mixBlendMode: 'darken'
921
+ });
922
+ });
923
+ });
924
+ }
925
+
926
+ /**
927
+ * MUI theme supports custom palettes.
928
+ * The components that iterate over the palette via `variants` should be able to render with or without applying the custom palette styles.
929
+ */
930
+ function testThemeCustomPalette(element, getOptions) {
931
+ describe('theme extended palette:', () => {
932
+ const {
933
+ render,
934
+ ThemeProvider,
935
+ createTheme
936
+ } = getOptions();
937
+ it.skipIf(!isJsdom() || !render || !ThemeProvider || !createTheme)('should render without errors', function test() {
938
+ var _ThemeProvider;
939
+ if (!render || !ThemeProvider || !createTheme) {
940
+ throw new Error('missing render, ThemeProvider or createTheme, should have been handled by skipIf');
941
+ }
942
+ const theme = createTheme({
943
+ palette: {
944
+ custom: {
945
+ main: '#ff5252'
946
+ },
947
+ unknown: null,
948
+ custom2: {
949
+ main: {
950
+ blue: {
951
+ dark: '#FFCC00'
952
+ }
953
+ }
954
+ }
955
+ }
956
+ });
957
+ expect(() => render(_ThemeProvider || (_ThemeProvider = /*#__PURE__*/_jsx(ThemeProvider, {
958
+ theme: theme,
959
+ children: element
960
+ })))).not.to.throw();
961
+ });
962
+ });
963
+ }
964
+ const fullSuite = {
965
+ componentProp: testComponentProp,
966
+ componentsProp: testComponentsProp,
967
+ mergeClassName: testClassName,
968
+ propsSpread: testPropsSpread,
969
+ refForwarding: describeRef,
970
+ rootClass: testRootClass,
971
+ slotPropsProp: testSlotPropsProp,
972
+ slotPropsCallback: testSlotPropsCallback,
973
+ slotPropsCallbackWithPropsAsOwnerState: testSlotPropsCallbackWithPropsAsOwnerState,
974
+ slotsProp: testSlotsProp,
975
+ themeDefaultProps: testThemeDefaultProps,
976
+ themeStyleOverrides: testThemeStyleOverrides,
977
+ themeVariants: testThemeVariants,
978
+ themeCustomPalette: testThemeCustomPalette
979
+ };
980
+
981
+ /**
982
+ * Tests various aspects of a component that should be equal across MUI
983
+ * components.
984
+ */
985
+ function describeConformance(minimalElement, getOptions) {
986
+ let originalMatchmedia;
987
+ const storage = {};
988
+ beforeEach(() => {
989
+ originalMatchmedia = window.matchMedia;
990
+ // Create mocks of localStorage getItem and setItem functions
991
+ Object.defineProperty(globalThis, 'localStorage', {
992
+ value: {
993
+ getItem: key => storage[key],
994
+ setItem: (key, value) => {
995
+ storage[key] = value;
996
+ }
997
+ },
998
+ configurable: true
999
+ });
1000
+ window.matchMedia = () => ({
1001
+ // Keep mocking legacy methods because @mui/material v5 still uses them
1002
+ addListener: () => {},
1003
+ addEventListener: () => {},
1004
+ removeListener: () => {},
1005
+ removeEventListener: () => {}
1006
+ });
1007
+ });
1008
+ afterEach(() => {
1009
+ window.matchMedia = originalMatchmedia;
1010
+ });
1011
+ const {
1012
+ after: runAfterHook = () => {},
1013
+ only = Object.keys(fullSuite),
1014
+ slots,
1015
+ skip = []
1016
+ } = getOptions();
1017
+ let filteredTests = Object.keys(fullSuite).filter(testKey => only.includes(testKey) && !skip.includes(testKey));
1018
+ const slotBasedTests = ['slotsProp', 'slotPropsProp', 'slotPropsCallback', 'slotPropsCallbackWithPropsAsOwnerState'];
1019
+ if (!slots) {
1020
+ // if `slots` are not defined, do not run tests that depend on them
1021
+ filteredTests = filteredTests.filter(testKey => !slotBasedTests.includes(testKey));
1022
+ }
1023
+ afterAll(runAfterHook);
1024
+ filteredTests.forEach(testKey => {
1025
+ const test = fullSuite[testKey];
1026
+ test(minimalElement, getOptions);
1027
+ });
1028
+ }
1029
+ export default createDescribe('MUI component API', describeConformance);