@mui/internal-test-utils 2.0.14 → 2.0.16

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