From 25249d1654859ef8f9979707eff63db0547cc314 Mon Sep 17 00:00:00 2001 From: Arif Khalid <88131400+Arif-Khalid@users.noreply.github.com> Date: Tue, 8 Apr 2025 21:05:19 +0800 Subject: [PATCH] feat(client): Persist editor open tabs (#59103) Co-authored-by: sembauke --- .../src/templates/Challenges/redux/index.js | 28 ++++++++-- ...-basic-css-by-building-a-cafe-menu-15.json | 3 ++ e2e/multifile-editor.spec.ts | 54 ++++++++++++++++++- 3 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 e2e/fixtures/learn-basic-css-by-building-a-cafe-menu-15.json diff --git a/client/src/templates/Challenges/redux/index.js b/client/src/templates/Challenges/redux/index.js index b24916a75bb..7a468a7259d 100644 --- a/client/src/templates/Challenges/redux/index.js +++ b/client/src/templates/Challenges/redux/index.js @@ -150,10 +150,30 @@ export const reducer = handleActions( ? state.consoleOut : state.consoleOut.concat(payload, state.logsOut) }), - [actionTypes.initVisibleEditors]: state => ({ - ...state, - visibleEditors: { [getTargetEditor(state.challengeFiles)]: true } - }), + [actionTypes.initVisibleEditors]: state => { + let persistingVisibleEditors = {}; + const prevVisibleEditorKeys = Object.keys(state.visibleEditors); + if (prevVisibleEditorKeys.length > 1) { + // Restore states of relevant visible editors for the current challengeFiles + persistingVisibleEditors = prevVisibleEditorKeys + .filter(editorKey => { + return state.challengeFiles.find( + challengeFile => challengeFile.fileKey === editorKey + ); + }) + .reduce((visibleEditors, key) => { + visibleEditors[key] = state.visibleEditors[key]; + return visibleEditors; + }, {}); + } + return { + ...state, + visibleEditors: { + ...persistingVisibleEditors, + [getTargetEditor(state.challengeFiles)]: true + } + }; + }, [actionTypes.updateChallengeMeta]: (state, { payload }) => ({ ...state, challengeMeta: { ...payload } diff --git a/e2e/fixtures/learn-basic-css-by-building-a-cafe-menu-15.json b/e2e/fixtures/learn-basic-css-by-building-a-cafe-menu-15.json new file mode 100644 index 00000000000..f9df1cb6fce --- /dev/null +++ b/e2e/fixtures/learn-basic-css-by-building-a-cafe-menu-15.json @@ -0,0 +1,3 @@ +{ + "content": "Cafe Menu

CAMPER CAFE

Est. 2020

Coffee

" +} diff --git a/e2e/multifile-editor.spec.ts b/e2e/multifile-editor.spec.ts index bb1745490cd..15588a81064 100644 --- a/e2e/multifile-editor.spec.ts +++ b/e2e/multifile-editor.spec.ts @@ -1,6 +1,8 @@ import { test, expect } from '@playwright/test'; -import { getEditors } from './utils/editor'; +import { clearEditor, focusEditor, getEditors } from './utils/editor'; +import solution from './fixtures/learn-basic-css-by-building-a-cafe-menu-15.json'; +import { isMacOS } from './utils/user-agent'; test.beforeEach(async ({ page }) => { await page.goto( @@ -72,4 +74,54 @@ test.describe('MultifileEditor Component', () => { expect(await editorPane.getAttribute('style')).toBe(newStyle); }); + + test('Multiple open editors should remain open on moving to next challenge', async ({ + page, + isMobile, + browserName, + context + }) => { + test.skip( + browserName !== 'chromium', + 'Only chromium allows us to use the clipboard API.' + ); + await context.grantPermissions(['clipboard-read', 'clipboard-write']); + + await focusEditor({ page, isMobile }); + await clearEditor({ page, browserName }); + + await page.evaluate( + async solution => await navigator.clipboard.writeText(solution.content), + solution + ); + + if (isMacOS) { + await page.keyboard.press('Meta+v'); + } else { + await page.keyboard.press('Control+v'); + } + + const stylesEditor = page.getByRole('button', { + name: 'styles.css Editor' + }); + await stylesEditor.click(); + const editorsCurrentPage = getEditors(page); + await expect(editorsCurrentPage).toHaveCount(2); + + await page.keyboard.press('Control+Enter'); + + const submitButton = page.getByRole('button', { + name: 'Submit and go to next challenge' + }); + + // Mobile screen shifts submit button out of view and Playwright fails at scrolling with multiple editors open + if (isMobile) { + await submitButton.dispatchEvent('click'); + } else { + await submitButton.click(); + } + + const editorsNextPage = getEditors(page); + await expect(editorsNextPage).toHaveCount(2); + }); });