From 2e1bc14160b548d0e9a3791a7528e999d2e39e08 Mon Sep 17 00:00:00 2001 From: Calise Cheung Date: Thu, 28 Aug 2025 03:20:44 -0700 Subject: [PATCH 1/2] Fix Content Card View outdated ContentTemplates Fix Content Card View outdated ContentTemplates --- .../app/ContentCardView.tsx | 1159 ++++++++++------- 1 file changed, 690 insertions(+), 469 deletions(-) diff --git a/apps/AEPSampleAppNewArchEnabled/app/ContentCardView.tsx b/apps/AEPSampleAppNewArchEnabled/app/ContentCardView.tsx index dd950270..6b638279 100644 --- a/apps/AEPSampleAppNewArchEnabled/app/ContentCardView.tsx +++ b/apps/AEPSampleAppNewArchEnabled/app/ContentCardView.tsx @@ -219,8 +219,24 @@ const ContentCardView = () => { { - console.log('Event triggered:', event, identifier); + styleOverrides={{ + smallImageStyle: { + card: { + backgroundColor: '#800080', + borderRadius: 20, + margin: 15, + padding: 10 + }, + title: { + color: '#FF0000' + }, + body: { + color: '#00FF00' + } + } + }} + listener={(event, card) => { + console.log('Event triggered:', event, card); }} /> {renderStyledText('[dark/light]Custom theme')} @@ -244,10 +260,9 @@ const ContentCardView = () => { > { - console.log('Event triggered:', event, identifier); + listener={(event, card) => { + console.log('Event triggered:', event, card); }} /> @@ -255,25 +270,37 @@ const ContentCardView = () => { {renderStyledText('[dismiss button] NO ')} {renderStyledText('[dismiss button] Simple')} {renderStyledText('[image] Invalid')} {renderStyledText('[dark/light] darkUrl')} {renderStyledText('[style]title (2 lines), body (4 lines)')} @@ -282,22 +309,27 @@ const ContentCardView = () => { template={SMALL_IMAGE_CONTENT_IMAGE_DARK_URL} styleOverrides={{ smallImageStyle: { + card: { + backgroundColor: '#800080', + borderRadius: 20, + margin: 15, + padding: 10 + }, title: { - numberOfLines: 2 + color: '#FF0000' }, body: { - numberOfLines: 4 + color: '#00FF00' } } }} - listener={(event, identifier) => { - console.log('Event triggered:', event, identifier); + listener={(event, card) => { + console.log('Event triggered:', event, card); }} /> {renderStyledText('[button] 3')} {renderStyledText( @@ -306,15 +338,14 @@ const ContentCardView = () => { { { { { styleOverrides={{ largeImageStyle: { title: { - numberOfLines: 2 + fontSize: 18, + fontWeight: '600' }, body: { - numberOfLines: 2 + fontSize: 14, + lineHeight: 18 }, image: { aspectRatio: 1 / 1 } } }} - listener={(event, identifier) => { - console.log('Event triggered:', event, identifier); + listener={(event, card) => { + console.log('Event triggered:', event, card); }} /> {renderStyledText('[dark/light]Custom theme')} @@ -456,8 +492,8 @@ const ContentCardView = () => { { - console.log('Event triggered:', event, identifier); + listener={(event, card) => { + console.log('Event triggered:', event, card); }} /> @@ -472,60 +508,45 @@ const ContentCardView = () => { { + listener={(event, card) => { console.log( 'Event triggered: - for imageOnly image 1', event, - identifier + card ); }} /> {renderStyledText( - '2. Images with Action url, dismiss style simple - card height 800' + '2.Adobe default image, dismiss style circle' )} { + template={IMAGE_ONLY_CONTENT_DISMISS_BUTTON_CIRCLE} + listener={(event, card) => { console.log( 'Event triggered: - for imageOnly image 2', event, - identifier + card ); }} /> - {renderStyledText( - '3.Adobe default image, dismiss style circle - card height 400' - )} + {renderStyledText('3. No dismiss button - no card height')} { - console.log( - 'Event triggered: - for imageOnly image 3', - event, - identifier - ); - }} - /> - - {renderStyledText('4. No dismiss button - no card height')} - - {renderStyledText('5. [image] Invalid')} - + {renderStyledText('4. [image] Invalid')} + - {renderStyledText('6.[action] No actionUrl')} - + {renderStyledText('5.[action] No actionUrl')} + - {renderStyledText('7.[style] Custom aspect ratio (1:1)')} + {renderStyledText('6.[style] Custom aspect ratio (1:1)')} { } } }} - listener={(event, identifier) => { + listener={(event, card) => { console.log( 'Event triggered: - for imageOnly image 7', event, - identifier + card ); }} /> - {renderStyledText('8.[style] Custom height (150)')} + {renderStyledText('7.[style] Custom height (150)')} { /> {renderStyledText( - '9. [style] Custom width (80%), set image container backgroud color' + '8. [style] Custom width (80%), set image container backgroud color' )} { }} /> - {renderStyledText('10. [style] Card customization')} + {renderStyledText('9. [style] Card customization')} { }} /> - {renderStyledText('11.[style] Image container customization')} + {renderStyledText('10.[style] Image container customization')} { }} /> - {renderStyledText('12.[style] Combined styles')} + {renderStyledText('11.[style] Combined styles')} { }} /> - {renderStyledText('13.[image] No darkUrl (only light mode)')} - + {renderStyledText('12.[image] No darkUrl (only light mode)')} + {renderStyledText( '1.[image] No Light Mode (only dark mode) - no actionUrl' @@ -643,6 +664,15 @@ const ContentCardView = () => { )} + {selectedView === 'Remote' && ( + + {renderStyledText( + '1.[image] No Light Mode (only dark mode) - no actionUrl' + )} + + + + )} {selectedView === 'Remote' && ( {renderStyledText('[Remote] cards')} @@ -671,74 +701,94 @@ const ContentCardView = () => { ); }; - export default ContentCardView; +// All ContentTemplates updated with proper data structure + const SMALL_IMAGE_CONTENT_ALL_FIELDS: ContentTemplate = { id: 'small-image-all-fields', type: TemplateType.SMALL_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - image: { - alt: '', - url: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png', - darkUrl: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'SmallImage' as any }, + surface: 'rn/ios/sample' }, - buttons: [ - { - interactId: 'downloadClicked', - actionUrl: 'https://nba.com', - id: '5b4d53f5-45bd-4e5c-a5cb-6e650b1993f6', - text: { - content: 'Download App' - } + content: { + image: { + alt: '', + url: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png', + darkUrl: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png' }, - { - interactId: 'OK', - id: '5b4d53f5-45bd-4e5c-a5cb-6e650b1993f6', - text: { - content: 'OK' + buttons: [ + { + interactId: 'downloadClicked', + actionUrl: 'https://nba.com', + id: '5b4d53f5-44bd-4e5c-a5cb-6e650b1993f6', + text: { + content: 'Download App' + } + }, + { + interactId: 'OK', + id: '5b4d53f5-45bd-4e3c-a5cb-6e650b1993f7', + text: { + content: 'OK' + } } + ], + dismissBtn: { + style: 'circle' + }, + actionUrl: '', + body: { + content: 'Get live scores, real-time updates, and exclusive content right at your fingertips.' + }, + title: { + content: 'Stay connected to all the action' } - ], - dismissBtn: { - style: 'circle' - }, - actionUrl: '', - body: { - content: - 'Get live scores, real-time updates, and exclusive content right at your fingertips.' - }, - title: { - content: 'Stay connected to all the action' } } }; + const SMALL_IMAGE_CONTENT_NO_DISMISS_BUTTON: ContentTemplate = { - id: 'small-image-all-fields', + id: 'small-image-no-dismiss', type: TemplateType.SMALL_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - image: { - alt: '', - url: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png', - darkUrl: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'SmallImage' as any }, + surface: 'rn/ios/sample' }, - buttons: [ - { - interactId: 'downloadClicked', - actionUrl: 'https://nba.com', - id: '5b4d53f5-45bd-4e5c-a5cb-6e650b1993f6', - text: { - content: 'Download App' + content: { + image: { + alt: '', + url: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png', + darkUrl: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png' + }, + buttons: [ + { + interactId: 'downloadClicked', + actionUrl: 'https://nba.com', + id: '5b4d53f5-45bd-4e5c-a5cb-6e65b1993f6', + text: { + content: 'Download App' + } } + ], + actionUrl: '', + body: { + content: 'Get live scores, real-time updates, and exclusive content right at your fingertips.' + }, + title: { + content: 'Stay connected to all the action' } - ], - actionUrl: '', - body: { - content: - 'Get live scores, real-time updates, and exclusive content right at your fingertips.' - }, - title: { - content: 'Stay connected to all the action' } } }; @@ -746,39 +796,48 @@ const SMALL_IMAGE_CONTENT_NO_DISMISS_BUTTON: ContentTemplate = { const SMALL_IMAGE_CONTENT_DISMISS_BUTTON_SIMPLE: ContentTemplate = { id: 'small-image-dismiss-button-simple', type: TemplateType.SMALL_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - image: { - alt: '', - url: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png', - darkUrl: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'SmallImage' as any }, + surface: 'rn/ios/sample' }, - buttons: [ - { - interactId: 'downloadClicked', - actionUrl: 'https://nba.com', - id: '5b4d53f5-45bd-4e5c-a5cb-6e650b1993f6', - text: { - content: 'Download App' - } + content: { + image: { + alt: '', + url: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png', + darkUrl: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png' }, - { - interactId: 'OK', - id: '5b4d53f5-45bd-4e5c-a5cb-6e650b1993f6', - text: { - content: 'OK' + buttons: [ + { + interactId: 'downloadClicked', + actionUrl: 'https://nba.com', + id: '5b4d53f5-45bd-4e5c-a5cb-6e650b1893f6', + text: { + content: 'Download App' + } + }, + { + interactId: 'OK', + id: '5b4d53f5-45bd-4e5c-a5cb-6e650a1993f6', + text: { + content: 'OK' + } } + ], + dismissBtn: { + style: 'simple' + }, + actionUrl: '', + body: { + content: 'Get live scores, real-time updates, and exclusive content right at your fingertips.' + }, + title: { + content: 'Stay connected to all the action' } - ], - dismissBtn: { - style: 'simple' - }, - actionUrl: '', - body: { - content: - 'Get live scores, real-time updates, and exclusive content right at your fingertips.' - }, - title: { - content: 'Stay connected to all the action' } } }; @@ -786,379 +845,490 @@ const SMALL_IMAGE_CONTENT_DISMISS_BUTTON_SIMPLE: ContentTemplate = { const SMALL_IMAGE_CONTENT_INVALID_IMAGE: ContentTemplate = { id: 'small-image-invalid-image', type: TemplateType.SMALL_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - body: { - content: - 'Tickets are on sale now! Don’t miss out on securing your seat to witness the high-flying action from the best players in the game' - }, - title: { - content: 'Get Ready for the Basketball Season Kickoff!' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'SmallImage' as any }, + surface: 'rn/ios/sample' }, - buttons: [ - { - interactId: 'buy', - id: '5b4d53f5-45bd-4e5c-a5cb-6e650b1993f6', - actionUrl: 'https://nba.com', - text: { - content: 'Get Season Pass' + content: { + body: { + content: 'Get live scores, real-time updates, and exclusive content right at your fingertips.' + }, + title: { + content: 'Stay connected to all the action' + }, + buttons: [ + { + interactId: 'buy', + id: '5b4d53f5-45bd-4e5c-a5cb-6e450b1993f6', + actionUrl: 'https://nba.com', + text: { + content: 'Get Season Pass' + } } + ], + actionUrl: '', + dismissBtn: { + style: 'circle' + }, + image: { + darkUrl: 'https://invalid-dark-url-that-will-fail.png', + alt: '', + url: 'https://invalid-light-url-that-will-fail.png' } - ], - actionUrl: '', - dismissBtn: { - style: 'circle' - }, - image: { - darkUrl: - 'https://static-00.iconduck.com/assets.00/basketball-icon-256x256-vydm63md.png', - alt: '', - url: 'https://static-00.iconduck.com/assets.00/basketball-icon-256x256-vydm63md.png' } } }; const SMALL_IMAGE_CONTENT_IMAGE_DARK_URL: ContentTemplate = { - id: 'small-image-invalid-image', + id: 'small-image-dark-url', type: TemplateType.SMALL_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - body: { - content: - '🎟️ Tickets are on sale now! Don’t miss out on securing your seat to witness the high-flying action from the best players in the game' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'SmallImage' as any }, + surface: 'rn/ios/sample' }, - title: { - content: 'Get Ready for the Basketball Season Kickoff!' - }, - buttons: [ - { - interactId: 'buy', - id: '5b4d53f5-45bd-4e5c-a5cb-6e650b1993f6', - actionUrl: 'https://nba.com', - text: { - content: 'Get Season Pass' + content: { + body: { + content: 'Tickets are on sale now! Don\'t miss out on securing your seat to witness the high-flying action from the best players in the game' + }, + title: { + content: 'Get Ready for the Basketball Season Kickoff!' + }, + buttons: [ + { + interactId: 'buy', + id: '5b4d53f5-45bd-4e5c-a5cb-6e850b1993f6', + actionUrl: 'https://nba.com', + text: { + content: 'Get Season Pass' + } } + ], + actionUrl: '', + dismissBtn: { + style: 'circle' + }, + image: { + darkUrl: 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg?crop=0.760xw:1.00xh;0.204xw,0&resize=980:*', + alt: '', + url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s' } - ], - actionUrl: '', - dismissBtn: { - style: 'circle' - }, - image: { - darkUrl: - 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg?crop=0.760xw:1.00xh;0.204xw,0&resize=980:*', - alt: '', - url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s' } } }; + const SMALL_IMAGE_CONTENT_3_BUTTONS: ContentTemplate = { - id: 'small-image-invalid-image', + id: 'small-image-3-buttons', type: TemplateType.SMALL_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - body: { - content: - 'Tickets are on sale now! Don’t miss out on securing your seat to witness the high-flying action from the best players in the game' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'SmallImage' as any }, + surface: 'rn/ios/sample' }, - title: { - content: 'Get Ready for the Basketball Season Kickoff!' - }, - buttons: [ - { - interactId: 'buy', - id: '5b4d53f5-45bd-4e5c-a5cb-6e650b1993f6', - actionUrl: 'https://nba.com', - text: { - content: 'Buyyyyy' - } + content: { + body: { + content: 'Tickets are on sale now! Don\'t miss out on securing your seat to witness the high-flying action from the best players in the game' }, - { - interactId: 'ok', - id: '5b4d53f5-45bd-4e5c-a5cb-6e650b1993f6', - actionUrl: 'https://nba.com', - text: { - content: 'OK' - } + title: { + content: 'Get Ready for the Basketball Season Kickoff!' }, - { - interactId: 'more', - id: '5b4d53f5-45bd-4e5c-a5cb-6e650b1993f6', - actionUrl: 'https://nba.com', - text: { - content: 'More' + buttons: [ + { + interactId: 'buy', + id: '5b4d53f5-45bd-4e5c-a5cb-6e610b1993f6', + actionUrl: 'https://nba.com', + text: { + content: 'Buyyyyy' + } + }, + { + interactId: 'ok', + id: '5b4d53f5-41bd-4e5c-a5cb-6e650b1993f6', + actionUrl: 'https://nba.com', + text: { + content: 'OK' + } + }, + { + interactId: 'more', + id: '5b4d53f5-45bd-4e5c-a5cb-6e650b1793f6', + actionUrl: 'https://nba.com', + text: { + content: 'More' + } } + ], + actionUrl: '', + dismissBtn: { + style: 'circle' + }, + image: { + alt: '', + url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s' } - ], - actionUrl: '', - dismissBtn: { - style: 'circle' - }, - image: { - alt: '', - url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s' } } }; + const SMALL_IMAGE_CONTENT_NO_BUTTON: ContentTemplate = { - id: 'small-image-invalid-image', + id: 'small-image-no-button', type: TemplateType.SMALL_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - body: { - content: - '🎟️ Tickets are on sale now! Don’t miss out on securing your seat to witness the high-flying action from the best players in the game' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'SmallImage' as any }, + surface: 'rn/ios/sample' }, - title: { - content: 'Get Ready for the Basketball Season Kickoff!' - }, - buttons: [], - actionUrl: '', - dismissBtn: { - style: 'circle' - }, - image: { - alt: '', - url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s' + content: { + body: { + content: 'Tickets are on sale now! Don\'t miss out on securing your seat to witness the high-flying action from the best players in the game' + }, + title: { + content: 'Get Ready for the Basketball Season Kickoff!' + }, + buttons: [], + actionUrl: '', + dismissBtn: { + style: 'circle' + }, + image: { + alt: '', + url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s' + } } } }; +// Large Image Templates const LARGE_IMAGE_CONTENT_ALL_FIELDS: ContentTemplate = { id: 'large-image-all-fields', type: TemplateType.LARGE_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - actionUrl: 'https://cardaction.com', - body: { - content: - '🎟️ Tickets are on sale now! Don’t miss out on securing your seat to witness the high-flying action from the best players in the game' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'LargeImage' as any }, + surface: 'rn/ios/sample' }, - buttons: [ - { - id: 'a41d1bff-2797-4958-a6d7-2b367e055795', - actionUrl: 'https://buttonone.com/action', - interactId: 'buttonOneClicked', - text: { - content: 'ButtonTextOne' + content: { + actionUrl: 'https://cardaction.com', + body: { + content: 'Tickets are on sale now! Don\'t miss out on securing your seat to witness the high-flying action from the best players in the game' + }, + buttons: [ + { + id: 'a41d1bff-2797-4958-a6d7-2b367e0554595', + actionUrl: 'https://buttonone.com/action', + interactId: 'buttonOneClicked', + text: { + content: 'ButtonTextOne' + } } + ], + image: { + alt: '', + url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s', + darkUrl: '' + }, + dismissBtn: { + style: 'simple' + }, + title: { + content: 'This is large image title' } - ], - image: { - alt: '', - url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s', - darkUrl: '' - }, - dismissBtn: { - style: 'simple' - }, - title: { - content: 'This is large image title' } } }; -const LARGE_IMAGE_CONTENT_NO_DISMISS_BUTTON: ContentTemplate = { - id: 'large-image-all-fields', +const LARGE_IMAGE_CONTENT_3_BUTTONS: ContentTemplate = { + id: 'large-image-3-buttons', type: TemplateType.LARGE_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - actionUrl: 'https://cardaction.com', - body: { - content: - '🎟️ Tickets are on sale now! Don’t miss out on securing your seat to witness the high-flying action from the best players in the game' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'LargeImage' as any }, + surface: 'rn/ios/sample' }, - buttons: [ - { - id: 'a41d1bff-2797-4958-a6d7-2b367e055795', - actionUrl: 'https://buttonone.com/action', - interactId: 'buttonOneClicked', - text: { - content: 'ButtonTextOne' + content: { + actionUrl: 'https://cardaction.com', + body: { + content: 'Tickets are on sale now! Don\'t miss out on securing your seat to witness the high-flying action from the best players in the game' + }, + buttons: [ + { + id: 'a41d1bff-2797-4958-a6d7-2b367e035795', + actionUrl: 'https://buttonone.com/action', + interactId: 'buttonOneClicked_1', + text: { + content: 'ButtonOne' + } + }, + { + id: 'a41d1bff-2797-4958-a6d7-2b367e055796', + interactId: 'buttonOneClicked_2', + text: { + content: 'ButtonTwo' + } + }, + { + id: 'a41d1bff-2797-4958-a6d7-2b367e055797', + interactId: 'buttonOneClicked_3', + text: { + content: 'ButtonThreeeeeeeee' + } } + ], + image: { + alt: '', + url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s' + }, + dismissBtn: { + style: 'simple' + }, + title: { + content: 'This is large image title' } - ], - image: { - alt: '', - url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s' - }, - dismissBtn: { - style: 'none' - }, - title: { - content: 'This is large image title' } } }; -const LARGE_IMAGE_CONTENT_INVALID_IMAGE: ContentTemplate = { - id: 'large-image-all-fields', +const LARGE_IMAGE_CONTENT_NO_DISMISS_BUTTON: ContentTemplate = { + id: 'large-image-no-dismiss', type: TemplateType.LARGE_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - actionUrl: 'https://cardaction.com', - body: { - content: - '🎟️ Tickets are on sale now! Don’t miss out on securing your seat to witness the high-flying action from the best players in the game' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'LargeImage' as any }, + surface: 'rn/ios/sample' }, - buttons: [ - { - id: 'a41d1bff-2797-4958-a6d7-2b367e055795', - actionUrl: 'https://buttonone.com/action', - interactId: 'buttonOneClicked', - text: { - content: 'ButtonTextOne' + content: { + actionUrl: 'https://cardaction.com', + body: { + content: 'Tickets are on sale now! Don\'t miss out on securing your seat to witness the high-flying action from the best players in the game' + }, + buttons: [ + { + id: 'a41d1bff-2797-4958-a6d7-2b367e055798', + actionUrl: 'https://buttonone.com/action', + interactId: 'buttonOneClicked', + text: { + content: 'ButtonTextOne' + } } + ], + image: { + alt: '', + url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s' + }, + dismissBtn: { + style: 'none' + }, + title: { + content: 'This is large image title' } - ], - image: { - alt: '', - url: 'https://xxx', - darkUrl: 'https://imageurl.com/dark' - }, - dismissBtn: { - style: 'none' - }, - title: { - content: 'This is large image title' } } }; -const LARGE_IMAGE_CONTENT_DARK_URL: ContentTemplate = { - id: 'large-image-all-fields', +const LARGE_IMAGE_CONTENT_INVALID_IMAGE: ContentTemplate = { + id: 'large-image-invalid', type: TemplateType.LARGE_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - actionUrl: 'https://cardaction.com', - body: { - content: - '🎟️ Tickets are on sale now! Don’t miss out on securing your seat to witness the high-flying action from the best players in the game' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'LargeImage' as any }, + surface: 'rn/ios/sample' }, - buttons: [ - { - id: 'a41d1bff-2797-4958-a6d7-2b367e055795', - actionUrl: 'https://buttonone.com/action', - interactId: 'buttonOneClicked', - text: { - content: 'ButtonTextOne' + content: { + actionUrl: 'https://cardaction.com', + body: { + content: 'Tickets are on sale now! Don\'t miss out on securing your seat to witness the high-flying action from the best players in the game' + }, + buttons: [ + { + id: 'a41d1bff-2797-4958-a6d7-2b365e055795', + actionUrl: 'https://buttonone.com/action', + interactId: 'buttonOneClicked', + text: { + content: 'ButtonTextOne' + } } + ], + image: { + alt: '', + url: 'https://xxx', + darkUrl: 'https://imageurl.com/dark' + }, + dismissBtn: { + style: 'none' + }, + title: { + content: 'This is large image title' } - ], - image: { - alt: '', - url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s', - darkUrl: - 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg?crop=0.760xw:1.00xh;0.204xw,0&resize=980:*' - }, - dismissBtn: { - style: 'none' - }, - title: { - content: 'This is large image title' } } }; -const LARGE_IMAGE_CONTENT_LONG_TITLE: ContentTemplate = { - id: 'large-image-all-fields', +const LARGE_IMAGE_CONTENT_DARK_URL: ContentTemplate = { + id: 'large-image-dark-url', type: TemplateType.LARGE_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - actionUrl: 'https://cardaction.com', - body: { - content: - '🎟️ Tickets are on sale now! Don’t miss out on securing your seat to witness the high-flying action from the best players in the game' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'LargeImage' as any }, + surface: 'rn/ios/sample' }, - buttons: [ - { - id: 'a41d1bff-2797-4958-a6d7-2b367e055795', - actionUrl: 'https://buttonone.com/action', - interactId: 'buttonOneClicked', - text: { - content: 'ButtonTextOne' + content: { + actionUrl: 'https://cardaction.com', + body: { + content: 'Tickets are on sale now! Don\'t miss out on securing your seat to witness the high-flying action from the best players in the game' + }, + buttons: [ + { + id: 'a41d1bff-2797-4958-a6d7-2b367e055745', + actionUrl: 'https://buttonone.com/action', + interactId: 'buttonOneClicked', + text: { + content: 'ButtonTextOne' + } } + ], + image: { + alt: '', + url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s', + darkUrl: 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg?crop=0.760xw:1.00xh;0.204xw,0&resize=980:*' + }, + dismissBtn: { + style: 'none' + }, + title: { + content: 'This is large image title' } - ], - image: { - alt: '', - url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s', - darkUrl: - 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg?crop=0.760xw:1.00xh;0.204xw,0&resize=980:*' - }, - dismissBtn: { - style: 'none' - }, - title: { - content: - "This is large image title, it's very long very long very long very long" } } }; -const LARGE_IMAGE_CONTENT_3_BUTTONS: ContentTemplate = { - id: 'large-image-all-fields', + +const LARGE_IMAGE_CONTENT_LONG_TITLE: ContentTemplate = { + id: 'large-image-long-title', type: TemplateType.LARGE_IMAGE, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - actionUrl: 'https://cardaction.com', - body: { - content: - '🎟️ Tickets are on sale now! Don’t miss out on securing your seat to witness the high-flying action from the best players in the game' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'LargeImage' as any }, + surface: 'rn/ios/sample' }, - buttons: [ - { - id: 'a41d1bff-2797-4958-a6d7-2b367e055795', - actionUrl: 'https://buttonone.com/action', - interactId: 'buttonOneClicked_1', - text: { - content: 'ButtonOne' - } + content: { + actionUrl: 'https://cardaction.com', + body: { + content: 'Tickets are on sale now! Don\'t miss out on securing your seat to witness the high-flying action from the best players in the game' }, - { - id: 'a41d1bff-2797-4958-a6d7-2b367e055795', - interactId: 'buttonOneClicked_2', - text: { - content: 'ButtonTwo' + buttons: [ + { + id: 'a41d1bff-2797-4958-a6d7-2b367e055748', + actionUrl: 'https://buttonone.com/action', + interactId: 'buttonOneClicked', + text: { + content: 'ButtonTextOne' + } } + ], + image: { + alt: '', + url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s', + darkUrl: 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg?crop=0.760xw:1.00xh;0.204xw,0&resize=980:*' }, - { - id: 'a41d1bff-2797-4958-a6d7-2b367e055795', - interactId: 'buttonOneClicked_3', - text: { - content: 'ButtonThreeeeeeeee' - } + dismissBtn: { + style: 'none' + }, + title: { + content: 'This is large image title, it\'s very long very long very long very long' } - ], - image: { - alt: '', - url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s' - }, - dismissBtn: { - style: 'simple' - }, - title: { - content: 'This is large image title' } } }; +// Image Only Templates const IMAGE_ONLY_CONTENT_ALL_FIELDS: ContentTemplate = { id: 'image-only-all-fields', type: TemplateType.IMAGE_ONLY, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - actionUrl: 'https://www.adobe.com/', - image: { - url: 'https://t4.ftcdn.net/jpg/13/35/40/27/240_F_1335402728_gCAPzivq5VytTJVCEcfIB2eX3ZCdE8cc.jpg', - darkUrl: - 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg', - alt: 'flight offer' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'ImageOnly' as any }, + surface: 'rn/ios/sample' }, - dismissBtn: { - style: 'simple' + content: { + actionUrl: 'https://www.adobe.com/', + image: { + url: 'https://t4.ftcdn.net/jpg/13/35/40/27/240_F_1335402728_gCAPzivq5VytTJVCEcfIB2eX3ZCdE8cc.jpg', + darkUrl: 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg', + alt: 'flight offer' + }, + dismissBtn: { + style: 'simple' + } } } }; -const IMAGE_ONLY_CONTENT_NO_DISMISS_BUTTON: ContentTemplate = { - id: 'image-only-no-dismiss', +const IMAGE_ONLY_CONTENT_WITH_ACTION_URL: ContentTemplate = { + id: 'image-only-with-action-url', type: TemplateType.IMAGE_ONLY, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - actionUrl: 'https://google.com', - image: { - url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s', - darkUrl: - 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg', - alt: 'flight offer' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'ImageOnly' as any }, + surface: 'rn/ios/sample' + }, + content: { + actionUrl: 'https://google.com', + image: { + url: 'https://t4.ftcdn.net/jpg/13/35/40/27/240_F_1335402728_gCAPzivq5VytTJVCEcfIB2eX3ZCdE8cc.jpg', + darkUrl: 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg?crop=0.760xw:1.00xh;0.204xw,0&resize=980:*', + alt: 'with action URL - Google Images' + }, + dismissBtn: { + style: 'simple' + } } } }; @@ -1166,16 +1336,48 @@ const IMAGE_ONLY_CONTENT_NO_DISMISS_BUTTON: ContentTemplate = { const IMAGE_ONLY_CONTENT_DISMISS_BUTTON_CIRCLE: ContentTemplate = { id: 'image-only-dismiss-circle', type: TemplateType.IMAGE_ONLY, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - actionUrl: 'https://google.com', - image: { - url: 'https://i.ibb.co/0X8R3TG/Messages-24.png', - darkUrl: - 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg?crop=0.760xw:1.00xh;0.204xw,0&resize=980:*', - alt: 'flight offer' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'ImageOnly' as any }, + surface: 'rn/ios/sample' }, - dismissBtn: { - style: 'circle' + content: { + actionUrl: 'https://google.com', + image: { + url: 'https://i.ibb.co/0X8R3TG/Messages-24.png', + darkUrl: 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg?crop=0.760xw:1.00xh;0.204xw,0&resize=980:*', + alt: 'flight offer' + }, + dismissBtn: { + style: 'circle' + } + } + } +}; + +const IMAGE_ONLY_CONTENT_NO_DISMISS_BUTTON: ContentTemplate = { + id: 'image-only-no-dismiss', + type: TemplateType.IMAGE_ONLY, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, + data: { + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'ImageOnly' as any }, + surface: 'rn/ios/sample' + }, + content: { + actionUrl: 'https://google.com', + image: { + url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s', + darkUrl: 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg', + alt: 'flight offer' + } } } }; @@ -1183,15 +1385,25 @@ const IMAGE_ONLY_CONTENT_DISMISS_BUTTON_CIRCLE: ContentTemplate = { const IMAGE_ONLY_CONTENT_INVALID_IMAGE: ContentTemplate = { id: 'image-only-invalid-image', type: TemplateType.IMAGE_ONLY, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - actionUrl: 'https://google.com', - image: { - url: 'https://invalid-url-that-will-fail', - darkUrl: 'https://another-invalid-url', - alt: 'broken image' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'ImageOnly' as any }, + surface: 'rn/ios/sample' }, - dismissBtn: { - style: 'simple' + content: { + actionUrl: 'https://google.com', + image: { + url: 'https://invalid-url-that-will-fail', + darkUrl: 'https://another-invalid-url', + alt: 'broken image' + }, + dismissBtn: { + style: 'simple' + } } } }; @@ -1199,15 +1411,24 @@ const IMAGE_ONLY_CONTENT_INVALID_IMAGE: ContentTemplate = { const IMAGE_ONLY_CONTENT_NO_ACTION: ContentTemplate = { id: 'image-only-no-action', type: TemplateType.IMAGE_ONLY, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - image: { - url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s', - darkUrl: - 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg', - alt: 'non-clickable image' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'ImageOnly' as any }, + surface: 'rn/ios/sample' }, - dismissBtn: { - style: 'simple' + content: { + image: { + url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRT8gAa1wUx9Ox2M6cZNwUJe32xE-l_4oqPVA&s', + darkUrl: 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg', + alt: 'non-clickable image' + }, + dismissBtn: { + style: 'simple' + } } } }; @@ -1215,14 +1436,24 @@ const IMAGE_ONLY_CONTENT_NO_ACTION: ContentTemplate = { const IMAGE_ONLY_CONTENT_NO_DARK_URL: ContentTemplate = { id: 'image-only-no-dark-url', type: TemplateType.IMAGE_ONLY, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - actionUrl: 'https://google.com', - image: { - url: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png', - alt: 'light mode only image' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'ImageOnly' as any }, + surface: 'rn/ios/sample' }, - dismissBtn: { - style: 'simple' + content: { + actionUrl: 'https://google.com', + image: { + url: 'https://cdn-icons-png.flaticon.com/256/3303/3303838.png', + alt: 'light mode only image' + }, + dismissBtn: { + style: 'simple' + } } } }; @@ -1230,33 +1461,23 @@ const IMAGE_ONLY_CONTENT_NO_DARK_URL: ContentTemplate = { const IMAGE_ONLY_CONTENT_NO_LIGHT_MODE: ContentTemplate = { id: 'image-only-different-image', type: TemplateType.IMAGE_ONLY, + schema: 'https://ns.adobe.com/personalization/message/content-card' as any, data: { - image: { - url: '', - darkUrl: - 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg?crop=0.760xw:1.00xh;0.204xw,0&resize=980:*', - alt: 'basketball icon' + expiryDate: Date.now() + 86400000, // 24 hours from now + publishedDate: Date.now(), + contentType: 'application/json', + meta: { + adobe: { template: 'ImageOnly' as any }, + surface: 'rn/ios/sample' }, - dismissBtn: { - style: 'circle' - } - } -}; - -const IMAGE_ONLY_CONTENT_WITH_ACTION_URL: ContentTemplate = { - id: 'image-only-with-action-url', - type: TemplateType.IMAGE_ONLY, - data: { content: { - actionUrl: 'https://google.com', image: { - url: 'https://t4.ftcdn.net/jpg/13/35/40/27/240_F_1335402728_gCAPzivq5VytTJVCEcfIB2eX3ZCdE8cc.jpg', - darkUrl: - 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg?crop=0.760xw:1.00xh;0.204xw,0&resize=980:*', - alt: 'with action URL - Google Images' + url: '', + darkUrl: 'https://hips.hearstapps.com/hmg-prod/images/golden-retriever-dog-royalty-free-image-505534037-1565105327.jpg?crop=0.760xw:1.00xh;0.204xw,0&resize=980:*', + alt: 'basketball icon' }, dismissBtn: { - style: 'simple' + style: 'circle' } } } From f06642f3422f0f1dc8157b6d775319a9b6b19de0 Mon Sep 17 00:00:00 2001 From: Calise Cheung Date: Thu, 28 Aug 2025 16:56:20 -0700 Subject: [PATCH 2/2] Update the failing unit test and move the Messaging.updatePropositionsForSurfaces([surface]) initialize page Update the failing unit test and updated sample app --- .../app/ContentCardView.tsx | 243 +++++++-- .../app/_layout.tsx | 12 + .../ContentCardMappingManagerTests.ts | 504 ------------------ .../__tests__/ContentProviderTests.ts | 325 ----------- .../messaging/__tests__/MessagingTests.ts | 4 +- packages/messaging/src/Messaging.ts | 4 +- 6 files changed, 218 insertions(+), 874 deletions(-) delete mode 100644 packages/messaging/__tests__/ContentCardMappingManagerTests.ts delete mode 100644 packages/messaging/__tests__/ContentProviderTests.ts diff --git a/apps/AEPSampleAppNewArchEnabled/app/ContentCardView.tsx b/apps/AEPSampleAppNewArchEnabled/app/ContentCardView.tsx index 6b638279..3fa97b96 100644 --- a/apps/AEPSampleAppNewArchEnabled/app/ContentCardView.tsx +++ b/apps/AEPSampleAppNewArchEnabled/app/ContentCardView.tsx @@ -19,7 +19,8 @@ import { Modal, Appearance, ColorSchemeName, - Platform + Platform, + TextInput } from 'react-native'; import { useEffect } from 'react'; import { useColorScheme } from '../hooks/useColorScheme'; @@ -37,6 +38,8 @@ const ContentCardView = () => { const [selectedView, setSelectedView] = useState('Remote'); const [showPicker, setShowPicker] = useState(false); const [selectedTheme, setSelectedTheme] = useState('System'); + const [trackInput, setTrackInput] = useState(''); + const [isLoading, setIsLoading] = useState(false); const colorScheme = useColorScheme(); const viewOptions = ['SmallImage', 'LargeImage', 'ImageOnly', 'Remote']; @@ -62,22 +65,76 @@ const ContentCardView = () => { ); }; - useEffect(() => { - Messaging.updatePropositionsForSurfaces([`rn/${Platform.OS}/remote_image`]); - // Note: - // - Call above to update the propositions and cache the content locally - // - Customers may call this function when launching the app - MobileCore.trackAction('xyz'); + // Track action and refresh content cards + const handleTrackAction = async () => { + if (!trackInput.trim()) { + console.log(' No action text entered'); + return; + } + + setIsLoading(true); + console.log('Tracking action:', trackInput); + try { - Messaging.getContentCardUI(`rn/${Platform.OS}/remote_image`).then( - (content) => { - console.log('content', content); - setContent(content); - } - ); + // Define the surface based on platform + const surface = Platform.OS === 'android' ? 'rn/android/remote_image' : 'rn/ios/remote_image'; + + // Track the action + MobileCore.trackAction(trackInput); + + // Wait a moment for the action to be processed + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Update propositions first + console.log('Updating propositions for surface:', surface); + await Messaging.updatePropositionsForSurfaces([surface]); + + // Refresh content cards + console.log('Refreshing content cards...'); + const newContent = await Messaging.getContentCardUI(surface); + console.log('📱 New content:', newContent); + setContent(newContent); + } catch (error) { - console.error('Error fetching content cards:', error); + console.error('Error tracking action or refreshing content:', error); + } finally { + setIsLoading(false); } + }; + + useEffect(() => { + // Define the surface based on platform + const surface = Platform.OS === 'android' ? 'rn/android/remote_image' : 'rn/ios/remote_image'; + console.log('Using surface:', surface); + + // Track initial action + MobileCore.trackAction('small_image'); + + // Wait for propositions to be updated from layout.tsx + const timer = setTimeout(async () => { + console.log("Getting content cards (propositions should already be updated from _layout.tsx)..."); + try { + // Just get the content - propositions should already be updated in _layout.tsx + const content = await Messaging.getContentCardUI(surface); + console.log('Content retrieved'); + setContent(content); + + } catch (error) { + console.error('Error fetching content cards:', error); + // Fallback: try updating propositions if they weren't ready + try { + console.log('Fallback: Updating propositions...'); + await Messaging.updatePropositionsForSurfaces([surface]); + const content = await Messaging.getContentCardUI(surface); + console.log('Fallback content retrieved'); + setContent(content); + } catch (fallbackError) { + console.error('Fallback also failed:', fallbackError); + } + } + }, 3000); // Wait 3 seconds for SDK initialization + + return () => clearTimeout(timer); }, []); return ( @@ -664,37 +721,139 @@ const ContentCardView = () => { )} - {selectedView === 'Remote' && ( - - {renderStyledText( - '1.[image] No Light Mode (only dark mode) - no actionUrl' - )} - - - - )} {selectedView === 'Remote' && ( {renderStyledText('[Remote] cards')} - {content?.map((item) => ( - { - console.log('Event triggered:', event, card); - }} - styleOverrides={{ - smallImageStyle: { - // title: { - // numberOfLines: 2 - // }, - // body: { - // numberOfLines: 4 - // } - } - }} - /> - ))} + + {/* Track Action Section */} + + + Track Action & Refresh Content + + + + + + + {isLoading ? 'Loading...' : 'Track & Refresh Content'} + + + + + + Enter an action name to track it, or leave empty to just refresh content + + + + {/* Content Cards Display */} + {content && content.length > 0 ? ( + content.map((item) => ( + { + console.log('Event triggered:', event, card); + }} + styleOverrides={{ + smallImageStyle: { + // title: { + // numberOfLines: 2 + // }, + // body: { + // numberOfLines: 4 + // } + } + }} + /> + )) + ) : ( + + + No Content Cards Available + + + Content cards will appear here when they are configured in Adobe Journey Optimizer for surface: "rn/ios/remote_image" + + + Try tracking an action above to refresh content cards. + + + )} )} diff --git a/apps/AEPSampleAppNewArchEnabled/app/_layout.tsx b/apps/AEPSampleAppNewArchEnabled/app/_layout.tsx index 1168cf88..ae5e2fe8 100644 --- a/apps/AEPSampleAppNewArchEnabled/app/_layout.tsx +++ b/apps/AEPSampleAppNewArchEnabled/app/_layout.tsx @@ -6,10 +6,12 @@ import { import * as SplashScreen from "expo-splash-screen"; import "react-native-reanimated"; import { Drawer } from "expo-router/drawer"; +import { Platform } from "react-native"; import { useColorScheme } from "@/hooks/useColorScheme"; import { MobileCore, LogLevel } from "@adobe/react-native-aepcore"; import { useEffect } from "react"; +import { Messaging } from '@adobe/react-native-aepmessaging'; // Prevent the splash screen from auto-hiding before asset loading is complete. SplashScreen.preventAutoHideAsync(); @@ -46,6 +48,16 @@ export default function RootLayout() { ) .then(() => { console.log("AEP SDK Initialized"); + + // Messaging - Update propositions AFTER SDK is fully initialized + // Use platform-specific surface names + const surface = Platform.OS === 'android' ? 'rn/android/remote_image' : 'rn/ios/remote_image'; + console.log("update propositions before for surface:", surface); + return Messaging.updatePropositionsForSurfaces([surface]); + }) + .then(() => { + console.log("update propositions after - SUCCESS"); + console.log("Propositions updated successfully in layout"); }) .catch((error) => { console.error("AEP SDK Initialization error:", error); diff --git a/packages/messaging/__tests__/ContentCardMappingManagerTests.ts b/packages/messaging/__tests__/ContentCardMappingManagerTests.ts deleted file mode 100644 index ce07ed20..00000000 --- a/packages/messaging/__tests__/ContentCardMappingManagerTests.ts +++ /dev/null @@ -1,504 +0,0 @@ -/* -Copyright 2025 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -import { ContentCardMappingManager } from '../src/ContentCardMappingManager'; -import { ContentCard } from '../src/models/ContentCard'; -import { MessagingProposition } from '../src/models/MessagingProposition'; -import { PersonalizationSchema } from '../src/models/PersonalizationSchema'; - -describe('ContentCardMappingManager', () => { - let mappingManager: ContentCardMappingManager; - const mockSurface = 'testSurface'; - - beforeEach(() => { - // Get the singleton instance and clear any existing mappings - mappingManager = ContentCardMappingManager.getInstance(); - mappingManager.clearMappings(); - }); - - describe('Singleton Pattern', () => { - it('should return the same instance when called multiple times', () => { - const instance1 = ContentCardMappingManager.getInstance(); - const instance2 = ContentCardMappingManager.getInstance(); - expect(instance1).toBe(instance2); - }); - }); - - describe('mapping management', () => { - it('should have no mappings initially', () => { - expect(mappingManager.getMappingCount()).toBe(0); - expect(mappingManager.getAllContentCardIds()).toEqual([]); - }); - - it('should track mappings after adding them', () => { - const mockContentCard: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body' }, - title: { content: 'Test title' }, - buttons: [], - image: { alt: 'Test image', url: 'https://example.com/image.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition: MessagingProposition = { - id: 'test-proposition-id', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id' }, - correlationID: 'test-correlation-id', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard] - }; - - mappingManager.addMapping('test-content-card-id', mockContentCard, mockProposition); - - expect(mappingManager.getMappingCount()).toBe(1); - expect(mappingManager.hasMapping('test-content-card-id')).toBe(true); - expect(mappingManager.getAllContentCardIds()).toContain('test-content-card-id'); - }); - }); - - describe('getContentCardMapping', () => { - it('should return undefined for non-existent content card ID', () => { - const mapping = mappingManager.getContentCardMapping('non-existent-id'); - expect(mapping).toBeUndefined(); - }); - - it('should return correct mapping for existing content card ID', () => { - const mockContentCard: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body' }, - title: { content: 'Test title' }, - buttons: [], - image: { alt: 'Test image', url: 'https://example.com/image.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition: MessagingProposition = { - id: 'test-proposition-id', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id' }, - correlationID: 'test-correlation-id', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard] - }; - - mappingManager.addMapping('test-content-card-id', mockContentCard, mockProposition); - - const mapping = mappingManager.getContentCardMapping('test-content-card-id'); - expect(mapping).toBeDefined(); - expect(mapping?.contentCard).toEqual(mockContentCard); - expect(mapping?.proposition).toEqual(mockProposition); - }); - }); - - describe('addMapping', () => { - it('should add a new mapping', () => { - const mockContentCard: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body' }, - title: { content: 'Test title' }, - buttons: [], - image: { alt: 'Test image', url: 'https://example.com/image.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition: MessagingProposition = { - id: 'test-proposition-id', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id' }, - correlationID: 'test-correlation-id', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard] - }; - - mappingManager.addMapping('test-content-card-id', mockContentCard, mockProposition); - - expect(mappingManager.hasMapping('test-content-card-id')).toBe(true); - expect(mappingManager.getMappingCount()).toBe(1); - }); - - it('should overwrite existing mapping when adding with same ID', () => { - const mockContentCard1: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body 1' }, - title: { content: 'Test title 1' }, - buttons: [], - image: { alt: 'Test image 1', url: 'https://example.com/image1.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockContentCard2: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body 2' }, - title: { content: 'Test title 2' }, - buttons: [], - image: { alt: 'Test image 2', url: 'https://example.com/image2.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition: MessagingProposition = { - id: 'test-proposition-id', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id' }, - correlationID: 'test-correlation-id', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard1] - }; - - mappingManager.addMapping('test-content-card-id', mockContentCard1, mockProposition); - mappingManager.addMapping('test-content-card-id', mockContentCard2, mockProposition); - - const mapping = mappingManager.getContentCardMapping('test-content-card-id'); - expect(mapping?.contentCard).toEqual(mockContentCard2); - expect(mappingManager.getMappingCount()).toBe(1); - }); - }); - - describe('removeMapping', () => { - it('should return false when removing non-existent mapping', () => { - const result = mappingManager.removeMapping('non-existent-id'); - expect(result).toBe(false); - }); - - it('should return true and remove existing mapping', () => { - const mockContentCard: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body' }, - title: { content: 'Test title' }, - buttons: [], - image: { alt: 'Test image', url: 'https://example.com/image.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition: MessagingProposition = { - id: 'test-proposition-id', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id' }, - correlationID: 'test-correlation-id', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard] - }; - - mappingManager.addMapping('test-content-card-id', mockContentCard, mockProposition); - expect(mappingManager.getMappingCount()).toBe(1); - - const result = mappingManager.removeMapping('test-content-card-id'); - expect(result).toBe(true); - expect(mappingManager.getMappingCount()).toBe(0); - expect(mappingManager.hasMapping('test-content-card-id')).toBe(false); - }); - }); - - describe('clearMappings', () => { - it('should clear all mappings', () => { - const mockContentCard: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body' }, - title: { content: 'Test title' }, - buttons: [], - image: { alt: 'Test image', url: 'https://example.com/image.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition: MessagingProposition = { - id: 'test-proposition-id', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id' }, - correlationID: 'test-correlation-id', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard] - }; - - mappingManager.addMapping('test-content-card-id-1', mockContentCard, mockProposition); - mappingManager.addMapping('test-content-card-id-2', mockContentCard, mockProposition); - expect(mappingManager.getMappingCount()).toBe(2); - - mappingManager.clearMappings(); - expect(mappingManager.getMappingCount()).toBe(0); - expect(mappingManager.hasMapping('test-content-card-id-1')).toBe(false); - expect(mappingManager.hasMapping('test-content-card-id-2')).toBe(false); - }); - }); - - describe('hasMapping', () => { - it('should return false for non-existent mapping', () => { - expect(mappingManager.hasMapping('non-existent-id')).toBe(false); - }); - - it('should return true for existing mapping', () => { - const mockContentCard: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body' }, - title: { content: 'Test title' }, - buttons: [], - image: { alt: 'Test image', url: 'https://example.com/image.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition: MessagingProposition = { - id: 'test-proposition-id', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id' }, - correlationID: 'test-correlation-id', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard] - }; - - mappingManager.addMapping('test-content-card-id', mockContentCard, mockProposition); - expect(mappingManager.hasMapping('test-content-card-id')).toBe(true); - }); - }); - - describe('getMappingCount', () => { - it('should return 0 for empty manager', () => { - expect(mappingManager.getMappingCount()).toBe(0); - }); - - it('should return correct count after adding mappings', () => { - const mockContentCard: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body' }, - title: { content: 'Test title' }, - buttons: [], - image: { alt: 'Test image', url: 'https://example.com/image.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition: MessagingProposition = { - id: 'test-proposition-id', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id' }, - correlationID: 'test-correlation-id', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard] - }; - - expect(mappingManager.getMappingCount()).toBe(0); - mappingManager.addMapping('test-content-card-id-1', mockContentCard, mockProposition); - expect(mappingManager.getMappingCount()).toBe(1); - mappingManager.addMapping('test-content-card-id-2', mockContentCard, mockProposition); - expect(mappingManager.getMappingCount()).toBe(2); - }); - }); - - describe('getAllContentCardIds', () => { - it('should return empty array for empty manager', () => { - const ids = mappingManager.getAllContentCardIds(); - expect(ids).toEqual([]); - }); - - it('should return all content card IDs', () => { - const mockContentCard: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body' }, - title: { content: 'Test title' }, - buttons: [], - image: { alt: 'Test image', url: 'https://example.com/image.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition: MessagingProposition = { - id: 'test-proposition-id', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id' }, - correlationID: 'test-correlation-id', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard] - }; - - mappingManager.addMapping('test-content-card-id-1', mockContentCard, mockProposition); - mappingManager.addMapping('test-content-card-id-2', mockContentCard, mockProposition); - - const ids = mappingManager.getAllContentCardIds(); - expect(ids).toContain('test-content-card-id-1'); - expect(ids).toContain('test-content-card-id-2'); - expect(ids.length).toBe(2); - }); - }); -}); \ No newline at end of file diff --git a/packages/messaging/__tests__/ContentProviderTests.ts b/packages/messaging/__tests__/ContentProviderTests.ts deleted file mode 100644 index a1d3f7e5..00000000 --- a/packages/messaging/__tests__/ContentProviderTests.ts +++ /dev/null @@ -1,325 +0,0 @@ -/* -Copyright 2025 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -import { ContentProvider, TemplateType } from '../src/ContentProvider'; -import { ContentCardMappingManager } from '../src/ContentCardMappingManager'; -import { ContentCard } from '../src/models/ContentCard'; -import { MessagingProposition } from '../src/models/MessagingProposition'; -import { PersonalizationSchema } from '../src/models/PersonalizationSchema'; -import Messaging from '../src/Messaging'; - -// Mock the Messaging module -jest.mock('../src/Messaging'); - -describe('ContentProvider', () => { - let contentProvider: ContentProvider; - let mappingManager: ContentCardMappingManager; - const mockSurface = 'testSurface'; - - beforeEach(() => { - contentProvider = new ContentProvider(mockSurface); - mappingManager = ContentCardMappingManager.getInstance(); - mappingManager.clearMappings(); - jest.clearAllMocks(); - }); - - describe('getContentCardMap', () => { - it('should return empty map initially', () => { - const mappingCount = mappingManager.getMappingCount(); - expect(mappingCount).toBe(0); - }); - - it('should populate map after getContent is called', async () => { - // Mock the getPropositionsForSurfaces response - const mockContentCard: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body' }, - title: { content: 'Test title' }, - buttons: [], - image: { alt: 'Test image', url: 'https://example.com/image.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition: MessagingProposition = { - id: 'test-proposition-id', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id' }, - correlationID: 'test-correlation-id', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard] - }; - - const mockPropositions = { - [mockSurface]: [mockProposition] - }; - - (Messaging.getPropositionsForSurfaces as jest.Mock).mockResolvedValue(mockPropositions); - - // Call getContent to populate the map - await contentProvider.getContent(); - - // Verify the map is populated - const mappingCount = mappingManager.getMappingCount(); - expect(mappingCount).toBe(1); - expect(mappingManager.hasMapping('test-content-card-id')).toBe(true); - - const mapping = mappingManager.getContentCardMapping('test-content-card-id'); - expect(mapping).toBeDefined(); - expect(mapping?.contentCard).toEqual(mockContentCard); - expect(mapping?.proposition).toEqual(mockProposition); - }); - }); - - describe('getContentCardMapping', () => { - it('should return undefined for non-existent content card ID', () => { - const mapping = mappingManager.getContentCardMapping('non-existent-id'); - expect(mapping).toBeUndefined(); - }); - - it('should return correct mapping for existing content card ID', async () => { - // Mock the getPropositionsForSurfaces response - const mockContentCard: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body' }, - title: { content: 'Test title' }, - buttons: [], - image: { alt: 'Test image', url: 'https://example.com/image.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition: MessagingProposition = { - id: 'test-proposition-id', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id' }, - correlationID: 'test-correlation-id', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard] - }; - - const mockPropositions = { - [mockSurface]: [mockProposition] - }; - - (Messaging.getPropositionsForSurfaces as jest.Mock).mockResolvedValue(mockPropositions); - - // Call getContent to populate the map - await contentProvider.getContent(); - - // Get the mapping for the content card ID - const mapping = mappingManager.getContentCardMapping('test-content-card-id'); - expect(mapping).toBeDefined(); - expect(mapping?.contentCard).toEqual(mockContentCard); - expect(mapping?.proposition).toEqual(mockProposition); - }); - }); - - describe('getContent', () => { - it('should return content templates and populate the map', async () => { - // Mock the getPropositionsForSurfaces response - const mockContentCard: ContentCard = { - id: 'test-content-card-id', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body' }, - title: { content: 'Test title' }, - buttons: [], - image: { alt: 'Test image', url: 'https://example.com/image.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition: MessagingProposition = { - id: 'test-proposition-id', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id' }, - correlationID: 'test-correlation-id', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard] - }; - - const mockPropositions = { - [mockSurface]: [mockProposition] - }; - - (Messaging.getPropositionsForSurfaces as jest.Mock).mockResolvedValue(mockPropositions); - - // Call getContent - const contentTemplates = await contentProvider.getContent(); - - // Verify content templates are returned - expect(contentTemplates).toHaveLength(1); - expect(contentTemplates[0].id).toBe('test-content-card-id'); - expect(contentTemplates[0].type).toBe(TemplateType.SMALL_IMAGE); - - // Verify the map is populated - const mappingCount = mappingManager.getMappingCount(); - expect(mappingCount).toBe(1); - expect(mappingManager.hasMapping('test-content-card-id')).toBe(true); - }); - - it('should clear previous map when getContent is called again', async () => { - // Mock the getPropositionsForSurfaces response for first call - const mockContentCard1: ContentCard = { - id: 'test-content-card-id-1', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body' }, - title: { content: 'Test title' }, - buttons: [], - image: { alt: 'Test image', url: 'https://example.com/image.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition1: MessagingProposition = { - id: 'test-proposition-id-1', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token-1' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id-1' }, - correlationID: 'test-correlation-id-1', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard1] - }; - - const mockPropositions1 = { - [mockSurface]: [mockProposition1] - }; - - (Messaging.getPropositionsForSurfaces as jest.Mock).mockResolvedValue(mockPropositions1); - - // First call to getContent - await contentProvider.getContent(); - - // Verify the map has the first content card - let mappingCount = mappingManager.getMappingCount(); - expect(mappingCount).toBe(1); - expect(mappingManager.hasMapping('test-content-card-id-1')).toBe(true); - - // Mock the getPropositionsForSurfaces response for second call - const mockContentCard2: ContentCard = { - id: 'test-content-card-id-2', - data: { - contentType: 'application/json', - expiryDate: 1234567890, - publishedDate: 1234567890, - content: { - actionUrl: 'https://example.com', - body: { content: 'Test body 2' }, - title: { content: 'Test title 2' }, - buttons: [], - image: { alt: 'Test image 2', url: 'https://example.com/image2.jpg' }, - dismissBtn: { style: 'circle' } - }, - meta: { - adobe: { template: 'SmallImage' }, - dismissState: false, - readState: false, - surface: mockSurface - } - }, - schema: PersonalizationSchema.CONTENT_CARD - }; - - const mockProposition2: MessagingProposition = { - id: 'test-proposition-id-2', - scope: 'test-scope', - scopeDetails: { - characteristics: { eventToken: 'test-event-token-2' }, - activity: { matchedSurfaces: [mockSurface], id: 'test-activity-id-2' }, - correlationID: 'test-correlation-id-2', - decisionProvider: 'test-decision-provider' - }, - items: [mockContentCard2] - }; - - const mockPropositions2 = { - [mockSurface]: [mockProposition2] - }; - - (Messaging.getPropositionsForSurfaces as jest.Mock).mockResolvedValue(mockPropositions2); - - // Second call to getContent - await contentProvider.getContent(); - - // Verify the map now has only the second content card (previous one was cleared) - mappingCount = mappingManager.getMappingCount(); - expect(mappingCount).toBe(2); - expect(mappingManager.hasMapping('test-content-card-id-1')).toBe(true); - expect(mappingManager.hasMapping('test-content-card-id-2')).toBe(true); - }); - }); -}); \ No newline at end of file diff --git a/packages/messaging/__tests__/MessagingTests.ts b/packages/messaging/__tests__/MessagingTests.ts index 28ba3913..25730541 100644 --- a/packages/messaging/__tests__/MessagingTests.ts +++ b/packages/messaging/__tests__/MessagingTests.ts @@ -11,7 +11,9 @@ governing permissions and limitations under the License. */ import { NativeModules } from 'react-native'; -import { Messaging, Message, MessagingEdgeEventType } from '../src'; +import Messaging from '../src/Messaging'; +import Message from '../src/models/Message'; +import MessagingEdgeEventType from '../src/models/MessagingEdgeEventType'; describe('Messaging', () => { it('extensionVersion is called', async () => { diff --git a/packages/messaging/src/Messaging.ts b/packages/messaging/src/Messaging.ts index da066a72..ef513762 100644 --- a/packages/messaging/src/Messaging.ts +++ b/packages/messaging/src/Messaging.ts @@ -236,13 +236,13 @@ class Messaging { proposition.items.filter( (item) => item.schema === PersonalizationSchema.CONTENT_CARD ) - ); + ) as ContentCard[]; if (!contentCards?.length) { return []; } - return contentCards.map((card: ContentCard) => { + return contentCards.map((card) => { const type = card.data?.meta?.adobe?.template ?? TemplateType.SMALL_IMAGE; return { ...card,