Skip to content

Commit 7278e0c

Browse files
committed
test: add unit tests for useEmbeddedView hook functionality
1 parent 47a283d commit 7278e0c

1 file changed

Lines changed: 234 additions & 0 deletions

File tree

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import { renderHook, act } from '@testing-library/react-native';
2+
3+
import { Iterable } from '../../../core/classes/Iterable';
4+
import { IterableAction } from '../../../core/classes/IterableAction';
5+
import { IterableEmbeddedViewType } from '../../enums';
6+
import type { IterableEmbeddedMessage } from '../../types/IterableEmbeddedMessage';
7+
import type { IterableEmbeddedMessageElementsButton } from '../../types/IterableEmbeddedMessageElementsButton';
8+
import { useEmbeddedView } from './useEmbeddedView';
9+
import { getMedia } from './getMedia';
10+
import { getStyles } from './getStyles';
11+
12+
jest.mock('./getMedia');
13+
jest.mock('./getStyles');
14+
15+
const mockGetMedia = getMedia as jest.MockedFunction<typeof getMedia>;
16+
const mockGetStyles = getStyles as jest.MockedFunction<typeof getStyles>;
17+
18+
const minimalMessage: IterableEmbeddedMessage = {
19+
metadata: { messageId: 'msg-1', placementId: 1 },
20+
};
21+
22+
const defaultMedia = { url: null, caption: null, shouldShow: false };
23+
const defaultStyles = {
24+
backgroundColor: '#ffffff',
25+
borderColor: '#E0DEDF',
26+
borderWidth: 1,
27+
borderCornerRadius: 10,
28+
primaryBtnBackgroundColor: '#6A266D',
29+
primaryBtnTextColor: '#ffffff',
30+
secondaryBtnBackgroundColor: 'transparent',
31+
secondaryBtnTextColor: '#ffffff',
32+
titleTextColor: '#000000',
33+
bodyTextColor: '#000000',
34+
};
35+
36+
describe('useEmbeddedView', () => {
37+
beforeEach(() => {
38+
jest.clearAllMocks();
39+
mockGetMedia.mockReturnValue(defaultMedia);
40+
mockGetStyles.mockReturnValue(defaultStyles);
41+
jest.spyOn(Iterable.embeddedManager, 'handleClick').mockImplementation(() => {});
42+
});
43+
44+
afterEach(() => {
45+
jest.restoreAllMocks();
46+
});
47+
48+
describe('return shape', () => {
49+
it('returns handleButtonClick, handleMessageClick, media, and parsedStyles', () => {
50+
const { result } = renderHook(() =>
51+
useEmbeddedView(IterableEmbeddedViewType.Notification, { message: minimalMessage })
52+
);
53+
54+
expect(result.current).toHaveProperty('handleButtonClick');
55+
expect(result.current).toHaveProperty('handleMessageClick');
56+
expect(result.current).toHaveProperty('media');
57+
expect(result.current).toHaveProperty('parsedStyles');
58+
expect(typeof result.current.handleButtonClick).toBe('function');
59+
expect(typeof result.current.handleMessageClick).toBe('function');
60+
});
61+
});
62+
63+
describe('getMedia / getStyles delegation', () => {
64+
it('calls getMedia with viewType and message', () => {
65+
renderHook(() =>
66+
useEmbeddedView(IterableEmbeddedViewType.Card, { message: minimalMessage })
67+
);
68+
69+
expect(mockGetMedia).toHaveBeenCalledWith(IterableEmbeddedViewType.Card, minimalMessage);
70+
});
71+
72+
it('calls getStyles with viewType and config', () => {
73+
const config = { backgroundColor: '#000000' };
74+
renderHook(() =>
75+
useEmbeddedView(IterableEmbeddedViewType.Notification, {
76+
message: minimalMessage,
77+
config,
78+
})
79+
);
80+
81+
expect(mockGetStyles).toHaveBeenCalledWith(IterableEmbeddedViewType.Notification, config);
82+
});
83+
84+
it('returns media from getMedia and parsedStyles from getStyles', () => {
85+
const customMedia = { url: 'https://example.com/img.png', caption: 'Cap', shouldShow: true };
86+
const customStyles = { ...defaultStyles, backgroundColor: '#111111' };
87+
mockGetMedia.mockReturnValue(customMedia);
88+
mockGetStyles.mockReturnValue(customStyles);
89+
90+
const { result } = renderHook(() =>
91+
useEmbeddedView(IterableEmbeddedViewType.Banner, { message: minimalMessage })
92+
);
93+
94+
expect(result.current.media).toEqual(customMedia);
95+
expect(result.current.parsedStyles).toEqual(customStyles);
96+
});
97+
});
98+
99+
describe('handleButtonClick', () => {
100+
it('calls onButtonClick with the button', () => {
101+
const onButtonClick = jest.fn();
102+
const button: IterableEmbeddedMessageElementsButton = {
103+
id: 'btn-1',
104+
title: 'Click me',
105+
action: null,
106+
};
107+
108+
const { result } = renderHook(() =>
109+
useEmbeddedView(IterableEmbeddedViewType.Notification, {
110+
message: minimalMessage,
111+
onButtonClick,
112+
})
113+
);
114+
115+
act(() => {
116+
result.current.handleButtonClick(button);
117+
});
118+
119+
expect(onButtonClick).toHaveBeenCalledTimes(1);
120+
expect(onButtonClick).toHaveBeenCalledWith(button);
121+
});
122+
123+
it('calls Iterable.embeddedManager.handleClick with message, button.id, and button.action', () => {
124+
const action = new IterableAction('openUrl', 'https://example.com');
125+
const button: IterableEmbeddedMessageElementsButton = {
126+
id: 'btn-2',
127+
title: 'Link',
128+
action,
129+
};
130+
131+
const { result } = renderHook(() =>
132+
useEmbeddedView(IterableEmbeddedViewType.Notification, {
133+
message: minimalMessage,
134+
})
135+
);
136+
137+
act(() => {
138+
result.current.handleButtonClick(button);
139+
});
140+
141+
expect(Iterable.embeddedManager.handleClick).toHaveBeenCalledWith(
142+
minimalMessage,
143+
'btn-2',
144+
action
145+
);
146+
});
147+
});
148+
149+
describe('handleMessageClick', () => {
150+
it('calls onMessageClick', () => {
151+
const onMessageClick = jest.fn();
152+
153+
const { result } = renderHook(() =>
154+
useEmbeddedView(IterableEmbeddedViewType.Notification, {
155+
message: minimalMessage,
156+
onMessageClick,
157+
})
158+
);
159+
160+
act(() => {
161+
result.current.handleMessageClick();
162+
});
163+
164+
expect(onMessageClick).toHaveBeenCalledTimes(1);
165+
});
166+
167+
it('calls Iterable.embeddedManager.handleClick with message, null, and message.elements?.defaultAction', () => {
168+
const defaultAction = new IterableAction('openUrl', 'https://iterable.com');
169+
const message: IterableEmbeddedMessage = {
170+
...minimalMessage,
171+
elements: { defaultAction },
172+
};
173+
174+
const { result } = renderHook(() =>
175+
useEmbeddedView(IterableEmbeddedViewType.Notification, { message })
176+
);
177+
178+
act(() => {
179+
result.current.handleMessageClick();
180+
});
181+
182+
expect(Iterable.embeddedManager.handleClick).toHaveBeenCalledWith(
183+
message,
184+
null,
185+
defaultAction
186+
);
187+
});
188+
189+
it('calls embeddedManager.handleClick with undefined defaultAction when message has no elements', () => {
190+
const { result } = renderHook(() =>
191+
useEmbeddedView(IterableEmbeddedViewType.Notification, { message: minimalMessage })
192+
);
193+
194+
act(() => {
195+
result.current.handleMessageClick();
196+
});
197+
198+
expect(Iterable.embeddedManager.handleClick).toHaveBeenCalledWith(
199+
minimalMessage,
200+
null,
201+
undefined
202+
);
203+
});
204+
});
205+
206+
describe('default callbacks', () => {
207+
it('does not throw when handleButtonClick is invoked without provided onButtonClick', () => {
208+
const button: IterableEmbeddedMessageElementsButton = { id: 'b', title: null, action: null };
209+
const { result } = renderHook(() =>
210+
useEmbeddedView(IterableEmbeddedViewType.Notification, { message: minimalMessage })
211+
);
212+
213+
expect(() => {
214+
act(() => {
215+
result.current.handleButtonClick(button);
216+
});
217+
}).not.toThrow();
218+
});
219+
220+
it('does not throw when handleMessageClick is invoked without provided onMessageClick', () => {
221+
const { result } = renderHook(() =>
222+
useEmbeddedView(IterableEmbeddedViewType.Notification, { message: minimalMessage })
223+
);
224+
225+
expect(() => {
226+
act(() => {
227+
result.current.handleMessageClick();
228+
});
229+
}).not.toThrow();
230+
});
231+
});
232+
233+
// memoization behavior (useMemo) is indirectly exercised above via getMedia/getStyles calls
234+
});

0 commit comments

Comments
 (0)