@@ -345,6 +345,80 @@ describe("Inbox toolbar", () => {
345345 act ( ( ) => root . unmount ( ) ) ;
346346 } ) ;
347347
348+ it ( "restores folded and unfolded sub-tasks across remounts" , async ( ) => {
349+ routerMock . location . pathname = "/inbox/mine" ;
350+ const storageKey = "paperclip:inbox:collapsed-parents:company-1" ;
351+ localStorage . removeItem ( storageKey ) ;
352+
353+ const parent = createIssue ( {
354+ id : "parent-issue" ,
355+ identifier : "PAP-1001" ,
356+ title : "Parent inbox task" ,
357+ } ) ;
358+ const child = createIssue ( {
359+ id : "child-issue" ,
360+ identifier : "PAP-1002" ,
361+ parentId : parent . id ,
362+ title : "Nested inbox task" ,
363+ } ) ;
364+ apiMocks . issuesList . mockResolvedValue ( [ parent , child ] ) ;
365+
366+ const mountInbox = async ( ) => {
367+ const queryClient = new QueryClient ( {
368+ defaultOptions : { queries : { retry : false , staleTime : 0 , gcTime : 0 } } ,
369+ } ) ;
370+ const root = createRoot ( container ) ;
371+ await act ( async ( ) => {
372+ root . render (
373+ < QueryClientProvider client = { queryClient } >
374+ < Inbox />
375+ </ QueryClientProvider > ,
376+ ) ;
377+ } ) ;
378+ await vi . waitFor ( ( ) => {
379+ expect ( container . textContent ) . toContain ( parent . title ) ;
380+ } ) ;
381+ return root ;
382+ } ;
383+ const parentToggle = ( ) => {
384+ const parentRow = Array . from ( container . querySelectorAll ( "[data-inbox-item]" ) )
385+ . find ( ( row ) => row . textContent ?. includes ( parent . title ) ) ;
386+ return parentRow ?. querySelector < HTMLButtonElement > ( 'button[data-slot="icon-button"]' ) ?? null ;
387+ } ;
388+
389+ let root = await mountInbox ( ) ;
390+ try {
391+ expect ( container . textContent ) . toContain ( child . title ) ;
392+
393+ await act ( async ( ) => {
394+ parentToggle ( ) ?. dispatchEvent ( new MouseEvent ( "click" , { bubbles : true } ) ) ;
395+ } ) ;
396+ await vi . waitFor ( ( ) => {
397+ expect ( container . textContent ) . not . toContain ( child . title ) ;
398+ } ) ;
399+ expect ( JSON . parse ( localStorage . getItem ( storageKey ) ?? "[]" ) ) . toEqual ( [ parent . id ] ) ;
400+
401+ act ( ( ) => root . unmount ( ) ) ;
402+ root = await mountInbox ( ) ;
403+ expect ( container . textContent ) . not . toContain ( child . title ) ;
404+
405+ await act ( async ( ) => {
406+ parentToggle ( ) ?. dispatchEvent ( new MouseEvent ( "click" , { bubbles : true } ) ) ;
407+ } ) ;
408+ await vi . waitFor ( ( ) => {
409+ expect ( container . textContent ) . toContain ( child . title ) ;
410+ } ) ;
411+ expect ( JSON . parse ( localStorage . getItem ( storageKey ) ?? "[]" ) ) . toEqual ( [ ] ) ;
412+
413+ act ( ( ) => root . unmount ( ) ) ;
414+ root = await mountInbox ( ) ;
415+ expect ( container . textContent ) . toContain ( child . title ) ;
416+ } finally {
417+ localStorage . removeItem ( storageKey ) ;
418+ act ( ( ) => root . unmount ( ) ) ;
419+ }
420+ } ) ;
421+
348422 it ( "shows blocked toolbar controls on the Blocked tab" , async ( ) => {
349423 routerMock . location . pathname = "/inbox/blocked" ;
350424 const queryClient = new QueryClient ( {
0 commit comments