diff --git a/package.json b/package.json index 21e1796..fdd8f0a 100644 --- a/package.json +++ b/package.json @@ -39,9 +39,11 @@ "author": "", "license": "Apache-2.0", "devDependencies": { + "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.1.4", - "@testing-library/react": "^14.1.2", + "@testing-library/react": "^16.3.0", "@testing-library/react-hooks": "^8.0.1", + "@testing-library/user-event": "^14.6.1", "@types/react": "^18.2.38", "@vitest/coverage-istanbul": "^0.34.6", "happy-dom": "^12.10.3", diff --git a/src/FlagContext.ts b/src/FlagContext.ts index dd0cfc5..a665b67 100644 --- a/src/FlagContext.ts +++ b/src/FlagContext.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { type startTransition } from 'react'; import type { UnleashClient } from 'unleash-proxy-client'; export interface IFlagContextValue @@ -7,14 +7,8 @@ export interface IFlagContextValue 'on' | 'off' | 'updateContext' | 'isEnabled' | 'getVariant' > { client: UnleashClient; - flagsReady: boolean; - setFlagsReady: React.Dispatch< - React.SetStateAction - >; - flagsError: any; - setFlagsError: React.Dispatch< - React.SetStateAction - >; + isInitiallyReady: boolean; + startTransition: typeof startTransition; } const FlagContext = React.createContext(null); diff --git a/src/FlagProvider.test.tsx b/src/FlagProvider.test.tsx index 8c8d679..9ecf9ee 100644 --- a/src/FlagProvider.test.tsx +++ b/src/FlagProvider.test.tsx @@ -186,64 +186,6 @@ test('A memoized consumer should not rerender when the context provider values a expect(renderCounter).toHaveBeenCalledTimes(1); }); -test('should update when ready event is sent', () => { - const localMock = vi.fn(); - UnleashClientSpy.mockReturnValue({ - getVariant: getVariantMock, - updateContext: updateContextMock, - start: startClientMock, - stop: stopClientMock, - isEnabled: isEnabledMock, - on: localMock, - off: offMock, - }); - - const client = new UnleashClient(givenConfig); - - render( - -
Hi
-
- ); - - localMock.mockImplementation((event, cb) => { - if (event === EVENTS.READY) { - cb(); - } - }); - - expect(localMock).toHaveBeenCalledWith(EVENTS.READY, expect.any(Function)); -}); - -test('should register error when error event is sent', () => { - const localMock = vi.fn(); - UnleashClientSpy.mockReturnValue({ - getVariant: getVariantMock, - updateContext: updateContextMock, - start: startClientMock, - stop: stopClientMock, - isEnabled: isEnabledMock, - on: localMock, - off: offMock, - }); - - const client = new UnleashClient(givenConfig); - - render( - -
Hi
-
- ); - - localMock.mockImplementation((event, cb) => { - if (event === EVENTS.ERROR) { - cb('Error'); - } - }); - - expect(localMock).toHaveBeenCalledWith(EVENTS.ERROR, expect.any(Function)); -}); - test('should not start client if startClient is false', () => { const localMock = vi.fn(); const stopMock = vi.fn(); @@ -282,7 +224,6 @@ test('should not start client if startClient is false when passing config', () = off: offMock, }); - render(
Hi
diff --git a/src/FlagProvider.tsx b/src/FlagProvider.tsx index 2943ecc..5e8580e 100644 --- a/src/FlagProvider.tsx +++ b/src/FlagProvider.tsx @@ -23,7 +23,6 @@ const offlineConfig: IConfig = { // save startTransition as var to avoid webpack analysis (https://github.com/webpack/webpack/issues/14814) const _startTransition = 'startTransition'; -// fallback for React <18 which doesn't support startTransition // Fallback for React <18 and exclude startTransition if in React Native const defaultStartTransition = React[_startTransition] || (fn => fn()); @@ -39,111 +38,45 @@ const FlagProvider: FC> = ({ const client = React.useRef( unleashClient || new UnleashClient(config) ); - const [flagsReady, setFlagsReady] = React.useState( - Boolean( - unleashClient - ? (customConfig?.bootstrap && customConfig?.bootstrapOverride !== false) || unleashClient.isReady?.() - : config.bootstrap && config.bootstrapOverride !== false - ) - ); - const [flagsError, setFlagsError] = useState(client.current.getError?.() || null); useEffect(() => { - if (!config && !unleashClient) { - console.error( - `You must provide either a config or an unleash client to the flag provider. - If you are initializing the client in useEffect, you can avoid this warning - by checking if the client exists before rendering.` - ); - } - - const errorCallback = (e: any) => { - startTransition(() => { - setFlagsError((currentError: any) => currentError || e); - }); - }; - - const clearErrorCallback = (e: any) => { - startTransition(() => { - setFlagsError(null); - }); - } - - let timeout: ReturnType | null = null; - const readyCallback = () => { - // wait for flags to resolve after useFlag gets the same event - timeout = setTimeout(() => { - startTransition(() => { - setFlagsReady(true); - }); - }, 0); - }; - - client.current.on('ready', readyCallback); - client.current.on('error', errorCallback); - client.current.on('recovered', clearErrorCallback); - if (startClient) { // defensively stop the client first client.current.stop(); - // start the client client.current.start(); } // stop unleash client on unmount return function cleanup() { - if (client.current) { - client.current.off('error', errorCallback); - client.current.off('ready', readyCallback); - client.current.off('recovered', clearErrorCallback); - if (stopClient) { - client.current.stop(); - } - } - if (timeout) { - clearTimeout(timeout); + if (stopClient) { + client.current.stop(); } }; }, []); - const on = useCallback(client.current.on, []); - - const off = useCallback(client.current.off, []); - - const isEnabled = useCallback( - (toggleName: string) => client.current.isEnabled(toggleName), - [] - ) - - const updateContext = useCallback( - async (context: IMutableContext) => - await client.current.updateContext(context), - [] - ) - - const getVariant = useCallback( - (toggleName: string) => client.current.getVariant(toggleName), - [] - ) - - const context = useMemo( + const value = useMemo( () => ({ - on, - off, - updateContext, - isEnabled, - getVariant, + on: ((...args) => client.current.on(...args)) as IFlagContextValue['on'], + off: ((...args) => client.current.off(...args)) as IFlagContextValue['off'], + updateContext: (async(...args) => await client.current.updateContext(...args)) as IFlagContextValue['updateContext'], + isEnabled: ((...args) => client.current.isEnabled(...args)) as IFlagContextValue['isEnabled'], + getVariant: ((...args) => client.current.getVariant(...args)) as IFlagContextValue['getVariant'], + client: client.current, - flagsReady, - flagsError, - setFlagsReady, - setFlagsError, + isInitiallyReady: Boolean( + unleashClient + ? (customConfig?.bootstrap && + customConfig?.bootstrapOverride !== false) || + unleashClient.isReady?.() + : config.bootstrap && config.bootstrapOverride !== false + ), + startTransition, }), - [flagsReady, flagsError, on, off, updateContext, isEnabled, getVariant] + [] ); return ( - {children} + {children} ); }; diff --git a/src/integration.test.tsx b/src/integration.test.tsx index 6ef6652..48c081a 100644 --- a/src/integration.test.tsx +++ b/src/integration.test.tsx @@ -1,12 +1,14 @@ -import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; +import React, { type FC } from 'react'; +import { act, getByTestId, render, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom' import { EVENTS, UnleashClient } from 'unleash-proxy-client'; import FlagProvider from './FlagProvider'; import useFlagsStatus from './useFlagsStatus'; -import { act } from 'react-dom/test-utils'; import useFlag from './useFlag'; import { useFlagContext } from './useFlagContext'; import useVariant from './useVariant'; +import useUnleashContext from './useUnleashContext'; +import userEvent from '@testing-library/user-event' const fetchMock = vi.fn(async () => { return Promise.resolve({ @@ -31,6 +33,10 @@ const fetchMock = vi.fn(async () => { }); }); +beforeEach(() => { + vi.clearAllMocks(); +}); + test('should render toggles', async () => { const client = new UnleashClient({ url: 'http://localhost:4242/api/frontend', @@ -78,21 +84,20 @@ test('should render toggles', async () => { ); // After client initialization - expect(fetchMock).toHaveBeenCalled(); - rerender(ui); expect(screen.getByTestId('ready')).toHaveTextContent('true'); expect(screen.getByTestId('state')).toHaveTextContent('true'); expect(screen.getByTestId('variant')).toHaveTextContent( '{"name":"A","payload":{"type":"string","value":"A"},"enabled":true,"feature_enabled":true}' ); + expect(fetchMock).toHaveBeenCalledOnce(); }); test('should be ready from the start if bootstrapped', () => { - const Component = React.memo(() => { - const { flagsReady } = useFlagContext(); + const Component: FC = () => { + const { flagsReady } = useFlagsStatus(); return <>{flagsReady ? 'ready' : ''}; - }); + } render( { const Component = () => { const enabled = useFlag('test-flag'); - const { flagsReady } = useFlagContext(); + const { flagsReady } = useFlagsStatus(); renders += 1; @@ -229,7 +234,7 @@ test('should resolve values before setting flagsReady', async () => { const Component = () => { const enabled = useFlag('test-flag'); - const { flagsReady } = useFlagContext(); + const { flagsReady } = useFlagsStatus(); renders += 1; @@ -262,3 +267,96 @@ test('should resolve values before setting flagsReady', async () => { expect(renders).toBe(3); }); }); + +test('should only re-render if flag changed, and not on status or context change', async () => { + const client = new UnleashClient({ + url: 'http://localhost:4242/api/frontend', + appName: 'test', + clientKey: 'test', + fetch: fetchMock, + }); + let renders = 0; + let sessionId = 0; + + const FlagTestComponent: FC = () => { + const flag = useFlag('another-flag'); + renders += 1; + + return ( +
{flag.toString()}
+ ); + }; + + const FlagContextComponent: FC = () => { + const updateContext = useUnleashContext(); + + return ( + + ); + }; + + const ui = ( + + + + + ); + + const { debug } = render(ui); + + // Before client initialization + expect(fetchMock).not.toHaveBeenCalled(); + expect(screen.getByTestId('flag')).toHaveTextContent('false'); + expect(renders).toBe(1); + + // Wait for client initialization + await act( + () => + new Promise((resolve) => { + client.on(EVENTS.READY, () => { + setTimeout(resolve, 1); + }); + }) + ); + + expect(screen.getByTestId('flag')).toHaveTextContent('false'); + + fetchMock.mockImplementationOnce(async () => { + return Promise.resolve({ + ok: true, + status: 200, + headers: new Headers({}), + json: () => { + return Promise.resolve({ + toggles: [ + { + name: 'another-flag', + enabled: true, + variant: { + name: 'A', + payload: { type: 'string', value: 'A' }, + enabled: true, + }, + }, + ], + }); + }, + }); + }); + + // Simulate flag updates + await act(() => client.updateToggles()); + expect(screen.getByTestId('flag')).toHaveTextContent('true'); + await userEvent.click(screen.getByRole('button', { name: 'update context' })); + await act(() => client.updateToggles()); + expect(screen.getByTestId('flag')).toHaveTextContent('false'); + await act(() => client.updateToggles()); + expect(screen.getByTestId('flag')).toHaveTextContent('false'); + + expect(fetchMock).toHaveBeenCalledTimes(5); + expect(renders).toBe(3); +}); diff --git a/src/useFlag.ts b/src/useFlag.ts index e853b95..22f5574 100644 --- a/src/useFlag.ts +++ b/src/useFlag.ts @@ -31,7 +31,7 @@ const useFlag = (featureName: string) => { client.off('update', updateHandler); client.off('ready', readyHandler); }; - }, [client]); + }, [client]); // TODO: test component renders with dynamic flag name return flag; }; diff --git a/src/useFlagContext.ts b/src/useFlagContext.ts index 673e7cb..4d72a28 100644 --- a/src/useFlagContext.ts +++ b/src/useFlagContext.ts @@ -1,4 +1,4 @@ -import { useContext } from "react"; +import { startTransition, useContext } from "react"; import FlagContext, { type IFlagContextValue } from "./FlagContext"; import type { UnleashClient } from "unleash-proxy-client"; @@ -41,14 +41,8 @@ const mockUnleashClient = { const defaultContextValue: IFlagContextValue = { ...methods, client: mockUnleashClient, - flagsReady: false, - setFlagsReady: () => { - console.error("setFlagsReady() must be used within a FlagProvider"); - }, - flagsError: null, - setFlagsError: () => { - console.error("setFlagsError() must be used within a FlagProvider"); - } + isInitiallyReady: false, + startTransition, }; export function useFlagContext() { @@ -57,5 +51,6 @@ export function useFlagContext() { console.error("useFlagContext() must be used within a FlagProvider"); return defaultContextValue; } + return context; } diff --git a/src/useFlagsStatus.test.ts b/src/useFlagsStatus.test.ts new file mode 100644 index 0000000..2a80767 --- /dev/null +++ b/src/useFlagsStatus.test.ts @@ -0,0 +1,60 @@ + +// test('should update when ready event is sent', () => { +// const localMock = vi.fn(); +// UnleashClientSpy.mockReturnValue({ +// getVariant: getVariantMock, +// updateContext: updateContextMock, +// start: startClientMock, +// stop: stopClientMock, +// isEnabled: isEnabledMock, +// on: localMock, +// off: offMock, +// }); + +// const client = new UnleashClient(givenConfig); + +// render( +// +//
Hi
+//
+// ); + +// localMock.mockImplementation((event, cb) => { +// if (event === EVENTS.READY) { +// cb(); +// } +// }); + +// expect(localMock).toHaveBeenCalledWith(EVENTS.READY, expect.any(Function)); +// }); + + + +// test('should register error when error event is sent', () => { +// const localMock = vi.fn(); +// UnleashClientSpy.mockReturnValue({ +// getVariant: getVariantMock, +// updateContext: updateContextMock, +// start: startClientMock, +// stop: stopClientMock, +// isEnabled: isEnabledMock, +// on: localMock, +// off: offMock, +// }); + +// const client = new UnleashClient(givenConfig); + +// render( +// +//
Hi
+//
+// ); + +// localMock.mockImplementation((event, cb) => { +// if (event === EVENTS.ERROR) { +// cb('Error'); +// } +// }); + +// expect(localMock).toHaveBeenCalledWith(EVENTS.ERROR, expect.any(Function)); +// }); \ No newline at end of file diff --git a/src/useFlagsStatus.ts b/src/useFlagsStatus.ts index 1e0c697..b4f1212 100644 --- a/src/useFlagsStatus.ts +++ b/src/useFlagsStatus.ts @@ -1,10 +1,55 @@ /** @format */ +import { useEffect, useMemo, useState } from 'react'; import { useFlagContext } from './useFlagContext'; const useFlagsStatus = () => { - const { flagsReady, flagsError } = useFlagContext(); + const { client, isInitiallyReady, startTransition } = useFlagContext(); + const [flagsReady, setFlagsReady] = useState(isInitiallyReady || client.isReady()); + const [flagsError, setFlagsError] = useState(client.getError?.() || null); - return { flagsReady, flagsError }; + useEffect(() => { + const errorCallback = (e: unknown) => { + startTransition(() => { + setFlagsError((currentError: unknown) => currentError || e); + }); + }; + + const clearErrorCallback = (e: unknown) => { + startTransition(() => { + setFlagsError(null); + }); + }; + + let timeout: ReturnType | null = null; + const readyCallback = () => { + // wait for flags to resolve after useFlag gets the same event + timeout = setTimeout(() => { + startTransition(() => { + setFlagsReady(true); + }); + }, 0); + }; + + client.on('ready', readyCallback); + client.on('error', errorCallback); + client.on('recovered', clearErrorCallback); + + return function cleanup() { + if (client) { + client.off('error', errorCallback); + client.off('ready', readyCallback); + client.off('recovered', clearErrorCallback); + } + if (timeout) { + clearTimeout(timeout); + } + }; + }, []); + + return useMemo( + () => ({ flagsReady, flagsError, setFlagsReady, setFlagsError }), + [flagsReady, flagsError] + ); }; export default useFlagsStatus; diff --git a/yarn.lock b/yarn.lock index d27d8dd..0c53dd3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,11 +16,13 @@ "@jridgewell/trace-mapping" "^0.3.9" "@babel/code-frame@^7.10.4": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz" - integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== dependencies: - "@babel/highlight" "^7.16.7" + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" "@babel/code-frame@^7.22.13": version "7.22.13" @@ -166,11 +168,6 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz" - integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== - "@babel/helper-validator-identifier@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" @@ -181,6 +178,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + "@babel/helper-validator-option@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" @@ -195,15 +197,6 @@ "@babel/traverse" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/highlight@^7.16.7": - version "7.16.10" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz" - integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - chalk "^2.0.0" - js-tokens "^4.0.0" - "@babel/highlight@^7.22.13": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" @@ -537,15 +530,15 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== -"@testing-library/dom@^9.0.0": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.0.tgz#ed8ce10aa5e05eb6eaf0635b5b8975d889f66075" - integrity sha512-Dffe68pGwI6WlLRYR2I0piIkyole9cSBH5jGQKCGMRpHW5RHCqAUaqc2Kv0tUyd4dU4DLPKhJIjyKOnjv4tuUw== +"@testing-library/dom@^10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8" + integrity sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" "@types/aria-query" "^5.0.1" - aria-query "^5.0.0" + aria-query "5.3.0" chalk "^4.1.0" dom-accessibility-api "^0.5.9" lz-string "^1.5.0" @@ -573,14 +566,17 @@ "@babel/runtime" "^7.12.5" react-error-boundary "^3.1.0" -"@testing-library/react@^14.1.2": - version "14.1.2" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.1.2.tgz#a2b9e9ee87721ec9ed2d7cfc51cc04e474537c32" - integrity sha512-z4p7DVBTPjKM5qDZ0t5ZjzkpSNb+fZy1u6bzO7kk8oeGagpPCAtgh4cx1syrfp7a+QWkM021jGqjJaxJJnXAZg== +"@testing-library/react@^16.3.0": + version "16.3.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.0.tgz#3a85bb9bdebf180cd76dba16454e242564d598a6" + integrity sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw== dependencies: "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^9.0.0" - "@types/react-dom" "^18.0.0" + +"@testing-library/user-event@^14.6.1": + version "14.6.1" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149" + integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw== "@types/argparse@1.0.38": version "1.0.38" @@ -588,9 +584,9 @@ integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA== "@types/aria-query@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc" - integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q== + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" + integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== "@types/chai-subset@^1.3.3": version "1.3.3" @@ -619,22 +615,6 @@ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz" integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== -"@types/react-dom@^18.0.0": - version "18.0.5" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.5.tgz#330b2d472c22f796e5531446939eacef8378444a" - integrity sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA== - dependencies: - "@types/react" "*" - -"@types/react@*": - version "17.0.38" - resolved "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz" - integrity sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - "@types/react@^18.2.38": version "18.2.38" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.38.tgz#3605ca41d3daff2c434e0b98d79a2469d4c2dd52" @@ -798,7 +778,7 @@ ajv@~6.12.6: ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-styles@^3.2.1: @@ -827,6 +807,13 @@ argparse@~1.0.9: dependencies: sprintf-js "~1.0.2" +aria-query@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + dependencies: + dequal "^2.0.3" + aria-query@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz" @@ -914,7 +901,7 @@ chalk@^3.0.0: chalk@^4.1.0: version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -1037,16 +1024,26 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + diff-sequences@^29.4.3: version "29.6.3" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== -dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: +dom-accessibility-api@^0.5.6: version "0.5.10" resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.10.tgz" integrity sha512-Xu9mD0UjrJisTmv7lmVSDMagQcU9R5hwAbxsaAE/35XPnPLJobbuREfV/rraiSaEj/UOvgrzQs66zyTWTlyd+g== +dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + electron-to-chromium@^1.4.428: version "1.4.428" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.428.tgz#c31fc88e854f49d8305cdabf6ec934ff1588a902" @@ -1639,9 +1636,9 @@ postcss@^8.4.27: source-map-js "^1.0.2" pretty-format@^27.0.2: - version "27.4.6" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz" - integrity sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g== + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== dependencies: ansi-regex "^5.0.1" ansi-styles "^5.0.0"