11import React from 'react' ;
22import { runSaga } from 'redux-saga' ;
3- import { describe , test , it , expect , beforeEach , vi , Mock } from 'vitest' ;
4- import { buildChallenge } from '@freecodecamp/challenge-builder/build' ;
5-
3+ import { describe , test , it , expect , beforeEach , vi , type Mock } from 'vitest' ;
64import { render } from '../../../../utils/test-utils' ;
75
86import { getCompletedPercentage } from '../../../utils/get-completion-percentage' ;
@@ -14,14 +12,20 @@ import {
1412 challengeMetaSelector ,
1513 challengeTestsSelector ,
1614 isBuildEnabledSelector ,
17- isBlockNewlyCompletedSelector
15+ isBlockNewlyCompletedSelector ,
16+ currentBlockIdsSelector
1817} from '../redux/selectors' ;
18+ import {
19+ completedChallengesIdsSelector ,
20+ allChallengesInfoSelector
21+ } from '../../../redux/selectors' ;
1922import { getTestRunner } from '../utils/build' ;
2023import CompletionModal , { combineFileData } from './completion-modal' ;
2124vi . mock ( '../../../analytics' ) ;
2225vi . mock ( '../../../utils/fire-confetti' ) ;
2326vi . mock ( '../../../components/Progress' ) ;
2427vi . mock ( '../redux/selectors' ) ;
28+ vi . mock ( '../../../redux/selectors' ) ;
2529vi . mock ( '../utils/build' ) ;
2630vi . mock ( '../../../utils/get-words' ) ;
2731vi . mock ( '@freecodecamp/challenge-builder/build' ) ;
@@ -32,7 +36,11 @@ const mockChallengeTestsSelector = challengeTestsSelector as Mock;
3236const mockChallengeMetaSelector = challengeMetaSelector as Mock ;
3337const mockChallengeDataSelector = challengeDataSelector as Mock ;
3438const mockIsBlockNewlyCompletedSelector = isBlockNewlyCompletedSelector as Mock ;
35- const mockBuildChallenge = buildChallenge as Mock ;
39+ const mockCurrentBlockIdsSelector = vi . mocked ( currentBlockIdsSelector ) ;
40+ const mockCompletedChallengesIdsSelector =
41+ completedChallengesIdsSelector as unknown as Mock ;
42+ const mockAllChallengesInfoSelector =
43+ allChallengesInfoSelector as unknown as Mock ;
3644const mockGetTestRunner = getTestRunner as Mock ;
3745mockBuildEnabledSelector . mockReturnValue ( true ) ;
3846mockChallengeTestsSelector . mockReturnValue ( [
@@ -44,36 +52,75 @@ mockChallengeMetaSelector.mockReturnValue({
4452mockChallengeDataSelector . mockReturnValue ( {
4553 challengeFiles : [ 'mock_challenge_files' ]
4654} ) ;
47- mockBuildChallenge . mockReturnValue ( { challengeType : 'mock_challenge_type' } ) ;
4855mockGetTestRunner . mockReturnValue ( mockTestRunner ) ;
4956
50- const completedChallengesIds = [ '1' , '3' , '5' ] ,
51- currentBlockIds = [ '1' , '3' , '5' , '7' ] ,
52- id = '7' ,
53- fakeCompletedChallengesIds = [ '1' , '3' , '5' , '7' , '8' ] ;
57+ const completedChallengesIds = [ '1' , '3' , '5' ] ;
58+ const currentBlockIds = [ '1' , '3' , '5' , '7' ] ;
59+ const id = '7' ;
60+ const fakeCompletedChallengesIds = [ '1' , '3' , '5' , '7' , '8' ] ;
5461
5562describe ( '<CompletionModal />' , ( ) => {
5663 describe ( 'fireConfetti' , ( ) => {
5764 beforeEach ( ( ) => {
5865 mockFireConfetti . mockClear ( ) ;
5966 } ) ;
60- test ( 'should fire when block is completed' , async ( ) => {
67+ test ( 'should fire when block is completed and challenge data exists ' , async ( ) => {
6168 const payload = { showCompletionModal : true } ;
69+ const challengeId = 'bd7158d8c442eddfaeb5bd18' ;
70+ const blockIds = [ 'step1' , 'step2' , 'step3' , challengeId ] ;
6271 const store = createStore ( {
6372 challenge : {
6473 modal : { completion : true } ,
6574 challengeMeta : {
66- id : 'bd7158d8c442eddfaeb5bd18' ,
67- certification : 'responsive-web-design' // Make sure the certification matches
75+ id : challengeId ,
76+ certification : 'responsive-web-design'
6877 }
6978 }
7079 } ) ;
7180 mockIsBlockNewlyCompletedSelector . mockReturnValue ( true ) ;
81+ mockChallengeMetaSelector . mockReturnValue ( {
82+ id : challengeId ,
83+ isLastChallengeInBlock : true ,
84+ challengeType : 'mock_challenge_type'
85+ } ) ;
86+ mockCurrentBlockIdsSelector . mockReturnValue ( blockIds ) ;
87+ mockCompletedChallengesIdsSelector . mockReturnValue ( [ 'step1' , 'step2' ] ) ;
88+ mockAllChallengesInfoSelector . mockReturnValue ( {
89+ challengeNodes : [ { challenge : { id : challengeId } } ] ,
90+ certificateNodes : [ ]
91+ } ) ;
7292 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
7393 // @ts -ignore
7494 await runSaga ( store , executeChallengeSaga , { payload } ) . done ;
7595 expect ( mockFireConfetti ) . toHaveBeenCalledTimes ( 1 ) ;
7696 } ) ;
97+ test ( 'should not fire when challenge data is empty (saga guard)' , async ( ) => {
98+ const payload = { showCompletionModal : true } ;
99+ const challengeId = 'bd7158d8c442eddfaeb5bd18' ;
100+ const store = createStore ( {
101+ challenge : {
102+ modal : { completion : true } ,
103+ challengeMeta : {
104+ id : challengeId ,
105+ certification : 'responsive-web-design'
106+ }
107+ }
108+ } ) ;
109+ mockIsBlockNewlyCompletedSelector . mockReturnValue ( true ) ;
110+ mockChallengeMetaSelector . mockReturnValue ( {
111+ id : challengeId ,
112+ isLastChallengeInBlock : true ,
113+ challengeType : 'mock_challenge_type'
114+ } ) ;
115+ mockAllChallengesInfoSelector . mockReturnValue ( {
116+ challengeNodes : [ ] ,
117+ certificateNodes : [ ]
118+ } ) ;
119+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
120+ // @ts -ignore
121+ await runSaga ( store , executeChallengeSaga , { payload } ) . done ;
122+ expect ( mockFireConfetti ) . toHaveBeenCalledTimes ( 0 ) ;
123+ } ) ;
77124 test ( 'should not fire when block is not completed' , async ( ) => {
78125 const payload = { showCompletionModal : true } ;
79126 const store = createStore ( {
0 commit comments