@@ -15,8 +15,14 @@ import generalMessages from '../messages';
15
15
import scanResultsMessages from './scan-results/messages' ;
16
16
import CourseOptimizerPage , { pollLinkCheckDuringScan } from './CourseOptimizerPage' ;
17
17
import { postLinkCheckCourseApiUrl , getLinkCheckStatusApiUrl } from './data/api' ;
18
- import { mockApiResponse , mockApiResponseForNoResultFound } from './mocks/mockApiResponse' ;
18
+ import {
19
+ mockApiResponse ,
20
+ mockApiResponseForNoResultFound ,
21
+ mockApiResponseWithPreviousRunLinks ,
22
+ mockApiResponseEmpty ,
23
+ } from './mocks/mockApiResponse' ;
19
24
import * as thunks from './data/thunks' ;
25
+ import { useWaffleFlags } from '../data/apiHooks' ;
20
26
21
27
let store ;
22
28
let axiosMock ;
@@ -29,6 +35,19 @@ jest.mock('../generic/model-store', () => ({
29
35
} ) ,
30
36
} ) ) ;
31
37
38
+ // Mock the waffle flags hook
39
+ jest . mock ( '../data/apiHooks' , ( ) => ( {
40
+ useWaffleFlags : jest . fn ( ( ) => ( {
41
+ enableCourseOptimizerCheckPrevRunLinks : false ,
42
+ } ) ) ,
43
+ } ) ) ;
44
+
45
+ jest . mock ( '../generic/model-store' , ( ) => ( {
46
+ useModel : jest . fn ( ) . mockReturnValue ( {
47
+ name : 'About Node JS' ,
48
+ } ) ,
49
+ } ) ) ;
50
+
32
51
const OptimizerPage = ( ) => (
33
52
< AppProvider store = { store } >
34
53
< IntlProvider locale = "en" messages = { { } } >
@@ -155,7 +174,7 @@ describe('CourseOptimizerPage', () => {
155
174
expect ( getByText ( messages . headingTitle . defaultMessage ) ) . toBeInTheDocument ( ) ;
156
175
fireEvent . click ( getByText ( messages . buttonTitle . defaultMessage ) ) ;
157
176
await waitFor ( ( ) => {
158
- expect ( getByText ( scanResultsMessages . noBrokenLinksCard . defaultMessage ) ) . toBeInTheDocument ( ) ;
177
+ expect ( getByText ( scanResultsMessages . noResultsFound . defaultMessage ) ) . toBeInTheDocument ( ) ;
159
178
} ) ;
160
179
} ) ;
161
180
@@ -180,7 +199,7 @@ describe('CourseOptimizerPage', () => {
180
199
} = await setupOptimizerPage ( ) ;
181
200
// Check if the modal is opened
182
201
expect ( getByText ( 'Locked' ) ) . toBeInTheDocument ( ) ;
183
- // Select the broken links checkbox
202
+ // Select the locked links checkbox
184
203
fireEvent . click ( getByLabelText ( scanResultsMessages . lockedLabel . defaultMessage ) ) ;
185
204
186
205
const collapsibleTrigger = container . querySelector ( '.collapsible-trigger' ) ;
@@ -205,7 +224,6 @@ describe('CourseOptimizerPage', () => {
205
224
expect ( getByText ( 'Broken' ) ) . toBeInTheDocument ( ) ;
206
225
// Select the broken links checkbox
207
226
fireEvent . click ( getByLabelText ( scanResultsMessages . brokenLabel . defaultMessage ) ) ;
208
-
209
227
const collapsibleTrigger = container . querySelector ( '.collapsible-trigger' ) ;
210
228
expect ( collapsibleTrigger ) . toBeInTheDocument ( ) ;
211
229
fireEvent . click ( collapsibleTrigger ) ;
@@ -317,14 +335,14 @@ describe('CourseOptimizerPage', () => {
317
335
expect ( collapsibleTrigger ) . toBeInTheDocument ( ) ;
318
336
fireEvent . click ( collapsibleTrigger ) ;
319
337
320
- // Assert that all links are displayed
338
+ // Assert that both links are displayed
321
339
await waitFor ( ( ) => {
322
340
expect ( getByText ( 'Test Broken Links' ) ) . toBeInTheDocument ( ) ;
323
341
expect ( getByText ( 'Test Manual Links' ) ) . toBeInTheDocument ( ) ;
324
342
expect ( queryByText ( 'Test Locked Links' ) ) . not . toBeInTheDocument ( ) ;
325
343
} ) ;
326
344
327
- // Click on the "Broken" chip to filter the results
345
+ // Click on the "Broken" chip to remove the broken filter (should leave only manual)
328
346
const brokenChip = getByTestId ( 'chip-brokenLinks' ) ;
329
347
fireEvent . click ( brokenChip ) ;
330
348
@@ -361,5 +379,88 @@ describe('CourseOptimizerPage', () => {
361
379
expect ( getByText ( scanResultsMessages . noResultsFound . defaultMessage ) ) . toBeInTheDocument ( ) ;
362
380
} ) ;
363
381
} ) ;
382
+
383
+ it ( 'should always show broken links section header even when no data' , async ( ) => {
384
+ axiosMock . onGet ( getLinkCheckStatusApiUrl ( courseId ) ) . reply ( 200 , mockApiResponseEmpty ) ;
385
+ const { getByText } = render ( < OptimizerPage /> ) ;
386
+
387
+ fireEvent . click ( getByText ( messages . buttonTitle . defaultMessage ) ) ;
388
+
389
+ await waitFor ( ( ) => {
390
+ expect ( getByText ( scanResultsMessages . brokenLinksHeader . defaultMessage ) ) . toBeInTheDocument ( ) ;
391
+ expect ( getByText ( scanResultsMessages . noResultsFound . defaultMessage ) ) . toBeInTheDocument ( ) ;
392
+ } ) ;
393
+ } ) ;
394
+
395
+ describe ( 'Previous Run Links Feature' , ( ) => {
396
+ beforeEach ( ( ) => {
397
+ // Enable the waffle flag for previous run links
398
+ useWaffleFlags . mockReturnValue ( {
399
+ enableCourseOptimizerCheckPrevRunLinks : true ,
400
+ } ) ;
401
+ } ) ;
402
+
403
+ afterEach ( ( ) => {
404
+ // Reset to default (disabled)
405
+ useWaffleFlags . mockReturnValue ( {
406
+ enableCourseOptimizerCheckPrevRunLinks : false ,
407
+ } ) ;
408
+ } ) ;
409
+
410
+ it ( 'should show previous run links section when waffle flag is enabled and links exist' , async ( ) => {
411
+ axiosMock . onGet ( getLinkCheckStatusApiUrl ( courseId ) ) . reply ( 200 , mockApiResponseWithPreviousRunLinks ) ;
412
+ const { getByText } = render ( < OptimizerPage /> ) ;
413
+
414
+ fireEvent . click ( getByText ( messages . buttonTitle . defaultMessage ) ) ;
415
+
416
+ await waitFor ( ( ) => {
417
+ expect ( getByText ( scanResultsMessages . linkToPrevCourseRun . defaultMessage ) ) . toBeInTheDocument ( ) ;
418
+ } ) ;
419
+ } ) ;
420
+
421
+ it ( 'should show no results found for previous run links when flag is enabled but no links exist' , async ( ) => {
422
+ axiosMock . onGet ( getLinkCheckStatusApiUrl ( courseId ) ) . reply ( 200 , mockApiResponseForNoResultFound ) ;
423
+ const { getByText, getAllByText } = render ( < OptimizerPage /> ) ;
424
+
425
+ fireEvent . click ( getByText ( messages . buttonTitle . defaultMessage ) ) ;
426
+
427
+ await waitFor ( ( ) => {
428
+ expect ( getByText ( scanResultsMessages . linkToPrevCourseRun . defaultMessage ) ) . toBeInTheDocument ( ) ;
429
+ // Should show "No results found" for previous run section
430
+ const noResultsElements = getAllByText ( scanResultsMessages . noResultsFound . defaultMessage ) ;
431
+ expect ( noResultsElements . length ) . toBeGreaterThan ( 0 ) ;
432
+ } ) ;
433
+ } ) ;
434
+
435
+ it ( 'should not show previous run links section when waffle flag is disabled' , async ( ) => {
436
+ // Disable the flag
437
+ useWaffleFlags . mockReturnValue ( {
438
+ enableCourseOptimizerCheckPrevRunLinks : false ,
439
+ } ) ;
440
+
441
+ axiosMock . onGet ( getLinkCheckStatusApiUrl ( courseId ) ) . reply ( 200 , mockApiResponseWithPreviousRunLinks ) ;
442
+ const { getByText, queryByText } = render ( < OptimizerPage /> ) ;
443
+
444
+ fireEvent . click ( getByText ( messages . buttonTitle . defaultMessage ) ) ;
445
+
446
+ await waitFor ( ( ) => {
447
+ expect ( queryByText ( scanResultsMessages . linkToPrevCourseRun . defaultMessage ) ) . not . toBeInTheDocument ( ) ;
448
+ } ) ;
449
+ } ) ;
450
+
451
+ it ( 'should handle previous run links in course updates and custom pages' , async ( ) => {
452
+ axiosMock . onGet ( getLinkCheckStatusApiUrl ( courseId ) ) . reply ( 200 , mockApiResponseWithPreviousRunLinks ) ;
453
+ const { getByText, container } = render ( < OptimizerPage /> ) ;
454
+
455
+ fireEvent . click ( getByText ( messages . buttonTitle . defaultMessage ) ) ;
456
+
457
+ await waitFor ( ( ) => {
458
+ expect ( getByText ( scanResultsMessages . linkToPrevCourseRun . defaultMessage ) ) . toBeInTheDocument ( ) ;
459
+
460
+ const prevRunSections = container . querySelectorAll ( '.scan-results' ) ;
461
+ expect ( prevRunSections . length ) . toBeGreaterThan ( 1 ) ;
462
+ } ) ;
463
+ } ) ;
464
+ } ) ;
364
465
} ) ;
365
466
} ) ;
0 commit comments