Skip to content

Commit 8df3131

Browse files
committed
test: add unit tests for IterableEmbeddedNotification component
1 parent f0cdd24 commit 8df3131

1 file changed

Lines changed: 347 additions & 0 deletions

File tree

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { fireEvent, render } from '@testing-library/react-native';
3+
4+
import { IterableEmbeddedViewType } from '../../enums/IterableEmbeddedViewType';
5+
import { useEmbeddedView } from '../../hooks/useEmbeddedView';
6+
import type { IterableEmbeddedMessage } from '../../types/IterableEmbeddedMessage';
7+
import type { IterableEmbeddedMessageElementsButton } from '../../types/IterableEmbeddedMessageElementsButton';
8+
import { IterableEmbeddedNotification } from './IterableEmbeddedNotification';
9+
10+
const mockHandleButtonClick = jest.fn();
11+
const mockHandleMessageClick = jest.fn();
12+
13+
jest.mock('../../hooks/useEmbeddedView', () => ({
14+
useEmbeddedView: jest.fn(),
15+
}));
16+
17+
const mockUseEmbeddedView = useEmbeddedView as jest.MockedFunction<
18+
typeof useEmbeddedView
19+
>;
20+
21+
const defaultParsedStyles = {
22+
backgroundColor: '#ffffff',
23+
borderColor: '#E0DEDF',
24+
borderCornerRadius: 8,
25+
borderWidth: 1,
26+
primaryBtnBackgroundColor: '#6A266D',
27+
primaryBtnTextColor: '#ffffff',
28+
secondaryBtnBackgroundColor: 'transparent',
29+
secondaryBtnTextColor: '#79347F',
30+
titleTextColor: '#3D3A3B',
31+
bodyTextColor: '#787174',
32+
};
33+
34+
function mockUseEmbeddedViewReturn(overrides: Partial<ReturnType<typeof useEmbeddedView>> = {}) {
35+
mockUseEmbeddedView.mockReturnValue({
36+
parsedStyles: defaultParsedStyles,
37+
handleButtonClick: mockHandleButtonClick,
38+
handleMessageClick: mockHandleMessageClick,
39+
media: { url: null, caption: null, shouldShow: false },
40+
...overrides,
41+
});
42+
}
43+
44+
describe('IterableEmbeddedNotification', () => {
45+
const baseMessage: IterableEmbeddedMessage = {
46+
metadata: {
47+
messageId: 'msg-1',
48+
campaignId: 1,
49+
placementId: 1,
50+
},
51+
elements: {
52+
title: 'Notification Title',
53+
body: 'Notification body text.',
54+
},
55+
};
56+
57+
beforeEach(() => {
58+
jest.clearAllMocks();
59+
mockUseEmbeddedViewReturn();
60+
});
61+
62+
describe('Rendering', () => {
63+
it('should render without crashing', () => {
64+
const { getByText } = render(
65+
<IterableEmbeddedNotification message={baseMessage} />
66+
);
67+
expect(getByText('Notification Title')).toBeTruthy();
68+
expect(getByText('Notification body text.')).toBeTruthy();
69+
});
70+
71+
it('should render title and body from message.elements', () => {
72+
const message: IterableEmbeddedMessage = {
73+
...baseMessage,
74+
elements: {
75+
title: 'Custom Title',
76+
body: 'Custom body content.',
77+
},
78+
};
79+
const { getByText } = render(
80+
<IterableEmbeddedNotification message={message} />
81+
);
82+
expect(getByText('Custom Title')).toBeTruthy();
83+
expect(getByText('Custom body content.')).toBeTruthy();
84+
});
85+
86+
it('should apply parsedStyles to container and text', () => {
87+
const customStyles = {
88+
...defaultParsedStyles,
89+
backgroundColor: '#000000',
90+
titleTextColor: '#ff0000',
91+
bodyTextColor: '#00ff00',
92+
};
93+
mockUseEmbeddedViewReturn({ parsedStyles: customStyles });
94+
95+
const { getByText, UNSAFE_getAllByType } = render(
96+
<IterableEmbeddedNotification message={baseMessage} />
97+
);
98+
99+
const views = UNSAFE_getAllByType('View' as any);
100+
const styleArray = (s: any) => (Array.isArray(s) ? s : [s]);
101+
const container = views.find(
102+
(v: any) =>
103+
v.props.style &&
104+
styleArray(v.props.style).some(
105+
(sty: any) => sty && sty.backgroundColor === '#000000'
106+
)
107+
);
108+
expect(container).toBeTruthy();
109+
expect(styleArray(container!.props.style)).toEqual(
110+
expect.arrayContaining([
111+
expect.any(Object),
112+
expect.objectContaining({
113+
backgroundColor: '#000000',
114+
borderColor: customStyles.borderColor,
115+
borderRadius: customStyles.borderCornerRadius,
116+
borderWidth: customStyles.borderWidth,
117+
}),
118+
])
119+
);
120+
121+
const title = getByText('Notification Title');
122+
const body = getByText('Notification body text.');
123+
expect(title.props.style).toEqual(
124+
expect.arrayContaining([
125+
expect.any(Object),
126+
expect.objectContaining({ color: '#ff0000' }),
127+
])
128+
);
129+
expect(body.props.style).toEqual(
130+
expect.arrayContaining([
131+
expect.any(Object),
132+
expect.objectContaining({ color: '#00ff00' }),
133+
])
134+
);
135+
});
136+
137+
it('should not render button container when message has no buttons', () => {
138+
const message: IterableEmbeddedMessage = {
139+
...baseMessage,
140+
elements: { ...baseMessage.elements, buttons: undefined },
141+
};
142+
const { queryByText } = render(
143+
<IterableEmbeddedNotification message={message} />
144+
);
145+
expect(queryByText('CTA')).toBeNull();
146+
});
147+
148+
it('should not render button container when buttons array is empty', () => {
149+
const message: IterableEmbeddedMessage = {
150+
...baseMessage,
151+
elements: { ...baseMessage.elements, buttons: [] },
152+
};
153+
const { queryByText } = render(
154+
<IterableEmbeddedNotification message={message} />
155+
);
156+
expect(queryByText('Primary')).toBeNull();
157+
});
158+
});
159+
160+
describe('Buttons', () => {
161+
const primaryButton: IterableEmbeddedMessageElementsButton = {
162+
id: 'btn-primary',
163+
title: 'Primary',
164+
action: { type: 'openUrl', data: 'https://example.com' },
165+
};
166+
const secondaryButton: IterableEmbeddedMessageElementsButton = {
167+
id: 'btn-secondary',
168+
title: 'Secondary',
169+
};
170+
171+
it('should render buttons when message has buttons', () => {
172+
const message: IterableEmbeddedMessage = {
173+
...baseMessage,
174+
elements: {
175+
...baseMessage.elements,
176+
buttons: [primaryButton, secondaryButton],
177+
},
178+
};
179+
const { getByText } = render(
180+
<IterableEmbeddedNotification message={message} />
181+
);
182+
expect(getByText('Primary')).toBeTruthy();
183+
expect(getByText('Secondary')).toBeTruthy();
184+
});
185+
186+
it('should apply primary and secondary button text colors from parsedStyles', () => {
187+
const message: IterableEmbeddedMessage = {
188+
...baseMessage,
189+
elements: {
190+
...baseMessage.elements,
191+
buttons: [primaryButton, secondaryButton],
192+
},
193+
};
194+
const { getByText } = render(
195+
<IterableEmbeddedNotification message={message} />
196+
);
197+
198+
const primaryText = getByText('Primary');
199+
const secondaryText = getByText('Secondary');
200+
expect(primaryText.props.style).toEqual(
201+
expect.arrayContaining([
202+
expect.any(Object),
203+
expect.objectContaining({
204+
color: defaultParsedStyles.primaryBtnTextColor,
205+
}),
206+
])
207+
);
208+
expect(secondaryText.props.style).toEqual(
209+
expect.arrayContaining([
210+
expect.any(Object),
211+
expect.objectContaining({
212+
color: defaultParsedStyles.secondaryBtnTextColor,
213+
}),
214+
])
215+
);
216+
});
217+
218+
it('should call handleButtonClick with correct button when button is pressed', () => {
219+
const message: IterableEmbeddedMessage = {
220+
...baseMessage,
221+
elements: {
222+
...baseMessage.elements,
223+
buttons: [primaryButton, secondaryButton],
224+
},
225+
};
226+
const { getByText } = render(
227+
<IterableEmbeddedNotification message={message} />
228+
);
229+
230+
fireEvent.press(getByText('Primary'));
231+
expect(mockHandleButtonClick).toHaveBeenCalledTimes(1);
232+
expect(mockHandleButtonClick).toHaveBeenCalledWith(primaryButton);
233+
234+
fireEvent.press(getByText('Secondary'));
235+
expect(mockHandleButtonClick).toHaveBeenCalledTimes(2);
236+
expect(mockHandleButtonClick).toHaveBeenLastCalledWith(secondaryButton);
237+
});
238+
});
239+
240+
describe('Message click', () => {
241+
it('should call handleMessageClick when message area is pressed', () => {
242+
const { getByText } = render(
243+
<IterableEmbeddedNotification message={baseMessage} />
244+
);
245+
246+
fireEvent.press(getByText('Notification Title'));
247+
expect(mockHandleMessageClick).toHaveBeenCalledTimes(1);
248+
249+
mockHandleMessageClick.mockClear();
250+
fireEvent.press(getByText('Notification body text.'));
251+
expect(mockHandleMessageClick).toHaveBeenCalledTimes(1);
252+
});
253+
});
254+
255+
describe('useEmbeddedView integration', () => {
256+
it('should call useEmbeddedView with Notification viewType and props', () => {
257+
const config = { backgroundColor: '#abc' } as any;
258+
const onButtonClick = jest.fn();
259+
const onMessageClick = jest.fn();
260+
261+
render(
262+
<IterableEmbeddedNotification
263+
message={baseMessage}
264+
config={config}
265+
onButtonClick={onButtonClick}
266+
onMessageClick={onMessageClick}
267+
/>
268+
);
269+
270+
expect(mockUseEmbeddedView).toHaveBeenCalledTimes(1);
271+
expect(mockUseEmbeddedView).toHaveBeenCalledWith(
272+
IterableEmbeddedViewType.Notification,
273+
{
274+
message: baseMessage,
275+
config,
276+
onButtonClick,
277+
onMessageClick,
278+
}
279+
);
280+
});
281+
282+
it('should call useEmbeddedView with default callbacks when not provided', () => {
283+
render(<IterableEmbeddedNotification message={baseMessage} />);
284+
285+
expect(mockUseEmbeddedView).toHaveBeenCalledWith(
286+
IterableEmbeddedViewType.Notification,
287+
expect.objectContaining({
288+
message: baseMessage,
289+
onButtonClick: expect.any(Function),
290+
onMessageClick: expect.any(Function),
291+
})
292+
);
293+
});
294+
});
295+
296+
describe('Edge cases', () => {
297+
it('should handle message with missing elements', () => {
298+
const message: IterableEmbeddedMessage = {
299+
metadata: baseMessage.metadata,
300+
elements: undefined,
301+
};
302+
const { queryByText } = render(
303+
<IterableEmbeddedNotification message={message} />
304+
);
305+
expect(queryByText('Notification Title')).toBeNull();
306+
expect(queryByText('Notification body text.')).toBeNull();
307+
});
308+
309+
it('should handle message with empty title and body without throwing', () => {
310+
const message: IterableEmbeddedMessage = {
311+
...baseMessage,
312+
elements: { title: '', body: '' },
313+
};
314+
const { getAllByText } = render(
315+
<IterableEmbeddedNotification message={message} />
316+
);
317+
const emptyTextNodes = getAllByText('');
318+
expect(emptyTextNodes.length).toBeGreaterThanOrEqual(1);
319+
});
320+
321+
it('should render multiple buttons and call handleButtonClick with correct button for each', () => {
322+
const message: IterableEmbeddedMessage = {
323+
...baseMessage,
324+
elements: {
325+
...baseMessage.elements,
326+
buttons: [
327+
{ id: 'unique-id-1', title: 'First' },
328+
{ id: 'unique-id-2', title: 'Second' },
329+
],
330+
},
331+
};
332+
const { getByText } = render(
333+
<IterableEmbeddedNotification message={message} />
334+
);
335+
expect(getByText('First')).toBeTruthy();
336+
expect(getByText('Second')).toBeTruthy();
337+
fireEvent.press(getByText('First'));
338+
expect(mockHandleButtonClick).toHaveBeenCalledWith(
339+
expect.objectContaining({ id: 'unique-id-1', title: 'First' })
340+
);
341+
fireEvent.press(getByText('Second'));
342+
expect(mockHandleButtonClick).toHaveBeenLastCalledWith(
343+
expect.objectContaining({ id: 'unique-id-2', title: 'Second' })
344+
);
345+
});
346+
});
347+
});

0 commit comments

Comments
 (0)