fix: show completion modal (#46589)

* fix: make ctrl/cmd + enter trigger modal

Multifile projects should be handled like legacy challenges in this
regard.

* test: check modal appears

* refactor: ignore additional solutions

* test: fix ctrl + enter tests

* fix: only run on electron

* fix: remove log

* fix: show completion modal if instructions focused

* fix: handle undefined challengeType
This commit is contained in:
Oliver Eyton-Williams
2022-06-26 19:23:46 +02:00
committed by GitHub
parent 600d37acaa
commit 8bc5b0a310
7 changed files with 69 additions and 7 deletions

View File

@@ -44,6 +44,7 @@ class EditorTabs extends Component<EditorTabsProps> {
<button
aria-expanded={visibleEditors[challengeFile.fileKey] ?? 'false'}
key={challengeFile.fileKey}
data-cy={`editor-tab-${challengeFile.fileKey}`}
onClick={() => toggleVisibleEditor(challengeFile.fileKey)}
>
{`${challengeFile.name}.${challengeFile.ext}`}{' '}

View File

@@ -40,7 +40,7 @@ import {
} from '../../../redux/prop-types';
import { editorToneOptions } from '../../../utils/tone/editor-config';
import { editorNotes } from '../../../utils/tone/editor-notes';
import { challengeTypes } from '../../../../utils/challenge-types';
import { challengeTypes, isProject } from '../../../../utils/challenge-types';
import {
canFocusEditorSelector,
challengeMetaSelector,
@@ -430,7 +430,7 @@ const Editor = (props: EditorProps): JSX.Element => {
/* eslint-disable no-bitwise */
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],
run: () => {
if (props.usesMultifileEditor) {
if (props.usesMultifileEditor && !isProject(props.challengeType)) {
if (challengeIsComplete()) {
tryToSubmitChallenge();
} else {

View File

@@ -129,7 +129,12 @@ const MultifileEditor = (props: MultifileEditorProps) => {
);
} else {
return (
<ReflexElement {...reflexProps} {...resizeProps} key={key}>
<ReflexElement
data-cy={`editor-container-${key}`}
{...reflexProps}
{...resizeProps}
key={key}
>
<Editor
canFocusOnMountRef={canFocusOnMountRef}
challengeFiles={challengeFiles}

View File

@@ -478,6 +478,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
return (
<Hotkeys
challengeType={challengeType}
editorRef={this.editorRef as React.RefObject<HTMLElement>}
executeChallenge={executeChallenge}
innerRef={this.containerRef}

View File

@@ -15,6 +15,7 @@ import {
openModal
} from '../redux';
import './hotkeys.css';
import { isProject } from '../../../../utils/challenge-types';
const mapStateToProps = createSelector(
canFocusEditorSelector,
@@ -53,6 +54,7 @@ const keyMap = {
interface HotkeysProps {
canFocusEditor: boolean;
challengeFiles: ChallengeFiles;
challengeType?: number;
children: React.ReactElement;
editorRef?: React.RefObject<HTMLElement>;
executeChallenge?: (options?: { showCompletionModal: boolean }) => void;
@@ -70,6 +72,7 @@ interface HotkeysProps {
function Hotkeys({
canFocusEditor,
challengeType,
children,
instructionsPanelRef,
editorRef,
@@ -96,7 +99,11 @@ function Hotkeys({
const testsArePassing = tests.every(test => test.pass && !test.err);
if (usesMultifileEditor) {
if (
usesMultifileEditor &&
typeof challengeType == 'number' &&
!isProject(challengeType)
) {
if (testsArePassing) {
submitChallenge();
} else {

View File

@@ -41,15 +41,15 @@ exports.challengeTypes = {
multifileCertProject
};
// (Oliver) I don't think we need this for codeally projects, so they're ignored
// here
exports.isProject = challengeType => {
if (typeof challengeType !== 'number')
throw Error('challengeType must be a number');
return (
challengeType === frontEndProject ||
challengeType === backEndProject ||
challengeType === pythonProject
challengeType === pythonProject ||
challengeType === codeAllyCert ||
challengeType === multifileCertProject
);
};

View File

@@ -136,4 +136,52 @@ describe('project submission', () => {
});
}
);
it(
'Ctrl + enter triggers the completion modal on multifile projects',
{ browser: 'electron' },
() => {
cy.fixture('../../config/curriculum.json').then(curriculum => {
const targetBlock = 'build-a-personal-portfolio-webpage-project';
const portfolioBlock = Object.values(curriculum).filter(
({ blocks }) => blocks[targetBlock]
)[0];
const { challenges, meta } = portfolioBlock.blocks[targetBlock];
const projectTitle = meta.challengeOrder[0][1];
const { block, superBlock, dashedName, solutions } = challenges.find(
({ title }) => title === projectTitle
);
const url = `/learn/${superBlock}/${block}/${dashedName}`;
cy.visit(url);
solutions[0].forEach(({ contents, fileKey }) => {
const tabSelector = `[data-cy=editor-tab-${fileKey}]`;
if (fileKey !== 'indexhtml') {
cy.get(tabSelector).click();
}
const editorContainerSelector = `[data-cy=editor-container-${fileKey}]`;
cy.get(editorContainerSelector, { timeout: 16000 })
.find(selectors.editor, { timeout: 16000 })
.click()
.focused()
.type('{ctrl+a}{del}');
// NOTE: clipboard operations are flaky in watch mode, because
// the document can lose focus
cy.window().its('navigator.clipboard').invoke('writeText', contents);
cy.document().invoke('execCommand', 'paste');
});
cy.get('[data-cy=editor-container-indexhtml', { timeout: 16000 })
.find(selectors.editor, { timeout: 16000 })
.click()
.focused()
.type('{ctrl+enter}');
// check the modal exists
cy.contains('Submit and go to next challenge');
cy.contains('Download my solution');
});
}
);
});