|
1 | | -jest.mock('react-native-teleport', () => { |
2 | | - const React = require('react'); |
3 | | - const { Text, View } = require('react-native'); |
4 | | - |
5 | | - return { |
6 | | - Portal: ({ |
7 | | - children, |
8 | | - hostName, |
9 | | - name, |
10 | | - }: { |
11 | | - children: React.ReactNode; |
12 | | - hostName?: string; |
13 | | - name: string; |
14 | | - }) => ( |
15 | | - <View testID={`portal-${name}`}> |
16 | | - <Text testID={`portal-${name}-host`}>{hostName ?? 'none'}</Text> |
17 | | - {children} |
18 | | - </View> |
19 | | - ), |
20 | | - PortalHost: ({ name }: { name: string }) => <Text testID={`portal-host-${name}`}>{name}</Text>, |
21 | | - PortalProvider: View, |
22 | | - usePortal: jest.fn().mockReturnValue({ removePortal: jest.fn() }), |
23 | | - }; |
24 | | -}); |
25 | | - |
26 | | -jest.mock('../../../state-store/message-overlay-store', () => ({ |
27 | | - clearClosingPortalLayout: jest.fn(), |
28 | | - createClosingPortalLayoutRegistrationId: jest.fn(), |
29 | | - setClosingPortalLayout: jest.fn(), |
30 | | - useShouldTeleportToClosingPortal: jest.fn(), |
31 | | -})); |
32 | | - |
33 | 1 | jest.mock('../../../state-store', () => { |
34 | | - const mockedMessageOverlayStore = jest.requireMock('../../../state-store/message-overlay-store'); |
| 2 | + const actual = jest.requireActual('../../../state-store'); |
| 3 | + const createClosingPortalLayoutRegistrationId = jest.fn(() => 'registration-1'); |
| 4 | + |
| 5 | + return new Proxy(actual, { |
| 6 | + get(target, prop, receiver) { |
| 7 | + if (prop === 'createClosingPortalLayoutRegistrationId') { |
| 8 | + return createClosingPortalLayoutRegistrationId; |
| 9 | + } |
35 | 10 |
|
36 | | - return { |
37 | | - ...mockedMessageOverlayStore, |
38 | | - }; |
| 11 | + return Reflect.get(target, prop, receiver); |
| 12 | + }, |
| 13 | + }); |
39 | 14 | }); |
40 | 15 |
|
41 | 16 | import React from 'react'; |
42 | 17 | import { Text } from 'react-native'; |
43 | 18 |
|
44 | 19 | import { act, cleanup, render, screen } from '@testing-library/react-native'; |
45 | 20 |
|
46 | | -import * as stateStore from '../../../state-store/message-overlay-store'; |
| 21 | +import * as stateStore from '../../../state-store'; |
47 | 22 | import { PortalWhileClosingView } from '../PortalWhileClosingView'; |
48 | 23 |
|
49 | | -const mockedCreateClosingPortalLayoutRegistrationId = |
50 | | - stateStore.createClosingPortalLayoutRegistrationId as jest.Mock; |
51 | | -const mockedClearClosingPortalLayout = stateStore.clearClosingPortalLayout as jest.Mock; |
52 | | -const mockedUseShouldTeleportToClosingPortal = |
53 | | - stateStore.useShouldTeleportToClosingPortal as jest.Mock; |
| 24 | +const BASE_RECT = { h: 48, w: 120, x: 12, y: 24 }; |
| 25 | + |
| 26 | +const flushAnimationFrameQueue = () => { |
| 27 | + act(() => { |
| 28 | + jest.runAllTimers(); |
| 29 | + }); |
| 30 | +}; |
| 31 | + |
| 32 | +const TeleportStateProbe = ({ |
| 33 | + hostName, |
| 34 | + id, |
| 35 | + testID, |
| 36 | +}: { |
| 37 | + hostName: string; |
| 38 | + id: string; |
| 39 | + testID: string; |
| 40 | +}) => { |
| 41 | + const shouldTeleport = stateStore.useShouldTeleportToClosingPortal(hostName, id); |
| 42 | + |
| 43 | + return <Text testID={testID}>{shouldTeleport ? 'true' : 'false'}</Text>; |
| 44 | +}; |
| 45 | + |
| 46 | +const BlacklistRegistrar = ({ hostNames }: { hostNames: string[] }) => { |
| 47 | + stateStore.useClosingPortalHostBlacklist(hostNames); |
| 48 | + return null; |
| 49 | +}; |
| 50 | + |
| 51 | +const findPortalNode = ( |
| 52 | + node: ReturnType<ReturnType<typeof render>['toJSON']>, |
| 53 | + portalName: string, |
| 54 | +): null | { props?: { hostName?: string; name?: string } } => { |
| 55 | + if (!node) { |
| 56 | + return null; |
| 57 | + } |
| 58 | + |
| 59 | + if (Array.isArray(node)) { |
| 60 | + for (const child of node) { |
| 61 | + const found = findPortalNode(child, portalName); |
| 62 | + if (found) { |
| 63 | + return found; |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + return null; |
| 68 | + } |
| 69 | + |
| 70 | + if (node.props?.name === portalName) { |
| 71 | + return node; |
| 72 | + } |
| 73 | + |
| 74 | + for (const child of node.children ?? []) { |
| 75 | + if (typeof child === 'string') { |
| 76 | + continue; |
| 77 | + } |
| 78 | + |
| 79 | + const found = findPortalNode(child, portalName); |
| 80 | + if (found) { |
| 81 | + return found; |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + return null; |
| 86 | +}; |
54 | 87 |
|
55 | 88 | describe('PortalWhileClosingView', () => { |
56 | 89 | beforeEach(() => { |
57 | | - mockedClearClosingPortalLayout.mockClear(); |
58 | | - mockedUseShouldTeleportToClosingPortal.mockReset(); |
59 | | - mockedUseShouldTeleportToClosingPortal.mockReturnValue(false); |
60 | | - mockedCreateClosingPortalLayoutRegistrationId.mockReset(); |
61 | | - mockedCreateClosingPortalLayoutRegistrationId |
62 | | - .mockReturnValueOnce('registration-1') |
63 | | - .mockReturnValueOnce('registration-2') |
64 | | - .mockReturnValue('registration-fallback'); |
| 90 | + jest.useFakeTimers(); |
| 91 | + |
| 92 | + act(() => { |
| 93 | + stateStore.finalizeCloseOverlay(); |
| 94 | + stateStore.overlayStore.next({ |
| 95 | + closing: false, |
| 96 | + closingPortalHostBlacklist: [], |
| 97 | + id: undefined, |
| 98 | + }); |
| 99 | + }); |
65 | 100 | }); |
66 | 101 |
|
67 | 102 | afterEach(() => { |
68 | 103 | cleanup(); |
| 104 | + |
| 105 | + act(() => { |
| 106 | + stateStore.clearClosingPortalLayout('overlay-composer', 'registration-1'); |
| 107 | + stateStore.finalizeCloseOverlay(); |
| 108 | + stateStore.overlayStore.next({ |
| 109 | + closing: false, |
| 110 | + closingPortalHostBlacklist: [], |
| 111 | + id: undefined, |
| 112 | + }); |
| 113 | + }); |
| 114 | + |
| 115 | + jest.clearAllMocks(); |
| 116 | + jest.clearAllTimers(); |
| 117 | + jest.useRealTimers(); |
69 | 118 | }); |
70 | 119 |
|
71 | | - it('keeps content local when the teleport hook returns false', () => { |
72 | | - render( |
73 | | - <PortalWhileClosingView portalHostName='overlay-composer' portalName='local-portal'> |
74 | | - <Text>Local</Text> |
75 | | - </PortalWhileClosingView>, |
| 120 | + it('uses the real store to teleport once the overlay is closing and this host is active', () => { |
| 121 | + const { toJSON } = render( |
| 122 | + <> |
| 123 | + <PortalWhileClosingView portalHostName='overlay-composer' portalName='composer-portal'> |
| 124 | + <Text>Composer</Text> |
| 125 | + </PortalWhileClosingView> |
| 126 | + <TeleportStateProbe |
| 127 | + hostName='overlay-composer' |
| 128 | + id='registration-1' |
| 129 | + testID='teleport-state' |
| 130 | + /> |
| 131 | + </>, |
76 | 132 | ); |
77 | 133 |
|
78 | | - expect(screen.getByTestId('portal-local-portal-host')).toHaveTextContent('none'); |
79 | | - }); |
| 134 | + act(() => { |
| 135 | + stateStore.setClosingPortalLayout('overlay-composer', 'registration-1', BASE_RECT); |
| 136 | + }); |
80 | 137 |
|
81 | | - it('teleports content to the closing host when the teleport hook returns true', () => { |
82 | | - mockedUseShouldTeleportToClosingPortal.mockReturnValue(true); |
| 138 | + expect(screen.getByTestId('teleport-state')).toHaveTextContent('false'); |
| 139 | + expect(findPortalNode(toJSON(), 'composer-portal')?.props?.hostName).toBeUndefined(); |
83 | 140 |
|
84 | | - render( |
85 | | - <PortalWhileClosingView portalHostName='overlay-composer' portalName='teleported-portal'> |
86 | | - <Text>Teleported</Text> |
87 | | - </PortalWhileClosingView>, |
88 | | - ); |
| 141 | + act(() => { |
| 142 | + stateStore.openOverlay('message-1'); |
| 143 | + stateStore.closeOverlay(); |
| 144 | + }); |
| 145 | + flushAnimationFrameQueue(); |
| 146 | + |
| 147 | + expect(screen.getByTestId('teleport-state')).toHaveTextContent('true'); |
| 148 | + expect(findPortalNode(toJSON(), 'composer-portal')?.props?.hostName).toBe('overlay-composer'); |
| 149 | + }); |
89 | 150 |
|
90 | | - expect(screen.getByTestId('portal-teleported-portal-host')).toHaveTextContent( |
91 | | - 'overlay-composer', |
| 151 | + it('keeps the portal local when the host is blacklisted', () => { |
| 152 | + const { toJSON } = render( |
| 153 | + <> |
| 154 | + <BlacklistRegistrar hostNames={['overlay-composer']} /> |
| 155 | + <PortalWhileClosingView portalHostName='overlay-composer' portalName='composer-portal'> |
| 156 | + <Text>Composer</Text> |
| 157 | + </PortalWhileClosingView> |
| 158 | + <TeleportStateProbe |
| 159 | + hostName='overlay-composer' |
| 160 | + id='registration-1' |
| 161 | + testID='teleport-state' |
| 162 | + /> |
| 163 | + </>, |
92 | 164 | ); |
| 165 | + |
| 166 | + act(() => { |
| 167 | + stateStore.setClosingPortalLayout('overlay-composer', 'registration-1', BASE_RECT); |
| 168 | + stateStore.openOverlay('message-1'); |
| 169 | + stateStore.closeOverlay(); |
| 170 | + }); |
| 171 | + flushAnimationFrameQueue(); |
| 172 | + |
| 173 | + expect(screen.getByTestId('teleport-state')).toHaveTextContent('false'); |
| 174 | + expect(findPortalNode(toJSON(), 'composer-portal')?.props?.hostName).toBeUndefined(); |
93 | 175 | }); |
94 | 176 |
|
95 | | - it('clears its registered layout entry when it unmounts', () => { |
96 | | - const { unmount } = render( |
97 | | - <PortalWhileClosingView portalHostName='overlay-composer' portalName='cleanup-portal'> |
98 | | - <Text>Cleanup</Text> |
99 | | - </PortalWhileClosingView>, |
| 177 | + it('clears its registration from the real store when it unmounts', () => { |
| 178 | + const { rerender } = render( |
| 179 | + <> |
| 180 | + <PortalWhileClosingView portalHostName='overlay-composer' portalName='composer-portal'> |
| 181 | + <Text>Composer</Text> |
| 182 | + </PortalWhileClosingView> |
| 183 | + <TeleportStateProbe |
| 184 | + hostName='overlay-composer' |
| 185 | + id='registration-1' |
| 186 | + testID='teleport-state' |
| 187 | + /> |
| 188 | + </>, |
100 | 189 | ); |
101 | 190 |
|
102 | 191 | act(() => { |
103 | | - unmount(); |
| 192 | + stateStore.setClosingPortalLayout('overlay-composer', 'registration-1', BASE_RECT); |
| 193 | + stateStore.openOverlay('message-1'); |
| 194 | + stateStore.closeOverlay(); |
104 | 195 | }); |
| 196 | + flushAnimationFrameQueue(); |
| 197 | + |
| 198 | + expect(screen.getByTestId('teleport-state')).toHaveTextContent('true'); |
105 | 199 |
|
106 | | - expect(mockedClearClosingPortalLayout).toHaveBeenCalledWith( |
107 | | - 'overlay-composer', |
108 | | - 'registration-1', |
| 200 | + rerender( |
| 201 | + <TeleportStateProbe |
| 202 | + hostName='overlay-composer' |
| 203 | + id='registration-1' |
| 204 | + testID='teleport-state' |
| 205 | + />, |
109 | 206 | ); |
| 207 | + |
| 208 | + expect(screen.getByTestId('teleport-state')).toHaveTextContent('false'); |
110 | 209 | }); |
111 | 210 | }); |
0 commit comments