From f7ed0ab978a0c8464fdaf89b8d6649e9f3bbb4f1 Mon Sep 17 00:00:00 2001 From: "fred.tran" Date: Tue, 19 Nov 2024 16:58:32 +0800 Subject: [PATCH] chore: refactor wallet-adapter-react to modular files --- .gitignore | 4 +- packages/react/coverage/coverage-summary.json | 2 - packages/react/jest-report.xml | 3 - packages/react/jest.config.ts | 4 +- packages/react/package.json | 2 +- packages/react/src/__tests__/context.test.tsx | 27 +++ packages/react/src/__tests__/init.test.ts | 7 +- packages/react/src/__tests__/provider.test.ts | 26 --- .../src/__tests__/useConnectWallet.test.tsx | 29 +++ .../src/__tests__/useExternalStore.test.tsx | 26 +++ packages/react/src/context.tsx | 39 ++++ packages/react/src/index.tsx | 172 +----------------- packages/react/src/init.ts | 34 ++++ packages/react/src/useConnectWallet.tsx | 77 ++++++++ packages/react/src/useExternalStore.tsx | 26 +++ 15 files changed, 273 insertions(+), 205 deletions(-) delete mode 100644 packages/react/coverage/coverage-summary.json delete mode 100644 packages/react/jest-report.xml create mode 100644 packages/react/src/__tests__/context.test.tsx delete mode 100644 packages/react/src/__tests__/provider.test.ts create mode 100644 packages/react/src/__tests__/useConnectWallet.test.tsx create mode 100644 packages/react/src/__tests__/useExternalStore.test.tsx create mode 100644 packages/react/src/context.tsx create mode 100644 packages/react/src/init.ts create mode 100644 packages/react/src/useConnectWallet.tsx create mode 100644 packages/react/src/useExternalStore.tsx diff --git a/.gitignore b/.gitignore index 0bfb9956..3a5d4b06 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,6 @@ package.json.lerna_backup *.crt *.pem -.eslintcache \ No newline at end of file +.eslintcache +**/jest-report.xml +**/coverage-summary.json diff --git a/packages/react/coverage/coverage-summary.json b/packages/react/coverage/coverage-summary.json deleted file mode 100644 index ef1ae37d..00000000 --- a/packages/react/coverage/coverage-summary.json +++ /dev/null @@ -1,2 +0,0 @@ -{"total": {"lines":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"},"statements":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"},"functions":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"},"branches":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"}} -} diff --git a/packages/react/jest-report.xml b/packages/react/jest-report.xml deleted file mode 100644 index ba050097..00000000 --- a/packages/react/jest-report.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/packages/react/jest.config.ts b/packages/react/jest.config.ts index 1d4d0531..9bd78b55 100644 --- a/packages/react/jest.config.ts +++ b/packages/react/jest.config.ts @@ -170,7 +170,9 @@ const config: Config = { '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest', }, // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation - // transformIgnorePatterns: [], + transformIgnorePatterns: [ + '.pnpm/node_modules/(?!((jest-)?react-native(-.*)?|@react-native(-community)?|victory(-.*)?|uuid)|react-navigation|@shopify/react-native-skia|@react-navigation/.*/)', + ], // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them // unmockedModulePathPatterns: undefined, diff --git a/packages/react/package.json b/packages/react/package.json index a9ce5d7d..1576f601 100755 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -23,7 +23,7 @@ "scripts": { "dev": "father dev", "build": "father build", - "test": "jest --config=jest.config.ts --detectOpenHandles", + "test": "NODE_OPTIONS='$NODE_OPTIONS --experimental-vm-modules' jest --config=jest.config.ts --detectOpenHandles", "test:coverage": "jest --config=jest.config.ts --coverage --detectOpenHandles" }, "dependencies": { diff --git a/packages/react/src/__tests__/context.test.tsx b/packages/react/src/__tests__/context.test.tsx new file mode 100644 index 00000000..c61e4194 --- /dev/null +++ b/packages/react/src/__tests__/context.test.tsx @@ -0,0 +1,27 @@ +import { render, screen } from '@testing-library/react'; +import { WebLoginProvider } from '../context'; + +const mockBridgeAPI = { + getSignIn: jest.fn((children) => children), +}; + +describe('WebLoginProvider', () => { + it('should render children with provided bridgeAPI', () => { + render( + +
Test Child
+
, + ); + + expect(screen.getByText('Test Child')).toBeInTheDocument(); + }); + + it('should return null if no bridgeAPI is provided', () => { + const { container } = render( + +
Test Child
+
, + ); + expect(container.firstChild).toBeNull(); + }); +}); diff --git a/packages/react/src/__tests__/init.test.ts b/packages/react/src/__tests__/init.test.ts index 4e8390b6..defa9d69 100644 --- a/packages/react/src/__tests__/init.test.ts +++ b/packages/react/src/__tests__/init.test.ts @@ -1,5 +1,4 @@ // import { initBridge, IConfigProps, IBridgeAPI } from '@aelf-web-login/wallet-adapter-bridge'; -import VConsole from 'vconsole'; import { init } from '../index'; jest.mock('vconsole'); @@ -8,14 +7,16 @@ jest.mock('@aelf-web-login/wallet-adapter-bridge', () => ({ })); describe('init', () => { - it('should initialize VConsole if showVconsole is true', () => { + it('should initialize VConsole if showVconsole is true', async () => { const options = { baseConfig: { showVconsole: true } }; init(options as any); + const VConsole = await import('vconsole'); expect(VConsole).toHaveBeenCalled(); }); - it('should not initialize VConsole if showVconsole is false', () => { + it('should not initialize VConsole if showVconsole is false', async () => { const options = { baseConfig: { showVconsole: false } }; init(options as any); + const VConsole = await import('vconsole'); expect(VConsole).not.toHaveBeenCalled(); }); diff --git a/packages/react/src/__tests__/provider.test.ts b/packages/react/src/__tests__/provider.test.ts deleted file mode 100644 index 6f92752c..00000000 --- a/packages/react/src/__tests__/provider.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import { WebLoginProvider, WebLoginContext } from '../index'; - -const mockBridgeAPI = { - getSignIn: jest.fn((children) => children), -}; - -// describe('WebLoginProvider', () => { -// it('should render children with provided bridgeAPI', () => { -// render( -// -//
Test Child
-//
> -// ); -// expect(screen.getByText('Test Child')).toBeInTheDocument(); -// }); - -// it('should return null if no bridgeAPI is provided', () => { -// const { container } = render( -// -//
Test Child
-//
-// ); -// expect(container.firstChild).toBeNull(); -// }); -// }); diff --git a/packages/react/src/__tests__/useConnectWallet.test.tsx b/packages/react/src/__tests__/useConnectWallet.test.tsx new file mode 100644 index 00000000..56c2e46f --- /dev/null +++ b/packages/react/src/__tests__/useConnectWallet.test.tsx @@ -0,0 +1,29 @@ +import { render } from '@testing-library/react'; +import useConnectWallet from '../useConnectWallet'; +import { WebLoginProvider } from '../context'; +import { IBridgeAPI } from '@aelf-web-login/wallet-adapter-bridge'; + +const mockBridgeAPI: IBridgeAPI = { + getSignIn: jest.fn((children) => children), + store: { + getState: () => null, + subscribe: () => null, + }, + instance: { + connect: () => null, + }, +}; + +const Comp = () => { + useConnectWallet(); + return null; +}; +describe('useConnectWallet', () => { + it('should render hook', () => { + render( + + + , + ); + }); +}); diff --git a/packages/react/src/__tests__/useExternalStore.test.tsx b/packages/react/src/__tests__/useExternalStore.test.tsx new file mode 100644 index 00000000..92fbaeea --- /dev/null +++ b/packages/react/src/__tests__/useExternalStore.test.tsx @@ -0,0 +1,26 @@ +import { render } from '@testing-library/react'; +import useExternalStore from '../useExternalStore'; +import { WebLoginProvider } from '../context'; +import { IBridgeAPI } from '@aelf-web-login/wallet-adapter-bridge'; + +const mockBridgeAPI: Partial = { + getSignIn: jest.fn((children) => children), + store: { + getState: () => null, + subscribe: () => null, + }, +}; + +const Comp = () => { + useExternalStore(); + return null; +}; +describe('useExternalStore', () => { + it('should render hook', () => { + render( + + + , + ); + }); +}); diff --git a/packages/react/src/context.tsx b/packages/react/src/context.tsx new file mode 100644 index 00000000..7e0e4629 --- /dev/null +++ b/packages/react/src/context.tsx @@ -0,0 +1,39 @@ +import { IBridgeAPI } from '@aelf-web-login/wallet-adapter-bridge'; +import React from 'react'; + +const HOOK_ERROR_MESSAGE = + 'Must call the provided initialization method`init` method before using hooks.'; + +const WebLoginContext: React.Context = React.createContext( + {} as IBridgeAPI, +); + +export default WebLoginContext; + +export function useWebLoginContext(): IBridgeAPI { + const bridgeAPI = React.useContext(WebLoginContext); + + if (!bridgeAPI) { + throw new Error(HOOK_ERROR_MESSAGE); + } + + return bridgeAPI; +} + +export interface IWebLoginProviderProps { + children: React.ReactNode; + bridgeAPI: IBridgeAPI; +} + +export const WebLoginProvider: React.FC = ({ children, bridgeAPI }) => { + const { getSignIn } = bridgeAPI ?? { + getSignIn: () => null, + }; + + if (!bridgeAPI) { + return null; + } + return ( + {getSignIn(children)} + ); +}; diff --git a/packages/react/src/index.tsx b/packages/react/src/index.tsx index 0b4373d1..9afdf899 100644 --- a/packages/react/src/index.tsx +++ b/packages/react/src/index.tsx @@ -1,169 +1,5 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -import { - ConnectedWallet, - enhancedLocalStorage, - PORTKEYAA, -} from '@aelf-web-login/wallet-adapter-base'; -import { IBridgeAPI, IConfigProps, initBridge } from '@aelf-web-login/wallet-adapter-bridge'; -import React, { useCallback, useMemo, useState, useSyncExternalStore } from 'react'; +/* istanbul ignore file */ -const HOOK_ERROR_MESSAGE = - 'Must call the provided initialization method`init` method before using hooks.'; -// let noCommonBaseModal = false; -export const init = (options: IConfigProps): IBridgeAPI => { - // noCommonBaseModal = options.baseConfig.noCommonBaseModal ?? false; - if (options.baseConfig.showVconsole && typeof window !== 'undefined') { - import('vconsole') - .then((VConsole) => new VConsole.default()) - .catch((err) => console.log('Error loading VConsole:', err)); - } - console.log('aelf-web-login-init..............31'); - function initScriptAndMountApp() { - if (options.baseConfig.omitTelegramScript) { - return; - } - // const HOSTNAME_PREFIX_LIST = ['tg.', 'tg-test.', 'localhost']; - const TELEGRAM_SRC = 'https://telegram.org/js/telegram-web-app.js'; - if (typeof window !== 'undefined' && typeof location !== 'undefined') { - // if (HOSTNAME_PREFIX_LIST.some(h => location.hostname.includes(h))) { - const script = document.createElement('script'); - script.src = TELEGRAM_SRC; - script.async = false; - document.head.appendChild(script); - console.log('initScriptAndMountApp'); - // } - } - } - - initScriptAndMountApp(); - const dataFromBridge = initBridge(options); - return dataFromBridge; -}; - -export const WebLoginContext: React.Context = React.createContext( - {} as IBridgeAPI, -); - -interface IWebLoginProviderProps { - children: React.ReactNode; - bridgeAPI: IBridgeAPI; -} - -export const WebLoginProvider: React.FC = ({ children, bridgeAPI }) => { - // const { mountApp, unMountApp, getSignIn } = bridgeAPI ?? { - // mountApp: () => {}, - // unMountApp: () => {}, - // getSignIn: () => null, - // }; - // useEffect(() => { - // if (noCommonBaseModal) { - // return; - // } - // mountApp(); - // return unMountApp; - // }, [mountApp, unMountApp]); - const { getSignIn } = bridgeAPI ?? { - getSignIn: () => null, - }; - - if (!bridgeAPI) { - return null; - } - return ( - - {/* {noCommonBaseModal ? getSignIn(children) : children} */} - {getSignIn(children)} - - ); -}; - -export function useWebLoginContext(): IBridgeAPI { - const bridgeAPI = React.useContext(WebLoginContext); - - if (!bridgeAPI) { - throw new Error(HOOK_ERROR_MESSAGE); - } - - return bridgeAPI; -} - -function useExternalStore() { - const { store } = useWebLoginContext(); - const subscribe = useCallback( - (onStoreChange: () => void) => { - const unsubscribe = store.subscribe(onStoreChange); - - return () => unsubscribe; - }, - [store], - ); - - const getSnapshot = useCallback(() => { - return store.getState(); - }, [store]); - - const getServerSnapshot = () => getSnapshot(); - - return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); -} - -export function useConnectWallet() { - const { instance } = useWebLoginContext(); - const stateFromStore = useExternalStore(); - const { - connect, - disConnect, - lock, - getAccountByChainId, - getWalletSyncIsCompleted, - callSendMethod, - callViewMethod, - sendMultiTransaction, - getSignature, - clearManagerReadonlyStatus, - checkLoginStatus, - } = instance; - const [connecting, setConnecting] = useState(false); - - const isConnected = useMemo(() => { - if (enhancedLocalStorage.getItem(ConnectedWallet) === PORTKEYAA) { - return !!stateFromStore.walletInfo; - } else { - return !!enhancedLocalStorage.getItem(ConnectedWallet) || !!stateFromStore.walletInfo; - } - }, [stateFromStore.walletInfo]); - - const connectWallet = useCallback(async () => { - setConnecting(true); - const rs = await connect(); - setConnecting(false); - return rs; - }, [connect]); - - const disConnectWallet = useCallback(async () => { - const rs = await disConnect(); - return rs; - }, [disConnect]); - - return { - connectWallet, - disConnectWallet, - connecting, - walletInfo: stateFromStore.walletInfo, - isLocking: stateFromStore.isLocking, - walletType: stateFromStore.walletType, - isConnected: isConnected, - loginError: stateFromStore.loginError, - loginOnChainStatus: stateFromStore.loginOnChainStatus, - approvedGuardians: stateFromStore.approvedGuardians, - lock, - getAccountByChainId, - getWalletSyncIsCompleted, - callSendMethod, - callViewMethod, - getSignature, - sendMultiTransaction, - clearManagerReadonlyStatus, - checkLoginStatus, - }; -} +export { default as init } from './init'; +export { default as WebLoginContext, useWebLoginContext, WebLoginProvider } from './context'; +export { default as useConnectWallet } from './useConnectWallet'; diff --git a/packages/react/src/init.ts b/packages/react/src/init.ts new file mode 100644 index 00000000..2d21e570 --- /dev/null +++ b/packages/react/src/init.ts @@ -0,0 +1,34 @@ +import { IBridgeAPI, IConfigProps, initBridge } from '@aelf-web-login/wallet-adapter-bridge'; + +// let noCommonBaseModal = false; +const init = (options: IConfigProps): IBridgeAPI => { + // noCommonBaseModal = options.baseConfig.noCommonBaseModal ?? false; + if (options.baseConfig.showVconsole && typeof window !== 'undefined') { + import('vconsole') + .then((VConsole) => new VConsole.default()) + .catch((err) => console.log('Error loading VConsole:', err)); + } + console.log('aelf-web-login-init..............31'); + function initScriptAndMountApp() { + if (options.baseConfig.omitTelegramScript) { + return; + } + // const HOSTNAME_PREFIX_LIST = ['tg.', 'tg-test.', 'localhost']; + const TELEGRAM_SRC = 'https://telegram.org/js/telegram-web-app.js'; + if (typeof window !== 'undefined' && typeof location !== 'undefined') { + // if (HOSTNAME_PREFIX_LIST.some(h => location.hostname.includes(h))) { + const script = document.createElement('script'); + script.src = TELEGRAM_SRC; + script.async = false; + document.head.appendChild(script); + console.log('initScriptAndMountApp'); + // } + } + } + + initScriptAndMountApp(); + const dataFromBridge = initBridge(options); + return dataFromBridge; +}; + +export default init; diff --git a/packages/react/src/useConnectWallet.tsx b/packages/react/src/useConnectWallet.tsx new file mode 100644 index 00000000..7e4035d0 --- /dev/null +++ b/packages/react/src/useConnectWallet.tsx @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { + ConnectedWallet, + enhancedLocalStorage, + PORTKEYAA, +} from '@aelf-web-login/wallet-adapter-base'; +import { useCallback, useMemo, useState } from 'react'; + +import { useWebLoginContext } from './context'; +import useExternalStore from './useExternalStore'; + +function useConnectWallet() { + const { instance } = useWebLoginContext(); + const stateFromStore = useExternalStore(); + const { walletInfo, isLocking, walletType, loginError, loginOnChainStatus, approvedGuardians } = + stateFromStore ?? {}; + const { + connect, + disConnect, + lock, + getAccountByChainId, + getWalletSyncIsCompleted, + callSendMethod, + callViewMethod, + sendMultiTransaction, + getSignature, + clearManagerReadonlyStatus, + checkLoginStatus, + } = instance; + const [connecting, setConnecting] = useState(false); + + const isConnected = useMemo(() => { + if (enhancedLocalStorage.getItem(ConnectedWallet) === PORTKEYAA) { + return !!walletInfo; + } else { + return !!enhancedLocalStorage.getItem(ConnectedWallet) || !!walletInfo; + } + }, [walletInfo]); + + const connectWallet = useCallback(async () => { + setConnecting(true); + const rs = await connect(); + setConnecting(false); + return rs; + }, [connect]); + + const disConnectWallet = useCallback(async () => { + const rs = await disConnect(); + return rs; + }, [disConnect]); + + return { + connectWallet, + disConnectWallet, + connecting, + + walletInfo, + isLocking, + walletType, + isConnected, + loginError, + loginOnChainStatus, + approvedGuardians, + + lock, + getAccountByChainId, + getWalletSyncIsCompleted, + callSendMethod, + callViewMethod, + getSignature, + sendMultiTransaction, + clearManagerReadonlyStatus, + checkLoginStatus, + }; +} + +export default useConnectWallet; diff --git a/packages/react/src/useExternalStore.tsx b/packages/react/src/useExternalStore.tsx new file mode 100644 index 00000000..e5f80bbf --- /dev/null +++ b/packages/react/src/useExternalStore.tsx @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { useCallback, useSyncExternalStore } from 'react'; + +import { useWebLoginContext } from './context'; + +function useExternalStore() { + const { store } = useWebLoginContext(); + const subscribe = useCallback( + (onStoreChange: () => void) => { + const unsubscribe = store.subscribe(onStoreChange); + + return () => unsubscribe; + }, + [store], + ); + + const getSnapshot = useCallback(() => { + return store.getState(); + }, [store]); + + const getServerSnapshot = () => getSnapshot(); + + return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); +} + +export default useExternalStore;