mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-19 04:00:56 -04:00
moved fireConfetti to execute-challenge-saga (#51938)
This commit is contained in:
@@ -1,15 +1,48 @@
|
||||
import React from 'react';
|
||||
import { runSaga } from 'redux-saga';
|
||||
import { render } from '../../../../utils/test-utils';
|
||||
|
||||
import { getCompletedPercentage } from '../../../utils/get-completion-percentage';
|
||||
import { fireConfetti } from '../../../utils/fire-confetti';
|
||||
import { createStore } from '../../../redux/create-store';
|
||||
import { executeChallengeSaga } from '../redux/execute-challenge-saga';
|
||||
import {
|
||||
challengeDataSelector,
|
||||
challengeMetaSelector,
|
||||
challengeTestsSelector,
|
||||
isBuildEnabledSelector,
|
||||
isBlockNewlyCompletedSelector
|
||||
} from '../redux/selectors';
|
||||
import { buildChallenge, getTestRunner } from '../utils/build';
|
||||
import CompletionModal from './completion-modal';
|
||||
|
||||
jest.mock('../../../analytics');
|
||||
jest.mock('../../../utils/fire-confetti');
|
||||
jest.mock('../../../components/ProgressBar');
|
||||
jest.mock('../redux/selectors');
|
||||
jest.mock('../utils/build');
|
||||
const mockFireConfetti = fireConfetti as jest.Mock;
|
||||
const mockTestRunner = jest.fn().mockReturnValue({ pass: true });
|
||||
const mockBuildEnabledSelector = isBuildEnabledSelector as jest.Mock;
|
||||
const mockChallengeTestsSelector = challengeTestsSelector as jest.Mock;
|
||||
const mockChallengeMetaSelector = challengeMetaSelector as jest.Mock;
|
||||
const mockChallengeDataSelector = challengeDataSelector as jest.Mock;
|
||||
const mockIsBlockNewlyCompletedSelector =
|
||||
isBlockNewlyCompletedSelector as jest.Mock;
|
||||
const mockBuildChallenge = buildChallenge as jest.Mock;
|
||||
const mockGetTestRunner = getTestRunner as jest.Mock;
|
||||
mockBuildEnabledSelector.mockReturnValue(true);
|
||||
mockChallengeTestsSelector.mockReturnValue([
|
||||
{ text: 'Test 1', testString: 'mock test code' }
|
||||
]);
|
||||
mockChallengeMetaSelector.mockReturnValue({
|
||||
challengeType: 'mock_challenge_type'
|
||||
});
|
||||
mockChallengeDataSelector.mockReturnValue({
|
||||
challengeFiles: ['mock_challenge_files']
|
||||
});
|
||||
mockBuildChallenge.mockReturnValue({ challengeType: 'mock_challenge_type' });
|
||||
mockGetTestRunner.mockReturnValue(mockTestRunner);
|
||||
|
||||
const completedChallengesIds = ['1', '3', '5'],
|
||||
currentBlockIds = ['1', '3', '5', '7'],
|
||||
@@ -21,7 +54,41 @@ describe('<CompletionModal />', () => {
|
||||
beforeEach(() => {
|
||||
mockFireConfetti.mockClear();
|
||||
});
|
||||
test('should fire if certification project has been completed', () => {
|
||||
test('should fire when block is completed', async () => {
|
||||
const payload = { showCompletionModal: true };
|
||||
const store = createStore({
|
||||
challenge: {
|
||||
modal: { completion: true },
|
||||
challengeMeta: {
|
||||
id: 'bd7158d8c442eddfaeb5bd18',
|
||||
certification: 'responsive-web-design' // Make sure the certification matches
|
||||
}
|
||||
}
|
||||
});
|
||||
mockIsBlockNewlyCompletedSelector.mockReturnValue(true);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
await runSaga(store, executeChallengeSaga, { payload }).done;
|
||||
expect(mockFireConfetti).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
test('should not fire when block is not completed', async () => {
|
||||
const payload = { showCompletionModal: true };
|
||||
const store = createStore({
|
||||
challenge: {
|
||||
modal: { completion: true },
|
||||
challengeMeta: {
|
||||
id: 'bd7158d8c442eddfaeb5bd18',
|
||||
certification: 'responsive-web-design' // Make sure the certification matches
|
||||
}
|
||||
}
|
||||
});
|
||||
mockIsBlockNewlyCompletedSelector.mockReturnValue(false);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
await runSaga(store, executeChallengeSaga, { payload }).done;
|
||||
expect(mockFireConfetti).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
test('should not fire if certification project has been completed', () => {
|
||||
const store = createStore({
|
||||
challenge: {
|
||||
modal: { completion: true },
|
||||
@@ -31,12 +98,10 @@ describe('<CompletionModal />', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
render(<CompletionModal />, store);
|
||||
|
||||
expect(mockFireConfetti).toHaveBeenCalledTimes(1);
|
||||
expect(mockFireConfetti).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('should NOT fire if the challenge is not a project', () => {
|
||||
const store = createStore({
|
||||
challenge: {
|
||||
|
||||
@@ -28,8 +28,6 @@ import ProgressBar from '../../../components/ProgressBar';
|
||||
import GreenPass from '../../../assets/icons/green-pass';
|
||||
|
||||
import './completion-modal.css';
|
||||
import { fireConfetti } from '../../../utils/fire-confetti';
|
||||
import { certsToProjects } from '../../../../config/cert-and-project-map';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
challengeFilesSelector,
|
||||
@@ -81,11 +79,6 @@ interface CompletionModalState {
|
||||
downloadURL: null | string;
|
||||
}
|
||||
|
||||
const isCertificationProject = (id: string) =>
|
||||
Object.values(certsToProjects).some(cert =>
|
||||
cert.some(project => project.id === id)
|
||||
);
|
||||
|
||||
class CompletionModal extends Component<
|
||||
CompletionModalsProps,
|
||||
CompletionModalState
|
||||
@@ -157,26 +150,18 @@ class CompletionModal extends Component<
|
||||
const {
|
||||
close,
|
||||
isOpen,
|
||||
id,
|
||||
isSignedIn,
|
||||
isSubmitting,
|
||||
message,
|
||||
t,
|
||||
dashedName,
|
||||
submitChallenge,
|
||||
completedChallengesIds
|
||||
submitChallenge
|
||||
} = this.props;
|
||||
|
||||
if (isOpen) {
|
||||
executeGA({ event: 'pageview', pagePath: '/completion-modal' });
|
||||
if (
|
||||
isCertificationProject(id) &&
|
||||
!completedChallengesIds.includes(id) &&
|
||||
!isSubmitting
|
||||
) {
|
||||
fireConfetti();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
data-cy='completion-modal'
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
} from '../utils/build';
|
||||
import { runPythonInFrame, mainPreviewId } from '../utils/frame';
|
||||
import { executeGA } from '../../../redux/actions';
|
||||
import { fireConfetti } from '../../../utils/fire-confetti';
|
||||
import { actionTypes } from './action-types';
|
||||
import {
|
||||
disableBuildOnError,
|
||||
@@ -53,7 +54,8 @@ import {
|
||||
challengeTestsSelector,
|
||||
isBuildEnabledSelector,
|
||||
isExecutingSelector,
|
||||
portalDocumentSelector
|
||||
portalDocumentSelector,
|
||||
isBlockNewlyCompletedSelector
|
||||
} from './selectors';
|
||||
|
||||
// How long before bailing out of a preview.
|
||||
@@ -87,7 +89,7 @@ function* executeCancellableChallengeSaga(payload) {
|
||||
yield cancel(task);
|
||||
}
|
||||
|
||||
function* executeChallengeSaga({ payload }) {
|
||||
export function* executeChallengeSaga({ payload }) {
|
||||
const isBuildEnabled = yield select(isBuildEnabledSelector);
|
||||
if (!isBuildEnabled) {
|
||||
return;
|
||||
@@ -126,8 +128,12 @@ function* executeChallengeSaga({ payload }) {
|
||||
yield put(updateTests(testResults));
|
||||
|
||||
const challengeComplete = testResults.every(test => test.pass && !test.err);
|
||||
const isBlockCompleted = yield select(isBlockNewlyCompletedSelector);
|
||||
if (challengeComplete) {
|
||||
playTone('tests-completed');
|
||||
if (isBlockCompleted) {
|
||||
fireConfetti();
|
||||
}
|
||||
} else {
|
||||
playTone('tests-failed');
|
||||
if (challengeMeta.certification === 'responsive-web-design') {
|
||||
@@ -141,6 +147,7 @@ function* executeChallengeSaga({ payload }) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (challengeComplete && payload?.showCompletionModal) {
|
||||
yield put(openModal('completion'));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user