@@ -15,8 +15,14 @@ import generalMessages from '../messages';
1515import scanResultsMessages from './scan-results/messages' ;
1616import CourseOptimizerPage , { pollLinkCheckDuringScan } from './CourseOptimizerPage' ;
1717import { 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' ;
1924import * as thunks from './data/thunks' ;
25+ import { useWaffleFlags } from '../data/apiHooks' ;
2026
2127let store ;
2228let axiosMock ;
@@ -29,6 +35,19 @@ jest.mock('../generic/model-store', () => ({
2935 } ) ,
3036} ) ) ;
3137
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+
3251const OptimizerPage = ( ) => (
3352 < AppProvider store = { store } >
3453 < IntlProvider locale = "en" messages = { { } } >
@@ -155,7 +174,7 @@ describe('CourseOptimizerPage', () => {
155174 expect ( getByText ( messages . headingTitle . defaultMessage ) ) . toBeInTheDocument ( ) ;
156175 fireEvent . click ( getByText ( messages . buttonTitle . defaultMessage ) ) ;
157176 await waitFor ( ( ) => {
158- expect ( getByText ( scanResultsMessages . noBrokenLinksCard . defaultMessage ) ) . toBeInTheDocument ( ) ;
177+ expect ( getByText ( scanResultsMessages . noResultsFound . defaultMessage ) ) . toBeInTheDocument ( ) ;
159178 } ) ;
160179 } ) ;
161180
@@ -180,7 +199,7 @@ describe('CourseOptimizerPage', () => {
180199 } = await setupOptimizerPage ( ) ;
181200 // Check if the modal is opened
182201 expect ( getByText ( 'Locked' ) ) . toBeInTheDocument ( ) ;
183- // Select the broken links checkbox
202+ // Select the locked links checkbox
184203 fireEvent . click ( getByLabelText ( scanResultsMessages . lockedLabel . defaultMessage ) ) ;
185204
186205 const collapsibleTrigger = container . querySelector ( '.collapsible-trigger' ) ;
@@ -205,7 +224,6 @@ describe('CourseOptimizerPage', () => {
205224 expect ( getByText ( 'Broken' ) ) . toBeInTheDocument ( ) ;
206225 // Select the broken links checkbox
207226 fireEvent . click ( getByLabelText ( scanResultsMessages . brokenLabel . defaultMessage ) ) ;
208-
209227 const collapsibleTrigger = container . querySelector ( '.collapsible-trigger' ) ;
210228 expect ( collapsibleTrigger ) . toBeInTheDocument ( ) ;
211229 fireEvent . click ( collapsibleTrigger ) ;
@@ -317,14 +335,14 @@ describe('CourseOptimizerPage', () => {
317335 expect ( collapsibleTrigger ) . toBeInTheDocument ( ) ;
318336 fireEvent . click ( collapsibleTrigger ) ;
319337
320- // Assert that all links are displayed
338+ // Assert that both links are displayed
321339 await waitFor ( ( ) => {
322340 expect ( getByText ( 'Test Broken Links' ) ) . toBeInTheDocument ( ) ;
323341 expect ( getByText ( 'Test Manual Links' ) ) . toBeInTheDocument ( ) ;
324342 expect ( queryByText ( 'Test Locked Links' ) ) . not . toBeInTheDocument ( ) ;
325343 } ) ;
326344
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)
328346 const brokenChip = getByTestId ( 'chip-brokenLinks' ) ;
329347 fireEvent . click ( brokenChip ) ;
330348
@@ -361,5 +379,88 @@ describe('CourseOptimizerPage', () => {
361379 expect ( getByText ( scanResultsMessages . noResultsFound . defaultMessage ) ) . toBeInTheDocument ( ) ;
362380 } ) ;
363381 } ) ;
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+ } ) ;
364465 } ) ;
365466} ) ;
0 commit comments