Skip to content

Commit a41952d

Browse files
authored
Merge pull request #818 from Iterable/loren/embedded/SDK-367-parse-media-for-components
[SDK-366] Parse media
2 parents 6cd11bb + 82f9b15 commit a41952d

4 files changed

Lines changed: 190 additions & 33 deletions

File tree

src/embedded/components/IterableEmbeddedView.tsx

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useMemo } from 'react';
2-
import { View, Text } from 'react-native';
2+
import { View, Text, Image } from 'react-native';
33

44
import { IterableEmbeddedViewType } from '../enums/IterableEmbeddedViewType';
55

@@ -45,40 +45,14 @@ export const IterableEmbeddedView = ({
4545
}
4646
}, [viewType]);
4747

48-
const { parsedStyles } = useEmbeddedView(viewType, props);
48+
const { media } = useEmbeddedView(viewType, props);
4949

5050
return Cmp ? (
5151
<View>
52-
<Text>
53-
parsedStyles.backgroundColor: {String(parsedStyles.backgroundColor)}
54-
</Text>
55-
<Text>parsedStyles.borderColor: {String(parsedStyles.borderColor)}</Text>
56-
<Text>parsedStyles.borderWidth: {parsedStyles.borderWidth}</Text>
57-
<Text>
58-
parsedStyles.borderCornerRadius: {parsedStyles.borderCornerRadius}
59-
</Text>
60-
<Text>
61-
parsedStyles.primaryBtnBackgroundColor:{' '}
62-
{String(parsedStyles.primaryBtnBackgroundColor)}
63-
</Text>
64-
<Text>
65-
parsedStyles.primaryBtnTextColor:{' '}
66-
{String(parsedStyles.primaryBtnTextColor)}
67-
</Text>
68-
<Text>
69-
parsedStyles.secondaryBtnBackgroundColor:{' '}
70-
{String(parsedStyles.secondaryBtnBackgroundColor)}
71-
</Text>
72-
<Text>
73-
parsedStyles.secondaryBtnTextColor:{' '}
74-
{String(parsedStyles.secondaryBtnTextColor)}
75-
</Text>
76-
<Text>
77-
parsedStyles.titleTextColor: {String(parsedStyles.titleTextColor)}
78-
</Text>
79-
<Text>
80-
parsedStyles.bodyTextColor: {String(parsedStyles.bodyTextColor)}
81-
</Text>
52+
<Text>media.url: {media.url}</Text>
53+
<Text>media.caption: {media.caption}</Text>
54+
<Text>media.shouldShow: {media.shouldShow ? 'true' : 'false'}</Text>
55+
{media.url ? <Image source={{ uri: media.url }} width={100} height={100} /> : null}
8256
<Cmp {...props} />
8357
</View>
8458
) : null;
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { getMedia } from './getMedia';
2+
import { IterableEmbeddedViewType } from '../../enums';
3+
import type { IterableEmbeddedMessage } from '../../types/IterableEmbeddedMessage';
4+
5+
const minimalMessage: IterableEmbeddedMessage = {
6+
metadata: { messageId: 'msg-1', placementId: 1 },
7+
};
8+
9+
describe('getMedia', () => {
10+
describe('viewType Notification', () => {
11+
it('returns no media regardless of message content', () => {
12+
const result = getMedia(IterableEmbeddedViewType.Notification, minimalMessage);
13+
14+
expect(result).toEqual({ url: null, caption: null, shouldShow: false });
15+
});
16+
17+
it('returns no media even when message has mediaUrl and caption', () => {
18+
const message: IterableEmbeddedMessage = {
19+
...minimalMessage,
20+
elements: {
21+
mediaUrl: 'https://example.com/image.png',
22+
mediaUrlCaption: 'Example caption',
23+
},
24+
};
25+
26+
const result = getMedia(IterableEmbeddedViewType.Notification, message);
27+
28+
expect(result).toEqual({ url: null, caption: null, shouldShow: false });
29+
});
30+
});
31+
32+
describe('viewType Card', () => {
33+
it('returns url and caption from message.elements, shouldShow true when url is non-empty', () => {
34+
const message: IterableEmbeddedMessage = {
35+
...minimalMessage,
36+
elements: {
37+
mediaUrl: 'https://example.com/photo.jpg',
38+
mediaUrlCaption: 'A nice photo',
39+
},
40+
};
41+
42+
const result = getMedia(IterableEmbeddedViewType.Card, message);
43+
44+
expect(result).toEqual({
45+
url: 'https://example.com/photo.jpg',
46+
caption: 'A nice photo',
47+
shouldShow: true,
48+
});
49+
});
50+
51+
it('returns url only (caption null) when message has no mediaUrlCaption', () => {
52+
const message: IterableEmbeddedMessage = {
53+
...minimalMessage,
54+
elements: { mediaUrl: 'https://example.com/img.png' },
55+
};
56+
57+
const result = getMedia(IterableEmbeddedViewType.Card, message);
58+
59+
expect(result).toEqual({
60+
url: 'https://example.com/img.png',
61+
caption: null,
62+
shouldShow: true,
63+
});
64+
});
65+
66+
it('returns shouldShow false when mediaUrl is empty string', () => {
67+
const message: IterableEmbeddedMessage = {
68+
...minimalMessage,
69+
elements: { mediaUrl: '', mediaUrlCaption: 'Caption' },
70+
};
71+
72+
const result = getMedia(IterableEmbeddedViewType.Card, message);
73+
74+
expect(result.url).toBe('');
75+
expect(result.caption).toBe('Caption');
76+
expect(result.shouldShow).toBe(false);
77+
});
78+
79+
it('returns null url/caption and shouldShow false when message has no elements', () => {
80+
const result = getMedia(IterableEmbeddedViewType.Card, minimalMessage);
81+
82+
expect(result).toEqual({ url: null, caption: null, shouldShow: false });
83+
});
84+
85+
it('returns null url/caption when elements exist but mediaUrl is undefined', () => {
86+
const message: IterableEmbeddedMessage = {
87+
...minimalMessage,
88+
elements: { title: 'Title', body: 'Body' },
89+
};
90+
91+
const result = getMedia(IterableEmbeddedViewType.Card, message);
92+
93+
expect(result).toEqual({ url: null, caption: null, shouldShow: false });
94+
});
95+
});
96+
97+
describe('viewType Banner', () => {
98+
it('returns url and caption from message.elements, shouldShow true when url is non-empty', () => {
99+
const message: IterableEmbeddedMessage = {
100+
...minimalMessage,
101+
elements: {
102+
mediaUrl: 'https://example.com/banner.png',
103+
mediaUrlCaption: 'Banner caption',
104+
},
105+
};
106+
107+
const result = getMedia(IterableEmbeddedViewType.Banner, message);
108+
109+
expect(result).toEqual({
110+
url: 'https://example.com/banner.png',
111+
caption: 'Banner caption',
112+
shouldShow: true,
113+
});
114+
});
115+
116+
it('returns null url/caption and shouldShow false when message has no elements', () => {
117+
const result = getMedia(IterableEmbeddedViewType.Banner, minimalMessage);
118+
119+
expect(result).toEqual({ url: null, caption: null, shouldShow: false });
120+
});
121+
});
122+
123+
describe('return shape', () => {
124+
it('returns an object with url, caption, and shouldShow', () => {
125+
const result = getMedia(IterableEmbeddedViewType.Card, minimalMessage);
126+
127+
expect(Object.keys(result)).toHaveLength(3);
128+
expect(result).toHaveProperty('url');
129+
expect(result).toHaveProperty('caption');
130+
expect(result).toHaveProperty('shouldShow');
131+
expect(typeof result.shouldShow).toBe('boolean');
132+
expect(result.url === null || typeof result.url === 'string').toBe(true);
133+
expect(result.caption === null || typeof result.caption === 'string').toBe(true);
134+
});
135+
});
136+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { IterableEmbeddedMessage } from '../../types/IterableEmbeddedMessage';
2+
import { IterableEmbeddedViewType } from '../../enums';
3+
4+
/**
5+
* This function is used to get the media to render for a given embedded view
6+
* type and message.
7+
*
8+
* @param viewType - The type of view to render.
9+
* @param message - The message to render.
10+
* @returns The media to render.
11+
*
12+
* @example
13+
* const media = getMedia(IterableEmbeddedViewType.Notification, message);
14+
* console.log(media.url);
15+
* console.log(media.caption);
16+
* console.log(media.shouldShow ? 'true' : 'false');
17+
*/
18+
export const getMedia = (
19+
/** The type of view to render. */
20+
viewType: IterableEmbeddedViewType,
21+
/** The message to render. */
22+
message: IterableEmbeddedMessage
23+
): {
24+
/** The URL of the media to render. */
25+
url: string | null;
26+
/** The caption of the media to render. */
27+
caption: string | null;
28+
/** Whether the media should be shown. */
29+
shouldShow: boolean;
30+
} => {
31+
if (viewType === IterableEmbeddedViewType.Notification) {
32+
return { url: null, caption: null, shouldShow: false };
33+
}
34+
const url = message.elements?.mediaUrl ?? null;
35+
const caption = message.elements?.mediaUrlCaption ?? null;
36+
const shouldShow = !!url && url.length > 0;
37+
return { url, caption, shouldShow };
38+
};

src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useMemo } from 'react';
2+
23
import { IterableEmbeddedViewType } from '../../enums';
34
import type { IterableEmbeddedComponentProps } from '../../types/IterableEmbeddedComponentProps';
5+
import { getMedia } from './getMedia';
46
import { getStyles } from './getStyles';
57

68
/**
@@ -11,7 +13,7 @@ import { getStyles } from './getStyles';
1113
* @returns The embedded view.
1214
*
1315
* @example
14-
* const \{ parsedStyles \} = useEmbeddedView(IterableEmbeddedViewType.Notification, \{
16+
* const \{ media, parsedStyles \} = useEmbeddedView(IterableEmbeddedViewType.Notification, \{
1517
* message,
1618
* config,
1719
* onButtonClick,
@@ -20,6 +22,8 @@ import { getStyles } from './getStyles';
2022
*
2123
* return (
2224
* <View>
25+
* <Text>\{media.url\}</Text>
26+
* <Text>\{media.caption\}</Text>
2327
* <Text>\{parsedStyles.backgroundColor\}</Text>
2428
* </View>
2529
* );
@@ -30,13 +34,18 @@ export const useEmbeddedView = (
3034
/** The props for the embedded view. */
3135
{
3236
config,
37+
message,
3338
}: IterableEmbeddedComponentProps
3439
) => {
3540
const parsedStyles = useMemo(() => {
3641
return getStyles(viewType, config);
3742
}, [viewType, config]);
43+
const media = useMemo(() => {
44+
return getMedia(viewType, message);
45+
}, [viewType, message]);
3846

3947
return {
4048
parsedStyles,
49+
media,
4150
};
4251
};

0 commit comments

Comments
 (0)