Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug/storybook #1141

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Fixed

- Bug causing py-enigma code to disable stop button
- Crashing caused by excessive file sizes (#1138)

### Changed

Expand Down
14 changes: 10 additions & 4 deletions cypress/e2e/spec-wc-skulpt.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,25 +121,31 @@ describe("Running the code with skulpt", () => {
runCode("import sense_hat");
cy.get("editor-wc")
.shadow()
.find("#root")
.find(".skulptrunner")
.should("contain", "Visual output");
});

it("does not render astro pi component on page load", () => {
cy.get("editor-wc").shadow().find("#root").should("not.contain", "yaw");
cy.get("editor-wc")
.shadow()
.find(".skulptrunner")
.should("not.contain", "yaw");
});

it("renders astro pi component if sense hat imported", () => {
runCode("import sense_hat");
cy.get("editor-wc").shadow().contains("Visual output").click();
cy.get("editor-wc").shadow().find("#root").should("contain", "yaw");
cy.get("editor-wc").shadow().find(".skulptrunner").should("contain", "yaw");
});

it("does not render astro pi component if sense hat unimported", () => {
runCode("import sense_hat");
runCode("import p5");
cy.get("editor-wc").shadow().contains("Visual output").click();
cy.get("editor-wc").shadow().find("#root").should("not.contain", "yaw");
cy.get("editor-wc")
.shadow()
.find(".skulptrunner")
.should("not.contain", "yaw");
});

it("runs a simple turtle program", () => {
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "@raspberrypifoundation/editor-ui",
"version": "0.28.7",
"private": true,
"workspaces": [
"storybook"
],
"dependencies": {
"@apollo/client": "^3.7.8",
"@babel/core": "^7.17.10",
Expand All @@ -15,8 +18,8 @@
"@hello-pangea/dnd": "^16.2.0",
"@juggle/resize-observer": "^3.3.1",
"@lezer/highlight": "^1.0.0",
"@raspberrypifoundation/design-system-core": "^0.1.9",
"@raspberrypifoundation/design-system-react": "^0.1.5",
"@raspberrypifoundation/design-system-core": "^1.6.0",
"@raspberrypifoundation/design-system-react": "^1.6.0",
"@react-three/drei": "9.114.3",
"@react-three/fiber": "^8.0.13",
"@reduxjs/toolkit": "^1.6.2",
Expand Down Expand Up @@ -45,6 +48,7 @@
"js-convert-case": "^4.2.0",
"jszip": "^3.10.1",
"jszip-utils": "^0.1.0",
"material-symbols": "^0.27.0",
"mime-types": "^2.1.35",
"node-html-parser": "^6.1.5",
"oidc-client": "^1.11.5",
Expand Down
4 changes: 4 additions & 0 deletions src/assets/stylesheets/EditorPanel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@
@include font-size-2(regular);
}
}

.rpf-alert {
margin: 0;
}
2 changes: 2 additions & 0 deletions src/assets/stylesheets/ExternalStyles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
@use "../../../node_modules/react-toggle/style.css";
@use "../../../node_modules/prismjs/plugins/line-numbers/prism-line-numbers.css";
@use "../../../node_modules/prismjs/plugins/line-highlight/prism-line-highlight.css";
@use "../../../node_modules/@raspberrypifoundation/design-system-core/scss/components/alert.scss";
@use "../../../node_modules/material-symbols/sharp.scss";
2 changes: 2 additions & 0 deletions src/assets/stylesheets/Tabs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
padding-inline-end: 0;
}
}

&__tab-close-btn {
block-size: 100%;
padding: $space-0-25;
Expand Down Expand Up @@ -112,6 +113,7 @@
&__tab-panel--selected {
flex: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
}
}
Expand Down
31 changes: 29 additions & 2 deletions src/components/Editor/EditorPanel/EditorPanel.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import "../../../assets/stylesheets/EditorPanel.scss";
import React, { useRef, useEffect, useContext } from "react";
import React, { useRef, useEffect, useContext, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { updateProjectComponent } from "../../../redux/EditorSlice";
import { useCookies } from "react-cookie";
Expand All @@ -11,16 +11,20 @@ import { EditorState } from "@codemirror/state";
import { defaultKeymap, indentWithTab } from "@codemirror/commands";
import { indentationMarkers } from "@replit/codemirror-indentation-markers";
import { indentUnit } from "@codemirror/language";
import "material-symbols";

import { html } from "@codemirror/lang-html";
import { css } from "@codemirror/lang-css";
import { python } from "@codemirror/lang-python";
import { javascript } from "@codemirror/lang-javascript";

import { Alert } from "@raspberrypifoundation/design-system-react";
import { editorLightTheme } from "../../../assets/themes/editorLightTheme";
import { editorDarkTheme } from "../../../assets/themes/editorDarkTheme";
import { SettingsContext } from "../../../utils/settings";

const MAX_CHARACTERS = 8500000;

const EditorPanel = ({ extension = "html", fileName = "index" }) => {
const editor = useRef();
const project = useSelector((state) => state.editor.project);
Expand All @@ -29,6 +33,7 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const settings = useContext(SettingsContext);
const [characterLimitExceeded, setCharacterLimitExceeded] = useState(false);

const updateStoredProject = (content) => {
dispatch(
Expand Down Expand Up @@ -86,6 +91,16 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => {
customIndentUnit = " ";
}

const limitCharacters = EditorState.transactionFilter.of((transaction) => {
const newDoc = transaction.newDoc;
if (newDoc.length > MAX_CHARACTERS) {
setCharacterLimitExceeded(true);
return [];
}
setCharacterLimitExceeded(false);
return transaction;
});

const startState = EditorState.create({
doc: code,
extensions: [
Expand All @@ -98,6 +113,7 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => {
indentationMarkers(),
indentUnit.of(customIndentUnit),
EditorView.editable.of(!readOnly),
limitCharacters,
],
});

Expand All @@ -123,7 +139,18 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => {
}, [cookies]);

return (
<div className={`editor editor--${settings.fontSize}`} ref={editor}></div>
<>
<div className={`editor editor--${settings.fontSize}`} ref={editor}></div>
{characterLimitExceeded && (
<Alert
title={t("editorPanel.characterLimitError")}
type="error"
text={t("editorPanel.characterLimitExplanation", {
maxCharacters: MAX_CHARACTERS,
})}
/>
)}
</>
);
};

Expand Down
47 changes: 46 additions & 1 deletion src/components/Editor/EditorPanel/EditorPanel.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import configureStore from "redux-mock-store";
import { Provider } from "react-redux";
import { SettingsContext } from "../../../utils/settings";
import { render, screen } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import { axe, toHaveNoViolations } from "jest-axe";
import EditorPanel from "./EditorPanel";

Expand Down Expand Up @@ -88,3 +88,48 @@ describe("When read only", () => {
expect(editorInputArea).toHaveAttribute("contenteditable", "false");
});
});

describe("When excessive file content is pasted into the editor", () => {
beforeEach(() => {
renderEditorPanel({ readOnly: false });
const editorInputArea = screen.getByLabelText("editorPanel.ariaLabel");
const massiveFileContent = "mango".repeat(2000000);
fireEvent.paste(editorInputArea, {
clipboardData: {
getData: () => massiveFileContent,
},
});
});

test("It does not display the file content", () => {
expect(screen.queryByText(/mango/)).not.toBeInTheDocument();
});

test("Character limit exceeded message is displayed", () => {
expect(
screen.getByText("editorPanel.characterLimitError"),
).toBeInTheDocument();
});

test("It allows the user to input text below the limit", () => {
const editorInputArea = screen.getByLabelText("editorPanel.ariaLabel");
fireEvent.paste(editorInputArea, {
clipboardData: {
getData: () => "mango",
},
});
expect(screen.getByText("mango")).toBeInTheDocument();
});

test("It removes the character limit exceeded message when the user inputs text below the limit", () => {
const editorInputArea = screen.getByLabelText("editorPanel.ariaLabel");
fireEvent.paste(editorInputArea, {
clipboardData: {
getData: () => "mango",
},
});
expect(
screen.queryByText("editorPanel.characterLimitError"),
).not.toBeInTheDocument();
});
});
11 changes: 10 additions & 1 deletion src/hooks/useProjectPersistence.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
} from "../redux/EditorSlice";
import { showLoginPrompt, showSavePrompt } from "../utils/Notifications";

const COMBINED_FILE_SIZE_SOFT_LIMIT = 1000000;

export const useProjectPersistence = ({
user,
project = {},
Expand All @@ -18,6 +20,13 @@ export const useProjectPersistence = ({
}) => {
const dispatch = useDispatch();

const combinedFileSize = project.components?.reduce(
(sum, component) => sum + component.content.length,
0,
);
const autoSaveInterval =
combinedFileSize > COMBINED_FILE_SIZE_SOFT_LIMIT ? 10000 : 2000;

const saveToLocalStorage = (project) => {
localStorage.setItem(
project.identifier || "project",
Expand Down Expand Up @@ -90,7 +99,7 @@ export const useProjectPersistence = ({
}
}
}
}, 2000);
}, autoSaveInterval);

return () => clearTimeout(debouncer);
}, [dispatch, project, user, hasShownSavePrompt]); // eslint-disable-line react-hooks/exhaustive-deps
Expand Down
28 changes: 28 additions & 0 deletions src/hooks/useProjectPersistence.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,34 @@ describe("When logged in", () => {
});
});

test("Increases save interval for large projects", async () => {
const largeProject = {
...project,
components: [
{
name: "main",
extension: "py",
content: "mango".repeat(200001),
},
],
};
renderHook(() =>
useProjectPersistence({
user: user1,
project: largeProject,
saveTriggered: false,
}),
);
jest.advanceTimersByTime(2500);
expect(saveProject).not.toHaveBeenCalled();
jest.runAllTimers();
expect(saveProject).toHaveBeenCalledWith({
project: largeProject,
accessToken: user1.access_token,
autosave: true,
});
});

test("Saves project to database if save triggered", async () => {
renderHook(() =>
useProjectPersistence({
Expand Down
3 changes: 3 additions & 0 deletions src/utils/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ i18n
},
editorPanel: {
ariaLabel: "editor text input",
characterLimitError: "Error: Character limit reached",
characterLimitExplanation:
"Files in the editor are limited to {{maxCharacters}} characters",
viewOnly: "View only",
},
filePanel: {
Expand Down
1 change: 1 addition & 0 deletions storybook/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ module.exports = {
});
// add the app to allow alias imports
config.resolve.modules.push(path.resolve(__dirname, "../../src"));
config.resolve.modules.push("../__webpack__");
return config;
},
managerWebpack: async (config) => {
Expand Down
4 changes: 3 additions & 1 deletion storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
"version": "0.1.0",
"scripts": {
"storybook": "PORT=6006 start-storybook -p 6006 --no-manager-cache",
"build-storybook": "build-storybook"
"build-storybook": "yarn install && build-storybook"
},
"dependencies": {
"@raspberrypifoundation/design-system-core": "^1.6.0",
"@raspberrypifoundation/design-system-react": "^1.6.0",
"@reduxjs/toolkit": "^1.8.3",
"@storybook/addon-actions": "6.5.10",
"@storybook/addon-essentials": "6.5.10",
Expand Down
2 changes: 1 addition & 1 deletion storybook/stories/components/Button.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import Button from "components/Button/Button";
import Button from "../../../src/components/Button/Button";

export default {
title: "Button",
Expand Down
2 changes: 1 addition & 1 deletion storybook/stories/components/DownloadPanel.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import DownloadPanel from "components/Menus/Sidebar/DownloadPanel/DownloadPanel";
import DownloadPanel from "../../../src/components/Menus/Sidebar/DownloadPanel/DownloadPanel";
import MockStore from "../../store/MockStore";

export default {
Expand Down
2 changes: 1 addition & 1 deletion storybook/stories/components/FilePanel.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import FilePanel from "components/Menus/Sidebar/FilePanel/FilePanel";
import FilePanel from "../../../src/components/Menus/Sidebar/FilePanel/FilePanel";
import MockStore from "../../store/MockStore";

export default {
Expand Down
Loading
Loading