From 8a34771a34f6e029caa73cc055d14a125123dc3b Mon Sep 17 00:00:00 2001 From: Jose C Quintas Jr Date: Thu, 7 Nov 2024 10:06:47 +0100 Subject: [PATCH] [code-infra] Changes for test util to work in `vitest` (#43625) Co-authored-by: MUI bot <2109932+Janpot@users.noreply.github.com> --- .eslintrc.js | 2 +- babel.config.js | 1 + package.json | 4 + packages-internal/test-utils/package.json | 4 +- .../test-utils/src/chai.types.ts | 106 +++ .../test-utils/src/chaiPlugin.ts | 512 ++++++++++++ packages-internal/test-utils/src/createDOM.js | 8 +- .../test-utils/src/createRenderer.tsx | 157 ++-- .../test-utils/src/describeConformance.tsx | 44 +- .../test-utils/src/describeSkipIf.tsx | 9 + packages-internal/test-utils/src/index.ts | 1 + .../test-utils/src/initMatchers.test.js | 10 +- .../test-utils/src/initMatchers.ts | 619 +-------------- .../test-utils/src/mochaHooks.test.js | 3 +- .../test-utils/src/setupJSDOM.js | 4 +- .../test-utils/src/setupVitest.ts | 117 +++ packages-internal/test-utils/tsconfig.json | 7 +- pnpm-lock.yaml | 731 ++++++++++++++++-- tsconfig.json | 2 +- 19 files changed, 1579 insertions(+), 762 deletions(-) create mode 100644 packages-internal/test-utils/src/chai.types.ts create mode 100644 packages-internal/test-utils/src/chaiPlugin.ts create mode 100644 packages-internal/test-utils/src/describeSkipIf.tsx create mode 100644 packages-internal/test-utils/src/setupVitest.ts diff --git a/.eslintrc.js b/.eslintrc.js index a97b7093a954a7..84e943f7d2a685 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -48,7 +48,7 @@ const NO_RESTRICTED_IMPORTS_PATTERNS_DEEPLY_NESTED = [ module.exports = /** @type {Config} */ ({ root: true, // So parent files don't get applied env: { - es6: true, + es2020: true, browser: true, node: true, }, diff --git a/babel.config.js b/babel.config.js index de9a6cdea126ae..0b0e796d325601 100644 --- a/babel.config.js +++ b/babel.config.js @@ -43,6 +43,7 @@ module.exports = function getBabelConfig(api) { '@mui/utils': resolveAliasPath('./packages/mui-utils/src'), '@mui/joy': resolveAliasPath('./packages/mui-joy/src'), '@mui/internal-docs-utils': resolveAliasPath('./packages-internal/docs-utils/src'), + '@mui/internal-test-utils': resolveAliasPath('./packages-internal/test-utils/src'), docs: resolveAliasPath('./docs'), test: resolveAliasPath('./test'), }; diff --git a/package.json b/package.json index 3e90f23a05a31f..6e5e82ba3ecd1c 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,8 @@ "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", + "@vitest/browser": "^2.1.2", + "@vitest/coverage-v8": "^2.1.2", "babel-loader": "^9.2.1", "babel-plugin-istanbul": "^7.0.0", "babel-plugin-module-resolver": "^5.0.2", @@ -192,6 +194,8 @@ "terser-webpack-plugin": "^5.3.10", "tsx": "^4.19.2", "typescript": "^5.6.3", + "vitest": "^2.1.2", + "vitest-fail-on-console": "^0.7.1", "webpack": "^5.96.1", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", diff --git a/packages-internal/test-utils/package.json b/packages-internal/test-utils/package.json index 07674b99fb13d7..d418be995965ed 100644 --- a/packages-internal/test-utils/package.json +++ b/packages-internal/test-utils/package.json @@ -21,7 +21,9 @@ "./setupBabel": "./build/setupBabel.js", "./setupBabelPlaywright": "./build/setupBabelPlaywright.js", "./setupJSDOM": "./build/setupJSDOM.js", - "./setupKarma": "./build/setupKarma.js" + "./setupKarma": "./build/setupKarma.js", + "./chaiPlugin": "./build/chaiPlugin.js", + "./setupVitest": "./build/setupVitest.js" }, "scripts": { "prebuild": "rimraf ./build", diff --git a/packages-internal/test-utils/src/chai.types.ts b/packages-internal/test-utils/src/chai.types.ts new file mode 100644 index 00000000000000..352be6b36312c9 --- /dev/null +++ b/packages-internal/test-utils/src/chai.types.ts @@ -0,0 +1,106 @@ +export {}; + +// https://stackoverflow.com/a/46755166/3406963 +declare global { + namespace Chai { + interface Assertion { + /** + * Checks `expectedStyle` is a subset of the elements inline style i.e. `element.style`. + * @example expect(element).toHaveInlineStyle({ width: '200px' }) + */ + toHaveInlineStyle( + expectedStyle: Partial< + Record< + Exclude< + keyof CSSStyleDeclaration, + | 'getPropertyPriority' + | 'getPropertyValue' + | 'item' + | 'removeProperty' + | 'setProperty' + | number + >, + string + > + >, + ): void; + /** + * Checks `expectedStyle` is a subset of the elements computed style i.e. `window.getComputedStyle(element)`. + * @example expect(element).toHaveComputedStyle({ width: '200px' }) + */ + toHaveComputedStyle( + expectedStyle: Partial< + Record< + Exclude< + keyof CSSStyleDeclaration, + | 'getPropertyPriority' + | 'getPropertyValue' + | 'item' + | 'removeProperty' + | 'setProperty' + | number + >, + string + > + >, + ): void; + /** + * Check if an element's [`visibility`](https://developer.mozilla.org/en-US/docs/Web/CSS/visibility) is not `hidden` or `collapsed`. + */ + toBeVisible(): void; + /** + * Check if an element's [`visibility`](https://developer.mozilla.org/en-US/docs/Web/CSS/visibility) is `hidden` or `collapsed`. + */ + toBeHidden(): void; + /** + * Checks if the element is inaccessible. + * + * Elements are considered inaccessible if they either: + * - have [`visibility`](https://developer.mozilla.org/en-US/docs/Web/CSS/visibility) `hidden` + * - have [`display`](https://developer.mozilla.org/en-US/docs/Web/CSS/display) `none` + * - have `aria-hidden` `true` or any of their parents + * + * @see [Excluding Elements from the Accessibility Tree](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion) + */ + toBeInaccessible(): void; + toHaveAccessibleDescription(description: string): void; + /** + * Checks if the accessible name computation (according to `accname` spec) + * matches the expectation. + * + * @see https://www.w3.org/TR/accname-1.2/ + * @param name + */ + toHaveAccessibleName(name: string): void; + /** + * Checks if the element is actually focused i.e. `document.activeElement` is equal to the actual element. + */ + toHaveFocus(): void; + /** + * Checks if the element is the active-descendant of the active element. + */ + toHaveVirtualFocus(): void; + /** + * Matches calls to `console.warn` in the asserted callback. + * + * @example expect(() => render()).not.toWarnDev() + * @example expect(() => render()).toWarnDev('single message') + * @example expect(() => render()).toWarnDev(['first warning', 'then the second']) + */ + toWarnDev(messages?: string | readonly (string | boolean)[]): void; + /** + * Matches calls to `console.error` in the asserted callback. + * + * @example expect(() => render()).not.toErrorDev() + * @example expect(() => render()).toErrorDev('single message') + * @example expect(() => render()).toErrorDev(['first warning', 'then the second']) + */ + toErrorDev(messages?: string | readonly (string | boolean)[]): void; + /** + * Asserts that the given callback throws an error matching the given message in development (process.env.NODE_ENV !== 'production'). + * In production it expects a minified error. + */ + toThrowMinified(message: string): void; + } + } +} diff --git a/packages-internal/test-utils/src/chaiPlugin.ts b/packages-internal/test-utils/src/chaiPlugin.ts new file mode 100644 index 00000000000000..87873cef70ba5b --- /dev/null +++ b/packages-internal/test-utils/src/chaiPlugin.ts @@ -0,0 +1,512 @@ +import { isInaccessible } from '@testing-library/dom'; +import { prettyDOM } from '@testing-library/react/pure'; +import chai, { AssertionError } from 'chai'; +import { computeAccessibleDescription, computeAccessibleName } from 'dom-accessibility-api'; +import formatUtil from 'format-util'; +import _ from 'lodash'; +import './chai.types'; + +const isKarma = Boolean(process.env.KARMA); + +function isInJSDOM() { + return /jsdom/.test(window.navigator.userAgent); +} + +// chai#utils.elToString that looks like stringified elements in testing-library +function elementToString(element: Element | null | undefined) { + if (typeof element?.nodeType === 'number') { + return prettyDOM(element, undefined, { highlight: !isKarma, maxDepth: 1 }); + } + return String(element); +} + +const chaiPlugin: Parameters<(typeof chai)['use']>[0] = (chaiAPI, utils) => { + const blockElements = new Set([ + 'html', + 'address', + 'blockquote', + 'body', + 'dd', + 'div', + 'dl', + 'dt', + 'fieldset', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'noframes', + 'ol', + 'p', + 'ul', + 'center', + 'dir', + 'hr', + 'menu', + 'pre', + ]); + + function pretendVisibleGetComputedStyle(element: Element): CSSStyleDeclaration { + // `CSSStyleDeclaration` is not constructable + // https://stackoverflow.com/a/52732909/3406963 + // this is not equivalent to the declaration from `getComputedStyle` + // for example `getComputedStyle` would return a readonly declaration + // let's hope this doesn't get passed around until it's no longer clear where it comes from + const declaration = document.createElement('span').style; + + // initial values + declaration.content = ''; + // technically it's `inline`. We partially apply the default user agent sheet (chrome) here + // we're only interested in elements that use block + declaration.display = blockElements.has(element.tagName) ? 'block' : 'inline'; + declaration.visibility = 'visible'; + + return declaration; + } + + // better diff view for expect(element).to.equal(document.activeElement) + chaiAPI.Assertion.addMethod('toHaveFocus', function elementIsFocused() { + const element = utils.flag(this, 'object'); + + this.assert( + element === document.activeElement, + // karma does not show the diff like mocha does + `expected element to have focus${isKarma ? '\nexpected #{exp}\nactual: #{act}' : ''}`, + `expected element to NOT have focus \n${elementToString(element)}`, + elementToString(element), + elementToString(document.activeElement), + ); + }); + + chaiAPI.Assertion.addMethod('toHaveVirtualFocus', function elementIsVirtuallyFocused() { + const element = utils.flag(this, 'object'); + const id = element.getAttribute('id'); + + const virtuallyFocusedElementId = document.activeElement!.getAttribute('aria-activedescendant'); + + this.assert( + virtuallyFocusedElementId === id, + `expected element to be virtually focused\nexpected id #{exp}\n${ + virtuallyFocusedElementId === null + ? `activeElement: ${elementToString(document.activeElement)}` + : 'actual id: #{act}' + }`, + 'expected element to NOT to be virtually focused', + id, + virtuallyFocusedElementId, + virtuallyFocusedElementId !== null, + ); + }); + + chaiAPI.Assertion.addMethod('toBeInaccessible', function elementIsAccessible() { + const element = utils.flag(this, 'object'); + + const inaccessible = isInaccessible(element); + + this.assert( + inaccessible === true, + `expected \n${elementToString(element)} to be inaccessible but it was accessible`, + `expected \n${elementToString(element)} to be accessible but it was inaccessible`, + // Not interested in a diff but the typings require the 4th parameter. + undefined, + ); + }); + + chaiAPI.Assertion.addMethod('toHaveAccessibleName', function hasAccessibleName(expectedName) { + const root = utils.flag(this, 'object'); + // make sure it's an Element + new chai.Assertion(root.nodeType, `Expected an Element but got '${String(root)}'`).to.equal(1); + + const actualName = computeAccessibleName(root, { + computedStyleSupportsPseudoElements: !isInJSDOM(), + // in local development we pretend to be visible. full getComputedStyle is + // expensive and reserved for CI + getComputedStyle: process.env.CI ? undefined : pretendVisibleGetComputedStyle, + }); + + this.assert( + actualName === expectedName, + `expected \n${elementToString(root)} to have accessible name #{exp} but got #{act} instead.`, + `expected \n${elementToString(root)} not to have accessible name #{exp}.`, + expectedName, + actualName, + ); + }); + + chaiAPI.Assertion.addMethod( + 'toHaveAccessibleDescription', + function hasAccessibleDescription(expectedDescription) { + const root = utils.flag(this, 'object'); + // make sure it's an Element + new chai.Assertion(root.nodeType, `Expected an Element but got '${String(root)}'`).to.equal( + 1, + ); + + const actualDescription = computeAccessibleDescription(root, { + // in local development we pretend to be visible. full getComputedStyle is + // expensive and reserved for CI + getComputedStyle: process.env.CI ? undefined : pretendVisibleGetComputedStyle, + }); + + const possibleDescriptionComputationMessage = root.hasAttribute('title') + ? ' computeAccessibleDescription can be misleading when a `title` attribute is used. This might be a bug in `dom-accessibility-api`.' + : ''; + this.assert( + actualDescription === expectedDescription, + `expected \n${elementToString( + root, + )} to have accessible description #{exp} but got #{act} instead.${possibleDescriptionComputationMessage}`, + `expected \n${elementToString( + root, + )} not to have accessible description #{exp}.${possibleDescriptionComputationMessage}`, + expectedDescription, + actualDescription, + ); + }, + ); + + /** + * Correct name for `to.be.visible` + */ + chaiAPI.Assertion.addMethod('toBeVisible', function toBeVisible() { + // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/no-unused-expressions + new chaiAPI.Assertion(this._obj).to.be.visible; + }); + + /** + * Correct name for `not.to.be.visible` + */ + chaiAPI.Assertion.addMethod('toBeHidden', function toBeHidden() { + // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/no-unused-expressions + new chaiAPI.Assertion(this._obj).not.to.be.visible; + }); + + function assertMatchingStyles( + this: Chai.AssertionStatic, + actualStyleDeclaration: CSSStyleDeclaration, + expectedStyleUnnormalized: Record, + options: { styleTypeHint: string }, + ): void { + const { styleTypeHint } = options; + + // Compare objects using hyphen case. + // This is closer to actual CSS and required for getPropertyValue anyway. + const expectedStyle: Record = {}; + Object.keys(expectedStyleUnnormalized).forEach((cssProperty) => { + const hyphenCasedPropertyName = _.kebabCase(cssProperty); + const isVendorPrefixed = /^(moz|ms|o|webkit)-/.test(hyphenCasedPropertyName); + const propertyName = isVendorPrefixed + ? `-${hyphenCasedPropertyName}` + : hyphenCasedPropertyName; + expectedStyle[propertyName] = expectedStyleUnnormalized[cssProperty]; + }); + + const shorthandProperties = new Set([ + 'all', + 'animation', + 'background', + 'border', + 'border-block-end', + 'border-block-start', + 'border-bottom', + 'border-color', + 'border-image', + 'border-inline-end', + 'border-inline-start', + 'border-left', + 'border-radius', + 'border-right', + 'border-style', + 'border-top', + 'border-width', + 'column-rule', + 'columns', + 'flex', + 'flex-flow', + 'font', + 'gap', + 'grid', + 'grid-area', + 'grid-column', + 'grid-row', + 'grid-template', + 'list-style', + 'margin', + 'mask', + 'offset', + 'outline', + 'overflow', + 'padding', + 'place-content', + 'place-items', + 'place-self', + 'scroll-margin', + 'scroll-padding', + 'text-decoration', + 'text-emphasis', + 'transition', + ]); + const usedShorthandProperties = Object.keys(expectedStyle).filter((cssProperty) => { + return shorthandProperties.has(cssProperty); + }); + if (usedShorthandProperties.length > 0) { + throw new Error( + [ + `Shorthand properties are not supported in ${styleTypeHint} styles matchers since browsers can compute them differently. `, + 'Use longhand properties instead for the follow shorthand properties:\n', + usedShorthandProperties + .map((cssProperty) => { + return `- https://developer.mozilla.org/en-US/docs/Web/CSS/${cssProperty}#constituent_properties`; + }) + .join('\n'), + ].join(''), + ); + } + + const actualStyle: Record = {}; + Object.keys(expectedStyle).forEach((cssProperty) => { + actualStyle[cssProperty] = actualStyleDeclaration.getPropertyValue(cssProperty); + }); + + const jsdomHint = + 'Styles in JSDOM e.g. from `test:unit` are often misleading since JSDOM does not implement the Cascade nor actual CSS property value computation. ' + + 'If results differ between real browsers and JSDOM, skip the test in JSDOM e.g. `if (/jsdom/.test(window.navigator.userAgent)) this.skip();`'; + const shorthandHint = + 'Browsers can compute shorthand properties differently. Prefer longhand properties e.g. `borderTopColor`, `borderRightColor` etc. instead of `border` or `border-color`.'; + const messageHint = `${jsdomHint}\n${shorthandHint}`; + + if (isKarma) { + // `#{exp}` and `#{act}` placeholders escape the newlines + const expected = JSON.stringify(expectedStyle, null, 2); + const actual = JSON.stringify(actualStyle, null, 2); + // karma's `dots` reporter does not support diffs + this.assert( + // TODO Fix upstream docs/types + (utils as any).eql(actualStyle, expectedStyle), + `expected ${styleTypeHint} style of #{this} did not match\nExpected:\n${expected}\nActual:\n${actual}\n\n\n${messageHint}`, + `expected #{this} to not have ${styleTypeHint} style\n${expected}\n\n\n${messageHint}`, + expectedStyle, + actualStyle, + ); + } else { + this.assert( + // TODO Fix upstream docs/types + (utils as any).eql(actualStyle, expectedStyle), + `expected #{this} to have ${styleTypeHint} style #{exp} \n\n${messageHint}`, + `expected #{this} not to have ${styleTypeHint} style #{exp}${messageHint}`, + expectedStyle, + actualStyle, + true, + ); + } + } + + chaiAPI.Assertion.addMethod( + 'toHaveInlineStyle', + function toHaveInlineStyle(expectedStyleUnnormalized: Record) { + const element = utils.flag(this, 'object') as HTMLElement; + if (element?.nodeType !== 1) { + // Same pre-condition for negated and unnegated assertion + throw new AssertionError(`Expected an Element but got ${String(element)}`); + } + + assertMatchingStyles.call(this, element.style, expectedStyleUnnormalized, { + styleTypeHint: 'inline', + }); + }, + ); + + chaiAPI.Assertion.addMethod( + 'toHaveComputedStyle', + function toHaveComputedStyle(expectedStyleUnnormalized: Record) { + const element = utils.flag(this, 'object') as HTMLElement; + if (element?.nodeType !== 1) { + // Same pre-condition for negated and unnegated assertion + throw new AssertionError(`Expected an Element but got ${String(element)}`); + } + const computedStyle = element.ownerDocument.defaultView!.getComputedStyle(element); + + assertMatchingStyles.call(this, computedStyle, expectedStyleUnnormalized, { + styleTypeHint: 'computed', + }); + }, + ); + + chaiAPI.Assertion.addMethod('toThrowMinified', function toThrowMinified(expectedDevMessage) { + // TODO: Investigate if `as any` can be removed after https://github.com/DefinitelyTyped/DefinitelyTyped/issues/48634 is resolved. + if (process.env.NODE_ENV !== 'production') { + (this as any).to.throw(expectedDevMessage); + } else { + utils.flag( + this, + 'message', + "Looks like the error was not minified. This can happen if the error code hasn't been generated yet. Run `pnpm extract-error-codes` and try again.", + ); + // TODO: Investigate if `as any` can be removed after https://github.com/DefinitelyTyped/DefinitelyTyped/issues/48634 is resolved. + (this as any).to.throw('Minified MUI error', 'helper'); + } + }); + + function addConsoleMatcher(matcherName: string, methodName: 'error' | 'warn') { + /** + * @param {string[]} expectedMessages + */ + function matcher(this: Chai.AssertionStatic, expectedMessagesInput = []) { + // documented pattern to get the actual value of the assertion + // eslint-disable-next-line no-underscore-dangle + const callback = this._obj; + + if (process.env.NODE_ENV !== 'production') { + const expectedMessages = + typeof expectedMessagesInput === 'string' + ? [expectedMessagesInput] + : expectedMessagesInput.slice(); + const unexpectedMessages: Error[] = []; + // TODO Remove type once MUI X enables noImplicitAny + let caughtError: unknown | null = null; + + this.assert( + expectedMessages.length > 0, + `Expected to call console.${methodName} but didn't provide messages. ` + + `If you don't expect any messages prefer \`expect().not.${matcherName}();\`.`, + `Expected no call to console.${methodName} while also expecting messages.` + + 'Expected no call to console.error but provided messages. ' + + "If you want to make sure a certain message isn't logged prefer the positive. " + + 'By expecting certain messages you automatically expect that no other messages are logged', + // Not interested in a diff but the typings require the 4th parameter. + undefined, + ); + + // Ignore skipped messages in e.g. `[condition && 'foo']` + const remainingMessages = expectedMessages.filter((messageOrFalse) => { + return messageOrFalse !== false; + }); + + // eslint-disable-next-line no-console + const originalMethod = console[methodName]; + + let messagesMatched = 0; + const consoleMatcher = (format: string, ...args: readonly unknown[]) => { + // Ignore legacy root deprecation warnings + // TODO: Remove once we no longer use legacy roots. + if ( + format.includes('Use createRoot instead.') || + format.includes('Use hydrateRoot instead.') + ) { + return; + } + const actualMessage = formatUtil(format, ...args); + const expectedMessage = remainingMessages.shift(); + messagesMatched += 1; + + // TODO Remove type once MUI X enables noImplicitAny + let message: string | null = null; + if (expectedMessage === undefined) { + message = `Expected no more error messages but got:\n"${actualMessage}"`; + } else if (!actualMessage.includes(expectedMessage)) { + message = `Expected #${messagesMatched} "${expectedMessage}" to be included in \n"${actualMessage}"`; + } + + if (message !== null) { + const error = new Error(message); + + const { stack: fullStack } = error; + const fullStacktrace = fullStack!.replace(`Error: ${message}\n`, '').split('\n'); + + const usefulStacktrace = fullStacktrace + // + // first line points to this frame which is irrelevant for the tester + .slice(1); + const usefulStack = `${message}\n${usefulStacktrace.join('\n')}`; + + error.stack = usefulStack; + unexpectedMessages.push(error); + } + }; + // eslint-disable-next-line no-console + console[methodName] = consoleMatcher; + + try { + callback(); + } catch (error) { + caughtError = error; + } finally { + // eslint-disable-next-line no-console + console[methodName] = originalMethod; + + // unexpected thrown error takes precedence over unexpected console call + if (caughtError !== null) { + // not the same pattern as described in the block because we don't rethrow in the catch + // eslint-disable-next-line no-unsafe-finally + throw caughtError; + } + + const formatMessages = (messages: ReadonlyArray) => { + const formattedMessages = messages.map((message) => { + if (typeof message === 'string') { + return `"${message}"`; + } + // full Error + return `${message.stack}`; + }); + return `\n\n - ${formattedMessages.join('\n\n- ')}`; + }; + + const shouldHaveWarned = utils.flag(this, 'negate') !== true; + + // unreachable from expect().not.toWarnDev(messages) + if (unexpectedMessages.length > 0) { + const unexpectedMessageRecordedMessage = `Recorded unexpected console.${methodName} calls: ${formatMessages( + unexpectedMessages, + )}`; + // chai will duplicate the stack frames from the unexpected calls in their assertion error + // it's not ideal but the test failure is located the second to last stack frame + // and the origin of the call is the second stackframe in the stack + this.assert( + // force chai to always trigger an assertion error + !shouldHaveWarned, + unexpectedMessageRecordedMessage, + unexpectedMessageRecordedMessage, + // Not interested in a diff but the typings require the 4th parameter. + undefined, + ); + } + + if (shouldHaveWarned) { + this.assert( + remainingMessages.length === 0, + `Could not match the following console.${methodName} calls. ` + + `Make sure previous actions didn't call console.${methodName} by wrapping them in expect(() => {}).not.${matcherName}(): ${formatMessages( + remainingMessages, + )}`, + `Impossible state reached in \`expect().${matcherName}()\`. ` + + `This is a bug in the matcher.`, + // Not interested in a diff but the typings require the 4th parameter. + undefined, + ); + } + } + } else { + // nothing to do in prod + // If there are still console calls than our test setup throws. + callback(); + } + } + + chaiAPI.Assertion.addMethod(matcherName, matcher); + } + + /** + * @example expect(() => render()).toWarnDev('single message') + * @example expect(() => render()).toWarnDev(['first warning', 'then the second']) + */ + addConsoleMatcher('toWarnDev', 'warn'); + addConsoleMatcher('toErrorDev', 'error'); +}; + +export default chaiPlugin; diff --git a/packages-internal/test-utils/src/createDOM.js b/packages-internal/test-utils/src/createDOM.js index c08017b408cf81..53d3b7407204bc 100644 --- a/packages-internal/test-utils/src/createDOM.js +++ b/packages-internal/test-utils/src/createDOM.js @@ -25,7 +25,7 @@ function createDOM() { pretendToBeVisual: true, url: 'http://localhost', }); - global.window = dom.window; + globalThis.window = dom.window; // Not yet supported: https://github.com/jsdom/jsdom/issues/2152 class Touch { constructor(instance) { @@ -52,14 +52,14 @@ function createDOM() { return this.instance.clientY; } } - global.window.Touch = Touch; + globalThis.window.Touch = Touch; Object.keys(dom.window) .filter((key) => !blacklist.includes(key)) .concat(whitelist) .forEach((key) => { - if (typeof global[key] === 'undefined') { - global[key] = dom.window[key]; + if (typeof globalThis[key] === 'undefined') { + globalThis[key] = dom.window[key]; } }); } diff --git a/packages-internal/test-utils/src/createRenderer.tsx b/packages-internal/test-utils/src/createRenderer.tsx index 6943b10d1d9967..0a9b92539bfbbd 100644 --- a/packages-internal/test-utils/src/createRenderer.tsx +++ b/packages-internal/test-utils/src/createRenderer.tsx @@ -1,23 +1,22 @@ /* eslint-env mocha */ -import * as React from 'react'; -import * as ReactDOMServer from 'react-dom/server'; import createEmotionCache from '@emotion/cache'; import { CacheProvider as EmotionCacheProvider } from '@emotion/react'; import { - act as rtlAct, buildQueries, cleanup, - fireEvent as rtlFireEvent, - queries, - queryHelpers, - render as testingLibraryRender, prettyDOM, - within, + queries, RenderResult, + act as rtlAct, + fireEvent as rtlFireEvent, screen as rtlScreen, Screen, + render as testingLibraryRender, + within, } from '@testing-library/react/pure'; import { userEvent } from '@testing-library/user-event'; +import * as React from 'react'; +import * as ReactDOMServer from 'react-dom/server'; import { useFakeTimers } from 'sinon'; import reactMajor from './reactMajor'; @@ -114,8 +113,8 @@ class DispatchingProfiler implements Profiler { private renders: RenderMark[] = []; - constructor(test: import('mocha').Test) { - this.id = test.fullTitle(); + constructor(id: string) { + this.id = id; } onRender: Profiler['onRender'] = ( @@ -193,18 +192,6 @@ const [ }, ); -const queryAllByMuiTest = queryHelpers.queryAllByAttribute.bind(null, 'data-mui-test'); -const [queryByMuiTest, getAllByMuiTest, getByMuiTest, findAllByMuiTest, findByMuiTest] = - buildQueries( - queryAllByMuiTest, - function getMultipleError(container, dataMuiTest) { - return `Found multiple elements with the data-mui-test attribute of: ${dataMuiTest}`; - }, - function getMissingError(container, dataMuiTest) { - return `Found no element with the data-mui-test attribute of: ${dataMuiTest}`; - }, - ); - const customQueries = { queryDescriptionOf, queryAllDescriptionsOf, @@ -212,30 +199,6 @@ const customQueries = { getAllDescriptionsOf, findDescriptionOf, findAllDescriptionsOf, - /** - * @deprecated Use `queryAllByTestId` instead - */ - queryAllByMuiTest, - /** - * @deprecated Use `queryByTestId` instead - */ - queryByMuiTest, - /** - * @deprecated Use `getAllByTestId` instead - */ - getAllByMuiTest, - /** - * @deprecated Use `getByTestId` instead - */ - getByMuiTest, - /** - * @deprecated Use `findAllByTestId` instead - */ - findAllByMuiTest, - /** - * @deprecated Use `findByTestId` instead - */ - findByMuiTest, }; interface RenderConfiguration { @@ -375,11 +338,88 @@ export interface Clock { export type ClockConfig = undefined | number | Date; +const isVitest = + // VITEST is present on the environment when not in browser mode. + process.env.VITEST === 'true' || + // VITEST_BROWSER_DEBUG is present on vitest in browser mode. + typeof process.env.VITEST_BROWSER_DEBUG !== 'undefined'; + +function createVitestClock( + defaultMode: 'fake' | 'real', + config: ClockConfig, + options: Exclude[0], number | Date>, + vi: any, +): Clock { + if (defaultMode === 'fake') { + beforeEach(() => { + vi.useFakeTimers(options); + if (config) { + vi.setSystemTime(config); + } + }); + afterEach(() => { + vi.useRealTimers(); + }); + } else { + beforeEach(() => { + if (config) { + vi.setSystemTime(config); + } + }); + afterEach(() => { + vi.useRealTimers(); + }); + } + + return { + withFakeTimers: () => { + beforeEach(() => { + vi.useFakeTimers(options); + }); + afterEach(() => { + vi.useRealTimers(); + }); + }, + runToLast: () => { + traceSync('runToLast', () => { + rtlAct(() => { + vi.runOnlyPendingTimers(); + }); + }); + }, + isReal() { + return !vi.isFakeTimers(); + }, + restore() { + vi.useRealTimers(); + }, + tick(timeoutMS: number) { + traceSync('tick', () => { + rtlAct(() => { + vi.advanceTimersByTime(timeoutMS); + }); + }); + }, + runAll() { + traceSync('runAll', () => { + rtlAct(() => { + vi.runAllTimers(); + }); + }); + }, + }; +} + function createClock( defaultMode: 'fake' | 'real', config: ClockConfig, - options?: Exclude[0], number | Date>, + options: Exclude[0], number | Date>, + vi: any, ): Clock { + if (isVitest) { + return createVitestClock(defaultMode, config, options, vi); + } + let clock: ReturnType | null = null; let mode = defaultMode; @@ -467,6 +507,11 @@ export interface CreateRendererOptions extends Pick[2]; + /** + * Vitest needs to be injected because this file is transpiled to commonjs and vitest is an esm module. + * @default {} + */ + vi?: any; } export function createRenderer(globalOptions: CreateRendererOptions = {}): Renderer { @@ -475,11 +520,12 @@ export function createRenderer(globalOptions: CreateRendererOptions = {}): Rende clockConfig, strict: globalStrict = true, strictEffects: globalStrictEffects = globalStrict, + vi = (globalThis as any).vi || {}, clockOptions, } = globalOptions; // save stack to re-use in test-hooks const { stack: createClientRenderStack } = new Error(); - const clock = createClock(clockMode, clockConfig, clockOptions); + const clock = createClock(clockMode, clockConfig, clockOptions, vi); /** * Flag whether `createRenderer` was called in a suite i.e. describe() block. @@ -531,13 +577,22 @@ export function createRenderer(globalOptions: CreateRendererOptions = {}): Rende throw error; } - const test = this.currentTest; - if (test === undefined) { + let id: string | null = null; + + if (isVitest) { + // @ts-expect-error + id = expect.getState().currentTestName; + } else { + id = this.currentTest?.fullTitle() ?? null; + } + + if (!id) { throw new Error( 'Unable to find the currently running test. This is a bug with the client-renderer. Please report this issue to a maintainer.', ); } - profiler = new UsedProfiler(test); + + profiler = new UsedProfiler(id); emotionCache = createEmotionCache({ key: 'emotion-client-render' }); diff --git a/packages-internal/test-utils/src/describeConformance.tsx b/packages-internal/test-utils/src/describeConformance.tsx index 3f9dd40eccfe80..e3625ea577ff73 100644 --- a/packages-internal/test-utils/src/describeConformance.tsx +++ b/packages-internal/test-utils/src/describeConformance.tsx @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import * as React from 'react'; import { expect } from 'chai'; +import * as React from 'react'; import createDescribe from './createDescribe'; import { MuiRenderResult } from './createRenderer'; @@ -661,9 +661,11 @@ function testThemeStyleOverrides( getOptions: () => ConformanceOptions, ) { describe('theme style overrides:', () => { - it("respect theme's styleOverrides custom state", async function test() { + it("respect theme's styleOverrides custom state", async function test(t = {}) { if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + this?.skip?.() ?? t?.skip(); } const { muiName, testStateOverrides, render, ThemeProvider, createTheme } = getOptions(); @@ -716,9 +718,11 @@ function testThemeStyleOverrides( expect(container.firstChild).to.toHaveComputedStyle(testStyle); }); - it("respect theme's styleOverrides slots", async function test() { + it("respect theme's styleOverrides slots", async function test(t = {}) { if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + this?.skip?.() ?? t?.skip(); } const { @@ -827,9 +831,11 @@ function testThemeStyleOverrides( } }); - it('overrideStyles does not replace each other in slots', async function test() { + it('overrideStyles does not replace each other in slots', async function test(t = {}) { if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + this?.skip?.() ?? t?.skip(); } const { muiName, classes, testStateOverrides, render, ThemeProvider, createTheme } = @@ -905,9 +911,11 @@ function testThemeStyleOverrides( */ function testThemeVariants(element: React.ReactElement, getOptions: () => ConformanceOptions) { describe('theme variants:', () => { - it("respect theme's variants", async function test() { + it("respect theme's variants", async function test(t = {}) { if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + this?.skip?.() ?? t?.skip(); } const { muiName, testVariantProps, render, ThemeProvider, createTheme } = getOptions(); @@ -960,9 +968,11 @@ function testThemeVariants(element: React.ReactElement, getOptions: () => C expect(getByTestId('without-props')).not.to.toHaveComputedStyle(testStyle); }); - it('supports custom variant', async function test() { + it('supports custom variant', async function test(t = {}) { if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + this?.skip?.() ?? t?.skip(); } const { muiName, testCustomVariant, render, ThemeProvider, createTheme } = getOptions(); @@ -1013,12 +1023,14 @@ function testThemeCustomPalette( getOptions: () => ConformanceOptions, ) { describe('theme extended palette:', () => { - it('should render without errors', function test() { + it('should render without errors', function test(t = {}) { const { render, ThemeProvider, createTheme } = getOptions(); if (!/jsdom/.test(window.navigator.userAgent) || !render || !ThemeProvider || !createTheme) { - this.skip(); + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + this?.skip?.() ?? t?.skip(); } - + // @ts-ignore const theme = createTheme({ palette: { custom: { @@ -1034,7 +1046,7 @@ function testThemeCustomPalette( }, }, }); - + // @ts-ignore expect(() => render({element})).not.to.throw(); }); }); @@ -1069,7 +1081,7 @@ function describeConformance( beforeEach(() => { originalMatchmedia = window.matchMedia; // Create mocks of localStorage getItem and setItem functions - Object.defineProperty(global, 'localStorage', { + Object.defineProperty(globalThis, 'localStorage', { value: { getItem: (key: string) => storage[key], setItem: (key: string, value: string) => { diff --git a/packages-internal/test-utils/src/describeSkipIf.tsx b/packages-internal/test-utils/src/describeSkipIf.tsx new file mode 100644 index 00000000000000..b1de04e1495447 --- /dev/null +++ b/packages-internal/test-utils/src/describeSkipIf.tsx @@ -0,0 +1,9 @@ +// Shim for vitest describe.skipIf to be able to run mocha and vitest side-by-side +// TODO: Remove after migration to vitest is complete +const describeSkipIf: (condition: boolean) => Mocha.PendingSuiteFunction = + (describe as any).skipIf ?? + function describeSkipIf(condition: boolean) { + return condition ? describe.skip : describe; + }; + +export default describeSkipIf; diff --git a/packages-internal/test-utils/src/index.ts b/packages-internal/test-utils/src/index.ts index 7eec3c3f704fe5..3c05d85cc51d96 100644 --- a/packages-internal/test-utils/src/index.ts +++ b/packages-internal/test-utils/src/index.ts @@ -15,6 +15,7 @@ export {} from './initMatchers'; export * as fireDiscreteEvent from './fireDiscreteEvent'; export { default as flushMicrotasks } from './flushMicrotasks'; export { default as reactMajor } from './reactMajor'; +export { default as describeSkipIf } from './describeSkipIf'; /** * Set to true if console logs during [lifecycles that are invoked twice in `React.StrictMode`](https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects) are suppressed. diff --git a/packages-internal/test-utils/src/initMatchers.test.js b/packages-internal/test-utils/src/initMatchers.test.js index ae6236539778ec..7af2d04adb5b8c 100644 --- a/packages-internal/test-utils/src/initMatchers.test.js +++ b/packages-internal/test-utils/src/initMatchers.test.js @@ -43,13 +43,12 @@ describe('custom matchers', () => { expect(caughtError.stack).to.include( 'Could not match the following console.error calls. ' + "Make sure previous actions didn't call console.error by wrapping them in expect(() => {}).not.toErrorDev(): \n\n" + - ' - "expected message"\n' + - ' at Context.', // `Context.it` in node 12.x, `Context.` in later node version + ' - "expected message"\n', ); // check that the top stackframe points to this test // if this test is moved to another file the next assertion fails expect(caughtError.stack).to.match( - /- "expected message"\s+at Context\.(|it) \(.+\/initMatchers\.test\.js:\d+:\d+\)/, + /- "expected message"\s+at .*\/initMatchers\.test\.js:\d+:\d+/, ); }); @@ -65,13 +64,12 @@ describe('custom matchers', () => { expect(caughtError.stack).to.include( 'Recorded unexpected console.error calls: \n\n' + ' - Expected #1 "expected message" to be included in \n' + - '"expected Message"\n' + - ' at callback', + '"expected Message"\n', ); // check that the top stackframe points to this test // if this test is moved to another file the next assertion fails expect(caughtError.stack).to.match( - /"expected Message"\s+at callback \(.+\/initMatchers\.test\.js:\d+:\d+\)/, + /"expected Message"\s+at .*\/initMatchers\.test\.js:\d+:\d+/, ); }); diff --git a/packages-internal/test-utils/src/initMatchers.ts b/packages-internal/test-utils/src/initMatchers.ts index c7beae7871e1c2..8ab13d2b5fd6e4 100644 --- a/packages-internal/test-utils/src/initMatchers.ts +++ b/packages-internal/test-utils/src/initMatchers.ts @@ -1,618 +1,7 @@ -import chai, { AssertionError } from 'chai'; +import chai from 'chai'; import chaiDom from 'chai-dom'; -import _ from 'lodash'; -import { isInaccessible } from '@testing-library/dom'; -import { prettyDOM } from '@testing-library/react/pure'; -import { computeAccessibleDescription, computeAccessibleName } from 'dom-accessibility-api'; -import formatUtil from 'format-util'; +import './chai.types'; +import chaiPlugin from './chaiPlugin'; chai.use(chaiDom); - -const isKarma = Boolean(process.env.KARMA); - -// https://stackoverflow.com/a/46755166/3406963 -declare global { - namespace Chai { - interface Assertion { - /** - * Checks `expectedStyle` is a subset of the elements inline style i.e. `element.style`. - * @example expect(element).toHaveInlineStyle({ width: '200px' }) - */ - toHaveInlineStyle( - expectedStyle: Partial< - Record< - Exclude< - keyof CSSStyleDeclaration, - | 'getPropertyPriority' - | 'getPropertyValue' - | 'item' - | 'removeProperty' - | 'setProperty' - | number - >, - string - > - >, - ): void; - /** - * Checks `expectedStyle` is a subset of the elements computed style i.e. `window.getComputedStyle(element)`. - * @example expect(element).toHaveComputedStyle({ width: '200px' }) - */ - toHaveComputedStyle( - expectedStyle: Partial< - Record< - Exclude< - keyof CSSStyleDeclaration, - | 'getPropertyPriority' - | 'getPropertyValue' - | 'item' - | 'removeProperty' - | 'setProperty' - | number - >, - string - > - >, - ): void; - /** - * Check if an element's [`visibility`](https://developer.mozilla.org/en-US/docs/Web/CSS/visibility) is not `hidden` or `collapsed`. - */ - toBeVisible(): void; - /** - * Check if an element's [`visibility`](https://developer.mozilla.org/en-US/docs/Web/CSS/visibility) is `hidden` or `collapsed`. - */ - toBeHidden(): void; - /** - * Checks if the element is inaccessible. - * - * Elements are considered inaccessible if they either: - * - have [`visibility`](https://developer.mozilla.org/en-US/docs/Web/CSS/visibility) `hidden` - * - have [`display`](https://developer.mozilla.org/en-US/docs/Web/CSS/display) `none` - * - have `aria-hidden` `true` or any of their parents - * - * @see [Excluding Elements from the Accessibility Tree](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion) - */ - toBeInaccessible(): void; - toHaveAccessibleDescription(description: string): void; - /** - * Checks if the accessible name computation (according to `accname` spec) - * matches the expectation. - * - * @see https://www.w3.org/TR/accname-1.2/ - * @param name - */ - toHaveAccessibleName(name: string): void; - /** - * Checks if the element is actually focused i.e. `document.activeElement` is equal to the actual element. - */ - toHaveFocus(): void; - /** - * Checks if the element is the active-descendant of the active element. - */ - toHaveVirtualFocus(): void; - /** - * Matches calls to `console.warn` in the asserted callback. - * - * @example expect(() => render()).not.toWarnDev() - * @example expect(() => render()).toWarnDev('single message') - * @example expect(() => render()).toWarnDev(['first warning', 'then the second']) - */ - toWarnDev(messages?: string | readonly (string | boolean)[]): void; - /** - * Matches calls to `console.error` in the asserted callback. - * - * @example expect(() => render()).not.toErrorDev() - * @example expect(() => render()).toErrorDev('single message') - * @example expect(() => render()).toErrorDev(['first warning', 'then the second']) - */ - toErrorDev(messages?: string | readonly (string | boolean)[]): void; - /** - * Asserts that the given callback throws an error matching the given message in development (process.env.NODE_ENV !== 'production'). - * In production it expects a minified error. - */ - toThrowMinified(message: string): void; - } - } -} - -function isInJSDOM() { - return /jsdom/.test(window.navigator.userAgent); -} - -// chai#utils.elToString that looks like stringified elements in testing-library -function elementToString(element: Element | null | undefined) { - if (typeof element?.nodeType === 'number') { - return prettyDOM(element, undefined, { highlight: !isKarma, maxDepth: 1 }); - } - return String(element); -} -chai.use((chaiAPI, utils) => { - const blockElements = new Set([ - 'html', - 'address', - 'blockquote', - 'body', - 'dd', - 'div', - 'dl', - 'dt', - 'fieldset', - 'form', - 'frame', - 'frameset', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'noframes', - 'ol', - 'p', - 'ul', - 'center', - 'dir', - 'hr', - 'menu', - 'pre', - ]); - - function pretendVisibleGetComputedStyle(element: Element): CSSStyleDeclaration { - // `CSSStyleDeclaration` is not constructable - // https://stackoverflow.com/a/52732909/3406963 - // this is not equivalent to the declaration from `getComputedStyle` - // for example `getComputedStyle` would return a readonly declaration - // let's hope this doesn't get passed around until it's no longer clear where it comes from - const declaration = document.createElement('span').style; - - // initial values - declaration.content = ''; - // technically it's `inline`. We partially apply the default user agent sheet (chrome) here - // we're only interested in elements that use block - declaration.display = blockElements.has(element.tagName) ? 'block' : 'inline'; - declaration.visibility = 'visible'; - - return declaration; - } - - // better diff view for expect(element).to.equal(document.activeElement) - chai.Assertion.addMethod('toHaveFocus', function elementIsFocused() { - const element = utils.flag(this, 'object'); - - this.assert( - element === document.activeElement, - // karma does not show the diff like mocha does - `expected element to have focus${isKarma ? '\nexpected #{exp}\nactual: #{act}' : ''}`, - `expected element to NOT have focus \n${elementToString(element)}`, - elementToString(element), - elementToString(document.activeElement), - ); - }); - - chai.Assertion.addMethod('toHaveVirtualFocus', function elementIsVirtuallyFocused() { - const element = utils.flag(this, 'object'); - const id = element.getAttribute('id'); - - const virtuallyFocusedElementId = document.activeElement!.getAttribute('aria-activedescendant'); - - this.assert( - virtuallyFocusedElementId === id, - `expected element to be virtually focused\nexpected id #{exp}\n${ - virtuallyFocusedElementId === null - ? `activeElement: ${elementToString(document.activeElement)}` - : 'actual id: #{act}' - }`, - 'expected element to NOT to be virtually focused', - id, - virtuallyFocusedElementId, - virtuallyFocusedElementId !== null, - ); - }); - - chai.Assertion.addMethod('toBeInaccessible', function elementIsAccessible() { - const element = utils.flag(this, 'object'); - - const inaccessible = isInaccessible(element); - - this.assert( - inaccessible === true, - `expected \n${elementToString(element)} to be inaccessible but it was accessible`, - `expected \n${elementToString(element)} to be accessible but it was inaccessible`, - // Not interested in a diff but the typings require the 4th parameter. - undefined, - ); - }); - - chai.Assertion.addMethod('toHaveAccessibleName', function hasAccessibleName(expectedName) { - const root = utils.flag(this, 'object'); - // make sure it's an Element - new chai.Assertion(root.nodeType, `Expected an Element but got '${String(root)}'`).to.equal(1); - - const actualName = computeAccessibleName(root, { - computedStyleSupportsPseudoElements: !isInJSDOM(), - // in local development we pretend to be visible. full getComputedStyle is - // expensive and reserved for CI - getComputedStyle: process.env.CI ? undefined : pretendVisibleGetComputedStyle, - }); - - this.assert( - actualName === expectedName, - `expected \n${elementToString(root)} to have accessible name #{exp} but got #{act} instead.`, - `expected \n${elementToString(root)} not to have accessible name #{exp}.`, - expectedName, - actualName, - ); - }); - - chai.Assertion.addMethod( - 'toHaveAccessibleDescription', - function hasAccessibleDescription(expectedDescription) { - const root = utils.flag(this, 'object'); - // make sure it's an Element - new chai.Assertion(root.nodeType, `Expected an Element but got '${String(root)}'`).to.equal( - 1, - ); - - const actualDescription = computeAccessibleDescription(root, { - // in local development we pretend to be visible. full getComputedStyle is - // expensive and reserved for CI - getComputedStyle: process.env.CI ? undefined : pretendVisibleGetComputedStyle, - }); - - const possibleDescriptionComputationMessage = root.hasAttribute('title') - ? ' computeAccessibleDescription can be misleading when a `title` attribute is used. This might be a bug in `dom-accessibility-api`.' - : ''; - this.assert( - actualDescription === expectedDescription, - `expected \n${elementToString( - root, - )} to have accessible description #{exp} but got #{act} instead.${possibleDescriptionComputationMessage}`, - `expected \n${elementToString( - root, - )} not to have accessible description #{exp}.${possibleDescriptionComputationMessage}`, - expectedDescription, - actualDescription, - ); - }, - ); - - /** - * Correct name for `to.be.visible` - */ - chai.Assertion.addMethod('toBeVisible', function toBeVisible() { - // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/no-unused-expressions - new chai.Assertion(this._obj).to.be.visible; - }); - - /** - * Correct name for `not.to.be.visible` - */ - chai.Assertion.addMethod('toBeHidden', function toBeHidden() { - // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/no-unused-expressions - new chai.Assertion(this._obj).not.to.be.visible; - }); - - function assertMatchingStyles( - this: Chai.AssertionStatic, - actualStyleDeclaration: CSSStyleDeclaration, - expectedStyleUnnormalized: Record, - options: { styleTypeHint: string }, - ): void { - const { styleTypeHint } = options; - - // Compare objects using hyphen case. - // This is closer to actual CSS and required for getPropertyValue anyway. - const expectedStyle: Record = {}; - Object.keys(expectedStyleUnnormalized).forEach((cssProperty) => { - const hyphenCasedPropertyName = _.kebabCase(cssProperty); - const isVendorPrefixed = /^(moz|ms|o|webkit)-/.test(hyphenCasedPropertyName); - const propertyName = isVendorPrefixed - ? `-${hyphenCasedPropertyName}` - : hyphenCasedPropertyName; - expectedStyle[propertyName] = expectedStyleUnnormalized[cssProperty]; - }); - - const shorthandProperties = new Set([ - 'all', - 'animation', - 'background', - 'border', - 'border-block-end', - 'border-block-start', - 'border-bottom', - 'border-color', - 'border-image', - 'border-inline-end', - 'border-inline-start', - 'border-left', - 'border-radius', - 'border-right', - 'border-style', - 'border-top', - 'border-width', - 'column-rule', - 'columns', - 'flex', - 'flex-flow', - 'font', - 'gap', - 'grid', - 'grid-area', - 'grid-column', - 'grid-row', - 'grid-template', - 'list-style', - 'margin', - 'mask', - 'offset', - 'outline', - 'overflow', - 'padding', - 'place-content', - 'place-items', - 'place-self', - 'scroll-margin', - 'scroll-padding', - 'text-decoration', - 'text-emphasis', - 'transition', - ]); - const usedShorthandProperties = Object.keys(expectedStyle).filter((cssProperty) => { - return shorthandProperties.has(cssProperty); - }); - if (usedShorthandProperties.length > 0) { - throw new Error( - [ - `Shorthand properties are not supported in ${styleTypeHint} styles matchers since browsers can compute them differently. `, - 'Use longhand properties instead for the follow shorthand properties:\n', - usedShorthandProperties - .map((cssProperty) => { - return `- https://developer.mozilla.org/en-US/docs/Web/CSS/${cssProperty}#constituent_properties`; - }) - .join('\n'), - ].join(''), - ); - } - - const actualStyle: Record = {}; - Object.keys(expectedStyle).forEach((cssProperty) => { - actualStyle[cssProperty] = actualStyleDeclaration.getPropertyValue(cssProperty); - }); - - const jsdomHint = - 'Styles in JSDOM e.g. from `test:unit` are often misleading since JSDOM does not implement the Cascade nor actual CSS property value computation. ' + - 'If results differ between real browsers and JSDOM, skip the test in JSDOM e.g. `if (/jsdom/.test(window.navigator.userAgent)) this.skip();`'; - const shorthandHint = - 'Browsers can compute shorthand properties differently. Prefer longhand properties e.g. `borderTopColor`, `borderRightColor` etc. instead of `border` or `border-color`.'; - const messageHint = `${jsdomHint}\n${shorthandHint}`; - - if (isKarma) { - // `#{exp}` and `#{act}` placeholders escape the newlines - const expected = JSON.stringify(expectedStyle, null, 2); - const actual = JSON.stringify(actualStyle, null, 2); - // karma's `dots` reporter does not support diffs - this.assert( - // TODO Fix upstream docs/types - (utils as any).eql(actualStyle, expectedStyle), - `expected ${styleTypeHint} style of #{this} did not match\nExpected:\n${expected}\nActual:\n${actual}\n\n\n${messageHint}`, - `expected #{this} to not have ${styleTypeHint} style\n${expected}\n\n\n${messageHint}`, - expectedStyle, - actualStyle, - ); - } else { - this.assert( - // TODO Fix upstream docs/types - (utils as any).eql(actualStyle, expectedStyle), - `expected #{this} to have ${styleTypeHint} style #{exp} \n\n${messageHint}`, - `expected #{this} not to have ${styleTypeHint} style #{exp}${messageHint}`, - expectedStyle, - actualStyle, - true, - ); - } - } - - chai.Assertion.addMethod( - 'toHaveInlineStyle', - function toHaveInlineStyle(expectedStyleUnnormalized: Record) { - const element = utils.flag(this, 'object') as HTMLElement; - if (element?.nodeType !== 1) { - // Same pre-condition for negated and unnegated assertion - throw new AssertionError(`Expected an Element but got ${String(element)}`); - } - - assertMatchingStyles.call(this, element.style, expectedStyleUnnormalized, { - styleTypeHint: 'inline', - }); - }, - ); - - chai.Assertion.addMethod( - 'toHaveComputedStyle', - function toHaveComputedStyle(expectedStyleUnnormalized: Record) { - const element = utils.flag(this, 'object') as HTMLElement; - if (element?.nodeType !== 1) { - // Same pre-condition for negated and unnegated assertion - throw new AssertionError(`Expected an Element but got ${String(element)}`); - } - const computedStyle = element.ownerDocument.defaultView!.getComputedStyle(element); - - assertMatchingStyles.call(this, computedStyle, expectedStyleUnnormalized, { - styleTypeHint: 'computed', - }); - }, - ); - - chai.Assertion.addMethod('toThrowMinified', function toThrowMinified(expectedDevMessage) { - // TODO: Investigate if `as any` can be removed after https://github.com/DefinitelyTyped/DefinitelyTyped/issues/48634 is resolved. - if (process.env.NODE_ENV !== 'production') { - (this as any).to.throw(expectedDevMessage); - } else { - utils.flag( - this, - 'message', - "Looks like the error was not minified. This can happen if the error code hasn't been generated yet. Run `pnpm extract-error-codes` and try again.", - ); - // TODO: Investigate if `as any` can be removed after https://github.com/DefinitelyTyped/DefinitelyTyped/issues/48634 is resolved. - (this as any).to.throw('Minified MUI error', 'helper'); - } - }); -}); - -chai.use((chaiAPI, utils) => { - function addConsoleMatcher(matcherName: string, methodName: 'error' | 'warn') { - /** - * @param {string[]} expectedMessages - */ - function matcher(this: Chai.AssertionStatic, expectedMessagesInput = []) { - // documented pattern to get the actual value of the assertion - // eslint-disable-next-line no-underscore-dangle - const callback = this._obj; - - if (process.env.NODE_ENV !== 'production') { - const expectedMessages = - typeof expectedMessagesInput === 'string' - ? [expectedMessagesInput] - : expectedMessagesInput.slice(); - const unexpectedMessages: Error[] = []; - // TODO Remove type once MUI X enables noImplicitAny - let caughtError: unknown | null = null; - - this.assert( - expectedMessages.length > 0, - `Expected to call console.${methodName} but didn't provide messages. ` + - `If you don't expect any messages prefer \`expect().not.${matcherName}();\`.`, - `Expected no call to console.${methodName} while also expecting messages.` + - 'Expected no call to console.error but provided messages. ' + - "If you want to make sure a certain message isn't logged prefer the positive. " + - 'By expecting certain messages you automatically expect that no other messages are logged', - // Not interested in a diff but the typings require the 4th parameter. - undefined, - ); - - // Ignore skipped messages in e.g. `[condition && 'foo']` - const remainingMessages = expectedMessages.filter((messageOrFalse) => { - return messageOrFalse !== false; - }); - - // eslint-disable-next-line no-console - const originalMethod = console[methodName]; - - let messagesMatched = 0; - const consoleMatcher = (format: string, ...args: readonly unknown[]) => { - // Ignore legacy root deprecation warnings - // TODO: Remove once we no longer use legacy roots. - if ( - format.includes('Use createRoot instead.') || - format.includes('Use hydrateRoot instead.') - ) { - return; - } - const actualMessage = formatUtil(format, ...args); - const expectedMessage = remainingMessages.shift(); - messagesMatched += 1; - - // TODO Remove type once MUI X enables noImplicitAny - let message: string | null = null; - if (expectedMessage === undefined) { - message = `Expected no more error messages but got:\n"${actualMessage}"`; - } else if (!actualMessage.includes(expectedMessage)) { - message = `Expected #${messagesMatched} "${expectedMessage}" to be included in \n"${actualMessage}"`; - } - - if (message !== null) { - const error = new Error(message); - - const { stack: fullStack } = error; - const fullStacktrace = fullStack!.replace(`Error: ${message}\n`, '').split('\n'); - - const usefulStacktrace = fullStacktrace - // - // first line points to this frame which is irrelevant for the tester - .slice(1); - const usefulStack = `${message}\n${usefulStacktrace.join('\n')}`; - - error.stack = usefulStack; - unexpectedMessages.push(error); - } - }; - // eslint-disable-next-line no-console - console[methodName] = consoleMatcher; - - try { - callback(); - } catch (error) { - caughtError = error; - } finally { - // eslint-disable-next-line no-console - console[methodName] = originalMethod; - - // unexpected thrown error takes precedence over unexpected console call - if (caughtError !== null) { - // not the same pattern as described in the block because we don't rethrow in the catch - // eslint-disable-next-line no-unsafe-finally - throw caughtError; - } - - const formatMessages = (messages: ReadonlyArray) => { - const formattedMessages = messages.map((message) => { - if (typeof message === 'string') { - return `"${message}"`; - } - // full Error - return `${message.stack}`; - }); - return `\n\n - ${formattedMessages.join('\n\n- ')}`; - }; - - const shouldHaveWarned = utils.flag(this, 'negate') !== true; - - // unreachable from expect().not.toWarnDev(messages) - if (unexpectedMessages.length > 0) { - const unexpectedMessageRecordedMessage = `Recorded unexpected console.${methodName} calls: ${formatMessages( - unexpectedMessages, - )}`; - // chai will duplicate the stack frames from the unexpected calls in their assertion error - // it's not ideal but the test failure is located the second to last stack frame - // and the origin of the call is the second stackframe in the stack - this.assert( - // force chai to always trigger an assertion error - !shouldHaveWarned, - unexpectedMessageRecordedMessage, - unexpectedMessageRecordedMessage, - // Not interested in a diff but the typings require the 4th parameter. - undefined, - ); - } - - if (shouldHaveWarned) { - this.assert( - remainingMessages.length === 0, - `Could not match the following console.${methodName} calls. ` + - `Make sure previous actions didn't call console.${methodName} by wrapping them in expect(() => {}).not.${matcherName}(): ${formatMessages( - remainingMessages, - )}`, - `Impossible state reached in \`expect().${matcherName}()\`. ` + - `This is a bug in the matcher.`, - // Not interested in a diff but the typings require the 4th parameter. - undefined, - ); - } - } - } else { - // nothing to do in prod - // If there are still console calls than our test setup throws. - callback(); - } - } - - chai.Assertion.addMethod(matcherName, matcher); - } - - /** - * @example expect(() => render()).toWarnDev('single message') - * @example expect(() => render()).toWarnDev(['first warning', 'then the second']) - */ - addConsoleMatcher('toWarnDev', 'warn'); - addConsoleMatcher('toErrorDev', 'error'); -}); +chai.use(chaiPlugin); diff --git a/packages-internal/test-utils/src/mochaHooks.test.js b/packages-internal/test-utils/src/mochaHooks.test.js index 2628a7a0543d68..f3f8dcfbbb5a66 100644 --- a/packages-internal/test-utils/src/mochaHooks.test.js +++ b/packages-internal/test-utils/src/mochaHooks.test.js @@ -4,8 +4,9 @@ import * as React from 'react'; import { stub } from 'sinon'; import { createMochaHooks } from './mochaHooks'; import { createRenderer, act } from './createRenderer'; +import describeSkipIf from './describeSkipIf'; -describe('mochaHooks', () => { +describeSkipIf(process.env.VITEST)('mochaHooks', () => { // one block per hook. describe('afterEach', () => { describe('on unexpected console.(warn|error) in afterEach', function suite() { diff --git a/packages-internal/test-utils/src/setupJSDOM.js b/packages-internal/test-utils/src/setupJSDOM.js index 49523ac38da135..916f5837685427 100644 --- a/packages-internal/test-utils/src/setupJSDOM.js +++ b/packages-internal/test-utils/src/setupJSDOM.js @@ -4,8 +4,8 @@ const createDOM = require('./createDOM'); const { createMochaHooks } = require('./mochaHooks'); // Enable missing act warnings: https://github.com/reactwg/react-18/discussions/102 -global.jest = null; -global.IS_REACT_ACT_ENVIRONMENT = true; +globalThis.jest = null; +globalThis.IS_REACT_ACT_ENVIRONMENT = true; createDOM(); require('./init'); diff --git a/packages-internal/test-utils/src/setupVitest.ts b/packages-internal/test-utils/src/setupVitest.ts new file mode 100644 index 00000000000000..d52705474f2c6c --- /dev/null +++ b/packages-internal/test-utils/src/setupVitest.ts @@ -0,0 +1,117 @@ +import { beforeAll, afterAll, it } from 'vitest'; +import * as testingLibrary from '@testing-library/dom'; +import failOnConsole from 'vitest-fail-on-console'; +import './initMatchers'; + +// checking if an element is hidden is quite expensive +// this is only done in CI as a fail safe. It can still explicitly be checked +// in the test files which helps documenting what is part of the DOM but hidden +// from assistive technology +const defaultHidden = !process.env.CI; + +// adds verbosity for something that might be confusing +console.warn(`${defaultHidden ? 'including' : 'excluding'} inaccessible elements by default`); + +testingLibrary.configure({ + // JSDOM logs errors otherwise on `getComputedStyle(element, pseudoElement)` calls. + computedStyleSupportsPseudoElements: false, + defaultHidden, +}); + +// Enable missing act warnings: https://github.com/reactwg/react-18/discussions/102 +(globalThis as any).jest = null; +(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; + +failOnConsole({ + silenceMessage: (message) => { + if (process.env.NODE_ENV === 'production') { + // TODO: mock scheduler + if (message.includes('act(...) is not supported in production builds of React')) { + return true; + } + } + + if (message.includes('Warning: useLayoutEffect does nothing on the server')) { + // Controversial warning that is commonly ignored by switching to `useEffect` on the server. + // https://github.com/facebook/react/issues/14927 + // However, this switch doesn't work since it relies on environment sniffing and we test SSR in a browser environment. + return true; + } + + // Unclear why this is an issue for the current occurrences of this warning. + // TODO: Revisit once https://github.com/facebook/react/issues/22796 is resolved + if ( + message.includes( + 'Detected multiple renderers concurrently rendering the same context provider.', + ) + ) { + return true; + } + + return false; + }, +}); + +function wrapIt(itFn: typeof it.only) { + return function wrapper(name: string, fn: Function) { + return itFn(name, (context) => { + return fn?.call({ + ...context, + currentTest: { + fullTitle: () => context.task.name, + }, + }); + }); + }; +} + +const wrappedIt: any = wrapIt(it); +wrappedIt.skip = wrapIt(it.skip); +wrappedIt.only = wrapIt(it.only); + +(globalThis as any).it = wrappedIt; + +if (!globalThis.before) { + (globalThis as any).before = beforeAll; +} +if (!globalThis.after) { + (globalThis as any).after = afterAll; +} +if (!globalThis.specify) { + (globalThis as any).specify = wrappedIt; +} + +const isJsdom = typeof window !== 'undefined' && window.navigator.userAgent.includes('jsdom'); + +// Only necessary when not in browser mode. +if (isJsdom) { + class Touch { + instance: any; + + constructor(instance: any) { + this.instance = instance; + } + + get identifier() { + return this.instance.identifier; + } + + get pageX() { + return this.instance.pageX; + } + + get pageY() { + return this.instance.pageY; + } + + get clientX() { + return this.instance.clientX; + } + + get clientY() { + return this.instance.clientY; + } + } + // @ts-expect-error + globalThis.window.Touch = Touch; +} diff --git a/packages-internal/test-utils/tsconfig.json b/packages-internal/test-utils/tsconfig.json index 6ba9f9bb572db7..2b79bf3292d517 100644 --- a/packages-internal/test-utils/tsconfig.json +++ b/packages-internal/test-utils/tsconfig.json @@ -3,11 +3,14 @@ "lib": ["es2020", "dom"], "noEmit": true, "moduleResolution": "node", - "types": ["node"], + "types": ["node", "mocha"], "strict": true, "esModuleInterop": true, "isolatedModules": true, - "jsx": "react" + "jsx": "react", + // https://github.com/vitest-dev/vitest/issues/4688 + // TODO: Remove after fully migrated chai to vitest + "skipLibCheck": true }, "include": ["./src/**/*"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5338c59c8a5e94..42d390df023712 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,6 +137,12 @@ importers: '@typescript-eslint/parser': specifier: ^7.18.0 version: 7.18.0(eslint@8.57.1)(typescript@5.6.3) + '@vitest/browser': + specifier: ^2.1.2 + version: 2.1.2(@vitest/spy@2.1.2)(playwright@1.48.2)(typescript@5.6.3)(vite@5.4.10(@types/node@20.17.6)(terser@5.29.2))(vitest@2.1.2) + '@vitest/coverage-v8': + specifier: ^2.1.2 + version: 2.1.2(@vitest/browser@2.1.2)(vitest@2.1.2) babel-loader: specifier: ^9.2.1 version: 9.2.1(@babel/core@7.26.0)(webpack@5.96.1) @@ -305,6 +311,12 @@ importers: typescript: specifier: ^5.6.3 version: 5.6.3 + vitest: + specifier: ^2.1.2 + version: 2.1.2(@types/node@20.17.6)(@vitest/browser@2.1.2)(happy-dom@12.10.3)(jsdom@24.0.0)(msw@2.4.9(typescript@5.6.3))(terser@5.29.2) + vitest-fail-on-console: + specifier: ^0.7.1 + version: 0.7.1(vite@5.4.10(@types/node@20.17.6)(terser@5.29.2))(vitest@2.1.2) webpack: specifier: ^5.96.1 version: 5.96.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.96.1)) @@ -3148,6 +3160,15 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@bundled-es-modules/cookie@2.0.0': + resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + '@chakra-ui/anatomy@2.2.2': resolution: {integrity: sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==} @@ -3830,6 +3851,26 @@ packages: cpu: [x64] os: [win32] + '@inquirer/confirm@3.2.0': + resolution: {integrity: sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw==} + engines: {node: '>=18'} + + '@inquirer/core@9.2.1': + resolution: {integrity: sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==} + engines: {node: '>=18'} + + '@inquirer/figures@1.0.7': + resolution: {integrity: sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==} + engines: {node: '>=18'} + + '@inquirer/type@1.5.5': + resolution: {integrity: sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==} + engines: {node: '>=18'} + + '@inquirer/type@2.0.0': + resolution: {integrity: sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==} + engines: {node: '>=18'} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -3898,6 +3939,10 @@ packages: resolution: {integrity: sha512-DPnl5lPX4v49eVxEbJnAizrpMdMTBz1qykZrAbBul9rfgk531v8oAt+Pm6O/rpAleRombNM7FJb5rYGzBJatOQ==} engines: {node: '>=18.0.0'} + '@mswjs/interceptors@0.35.9': + resolution: {integrity: sha512-SSnyl/4ni/2ViHKkiZb8eajA/eN1DNFaHjhGiLUdZvDz6PKF4COSf/17xqSz64nOo2Ia29SA6B2KNCsyCbVmaQ==} + engines: {node: '>=18'} + '@mui/base@5.0.0-beta.30': resolution: {integrity: sha512-dc38W4W3K42atE9nSaOeoJ7/x9wGIfawdwC/UmMxMLlZ1iSsITQ8dQJaTATCbn98YvYPINK/EH541YA5enQIPQ==} engines: {node: '>=12.0.0'} @@ -4826,6 +4871,15 @@ packages: '@octokit/types@9.3.2': resolution: {integrity: sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@opentelemetry/api@1.8.0': resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==} engines: {node: '>=8.0.0'} @@ -4857,8 +4911,8 @@ packages: engines: {node: '>=18'} hasBin: true - '@polka/url@1.0.0-next.21': - resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} + '@polka/url@1.0.0-next.28': + resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -5422,6 +5476,9 @@ packages: '@types/cookie@0.4.1': resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/cors@2.8.12': resolution: {integrity: sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==} @@ -5545,6 +5602,9 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + '@types/mute-stream@0.0.4': + resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} + '@types/node-forge@1.3.11': resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} @@ -5611,12 +5671,18 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/statuses@2.0.5': + resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + '@types/styled-system@5.1.15': resolution: {integrity: sha512-1uls4wipZn8FtYFZ7upRVFDoEeOXTQTs2zuyOZPn02T6rjIxtvj2P2lG5qsxXHhKuKsu3thveCZrtaeLE/ibLg==} '@types/stylis@4.2.5': resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/unist@3.0.2': resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} @@ -5632,6 +5698,9 @@ packages: '@types/webxr@0.5.14': resolution: {integrity: sha512-UEMMm/Xn3DtEa+gpzUrOcDj+SJS1tk5YodjwOxcqStNhCfPcwgyC5Srg2ToVKyg2Fhq16Ffpb0UWUQHqoT9AMA==} + '@types/wrap-ansi@3.0.0': + resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} + '@types/ws@8.5.13': resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} @@ -5717,6 +5786,60 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 + '@vitest/browser@2.1.2': + resolution: {integrity: sha512-tqpGfz2sfjFFNuZ2iLZ6EGRVnH8z18O93ZIicbLsxDhiLgRNz84UcjSvX4pbheuddW+BJeNbLGdM3BU8vohbEg==} + peerDependencies: + playwright: '*' + safaridriver: '*' + vitest: 2.1.2 + webdriverio: '*' + peerDependenciesMeta: + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + + '@vitest/coverage-v8@2.1.2': + resolution: {integrity: sha512-b7kHrFrs2urS0cOk5N10lttI8UdJ/yP3nB4JYTREvR5o18cR99yPpK4gK8oQgI42BVv0ILWYUSYB7AXkAUDc0g==} + peerDependencies: + '@vitest/browser': 2.1.2 + vitest: 2.1.2 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@2.1.2': + resolution: {integrity: sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==} + + '@vitest/mocker@2.1.2': + resolution: {integrity: sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==} + peerDependencies: + '@vitest/spy': 2.1.2 + msw: ^2.3.5 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.2': + resolution: {integrity: sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==} + + '@vitest/runner@2.1.2': + resolution: {integrity: sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==} + + '@vitest/snapshot@2.1.2': + resolution: {integrity: sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==} + + '@vitest/spy@2.1.2': + resolution: {integrity: sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==} + + '@vitest/utils@2.1.2': + resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==} + '@webassemblyjs/ast@1.12.1': resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} @@ -6081,6 +6204,10 @@ packages: assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.7: resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} @@ -6397,6 +6524,10 @@ packages: engines: {node: '>=10.12.0'} hasBin: true + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + cacache@18.0.3: resolution: {integrity: sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==} engines: {node: ^16.14.0 || >=18.0.0} @@ -6468,6 +6599,10 @@ packages: resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} engines: {node: '>=4'} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + chainsaw@0.1.0: resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} @@ -6511,6 +6646,10 @@ packages: check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -6591,6 +6730,10 @@ packages: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -6839,6 +6982,10 @@ packages: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} + cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + cookie@0.7.1: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} @@ -7186,6 +7333,10 @@ packages: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -7724,6 +7875,9 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -8166,9 +8320,8 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - glob@10.3.10: - resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} - engines: {node: '>=16 || 14 >=14.17'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true glob@11.0.0: @@ -8254,6 +8407,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql@16.9.0: + resolution: {integrity: sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + gtoken@7.0.1: resolution: {integrity: sha512-KcFVtoP1CVFtQu0aSk3AyAt2og66PFhZAlkUOuWKwzMLoulHXG5W5wE5xAnHb+yl3/wEFoqGW7/cDGMU8igDZQ==} engines: {node: '>=14.0.0'} @@ -8338,6 +8495,9 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + hermes-estree@0.20.1: resolution: {integrity: sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==} @@ -8685,6 +8845,9 @@ packages: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} @@ -8844,8 +9007,8 @@ packages: resolution: {integrity: sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==} engines: {node: '>=6'} - istanbul-lib-coverage@3.2.0: - resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} istanbul-lib-hook@3.0.0: @@ -8860,9 +9023,9 @@ packages: resolution: {integrity: sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==} engines: {node: '>=8'} - istanbul-lib-report@3.0.0: - resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} - engines: {node: '>=8'} + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} istanbul-lib-source-maps@3.0.6: resolution: {integrity: sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==} @@ -8872,8 +9035,12 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} - istanbul-reports@3.1.5: - resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==} + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} engines: {node: '>=8'} iterator.prototype@1.1.3: @@ -8885,9 +9052,8 @@ packages: peerDependencies: react: '>=18.0' - jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} jackspeak@4.0.1: resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==} @@ -9417,6 +9583,9 @@ packages: loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} @@ -9444,6 +9613,9 @@ packages: magic-string@0.30.11: resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -9872,8 +10044,8 @@ packages: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} - mrmime@1.0.1: - resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} + mrmime@2.0.0: + resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} engines: {node: '>=10'} ms@2.0.0: @@ -9885,6 +10057,16 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.4.9: + resolution: {integrity: sha512-1m8xccT6ipN4PTqLinPwmzhxQREuxaEJYdx4nIbggxP8aM7r1e71vE7RtOUSQoAm1LydjGfZKy7370XD/tsuYg==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + multimatch@5.0.0: resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} engines: {node: '>=10'} @@ -10263,6 +10445,9 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + override-require@1.1.1: resolution: {integrity: sha512-eoJ9YWxFcXbrn2U8FKT6RV+/Kj7fiGAB1VvHzbYKt8xM5ZuKZgCGvnHzDxmreEjcBH28ejg5MiOH4iyY1mQnkg==} @@ -10491,9 +10676,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.10.1: - resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} - engines: {node: '>=16 || 14 >=14.17'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} path-scurry@2.0.0: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} @@ -10530,6 +10715,10 @@ packages: pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + pause-stream@0.0.11: resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} @@ -11618,6 +11807,9 @@ packages: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -11635,8 +11827,8 @@ packages: sinon@19.0.2: resolution: {integrity: sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==} - sirv@2.0.3: - resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} + sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} sisteransi@1.0.5: @@ -11748,6 +11940,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -11763,6 +11958,9 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + stream-browserify@3.0.0: resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} @@ -11783,6 +11981,9 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-convert@0.2.1: resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} @@ -12070,6 +12271,10 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + text-extensions@1.9.0: resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} engines: {node: '>=0.10'} @@ -12124,6 +12329,24 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.1: + resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + + tinypool@1.0.1: + resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + title@3.5.3: resolution: {integrity: sha512-20JyowYglSEeCvZv3EZ0nZ046vLarO37prvV0mbtQV7C8DJPGgN967r8SJkqd3XK3K3lD3/Iyfp3avjfil8Q2Q==} hasBin: true @@ -12155,8 +12378,8 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - tough-cookie@4.1.3: - resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} tr46@0.0.3: @@ -12278,6 +12501,10 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} + type-fest@4.26.1: + resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==} + engines: {node: '>=16'} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -12510,6 +12737,11 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@2.1.2: + resolution: {integrity: sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite-plugin-node-polyfills@0.22.0: resolution: {integrity: sha512-F+G3LjiGbG8QpbH9bZ//GSBr9i1InSTkaulfUHFa9jkLqVGORFBoqc2A/Yu5Mmh1kNAbiAeKeK+6aaQUf3x0JA==} peerDependencies: @@ -12564,6 +12796,37 @@ packages: terser: optional: true + vitest-fail-on-console@0.7.1: + resolution: {integrity: sha512-/PjuonFu7CwUVrKaiQPIGXOtiEv2/Gz3o8MbLmovX9TGDxoRCctRC8CA9zJMRUd6AvwGu/V5a3znObTmlPNTgw==} + peerDependencies: + vite: '>=4.5.2' + vitest: '>=0.26.2' + + vitest@2.1.2: + resolution: {integrity: sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^20.17.6 + '@vitest/browser': 2.1.2 + '@vitest/ui': 2.1.2 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vlq@1.0.1: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} @@ -12712,6 +12975,11 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} @@ -12796,6 +13064,18 @@ packages: utf-8-validate: optional: true + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xcase@2.0.1: resolution: {integrity: sha512-UmFXIPU+9Eg3E9m/728Bii0lAIuoc+6nbrNUKaRPJOFp91ih44qqGlWtxMB6kXFrRD6po+86ksHM5XHCfk6iPw==} @@ -12892,6 +13172,10 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + yoctocolors@2.0.2: resolution: {integrity: sha512-Ct97huExsu7cWeEjmrXlofevF8CvzUglJ4iGUet5B8xn1oumtAZBpHU4GzYuoE6PVqcZ5hghtBrSlhwHuR1Jmw==} engines: {node: '>=18'} @@ -13956,6 +14240,19 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@bundled-es-modules/cookie@2.0.0': + dependencies: + cookie: 0.5.0 + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.1 + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + '@chakra-ui/anatomy@2.2.2': {} '@chakra-ui/color-mode@2.2.0(react@18.3.1)': @@ -14529,6 +14826,36 @@ snapshots: '@img/sharp-win32-x64@0.33.5': optional: true + '@inquirer/confirm@3.2.0': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 1.5.5 + + '@inquirer/core@9.2.1': + dependencies: + '@inquirer/figures': 1.0.7 + '@inquirer/type': 2.0.0 + '@types/mute-stream': 0.0.4 + '@types/node': 20.17.6 + '@types/wrap-ansi': 3.0.0 + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + + '@inquirer/figures@1.0.7': {} + + '@inquirer/type@1.5.5': + dependencies: + mute-stream: 1.0.0 + + '@inquirer/type@2.0.0': + dependencies: + mute-stream: 1.0.0 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -14698,6 +15025,15 @@ snapshots: - supports-color - typescript + '@mswjs/interceptors@0.35.9': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + '@mui/base@5.0.0-beta.30(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 @@ -15370,7 +15706,7 @@ snapshots: '@npmcli/map-workspaces@3.0.6': dependencies: '@npmcli/name-from-folder': 2.0.0 - glob: 10.3.10 + glob: 10.4.5 minimatch: 9.0.5 read-package-json-fast: 3.0.2 @@ -15392,7 +15728,7 @@ snapshots: '@npmcli/package-json@5.2.0': dependencies: '@npmcli/git': 5.0.3 - glob: 10.3.10 + glob: 10.4.5 hosted-git-info: 7.0.2 json-parse-even-better-errors: 3.0.2 normalize-package-data: 6.0.2 @@ -15699,6 +16035,15 @@ snapshots: dependencies: '@octokit/openapi-types': 18.0.0 + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + '@opentelemetry/api@1.8.0': optional: true @@ -15773,7 +16118,7 @@ snapshots: dependencies: playwright: 1.48.2 - '@polka/url@1.0.0-next.21': {} + '@polka/url@1.0.0-next.28': {} '@popperjs/core@2.11.8': {} @@ -16311,7 +16656,7 @@ snapshots: '@types/node': 20.17.6 '@types/ws': 8.5.13 eventemitter3: 5.0.1 - ws: 8.17.1 + ws: 8.18.0 transitivePeerDependencies: - bufferutil - debug @@ -16578,6 +16923,8 @@ snapshots: '@types/cookie@0.4.1': {} + '@types/cookie@0.6.0': {} + '@types/cors@2.8.12': {} '@types/css-mediaquery@0.1.4': {} @@ -16705,6 +17052,10 @@ snapshots: '@types/ms@0.7.34': {} + '@types/mute-stream@0.0.4': + dependencies: + '@types/node': 20.17.6 + '@types/node-forge@1.3.11': dependencies: '@types/node': 20.17.6 @@ -16779,12 +17130,16 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/statuses@2.0.5': {} + '@types/styled-system@5.1.15': dependencies: csstype: 3.1.3 '@types/stylis@4.2.5': {} + '@types/tough-cookie@4.0.5': {} + '@types/unist@3.0.2': {} '@types/use-sync-external-store@0.0.3': {} @@ -16795,6 +17150,8 @@ snapshots: '@types/webxr@0.5.14': {} + '@types/wrap-ansi@3.0.0': {} + '@types/ws@8.5.13': dependencies: '@types/node': 20.17.6 @@ -16914,6 +17271,88 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/browser@2.1.2(@vitest/spy@2.1.2)(playwright@1.48.2)(typescript@5.6.3)(vite@5.4.10(@types/node@20.17.6)(terser@5.29.2))(vitest@2.1.2)': + dependencies: + '@testing-library/dom': 10.4.0 + '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) + '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(msw@2.4.9(typescript@5.6.3))(vite@5.4.10(@types/node@20.17.6)(terser@5.29.2)) + '@vitest/utils': 2.1.2 + magic-string: 0.30.11 + msw: 2.4.9(typescript@5.6.3) + sirv: 2.0.4 + tinyrainbow: 1.2.0 + vitest: 2.1.2(@types/node@20.17.6)(@vitest/browser@2.1.2)(happy-dom@12.10.3)(jsdom@24.0.0)(msw@2.4.9(typescript@5.6.3))(terser@5.29.2) + ws: 8.18.0 + optionalDependencies: + playwright: 1.48.2 + transitivePeerDependencies: + - '@vitest/spy' + - bufferutil + - typescript + - utf-8-validate + - vite + + '@vitest/coverage-v8@2.1.2(@vitest/browser@2.1.2)(vitest@2.1.2)': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.7(supports-color@8.1.1) + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.11 + magicast: 0.3.5 + std-env: 3.7.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.2(@types/node@20.17.6)(@vitest/browser@2.1.2)(happy-dom@12.10.3)(jsdom@24.0.0)(msw@2.4.9(typescript@5.6.3))(terser@5.29.2) + optionalDependencies: + '@vitest/browser': 2.1.2(@vitest/spy@2.1.2)(playwright@1.48.2)(typescript@5.6.3)(vite@5.4.10(@types/node@20.17.6)(terser@5.29.2))(vitest@2.1.2) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.1.2': + dependencies: + '@vitest/spy': 2.1.2 + '@vitest/utils': 2.1.2 + chai: 5.1.2 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(msw@2.4.9(typescript@5.6.3))(vite@5.4.10(@types/node@20.17.6)(terser@5.29.2))': + dependencies: + '@vitest/spy': 2.1.2 + estree-walker: 3.0.3 + magic-string: 0.30.11 + optionalDependencies: + msw: 2.4.9(typescript@5.6.3) + vite: 5.4.10(@types/node@20.17.6)(terser@5.29.2) + + '@vitest/pretty-format@2.1.2': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.2': + dependencies: + '@vitest/utils': 2.1.2 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.2': + dependencies: + '@vitest/pretty-format': 2.1.2 + magic-string: 0.30.11 + pathe: 1.1.2 + + '@vitest/spy@2.1.2': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.2': + dependencies: + '@vitest/pretty-format': 2.1.2 + loupe: 3.1.2 + tinyrainbow: 1.2.0 + '@webassemblyjs/ast@1.12.1': dependencies: '@webassemblyjs/helper-numbers': 1.11.6 @@ -17371,6 +17810,8 @@ snapshots: assertion-error@1.1.0: {} + assertion-error@2.0.1: {} + ast-types-flow@0.0.7: {} ast-types@0.14.2: @@ -17785,20 +18226,22 @@ snapshots: '@istanbuljs/schema': 0.1.3 find-up: 5.0.0 foreground-child: 2.0.0 - istanbul-lib-coverage: 3.2.0 - istanbul-lib-report: 3.0.0 - istanbul-reports: 3.1.5 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.1.7 rimraf: 3.0.2 test-exclude: 6.0.0 v8-to-istanbul: 9.0.1 yargs: 16.2.0 yargs-parser: 20.2.9 + cac@6.7.14: {} + cacache@18.0.3: dependencies: '@npmcli/fs': 3.1.1 fs-minipass: 3.0.2 - glob: 10.3.10 + glob: 10.4.5 lru-cache: 10.4.3 minipass: 7.1.2 minipass-collect: 2.0.1 @@ -17880,6 +18323,14 @@ snapshots: pathval: 1.1.1 type-detect: 4.1.0 + chai@5.1.2: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 + chainsaw@0.1.0: dependencies: traverse: 0.3.9 @@ -17924,6 +18375,8 @@ snapshots: dependencies: get-func-name: 2.0.2 + check-error@2.1.1: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -18017,6 +18470,8 @@ snapshots: cli-width@3.0.0: {} + cli-width@4.1.0: {} + client-only@0.0.1: {} clipboard-copy@4.0.1: {} @@ -18285,6 +18740,8 @@ snapshots: cookie-signature@1.2.2: {} + cookie@0.5.0: {} + cookie@0.7.1: {} cookie@0.7.2: {} @@ -18691,6 +19148,8 @@ snapshots: dependencies: type-detect: 4.1.0 + deep-eql@5.0.2: {} + deep-extend@0.6.0: {} deep-is@0.1.4: {} @@ -19413,6 +19872,10 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + esutils@2.0.3: {} etag@1.8.1: {} @@ -19992,13 +20455,14 @@ snapshots: glob-to-regexp@0.4.1: {} - glob@10.3.10: + glob@10.4.5: dependencies: foreground-child: 3.3.0 - jackspeak: 2.3.6 + jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 - path-scurry: 1.10.1 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 glob@11.0.0: dependencies: @@ -20040,7 +20504,7 @@ snapshots: fs.realpath: 1.0.0 minimatch: 8.0.4 minipass: 4.2.8 - path-scurry: 1.10.1 + path-scurry: 1.11.1 global-modules@2.0.0: dependencies: @@ -20136,6 +20600,8 @@ snapshots: graphemer@1.4.0: {} + graphql@16.9.0: {} + gtoken@7.0.1(encoding@0.1.13): dependencies: gaxios: 6.1.1(encoding@0.1.13) @@ -20230,6 +20696,8 @@ snapshots: he@1.2.0: {} + headers-polyfill@4.0.3: {} + hermes-estree@0.20.1: {} hermes-estree@0.22.0: {} @@ -20585,6 +21053,8 @@ snapshots: is-negative-zero@2.0.3: {} + is-node-process@1.2.0: {} + is-number-object@1.0.7: dependencies: has-tostringtag: 1.0.2 @@ -20697,7 +21167,7 @@ snapshots: istanbul-lib-coverage@2.0.5: {} - istanbul-lib-coverage@3.2.0: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-hook@3.0.0: dependencies: @@ -20708,7 +21178,7 @@ snapshots: '@babel/core': 7.26.0 '@babel/parser': 7.26.1 '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.0 + istanbul-lib-coverage: 3.2.2 semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -20717,15 +21187,15 @@ snapshots: dependencies: archy: 1.0.0 cross-spawn: 7.0.3 - istanbul-lib-coverage: 3.2.0 + istanbul-lib-coverage: 3.2.2 p-map: 3.0.0 rimraf: 3.0.2 uuid: 8.3.2 - istanbul-lib-report@3.0.0: + istanbul-lib-report@3.0.1: dependencies: - istanbul-lib-coverage: 3.2.0 - make-dir: 3.1.0 + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 supports-color: 7.2.0 istanbul-lib-source-maps@3.0.6: @@ -20741,15 +21211,23 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: debug: 4.3.7(supports-color@8.1.1) - istanbul-lib-coverage: 3.2.0 + istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: - supports-color - istanbul-reports@3.1.5: + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.3.7(supports-color@8.1.1) + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: dependencies: html-escaper: 2.0.2 - istanbul-lib-report: 3.0.0 + istanbul-lib-report: 3.0.1 iterator.prototype@1.1.3: dependencies: @@ -20764,7 +21242,7 @@ snapshots: '@types/react-reconciler': 0.28.8 react: 18.3.1 - jackspeak@2.3.6: + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: @@ -20954,13 +21432,13 @@ snapshots: rrweb-cssom: 0.6.0 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 4.1.3 + tough-cookie: 4.1.4 w3c-xmlserializer: 5.0.0 webidl-conversions: 7.0.0 whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.0.0 - ws: 8.17.1 + ws: 8.18.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -21180,10 +21658,10 @@ snapshots: karma-coverage-istanbul-reporter@3.0.3: dependencies: - istanbul-lib-coverage: 3.2.0 - istanbul-lib-report: 3.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 3.0.6 - istanbul-reports: 3.1.5 + istanbul-reports: 3.1.7 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -21550,6 +22028,8 @@ snapshots: dependencies: get-func-name: 2.0.2 + loupe@3.1.2: {} + lower-case@2.0.2: dependencies: tslib: 2.6.2 @@ -21577,6 +22057,12 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.26.1 + '@babel/types': 7.26.0 + source-map-js: 1.2.1 + make-dir@2.1.0: dependencies: pify: 4.0.1 @@ -22229,7 +22715,7 @@ snapshots: mri@1.2.0: {} - mrmime@1.0.1: {} + mrmime@2.0.0: {} ms@2.0.0: {} @@ -22237,6 +22723,28 @@ snapshots: ms@2.1.3: {} + msw@2.4.9(typescript@5.6.3): + dependencies: + '@bundled-es-modules/cookie': 2.0.0 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 3.2.0 + '@mswjs/interceptors': 0.35.9 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.5 + chalk: 4.1.2 + graphql: 16.9.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + strict-event-emitter: 0.5.1 + type-fest: 4.26.1 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.6.3 + multimatch@5.0.0: dependencies: '@types/minimatch': 3.0.5 @@ -22374,7 +22882,7 @@ snapshots: dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.1 - glob: 10.3.10 + glob: 10.4.5 graceful-fs: 4.2.11 make-fetch-happen: 13.0.1 nopt: 7.2.1 @@ -22588,13 +23096,13 @@ snapshots: foreground-child: 3.3.0 get-package-type: 0.1.0 glob: 7.2.3 - istanbul-lib-coverage: 3.2.0 + istanbul-lib-coverage: 3.2.2 istanbul-lib-hook: 3.0.0 istanbul-lib-instrument: 6.0.2 istanbul-lib-processinfo: 2.0.3 - istanbul-lib-report: 3.0.0 + istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.5 + istanbul-reports: 3.1.7 make-dir: 3.1.0 node-preload: 0.2.1 p-map: 3.0.0 @@ -22753,6 +23261,8 @@ snapshots: os-tmpdir@1.0.2: {} + outvariant@1.4.3: {} + override-require@1.1.1: {} p-event@5.0.1: @@ -22976,7 +23486,7 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.10.1: + path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 minipass: 7.1.2 @@ -23006,6 +23516,8 @@ snapshots: pathval@1.1.1: {} + pathval@2.0.0: {} + pause-stream@0.0.11: dependencies: through: 2.3.8 @@ -24301,6 +24813,8 @@ snapshots: get-intrinsic: 1.2.4 object-inspect: 1.13.1 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -24329,10 +24843,10 @@ snapshots: nise: 6.1.1 supports-color: 7.2.0 - sirv@2.0.3: + sirv@2.0.4: dependencies: - '@polka/url': 1.0.0-next.21 - mrmime: 1.0.1 + '@polka/url': 1.0.0-next.28 + mrmime: 2.0.0 totalist: 3.0.1 sisteransi@1.0.5: {} @@ -24464,6 +24978,8 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + stackback@0.0.2: {} + stackframe@1.3.4: {} stacktrace-parser@0.1.10: @@ -24474,6 +24990,8 @@ snapshots: statuses@2.0.1: {} + std-env@3.7.0: {} + stream-browserify@3.0.0: dependencies: inherits: 2.0.4 @@ -24502,6 +25020,8 @@ snapshots: streamsearch@1.1.0: {} + strict-event-emitter@0.5.1: {} + string-convert@0.2.1: {} string-width@4.2.3: @@ -24883,6 +25403,12 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-extensions@1.9.0: {} text-table@0.2.0: {} @@ -24940,6 +25466,16 @@ snapshots: tiny-warning@1.0.3: {} + tinybench@2.9.0: {} + + tinyexec@0.3.1: {} + + tinypool@1.0.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + title@3.5.3: dependencies: arg: 1.0.0 @@ -24965,7 +25501,7 @@ snapshots: totalist@3.0.1: {} - tough-cookie@4.1.3: + tough-cookie@4.1.4: dependencies: psl: 1.9.0 punycode: 2.3.1 @@ -25062,6 +25598,8 @@ snapshots: type-fest@2.19.0: {} + type-fest@4.26.1: {} + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -25315,6 +25853,23 @@ snapshots: '@types/unist': 3.0.2 vfile-message: 4.0.2 + vite-node@2.1.2(@types/node@20.17.6)(terser@5.29.2): + dependencies: + cac: 6.7.14 + debug: 4.3.7(supports-color@8.1.1) + pathe: 1.1.2 + vite: 5.4.10(@types/node@20.17.6)(terser@5.29.2) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-plugin-node-polyfills@0.22.0(rollup@4.21.1)(vite@5.4.10(@types/node@20.17.6)(terser@5.29.2)): dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.21.1) @@ -25348,6 +25903,49 @@ snapshots: fsevents: 2.3.3 terser: 5.29.2 + vitest-fail-on-console@0.7.1(vite@5.4.10(@types/node@20.17.6)(terser@5.29.2))(vitest@2.1.2): + dependencies: + chalk: 5.3.0 + vite: 5.4.10(@types/node@20.17.6)(terser@5.29.2) + vitest: 2.1.2(@types/node@20.17.6)(@vitest/browser@2.1.2)(happy-dom@12.10.3)(jsdom@24.0.0)(msw@2.4.9(typescript@5.6.3))(terser@5.29.2) + + vitest@2.1.2(@types/node@20.17.6)(@vitest/browser@2.1.2)(happy-dom@12.10.3)(jsdom@24.0.0)(msw@2.4.9(typescript@5.6.3))(terser@5.29.2): + dependencies: + '@vitest/expect': 2.1.2 + '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(msw@2.4.9(typescript@5.6.3))(vite@5.4.10(@types/node@20.17.6)(terser@5.29.2)) + '@vitest/pretty-format': 2.1.2 + '@vitest/runner': 2.1.2 + '@vitest/snapshot': 2.1.2 + '@vitest/spy': 2.1.2 + '@vitest/utils': 2.1.2 + chai: 5.1.2 + debug: 4.3.7(supports-color@8.1.1) + magic-string: 0.30.11 + pathe: 1.1.2 + std-env: 3.7.0 + tinybench: 2.9.0 + tinyexec: 0.3.1 + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.4.10(@types/node@20.17.6)(terser@5.29.2) + vite-node: 2.1.2(@types/node@20.17.6)(terser@5.29.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.17.6 + '@vitest/browser': 2.1.2(@vitest/spy@2.1.2)(playwright@1.48.2)(typescript@5.6.3)(vite@5.4.10(@types/node@20.17.6)(terser@5.29.2))(vitest@2.1.2) + happy-dom: 12.10.3 + jsdom: 24.0.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vlq@1.0.1: {} vm-browserify@1.1.2: {} @@ -25395,7 +25993,7 @@ snapshots: html-escaper: 2.0.2 opener: 1.5.2 picocolors: 1.1.0 - sirv: 2.0.3 + sirv: 2.0.4 ws: 7.5.9 transitivePeerDependencies: - bufferutil @@ -25545,6 +26143,11 @@ snapshots: dependencies: isexe: 3.1.1 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wide-align@1.1.5: dependencies: string-width: 4.2.3 @@ -25620,6 +26223,8 @@ snapshots: ws@8.17.1: {} + ws@8.18.0: {} + xcase@2.0.1: {} xml-js@1.6.11: @@ -25717,6 +26322,8 @@ snapshots: yocto-queue@1.0.0: {} + yoctocolors-cjs@2.1.2: {} + yoctocolors@2.0.2: {} zdog@1.1.3: {} diff --git a/tsconfig.json b/tsconfig.json index 7a6f730e4ae2c8..77600e5acb52b5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -52,7 +52,7 @@ "@mui/internal-test-utils/*": ["./packages-internal/test-utils/src/*"] }, // Otherwise we get react-native typings which conflict with dom.lib. - "types": ["node", "react"] + "types": ["node", "react", "mocha"] }, "exclude": ["**/.*/", "**/build", "**/node_modules", "docs/export"] }