@@ -16,7 +16,11 @@ describe('ThreadList', () => {
1616 'pageflow_scrolled.review.reply_placeholder' : 'Reply...' ,
1717 'pageflow_scrolled.review.send' : 'Send' ,
1818 'pageflow_scrolled.review.enter_for_new_line' : 'Enter for new line' ,
19- 'pageflow_scrolled.review.toggle_replies' : 'Toggle replies'
19+ 'pageflow_scrolled.review.toggle_replies' : 'Toggle replies' ,
20+ 'pageflow_scrolled.review.resolve' : 'Mark as resolved' ,
21+ 'pageflow_scrolled.review.unresolve' : 'Mark as unresolved' ,
22+ 'pageflow_scrolled.review.resolved_count.one' : '1 resolved' ,
23+ 'pageflow_scrolled.review.resolved_count.other' : '%{count} resolved'
2024 } ) ;
2125 it ( 'displays comments of threads for subject' , ( ) => {
2226 const { getByText} = renderWithReviewState (
@@ -389,6 +393,127 @@ describe('ThreadList', () => {
389393 expect ( getByRole ( 'button' , { name : 'Send' } ) ) . toBeInTheDocument ( ) ;
390394 } ) ;
391395
396+ it ( 'hides resolved threads and shows resolved count pill' , ( ) => {
397+ const { queryByText, getByText} = renderWithReviewState (
398+ < ThreadList subjectType = "ContentElement" subjectId = { 10 } /> ,
399+ {
400+ commentThreads : [
401+ { id : 1 , subjectType : 'ContentElement' , subjectId : 10 ,
402+ resolvedAt : '2026-04-09T10:00:00Z' ,
403+ comments : [ { id : 10 , body : 'Resolved thread' , creatorName : 'Bob' , creatorId : 2 } ] } ,
404+ { id : 2 , subjectType : 'ContentElement' , subjectId : 10 ,
405+ resolvedAt : null ,
406+ comments : [ { id : 20 , body : 'Active thread' , creatorName : 'Alice' , creatorId : 1 } ] }
407+ ]
408+ }
409+ ) ;
410+
411+ expect ( getByText ( 'Active thread' ) ) . toBeInTheDocument ( ) ;
412+ expect ( queryByText ( 'Resolved thread' ) ) . not . toBeInTheDocument ( ) ;
413+ expect ( getByText ( '1 resolved' ) ) . toBeInTheDocument ( ) ;
414+ } ) ;
415+
416+ it ( 'toggles resolved threads when pill is clicked' , async ( ) => {
417+ const user = userEvent . setup ( ) ;
418+
419+ const { getByText, queryByText} = renderWithReviewState (
420+ < ThreadList subjectType = "ContentElement" subjectId = { 10 } /> ,
421+ {
422+ commentThreads : [
423+ { id : 1 , subjectType : 'ContentElement' , subjectId : 10 ,
424+ resolvedAt : '2026-04-09T10:00:00Z' ,
425+ comments : [ { id : 10 , body : 'Resolved thread' , creatorName : 'Bob' , creatorId : 2 } ] } ,
426+ { id : 2 , subjectType : 'ContentElement' , subjectId : 10 ,
427+ resolvedAt : null ,
428+ comments : [ { id : 20 , body : 'Active thread' , creatorName : 'Alice' , creatorId : 1 } ] }
429+ ]
430+ }
431+ ) ;
432+
433+ await user . click ( getByText ( '1 resolved' ) ) ;
434+ expect ( getByText ( 'Resolved thread' ) ) . toBeInTheDocument ( ) ;
435+ expect ( getByText ( '1 resolved' ) ) . toBeInTheDocument ( ) ;
436+
437+ await user . click ( getByText ( '1 resolved' ) ) ;
438+ expect ( queryByText ( 'Resolved thread' ) ) . not . toBeInTheDocument ( ) ;
439+ } ) ;
440+
441+ it ( 'posts resolve message when resolve button is clicked' , async ( ) => {
442+ const user = userEvent . setup ( ) ;
443+ const postMessage = jest . spyOn ( window . top , 'postMessage' ) . mockImplementation ( ( ) => { } ) ;
444+
445+ const { getByText} = renderWithReviewState (
446+ < ThreadList subjectType = "ContentElement" subjectId = { 10 } /> ,
447+ {
448+ commentThreads : [
449+ { id : 1 , subjectType : 'ContentElement' , subjectId : 10 ,
450+ resolvedAt : null ,
451+ comments : [ { id : 10 , body : 'Open thread' , creatorName : 'Bob' , creatorId : 2 } ] }
452+ ]
453+ }
454+ ) ;
455+
456+ await user . click ( getByText ( 'Mark as resolved' ) ) ;
457+
458+ expect ( postMessage ) . toHaveBeenCalledWith (
459+ { type : 'UPDATE_THREAD' , payload : { threadId : 1 , resolved : true } } ,
460+ window . location . origin
461+ ) ;
462+
463+ postMessage . mockRestore ( ) ;
464+ } ) ;
465+
466+ it ( 'does not show resolve button on collapsed threads with replies' , ( ) => {
467+ const { queryByText} = renderWithReviewState (
468+ < ThreadList subjectType = "ContentElement" subjectId = { 10 } /> ,
469+ {
470+ commentThreads : [
471+ { id : 1 , subjectType : 'ContentElement' , subjectId : 10 ,
472+ resolvedAt : null ,
473+ comments : [
474+ { id : 10 , body : 'First thread' , creatorName : 'Bob' , creatorId : 2 } ,
475+ { id : 11 , body : 'Reply' , creatorName : 'Alice' , creatorId : 1 }
476+ ] } ,
477+ { id : 2 , subjectType : 'ContentElement' , subjectId : 10 ,
478+ resolvedAt : null ,
479+ comments : [
480+ { id : 20 , body : 'Second thread' , creatorName : 'Alice' , creatorId : 1 } ,
481+ { id : 21 , body : 'Reply' , creatorName : 'Bob' , creatorId : 2 }
482+ ] }
483+ ]
484+ }
485+ ) ;
486+
487+ expect ( queryByText ( 'Mark as resolved' ) ) . not . toBeInTheDocument ( ) ;
488+ } ) ;
489+
490+ it ( 'does not show reply form on resolved threads' , async ( ) => {
491+ const user = userEvent . setup ( ) ;
492+
493+ const { queryByPlaceholderText, getByText} = renderWithReviewState (
494+ < ThreadList subjectType = "ContentElement" subjectId = { 10 } /> ,
495+ {
496+ commentThreads : [
497+ { id : 1 , subjectType : 'ContentElement' , subjectId : 10 ,
498+ resolvedAt : null ,
499+ comments : [ { id : 10 , body : 'Active thread' , creatorName : 'Bob' , creatorId : 2 } ] } ,
500+ { id : 2 , subjectType : 'ContentElement' , subjectId : 10 ,
501+ resolvedAt : '2026-04-09T10:00:00Z' ,
502+ comments : [ { id : 20 , body : 'Resolved thread' , creatorName : 'Alice' , creatorId : 1 } ] }
503+ ]
504+ }
505+ ) ;
506+
507+ await user . click ( getByText ( '1 resolved' ) ) ;
508+
509+ const replyFields = queryByPlaceholderText ( 'Reply...' ) ;
510+ expect ( replyFields ) . toBeInTheDocument ( ) ;
511+
512+ expect ( getByText ( 'Resolved thread' ) ) . toBeInTheDocument ( ) ;
513+ const resolvedThread = getByText ( 'Resolved thread' ) . closest ( '[class*="thread"]' ) ;
514+ expect ( resolvedThread . querySelector ( 'textarea[placeholder="Reply..."]' ) ) . toBeNull ( ) ;
515+ } ) ;
516+
392517 it ( 'shows reply form in collapsed thread without replies' , ( ) => {
393518 const { getAllByPlaceholderText} = renderWithReviewState (
394519 < ThreadList subjectType = "ContentElement" subjectId = { 10 } /> ,
0 commit comments