@@ -334,7 +334,10 @@ describeEachAppLayout({ themes: ['refresh-toolbar'] }, ({ size }) => {
334334 } ) ;
335335
336336 test ( 'shows feature prompt for a latest unseen features' , async ( ) => {
337- mockRetrieveFeatureNotifications . mockResolvedValue ( { 'feature-1' : mockDate2025 . toString ( ) } ) ;
337+ mockRetrieveFeatureNotifications . mockResolvedValue ( {
338+ 'feature-1' : mockDate2025 . toString ( ) ,
339+ 'feature-1_feature-prompt' : mockDate2025 . toString ( ) ,
340+ } ) ;
338341 featureNotifications . registerFeatureNotifications ( featureNotificationsDefaults ) ;
339342 const { container } = renderComponent ( < AppLayout /> ) ;
340343 await delay ( ) ;
@@ -376,7 +379,7 @@ describeEachAppLayout({ themes: ['refresh-toolbar'] }, ({ size }) => {
376379 'feature-2' : mockDate2024 . toString ( ) ,
377380 'feature-old' : mockDateOld . toString ( ) ,
378381 } ) ;
379- featureNotifications . registerFeatureNotifications ( { ... featureNotificationsDefaults , suppressFeaturePrompt : true } ) ;
382+ featureNotifications . registerFeatureNotifications ( featureNotificationsDefaults ) ;
380383 const { container } = renderComponent ( < AppLayout /> ) ;
381384
382385 const featurePromptWrapper = new FeaturePromptWrapper ( container ) ;
@@ -408,9 +411,13 @@ describeEachAppLayout({ themes: ['refresh-toolbar'] }, ({ size }) => {
408411
409412 wrapper . findDrawerTriggerById ( featureNotificationsDefaults . id ) ! . click ( ) ;
410413
411- expect ( mockPersistFeatureNotifications ) . toHaveBeenCalled ( ) ;
412-
413- const persistedFeaturesMap = mockPersistFeatureNotifications . mock . calls [ 0 ] [ 1 ] ;
414+ // The first persist call may be the feature prompt dismissal (if the prompt was shown and auto-dismissed).
415+ // The "mark all as read" call is the one that contains all feature IDs.
416+ const markAllAsReadCall = mockPersistFeatureNotifications . mock . calls . find (
417+ call => call [ 1 ] [ 'feature-1' ] && call [ 1 ] [ 'feature-2' ]
418+ ) ;
419+ expect ( markAllAsReadCall ) . toBeTruthy ( ) ;
420+ const persistedFeaturesMap = markAllAsReadCall ! [ 1 ] ;
414421
415422 expect ( persistedFeaturesMap ) . toHaveProperty ( 'feature-1' ) ;
416423 expect ( persistedFeaturesMap ) . toHaveProperty ( 'feature-2' ) ;
@@ -432,6 +439,87 @@ describeEachAppLayout({ themes: ['refresh-toolbar'] }, ({ size }) => {
432439 expect ( wrapper . findDrawerTriggerById ( 'empty-features' ) ) . toBeFalsy ( ) ;
433440 } ) ;
434441
442+ test ( 'does not show feature prompt if it was previously dismissed (persisted)' , async ( ) => {
443+ // Simulate that the feature prompt for feature-1 was previously dismissed and persisted
444+ mockRetrieveFeatureNotifications . mockResolvedValue ( {
445+ 'feature-1_feature-prompt' : mockDate2025 . toString ( ) ,
446+ } ) ;
447+ featureNotifications . registerFeatureNotifications ( featureNotificationsDefaults ) ;
448+ const { container } = renderComponent ( < AppLayout /> ) ;
449+ await delay ( ) ;
450+
451+ const featurePromptWrapper = new FeaturePromptWrapper ( container ) ;
452+ // Feature prompt should not appear because the latest unseen feature's prompt was already dismissed
453+ expect ( featurePromptWrapper . findContent ( ) ) . toBeFalsy ( ) ;
454+ } ) ;
455+
456+ test ( 'persists feature prompt dismissal when user dismisses the prompt' , async ( ) => {
457+ featureNotifications . registerFeatureNotifications ( featureNotificationsDefaults ) ;
458+ const { container, unmount } = renderComponent (
459+ < TestI18nProvider messages = { i18nMessages } >
460+ < AppLayout />
461+ </ TestI18nProvider >
462+ ) ;
463+ await delay ( ) ;
464+
465+ const featurePromptWrapper = new FeaturePromptWrapper ( container ) ;
466+ expect ( featurePromptWrapper . findContent ( ) ! . getElement ( ) ) . toHaveTextContent ( 'This is the first new feature content' ) ;
467+
468+ featurePromptWrapper . findDismissButton ( ) ! . click ( ) ;
469+
470+ await waitFor ( ( ) => {
471+ expect ( mockPersistFeatureNotifications ) . toHaveBeenCalled ( ) ;
472+ const persistedMap = mockPersistFeatureNotifications . mock . calls [ 0 ] [ 1 ] ;
473+ expect ( persistedMap ) . toHaveProperty ( 'feature-1_feature-prompt' ) ;
474+ } ) ;
475+
476+ unmount ( ) ;
477+
478+ const { container : containerAfterRemount } = renderComponent (
479+ < TestI18nProvider messages = { i18nMessages } >
480+ < AppLayout />
481+ </ TestI18nProvider >
482+ ) ;
483+ expect ( new FeaturePromptWrapper ( containerAfterRemount ) . findContent ( ) ) . toBeFalsy ( ) ;
484+ } ) ;
485+
486+ test ( 'feature prompt dismissal persists with filtered outdated seen features' , async ( ) => {
487+ const oldSeenFeatureDate = new Date ( mockCurrentDate ) ;
488+ oldSeenFeatureDate . setDate ( oldSeenFeatureDate . getDate ( ) - 200 ) ; // More than 180 days ago
489+
490+ const recentSeenFeatureDate = new Date ( mockCurrentDate ) ;
491+ recentSeenFeatureDate . setDate ( recentSeenFeatureDate . getDate ( ) - 100 ) ; // Less than 180 days ago
492+
493+ mockRetrieveFeatureNotifications . mockResolvedValue ( {
494+ 'old-seen-feature' : oldSeenFeatureDate . toISOString ( ) ,
495+ 'recent-seen-feature' : recentSeenFeatureDate . toISOString ( ) ,
496+ } ) ;
497+
498+ featureNotifications . registerFeatureNotifications ( featureNotificationsDefaults ) ;
499+ const { container } = renderComponent (
500+ < TestI18nProvider messages = { i18nMessages } >
501+ < AppLayout />
502+ </ TestI18nProvider >
503+ ) ;
504+ await delay ( ) ;
505+
506+ const featurePromptWrapper = new FeaturePromptWrapper ( container ) ;
507+ expect ( featurePromptWrapper . findContent ( ) ! . getElement ( ) ) . toHaveTextContent ( 'This is the first new feature content' ) ;
508+
509+ featurePromptWrapper . findDismissButton ( ) ! . click ( ) ;
510+
511+ await waitFor ( ( ) => {
512+ expect ( mockPersistFeatureNotifications ) . toHaveBeenCalled ( ) ;
513+ const persistedMap = mockPersistFeatureNotifications . mock . calls [ 0 ] [ 1 ] ;
514+ // Should include the prompt dismissal key
515+ expect ( persistedMap ) . toHaveProperty ( 'feature-1_feature-prompt' ) ;
516+ // Should keep recent seen features
517+ expect ( persistedMap ) . toHaveProperty ( 'recent-seen-feature' ) ;
518+ // Should filter out outdated seen features (>180 days)
519+ expect ( persistedMap ) . not . toHaveProperty ( 'old-seen-feature' ) ;
520+ } ) ;
521+ } ) ;
522+
435523 test ( 'renders feature notifications drawer alongside tools' , ( ) => {
436524 featureNotifications . registerFeatureNotifications ( featureNotificationsDefaults ) ;
437525 const { wrapper } = renderComponent (
0 commit comments