From cb360a1cc79ec5f66ca1cfc6583afe10c2a0a348 Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Mon, 16 Mar 2026 19:53:33 +0100 Subject: [PATCH 1/6] feat: add upload button to project bar --- public/translations/en.json | 1 + src/components/ProjectBar/ProjectBar.jsx | 12 +++++++++ src/components/ProjectBar/ProjectBar.test.js | 28 ++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/public/translations/en.json b/public/translations/en.json index acc3e94cb..1cb59b717 100644 --- a/public/translations/en.json +++ b/public/translations/en.json @@ -74,6 +74,7 @@ }, "header": { "download": "Download", + "upload": "Upload", "downloadFileNameDefault": "my {{project_type}} project", "editorLogoAltText": "Editor logo", "projects": "Your projects", diff --git a/src/components/ProjectBar/ProjectBar.jsx b/src/components/ProjectBar/ProjectBar.jsx index 6d4e71fce..241d202cc 100644 --- a/src/components/ProjectBar/ProjectBar.jsx +++ b/src/components/ProjectBar/ProjectBar.jsx @@ -3,9 +3,11 @@ import { useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; import SaveStatus from "../SaveStatus/SaveStatus"; import DownloadIcon from "../../assets/icons/download.svg"; +import UploadIcon from "../../assets/icons/upload.svg"; import SaveIcon from "../../assets/icons/save.svg"; import ProjectName from "../ProjectName/ProjectName"; import DownloadButton from "../DownloadButton/DownloadButton"; +import UploadButton from "../UploadButton/UploadButton"; import SaveButton from "../SaveButton/SaveButton"; import DesignSystemButton from "../DesignSystemButton/DesignSystemButton"; @@ -42,6 +44,16 @@ const ProjectBar = ({ nameEditable = true }) => {
+ {isScratchProject && ( +
+ +
+ )}
{ expect(screen.queryByText("header.download")).toBeInTheDocument(); }); + test("Upload button is not shown", () => { + expect(screen.queryByText("header.upload")).not.toBeInTheDocument(); + }); + test("Save button is not shown", () => { expect(screen.queryByText("header.save")).not.toBeInTheDocument(); }); @@ -129,6 +133,10 @@ describe("When logged in and no project identifier", () => { expect(screen.queryByText("header.download")).toBeInTheDocument(); }); + test("Upload button is not shown", () => { + expect(screen.queryByText("header.upload")).not.toBeInTheDocument(); + }); + test("Project name is shown", () => { expect(screen.queryByText(project.name)).toBeInTheDocument(); }); @@ -155,6 +163,10 @@ describe("When not logged in", () => { expect(screen.queryByText("header.download")).toBeInTheDocument(); }); + test("Upload button is not shown", () => { + expect(screen.queryByText("header.upload")).not.toBeInTheDocument(); + }); + test("Project name is shown", () => { expect(screen.queryByText(project.name)).toBeInTheDocument(); }); @@ -189,6 +201,10 @@ describe("When no project loaded", () => { expect(screen.queryByText("header.download")).not.toBeInTheDocument(); }); + test("No upload button", () => { + expect(screen.queryByText("header.upload")).not.toBeInTheDocument(); + }); + test("No save button", () => { expect(screen.queryByText("header.save")).not.toBeInTheDocument(); }); @@ -212,6 +228,10 @@ describe("When read only", () => { expect(screen.queryByTitle("header.renameProject")).not.toBeInTheDocument(); }); + test("Upload button is not shown", () => { + expect(screen.queryByText("header.upload")).not.toBeInTheDocument(); + }); + test("Save button is not shown", () => { expect(screen.queryByText("header.save")).not.toBeInTheDocument(); }); @@ -234,6 +254,14 @@ describe("When project is Scratch", () => { }); }); + test("Upload button shown", () => { + expect(screen.queryByText("header.upload")).toBeInTheDocument(); + }); + + test("Download button shown", () => { + expect(screen.queryByText("header.download")).toBeInTheDocument(); + }); + test("clicking Save sends scratch-gui-save message", () => { fireEvent.click(screen.getByRole("button", { name: "header.save" })); From 43a5668d37109af979a22082a4a2d839b5b01124 Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Tue, 17 Mar 2026 12:37:58 +0100 Subject: [PATCH 2/6] feat: hide upload button on read only --- src/components/ProjectBar/ProjectBar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ProjectBar/ProjectBar.jsx b/src/components/ProjectBar/ProjectBar.jsx index 241d202cc..36637f450 100644 --- a/src/components/ProjectBar/ProjectBar.jsx +++ b/src/components/ProjectBar/ProjectBar.jsx @@ -44,7 +44,7 @@ const ProjectBar = ({ nameEditable = true }) => {
- {isScratchProject && ( + {isScratchProject && !readOnly && (
Date: Tue, 17 Mar 2026 12:56:53 +0100 Subject: [PATCH 3/6] chore: update the agents file --- AGENTS.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 2af5e1731..3d3f60aca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,6 +56,11 @@ yarn exec cypress open `src/assets/stylesheets/InternalStyles.scss` (and external libs in `ExternalStyles.scss`); avoid `rem`, prefer `em` and `--scale-factor`. +## Translations / locale files +- Only update `public/translations/en.json` when adding or changing translation keys. +- Do not add or edit keys in other locale files under `public/translations/`; Crowdin sync overwrites them. +- Missing keys in non-en locales are intentional until Crowdin syncs; the app falls back to `en`. Do not suggest adding a new key to other locale files when it appears only in `en.json`. + ## Git & Commit Guidance - Write descriptive commit messages that explain why a change was made. - When applicable, include alternatives considered and why they were not From 14af8ea0e3fdcd54777f90b7367d72fb726ece51 Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Tue, 17 Mar 2026 15:38:37 +0100 Subject: [PATCH 4/6] refactor: create seperate scratch project bar --- src/components/Editor/Project/Project.jsx | 8 +- src/components/Editor/Project/Project.test.js | 46 +++++ src/components/ProjectBar/ProjectBar.jsx | 45 +---- src/components/ProjectBar/ProjectBar.test.js | 142 +------------ .../ProjectBar/ScratchProjectBar.jsx | 75 +++++++ .../ProjectBar/ScratchProjectBar.test.js | 188 ++++++++++++++++++ 6 files changed, 320 insertions(+), 184 deletions(-) create mode 100644 src/components/ProjectBar/ScratchProjectBar.jsx create mode 100644 src/components/ProjectBar/ScratchProjectBar.test.js diff --git a/src/components/Editor/Project/Project.jsx b/src/components/Editor/Project/Project.jsx index 91b2f6da3..6bc0e0c71 100644 --- a/src/components/Editor/Project/Project.jsx +++ b/src/components/Editor/Project/Project.jsx @@ -10,6 +10,7 @@ import "../../../assets/stylesheets/Project.scss"; import Output from "../Output/Output"; import { showSavedMessage } from "../../../utils/Notifications"; import ProjectBar from "../../ProjectBar/ProjectBar"; +import ScratchProjectBar from "../../ProjectBar/ScratchProjectBar"; import Sidebar from "../../Menus/Sidebar/Sidebar"; import EditorInput from "../EditorInput/EditorInput"; import ResizableWithHandle from "../../../utils/ResizableWithHandle"; @@ -76,7 +77,12 @@ const Project = (props) => { /> )}
- {withProjectbar && } + {withProjectbar && + (isCodeEditorScratchProject ? ( + + ) : ( + + ))} {!loading && !isCodeEditorScratchProject && (
{ expect(screen.queryByTitle("sidebar.settings")).toBeInTheDocument(); }); +describe("Project bar selection", () => { + test("renders default project bar for non-scratch projects", () => { + const middlewares = []; + const mockStore = configureStore(middlewares); + const initialState = { + editor: { + project: { components: [], project_type: "python" }, + loading: "success", + }, + auth: {}, + }; + const store = mockStore(initialState); + render( + + + + + , + ); + expect(screen.getByTestId("default-project-bar")).toBeInTheDocument(); + expect(screen.queryByTestId("scratch-project-bar")).not.toBeInTheDocument(); + }); + + test("renders scratch project bar for code_editor_scratch projects", () => { + const middlewares = []; + const mockStore = configureStore(middlewares); + const initialState = { + editor: { + project: { components: [], project_type: "code_editor_scratch" }, + loading: "success", + }, + auth: {}, + }; + const store = mockStore(initialState); + render( + + + + + , + ); + expect(screen.getByTestId("scratch-project-bar")).toBeInTheDocument(); + expect(screen.queryByTestId("default-project-bar")).not.toBeInTheDocument(); + }); +}); + test("Renders container for scratch projects", () => { const middlewares = []; const mockStore = configureStore(middlewares); diff --git a/src/components/ProjectBar/ProjectBar.jsx b/src/components/ProjectBar/ProjectBar.jsx index 36637f450..9ba0ddff8 100644 --- a/src/components/ProjectBar/ProjectBar.jsx +++ b/src/components/ProjectBar/ProjectBar.jsx @@ -3,21 +3,15 @@ import { useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; import SaveStatus from "../SaveStatus/SaveStatus"; import DownloadIcon from "../../assets/icons/download.svg"; -import UploadIcon from "../../assets/icons/upload.svg"; -import SaveIcon from "../../assets/icons/save.svg"; import ProjectName from "../ProjectName/ProjectName"; import DownloadButton from "../DownloadButton/DownloadButton"; -import UploadButton from "../UploadButton/UploadButton"; import SaveButton from "../SaveButton/SaveButton"; -import DesignSystemButton from "../DesignSystemButton/DesignSystemButton"; import "../../assets/stylesheets/ProjectBar.scss"; import { isOwner } from "../../utils/projectHelpers"; -import { useScratchSaveState } from "../../hooks/useScratchSaveState"; const ProjectBar = ({ nameEditable = true }) => { const { t } = useTranslation(); - const project = useSelector((state) => state.editor.project); const user = useSelector((state) => state.auth.user); const loading = useSelector((state) => state.editor.loading); @@ -25,35 +19,15 @@ const ProjectBar = ({ nameEditable = true }) => { const lastSavedTime = useSelector((state) => state.editor.lastSavedTime); const projectOwner = isOwner(user, project); const readOnly = useSelector((state) => state.editor.readOnly); - const isScratchProject = project?.project_type === "code_editor_scratch"; - const showScratchSaveButton = Boolean(isScratchProject && user && !readOnly); - const enableScratchSaveState = Boolean( - loading === "success" && showScratchSaveButton, - ); - const { isScratchSaving, saveScratchProject, scratchSaveLabelKey } = - useScratchSaveState({ - enabled: enableScratchSaveState, - }); - const scratchSaveLabel = t(scratchSaveLabelKey); if (loading !== "success") { return null; } return ( -
+
- {isScratchProject && !readOnly && ( -
- -
- )}
{ type="tertiary" />
- {!isScratchProject && !projectOwner && !readOnly && ( + {!projectOwner && !readOnly && (
)} - {showScratchSaveButton && ( -
- } - type="primary" - disabled={isScratchSaving} - /> -
- )} - {lastSavedTime && user && !readOnly && !isScratchProject && ( + {lastSavedTime && user && !readOnly && ( )}
diff --git a/src/components/ProjectBar/ProjectBar.test.js b/src/components/ProjectBar/ProjectBar.test.js index be1cf35f4..9a664c14b 100644 --- a/src/components/ProjectBar/ProjectBar.test.js +++ b/src/components/ProjectBar/ProjectBar.test.js @@ -1,15 +1,11 @@ import React from "react"; -import { act, fireEvent, render, screen } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import { Provider } from "react-redux"; import configureStore from "redux-mock-store"; import { MemoryRouter } from "react-router-dom"; import ProjectBar from "./ProjectBar"; -import { postMessageToScratchIframe } from "../../utils/scratchIframe"; jest.mock("axios"); -jest.mock("../../utils/scratchIframe", () => ({ - postMessageToScratchIframe: jest.fn(), -})); jest.mock("react-router-dom", () => ({ ...jest.requireActual("react-router-dom"), @@ -31,11 +27,6 @@ const user = { }, }; -const scratchProject = { - ...project, - project_type: "code_editor_scratch", -}; - const renderProjectBar = (state) => { const middlewares = []; const mockStore = configureStore(middlewares); @@ -60,23 +51,6 @@ const renderProjectBar = (state) => { ); }; -const getScratchOrigin = () => process.env.ASSETS_URL || window.location.origin; - -const dispatchScratchMessage = (type, origin = getScratchOrigin()) => { - act(() => { - window.dispatchEvent( - new MessageEvent("message", { - origin, - data: { type }, - }), - ); - }); -}; - -beforeEach(() => { - jest.clearAllMocks(); -}); - describe("When logged in and user owns project", () => { beforeEach(() => { renderProjectBar({ @@ -240,117 +214,3 @@ describe("When read only", () => { expect(screen.queryByText(/saveStatus.saved/)).not.toBeInTheDocument(); }); }); - -describe("When project is Scratch", () => { - beforeEach(() => { - postMessageToScratchIframe.mockClear(); - renderProjectBar({ - editor: { - project: scratchProject, - }, - auth: { - user, - }, - }); - }); - - test("Upload button shown", () => { - expect(screen.queryByText("header.upload")).toBeInTheDocument(); - }); - - test("Download button shown", () => { - expect(screen.queryByText("header.download")).toBeInTheDocument(); - }); - - test("clicking Save sends scratch-gui-save message", () => { - fireEvent.click(screen.getByRole("button", { name: "header.save" })); - - expect(postMessageToScratchIframe).toHaveBeenCalledTimes(1); - expect(postMessageToScratchIframe).toHaveBeenCalledWith({ - type: "scratch-gui-save", - }); - }); -}); - -describe("Additional Scratch manual save states", () => { - test("shows the saving state from the scratch save hook", () => { - renderProjectBar({ - editor: { - project: scratchProject, - }, - auth: { - user, - }, - }); - - dispatchScratchMessage("scratch-gui-saving-started"); - - expect( - screen.getByRole("button", { name: "saveStatus.saving" }), - ).toBeDisabled(); - }); - - test("shows the saved state from the scratch save hook", () => { - renderProjectBar({ - editor: { - project: scratchProject, - }, - auth: { - user, - }, - }); - - dispatchScratchMessage("scratch-gui-saving-succeeded"); - - expect( - screen.getByRole("button", { name: "saveStatus.saved" }), - ).toBeInTheDocument(); - }); - - test("does not show save for logged-out Scratch users", () => { - renderProjectBar({ - editor: { - project: scratchProject, - }, - }); - - expect(screen.queryByText("header.save")).not.toBeInTheDocument(); - expect(screen.queryByText("header.loginToSave")).not.toBeInTheDocument(); - }); - - test("shows save for logged-in non-owners", () => { - renderProjectBar({ - editor: { - project: { - ...scratchProject, - user_id: "teacher-id", - }, - }, - auth: { - user, - }, - }); - - expect( - screen.getByRole("button", { name: "header.save" }), - ).toBeInTheDocument(); - }); - - test("shows save for logged-in users without a Scratch project identifier", () => { - renderProjectBar({ - editor: { - project: { - ...scratchProject, - identifier: null, - }, - }, - auth: { - user, - }, - }); - - expect( - screen.getByRole("button", { name: "header.save" }), - ).toBeInTheDocument(); - }); -}); diff --git a/src/components/ProjectBar/ScratchProjectBar.jsx b/src/components/ProjectBar/ScratchProjectBar.jsx new file mode 100644 index 000000000..30f9ec93d --- /dev/null +++ b/src/components/ProjectBar/ScratchProjectBar.jsx @@ -0,0 +1,75 @@ +import React from "react"; +import { useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; +import DownloadIcon from "../../assets/icons/download.svg"; +import UploadIcon from "../../assets/icons/upload.svg"; +import SaveIcon from "../../assets/icons/save.svg"; +import ProjectName from "../ProjectName/ProjectName"; +import DownloadButton from "../DownloadButton/DownloadButton"; +import UploadButton from "../UploadButton/UploadButton"; +import DesignSystemButton from "../DesignSystemButton/DesignSystemButton"; + +import "../../assets/stylesheets/ProjectBar.scss"; +import { useScratchSaveState } from "../../hooks/useScratchSaveState"; + +const ScratchProjectBar = ({ nameEditable = true }) => { + const { t } = useTranslation(); + + const user = useSelector((state) => state.auth.user); + const loading = useSelector((state) => state.editor.loading); + const readOnly = useSelector((state) => state.editor.readOnly); + const showScratchSaveButton = Boolean(user && !readOnly); + const enableScratchSaveState = Boolean( + loading === "success" && showScratchSaveButton, + ); + const { isScratchSaving, saveScratchProject, scratchSaveLabelKey } = + useScratchSaveState({ + enabled: enableScratchSaveState, + }); + const scratchSaveLabel = t(scratchSaveLabelKey); + + if (loading !== "success") { + return null; + } + + return ( +
+ +
+ {!readOnly && ( +
+ +
+ )} +
+ +
+ {showScratchSaveButton && ( +
+ } + type="primary" + disabled={isScratchSaving} + /> +
+ )} +
+
+ ); +}; + +export default ScratchProjectBar; diff --git a/src/components/ProjectBar/ScratchProjectBar.test.js b/src/components/ProjectBar/ScratchProjectBar.test.js new file mode 100644 index 000000000..0fbf0f068 --- /dev/null +++ b/src/components/ProjectBar/ScratchProjectBar.test.js @@ -0,0 +1,188 @@ +import React from "react"; +import { act, fireEvent, render, screen } from "@testing-library/react"; +import { Provider } from "react-redux"; +import configureStore from "redux-mock-store"; +import { MemoryRouter } from "react-router-dom"; +import ScratchProjectBar from "./ScratchProjectBar"; +import { postMessageToScratchIframe } from "../../utils/scratchIframe"; + +jest.mock("axios"); +jest.mock("../../utils/scratchIframe", () => ({ + postMessageToScratchIframe: jest.fn(), +})); + +jest.mock("react-router-dom", () => ({ + ...jest.requireActual("react-router-dom"), + useNavigate: () => jest.fn(), +})); + +const scratchProject = { + name: "Hello world", + identifier: "hello-world-project", + components: [], + image_list: [], + user_id: "b48e70e2-d9ed-4a59-aee5-fc7cf09dbfaf", + project_type: "code_editor_scratch", +}; + +const user = { + access_token: "39a09671-be55-4847-baf5-8919a0c24a25", + profile: { + user: "b48e70e2-d9ed-4a59-aee5-fc7cf09dbfaf", + }, +}; + +const renderScratchProjectBar = (state) => { + const middlewares = []; + const mockStore = configureStore(middlewares); + const store = mockStore({ + editor: { + loading: "success", + project: {}, + ...state.editor, + }, + auth: { + user: null, + ...state.auth, + }, + }); + + render( + + + + + , + ); +}; + +const getScratchOrigin = () => process.env.ASSETS_URL || window.location.origin; + +const dispatchScratchMessage = (type, origin = getScratchOrigin()) => { + act(() => { + window.dispatchEvent( + new MessageEvent("message", { + origin, + data: { type }, + }), + ); + }); +}; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe("When project is Scratch", () => { + beforeEach(() => { + postMessageToScratchIframe.mockClear(); + renderScratchProjectBar({ + editor: { + project: scratchProject, + }, + auth: { + user, + }, + }); + }); + + test("Upload button shown", () => { + expect(screen.queryByText("header.upload")).toBeInTheDocument(); + }); + + test("Download button shown", () => { + expect(screen.queryByText("header.download")).toBeInTheDocument(); + }); + + test("clicking Save sends scratch-gui-save message", () => { + fireEvent.click(screen.getByRole("button", { name: "header.save" })); + + expect(postMessageToScratchIframe).toHaveBeenCalledTimes(1); + expect(postMessageToScratchIframe).toHaveBeenCalledWith({ + type: "scratch-gui-save", + }); + }); +}); + +describe("Additional Scratch manual save states", () => { + test("shows the saving state from the scratch save hook", () => { + renderScratchProjectBar({ + editor: { + project: scratchProject, + }, + auth: { + user, + }, + }); + + dispatchScratchMessage("scratch-gui-saving-started"); + + expect( + screen.getByRole("button", { name: "saveStatus.saving" }), + ).toBeDisabled(); + }); + + test("shows the saved state from the scratch save hook", () => { + renderScratchProjectBar({ + editor: { + project: scratchProject, + }, + auth: { + user, + }, + }); + + dispatchScratchMessage("scratch-gui-saving-succeeded"); + + expect( + screen.getByRole("button", { name: "saveStatus.saved" }), + ).toBeInTheDocument(); + }); + + test("does not show save for logged-out Scratch users", () => { + renderScratchProjectBar({ + editor: { + project: scratchProject, + }, + }); + + expect(screen.queryByText("header.save")).not.toBeInTheDocument(); + expect(screen.queryByText("header.loginToSave")).not.toBeInTheDocument(); + }); + + test("shows save for logged-in non-owners", () => { + renderScratchProjectBar({ + editor: { + project: { + ...scratchProject, + user_id: "teacher-id", + }, + }, + auth: { + user, + }, + }); + + expect( + screen.getByRole("button", { name: "header.save" }), + ).toBeInTheDocument(); + }); + + test("shows save for logged-in users without a Scratch project identifier", () => { + renderScratchProjectBar({ + editor: { + project: { + ...scratchProject, + identifier: null, + }, + }, + auth: { + user, + }, + }); + + expect( + screen.getByRole("button", { name: "header.save" }), + ).toBeInTheDocument(); + }); +}); From f1410a2914e968c4ce03873266edd653cf7ba55a Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Tue, 17 Mar 2026 16:04:42 +0100 Subject: [PATCH 5/6] test: fix broken test --- src/components/Editor/Project/Project.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/Editor/Project/Project.test.js b/src/components/Editor/Project/Project.test.js index 7799ae36a..e275a4c16 100644 --- a/src/components/Editor/Project/Project.test.js +++ b/src/components/Editor/Project/Project.test.js @@ -75,6 +75,8 @@ describe("Project bar selection", () => { editor: { project: { components: [], project_type: "python" }, loading: "success", + openFiles: [[]], + focussedFileIndices: [0], }, auth: {}, }; @@ -97,6 +99,8 @@ describe("Project bar selection", () => { editor: { project: { components: [], project_type: "code_editor_scratch" }, loading: "success", + openFiles: [[]], + focussedFileIndices: [0], }, auth: {}, }; From 826e110cea82479ee32fe2f0379e218489750d29 Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Tue, 17 Mar 2026 16:39:48 +0100 Subject: [PATCH 6/6] test: fix cypress test --- cypress/helpers/editor.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/cypress/helpers/editor.js b/cypress/helpers/editor.js index 7a872d611..2c001196c 100644 --- a/cypress/helpers/editor.js +++ b/cypress/helpers/editor.js @@ -1,24 +1,28 @@ export const getEditorShadow = () => cy.get("editor-wc").shadow(); +export const getSidebarPanel = () => + getEditorShadow().findByTestId("sidebar__panel"); + export const openSaveAndDownloadPanel = () => { getEditorShadow().findByRole("button", { name: "Download project" }).click(); - getEditorShadow() + + getSidebarPanel() .findByRole("heading", { name: "Save & download" }) .should("be.visible"); return { uploadProject: (fixturePath) => { - getEditorShadow() - .find(".download-panel__download-section") - .findByRole("button", { name: "Upload project" }) - .should("be.visible"); - getEditorShadow() - .findByTestId("upload-file-input") - .selectFile(fixturePath, { force: true }); + getSidebarPanel().within(() => { + cy.findByRole("button", { name: "Upload project" }).should( + "be.visible", + ); + cy.findByTestId("upload-file-input").selectFile(fixturePath, { + force: true, + }); + }); }, downloadProject: () => { - getEditorShadow() - .find(".download-panel__download-section") + getSidebarPanel() .findByRole("button", { name: "Download project" }) .click(); },