@@ -53,6 +53,7 @@ describe("NotificationDrawer", () => {
5353 vi . advanceTimersByTime ( 0 ) ;
5454 expect ( screen . getByText ( / S o m e t h i n g f a i l e d / ) ) . toBeDefined ( ) ;
5555 expect ( screen . getByText ( / a p i / ) ) . toBeDefined ( ) ;
56+ expect ( screen . queryByText ( "(will retry)" ) ) . toBeNull ( ) ;
5657 } ) ;
5758
5859 it ( "shows newest notification first in the list" , ( ) => {
@@ -108,20 +109,23 @@ describe("NotificationDrawer", () => {
108109 } ) ;
109110
110111 it ( "calls onClose when overlay backdrop is clicked" , ( ) => {
112+ // Kobalte dismisses via document-level capture-phase pointerdown (createInteractOutside),
113+ // not an overlay click handler. data-testid targets the overlay because Dialog.Overlay
114+ // has no ARIA role to query by.
111115 const onClose = vi . fn ( ) ;
112116 render ( ( ) => < NotificationDrawer open = { true } onClose = { onClose } /> ) ;
113117 vi . advanceTimersByTime ( 0 ) ;
114118 const overlay = screen . getByTestId ( "notification-overlay" ) ;
115119 fireEvent . pointerDown ( overlay ) ;
116- expect ( onClose ) . toHaveBeenCalled ( ) ;
120+ expect ( onClose ) . toHaveBeenCalledTimes ( 1 ) ;
117121 } ) ;
118122
119123 it ( "calls onClose when X button is clicked" , ( ) => {
120124 const onClose = vi . fn ( ) ;
121125 render ( ( ) => < NotificationDrawer open = { true } onClose = { onClose } /> ) ;
122126 vi . advanceTimersByTime ( 0 ) ;
123127 fireEvent . click ( screen . getByLabelText ( "Close notifications" ) ) ;
124- expect ( onClose ) . toHaveBeenCalled ( ) ;
128+ expect ( onClose ) . toHaveBeenCalledTimes ( 1 ) ;
125129 } ) ;
126130
127131 it ( "shows empty state text when no notifications" , ( ) => {
@@ -159,6 +163,35 @@ describe("NotificationDrawer", () => {
159163 render ( ( ) => < NotificationDrawer open = { true } onClose = { onClose } /> ) ;
160164 vi . advanceTimersByTime ( 0 ) ;
161165 fireEvent . keyDown ( document , { key : "Escape" } ) ;
162- expect ( onClose ) . toHaveBeenCalled ( ) ;
166+ expect ( onClose ) . toHaveBeenCalledTimes ( 1 ) ;
167+ } ) ;
168+
169+ it ( "shows retryable indicator when notification is retryable" , ( ) => {
170+ pushNotification ( "api" , "Transient failure" , "error" , true ) ;
171+ renderDrawer ( true ) ;
172+ vi . advanceTimersByTime ( 0 ) ;
173+ expect ( screen . queryByText ( "(will retry)" ) ) . not . toBeNull ( ) ;
174+ } ) ;
175+
176+ it ( "marks dialog as closed when open transitions from true to false" , ( ) => {
177+ // solid-presence waits for animationend to unmount; happy-dom has no CSS engine
178+ // so the element stays in DOM with data-closed instead of being removed
179+ const { setIsOpen } = renderDrawer ( true ) ;
180+ vi . advanceTimersByTime ( 0 ) ;
181+ const dialog = screen . getByRole ( "dialog" ) ;
182+ expect ( dialog . hasAttribute ( "data-closed" ) ) . toBe ( false ) ;
183+ setIsOpen ( false ) ;
184+ vi . advanceTimersByTime ( 0 ) ;
185+ expect ( dialog . hasAttribute ( "data-closed" ) ) . toBe ( true ) ;
186+ } ) ;
187+
188+ it ( "has accessible description for screen readers" , ( ) => {
189+ renderDrawer ( true ) ;
190+ vi . advanceTimersByTime ( 0 ) ;
191+ const dialog = screen . getByRole ( "dialog" ) ;
192+ const descId = dialog . getAttribute ( "aria-describedby" ) ;
193+ expect ( descId ) . toBeTruthy ( ) ;
194+ const descEl = document . getElementById ( descId ! ) ;
195+ expect ( descEl ?. textContent ) . toContain ( "Recent system notifications and errors" ) ;
163196 } ) ;
164197} ) ;
0 commit comments