Skip to content

Commit fc3c7cb

Browse files
committed
chore: first pass at tests
1 parent 7c506c1 commit fc3c7cb

File tree

3 files changed

+474
-0
lines changed

3 files changed

+474
-0
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
jest.mock('../../../state-store', () => {
34+
const mockedMessageOverlayStore = jest.requireMock('../../../state-store/message-overlay-store');
35+
36+
return {
37+
...mockedMessageOverlayStore,
38+
};
39+
});
40+
41+
import React from 'react';
42+
import { Text } from 'react-native';
43+
44+
import { act, cleanup, render, screen } from '@testing-library/react-native';
45+
46+
import * as stateStore from '../../../state-store/message-overlay-store';
47+
import { PortalWhileClosingView } from '../PortalWhileClosingView';
48+
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;
54+
55+
describe('PortalWhileClosingView', () => {
56+
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');
65+
});
66+
67+
afterEach(() => {
68+
cleanup();
69+
});
70+
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>,
76+
);
77+
78+
expect(screen.getByTestId('portal-local-portal-host')).toHaveTextContent('none');
79+
});
80+
81+
it('teleports content to the closing host when the teleport hook returns true', () => {
82+
mockedUseShouldTeleportToClosingPortal.mockReturnValue(true);
83+
84+
render(
85+
<PortalWhileClosingView portalHostName='overlay-composer' portalName='teleported-portal'>
86+
<Text>Teleported</Text>
87+
</PortalWhileClosingView>,
88+
);
89+
90+
expect(screen.getByTestId('portal-teleported-portal-host')).toHaveTextContent(
91+
'overlay-composer',
92+
);
93+
});
94+
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>,
100+
);
101+
102+
act(() => {
103+
unmount();
104+
});
105+
106+
expect(mockedClearClosingPortalLayout).toHaveBeenCalledWith(
107+
'overlay-composer',
108+
'registration-1',
109+
);
110+
});
111+
});
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
jest.mock('react-native-reanimated', () => {
2+
const actual = jest.requireActual('react-native-reanimated/mock');
3+
const { View } = require('react-native');
4+
5+
return {
6+
...actual,
7+
Animated: {
8+
...actual.default,
9+
View,
10+
},
11+
default: {
12+
...actual.default,
13+
View,
14+
},
15+
makeMutable: (value: unknown) => ({ value }),
16+
useAnimatedStyle: (updater: () => unknown) => updater(),
17+
};
18+
});
19+
20+
jest.mock('react-native-teleport', () => {
21+
const React = require('react');
22+
const { Text, View } = require('react-native');
23+
24+
return {
25+
Portal: View,
26+
PortalHost: ({ name }: { name: string }) => (
27+
<View testID={`portal-host-${name}`}>
28+
<Text>{name}</Text>
29+
</View>
30+
),
31+
PortalProvider: View,
32+
usePortal: jest.fn().mockReturnValue({ removePortal: jest.fn() }),
33+
};
34+
});
35+
36+
import React from 'react';
37+
import type { SharedValue } from 'react-native-reanimated';
38+
39+
import { act, cleanup, render, screen } from '@testing-library/react-native';
40+
41+
import { clearClosingPortalLayout, setClosingPortalLayout } from '../../../state-store';
42+
import { ClosingPortalHostsLayer } from '../ClosingPortalHostsLayer';
43+
44+
const FIRST_RECT = { h: 40, w: 100, x: 10, y: 20 };
45+
const SECOND_RECT = { h: 52, w: 140, x: 30, y: 45 };
46+
47+
describe('ClosingPortalHostsLayer', () => {
48+
beforeEach(() => {
49+
cleanup();
50+
});
51+
52+
afterEach(() => {
53+
act(() => {
54+
clearClosingPortalLayout('overlay-header', 'first-entry');
55+
clearClosingPortalLayout('overlay-header', 'second-entry');
56+
clearClosingPortalLayout('overlay-composer', 'composer-entry');
57+
});
58+
cleanup();
59+
});
60+
61+
it('renders the geometry for the top-most registration of a host and falls back when it is removed', () => {
62+
const { toJSON } = render(
63+
<ClosingPortalHostsLayer closeCoverOpacity={{ value: 0.5 } as SharedValue<number>} />,
64+
);
65+
66+
act(() => {
67+
setClosingPortalLayout('overlay-header', 'first-entry', FIRST_RECT);
68+
});
69+
70+
let tree = toJSON();
71+
let slot = Array.isArray(tree) ? tree[0] : tree;
72+
73+
expect(slot).toMatchObject({
74+
children: [
75+
expect.objectContaining({
76+
props: expect.objectContaining({ testID: 'portal-host-overlay-header' }),
77+
}),
78+
],
79+
props: expect.objectContaining({
80+
pointerEvents: 'box-none',
81+
style: expect.objectContaining({
82+
height: FIRST_RECT.h,
83+
left: FIRST_RECT.x,
84+
opacity: 0.5,
85+
position: 'absolute',
86+
top: FIRST_RECT.y,
87+
width: FIRST_RECT.w,
88+
}),
89+
}),
90+
});
91+
92+
act(() => {
93+
setClosingPortalLayout('overlay-header', 'second-entry', SECOND_RECT);
94+
});
95+
96+
tree = toJSON();
97+
slot = Array.isArray(tree) ? tree[0] : tree;
98+
99+
expect(slot).toMatchObject({
100+
children: [
101+
expect.objectContaining({
102+
props: expect.objectContaining({ testID: 'portal-host-overlay-header' }),
103+
}),
104+
],
105+
props: expect.objectContaining({
106+
pointerEvents: 'box-none',
107+
style: expect.objectContaining({
108+
height: SECOND_RECT.h,
109+
left: SECOND_RECT.x,
110+
opacity: 0.5,
111+
position: 'absolute',
112+
top: SECOND_RECT.y,
113+
width: SECOND_RECT.w,
114+
}),
115+
}),
116+
});
117+
118+
act(() => {
119+
clearClosingPortalLayout('overlay-header', 'second-entry');
120+
});
121+
122+
tree = toJSON();
123+
slot = Array.isArray(tree) ? tree[0] : tree;
124+
125+
expect(slot).toMatchObject({
126+
children: [
127+
expect.objectContaining({
128+
props: expect.objectContaining({ testID: 'portal-host-overlay-header' }),
129+
}),
130+
],
131+
props: expect.objectContaining({
132+
pointerEvents: 'box-none',
133+
style: expect.objectContaining({
134+
height: FIRST_RECT.h,
135+
left: FIRST_RECT.x,
136+
opacity: 0.5,
137+
position: 'absolute',
138+
top: FIRST_RECT.y,
139+
width: FIRST_RECT.w,
140+
}),
141+
}),
142+
});
143+
144+
act(() => {
145+
clearClosingPortalLayout('overlay-header', 'first-entry');
146+
});
147+
148+
expect(toJSON()).toBeNull();
149+
});
150+
151+
it('renders one closing host slot per host name even when multiple entries are registered', () => {
152+
render(<ClosingPortalHostsLayer closeCoverOpacity={{ value: 1 } as SharedValue<number>} />);
153+
154+
act(() => {
155+
setClosingPortalLayout('overlay-header', 'first-entry', FIRST_RECT);
156+
setClosingPortalLayout('overlay-header', 'second-entry', SECOND_RECT);
157+
setClosingPortalLayout('overlay-composer', 'composer-entry', {
158+
h: 60,
159+
w: 160,
160+
x: 5,
161+
y: 200,
162+
});
163+
});
164+
165+
expect(screen.getAllByTestId('portal-host-overlay-header')).toHaveLength(1);
166+
expect(screen.getAllByTestId('portal-host-overlay-composer')).toHaveLength(1);
167+
});
168+
});

0 commit comments

Comments
 (0)