Skip to content

Commit 96ad2c6

Browse files
committed
test: add unit tests for IterableEmbeddedCard component
1 parent 2e983a8 commit 96ad2c6

1 file changed

Lines changed: 371 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)