@@ -10,13 +10,14 @@ import {
10
10
render ,
11
11
waitFor ,
12
12
screen ,
13
+ within ,
13
14
} from '@testing-library/react' ;
14
15
import fetchMock from 'fetch-mock-jest' ;
15
16
import initializeStore from '../store' ;
16
17
import { getContentSearchConfigUrl } from '../search-manager/data/api' ;
17
18
import mockResult from '../search-modal/__mocks__/search-result.json' ;
18
19
import mockEmptyResult from '../search-modal/__mocks__/empty-search-result.json' ;
19
- import { getContentLibraryApiUrl , type ContentLibrary } from './data/api' ;
20
+ import { getContentLibraryApiUrl , getXBlockFieldsApiUrl , type ContentLibrary } from './data/api' ;
20
21
import { LibraryLayout } from '.' ;
21
22
22
23
let store ;
@@ -61,16 +62,17 @@ const returnEmptyResult = (_url, req) => {
61
62
const returnLowNumberResults = ( _url , req ) => {
62
63
const requestData = JSON . parse ( req . body ?. toString ( ) ?? '' ) ;
63
64
const query = requestData ?. queries [ 0 ] ?. q ?? '' ;
65
+ const newMockResult = { ...mockResult } ;
64
66
// We have to replace the query (search keywords) in the mock results with the actual query,
65
67
// because otherwise we may have an inconsistent state that causes more queries and unexpected results.
66
- mockResult . results [ 0 ] . query = query ;
68
+ newMockResult . results [ 0 ] . query = query ;
67
69
// 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
+ newMockResult . results [ 0 ] . hits = mockResult . results [ 0 ] ?. hits . slice ( 0 , 2 ) ;
71
+ newMockResult . results [ 0 ] . estimatedTotalHits = 2 ;
70
72
// And fake the required '_formatted' fields; it contains the highlighting <mark>...</mark> around matched words
71
73
// eslint-disable-next-line no-underscore-dangle, no-param-reassign
72
- mockResult . results [ 0 ] ?. hits . forEach ( ( hit ) => { hit . _formatted = { ...hit } ; } ) ;
73
- return mockResult ;
74
+ newMockResult . results [ 0 ] ?. hits . forEach ( ( hit ) => { hit . _formatted = { ...hit } ; } ) ;
75
+ return newMockResult ;
74
76
} ;
75
77
76
78
const libraryData : ContentLibrary = {
@@ -97,6 +99,13 @@ const libraryData: ContentLibrary = {
97
99
updated : '2024-07-20' ,
98
100
} ;
99
101
102
+ const xBlockFields = {
103
+ display_name : 'Test HTML Block' ,
104
+ metadata : {
105
+ display_name : 'Test HTML Block' ,
106
+ } ,
107
+ } ;
108
+
100
109
const clipboardBroadcastChannelMock = {
101
110
postMessage : jest . fn ( ) ,
102
111
close : jest . fn ( ) ,
@@ -158,6 +167,19 @@ describe('<LibraryAuthoringPage />', () => {
158
167
queryClient . clear ( ) ;
159
168
} ) ;
160
169
170
+ const renderLibraryPage = async ( ) => {
171
+ mockUseParams . mockReturnValue ( { libraryId : libraryData . id } ) ;
172
+ axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
173
+
174
+ const result = render ( < RootWrapper /> ) ;
175
+
176
+ // Ensure the search endpoint is called:
177
+ // Call 1: To fetch searchable/filterable/sortable library data
178
+ await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 1 , searchEndpoint , 'post' ) ; } ) ;
179
+
180
+ return result ;
181
+ } ;
182
+
161
183
it ( 'shows the spinner before the query is complete' , ( ) => {
162
184
mockUseParams . mockReturnValue ( { libraryId : '1' } ) ;
163
185
// @ts -ignore Use unresolved promise to keep the Loading visible
@@ -185,12 +207,9 @@ describe('<LibraryAuthoringPage />', () => {
185
207
} ) ;
186
208
187
209
it ( 'show library data' , async ( ) => {
188
- mockUseParams . mockReturnValue ( { libraryId : libraryData . id } ) ;
189
- axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
190
-
191
210
const {
192
211
getByRole, getAllByText, getByText, queryByText, findByText, findAllByText,
193
- } = render ( < RootWrapper /> ) ;
212
+ } = await renderLibraryPage ( ) ;
194
213
195
214
await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 1 , searchEndpoint , 'post' ) ; } ) ;
196
215
@@ -263,10 +282,7 @@ describe('<LibraryAuthoringPage />', () => {
263
282
} ) ;
264
283
265
284
it ( 'show new content button' , async ( ) => {
266
- mockUseParams . mockReturnValue ( { libraryId : libraryData . id } ) ;
267
- axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
268
-
269
- render ( < RootWrapper /> ) ;
285
+ await renderLibraryPage ( ) ;
270
286
271
287
expect ( await screen . findByRole ( 'heading' ) ) . toBeInTheDocument ( ) ;
272
288
expect ( screen . getByRole ( 'button' , { name : / n e w / i } ) ) . toBeInTheDocument ( ) ;
@@ -322,10 +338,7 @@ describe('<LibraryAuthoringPage />', () => {
322
338
} ) ;
323
339
324
340
it ( 'should open and close new content sidebar' , async ( ) => {
325
- mockUseParams . mockReturnValue ( { libraryId : libraryData . id } ) ;
326
- axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
327
-
328
- render ( < RootWrapper /> ) ;
341
+ await renderLibraryPage ( ) ;
329
342
330
343
expect ( await screen . findByRole ( 'heading' ) ) . toBeInTheDocument ( ) ;
331
344
expect ( screen . queryByText ( / a d d c o n t e n t / i) ) . not . toBeInTheDocument ( ) ;
@@ -342,10 +355,7 @@ describe('<LibraryAuthoringPage />', () => {
342
355
} ) ;
343
356
344
357
it ( 'should open Library Info by default' , async ( ) => {
345
- mockUseParams . mockReturnValue ( { libraryId : libraryData . id } ) ;
346
- axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
347
-
348
- render ( < RootWrapper /> ) ;
358
+ await renderLibraryPage ( ) ;
349
359
350
360
expect ( await screen . findByText ( 'Content library' ) ) . toBeInTheDocument ( ) ;
351
361
expect ( ( await screen . findAllByText ( libraryData . title ) ) [ 0 ] ) . toBeInTheDocument ( ) ;
@@ -361,10 +371,7 @@ describe('<LibraryAuthoringPage />', () => {
361
371
} ) ;
362
372
363
373
it ( 'should close and open Library Info' , async ( ) => {
364
- mockUseParams . mockReturnValue ( { libraryId : libraryData . id } ) ;
365
- axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
366
-
367
- render ( < RootWrapper /> ) ;
374
+ await renderLibraryPage ( ) ;
368
375
369
376
expect ( await screen . findByText ( 'Content library' ) ) . toBeInTheDocument ( ) ;
370
377
expect ( ( await screen . findAllByText ( libraryData . title ) ) [ 0 ] ) . toBeInTheDocument ( ) ;
@@ -389,14 +396,9 @@ describe('<LibraryAuthoringPage />', () => {
389
396
} ) ;
390
397
391
398
it ( 'show the "View All" button when viewing library with many components' , async ( ) => {
392
- mockUseParams . mockReturnValue ( { libraryId : libraryData . id } ) ;
393
- axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
394
-
395
399
const {
396
400
getByRole, getByText, queryByText, getAllByText, findAllByText,
397
- } = render ( < RootWrapper /> ) ;
398
-
399
- await waitFor ( ( ) => { expect ( fetchMock ) . toHaveFetchedTimes ( 1 , searchEndpoint , 'post' ) ; } ) ;
401
+ } = await renderLibraryPage ( ) ;
400
402
401
403
expect ( getByText ( 'Content library' ) ) . toBeInTheDocument ( ) ;
402
404
expect ( ( await findAllByText ( libraryData . title ) ) [ 0 ] ) . toBeInTheDocument ( ) ;
@@ -456,13 +458,9 @@ describe('<LibraryAuthoringPage />', () => {
456
458
} ) ;
457
459
458
460
it ( 'sort library components' , async ( ) => {
459
- mockUseParams . mockReturnValue ( { libraryId : libraryData . id } ) ;
460
- axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
461
- fetchMock . post ( searchEndpoint , returnEmptyResult , { overwriteRoutes : true } ) ;
462
-
463
461
const {
464
462
findByTitle, getAllByText, getByRole, getByTitle,
465
- } = render ( < RootWrapper /> ) ;
463
+ } = await renderLibraryPage ( ) ;
466
464
467
465
expect ( await findByTitle ( 'Sort search results' ) ) . toBeInTheDocument ( ) ;
468
466
@@ -514,7 +512,7 @@ describe('<LibraryAuthoringPage />', () => {
514
512
515
513
// Re-selecting the previous sort option resets sort to default "Recently Modified"
516
514
await testSortOption ( 'Recently Published' , 'modified:desc' , true ) ;
517
- expect ( getAllByText ( 'Recently Modified' ) . length ) . toEqual ( 2 ) ;
515
+ expect ( getAllByText ( 'Recently Modified' ) . length ) . toEqual ( 3 ) ;
518
516
519
517
// Enter a keyword into the search box
520
518
const searchBox = getByRole ( 'searchbox' ) ;
@@ -531,6 +529,27 @@ describe('<LibraryAuthoringPage />', () => {
531
529
} ) ;
532
530
} ) ;
533
531
532
+ it ( 'should open and close the component sidebar' , async ( ) => {
533
+ const usageKey = mockResult . results [ 0 ] . hits [ 0 ] . usage_key ;
534
+ const { getAllByText, queryByTestId, queryByText } = await renderLibraryPage ( ) ;
535
+ axiosMock . onGet ( getXBlockFieldsApiUrl ( usageKey ) ) . reply ( 200 , xBlockFields ) ;
536
+
537
+ // Click on the first component
538
+ waitFor ( ( ) => expect ( queryByText ( 'Test HTML Block' ) ) . toBeInTheDocument ( ) ) ;
539
+ fireEvent . click ( getAllByText ( 'Test HTML Block' ) [ 0 ] ) ;
540
+
541
+ const sidebar = screen . getByTestId ( 'library-sidebar' ) ;
542
+
543
+ const { getByRole, getByText } = within ( sidebar ) ;
544
+
545
+ await waitFor ( ( ) => expect ( getByText ( 'Test HTML Block' ) ) . toBeInTheDocument ( ) ) ;
546
+
547
+ const closeButton = getByRole ( 'button' , { name : / c l o s e / i } ) ;
548
+ fireEvent . click ( closeButton ) ;
549
+
550
+ await waitFor ( ( ) => expect ( queryByTestId ( 'library-sidebar' ) ) . not . toBeInTheDocument ( ) ) ;
551
+ } ) ;
552
+
534
553
it ( 'filter by capa problem type' , async ( ) => {
535
554
mockUseParams . mockReturnValue ( { libraryId : libraryData . id } ) ;
536
555
axiosMock . onGet ( getContentLibraryApiUrl ( libraryData . id ) ) . reply ( 200 , libraryData ) ;
0 commit comments