fix: handle challenge resets through redux (#43843)

Instead of relying on heuristics like "does the current content
differ from the initial content?" this just resets if the reset button
has been pressed.
This commit is contained in:
Oliver Eyton-Williams
2021-10-18 13:58:06 +02:00
committed by GitHub
parent 91717384fd
commit 6a5f586f73
3 changed files with 34 additions and 17 deletions

View File

@ -38,7 +38,9 @@ import {
updateFile, updateFile,
challengeTestsSelector, challengeTestsSelector,
submitChallenge, submitChallenge,
initTests initTests,
isResettingSelector,
stopResetting
} from '../redux'; } from '../redux';
import './editor.css'; import './editor.css';
@ -61,11 +63,13 @@ interface EditorProps {
initTests: (tests: Test[]) => void; initTests: (tests: Test[]) => void;
initialTests: Test[]; initialTests: Test[];
isProjectStep: boolean; isProjectStep: boolean;
isResetting: boolean;
output: string[]; output: string[];
resizeProps: ResizePropsType; resizeProps: ResizePropsType;
saveEditorContent: () => void; saveEditorContent: () => void;
setEditorFocusability: (isFocusable: boolean) => void; setEditorFocusability: (isFocusable: boolean) => void;
submitChallenge: () => void; submitChallenge: () => void;
stopResetting: () => void;
tests: Test[]; tests: Test[];
theme: string; theme: string;
title: string; title: string;
@ -104,16 +108,19 @@ const mapStateToProps = createSelector(
canFocusEditorSelector, canFocusEditorSelector,
consoleOutputSelector, consoleOutputSelector,
isDonationModalOpenSelector, isDonationModalOpenSelector,
isResettingSelector,
userSelector, userSelector,
challengeTestsSelector, challengeTestsSelector,
( (
canFocus: boolean, canFocus: boolean,
output: string[], output: string[],
open, open,
isResetting: boolean,
{ theme = 'default' }: { theme: string }, { theme = 'default' }: { theme: string },
tests: [{ text: string; testString: string }] tests: [{ text: string; testString: string }]
) => ({ ) => ({
canFocus: open ? false : canFocus, canFocus: open ? false : canFocus,
isResetting,
output, output,
theme, theme,
tests tests
@ -128,7 +135,8 @@ const mapDispatchToProps = {
setEditorFocusability, setEditorFocusability,
updateFile, updateFile,
submitChallenge, submitChallenge,
initTests initTests,
stopResetting
}; };
const modeMap = { const modeMap = {
@ -281,7 +289,7 @@ const Editor = (props: EditorProps): JSX.Element => {
// Updates the model if the contents has changed. This is only necessary for // Updates the model if the contents has changed. This is only necessary for
// changes coming from outside the editor (such as code resets). // changes coming from outside the editor (such as code resets).
const updateEditorValues = () => { const resetEditorValues = () => {
const { challengeFiles, fileKey } = props; const { challengeFiles, fileKey } = props;
const { model } = data; const { model } = data;
@ -290,9 +298,6 @@ const Editor = (props: EditorProps): JSX.Element => {
)?.contents; )?.contents;
if (model?.getValue() !== newContents) { if (model?.getValue() !== newContents) {
model?.setValue(newContents ?? ''); model?.setValue(newContents ?? '');
return true;
} else {
return false;
} }
}; };
@ -1001,21 +1006,24 @@ const Editor = (props: EditorProps): JSX.Element => {
// editor. // editor.
const { editor } = data; const { editor } = data;
const hasChangedContents = updateEditorValues(); if (props.isResetting) {
if (hasChangedContents && hasEditableRegion()) { // NOTE: this looks a lot like a race condition, since stopResetting gets
initializeDescriptionAndOutputWidgets(); // called in each editor and changes isResetting. However, all open editor
updateDescriptionZone(); // are rendered in a batch (before stopResetting talks to redux), so they
updateOutputZone(); // all get to this point. Also stopResetting is idempotent, so it doesn't
} // matter that each editor calls it.
props.stopResetting();
if (hasChangedContents) { resetEditorValues();
focusIfTargetEditor(); focusIfTargetEditor();
} }
if (props.initialTests) initTests(props.initialTests); if (props.initialTests) initTests(props.initialTests);
if (hasEditableRegion() && editor) { if (hasEditableRegion() && editor) {
if (hasChangedContents) { if (props.isResetting) {
initializeDescriptionAndOutputWidgets();
updateDescriptionZone();
updateOutputZone();
showEditableRegion(editor); showEditableRegion(editor);
} }
// resetting test output // resetting test output
@ -1048,7 +1056,7 @@ const Editor = (props: EditorProps): JSX.Element => {
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.challengeFiles]); }, [props.challengeFiles, props.isResetting]);
useEffect(() => { useEffect(() => {
const { output } = props; const { output } = props;
const editableRegion = getEditableRegionFromRedux(); const editableRegion = getEditableRegionFromRedux();

View File

@ -36,6 +36,7 @@ export const actionTypes = createTypes(
'checkChallenge', 'checkChallenge',
'executeChallenge', 'executeChallenge',
'resetChallenge', 'resetChallenge',
'stopResetting',
'submitChallenge', 'submitChallenge',
'moveToTab', 'moveToTab',

View File

@ -33,6 +33,7 @@ const initialState = {
hasCompletedBlock: false, hasCompletedBlock: false,
isCodeLocked: false, isCodeLocked: false,
isBuildEnabled: true, isBuildEnabled: true,
isResetting: false,
logsOut: [], logsOut: [],
modal: { modal: {
completion: false, completion: false,
@ -117,6 +118,7 @@ export const challengeMounted = createAction(actionTypes.challengeMounted);
export const checkChallenge = createAction(actionTypes.checkChallenge); export const checkChallenge = createAction(actionTypes.checkChallenge);
export const executeChallenge = createAction(actionTypes.executeChallenge); export const executeChallenge = createAction(actionTypes.executeChallenge);
export const resetChallenge = createAction(actionTypes.resetChallenge); export const resetChallenge = createAction(actionTypes.resetChallenge);
export const stopResetting = createAction(actionTypes.stopResetting);
export const submitChallenge = createAction(actionTypes.submitChallenge); export const submitChallenge = createAction(actionTypes.submitChallenge);
export const moveToTab = createAction(actionTypes.moveToTab); export const moveToTab = createAction(actionTypes.moveToTab);
@ -146,6 +148,7 @@ export const isCompletionModalOpenSelector = state =>
export const isHelpModalOpenSelector = state => state[ns].modal.help; export const isHelpModalOpenSelector = state => state[ns].modal.help;
export const isVideoModalOpenSelector = state => state[ns].modal.video; export const isVideoModalOpenSelector = state => state[ns].modal.video;
export const isResetModalOpenSelector = state => state[ns].modal.reset; export const isResetModalOpenSelector = state => state[ns].modal.reset;
export const isResettingSelector = state => state[ns].isResetting;
export const isBuildEnabledSelector = state => state[ns].isBuildEnabled; export const isBuildEnabledSelector = state => state[ns].isBuildEnabled;
export const successMessageSelector = state => state[ns].successMessage; export const successMessageSelector = state => state[ns].successMessage;
@ -295,9 +298,14 @@ export const reducer = handleActions(
text, text,
testString testString
})), })),
consoleOut: [] consoleOut: [],
isResetting: true
}; };
}, },
[actionTypes.stopResetting]: state => ({
...state,
isResetting: false
}),
[actionTypes.updateSolutionFormValues]: (state, { payload }) => ({ [actionTypes.updateSolutionFormValues]: (state, { payload }) => ({
...state, ...state,
projectFormValues: payload projectFormValues: payload