diff --git a/packages/@magic-sdk/provider/src/core/sdk.ts b/packages/@magic-sdk/provider/src/core/sdk.ts index 3c5201d09..971cc97cb 100644 --- a/packages/@magic-sdk/provider/src/core/sdk.ts +++ b/packages/@magic-sdk/provider/src/core/sdk.ts @@ -196,6 +196,6 @@ export class SDKBase { * has completed loading and is ready for requests. */ public async preload() { - await this.overlay.ready; + await this.overlay.checkIsReadyForRequest; } } diff --git a/packages/@magic-sdk/provider/src/core/view-controller.ts b/packages/@magic-sdk/provider/src/core/view-controller.ts index 442926c22..6e575c577 100644 --- a/packages/@magic-sdk/provider/src/core/view-controller.ts +++ b/packages/@magic-sdk/provider/src/core/view-controller.ts @@ -10,6 +10,7 @@ import { createPromise } from '../util/promise-tools'; import { getItem, setItem } from '../util/storage'; import { createJwt } from '../util/web-crypto'; import { SDKEnvironment } from './sdk-environment'; +import { createModalNotReadyError } from './sdk-exceptions'; interface RemoveEventListenerFunction { (): void; @@ -88,7 +89,8 @@ async function persistMagicEventRefreshToken(event: MagicMessageEvent) { } export abstract class ViewController { - public ready: Promise; + private isReadyForRequest = false; + public checkIsReadyForRequest: Promise; protected readonly messageHandlers = new Set<(event: MagicMessageEvent) => any>(); /** @@ -99,7 +101,7 @@ export abstract class ViewController { * relevant iframe context. */ constructor(protected readonly endpoint: string, protected readonly parameters: string) { - this.ready = this.waitForReady(); + this.checkIsReadyForRequest = this.waitForReady(); this.listen(); } @@ -129,8 +131,17 @@ export abstract class ViewController { msgType: MagicOutgoingWindowMessage, payload: JsonRpcRequestPayload | JsonRpcRequestPayload[], ): Promise | JsonRpcResponse[]> { - return createPromise(async (resolve) => { - await this.ready; + return createPromise(async (resolve, reject) => { + if (SDKEnvironment.platform !== 'react-native') { + await this.checkIsReadyForRequest; + } else if (!this.isReadyForRequest) { + // On a mobile environment, `this.checkIsReadyForRequest` never resolves + // if the app was initially opened without internet connection. That is + // why we reject the promise without waiting and just let them call it + // again when internet connection is re-established. + const error = createModalNotReadyError(); + reject(error); + } const batchData: JsonRpcResponse[] = []; const batchIds = Array.isArray(payload) ? payload.map((p) => p.id) : []; @@ -194,7 +205,10 @@ export abstract class ViewController { private waitForReady() { return new Promise((resolve) => { - this.on(MagicIncomingWindowMessage.MAGIC_OVERLAY_READY, () => resolve()); + this.on(MagicIncomingWindowMessage.MAGIC_OVERLAY_READY, () => { + resolve(); + this.isReadyForRequest = true; + }); }); } diff --git a/packages/@magic-sdk/provider/test/spec/core/view-controller/post.spec.ts b/packages/@magic-sdk/provider/test/spec/core/view-controller/post.spec.ts index 47813eb61..f03975cb6 100644 --- a/packages/@magic-sdk/provider/test/spec/core/view-controller/post.spec.ts +++ b/packages/@magic-sdk/provider/test/spec/core/view-controller/post.spec.ts @@ -3,12 +3,12 @@ import browserEnv from '@ikscodes/browser-env'; import { MagicIncomingWindowMessage, MagicOutgoingWindowMessage, JsonRpcRequestPayload } from '@magic-sdk/types'; -import _ from 'lodash'; import { createViewController } from '../../../factories'; import { JsonRpcResponse } from '../../../../src/core/json-rpc'; import * as storage from '../../../../src/util/storage'; import * as webCryptoUtils from '../../../../src/util/web-crypto'; import { SDKEnvironment } from '../../../../src/core/sdk-environment'; +import { createModalNotReadyError } from '../../../../src/core/sdk-exceptions'; /** * Create a dummy request payload. @@ -56,7 +56,7 @@ function stubViewController(viewController: any, events: [MagicIncomingWindowMes const postSpy = jest.fn(); viewController.on = onSpy; - viewController.ready = Promise.resolve(); + viewController.checkIsReadyForRequest = Promise.resolve(); viewController._post = postSpy; return { handlerSpy, onSpy, postSpy }; @@ -201,6 +201,27 @@ test('Sends payload and stores rt if response event contains rt', async () => { expect(FAKE_STORE.rt).toEqual(FAKE_RT); }); +test('does not wait for ready and throws error when platform is react-native', async () => { + SDKEnvironment.platform = 'react-native'; + const eventWithRt = { data: { ...responseEvent().data } }; + const viewController = createViewController('asdf'); + const { handlerSpy, onSpy } = stubViewController(viewController, [ + [MagicIncomingWindowMessage.MAGIC_HANDLE_RESPONSE, eventWithRt], + ]); + viewController.checkIsReadyForRequest = new Promise(() => null); + + const payload = requestPayload(); + + try { + await viewController.post(MagicOutgoingWindowMessage.MAGIC_HANDLE_REQUEST, payload); + } catch (e) { + expect(e).toEqual(createModalNotReadyError()); + } + expect(createJwtStub).not.toHaveBeenCalledWith(); + expect(onSpy.mock.calls[0][0]).toEqual(MagicIncomingWindowMessage.MAGIC_HANDLE_RESPONSE); + expect(handlerSpy).not.toHaveBeenCalled(); +}); + test('does not call web crypto api if platform is not web', async () => { SDKEnvironment.platform = 'react-native'; const eventWithRt = { data: { ...responseEvent().data } }; @@ -210,6 +231,9 @@ test('does not call web crypto api if platform is not web', async () => { ]); const payload = requestPayload(); + // @ts-ignore isReadyForRequest is private + viewController.isReadyForRequest = true; + const response = await viewController.post(MagicOutgoingWindowMessage.MAGIC_HANDLE_REQUEST, payload); expect(createJwtStub).not.toHaveBeenCalledWith(); diff --git a/packages/@magic-sdk/react-native-bare/README.md b/packages/@magic-sdk/react-native-bare/README.md index 25a92ba1e..98b9f7d1c 100644 --- a/packages/@magic-sdk/react-native-bare/README.md +++ b/packages/@magic-sdk/react-native-bare/README.md @@ -27,12 +27,14 @@ npm install --save @magic-sdk/react-native-bare npm install --save react-native-device-info # Required Peer Dependency npm install --save @react-native-community/async-storage # Required Peer Dependency npm install --save react-native-safe-area-context # Required Peer Dependency +npm install --save @react-native-community/netinfo # Required Peer Dependency # Via Yarn: yarn add @magic-sdk/react-native-bare -⁠yarn add react-native-device-info # Required Peer Dependency +yarn add react-native-device-info # Required Peer Dependency yarn add @react-native-community/async-storage # Required Peer Dependency yarn add react-native-safe-area-context # Required Peer Dependency +yarn add @react-native-community/netinfo # Required Peer Dependency ``` ## ⚡️ Quick Start @@ -69,4 +71,54 @@ Please note that as of **v14.0.0** our React Native package offerings wrap the ` We have also added an optional `backgroundColor` prop to the `Relayer` to fix issues with `SafeAreaView` showing the background. By default, the background will be white. If you have changed the background color as part of your [custom branding setup](https://magic.link/docs/authentication/features/login-ui#configuration), make sure to pass your custom background color to `magic.Relayer`: ```tsx +``` + +## 🙌🏾 Troubleshooting + +### Symlinking in Monorepo w/ Metro + +For React Native projects living within a **monorepo** that run into the following `TypeError: Undefined is not an object` error: + +Screenshot 2022-11-23 at 12 19 19 PM + +When attempting to import `Magic`, take note that the React Native metro bundler doesn’t work well with symlinks, which tend to be utilized by most package managers. + +For this issue consider using Microsoft's [rnx-kit](https://microsoft.github.io/rnx-kit/docs/guides/bundling) suite of tools that include a plugin for metro that fixes this symlink related error. + +### Handling internet connection problems +When an app is opened without internet connection, any request to the Magic SDK will result in a rejection with a `MagicSDKError`: + +```json +{ + "code": "MODAL_NOT_READY", + "rawMessage": "Modal is not ready." +} +``` + + +It is good practice to use [@react-native-community/netinfo](https://www.npmjs.com/package/@react-native-community/netinfo) to track the internet connection state of the device. For your convenience, we've also added a hook that uses this library behind the scenes: + + + ```tsx +import { useInternetConnection } from '@magic-sdk/react-native-expo'; + +const magic = new Magic('YOUR_API_KEY'); + +const connected = useInternetConnection() + +useEffect(() => { + if (!connected) { + // Unomount this component and show your "You're offline" screen. + } +}, [connected]) + +export default function App() { + return <> + + {/* Render the Magic iframe! */} + + {...} + + +} ``` \ No newline at end of file diff --git a/packages/@magic-sdk/react-native-bare/jest.config.ts b/packages/@magic-sdk/react-native-bare/jest.config.ts index 5c03b1ea8..a948d3ed2 100644 --- a/packages/@magic-sdk/react-native-bare/jest.config.ts +++ b/packages/@magic-sdk/react-native-bare/jest.config.ts @@ -3,7 +3,7 @@ import type { Config } from '@jest/types'; const config: Config.InitialOptions = { ...baseJestConfig, - preset: 'react-native', + preset: '@testing-library/react-native', transform: { '^.+\\.(js|jsx)$': 'babel-jest', '\\.(ts|tsx)$': 'ts-jest', diff --git a/packages/@magic-sdk/react-native-bare/package.json b/packages/@magic-sdk/react-native-bare/package.json index 9e554fc7f..9569ead82 100644 --- a/packages/@magic-sdk/react-native-bare/package.json +++ b/packages/@magic-sdk/react-native-bare/package.json @@ -37,15 +37,19 @@ "@babel/plugin-transform-flow-strip-types": "^7.14.5", "@babel/runtime": "~7.10.4", "@react-native-async-storage/async-storage": "^1.15.5", + "@react-native-community/netinfo": ">11.0.0", + "@testing-library/react-native": "^12.4.0", "metro-react-native-babel-preset": "^0.66.2", "react": "^16.13.1", "react-native": "^0.62.2", "react-native-device-info": "^10.3.0", "react-native-safe-area-context": "^4.4.1", - "react-native-webview": "^12.4.0" + "react-native-webview": "^12.4.0", + "react-test-renderer": "^16.13.1" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.15.5", + "@react-native-community/netinfo": ">11.0.0", "react": ">=16", "react-native": ">=0.60", "react-native-device-info": ">=10.3.0", diff --git a/packages/@magic-sdk/react-native-bare/src/hooks.ts b/packages/@magic-sdk/react-native-bare/src/hooks.ts new file mode 100644 index 000000000..fb68bcece --- /dev/null +++ b/packages/@magic-sdk/react-native-bare/src/hooks.ts @@ -0,0 +1,16 @@ +import { useEffect, useState } from 'react'; +import NetInfo, { NetInfoState } from '@react-native-community/netinfo'; + +export const useInternetConnection = () => { + const [isConnected, setIsConnected] = useState(true); + useEffect(() => { + const handleConnectionChange = (connectionInfo: NetInfoState) => { + setIsConnected(!!connectionInfo.isConnected); + }; + + // Subscribe to connection changes and cleanup on unmount + return NetInfo.addEventListener(handleConnectionChange); + }, []); + + return isConnected; +}; diff --git a/packages/@magic-sdk/react-native-bare/src/index.ts b/packages/@magic-sdk/react-native-bare/src/index.ts index 0fd3bf919..c8fd7b37f 100644 --- a/packages/@magic-sdk/react-native-bare/src/index.ts +++ b/packages/@magic-sdk/react-native-bare/src/index.ts @@ -69,3 +69,5 @@ export type Magic = MagicSDKExtensionsOp SDKBaseReactNative, T >; + +export { useInternetConnection } from './hooks'; diff --git a/packages/@magic-sdk/react-native-bare/test/mocks.ts b/packages/@magic-sdk/react-native-bare/test/mocks.ts index c183ae590..2656ebedc 100644 --- a/packages/@magic-sdk/react-native-bare/test/mocks.ts +++ b/packages/@magic-sdk/react-native-bare/test/mocks.ts @@ -1,3 +1,41 @@ +// @react-native-community/netinfo mocks +const defaultState = { + type: 'cellular', + isConnected: true, + isInternetReachable: true, + details: { + isConnectionExpensive: true, + cellularGeneration: '3g', + }, +}; + +const NetInfoStateType = { + unknown: 'unknown', + none: 'none', + cellular: 'cellular', + wifi: 'wifi', + bluetooth: 'bluetooth', + ethernet: 'ethernet', + wimax: 'wimax', + vpn: 'vpn', + other: 'other', +}; + +const RNCNetInfoMock = { + NetInfoStateType, + configure: jest.fn(), + fetch: jest.fn(), + refresh: jest.fn(), + addEventListener: jest.fn(), + useNetInfo: jest.fn(), + getCurrentState: jest.fn(), +}; + +RNCNetInfoMock.fetch.mockResolvedValue(defaultState); +RNCNetInfoMock.refresh.mockResolvedValue(defaultState); +RNCNetInfoMock.useNetInfo.mockReturnValue(defaultState); +RNCNetInfoMock.addEventListener.mockReturnValue(jest.fn()); + export function reactNativeStyleSheetStub() { const { StyleSheet } = jest.requireActual('react-native'); return jest.spyOn(StyleSheet, 'create'); @@ -6,9 +44,9 @@ export function reactNativeStyleSheetStub() { const noopModule = () => ({}); export function removeReactDependencies() { - jest.mock('react', noopModule); jest.mock('react-native-webview', noopModule); jest.mock('react-native-safe-area-context', noopModule); + jest.mock('@react-native-community/netinfo', () => RNCNetInfoMock); // The `localforage` driver we use to enable React Native's `AsyncStorage` // currently uses an `import` statement at the top of it's index file, this is diff --git a/packages/@magic-sdk/react-native-bare/test/spec/hooks.spec.ts b/packages/@magic-sdk/react-native-bare/test/spec/hooks.spec.ts new file mode 100644 index 000000000..e665eb525 --- /dev/null +++ b/packages/@magic-sdk/react-native-bare/test/spec/hooks.spec.ts @@ -0,0 +1,54 @@ +import { act, renderHook } from '@testing-library/react-native'; +import NetInfo, { NetInfoStateType } from '@react-native-community/netinfo'; +import { useInternetConnection } from '../../src/hooks'; + +beforeAll(() => { + // @ts-ignore mock resolved value + NetInfo.getCurrentState.mockResolvedValue({ + type: NetInfoStateType.cellular, + isConnected: true, + isInternetReachable: true, + details: { + isConnectionExpensive: true, + cellularGeneration: '4g', + }, + }); +}); + +describe('useInternetConnection', () => { + it('should initialize with true when connected', async () => { + const { result } = renderHook(() => useInternetConnection()); + + expect(result.current).toBe(true); + }); + + it('should call the listener when the connection changes', async () => { + NetInfo.addEventListener = jest.fn(); + + const { result } = renderHook(() => useInternetConnection()); + + // Initial render, assuming it's connected + expect(result.current).toBe(true); + + // Simulate a change in connection status + act(() => { + // @ts-ignore mock calls + NetInfo.addEventListener.mock.calls[0][0]({ + type: 'cellular', + isConnected: false, + isInternetReachable: true, + details: { + isConnectionExpensive: true, + }, + }); + }); + + // Wait for the next tick of the event loop to allow state update + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); // or setImmediate + }); + + // Check if the hook state has been updated + expect(result.current).toBe(false); + }); +}); diff --git a/packages/@magic-sdk/react-native-expo/README.md b/packages/@magic-sdk/react-native-expo/README.md index 4436adf8a..9f9aa6018 100644 --- a/packages/@magic-sdk/react-native-expo/README.md +++ b/packages/@magic-sdk/react-native-expo/README.md @@ -25,11 +25,13 @@ As of `v19.0.0`, passcodes (ie. `loginWithSMS()`, `loginWithEmailOTP()`) are rep npm install --save @magic-sdk/react-native-expo npm install --save react-native-webview@^11.26.0 # Required Peer Dependency npm install --save react-native-safe-area-context # Required Peer Dependency + npm install --save @react-native-community/netinfo # Required Peer Dependency # Via Yarn: yarn add @magic-sdk/react-native-expo yarn add react-native-webview@^11.26.0 # Required Peer Dependency yarn add react-native-safe-area-context # Required Peer Dependency + yarn add @react-native-community/netinfo # Required Peer Dependency ``` ## ⚡️ Quick Start @@ -73,3 +75,40 @@ For React Native projects living within a **monorepo** that run into the followi When attempting to import `Magic`, take note that the React Native metro bundler doesn’t work well with symlinks, which tend to be utilized by most package managers. For this issue consider using Microsoft's [rnx-kit](https://microsoft.github.io/rnx-kit/docs/guides/bundling) suite of tools that include a plugin for metro that fixes this symlink related error. + +### Handling internet connection problems +When an app is opened without internet connection, any request to the Magic SDK will result in a rejection with a `MagicSDKError`: + +```json +{ + "code": "MODAL_NOT_READY", + "rawMessage": "Modal is not ready." +} +``` + +It is good practice to use [@react-native-community/netinfo](https://www.npmjs.com/package/@react-native-community/netinfo) to track the internet connection state of the device. For your convenience, we've also added a hook that uses this library behind the scenes: + + + ```tsx +import { useInternetConnection } from '@magic-sdk/react-native-expo'; + +const magic = new Magic('YOUR_API_KEY'); + +const connected = useInternetConnection() + +useEffect(() => { + if (!connected) { + // Unomount this component and show your "You're offline" screen. + } +}, [connected]) + +export default function App() { + return <> + + {/* Render the Magic iframe! */} + + {...} + + +} +``` \ No newline at end of file diff --git a/packages/@magic-sdk/react-native-expo/jest.config.ts b/packages/@magic-sdk/react-native-expo/jest.config.ts index 5c03b1ea8..a948d3ed2 100644 --- a/packages/@magic-sdk/react-native-expo/jest.config.ts +++ b/packages/@magic-sdk/react-native-expo/jest.config.ts @@ -3,7 +3,7 @@ import type { Config } from '@jest/types'; const config: Config.InitialOptions = { ...baseJestConfig, - preset: 'react-native', + preset: '@testing-library/react-native', transform: { '^.+\\.(js|jsx)$': 'babel-jest', '\\.(ts|tsx)$': 'ts-jest', diff --git a/packages/@magic-sdk/react-native-expo/package.json b/packages/@magic-sdk/react-native-expo/package.json index 067263671..a4a5f88f7 100644 --- a/packages/@magic-sdk/react-native-expo/package.json +++ b/packages/@magic-sdk/react-native-expo/package.json @@ -36,14 +36,18 @@ "@babel/core": "^7.15.0", "@babel/plugin-transform-flow-strip-types": "^7.14.5", "@babel/runtime": "~7.10.4", + "@react-native-community/netinfo": ">11.0.0", + "@testing-library/react-native": "^12.4.0", "expo-modules-core": "^1.0.4", "metro-react-native-babel-preset": "^0.66.2", "react": "^16.13.1", "react-native": "^0.62.2", "react-native-safe-area-context": "^4.4.1", - "react-native-webview": ">=12.4.0" + "react-native-webview": ">=12.4.0", + "react-test-renderer": "^16.13.1" }, "peerDependencies": { + "@react-native-community/netinfo": ">11.0.0", "expo": "*", "react": ">=16", "react-native": ">=0.60", diff --git a/packages/@magic-sdk/react-native-expo/src/hooks.ts b/packages/@magic-sdk/react-native-expo/src/hooks.ts new file mode 100644 index 000000000..8178bfa35 --- /dev/null +++ b/packages/@magic-sdk/react-native-expo/src/hooks.ts @@ -0,0 +1,16 @@ +import { useEffect, useState } from 'react'; +import NetInfo, { NetInfoState } from '@react-native-community/netinfo'; + +export const useInternetConnection = () => { + const [isConnected, setIsConnected] = useState(true); + useEffect(() => { + const handleConnectionChange = (connectionInfo: NetInfoState) => { + setIsConnected(!!connectionInfo.isConnected); + }; + + // Subscribe to connection changes + return NetInfo.addEventListener(handleConnectionChange); + }, []); + + return isConnected; +}; diff --git a/packages/@magic-sdk/react-native-expo/src/index.ts b/packages/@magic-sdk/react-native-expo/src/index.ts index f983181d8..5f287a4e4 100644 --- a/packages/@magic-sdk/react-native-expo/src/index.ts +++ b/packages/@magic-sdk/react-native-expo/src/index.ts @@ -69,3 +69,5 @@ export type Magic = MagicSDKExtensionsOp SDKBaseReactNative, T >; + +export { useInternetConnection } from './hooks'; diff --git a/packages/@magic-sdk/react-native-expo/test/mocks.ts b/packages/@magic-sdk/react-native-expo/test/mocks.ts index e7af0a8fc..f40970904 100644 --- a/packages/@magic-sdk/react-native-expo/test/mocks.ts +++ b/packages/@magic-sdk/react-native-expo/test/mocks.ts @@ -1,3 +1,41 @@ +// @react-native-community/netinfo mocks +const defaultState = { + type: 'cellular', + isConnected: true, + isInternetReachable: true, + details: { + isConnectionExpensive: true, + cellularGeneration: '3g', + }, +}; + +const NetInfoStateType = { + unknown: 'unknown', + none: 'none', + cellular: 'cellular', + wifi: 'wifi', + bluetooth: 'bluetooth', + ethernet: 'ethernet', + wimax: 'wimax', + vpn: 'vpn', + other: 'other', +}; + +const RNCNetInfoMock = { + NetInfoStateType, + configure: jest.fn(), + fetch: jest.fn(), + refresh: jest.fn(), + addEventListener: jest.fn(), + useNetInfo: jest.fn(), + getCurrentState: jest.fn(), +}; + +RNCNetInfoMock.fetch.mockResolvedValue(defaultState); +RNCNetInfoMock.refresh.mockResolvedValue(defaultState); +RNCNetInfoMock.useNetInfo.mockReturnValue(defaultState); +RNCNetInfoMock.addEventListener.mockReturnValue(jest.fn()); + export function reactNativeStyleSheetStub() { const { StyleSheet } = jest.requireActual('react-native'); return jest.spyOn(StyleSheet, 'create'); @@ -6,9 +44,9 @@ export function reactNativeStyleSheetStub() { const noopModule = () => ({}); export function removeReactDependencies() { - jest.mock('react', noopModule); jest.mock('react-native-webview', noopModule); jest.mock('react-native-safe-area-context', noopModule); + jest.mock('@react-native-community/netinfo', () => RNCNetInfoMock); // The `localforage` driver we use to enable React Native's `AsyncStorage` // currently uses an `import` statement at the top of it's index file, this is diff --git a/packages/@magic-sdk/react-native-expo/test/spec/hooks.spec.ts b/packages/@magic-sdk/react-native-expo/test/spec/hooks.spec.ts new file mode 100644 index 000000000..e665eb525 --- /dev/null +++ b/packages/@magic-sdk/react-native-expo/test/spec/hooks.spec.ts @@ -0,0 +1,54 @@ +import { act, renderHook } from '@testing-library/react-native'; +import NetInfo, { NetInfoStateType } from '@react-native-community/netinfo'; +import { useInternetConnection } from '../../src/hooks'; + +beforeAll(() => { + // @ts-ignore mock resolved value + NetInfo.getCurrentState.mockResolvedValue({ + type: NetInfoStateType.cellular, + isConnected: true, + isInternetReachable: true, + details: { + isConnectionExpensive: true, + cellularGeneration: '4g', + }, + }); +}); + +describe('useInternetConnection', () => { + it('should initialize with true when connected', async () => { + const { result } = renderHook(() => useInternetConnection()); + + expect(result.current).toBe(true); + }); + + it('should call the listener when the connection changes', async () => { + NetInfo.addEventListener = jest.fn(); + + const { result } = renderHook(() => useInternetConnection()); + + // Initial render, assuming it's connected + expect(result.current).toBe(true); + + // Simulate a change in connection status + act(() => { + // @ts-ignore mock calls + NetInfo.addEventListener.mock.calls[0][0]({ + type: 'cellular', + isConnected: false, + isInternetReachable: true, + details: { + isConnectionExpensive: true, + }, + }); + }); + + // Wait for the next tick of the event loop to allow state update + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); // or setImmediate + }); + + // Check if the hook state has been updated + expect(result.current).toBe(false); + }); +}); diff --git a/yarn.lock b/yarn.lock index 52c3d22c7..73e3d8de1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1841,6 +1841,15 @@ __metadata: languageName: node linkType: hard +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": ^0.27.8 + checksum: 910040425f0fc93cd13e68c750b7885590b8839066dfa0cd78e7def07bbb708ad869381f725945d66f2284de5663bbecf63e8fdd856e2ae6e261ba30b1687e93 + languageName: node + linkType: hard + "@jest/source-map@npm:^24.9.0": version: 24.9.0 resolution: "@jest/source-map@npm:24.9.0" @@ -3120,6 +3129,8 @@ __metadata: "@magic-sdk/provider": ^21.2.1 "@magic-sdk/types": ^17.2.0 "@react-native-async-storage/async-storage": ^1.15.5 + "@react-native-community/netinfo": ">11.0.0" + "@testing-library/react-native": ^12.4.0 "@types/lodash": ^4.14.158 buffer: ~5.6.0 localforage: ^1.7.4 @@ -3132,10 +3143,12 @@ __metadata: react-native-device-info: ^10.3.0 react-native-safe-area-context: ^4.4.1 react-native-webview: ^12.4.0 + react-test-renderer: ^16.13.1 tslib: ^2.0.3 whatwg-url: ~8.1.0 peerDependencies: "@react-native-async-storage/async-storage": ">=1.15.5" + "@react-native-community/netinfo": ">11.0.0" react: ">=16" react-native: ">=0.60" react-native-device-info: ">=10.3.0" @@ -3156,6 +3169,8 @@ __metadata: "@magic-sdk/provider": ^21.2.1 "@magic-sdk/types": ^17.2.0 "@react-native-async-storage/async-storage": ^1.15.5 + "@react-native-community/netinfo": ">11.0.0" + "@testing-library/react-native": ^12.4.0 "@types/lodash": ^4.14.158 buffer: ~5.6.0 expo-application: ^5.0.1 @@ -3169,9 +3184,11 @@ __metadata: react-native: ^0.62.2 react-native-safe-area-context: ^4.4.1 react-native-webview: ">=12.4.0" + react-test-renderer: ^16.13.1 tslib: ^2.0.3 whatwg-url: ~8.1.0 peerDependencies: + "@react-native-community/netinfo": ">11.0.0" expo: "*" react: ">=16" react-native: ">=0.60" @@ -3877,6 +3894,15 @@ __metadata: languageName: node linkType: hard +"@react-native-community/netinfo@npm:>11.0.0": + version: 11.1.0 + resolution: "@react-native-community/netinfo@npm:11.1.0" + peerDependencies: + react-native: ">=0.59" + checksum: bbfa921c7d21f4c1c8bd01789256e096eed054ca6663caaa34446f1429f8394ec630c99f524a35e1a0a5873b3bc7f135e5721a7cc53f6cea68509b8723386db8 + languageName: node + linkType: hard + "@scure/base@npm:~1.1.0": version: 1.1.1 resolution: "@scure/base@npm:1.1.1" @@ -3894,6 +3920,13 @@ __metadata: languageName: node linkType: hard +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 00bd7362a3439021aa1ea51b0e0d0a0e8ca1351a3d54c606b115fdcc49b51b16db6e5f43b4fe7a28c38688523e22a94d49dd31168868b655f0d4d50f032d07a1 + languageName: node + linkType: hard + "@sinonjs/commons@npm:^1.7.0": version: 1.8.6 resolution: "@sinonjs/commons@npm:1.8.6" @@ -3944,6 +3977,25 @@ __metadata: languageName: node linkType: hard +"@testing-library/react-native@npm:^12.4.0": + version: 12.4.0 + resolution: "@testing-library/react-native@npm:12.4.0" + dependencies: + jest-matcher-utils: ^29.7.0 + pretty-format: ^29.7.0 + redent: ^3.0.0 + peerDependencies: + jest: ">=28.0.0" + react: ">=16.8.0" + react-native: ">=0.59" + react-test-renderer: ">=16.8.0" + peerDependenciesMeta: + jest: + optional: true + checksum: 417c6deaddda3c853c23ae9db21ee817c91cdeefebf6a905bbf85ff5038863f240a8f0a6c91e48a7171030e4d7ead093d7c4e1d075fb65e48b2b266b7fa0a5f3 + languageName: node + linkType: hard + "@tootallnate/once@npm:1": version: 1.1.2 resolution: "@tootallnate/once@npm:1.1.2" @@ -7452,6 +7504,13 @@ __metadata: languageName: node linkType: hard +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: f4914158e1f2276343d98ff5b31fc004e7304f5470bf0f1adb2ac6955d85a531a6458d33e87667f98f6ae52ebd3891bb47d420bb48a5bd8b7a27ee25b20e33aa + languageName: node + linkType: hard + "diff@npm:^4.0.1": version: 4.0.2 resolution: "diff@npm:4.0.2" @@ -11582,6 +11641,18 @@ fsevents@^2.3.2: languageName: node linkType: hard +"jest-diff@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: ^4.0.0 + diff-sequences: ^29.6.3 + jest-get-type: ^29.6.3 + pretty-format: ^29.7.0 + checksum: 08e24a9dd43bfba1ef07a6374e5af138f53137b79ec3d5cc71a2303515335898888fa5409959172e1e05de966c9e714368d15e8994b0af7441f0721ee8e1bb77 + languageName: node + linkType: hard + "jest-docblock@npm:^27.5.1": version: 27.5.1 resolution: "jest-docblock@npm:27.5.1" @@ -11647,6 +11718,13 @@ fsevents@^2.3.2: languageName: node linkType: hard +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 88ac9102d4679d768accae29f1e75f592b760b44277df288ad76ce5bf038c3f5ce3719dea8aa0f035dac30e9eb034b848ce716b9183ad7cc222d029f03e92205 + languageName: node + linkType: hard + "jest-haste-map@npm:^24.9.0": version: 24.9.0 resolution: "jest-haste-map@npm:24.9.0" @@ -11741,6 +11819,18 @@ fsevents@^2.3.2: languageName: node linkType: hard +"jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" + dependencies: + chalk: ^4.0.0 + jest-diff: ^29.7.0 + jest-get-type: ^29.6.3 + pretty-format: ^29.7.0 + checksum: d7259e5f995d915e8a37a8fd494cb7d6af24cd2a287b200f831717ba0d015190375f9f5dc35393b8ba2aae9b2ebd60984635269c7f8cff7d85b077543b7744cd + languageName: node + linkType: hard + "jest-message-util@npm:^24.9.0": version: 24.9.0 resolution: "jest-message-util@npm:24.9.0" @@ -15644,6 +15734,17 @@ fsevents@^2.3.2: languageName: node linkType: hard +"pretty-format@npm:^29.7.0": + version: 29.7.0 + resolution: "pretty-format@npm:29.7.0" + dependencies: + "@jest/schemas": ^29.6.3 + ansi-styles: ^5.0.0 + react-is: ^18.0.0 + checksum: 032c1602383e71e9c0c02a01bbd25d6759d60e9c7cf21937dde8357aa753da348fcec5def5d1002c9678a8524d5fe099ad98861286550ef44de8808cc61e43b6 + languageName: node + linkType: hard + "pretty-ms@npm:^7.0.0": version: 7.0.1 resolution: "pretty-ms@npm:7.0.1" @@ -15984,7 +16085,7 @@ fsevents@^2.3.2: languageName: node linkType: hard -"react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.8.4": +"react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.8.4, react-is@npm:^16.8.6": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -15998,6 +16099,13 @@ fsevents@^2.3.2: languageName: node linkType: hard +"react-is@npm:^18.0.0": + version: 18.2.0 + resolution: "react-is@npm:18.2.0" + checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e + languageName: node + linkType: hard + "react-native-device-info@npm:^10.3.0": version: 10.4.0 resolution: "react-native-device-info@npm:10.4.0" @@ -16114,6 +16222,20 @@ fsevents@^2.3.2: languageName: node linkType: hard +"react-test-renderer@npm:^16.13.1": + version: 16.14.0 + resolution: "react-test-renderer@npm:16.14.0" + dependencies: + object-assign: ^4.1.1 + prop-types: ^15.6.2 + react-is: ^16.8.6 + scheduler: ^0.19.1 + peerDependencies: + react: ^16.14.0 + checksum: 96eb8a2566e67ebd246ef6e1b36d8c8498c68ebfdb94ca8399c19b4e3b73368caf0ffbe44767593e3499f2f58b4b5e57ba0565a47628048d2ab01b23a422724e + languageName: node + linkType: hard + "react@npm:^16.13.1": version: 16.14.0 resolution: "react@npm:16.14.0" @@ -16969,6 +17091,16 @@ resolve@~1.7.1: languageName: node linkType: hard +"scheduler@npm:^0.19.1": + version: 0.19.1 + resolution: "scheduler@npm:0.19.1" + dependencies: + loose-envify: ^1.1.0 + object-assign: ^4.1.1 + checksum: 73e185a59e2ff5aa3609f5b9cb97ddd376f89e1610579d29939d952411ca6eb7a24907a4ea4556569dacb931467a1a4a56d94fe809ef713aa76748642cd96a6c + languageName: node + linkType: hard + "scrypt-js@npm:^3.0.0": version: 3.0.1 resolution: "scrypt-js@npm:3.0.1"