@@ -3,6 +3,7 @@ import '@testing-library/jest-dom/extend-expect';
33import { useFakeTranslations } from 'pageflow/testHelpers' ;
44
55import { Thread } from 'review/Thread' ;
6+ import { review } from 'review/api' ;
67import { renderWithReviewState } from 'support/renderWithReviewState' ;
78
89describe ( 'Thread' , ( ) => {
@@ -42,4 +43,131 @@ describe('Thread', () => {
4243 expect ( hint . compareDocumentPosition ( comment ) & Node . DOCUMENT_POSITION_FOLLOWING )
4344 . toBeTruthy ( ) ;
4445 } ) ;
46+
47+ describe ( 'comment quotes' , ( ) => {
48+ const seed = {
49+ sections : [ { id : 1 , permaId : 1 } ] ,
50+ contentElements : [
51+ {
52+ id : 1 , permaId : 10 , sectionId : 1 , typeName : 'textBlock' ,
53+ configuration : { value : 'Current wording' }
54+ }
55+ ]
56+ } ;
57+
58+ const quotingThread = {
59+ ...thread ,
60+ subjectType : 'ContentElement' ,
61+ subjectId : 10 ,
62+ subjectRange : { anchor : { path : [ 0 , 0 ] , offset : 0 } , focus : { path : [ 0 , 0 ] , offset : 7 } }
63+ } ;
64+
65+ function threadWithQuotes ( ...quotes ) {
66+ return {
67+ ...quotingThread ,
68+ comments : quotes . map ( ( quote , index ) => ( {
69+ id : 10 + index ,
70+ body : `Comment ${ index + 1 } ` ,
71+ creatorName : 'Bob' ,
72+ creatorId : 2 ,
73+ quote
74+ } ) )
75+ } ;
76+ }
77+
78+ beforeEach ( ( ) => {
79+ review . contentElementTypes . register ( 'textBlock' , {
80+ extractQuote : configuration => configuration . value
81+ } ) ;
82+ } ) ;
83+
84+ afterEach ( ( ) => {
85+ review . contentElementTypes . types = { } ;
86+ } ) ;
87+
88+ it ( 'renders a quote for a comment whose text has since changed' , ( ) => {
89+ const { getByText} = renderWithReviewState (
90+ < Thread thread = { threadWithQuotes ( 'Original wording' ) } interactive = { false } /> ,
91+ { seed}
92+ ) ;
93+
94+ expect ( getByText ( 'Original wording' ) ) . toBeInTheDocument ( ) ;
95+ } ) ;
96+
97+ it ( 'renders no quote while the text still reads the same' , ( ) => {
98+ const { container} = renderWithReviewState (
99+ < Thread thread = { threadWithQuotes ( 'Current wording' ) } interactive = { false } /> ,
100+ { seed}
101+ ) ;
102+
103+ expect ( container . querySelector ( 'blockquote' ) ) . toBeNull ( ) ;
104+ } ) ;
105+
106+ it ( 'renders the quote inside the comment, above its body' , ( ) => {
107+ const { getByText} = renderWithReviewState (
108+ < Thread thread = { threadWithQuotes ( 'Original wording' ) } interactive = { false } /> ,
109+ { seed}
110+ ) ;
111+
112+ const quote = getByText ( 'Original wording' ) ;
113+ const body = getByText ( 'Comment 1' ) ;
114+
115+ expect ( quote . parentNode ) . toBe ( body . parentNode ) ;
116+ expect ( quote . compareDocumentPosition ( body ) & Node . DOCUMENT_POSITION_FOLLOWING )
117+ . toBeTruthy ( ) ;
118+ } ) ;
119+
120+ it ( 'renders a quote per version once the text changes mid thread' , ( ) => {
121+ const { getByText} = renderWithReviewState (
122+ < Thread thread = { threadWithQuotes ( 'First wording' , 'Second wording' ) }
123+ interactive = { false } /> ,
124+ { seed}
125+ ) ;
126+
127+ expect ( getByText ( 'First wording' ) ) . toBeInTheDocument ( ) ;
128+ expect ( getByText ( 'Second wording' ) ) . toBeInTheDocument ( ) ;
129+ } ) ;
130+
131+ it ( 'renders the quote once for a run of replies about the same wording' , ( ) => {
132+ const { queryAllByText} = renderWithReviewState (
133+ < Thread thread = { threadWithQuotes ( 'Same wording' , 'Same wording' ) }
134+ interactive = { false } /> ,
135+ { seed}
136+ ) ;
137+
138+ expect ( queryAllByText ( 'Same wording' ) ) . toHaveLength ( 1 ) ;
139+ } ) ;
140+
141+ it ( 'renders no quote for the last comment when it matches the current text' , ( ) => {
142+ const { getByText, queryByText} = renderWithReviewState (
143+ < Thread thread = { threadWithQuotes ( 'Original wording' , 'Current wording' ) }
144+ interactive = { false } /> ,
145+ { seed}
146+ ) ;
147+
148+ expect ( getByText ( 'Original wording' ) ) . toBeInTheDocument ( ) ;
149+ expect ( queryByText ( 'Current wording' ) ) . not . toBeInTheDocument ( ) ;
150+ } ) ;
151+
152+ it ( 'renders quotes for threads whose content element was deleted' , ( ) => {
153+ const { getByText} = renderWithReviewState (
154+ < Thread thread = { { ...threadWithQuotes ( 'Original wording' ) ,
155+ subjectId : 999 ,
156+ orphaned : true } }
157+ interactive = { false } /> ,
158+ { seed}
159+ ) ;
160+
161+ expect ( getByText ( 'Original wording' ) ) . toBeInTheDocument ( ) ;
162+ } ) ;
163+
164+ it ( 'renders no quote for comments recorded without one' , ( ) => {
165+ const { container} = renderWithReviewState (
166+ < Thread thread = { quotingThread } interactive = { false } /> ,
167+ { seed}
168+ ) ;
169+
170+ expect ( container . querySelector ( 'blockquote' ) ) . toBeNull ( ) ;
171+ } ) ;
172+ } ) ;
45173} ) ;
0 commit comments