From 5f60026666718c64bf5ad877212e283589a3fa8c Mon Sep 17 00:00:00 2001 From: Venkataramana Devathoti <114353712+Venkat-Entropik@users.noreply.github.com> Date: Thu, 21 May 2026 16:44:14 +0530 Subject: [PATCH] fix (client): prevent completion modal in multi-file labs (#66552) Co-authored-by: Venkat Co-authored-by: sembauke --- .../templates/Challenges/classic/editor.tsx | 5 +- .../src/templates/Challenges/classic/show.tsx | 1 + .../Challenges/components/hotkeys.tsx | 6 +- e2e/hotkeys.spec.ts | 62 ++++++++++++++++++- e2e/navigation-from-last-challenge.spec.ts | 10 +-- e2e/projects.spec.ts | 8 ++- 6 files changed, 80 insertions(+), 12 deletions(-) diff --git a/client/src/templates/Challenges/classic/editor.tsx b/client/src/templates/Challenges/classic/editor.tsx index 4aa31210fd8..6788b3eee99 100644 --- a/client/src/templates/Challenges/classic/editor.tsx +++ b/client/src/templates/Challenges/classic/editor.tsx @@ -633,6 +633,7 @@ const Editor = (props: EditorProps): JSX.Element => { monaco.KeyMod.WinCtrl | monaco.KeyCode.Enter ], run: () => { + const shouldShowCompletionModal = !props.showIndependentLowerJaw; if (props.usesMultifileEditor && !isProjectBased(props.challengeType)) { if (challengeIsComplete()) { tryToSubmitChallenge(); @@ -640,7 +641,9 @@ const Editor = (props: EditorProps): JSX.Element => { tryToExecuteChallenge(); } } else { - props.executeChallenge({ showCompletionModal: true }); + props.executeChallenge({ + showCompletionModal: shouldShowCompletionModal + }); } } }); diff --git a/client/src/templates/Challenges/classic/show.tsx b/client/src/templates/Challenges/classic/show.tsx index 96e41e2c608..009fcd4fc64 100644 --- a/client/src/templates/Challenges/classic/show.tsx +++ b/client/src/templates/Challenges/classic/show.tsx @@ -445,6 +445,7 @@ function ShowClassic({ instructionsPanelRef={instructionsPanelRef} usesMultifileEditor={usesMultifileEditor} editorRef={editorRef} + showIndependentLowerJaw={showIndependentLowerJaw} > diff --git a/client/src/templates/Challenges/components/hotkeys.tsx b/client/src/templates/Challenges/components/hotkeys.tsx index a2525b6a1d3..ba9eefc48c9 100644 --- a/client/src/templates/Challenges/components/hotkeys.tsx +++ b/client/src/templates/Challenges/components/hotkeys.tsx @@ -97,6 +97,7 @@ export type HotkeysProps = Pick< openShortcutsModal: () => void; playScene?: () => void; keyboardShortcuts: boolean; + showIndependentLowerJaw?: boolean; }; function Hotkeys({ @@ -119,7 +120,8 @@ function Hotkeys({ isHelpModalOpen, isResetModalOpen, isShortcutsModalOpen, - isProjectPreviewModalOpen + isProjectPreviewModalOpen, + showIndependentLowerJaw }: HotkeysProps): JSX.Element { const submitChallenge = useSubmit(); @@ -167,7 +169,7 @@ function Hotkeys({ executeChallenge(); } } else { - executeChallenge({ showCompletionModal: true }); + executeChallenge({ showCompletionModal: !showIndependentLowerJaw }); } }, ...(keyboardShortcuts diff --git a/e2e/hotkeys.spec.ts b/e2e/hotkeys.spec.ts index fda92424aa3..2735a61b88d 100644 --- a/e2e/hotkeys.spec.ts +++ b/e2e/hotkeys.spec.ts @@ -2,7 +2,7 @@ import { test, expect, type Page } from '@playwright/test'; import translations from '../client/i18n/locales/english/translations.json'; import { authedRequest } from './utils/request'; -import { getEditors } from './utils/editor'; +import { clearEditor, getEditors } from './utils/editor'; import { alertToBeVisible } from './utils/alerts'; const links = { @@ -25,7 +25,9 @@ const links = { multipleChoiceQuestion: '/learn/a2-english-for-developers/learn-greetings-in-your-first-day-at-the-office/task-7', assignment: - '/learn/responsive-web-design-v9/review-semantic-html/review-semantic-html' + '/learn/responsive-web-design-v9/review-semantic-html/review-semantic-html', + multifileLab: + '/learn/responsive-web-design-v9/lab-debug-camperbots-profile-page/lab-debug-camperbots-profile-page' }; const titles = { @@ -37,6 +39,15 @@ const titles = { }; type PageId = keyof typeof titles; +const multifileLabSolution = `

Hello from Camperbot!

+ +

About

+ +

My name is Camperbot and I love learning new things.

+ +

Background and Interests

+

I enjoy solving puzzles.

`; + // The hotkeys are attached to specific elements, so we need to wait for the // wrapper to be focused before we can test the hotkeys. const waitUntilListening = async (page: Page) => @@ -50,6 +61,31 @@ const waitUntilHydrated = async (page: Page, pageId: PageId) => { await waitUntilListening(page); }; +const completeMultifileLabWithHotkey = async ({ + browserName, + hotkey, + page +}: { + browserName: string; + hotkey: 'Control+Enter' | 'Meta+Enter'; + page: Page; +}) => { + await page.goto(links.multifileLab); + + const editor = getEditors(page); + await editor.focus(); + await expect(editor).toBeFocused(); + await clearEditor({ page, browserName }); + await editor.fill(multifileLabSolution); + + await page.keyboard.press(hotkey); + + await expect( + page.getByTestId('independentLowerJaw-submit-button') + ).toBeVisible(); + await expect(page.getByRole('dialog')).toHaveCount(0); +}; + test.beforeAll(async ({ request }) => { await authedRequest({ request, @@ -215,3 +251,25 @@ test('User can use Cmd+Enter to submit their answer in an assignment-type challe // Completion modal shows up await expect(page.getByRole('dialog')).toBeVisible(); }); + +test('Ctrl+Enter should not open completion modal in multifile editor (uses lower jaw)', async ({ + page, + browserName +}) => { + await completeMultifileLabWithHotkey({ + browserName, + hotkey: 'Control+Enter', + page + }); +}); + +test('Cmd+Enter should not open completion modal in multifile editor (uses lower jaw)', async ({ + page, + browserName +}) => { + await completeMultifileLabWithHotkey({ + browserName, + hotkey: 'Meta+Enter', + page + }); +}); diff --git a/e2e/navigation-from-last-challenge.spec.ts b/e2e/navigation-from-last-challenge.spec.ts index 5f0db32b157..5490a23ab46 100644 --- a/e2e/navigation-from-last-challenge.spec.ts +++ b/e2e/navigation-from-last-challenge.spec.ts @@ -1,4 +1,4 @@ -import { test } from '@playwright/test'; +import { test, expect } from '@playwright/test'; import solution from './fixtures/build-a-personal-portfolio-webpage.json'; import { clearEditor, focusEditor } from './utils/editor'; import { isMacOS } from './utils/user-agent'; @@ -81,9 +81,11 @@ test.describe('Should take you to the next superblock (with editor solution)', ( await page.keyboard.press('Control+Enter'); - await page - .getByRole('button', { name: 'Submit and go to next challenge' }) - .click(); + const submitButton = page.locator( + '[data-playwright-test-label="independentLowerJaw-submit-button"]' + ); + await expect(submitButton).toBeVisible(); + await submitButton.click(); await page.waitForURL(rwdChallenge.nextUrl); }); }); diff --git a/e2e/projects.spec.ts b/e2e/projects.spec.ts index 39efa0dfe11..f30ac5e0f01 100644 --- a/e2e/projects.spec.ts +++ b/e2e/projects.spec.ts @@ -243,13 +243,13 @@ test.describe('JavaScript projects can be submitted and then viewed in /settings }); }); -test.describe('Completion modal should be shown after submitting a project', () => { +test.describe('Submit button should be shown after submitting a project', () => { test.skip( ({ browserName }) => browserName !== 'chromium', 'Only chromium allows us to use the clipboard API.' ); - test('Ctrl + enter triggers the completion modal on multifile projects', async ({ + test('Ctrl + enter triggers the submit button on multifile projects', async ({ page, context, isMobile @@ -280,7 +280,9 @@ test.describe('Completion modal should be shown after submitting a project', () await page.keyboard.press('Control+Enter'); await page - .getByRole('button', { name: 'Go to next challenge', exact: false }) + .locator( + '[data-playwright-test-label="independentLowerJaw-submit-button"]' + ) .click(); }); });