mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-30 16:01:14 -04:00
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:
committed by
GitHub
parent
600d37acaa
commit
8bc5b0a310
@@ -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}`}{' '}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user