diff --git a/packages/bridge/babel.config.cjs b/packages/bridge/babel.config.cjs new file mode 100644 index 00000000..95422c49 --- /dev/null +++ b/packages/bridge/babel.config.cjs @@ -0,0 +1,12 @@ +module.exports = { + presets: [ + ['@babel/preset-env', { modules: 'auto' }], + [ + '@babel/preset-react', + { + runtime: 'automatic', + }, + ], + '@babel/preset-typescript', + ], +}; diff --git a/packages/bridge/coverage/coverage-summary.json b/packages/bridge/coverage/coverage-summary.json index 488ca083..8304acbc 100644 --- a/packages/bridge/coverage/coverage-summary.json +++ b/packages/bridge/coverage/coverage-summary.json @@ -1,3 +1,6 @@ -{"total": {"lines":{"total":22,"covered":20,"skipped":0,"pct":90.9},"statements":{"total":27,"covered":22,"skipped":0,"pct":81.48},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":5,"covered":5,"skipped":0,"pct":100},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"}} -,"/Users/aelf/Documents/Projects/aelf/aelf-web-login/packages/bridge/src/utils.ts": {"lines":{"total":22,"covered":20,"skipped":0,"pct":90.9},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":27,"covered":22,"skipped":0,"pct":81.48},"branches":{"total":5,"covered":5,"skipped":0,"pct":100}} +{"total": {"lines":{"total":56,"covered":54,"skipped":0,"pct":96.42},"statements":{"total":58,"covered":56,"skipped":0,"pct":96.55},"functions":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":16,"covered":16,"skipped":0,"pct":100},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"}} +,"/Users/liuxiyang/work/code/aelf-web-login/packages/bridge/src/mountApp.tsx": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} +,"/Users/liuxiyang/work/code/aelf-web-login/packages/bridge/src/useLockCallback.ts": {"lines":{"total":10,"covered":10,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} +,"/Users/liuxiyang/work/code/aelf-web-login/packages/bridge/src/useVerifier.ts": {"lines":{"total":25,"covered":25,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":26,"covered":26,"skipped":0,"pct":100},"branches":{"total":8,"covered":8,"skipped":0,"pct":100}} +,"/Users/liuxiyang/work/code/aelf-web-login/packages/bridge/src/utils.ts": {"lines":{"total":18,"covered":16,"skipped":0,"pct":88.88},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":18,"covered":16,"skipped":0,"pct":88.88},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}} } diff --git a/packages/bridge/jest-report.xml b/packages/bridge/jest-report.xml index 685de988..1396760d 100644 --- a/packages/bridge/jest-report.xml +++ b/packages/bridge/jest-report.xml @@ -1,13 +1,45 @@ - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/bridge/jest.config.ts b/packages/bridge/jest.config.ts index e04c7daa..1bf3ec63 100644 --- a/packages/bridge/jest.config.ts +++ b/packages/bridge/jest.config.ts @@ -166,9 +166,8 @@ const config = { // testRunner: "jest-circus/runner", // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': '@swc/jest', - }, + transform: { '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest', '^.+\\.js$': 'babel-jest' }, + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation transformIgnorePatterns: [ '.pnpm/node_modules/(?!((jest-)?react-native(-.*)?|@react-native(-community)?|victory(-.*)?|uuid)|react-navigation|@shopify/react-native-skia|@react-navigation/.*/)', diff --git a/packages/bridge/package.json b/packages/bridge/package.json index 65c34310..93ae7009 100755 --- a/packages/bridge/package.json +++ b/packages/bridge/package.json @@ -40,11 +40,20 @@ "react-dom": "^18.0.0" }, "devDependencies": { + "babel-jest": "^29.7.0", "@portkey/did-ui-react": "^2.15.9", "@portkey/types": "^2.15.9", "@portkey/utils": "^2.15.9", + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/preset-react": "^7.24.7", + "@babel/preset-typescript": "^7.24.7", "@swc/core": "^1.9.3", "@swc/jest": "^0.2.37", + "@testing-library/jest-dom": "^6.5.0", + "@testing-library/react": "^16.0.1", + "@testing-library/react-hooks": "^8.0.1", + "@types/jest": "^29.5.14", "@types/react": "^18.3.1", "@types/react-dom": "^18.3.0", "antd": "4.24.14", diff --git a/packages/bridge/src/__tests__/mountApp.test.tsx b/packages/bridge/src/__tests__/mountApp.test.tsx new file mode 100644 index 00000000..8341344d --- /dev/null +++ b/packages/bridge/src/__tests__/mountApp.test.tsx @@ -0,0 +1,57 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { render, screen } from '@testing-library/react'; +import { useMountSignIn } from '../mountApp'; +import { Bridge } from '../bridge'; +import { NetworkEnum, WalletAdapter } from '@aelf-web-login/wallet-adapter-base'; +import { IBaseConfig } from '../index'; +import '@testing-library/jest-dom'; + +// Mock components and dependencies +jest.mock('../ui', () => jest.fn(() =>
SignInModal Component
)); + +jest.mock('@portkey/did-ui-react', () => ({ + PortkeyProvider: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); + +describe('useMountSignIn', () => { + let bridgeInstance: Bridge; + let wallets: WalletAdapter[]; + let baseConfig: IBaseConfig; + let children: React.ReactNode; + + beforeEach(() => { + bridgeInstance = {} as Bridge; + wallets = [{} as WalletAdapter]; + baseConfig = { + PortkeyProviderProps: {}, + networkType: NetworkEnum.TESTNET, + } as IBaseConfig; + children =
Child Component
; + }); + + it('should render the SignInModal and children wrapped in PortkeyProvider', () => { + const { result } = renderHook(() => + useMountSignIn(bridgeInstance, wallets, baseConfig, children), + ); + + const SignInNode = result.current; + + render(SignInNode); + + expect(screen.getByText('Child Component')).toBeInTheDocument(); + }); + + it('should memoize the rendered component', () => { + const { result, rerender } = renderHook(() => + useMountSignIn(bridgeInstance, wallets, baseConfig, children), + ); + + const firstRender = result.current; + + rerender(); + + const secondRender = result.current; + + expect(firstRender).toBe(secondRender); + }); +}); diff --git a/packages/bridge/src/__tests__/useLockCallback.test.ts b/packages/bridge/src/__tests__/useLockCallback.test.ts new file mode 100644 index 00000000..598038e2 --- /dev/null +++ b/packages/bridge/src/__tests__/useLockCallback.test.ts @@ -0,0 +1,89 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import useLockCallback from '../useLockCallback'; + +describe('useLockCallback Hook', () => { + it('should execute function and lock', async () => { + const mockFn = jest.fn(async () => { + await new Promise((res) => setTimeout(res, 100)); + return 'result'; + }); + + const { result } = renderHook(() => useLockCallback(mockFn, [])); + + // First call should execute + let value; + await act(async () => { + value = await result.current(); + }); + + expect(mockFn).toHaveBeenCalledTimes(1); + expect(value).toBe('result'); + }); + + it('should prevent concurrent execution', async () => { + const mockFn = jest.fn(async () => { + await new Promise((res) => setTimeout(res, 100)); + }); + + const { result } = renderHook(() => useLockCallback(mockFn, [])); + + // First call will run + let firstCall; + await act(async () => { + firstCall = result.current(); + await result.current(); + }); + + expect(mockFn).toHaveBeenCalledTimes(1); // Should only run once + + await firstCall; // Resolve first call + }); + + it('should allow another execution after previous is finished', async () => { + const mockFn = jest.fn(async () => { + await new Promise((res) => setTimeout(res, 100)); + return 'success'; + }); + + const { result } = renderHook(() => useLockCallback(mockFn, [])); + + // First call + let firstResult; + await act(async () => { + firstResult = await result.current(); + }); + + expect(firstResult).toBe('success'); + + // Second call, after first one has finished + let secondResult; + await act(async () => { + secondResult = await result.current(); + }); + + expect(secondResult).toBe('success'); + expect(mockFn).toHaveBeenCalledTimes(2); // Second call should now be allowed + }); + + it('should release the lock if the callback throws an error', async () => { + const mockFn = jest.fn(async () => { + throw new Error('Test error'); + }); + + const { result } = renderHook(() => useLockCallback(mockFn, [])); + + const lockedCallback = result.current; + + // First call should execute + const firstPromise = lockedCallback(); + expect(mockFn).toHaveBeenCalledTimes(1); + + // Expect the first call to throw an error + await expect(firstPromise).rejects.toThrow('Test error'); + + // Second call should now execute + const secondPromise = lockedCallback(); + await expect(secondPromise).rejects.toThrow('Test error'); + expect(mockFn).toHaveBeenCalledTimes(2); + }); +}); diff --git a/packages/bridge/src/__tests__/useVerifier.test.ts b/packages/bridge/src/__tests__/useVerifier.test.ts new file mode 100644 index 00000000..6e6d72d5 --- /dev/null +++ b/packages/bridge/src/__tests__/useVerifier.test.ts @@ -0,0 +1,178 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import useVerifier from '../useVerifier'; +import { did, ConfigProvider, useVerifyToken, IVerifier } from '@portkey/did-ui-react'; +import { TChainId } from '@aelf-web-login/wallet-adapter-base'; +import { OperationTypeEnum } from '@portkey/services'; + +jest.mock('@portkey/did-ui-react', () => ({ + did: { + services: { + getRecommendationVerifier: jest.fn(), + }, + }, + ConfigProvider: { + getSocialLoginConfig: jest.fn(), + }, + useVerifyToken: jest.fn(), +})); + +jest.mock('@aelf-web-login/wallet-adapter-base', () => ({ + TChainId: { + AELF: 'AELF', + }, +})); + +jest.mock('@portkey/services', () => ({ + AccountType: { + Apple: 'Apple', + Google: 'Google', + Telegram: 'Telegram', + }, + OperationTypeEnum: { + Login: 'Login', + }, +})); + +describe('useVerifier', () => { + const mockVerifyToken = jest.fn(); + const mockGetRecommendationVerifier = jest.fn(); + const mockSocialLoginConfig = { + Apple: { + clientId: 'apple-client-id', + redirectURI: 'apple-redirect-uri', + customLoginHandler: jest.fn(), + }, + Google: { + clientId: 'google-client-id', + customLoginHandler: jest.fn(), + }, + Telegram: { + customLoginHandler: jest.fn(), + }, + }; + + beforeEach(() => { + (did.services.getRecommendationVerifier as jest.Mock).mockReturnValue( + mockGetRecommendationVerifier, + ); + (ConfigProvider.getSocialLoginConfig as jest.Mock).mockReturnValue(mockSocialLoginConfig); + (useVerifyToken as jest.Mock).mockReturnValue(mockVerifyToken); + }); + + it('should return getRecommendationVerifier and verifySocialToken functions', () => { + const { result } = renderHook(() => useVerifier()); + + expect(result.current.getRecommendationVerifier).toBeInstanceOf(Function); + expect(result.current.verifySocialToken).toBeInstanceOf(Function); + }); + + it('should call getRecommendationVerifier with correct chainId', async () => { + const chainId: TChainId = 'AELF'; + const { result } = renderHook(() => useVerifier()); + + await act(async () => { + await result.current.getRecommendationVerifier(chainId); + }); + + expect(did.services.getRecommendationVerifier).toHaveBeenCalledWith({ chainId }); + }); + + it('should handle Apple account type correctly', async () => { + const chainId: TChainId = 'AELF'; + const { result } = renderHook(() => useVerifier()); + + await act(async () => { + await result.current.verifySocialToken({ + accountType: 'Apple', + token: 'apple-token', + guardianIdentifier: 'guardian-id', + verifier: { id: 'verifier-id' } as IVerifier, + chainId, + operationType: OperationTypeEnum.register, + operationDetails: 'login-details', + }); + }); + + expect(mockVerifyToken).toHaveBeenCalledWith('Apple', expect.any(Object)); + }); + + it('should handle Google account type correctly', async () => { + const chainId: TChainId = 'AELF'; + const { result } = renderHook(() => useVerifier()); + + await act(async () => { + await result.current.verifySocialToken({ + accountType: 'Google', + token: 'google-token', + guardianIdentifier: 'guardian-id', + verifier: { id: 'verifier-id' } as IVerifier, + chainId, + operationType: OperationTypeEnum.register, + operationDetails: 'login-details', + }); + }); + + expect(mockVerifyToken).toHaveBeenCalledWith('Google', expect.any(Object)); + }); + + it('should handle Telegram account type correctly', async () => { + const chainId: TChainId = 'AELF'; + const { result } = renderHook(() => useVerifier()); + + await act(async () => { + await result.current.verifySocialToken({ + accountType: 'Telegram', + token: 'telegram-token', + guardianIdentifier: 'guardian-id', + verifier: { id: 'verifier-id' } as IVerifier, + chainId, + operationType: OperationTypeEnum.register, + operationDetails: 'login-details', + }); + }); + + expect(mockVerifyToken).toHaveBeenCalledWith('Telegram', expect.any(Object)); + }); + + it('should throw error for unsupported account type', async () => { + const chainId: TChainId = 'AELF'; + const { result } = renderHook(() => useVerifier()); + + await act(async () => { + try { + await result.current.verifySocialToken({ + accountType: 'Unsupported' as any, + token: 'unsupported-token', + guardianIdentifier: 'guardian-id', + verifier: { id: 'verifier-id' } as IVerifier, + chainId, + operationType: OperationTypeEnum.register, + operationDetails: 'login-details', + }); + } catch (error) { + expect(error).toBe('accountType is not supported'); + } + }); + }); + + it('should throw error if verifier is missing', async () => { + const chainId: TChainId = 'AELF'; + const { result } = renderHook(() => useVerifier()); + + await act(async () => { + try { + await result.current.verifySocialToken({ + accountType: 'Apple', + token: 'apple-token', + guardianIdentifier: 'guardian-id', + verifier: { id: '' } as IVerifier, + chainId, + operationType: OperationTypeEnum.register, + operationDetails: 'login-details', + }); + } catch (error) { + expect(error).toBe('Verifier is not missing'); + } + }); + }); +}); diff --git a/packages/bridge/src/index.ts b/packages/bridge/src/index.ts index b9bb9262..42195376 100644 --- a/packages/bridge/src/index.ts +++ b/packages/bridge/src/index.ts @@ -1,4 +1,3 @@ -// export * from './mountApp' import { TChainId, SignInDesignEnum, @@ -6,7 +5,7 @@ import { WalletAdapter, } from '@aelf-web-login/wallet-adapter-base'; import { Bridge } from './bridge'; -import { mountApp, unMountApp, useMountSignIn } from './mountApp'; +import { useMountSignIn } from './mountApp'; import { store, AppStore } from './store'; import { GlobalConfigProps } from '@portkey/did-ui-react/dist/_types/src/components/config-provider/types'; import { ConfigProvider, SignInProps, ISignIn, PortkeyProvider } from '@portkey/did-ui-react'; @@ -41,8 +40,6 @@ export interface IConfigProps { export interface IBridgeAPI { instance: Bridge; store: AppStore; - mountApp: () => void; - unMountApp: () => void; getSignIn: (arg: React.ReactNode) => React.ReactNode; } export function initBridge({ baseConfig, wallets, didConfig }: IConfigProps): IBridgeAPI { @@ -50,12 +47,9 @@ export function initBridge({ baseConfig, wallets, didConfig }: IConfigProps): IB ConfigProvider.setGlobalConfig(didConfig); console.log('init bridge'); - // mountApp(bridgeInstance, wallets, baseConfig); return { instance: bridgeInstance, store, - mountApp: mountApp.bind(null, bridgeInstance, wallets, baseConfig), - unMountApp, getSignIn: useMountSignIn.bind(null, bridgeInstance, wallets, baseConfig), }; } diff --git a/packages/bridge/src/mountApp.tsx b/packages/bridge/src/mountApp.tsx index c649a62a..83aa7c09 100644 --- a/packages/bridge/src/mountApp.tsx +++ b/packages/bridge/src/mountApp.tsx @@ -1,37 +1,10 @@ import { WalletAdapter } from '@aelf-web-login/wallet-adapter-base'; -import { createRoot } from 'react-dom/client'; import SignInModal from './ui'; import { Bridge } from './bridge'; import { IBaseConfig } from '.'; import { PortkeyProvider } from '@portkey/did-ui-react'; import { useMemo } from 'react'; -export function mountApp( - bridgeInstance: Bridge, - wallets: WalletAdapter[], - baseConfig: IBaseConfig, -) { - if (typeof window === 'undefined') { - return; - } - console.log('mountApp--'); - const containerElementQuery = 'body'; - const containerElement = document.querySelector(containerElementQuery); - if (!containerElement) { - throw new Error(`Element with query ${containerElementQuery} does not exist.`); - } - - const SignInWrapperDom = document.createElement('div'); - SignInWrapperDom.setAttribute('id', 'sign-in-wrapper'); - const root = createRoot(SignInWrapperDom); - root.render( - - - , - ); - containerElement.appendChild(SignInWrapperDom); -} - export function useMountSignIn( bridgeInstance: Bridge, wallets: WalletAdapter[], @@ -48,14 +21,3 @@ export function useMountSignIn( }, [baseConfig, bridgeInstance, children, wallets]); return SignInNode; } - -export function unMountApp() { - if (typeof window === 'undefined') { - return; - } - const SignInWrapperDom = document.querySelector('#sign-in-wrapper'); - if (!SignInWrapperDom) { - return; - } - document.body.removeChild(SignInWrapperDom); -} diff --git a/packages/bridge/src/useWallet.ts b/packages/bridge/src/useWallet.ts deleted file mode 100644 index 0af883f9..00000000 --- a/packages/bridge/src/useWallet.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { createContext, useContext } from 'react'; - -export interface WalletContextState { - login(): Promise; -} - -const DEFAULT_CONTEXT: Partial = { - login() { - return Promise.reject(logMissingProviderError('call', 'connect')); - }, -}; - -function logMissingProviderError(action: string, property: string) { - const error = new Error( - `You have tried to ${action} "${property}" on a WalletContext without providing one. ` + - 'Make sure to render a WalletProvider as an ancestor of the component that uses WalletContext.', - ); - console.error(error); - return error; -} - -export const WalletContext: React.Context = createContext( - DEFAULT_CONTEXT as WalletContextState, -); - -export function useWallet(): WalletContextState { - return useContext(WalletContext); -} diff --git a/packages/react/jest.setup.ts b/packages/react/jest.setup.ts index 446173b6..8bd80ada 100644 --- a/packages/react/jest.setup.ts +++ b/packages/react/jest.setup.ts @@ -11,8 +11,6 @@ jest.mock('@aelf-web-login/wallet-adapter-bridge', () => ({ subscribe: () => null, }, instance: {} as IBridgeAPI['instance'], - mountApp: () => null, - unMountApp: () => null, }), })); diff --git a/packages/react/src/__tests__/context.test.tsx b/packages/react/src/__tests__/context.test.tsx index 5d12b0e2..63362432 100644 --- a/packages/react/src/__tests__/context.test.tsx +++ b/packages/react/src/__tests__/context.test.tsx @@ -10,8 +10,6 @@ const mockBridgeAPI: IBridgeAPI = { subscribe: () => null as unknown as ReturnType, } as unknown as IBridgeAPI['store'], instance: {} as IBridgeAPI['instance'], - mountApp: () => null, - unMountApp: () => null, }; describe('WebLoginProvider', () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 900a1b4f..160a3a80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -155,6 +155,18 @@ importers: specifier: ^18.0.0 version: 18.3.1(react@18.3.1) devDependencies: + '@babel/core': + specifier: ^7.0.0 + version: 7.25.2 + '@babel/preset-env': + specifier: ^7.24.7 + version: 7.25.4(@babel/core@7.25.2) + '@babel/preset-react': + specifier: ^7.24.7 + version: 7.24.7(@babel/core@7.25.2) + '@babel/preset-typescript': + specifier: ^7.24.7 + version: 7.24.7(@babel/core@7.25.2) '@portkey/did-ui-react': specifier: ^2.15.9 version: 2.15.9(@types/react@18.3.9)(aelf-sdk@3.4.16-alpha.7(@babel/core@7.25.2)(encoding@0.1.13)(eslint@8.57.1))(encoding@0.1.13)(i18next@23.15.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -170,6 +182,18 @@ importers: '@swc/jest': specifier: ^0.2.37 version: 0.2.37(@swc/core@1.9.3(@swc/helpers@0.5.1)) + '@testing-library/jest-dom': + specifier: ^6.5.0 + version: 6.5.0 + '@testing-library/react': + specifier: ^16.0.1 + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@testing-library/react-hooks': + specifier: ^8.0.1 + version: 8.0.1(@types/react@18.3.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 '@types/react': specifier: ^18.3.1 version: 18.3.9 @@ -179,15 +203,15 @@ importers: antd: specifier: 4.24.14 version: 4.24.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + babel-jest: + specifier: ^29.7.0 + version: 29.7.0(@babel/core@7.25.2) father: specifier: ^4.3.8 version: 4.5.0(@babel/core@7.25.2)(@types/node@22.7.3)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(type-fest@0.21.3)(webpack@5.95.0(@swc/core@1.9.3(@swc/helpers@0.5.1))) jest: specifier: ^29.7.0 version: 29.7.0(@types/node@22.7.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.1))(@types/node@22.7.3)(typescript@5.6.2)) - jest-canvas-mock: - specifier: ^2.5.2 - version: 2.5.2 typescript: specifier: ^5.3.3 version: 5.6.2 @@ -3489,6 +3513,22 @@ packages: resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@testing-library/react-hooks@8.0.1': + resolution: {integrity: sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==} + engines: {node: '>=12'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 + react: ^16.9.0 || ^17.0.0 + react-dom: ^16.9.0 || ^17.0.0 + react-test-renderer: ^16.9.0 || ^17.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + react-dom: + optional: true + react-test-renderer: + optional: true + '@testing-library/react@14.3.1': resolution: {integrity: sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==} engines: {node: '>=14'} @@ -3597,6 +3637,9 @@ packages: '@types/jest@29.5.13': resolution: {integrity: sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==} + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + '@types/js-cookie@2.2.7': resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} @@ -9322,6 +9365,12 @@ packages: peerDependencies: react: ^18.3.1 + react-error-boundary@3.1.4: + resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + react: '>=16.13.1' + react-error-boundary@4.0.13: resolution: {integrity: sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==} peerDependencies: @@ -15253,6 +15302,15 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 + '@testing-library/react-hooks@8.0.1(@types/react@18.3.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.25.6 + react: 18.3.1 + react-error-boundary: 3.1.4(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.9 + react-dom: 18.3.1(react@18.3.1) + '@testing-library/react@14.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.25.6 @@ -15366,6 +15424,11 @@ snapshots: expect: 29.7.0 pretty-format: 29.7.0 + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + '@types/js-cookie@2.2.7': {} '@types/jsdom@20.0.1': @@ -23179,6 +23242,11 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-error-boundary@3.1.4(react@18.3.1): + dependencies: + '@babel/runtime': 7.25.6 + react: 18.3.1 + react-error-boundary@4.0.13(react@18.3.1): dependencies: '@babel/runtime': 7.25.6