@@ -3,6 +3,7 @@ import { render, act, fireEvent } from '@testing-library/react-native';
33import Braze from '@braze/react-native-sdk' ;
44import BrazeBanner from './BrazeBanner' ;
55import { BRAZE_BANNER_TEST_IDS } from './BrazeBanner.testIds' ;
6+ import { SKELETON_TIMEOUT_MS } from './BrazeBanner.constants' ;
67
78const TEST_PLACEMENT_ID = 'test-placement-1' ;
89
@@ -139,6 +140,7 @@ jest.mock('./BrazeBannerCard', () => {
139140// ---------------------------------------------------------------------------
140141function makeRawProperties ( props : {
141142 bannerId ?: string ;
143+ dismissable ?: boolean ;
142144 deeplink ?: string ;
143145 body ?: string ;
144146} ) : Record < string , { type : string ; value : unknown } > {
@@ -147,7 +149,9 @@ function makeRawProperties(props: {
147149 body : { type : 'string' , value : props . body ?? 'Default body text' } ,
148150 } ;
149151 if ( props . bannerId !== undefined )
150- result . bannerId = { type : 'string' , value : props . bannerId } ;
152+ result . banner_id = { type : 'string' , value : props . bannerId } ;
153+ if ( props . dismissable !== undefined )
154+ result . dismissable = { type : 'boolean' , value : props . dismissable } ;
151155 if ( props . deeplink !== undefined )
152156 result . deeplink = { type : 'string' , value : props . deeplink } ;
153157 return result ;
@@ -158,6 +162,7 @@ function makeBanner(
158162 trackingId : string ;
159163 placementId : string ;
160164 bannerId : string ;
165+ dismissable : boolean ;
161166 deeplink : string ;
162167 body : string ;
163168 } > = { } ,
@@ -170,6 +175,7 @@ function makeBanner(
170175 expiresAt : - 1 ,
171176 properties : makeRawProperties ( {
172177 bannerId : overrides . bannerId ,
178+ dismissable : overrides . dismissable ,
173179 deeplink : overrides . deeplink ,
174180 body : overrides . body ,
175181 } ) ,
@@ -182,6 +188,39 @@ const fireBannerEvent = (banners: object[]) => {
182188 } ) ;
183189} ;
184190
191+ type BannerState = 'loading' | 'visible' | 'empty' ;
192+
193+ /**
194+ * Asserts the complete set of structural test IDs for a given banner state:
195+ * - `loading` → CONTAINER + SKELETON present; PRESSABLE + DISMISS_BUTTON absent
196+ * - `visible` → CONTAINER + PRESSABLE + DISMISS_BUTTON present; SKELETON absent
197+ * - `empty` → all four absent (component returns null)
198+ */
199+ function assertBannerState (
200+ queryByTestId : ( id : string ) => unknown ,
201+ state : BannerState ,
202+ ) {
203+ const present = ( id : string ) => expect ( queryByTestId ( id ) ) . toBeTruthy ( ) ;
204+ const absent = ( id : string ) => expect ( queryByTestId ( id ) ) . toBeNull ( ) ;
205+
206+ if ( state === 'loading' ) {
207+ present ( BRAZE_BANNER_TEST_IDS . CONTAINER ) ;
208+ present ( BRAZE_BANNER_TEST_IDS . SKELETON ) ;
209+ absent ( BRAZE_BANNER_TEST_IDS . PRESSABLE ) ;
210+ absent ( BRAZE_BANNER_TEST_IDS . DISMISS_BUTTON ) ;
211+ } else if ( state === 'visible' ) {
212+ present ( BRAZE_BANNER_TEST_IDS . CONTAINER ) ;
213+ absent ( BRAZE_BANNER_TEST_IDS . SKELETON ) ;
214+ present ( BRAZE_BANNER_TEST_IDS . PRESSABLE ) ;
215+ present ( BRAZE_BANNER_TEST_IDS . DISMISS_BUTTON ) ;
216+ } else {
217+ absent ( BRAZE_BANNER_TEST_IDS . CONTAINER ) ;
218+ absent ( BRAZE_BANNER_TEST_IDS . SKELETON ) ;
219+ absent ( BRAZE_BANNER_TEST_IDS . PRESSABLE ) ;
220+ absent ( BRAZE_BANNER_TEST_IDS . DISMISS_BUTTON ) ;
221+ }
222+ }
223+
185224// ---------------------------------------------------------------------------
186225// Tests
187226// ---------------------------------------------------------------------------
@@ -205,9 +244,7 @@ describe('BrazeBanner', () => {
205244 < BrazeBanner placementId = { TEST_PLACEMENT_ID } /> ,
206245 ) ;
207246
208- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . SKELETON ) ) . toBeTruthy ( ) ;
209- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . DISMISS_BUTTON ) ) . toBeNull ( ) ;
210- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . PRESSABLE ) ) . toBeNull ( ) ;
247+ assertBannerState ( queryByTestId , 'loading' ) ;
211248 } ) ;
212249
213250 it ( 'shows the banner card and dismiss button when a banner event arrives' , async ( ) => {
@@ -217,9 +254,7 @@ describe('BrazeBanner', () => {
217254
218255 fireBannerEvent ( [ makeBanner ( ) ] ) ;
219256
220- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . SKELETON ) ) . toBeNull ( ) ;
221- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . PRESSABLE ) ) . toBeTruthy ( ) ;
222- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . DISMISS_BUTTON ) ) . toBeTruthy ( ) ;
257+ assertBannerState ( queryByTestId , 'visible' ) ;
223258 } ) ;
224259
225260 it ( 'renders nothing after the skeleton timeout when no banner arrives' , ( ) => {
@@ -228,22 +263,21 @@ describe('BrazeBanner', () => {
228263 ) ;
229264
230265 act ( ( ) => {
231- jest . advanceTimersByTime ( 5000 ) ;
266+ jest . advanceTimersByTime ( SKELETON_TIMEOUT_MS ) ;
232267 } ) ;
233268
234- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . CONTAINER ) ) . toBeNull ( ) ;
235- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . SKELETON ) ) . toBeNull ( ) ;
269+ assertBannerState ( queryByTestId , 'empty' ) ;
236270 } ) ;
237271
238- it ( 'renders nothing when bannerCardsUpdated contains no banner for the placement' , ( ) => {
272+ it ( 'stays in loading skeleton when bannerCardsUpdated arrives with no banner for the placement' , ( ) => {
239273 const { queryByTestId } = render (
240274 < BrazeBanner placementId = { TEST_PLACEMENT_ID } /> ,
241275 ) ;
242276
243277 fireBannerEvent ( [ ] ) ;
244278
245- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . CONTAINER ) ) . toBeNull ( ) ;
246- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . PRESSABLE ) ) . toBeNull ( ) ;
279+ // No match → handleBanner not called → stays loading (timeout handles empty)
280+ assertBannerState ( queryByTestId , 'loading' ) ;
247281 } ) ;
248282
249283 it ( 'renders nothing when incoming banner bannerId matches lastDismissedBrazeBanner' , ( ) => {
@@ -254,10 +288,8 @@ describe('BrazeBanner', () => {
254288
255289 fireBannerEvent ( [ makeBanner ( { bannerId : 'campaign-xyz' } ) ] ) ;
256290
257- // Dismissed banner → empty state → component returns null
258- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . CONTAINER ) ) . toBeNull ( ) ;
259- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . SKELETON ) ) . toBeNull ( ) ;
260- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . PRESSABLE ) ) . toBeNull ( ) ;
291+ // Dismissed banner is skipped; hook stays in loading so skeleton remains visible
292+ assertBannerState ( queryByTestId , 'loading' ) ;
261293 } ) ;
262294
263295 it ( 'renders normally when lastDismissedBrazeBanner does not match incoming banner' , ( ) => {
@@ -268,7 +300,7 @@ describe('BrazeBanner', () => {
268300
269301 fireBannerEvent ( [ makeBanner ( { bannerId : 'new-campaign' } ) ] ) ;
270302
271- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . PRESSABLE ) ) . toBeTruthy ( ) ;
303+ assertBannerState ( queryByTestId , 'visible' ) ;
272304 } ) ;
273305
274306 it ( 'renders null immediately on dismiss with no skeleton shown' , ( ) => {
@@ -281,16 +313,17 @@ describe('BrazeBanner', () => {
281313
282314 fireEvent . press ( getByTestId ( BRAZE_BANNER_TEST_IDS . DISMISS_BUTTON ) ) ;
283315
284- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . CONTAINER ) ) . toBeNull ( ) ;
285- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . SKELETON ) ) . toBeNull ( ) ;
316+ assertBannerState ( queryByTestId , 'empty' ) ;
286317 } ) ;
287318
288- it ( 'dispatches setLastDismissedBrazeBanner on dismiss when bannerId is set' , ( ) => {
319+ it ( 'dispatches setLastDismissedBrazeBanner on dismiss when banner_id and dismissable:true are set' , ( ) => {
289320 const { getByTestId } = render (
290321 < BrazeBanner placementId = { TEST_PLACEMENT_ID } /> ,
291322 ) ;
292323
293- fireBannerEvent ( [ makeBanner ( { bannerId : 'campaign-xyz' } ) ] ) ;
324+ fireBannerEvent ( [
325+ makeBanner ( { bannerId : 'campaign-xyz' , dismissable : true } ) ,
326+ ] ) ;
294327 fireEvent . press ( getByTestId ( BRAZE_BANNER_TEST_IDS . DISMISS_BUTTON ) ) ;
295328
296329 expect ( mockDispatch ) . toHaveBeenCalledWith (
@@ -319,7 +352,7 @@ describe('BrazeBanner', () => {
319352
320353 fireBannerEvent ( [ makeBanner ( { trackingId : 'new-banner' } ) ] ) ;
321354
322- expect ( queryByTestId ( BRAZE_BANNER_TEST_IDS . CONTAINER ) ) . toBeNull ( ) ;
355+ assertBannerState ( queryByTestId , 'empty' ) ;
323356 } ) ;
324357
325358 it ( 'uses warm-cache banner from getBannerForPlacement on mount' , async ( ) => {
0 commit comments