From 00093d0732f772e8b1b945bdb676e3246557d329 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Fri, 22 Jul 2022 14:11:39 +0300 Subject: [PATCH] PROD-2560 - fix issue where hints flash when you resubmit --- .../templates/Challenges/classic/editor.tsx | 14 ++- .../Challenges/classic/lower-jaw.tsx | 97 ++++++++++--------- .../src/templates/Challenges/classic/show.tsx | 6 +- .../Challenges/components/side-panel.tsx | 4 + .../Challenges/components/tool-panel.tsx | 3 + .../Challenges/redux/action-types.js | 1 + .../redux/execute-challenge-saga.js | 5 +- .../src/templates/Challenges/redux/index.js | 7 ++ 8 files changed, 85 insertions(+), 52 deletions(-) diff --git a/client/src/templates/Challenges/classic/editor.tsx b/client/src/templates/Challenges/classic/editor.tsx index 8a42878fdbf2ea..17455f4133cb37 100644 --- a/client/src/templates/Challenges/classic/editor.tsx +++ b/client/src/templates/Challenges/classic/editor.tsx @@ -55,7 +55,8 @@ import { stopResetting, isProjectPreviewModalOpenSelector, openModal, - isChallengeCompletedSelector + isChallengeCompletedSelector, + testsRunningSelector } from '../redux'; import GreenPass from '../../../assets/icons/green-pass'; import Code from '../../../assets/icons/code'; @@ -107,6 +108,7 @@ interface EditorProps { }) => void; usesMultifileEditor: boolean; isChallengeCompleted: boolean; + testsRunning: boolean; } // TODO: this is grab bag of unrelated properties. There's no need for them to @@ -136,6 +138,7 @@ const mapStateToProps = createSelector( userSelector, challengeTestsSelector, isChallengeCompletedSelector, + testsRunningSelector, ( canFocus: boolean, { challengeType }: { challengeType: number }, @@ -146,7 +149,8 @@ const mapStateToProps = createSelector( isSignedIn: boolean, { theme = Themes.Default }: { theme: Themes }, tests: [{ text: string; testString: string }], - isChallengeCompleted: boolean + isChallengeCompleted: boolean, + testsRunning: boolean ) => ({ canFocus: open ? false : canFocus, challengeType, @@ -156,7 +160,8 @@ const mapStateToProps = createSelector( output, theme, tests, - isChallengeCompleted + isChallengeCompleted, + testsRunning }) ); @@ -584,6 +589,7 @@ const Editor = (props: EditorProps): JSX.Element => { challengeHasErrors={challengeHasErrors()} tryToSubmitChallenge={tryToSubmitChallenge} isEditorInFocus={isEditorInFocus} + isRunningTests={props.testsRunning} />, outputNode, callback @@ -1089,7 +1095,7 @@ const Editor = (props: EditorProps): JSX.Element => { dataRef.current.outputNode = lowerJawElement; updateOutputZone(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.tests]); + }, [props.tests, props.testsRunning]); useEffect(() => { const editor = dataRef.current.editor; diff --git a/client/src/templates/Challenges/classic/lower-jaw.tsx b/client/src/templates/Challenges/classic/lower-jaw.tsx index 05095fd451eedb..d59aa450da2779 100644 --- a/client/src/templates/Challenges/classic/lower-jaw.tsx +++ b/client/src/templates/Challenges/classic/lower-jaw.tsx @@ -1,4 +1,10 @@ -import React, { useState, useEffect } from 'react'; +import React, { + useState, + useEffect, + useMemo, + useCallback, + useRef +} from 'react'; import { useTranslation } from 'react-i18next'; import TestFail from '../../../assets/icons/test-fail'; @@ -16,18 +22,18 @@ interface LowerJawProps { challengeHasErrors?: boolean; testsLength?: number; attemptsNumber?: number; + isRunningTests?: boolean; } const LowerJaw = ({ - openHelpModal, challengeIsCompleted, challengeHasErrors, hint, tryToExecuteChallenge, tryToSubmitChallenge, attemptsNumber, - testsLength, - isEditorInFocus + isEditorInFocus, + isRunningTests }: LowerJawProps): JSX.Element => { const [previousHint, setpreviousHint] = useState(''); const [runningTests, setRunningTests] = useState(false); @@ -37,6 +43,14 @@ const LowerJaw = ({ const { t } = useTranslation(); const submitButtonRef = React.createRef(); const testFeedbackRef = React.createRef(); + const challengeHasBeenCompletedRef = useRef(false); + + // if a challenge was ever completed keep the state as completed + if (challengeIsCompleted) { + challengeHasBeenCompletedRef.current = true; + } + // keep the value of the reference.current as a separate value for convenience + const challengeHasBeenCompleted = challengeHasBeenCompletedRef.current; useEffect(() => { if (attemptsNumber && attemptsNumber > 0) { @@ -67,7 +81,7 @@ const LowerJaw = ({ }, [challengeHasErrors, hint]); useEffect(() => { - if (challengeIsCompleted && submitButtonRef?.current) { + if (challengeHasBeenCompleted && submitButtonRef?.current) { submitButtonRef.current.focus(); setTimeout(() => { setTestBtnariaHidden(true); @@ -75,7 +89,7 @@ const LowerJaw = ({ } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [challengeIsCompleted]); + }, [challengeHasBeenCompleted]); // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { @@ -84,12 +98,23 @@ const LowerJaw = ({ } }); - const renderTestFeedbackContainer = () => { + const sentencePicker = useCallback(() => { + const sentenceArray = [ + 'learn.sorry-try-again', + 'learn.sorry-keep-trying', + 'learn.sorry-getting-there', + 'learn.sorry-hang-in-there', + 'learn.sorry-dont-giveup' + ]; + return attemptsNumber + ? sentenceArray[attemptsNumber % sentenceArray.length] + : sentenceArray[0]; + }, [attemptsNumber]); + + const feedbackContent = useMemo(() => { if (attemptsNumber === 0) { return ''; - } else if (runningTests) { - return ''; - } else if (challengeIsCompleted) { + } else if (challengeHasBeenCompleted) { const submitKeyboardInstructions = isEditorInFocus ? ( {t('aria.submit')} ) : ( @@ -142,69 +167,49 @@ const LowerJaw = ({ ); } - }; - - const sentencePicker = () => { - const sentenceArray = [ - 'learn.sorry-try-again', - 'learn.sorry-keep-trying', - 'learn.sorry-getting-there', - 'learn.sorry-hang-in-there', - 'learn.sorry-dont-giveup' - ]; - return attemptsNumber - ? sentenceArray[attemptsNumber % sentenceArray.length] - : sentenceArray[0]; - }; - - const renderHelpButton = () => { - const isAtteptsLargerThanTest = - attemptsNumber && testsLength && attemptsNumber >= testsLength; - - if (isAtteptsLargerThanTest && !challengeIsCompleted) - return ( - - ); - }; + }, [ + attemptsNumber, + challengeHasBeenCompleted, + hint, + isEditorInFocus, + isFeedbackHidden, + previousHint, + sentencePicker, + t + ]); const renderButtons = () => { return ( <>
- {renderHelpButton()}
); }; - const feedbackContent = renderTestFeedbackContainer(); - return (
- {feedbackContent && ( + {!isRunningTests && feedbackContent && (
void; openModal: (modal: string) => void; setEditorFocusability: (canFocus: boolean) => void; @@ -337,6 +340,7 @@ class ShowClassic extends Component { instructionsPanelRef={this.instructionsPanelRef} showToolPanel={showToolPanel} videoUrl={this.getVideoUrl()} + testsRunning={this.props.testsRunning} /> ); } diff --git a/client/src/templates/Challenges/components/side-panel.tsx b/client/src/templates/Challenges/components/side-panel.tsx index 7c6d8ffabf2443..0379b12cedfa98 100644 --- a/client/src/templates/Challenges/components/side-panel.tsx +++ b/client/src/templates/Challenges/components/side-panel.tsx @@ -27,6 +27,7 @@ interface SidePanelProps { instructionsPanelRef: React.RefObject; showToolPanel: boolean; tests: Test[]; + testsRunning: boolean; videoUrl: string; } @@ -38,6 +39,7 @@ export function SidePanel({ instructionsPanelRef, showToolPanel = false, tests, + testsRunning, videoUrl }: SidePanelProps): JSX.Element { const isChallengeComplete = tests.every(test => test.pass && !test.err); @@ -85,6 +87,7 @@ export function SidePanel({ guideUrl={guideUrl} videoUrl={videoUrl} challengeIsCompleted={isChallengeComplete} + isRunningTests={testsRunning} /> )} @@ -93,6 +96,7 @@ export function SidePanel({ guideUrl={guideUrl} videoUrl={videoUrl} challengeIsCompleted={isChallengeComplete} + isRunningTests={testsRunning} /> )}
diff --git a/client/src/templates/Challenges/components/tool-panel.tsx b/client/src/templates/Challenges/components/tool-panel.tsx index 6b2e8a2e60c5bd..10d406d00b3e1f 100644 --- a/client/src/templates/Challenges/components/tool-panel.tsx +++ b/client/src/templates/Challenges/components/tool-panel.tsx @@ -52,6 +52,7 @@ interface ToolPanelProps { saveChallenge: () => void; isMobile?: boolean; isSignedIn: boolean; + isRunningTests?: boolean; openHelpModal: () => void; openVideoModal: () => void; openResetModal: () => void; @@ -66,6 +67,7 @@ function ToolPanel({ saveChallenge, isMobile, isSignedIn, + isRunningTests, openHelpModal, openVideoModal, openResetModal, @@ -95,6 +97,7 @@ function ToolPanel({ onClick={handleRunTests} > {isMobile ? t('buttons.run') : t('buttons.run-test')} + {isRunningTests && ' ...'} )} {challengeIsCompleted && ( diff --git a/client/src/templates/Challenges/redux/action-types.js b/client/src/templates/Challenges/redux/action-types.js index d7488e90b0239f..ba9889b2ed1925 100644 --- a/client/src/templates/Challenges/redux/action-types.js +++ b/client/src/templates/Challenges/redux/action-types.js @@ -18,6 +18,7 @@ export const actionTypes = createTypes( 'updateTests', 'updateLogs', 'cancelTests', + 'updateTestsRunning', 'logsToConsole', diff --git a/client/src/templates/Challenges/redux/execute-challenge-saga.js b/client/src/templates/Challenges/redux/execute-challenge-saga.js index ff4465feba2512..6eb99c87a32b24 100644 --- a/client/src/templates/Challenges/redux/execute-challenge-saga.js +++ b/client/src/templates/Challenges/redux/execute-challenge-saga.js @@ -47,7 +47,8 @@ import { updateTests, openModal, isBuildEnabledSelector, - disableBuildOnError + disableBuildOnError, + updateTestsRunning } from './'; // How long before bailing out of a preview. @@ -101,6 +102,7 @@ export function* executeChallengeSaga({ payload }) { try { yield put(initLogs()); yield put(initConsole(i18next.t('learn.running-tests'))); + yield put(updateTestsRunning(true)); // reset tests to initial state const tests = (yield select(challengeTestsSelector)).map( ({ text, testString }) => ({ text, testString }) @@ -139,6 +141,7 @@ export function* executeChallengeSaga({ payload }) { } yield put(updateConsole(i18next.t('learn.tests-completed'))); yield put(logsToConsole(i18next.t('learn.console-output'))); + yield put(updateTestsRunning(false)); } catch (e) { yield put(updateConsole(e)); } finally { diff --git a/client/src/templates/Challenges/redux/index.js b/client/src/templates/Challenges/redux/index.js index dbd1eae5cdc616..b9eed0bff13c1b 100644 --- a/client/src/templates/Challenges/redux/index.js +++ b/client/src/templates/Challenges/redux/index.js @@ -36,6 +36,7 @@ const initialState = { isBuildEnabled: true, isResetting: false, logsOut: [], + testsRunning: false, modal: { completion: false, help: false, @@ -80,6 +81,7 @@ export const createQuestion = createAction(actionTypes.createQuestion); export const initTests = createAction(actionTypes.initTests); export const updateTests = createAction(actionTypes.updateTests); export const cancelTests = createAction(actionTypes.cancelTests); +export const updateTestsRunning = createAction(actionTypes.updateTestsRunning); export const initConsole = createAction(actionTypes.initConsole); export const initLogs = createAction(actionTypes.initLogs); @@ -134,6 +136,7 @@ export const challengeFilesSelector = state => state[ns].challengeFiles; export const challengeMetaSelector = state => state[ns].challengeMeta; export const challengeTestsSelector = state => state[ns].challengeTests; export const consoleOutputSelector = state => state[ns].consoleOut; +export const testsRunningSelector = state => state[ns].testsRunning; export const completedChallengesIds = state => completedChallengesSelector(state).map(node => node.id); export const isChallengeCompletedSelector = state => { @@ -261,6 +264,10 @@ export const reducer = handleActions( ...state, consoleOut: state.consoleOut.concat(payload) }), + [actionTypes.updateTestsRunning]: (state, { payload }) => ({ + ...state, + testsRunning: payload + }), [actionTypes.initLogs]: state => ({ ...state, logsOut: []