@@ -38,6 +38,9 @@ const queryClient = new QueryClient({
38
38
} ,
39
39
} ) ;
40
40
41
+ /**
42
+ * Returns 0 components from the search query.
43
+ */
41
44
const returnEmptyResult = ( _url , req ) => {
42
45
const requestData = JSON . parse ( req . body ?. toString ( ) ?? '' ) ;
43
46
const query = requestData ?. queries [ 0 ] ?. q ?? '' ;
@@ -50,6 +53,26 @@ const returnEmptyResult = (_url, req) => {
50
53
return mockEmptyResult ;
51
54
} ;
52
55
56
+ /**
57
+ * Returns 2 components from the search query.
58
+ * This lets us test that the StudioHome "View All" button is hidden when a
59
+ * low number of search results are shown (<=4 by default).
60
+ */
61
+ const returnLowNumberResults = ( _url , req ) => {
62
+ const requestData = JSON . parse ( req . body ?. toString ( ) ?? '' ) ;
63
+ const query = requestData ?. queries [ 0 ] ?. q ?? '' ;
64
+ // We have to replace the query (search keywords) in the mock results with the actual query,
65
+ // because otherwise we may have an inconsistent state that causes more queries and unexpected results.
66
+ mockResult . results [ 0 ] . query = query ;
67
+ // Limit number of results to just 2
68
+ mockResult . results [ 0 ] . hits = mockResult . results [ 0 ] ?. hits . slice ( 0 , 2 ) ;
69
+ mockResult . results [ 0 ] . estimatedTotalHits = 2 ;
70
+ // And fake the required '_formatted' fields; it contains the highlighting <mark>...</mark> around matched words
71
+ // eslint-disable-next-line no-underscore-dangle, no-param-reassign
72
+ mockResult . results [ 0 ] ?. hits . forEach ( ( hit ) => { hit . _formatted = { ...hit } ; } ) ;
73
+ return mockResult ;
74
+ } ;
75
+
53
76
const libraryData : ContentLibrary = {
54
77
id : 'lib:org1:lib1' ,
55
78
type : 'complex' ,
@@ -154,11 +177,13 @@ describe('<LibraryAuthoringPage />', () => {
154
177
axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
155
178
156
179
const {
157
- getByRole, getByText, queryByText , findByText ,
180
+ getByRole, getByText, getAllByText , queryByText ,
158
181
} = render ( < RootWrapper /> ) ;
159
182
160
- // Ensure the search endpoint is called
161
- await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 1 , searchEndpoint , 'post' ) ; } ) ;
183
+ // Ensure the search endpoint is called:
184
+ // Call 1: To fetch searchable/filterable/sortable library data
185
+ // Call 2: To fetch the recently modified components only
186
+ await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 2 , searchEndpoint , 'post' ) ; } ) ;
162
187
163
188
expect ( getByText ( 'Content library' ) ) . toBeInTheDocument ( ) ;
164
189
expect ( getByText ( libraryData . title ) ) . toBeInTheDocument ( ) ;
@@ -168,7 +193,7 @@ describe('<LibraryAuthoringPage />', () => {
168
193
expect ( getByText ( 'Recently Modified' ) ) . toBeInTheDocument ( ) ;
169
194
expect ( getByText ( 'Collections (0)' ) ) . toBeInTheDocument ( ) ;
170
195
expect ( getByText ( 'Components (6)' ) ) . toBeInTheDocument ( ) ;
171
- expect ( await findByText ( 'Test HTML Block' ) ) . toBeInTheDocument ( ) ;
196
+ expect ( getAllByText ( 'Test HTML Block' ) [ 0 ] ) . toBeInTheDocument ( ) ;
172
197
173
198
// Navigate to the components tab
174
199
fireEvent . click ( getByRole ( 'tab' , { name : 'Components' } ) ) ;
@@ -202,8 +227,10 @@ describe('<LibraryAuthoringPage />', () => {
202
227
expect ( await findByText ( 'Content library' ) ) . toBeInTheDocument ( ) ;
203
228
expect ( await findByText ( libraryData . title ) ) . toBeInTheDocument ( ) ;
204
229
205
- // Ensure the search endpoint is called
206
- await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 1 , searchEndpoint , 'post' ) ; } ) ;
230
+ // Ensure the search endpoint is called:
231
+ // Call 1: To fetch searchable/filterable/sortable library data
232
+ // Call 2: To fetch the recently modified components only
233
+ await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 2 , searchEndpoint , 'post' ) ; } ) ;
207
234
208
235
expect ( getByText ( 'You have not added any content to this library yet.' ) ) . toBeInTheDocument ( ) ;
209
236
} ) ;
@@ -228,13 +255,16 @@ describe('<LibraryAuthoringPage />', () => {
228
255
expect ( await findByText ( 'Content library' ) ) . toBeInTheDocument ( ) ;
229
256
expect ( await findByText ( libraryData . title ) ) . toBeInTheDocument ( ) ;
230
257
231
- // Ensure the search endpoint is called
232
- await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 1 , searchEndpoint , 'post' ) ; } ) ;
258
+ // Ensure the search endpoint is called:
259
+ // Call 1: To fetch searchable/filterable/sortable library data
260
+ // Call 2: To fetch the recently modified components only
261
+ await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 2 , searchEndpoint , 'post' ) ; } ) ;
233
262
234
263
fireEvent . change ( getByRole ( 'searchbox' ) , { target : { value : 'noresults' } } ) ;
235
264
236
- // Ensure the search endpoint is called again
237
- await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 2 , searchEndpoint , 'post' ) ; } ) ;
265
+ // Ensure the search endpoint is called again, only once more since the recently modified call
266
+ // should not be impacted by the search
267
+ await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 3 , searchEndpoint , 'post' ) ; } ) ;
238
268
239
269
expect ( getByText ( 'No matching components found in this library.' ) ) . toBeInTheDocument ( ) ;
240
270
@@ -266,4 +296,122 @@ describe('<LibraryAuthoringPage />', () => {
266
296
267
297
expect ( screen . queryByText ( / a d d c o n t e n t / i) ) . not . toBeInTheDocument ( ) ;
268
298
} ) ;
299
+
300
+ it ( 'show the "View All" button when viewing library with many components' , async ( ) => {
301
+ mockUseParams . mockReturnValue ( { libraryId : libraryData . id } ) ;
302
+ axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
303
+
304
+ const {
305
+ getByRole, getByText, queryByText, getAllByText,
306
+ } = render ( < RootWrapper /> ) ;
307
+
308
+ // Ensure the search endpoint is called:
309
+ // Call 1: To fetch searchable/filterable/sortable library data
310
+ // Call 2: To fetch the recently modified components only
311
+ await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 2 , searchEndpoint , 'post' ) ; } ) ;
312
+
313
+ expect ( getByText ( 'Content library' ) ) . toBeInTheDocument ( ) ;
314
+ expect ( getByText ( libraryData . title ) ) . toBeInTheDocument ( ) ;
315
+
316
+ expect ( getByText ( 'Recently Modified' ) ) . toBeInTheDocument ( ) ;
317
+ expect ( getByText ( 'Collections (0)' ) ) . toBeInTheDocument ( ) ;
318
+ expect ( getByText ( 'Components (6)' ) ) . toBeInTheDocument ( ) ;
319
+ expect ( getAllByText ( 'Test HTML Block' ) [ 0 ] ) . toBeInTheDocument ( ) ;
320
+ expect ( queryByText ( 'You have not added any content to this library yet.' ) ) . not . toBeInTheDocument ( ) ;
321
+
322
+ // There should only be one "View All" button, since the Components count
323
+ // are above the preview limit (4)
324
+ expect ( getByText ( 'View All' ) ) . toBeInTheDocument ( ) ;
325
+
326
+ // Clicking on "View All" button should navigate to the Components tab
327
+ fireEvent . click ( getByText ( 'View All' ) ) ;
328
+ expect ( queryByText ( 'Recently Modified' ) ) . not . toBeInTheDocument ( ) ;
329
+ expect ( queryByText ( 'Collections (0)' ) ) . not . toBeInTheDocument ( ) ;
330
+ expect ( queryByText ( 'Components (6)' ) ) . not . toBeInTheDocument ( ) ;
331
+ expect ( getAllByText ( 'Test HTML Block' ) [ 0 ] ) . toBeInTheDocument ( ) ;
332
+
333
+ // Go back to Home tab
334
+ // This step is necessary to avoid the url change leak to other tests
335
+ fireEvent . click ( getByRole ( 'tab' , { name : 'Home' } ) ) ;
336
+ expect ( getByText ( 'Recently Modified' ) ) . toBeInTheDocument ( ) ;
337
+ expect ( getByText ( 'Collections (0)' ) ) . toBeInTheDocument ( ) ;
338
+ expect ( getByText ( 'Components (6)' ) ) . toBeInTheDocument ( ) ;
339
+ } ) ;
340
+
341
+ it ( 'should not show the "View All" button when viewing library with low number of components' , async ( ) => {
342
+ mockUseParams . mockReturnValue ( { libraryId : libraryData . id } ) ;
343
+ axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
344
+ fetchMock . post ( searchEndpoint , returnLowNumberResults , { overwriteRoutes : true } ) ;
345
+
346
+ const {
347
+ getByText, queryByText, getAllByText,
348
+ } = render ( < RootWrapper /> ) ;
349
+
350
+ // Ensure the search endpoint is called:
351
+ // Call 1: To fetch searchable/filterable/sortable library data
352
+ // Call 2: To fetch the recently modified components only
353
+ await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 2 , searchEndpoint , 'post' ) ; } ) ;
354
+
355
+ expect ( getByText ( 'Content library' ) ) . toBeInTheDocument ( ) ;
356
+ expect ( getByText ( libraryData . title ) ) . toBeInTheDocument ( ) ;
357
+
358
+ expect ( getByText ( 'Recently Modified' ) ) . toBeInTheDocument ( ) ;
359
+ expect ( getByText ( 'Collections (0)' ) ) . toBeInTheDocument ( ) ;
360
+ expect ( getByText ( 'Components (2)' ) ) . toBeInTheDocument ( ) ;
361
+ expect ( getAllByText ( 'Test HTML Block' ) [ 0 ] ) . toBeInTheDocument ( ) ;
362
+
363
+ expect ( queryByText ( 'You have not added any content to this library yet.' ) ) . not . toBeInTheDocument ( ) ;
364
+
365
+ // There should not be any "View All" button on page since Components count
366
+ // is less than the preview limit (4)
367
+ expect ( queryByText ( 'View All' ) ) . not . toBeInTheDocument ( ) ;
368
+ } ) ;
369
+
370
+ it ( 'sort library components' , async ( ) => {
371
+ mockUseParams . mockReturnValue ( { libraryId : libraryData . id } ) ;
372
+ axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
373
+ fetchMock . post ( searchEndpoint , returnEmptyResult , { overwriteRoutes : true } ) ;
374
+
375
+ const {
376
+ findByTitle, getAllByText, getByText, getByTitle,
377
+ } = render ( < RootWrapper /> ) ;
378
+
379
+ expect ( await findByTitle ( 'Sort search results' ) ) . toBeInTheDocument ( ) ;
380
+
381
+ const testSortOption = ( async ( optionText , sortBy ) => {
382
+ if ( optionText ) {
383
+ fireEvent . click ( getByTitle ( 'Sort search results' ) ) ;
384
+ fireEvent . click ( getByText ( optionText ) ) ;
385
+ }
386
+ const bodyText = sortBy ? `"sort":["${ sortBy } "]` : '"sort":[]' ;
387
+ const searchText = sortBy ? `?sort=${ encodeURIComponent ( sortBy ) } ` : '' ;
388
+ await waitFor ( ( ) => {
389
+ expect ( fetchMock ) . toHaveBeenLastCalledWith ( searchEndpoint , {
390
+ body : expect . stringContaining ( bodyText ) ,
391
+ method : 'POST' ,
392
+ headers : expect . anything ( ) ,
393
+ } ) ;
394
+ } ) ;
395
+ expect ( window . location . search ) . toEqual ( searchText ) ;
396
+ } ) ;
397
+
398
+ await testSortOption ( 'Title, A-Z' , 'display_name:asc' ) ;
399
+ await testSortOption ( 'Title, Z-A' , 'display_name:desc' ) ;
400
+ await testSortOption ( 'Newest' , 'created:desc' ) ;
401
+ await testSortOption ( 'Oldest' , 'created:asc' ) ;
402
+
403
+ // Sorting by Recently Published also excludes unpublished components
404
+ await testSortOption ( 'Recently Published' , 'last_published:desc' ) ;
405
+ await waitFor ( ( ) => {
406
+ expect ( fetchMock ) . toHaveBeenLastCalledWith ( searchEndpoint , {
407
+ body : expect . stringContaining ( 'last_published IS NOT NULL' ) ,
408
+ method : 'POST' ,
409
+ headers : expect . anything ( ) ,
410
+ } ) ;
411
+ } ) ;
412
+
413
+ // Clearing filters clears the url search param and uses default sort
414
+ fireEvent . click ( getAllByText ( 'Clear Filters' ) [ 0 ] ) ;
415
+ await testSortOption ( '' , '' ) ;
416
+ } ) ;
269
417
} ) ;
0 commit comments