From aa09f157fa4dd470ddf94414c2791f2ca3df9888 Mon Sep 17 00:00:00 2001 From: "create-issue-branch[bot]" <53036503+create-issue-branch[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 11:15:00 +0000 Subject: [PATCH 01/59] Create draft PR for #936 From 8814caed99bfef8ef13c9ea187340f82396355a6 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Tue, 27 Feb 2024 12:50:27 +0000 Subject: [PATCH 02/59] default to pyodide and switch to skulpt if needed --- src/app/store.js | 2 ++ src/components/Editor/Output/Output.jsx | 14 ++++++++++++-- .../Editor/Runners/PyodideRunner/PyodideRunner.jsx | 5 +++++ .../Editor/Runners/PyodideRunner/PyodideWorker.js | 7 +++++++ src/redux/RunnerSlice.js | 13 +++++++++++++ src/redux/reducers/runnerReducers.js | 5 +++++ 6 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/redux/RunnerSlice.js create mode 100644 src/redux/reducers/runnerReducers.js diff --git a/src/app/store.js b/src/app/store.js index aa1691a78..6290ba4b1 100644 --- a/src/app/store.js +++ b/src/app/store.js @@ -1,12 +1,14 @@ import { configureStore } from "@reduxjs/toolkit"; import EditorReducer from "../redux/EditorSlice"; import InstructionsReducer from "../redux/InstructionsSlice"; +import RunnerReducer from "../redux/RunnerSlice"; import { reducer, loadUser } from "redux-oidc"; import userManager from "../utils/userManager"; const store = configureStore({ reducer: { editor: EditorReducer, + runner: RunnerReducer, instructions: InstructionsReducer, auth: reducer, }, diff --git a/src/components/Editor/Output/Output.jsx b/src/components/Editor/Output/Output.jsx index 2647359e6..c2d674635 100644 --- a/src/components/Editor/Output/Output.jsx +++ b/src/components/Editor/Output/Output.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { useSelector } from "react-redux"; import ExternalFiles from "../../ExternalFiles/ExternalFiles"; import RunnerFactory from "../Runners/RunnerFactory"; @@ -9,7 +9,17 @@ const Output = () => { const isEmbedded = useSelector((state) => state.editor.isEmbedded); const searchParams = new URLSearchParams(window.location.search); const isBrowserPreview = searchParams.get("browserPreview") === "true"; - const usePyodide = searchParams.get("pyodide") === "true"; + const pythonInterpreter = useSelector( + (state) => state.runner.pythonInterpreter, + ); + + const [usePyodide, setUsePyodide] = useState(true); + + useEffect(() => { + if (pythonInterpreter === "skulpt") { + setUsePyodide(false); + } + }, [pythonInterpreter]); return ( <> diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx index 6c74c1f35..a23ec1d41 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx @@ -20,6 +20,7 @@ import OutputViewToggle from "../PythonRunner/OutputViewToggle"; import { SettingsContext } from "../../../../utils/settings"; import RunnerControls from "../../../RunButton/RunnerControls"; import PyodideWorker from "worker-loader!./PyodideWorker.js"; +import { switchToSkulpt } from "../../../../redux/RunnerSlice"; const PyodideRunner = () => { const pyodideWorker = useMemo(() => new PyodideWorker(), []); @@ -70,6 +71,10 @@ const PyodideRunner = () => { case "handleSenseHatEvent": handleSenseHatEvent(data.type); break; + case "switchToSkulpt": + console.log("switching to skulpt"); + dispatch(switchToSkulpt()); + break; default: throw new Error(`Unsupported method: ${data.method}`); } diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js b/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js index 3e703fde2..8f7562235 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js +++ b/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js @@ -72,6 +72,13 @@ const checkIfStopped = () => { const withSupportForPackages = async (python, runPythonFn) => { const imports = await pyodide._api.pyodide_code.find_imports(python).toJs(); + console.log(imports); + const skulptOnlyModules = ["p5", "py5", "sense_hat"]; + if (imports.some((name) => skulptOnlyModules.includes(name))) { + console.log("skulpt only modules detected"); + postMessage({ method: "switchToSkulpt" }); + return; + } await Promise.all(imports.map((name) => loadDependency(name))); checkIfStopped(); diff --git a/src/redux/RunnerSlice.js b/src/redux/RunnerSlice.js new file mode 100644 index 000000000..c66cff007 --- /dev/null +++ b/src/redux/RunnerSlice.js @@ -0,0 +1,13 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { reducers } from "./reducers/runnerReducers"; + +export const RunnerSlice = createSlice({ + name: "runner", + initialState: { + pythonInterpreter: "pyodide", + }, + reducers, +}); + +export const { switchToSkulpt } = RunnerSlice.actions; +export default RunnerSlice.reducer; diff --git a/src/redux/reducers/runnerReducers.js b/src/redux/reducers/runnerReducers.js new file mode 100644 index 000000000..9f277c081 --- /dev/null +++ b/src/redux/reducers/runnerReducers.js @@ -0,0 +1,5 @@ +export const switchToSkulpt = (state) => { + state.pythonInterpreter = "skulpt"; +}; + +export const reducers = { switchToSkulpt }; From 1f0e5d06613a2ef35b372edcbbafe85328b4e1b9 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Tue, 27 Feb 2024 16:44:44 +0000 Subject: [PATCH 03/59] attempt to parse imports in the runnerfactory --- package.json | 1 + src/components/Editor/Output/Output.jsx | 20 +++--- .../Editor/Runners/RunnerFactory.jsx | 64 ++++++++++++++++++- yarn.lock | 8 +++ 4 files changed, 81 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 4e1c639b6..1a7a2d942 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "container-query-polyfill": "^1.0.2", "date-fns": "^2.29.3", "eslint-config-prettier": "^8.8.0", + "filbert": "^0.1.20", "file-saver": "^2.0.5", "fs-extra": "^9.0.1", "graphql": "^16.6.0", diff --git a/src/components/Editor/Output/Output.jsx b/src/components/Editor/Output/Output.jsx index c2d674635..65b6eaa86 100644 --- a/src/components/Editor/Output/Output.jsx +++ b/src/components/Editor/Output/Output.jsx @@ -9,17 +9,17 @@ const Output = () => { const isEmbedded = useSelector((state) => state.editor.isEmbedded); const searchParams = new URLSearchParams(window.location.search); const isBrowserPreview = searchParams.get("browserPreview") === "true"; - const pythonInterpreter = useSelector( - (state) => state.runner.pythonInterpreter, - ); + // const pythonInterpreter = useSelector( + // (state) => state.runner.pythonInterpreter, + // ); - const [usePyodide, setUsePyodide] = useState(true); + // const [usePyodide, setUsePyodide] = useState(true); - useEffect(() => { - if (pythonInterpreter === "skulpt") { - setUsePyodide(false); - } - }, [pythonInterpreter]); + // useEffect(() => { + // if (pythonInterpreter === "skulpt") { + // setUsePyodide(false); + // } + // }, [pythonInterpreter]); return ( <> @@ -27,7 +27,7 @@ const Output = () => {
{isEmbedded && !isBrowserPreview ? : null}
diff --git a/src/components/Editor/Runners/RunnerFactory.jsx b/src/components/Editor/Runners/RunnerFactory.jsx index beed3b28b..89b01b6a0 100644 --- a/src/components/Editor/Runners/RunnerFactory.jsx +++ b/src/components/Editor/Runners/RunnerFactory.jsx @@ -1,10 +1,70 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import PyodideRunner from "./PyodideRunner/PyodideRunner"; import PythonRunner from "./PythonRunner/PythonRunner"; import HtmlRunner from "./HtmlRunner/HtmlRunner"; +import { useSelector } from "react-redux"; +// import { parse } from "filbert"; -const RunnerFactory = ({ projectType, usePyodide }) => { +const SKULPT_ONLY_MODULES = ["p5", "py5", "sense_hat"]; + +const RunnerFactory = ({ projectType }) => { const Runner = () => { + const project = useSelector((state) => state.editor.project); + const [usePyodide, setUsePyodide] = useState(false); + + // useEffect(() => { + // project.components.forEach((component) => { + // if (component.type === "python") { + // // const ast = parse(component.code); + // const imports = ast.body.filter((node) => node.type === "Import"); + // const importNames = imports.map((node) => node.names[0].name); + // const hasSkulptOnlyModules = importNames.some((name) => + // SKULPT_ONLY_MODULES.includes(name), + // ); + // if (hasSkulptOnlyModules) { + // setUsePyodide(false); + // } + // } + // }); + // }, [project]); + + const getImports = (code) => { + const importRegex = + /(from\s+([a-zA-Z0-9_\.]+)(\s+import\s+([a-zA-Z0-9_\.]+))?)|(import\s+([a-zA-Z0-9_\.]+))/g; + const matches = code.match(importRegex); + const imports = matches + ? matches.map( + (match) => + match + .split(/from|import/) + .filter(Boolean) + .map((s) => s.trim())[0], + ) + : []; + return imports; + }; + + useEffect(() => { + console.log("scanning imports"); + project.components.forEach((component) => { + if (projectType === "python" && component.extension === "py") { + const imports = getImports(component.content); + console.log(imports); + // const importNames = imports.map((importArr) => importArr[1]); + const hasSkulptOnlyModules = imports.some((name) => + SKULPT_ONLY_MODULES.includes(name), + ); + if (hasSkulptOnlyModules) { + console.log("using skulpt"); + setUsePyodide(false); + } else { + console.log("using pyodide"); + setUsePyodide(true); + } + } + }); + }, [project]); + if (projectType === "html") { return HtmlRunner; } else if (usePyodide) { diff --git a/yarn.lock b/yarn.lock index f61e0f34a..66a80f77b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2629,6 +2629,7 @@ __metadata: eslint-plugin-react-hooks: ^4.2.0 eslint-plugin-testing-library: ^3.9.2 eslint-webpack-plugin: ^2.5.2 + filbert: ^0.1.20 file-loader: 6.1.1 file-saver: ^2.0.5 fs-extra: ^9.0.1 @@ -8797,6 +8798,13 @@ __metadata: languageName: node linkType: hard +"filbert@npm:^0.1.20": + version: 0.1.20 + resolution: "filbert@npm:0.1.20" + checksum: ad709094016811f8f308dcaa41d860bad181b77808577dd7872434ea43a90464a0c8114fd87a9d8e6f9797b9f005d959a7b855de1bc7316437573f02fef4f981 + languageName: node + linkType: hard + "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" From 12bdce422ccee6909324e6e89f892dbe2d9b0bcc Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Wed, 28 Feb 2024 12:07:56 +0000 Subject: [PATCH 04/59] refactoring --- package.json | 1 - .../Runners/PyodideRunner/PyodideRunner.jsx | 18 +- .../Runners/PythonRunner/PythonRunner.jsx | 493 ++---------------- .../Runners/PythonRunner/SkulptRunner.jsx | 474 +++++++++++++++++ ...honRunner.test.js => SkulptRunner.test.js} | 48 +- .../Editor/Runners/RunnerFactory.jsx | 98 ++-- yarn.lock | 8 - 7 files changed, 605 insertions(+), 535 deletions(-) create mode 100644 src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx rename src/components/Editor/Runners/PythonRunner/{PythonRunner.test.js => SkulptRunner.test.js} (97%) diff --git a/package.json b/package.json index 1a7a2d942..4e1c639b6 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "container-query-polyfill": "^1.0.2", "date-fns": "^2.29.3", "eslint-config-prettier": "^8.8.0", - "filbert": "^0.1.20", "file-saver": "^2.0.5", "fs-extra": "^9.0.1", "graphql": "^16.6.0", diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx index a23ec1d41..0f64af9a1 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx @@ -22,7 +22,7 @@ import RunnerControls from "../../../RunButton/RunnerControls"; import PyodideWorker from "worker-loader!./PyodideWorker.js"; import { switchToSkulpt } from "../../../../redux/RunnerSlice"; -const PyodideRunner = () => { +const PyodideRunner = ({ active }) => { const pyodideWorker = useMemo(() => new PyodideWorker(), []); const interruptBuffer = useRef(); const stdinBuffer = useRef(); @@ -46,6 +46,7 @@ const PyodideRunner = () => { const showVisualTab = queryParams.get("show_visual_tab") === "true"; const [hasVisual, setHasVisual] = useState(showVisualTab || senseHatAlways); const [visuals, setVisuals] = useState([]); + const [showRunner, setShowRunner] = useState(true); useEffect(() => { pyodideWorker.onmessage = ({ data }) => { @@ -82,13 +83,19 @@ const PyodideRunner = () => { }, []); useEffect(() => { - if (codeRunTriggered) { + if (codeRunTriggered && active) { handleRun(); } }, [codeRunTriggered]); useEffect(() => { - if (codeRunStopped) { + if (codeRunTriggered) { + setShowRunner(active); + } + }, [codeRunTriggered]); + + useEffect(() => { + if (codeRunStopped && active) { handleStop(); } }, [codeRunStopped]); @@ -277,7 +284,10 @@ const PyodideRunner = () => { }; return ( -
+
{isSplitView ? ( <> {hasVisual && ( diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index e2a92fc87..5ce8be3bc 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -1,462 +1,63 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import "../../../../assets/stylesheets/PythonRunner.scss"; -import React, { useContext, useEffect, useRef, useState } from "react"; -import { useSelector, useDispatch } from "react-redux"; -import { useTranslation } from "react-i18next"; -import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; -import Sk from "skulpt"; -import { useMediaQuery } from "react-responsive"; -import { - setError, - codeRunHandled, - stopDraw, - setSenseHatEnabled, - triggerDraw, -} from "../../../../redux/EditorSlice"; -import ErrorMessage from "../../ErrorMessage/ErrorMessage"; -import { createError } from "../../../../utils/apiCallHandler"; -import store from "../../../../app/store"; -import VisualOutputPane from "./VisualOutputPane"; -import OutputViewToggle from "./OutputViewToggle"; -import { SettingsContext } from "../../../../utils/settings"; -import RunnerControls from "../../../RunButton/RunnerControls"; -import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints"; +import React, { useEffect, useState } from "react"; -const externalLibraries = { - "./pygal/__init__.js": { - path: `${process.env.PUBLIC_URL}/shims/pygal/pygal.js`, - dependencies: [ - "https://cdnjs.cloudflare.com/ajax/libs/highcharts/6.0.2/highcharts.js", - "https://cdnjs.cloudflare.com/ajax/libs/highcharts/6.0.2/js/highcharts-more.js", - ], - }, - "./py5/__init__.js": { - path: `${process.env.PUBLIC_URL}/shims/processing/py5/py5-shim.js`, - dependencies: [`${process.env.PUBLIC_URL}/libraries/processing/p5/p5.js`], - }, - "./py5_imported/__init__.js": { - path: `${process.env.PUBLIC_URL}/shims/processing/py5_imported_mode/py5_imported.js`, - }, - "./py5_imported_mode.py": { - path: `${process.env.PUBLIC_URL}/shims/processing/py5_imported_mode/py5_imported_mode.py`, - }, - "./p5/__init__.js": { - path: `${process.env.PUBLIC_URL}/shims/processing/p5/p5-shim.js`, - dependencies: [`${process.env.PUBLIC_URL}/libraries/processing/p5/p5.js`], - }, - "./_internal_sense_hat/__init__.js": { - path: `${process.env.PUBLIC_URL}/shims/sense_hat/_internal_sense_hat.js`, - }, - "./sense_hat.py": { - path: `${process.env.PUBLIC_URL}/shims/sense_hat/sense_hat_blob.py`, - }, -}; +import PyodideRunner from "../PyodideRunner/PyodideRunner"; +import SkulptRunner from "./SkulptRunner"; +import { useSelector } from "react-redux"; + +const SKULPT_ONLY_MODULES = ["p5", "py5", "sense_hat"]; const PythonRunner = () => { - const projectCode = useSelector((state) => state.editor.project.components); - const projectIdentifier = useSelector( - (state) => state.editor.project.identifier, - ); - const user = useSelector((state) => state.auth.user); - const isSplitView = useSelector((state) => state.editor.isSplitView); - const isEmbedded = useSelector((state) => state.editor.isEmbedded); + const project = useSelector((state) => state.editor.project); const codeRunTriggered = useSelector( (state) => state.editor.codeRunTriggered, ); - const codeRunStopped = useSelector((state) => state.editor.codeRunStopped); - const drawTriggered = useSelector((state) => state.editor.drawTriggered); - const senseHatAlwaysEnabled = useSelector( - (state) => state.editor.senseHatAlwaysEnabled, - ); - const output = useRef(); - const dispatch = useDispatch(); - const { t } = useTranslation(); - const settings = useContext(SettingsContext); - const isMobile = useMediaQuery({ query: MOBILE_MEDIA_QUERY }); - - const queryParams = new URLSearchParams(window.location.search); - const [hasVisualOutput, setHasVisualOutput] = useState( - queryParams.get("show_visual_tab") === "true" || senseHatAlwaysEnabled, - ); - - const getInput = () => { - const pageInput = document.getElementById("input"); - const webComponentInput = document.querySelector("editor-wc") - ? document.querySelector("editor-wc").shadowRoot.getElementById("input") - : null; - return pageInput || webComponentInput; - }; - - useEffect(() => { - if (codeRunTriggered) { - runCode(); - } - }, [codeRunTriggered]); - - useEffect(() => { - if (codeRunStopped && getInput()) { - const input = getInput(); - input.removeAttribute("id"); - input.removeAttribute("contentEditable"); - dispatch(setError(t("output.errors.interrupted"))); - dispatch(codeRunHandled()); - } - }, [codeRunStopped]); - - useEffect(() => { - if (!codeRunTriggered && !drawTriggered) { - if (getInput()) { - const input = getInput(); - input.removeAttribute("id"); - input.removeAttribute("contentEditable"); - } - } - }, [drawTriggered, codeRunTriggered]); - - const visualLibraries = [ - "./pygal/__init__.js", - "./py5/__init__.js", - "./py5_imported/__init__.js", - "./p5/__init__.js", - "./_internal_sense_hat/__init__.js", - "src/builtin/turtle/__init__.js", - ]; - - const outf = (text) => { - if (text !== "") { - const node = output.current; - const div = document.createElement("span"); - div.classList.add("pythonrunner-console-output-line"); - div.innerHTML = new Option(text).innerHTML; - node.appendChild(div); - node.scrollTop = node.scrollHeight; - } - }; - - const builtinRead = (library) => { - if (library === "./_internal_sense_hat/__init__.js") { - dispatch(setSenseHatEnabled(true)); - } - - if (library === "./p5/__init__.js" || library === "./py5/__init__.js") { - dispatch(triggerDraw()); - } - - // TODO: Handle pre-importing py5_imported when refactored py5 shim imported - - if (visualLibraries.includes(library)) { - setHasVisualOutput(true); - } - - let localProjectFiles = projectCode - .filter((component) => component.name !== "main") - .map((component) => `./${component.name}.py`); - - if (localProjectFiles.includes(library)) { - let filename = library.slice(2, -3); - let component = projectCode.find((x) => x.name === filename); - if (component) { - return component.content; - } - } - - if ( - Sk.builtinFiles !== undefined && - Sk.builtinFiles["files"][library] !== undefined - ) { - return Sk.builtinFiles["files"][library]; - } - - if (externalLibraries[library]) { - var externalLibraryInfo = externalLibraries[library]; - - return ( - externalLibraries[library].code || - Sk.misceval.promiseToSuspension( - fetch(externalLibraryInfo.path) - .then((response) => response.text()) - .then((code) => { - if (!code) { - throw new Sk.builtin.ImportError( - "Failed to load remote module", - ); - } - externalLibraries[library].code = code; - var promise; - - function mapUrlToPromise(path) { - // If the script is already in the DOM don't add it again. - const existingScriptElement = document.querySelector( - `script[src="${path}"]`, - ); - if (!existingScriptElement) { - return new Promise(function (resolve, _reject) { - let scriptElement = document.createElement("script"); - scriptElement.type = "text/javascript"; - scriptElement.src = path; - scriptElement.async = true; - scriptElement.onload = function () { - resolve(true); - }; - - document.body.appendChild(scriptElement); - }); - } - } - if (externalLibraryInfo.loadDepsSynchronously) { - promise = (externalLibraryInfo.dependencies || []).reduce( - (p, url) => { - return p.then(() => mapUrlToPromise(url)); - }, - Promise.resolve(), - ); // initial - } else { - promise = Promise.all( - (externalLibraryInfo.dependencies || []).map(mapUrlToPromise), - ); - } - - return promise - .then(function () { - return code; - }) - .catch(function () { - throw new Sk.builtin.ImportError( - "Failed to load dependencies required", - ); - }); - }), + const [usePyodide, setUsePyodide] = useState(false); + + const getImports = (code) => { + const importRegex = + /^(from\s+([a-zA-Z0-9_.]+)(\s+import\s+([a-zA-Z0-9_.]+))?)|^(import\s+([a-zA-Z0-9_.]+))/gm; + const matches = code.match(importRegex); + const imports = matches + ? matches.map( + (match) => + match + .split(/from|import/) + .filter(Boolean) + .map((s) => s.trim())[0], ) - ); - } - - throw new Error("File not found: '" + library + "'"); - }; - - const inputSpan = () => { - const span = document.createElement("span"); - span.setAttribute("id", "input"); - span.setAttribute("spellCheck", "false"); - span.setAttribute("class", "pythonrunner-input"); - span.setAttribute("contentEditable", "true"); - return span; + : []; + return imports; }; - const inf = function () { - if (Sk.sense_hat) { - Sk.sense_hat.mz_criteria.noInputEvents = false; - } - const outputPane = output.current; - outputPane.appendChild(inputSpan()); - - const input = getInput(); - input.focus(); - - return new Promise(function (resolve, reject) { - input.addEventListener("keydown", function removeInput(e) { - if (e.key === "Enter") { - input.removeEventListener(e.type, removeInput); - // resolve the promise with the value of the input field - const answer = input.innerText; - input.removeAttribute("id"); - input.removeAttribute("contentEditable"); - input.innerText = answer + "\n"; - - document.addEventListener("keyup", function storeInput(e) { - if (e.key === "Enter") { - document.removeEventListener(e.type, storeInput); - resolve(answer); - } - }); + useEffect(() => { + console.log("scanning imports"); + project.components.forEach((component) => { + if (component.extension === "py" && !codeRunTriggered) { + try { + const imports = getImports(component.content); + console.log(imports); + const hasSkulptOnlyModules = imports.some((name) => + SKULPT_ONLY_MODULES.includes(name), + ); + if (hasSkulptOnlyModules) { + console.log("using skulpt"); + setUsePyodide(false); + } else { + console.log("using pyodide"); + setUsePyodide(true); + } + } catch (error) { + console.error("Error occurred while getting imports:", error); } - }); - }); - }; - - const handleError = (err) => { - let errorMessage; - if (err.message === t("output.errors.interrupted")) { - errorMessage = err.message; - } else { - const errorDetails = (err.tp$str && err.tp$str().v) - .replace(/\[(.*?)\]/, "") - .replace(/\.$/, ""); - const errorType = err.tp$name || err.constructor.name; - const lineNumber = err.traceback[0].lineno; - const fileName = err.traceback[0].filename.replace(/^\.\//, ""); - - let userId; - if (user?.profile) { - userId = user.profile?.user; - } - - errorMessage = `${errorType}: ${errorDetails} on line ${lineNumber} of ${fileName}`; - createError(projectIdentifier, userId, { - errorType, - errorMessage, - }); - } - - dispatch(setError(errorMessage)); - dispatch(stopDraw()); - if (getInput()) { - const input = getInput(); - input.removeAttribute("id"); - input.removeAttribute("contentEditable"); - } - }; - - const runCode = () => { - // clear previous output - dispatch(setError("")); - output.current.innerHTML = ""; - dispatch(setSenseHatEnabled(false)); - - var prog = projectCode[0].content; - - if (prog.includes(`# ${t("input.comment.py5")}`)) { - prog = prog.replace( - `# ${t("input.comment.py5")}`, - "from py5_imported_mode import *", - ); - - if (!prog.match(/(\nrun_sketch)/)) { - prog = prog.concat("\nrun_sketch()"); } - } - - Sk.configure({ - inputfun: inf, - output: outf, - read: builtinRead, - debugging: true, - inputTakesPrompt: true, - uncaughtException: handleError, }); - - var myPromise = Sk.misceval - .asyncToPromise(() => Sk.importMainWithBody("main", false, prog, true), { - "*": () => { - if (store.getState().editor.codeRunStopped) { - throw new Error(t("output.errors.interrupted")); - } - }, - }) - .catch((err) => { - handleError(err); - }) - .finally(() => { - dispatch(codeRunHandled()); - }); - myPromise.then(function (_mod) {}); - }; - - function shiftFocusToInput(e) { - if (document.getSelection().toString().length > 0) { - return; - } - - const inputBox = getInput(); - if (inputBox && e.target !== inputBox) { - const input = getInput(); - const selection = window.getSelection(); - selection.removeAllRanges(); - - if (input.innerText && input.innerText.length > 0) { - const range = document.createRange(); - range.setStart(input, 1); - range.collapse(true); - selection.addRange(range); - } - input.focus(); - } - } - + }, [project, codeRunTriggered]); return ( -
- {isSplitView ? ( - <> - {hasVisualOutput ? ( -
- -
- - - - {t("output.visualOutput")} - - - - {!isEmbedded && hasVisualOutput ? : null} - {!isEmbedded && isMobile ? : null} -
- - - -
-
- ) : null} -
- -
- - - - {t("output.textOutput")} - - - - {!hasVisualOutput && !isEmbedded && isMobile ? ( - - ) : null} -
- - -

-              
-
-
- - ) : ( - -
- - {hasVisualOutput ? ( - - - {t("output.visualOutput")} - - - ) : null} - - - {t("output.textOutput")} - - - - {!isEmbedded && hasVisualOutput ? : null} - {!isEmbedded && isMobile ? : null} -
- - {hasVisualOutput ? ( - - - - ) : null} - -

-          
-
- )} -
+ <> + + + {/* {usePyodide ? : } */} + ); }; diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx new file mode 100644 index 000000000..c0cc80e6d --- /dev/null +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx @@ -0,0 +1,474 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import "../../../../assets/stylesheets/PythonRunner.scss"; +import React, { useContext, useEffect, useRef, useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { useTranslation } from "react-i18next"; +import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; +import Sk from "skulpt"; +import { useMediaQuery } from "react-responsive"; +import { + setError, + codeRunHandled, + stopDraw, + setSenseHatEnabled, + triggerDraw, +} from "../../../../redux/EditorSlice"; +import ErrorMessage from "../../ErrorMessage/ErrorMessage"; +import { createError } from "../../../../utils/apiCallHandler"; +import store from "../../../../app/store"; +import VisualOutputPane from "./VisualOutputPane"; +import OutputViewToggle from "./OutputViewToggle"; +import { SettingsContext } from "../../../../utils/settings"; +import RunnerControls from "../../../RunButton/RunnerControls"; +import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints"; + +const externalLibraries = { + "./pygal/__init__.js": { + path: `${process.env.PUBLIC_URL}/shims/pygal/pygal.js`, + dependencies: [ + "https://cdnjs.cloudflare.com/ajax/libs/highcharts/6.0.2/highcharts.js", + "https://cdnjs.cloudflare.com/ajax/libs/highcharts/6.0.2/js/highcharts-more.js", + ], + }, + "./py5/__init__.js": { + path: `${process.env.PUBLIC_URL}/shims/processing/py5/py5-shim.js`, + dependencies: [`${process.env.PUBLIC_URL}/libraries/processing/p5/p5.js`], + }, + "./py5_imported/__init__.js": { + path: `${process.env.PUBLIC_URL}/shims/processing/py5_imported_mode/py5_imported.js`, + }, + "./py5_imported_mode.py": { + path: `${process.env.PUBLIC_URL}/shims/processing/py5_imported_mode/py5_imported_mode.py`, + }, + "./p5/__init__.js": { + path: `${process.env.PUBLIC_URL}/shims/processing/p5/p5-shim.js`, + dependencies: [`${process.env.PUBLIC_URL}/libraries/processing/p5/p5.js`], + }, + "./_internal_sense_hat/__init__.js": { + path: `${process.env.PUBLIC_URL}/shims/sense_hat/_internal_sense_hat.js`, + }, + "./sense_hat.py": { + path: `${process.env.PUBLIC_URL}/shims/sense_hat/sense_hat_blob.py`, + }, +}; + +const SkulptRunner = ({ active }) => { + const projectCode = useSelector((state) => state.editor.project.components); + const projectIdentifier = useSelector( + (state) => state.editor.project.identifier, + ); + const user = useSelector((state) => state.auth.user); + const isSplitView = useSelector((state) => state.editor.isSplitView); + const isEmbedded = useSelector((state) => state.editor.isEmbedded); + const codeRunTriggered = useSelector( + (state) => state.editor.codeRunTriggered, + ); + const codeRunStopped = useSelector((state) => state.editor.codeRunStopped); + const drawTriggered = useSelector((state) => state.editor.drawTriggered); + const senseHatAlwaysEnabled = useSelector( + (state) => state.editor.senseHatAlwaysEnabled, + ); + const output = useRef(); + const dispatch = useDispatch(); + const { t } = useTranslation(); + const settings = useContext(SettingsContext); + const isMobile = useMediaQuery({ query: MOBILE_MEDIA_QUERY }); + + const queryParams = new URLSearchParams(window.location.search); + const [hasVisualOutput, setHasVisualOutput] = useState( + queryParams.get("show_visual_tab") === "true" || senseHatAlwaysEnabled, + ); + + const [showRunner, setShowRunner] = useState(false); + + const getInput = () => { + const pageInput = document.getElementById("input"); + const webComponentInput = document.querySelector("editor-wc") + ? document.querySelector("editor-wc").shadowRoot.getElementById("input") + : null; + return pageInput || webComponentInput; + }; + + useEffect(() => { + if (codeRunTriggered && active) { + runCode(); + } + }, [codeRunTriggered]); + + useEffect(() => { + if (codeRunStopped && active && getInput()) { + const input = getInput(); + input.removeAttribute("id"); + input.removeAttribute("contentEditable"); + dispatch(setError(t("output.errors.interrupted"))); + dispatch(codeRunHandled()); + } + }, [codeRunStopped]); + + useEffect(() => { + if (!codeRunTriggered && !drawTriggered && active) { + if (getInput()) { + const input = getInput(); + input.removeAttribute("id"); + input.removeAttribute("contentEditable"); + } + } + }, [drawTriggered, codeRunTriggered]); + + const visualLibraries = [ + "./pygal/__init__.js", + "./py5/__init__.js", + "./py5_imported/__init__.js", + "./p5/__init__.js", + "./_internal_sense_hat/__init__.js", + "src/builtin/turtle/__init__.js", + ]; + + const outf = (text) => { + if (text !== "") { + const node = output.current; + const div = document.createElement("span"); + div.classList.add("pythonrunner-console-output-line"); + div.innerHTML = new Option(text).innerHTML; + node.appendChild(div); + node.scrollTop = node.scrollHeight; + } + }; + + const builtinRead = (library) => { + if (library === "./_internal_sense_hat/__init__.js") { + dispatch(setSenseHatEnabled(true)); + } + + if (library === "./p5/__init__.js" || library === "./py5/__init__.js") { + dispatch(triggerDraw()); + } + + // TODO: Handle pre-importing py5_imported when refactored py5 shim imported + + if (visualLibraries.includes(library)) { + setHasVisualOutput(true); + } + + let localProjectFiles = projectCode + .filter((component) => component.name !== "main") + .map((component) => `./${component.name}.py`); + + if (localProjectFiles.includes(library)) { + let filename = library.slice(2, -3); + let component = projectCode.find((x) => x.name === filename); + if (component) { + return component.content; + } + } + + if ( + Sk.builtinFiles !== undefined && + Sk.builtinFiles["files"][library] !== undefined + ) { + return Sk.builtinFiles["files"][library]; + } + + if (externalLibraries[library]) { + var externalLibraryInfo = externalLibraries[library]; + + return ( + externalLibraries[library].code || + Sk.misceval.promiseToSuspension( + fetch(externalLibraryInfo.path) + .then((response) => response.text()) + .then((code) => { + if (!code) { + throw new Sk.builtin.ImportError( + "Failed to load remote module", + ); + } + externalLibraries[library].code = code; + var promise; + + function mapUrlToPromise(path) { + // If the script is already in the DOM don't add it again. + const existingScriptElement = document.querySelector( + `script[src="${path}"]`, + ); + if (!existingScriptElement) { + return new Promise(function (resolve, _reject) { + let scriptElement = document.createElement("script"); + scriptElement.type = "text/javascript"; + scriptElement.src = path; + scriptElement.async = true; + scriptElement.onload = function () { + resolve(true); + }; + + document.body.appendChild(scriptElement); + }); + } + } + if (externalLibraryInfo.loadDepsSynchronously) { + promise = (externalLibraryInfo.dependencies || []).reduce( + (p, url) => { + return p.then(() => mapUrlToPromise(url)); + }, + Promise.resolve(), + ); // initial + } else { + promise = Promise.all( + (externalLibraryInfo.dependencies || []).map(mapUrlToPromise), + ); + } + + return promise + .then(function () { + return code; + }) + .catch(function () { + throw new Sk.builtin.ImportError( + "Failed to load dependencies required", + ); + }); + }), + ) + ); + } + + throw new Error("File not found: '" + library + "'"); + }; + + const inputSpan = () => { + const span = document.createElement("span"); + span.setAttribute("id", "input"); + span.setAttribute("spellCheck", "false"); + span.setAttribute("class", "pythonrunner-input"); + span.setAttribute("contentEditable", "true"); + return span; + }; + + const inf = function () { + if (Sk.sense_hat) { + Sk.sense_hat.mz_criteria.noInputEvents = false; + } + const outputPane = output.current; + outputPane.appendChild(inputSpan()); + + const input = getInput(); + input.focus(); + + return new Promise(function (resolve, reject) { + input.addEventListener("keydown", function removeInput(e) { + if (e.key === "Enter") { + input.removeEventListener(e.type, removeInput); + // resolve the promise with the value of the input field + const answer = input.innerText; + input.removeAttribute("id"); + input.removeAttribute("contentEditable"); + input.innerText = answer + "\n"; + + document.addEventListener("keyup", function storeInput(e) { + if (e.key === "Enter") { + document.removeEventListener(e.type, storeInput); + resolve(answer); + } + }); + } + }); + }); + }; + + const handleError = (err) => { + let errorMessage; + if (err.message === t("output.errors.interrupted")) { + errorMessage = err.message; + } else { + const errorDetails = (err.tp$str && err.tp$str().v) + .replace(/\[(.*?)\]/, "") + .replace(/\.$/, ""); + const errorType = err.tp$name || err.constructor.name; + const lineNumber = err.traceback[0].lineno; + const fileName = err.traceback[0].filename.replace(/^\.\//, ""); + + let userId; + if (user?.profile) { + userId = user.profile?.user; + } + + errorMessage = `${errorType}: ${errorDetails} on line ${lineNumber} of ${fileName}`; + createError(projectIdentifier, userId, { + errorType, + errorMessage, + }); + } + + dispatch(setError(errorMessage)); + dispatch(stopDraw()); + if (getInput()) { + const input = getInput(); + input.removeAttribute("id"); + input.removeAttribute("contentEditable"); + } + }; + + const runCode = () => { + // clear previous output + dispatch(setError("")); + output.current.innerHTML = ""; + dispatch(setSenseHatEnabled(false)); + + var prog = projectCode[0].content; + + if (prog.includes(`# ${t("input.comment.py5")}`)) { + prog = prog.replace( + `# ${t("input.comment.py5")}`, + "from py5_imported_mode import *", + ); + + if (!prog.match(/(\nrun_sketch)/)) { + prog = prog.concat("\nrun_sketch()"); + } + } + + Sk.configure({ + inputfun: inf, + output: outf, + read: builtinRead, + debugging: true, + inputTakesPrompt: true, + uncaughtException: handleError, + }); + + var myPromise = Sk.misceval + .asyncToPromise(() => Sk.importMainWithBody("main", false, prog, true), { + "*": () => { + if (store.getState().editor.codeRunStopped) { + throw new Error(t("output.errors.interrupted")); + } + }, + }) + .catch((err) => { + handleError(err); + }) + .finally(() => { + dispatch(codeRunHandled()); + }); + myPromise.then(function (_mod) {}); + }; + + function shiftFocusToInput(e) { + if (document.getSelection().toString().length > 0) { + return; + } + + const inputBox = getInput(); + if (inputBox && e.target !== inputBox) { + const input = getInput(); + const selection = window.getSelection(); + selection.removeAllRanges(); + + if (input.innerText && input.innerText.length > 0) { + const range = document.createRange(); + range.setStart(input, 1); + range.collapse(true); + selection.addRange(range); + } + input.focus(); + } + } + + useEffect(() => { + if (codeRunTriggered) { + setShowRunner(active); + } + }, [codeRunTriggered]); + + return ( +
+ {isSplitView ? ( + <> + {hasVisualOutput ? ( +
+ +
+ + + + {t("output.visualOutput")} + + + + {!isEmbedded && hasVisualOutput ? : null} + {!isEmbedded && isMobile ? : null} +
+ + + +
+
+ ) : null} +
+ +
+ + + + {t("output.textOutput")} + + + + {!hasVisualOutput && !isEmbedded && isMobile ? ( + + ) : null} +
+ + +

+              
+
+
+ + ) : ( + +
+ + {hasVisualOutput ? ( + + + {t("output.visualOutput")} + + + ) : null} + + + {t("output.textOutput")} + + + + {!isEmbedded && hasVisualOutput ? : null} + {!isEmbedded && isMobile ? : null} +
+ + {hasVisualOutput ? ( + + + + ) : null} + +

+          
+
+ )} +
+ ); +}; + +export default SkulptRunner; diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js b/src/components/Editor/Runners/PythonRunner/SkulptRunner.test.js similarity index 97% rename from src/components/Editor/Runners/PythonRunner/PythonRunner.test.js rename to src/components/Editor/Runners/PythonRunner/SkulptRunner.test.js index d2a091193..e28c6f019 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner.test.js @@ -3,7 +3,7 @@ import { fireEvent, render, screen } from "@testing-library/react"; import { Provider } from "react-redux"; import configureStore from "redux-mock-store"; -import PythonRunner from "./PythonRunner"; +import SkulptRunner from "./SkulptRunner"; import { codeRunHandled, setError, @@ -55,7 +55,7 @@ describe("Testing basic input span functionality", () => { store = mockStore(initialState); render( - + , ); input = document.getElementById("input"); @@ -109,7 +109,7 @@ test("Input box not there when input function not called", () => { const store = mockStore(initialState); render( - + , ); expect(document.getElementById("input")).toBeNull(); @@ -140,7 +140,7 @@ describe("Testing stopping the code run with input", () => { store = mockStore(initialState); render( - + , ); }); @@ -189,7 +189,7 @@ describe("When in split view, no visual libraries used and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -233,7 +233,7 @@ describe("When in split view, py5 imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -280,7 +280,7 @@ describe("When in split view, py5_imported imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -323,7 +323,7 @@ describe("When in split view, pygal imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -366,7 +366,7 @@ describe("When in split view, turtle imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -409,7 +409,7 @@ describe("When in split view, sense_hat imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -453,7 +453,7 @@ describe("When in tabbed view, no visual libraries used and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -497,7 +497,7 @@ describe("When in tabbed view, py5 imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -544,7 +544,7 @@ describe("When in tabbed view, py5_imported imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -587,7 +587,7 @@ describe("When in tabbed view, pygal imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -630,7 +630,7 @@ describe("When in tabbed view, turtle imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -673,7 +673,7 @@ describe("When in tabbed view, sense_hat imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -714,7 +714,7 @@ test("When embedded in split view with visual output does not render output view const store = mockStore(initialState); render( - + , ); expect(screen.queryByRole("button")).not.toBeInTheDocument(); @@ -737,7 +737,7 @@ test("When embedded in split view with no visual output does not render output v const store = mockStore(initialState); render( - + , ); expect(screen.queryByRole("button")).not.toBeInTheDocument(); @@ -759,7 +759,7 @@ test("When embedded in tabbed view does not render output view toggle", () => { const store = mockStore(initialState); render( - + , ); expect(screen.queryByRole("button")).not.toBeInTheDocument(); @@ -781,7 +781,7 @@ test("Tabbed view has text and visual tabs with same parent element", () => { const store = mockStore(initialState); render( - + , ); expect( @@ -805,7 +805,7 @@ test("Split view has text and visual tabs with different parent elements", () => const store = mockStore(initialState); render( - + , ); expect(screen.getByText("output.visualOutput").parentElement).not.toEqual( @@ -833,7 +833,7 @@ describe("When font size is set", () => { - + , ); @@ -872,7 +872,7 @@ describe("When on desktop", () => { const store = mockStore(initialState); render( - + , ); }); @@ -908,7 +908,7 @@ describe("When on mobile and not embedded", () => { const store = mockStore(initialState); render( - + , ); }); diff --git a/src/components/Editor/Runners/RunnerFactory.jsx b/src/components/Editor/Runners/RunnerFactory.jsx index 89b01b6a0..adf3c5832 100644 --- a/src/components/Editor/Runners/RunnerFactory.jsx +++ b/src/components/Editor/Runners/RunnerFactory.jsx @@ -3,72 +3,66 @@ import PyodideRunner from "./PyodideRunner/PyodideRunner"; import PythonRunner from "./PythonRunner/PythonRunner"; import HtmlRunner from "./HtmlRunner/HtmlRunner"; import { useSelector } from "react-redux"; -// import { parse } from "filbert"; const SKULPT_ONLY_MODULES = ["p5", "py5", "sense_hat"]; const RunnerFactory = ({ projectType }) => { const Runner = () => { - const project = useSelector((state) => state.editor.project); - const [usePyodide, setUsePyodide] = useState(false); + // const project = useSelector((state) => state.editor.project); + // const codeRunTriggered = useSelector( + // (state) => state.editor.codeRunTriggered, + // ); + // const [usePyodide, setUsePyodide] = useState(false); + + // const getImports = (code) => { + // const importRegex = + // /^(from\s+([a-zA-Z0-9_\.]+)(\s+import\s+([a-zA-Z0-9_\.]+))?)|^(import\s+([a-zA-Z0-9_\.]+))/gm; + // const matches = code.match(importRegex); + // const imports = matches + // ? matches.map( + // (match) => + // match + // .split(/from|import/) + // .filter(Boolean) + // .map((s) => s.trim())[0], + // ) + // : []; + // return imports; + // }; // useEffect(() => { + // console.log("scanning imports"); // project.components.forEach((component) => { - // if (component.type === "python") { - // // const ast = parse(component.code); - // const imports = ast.body.filter((node) => node.type === "Import"); - // const importNames = imports.map((node) => node.names[0].name); - // const hasSkulptOnlyModules = importNames.some((name) => - // SKULPT_ONLY_MODULES.includes(name), - // ); - // if (hasSkulptOnlyModules) { - // setUsePyodide(false); + // if ( + // projectType === "python" && + // component.extension === "py" && + // !codeRunTriggered + // ) { + // try { + // const imports = getImports(component.content); + // console.log(imports); + // // const importNames = imports.map((importArr) => importArr[1]); + // const hasSkulptOnlyModules = imports.some((name) => + // SKULPT_ONLY_MODULES.includes(name), + // ); + // if (hasSkulptOnlyModules) { + // console.log("using skulpt"); + // setUsePyodide(false); + // } else { + // console.log("using pyodide"); + // setUsePyodide(true); + // } + // } catch (error) { + // console.error("Error occurred while getting imports:", error); // } // } // }); - // }, [project]); - - const getImports = (code) => { - const importRegex = - /(from\s+([a-zA-Z0-9_\.]+)(\s+import\s+([a-zA-Z0-9_\.]+))?)|(import\s+([a-zA-Z0-9_\.]+))/g; - const matches = code.match(importRegex); - const imports = matches - ? matches.map( - (match) => - match - .split(/from|import/) - .filter(Boolean) - .map((s) => s.trim())[0], - ) - : []; - return imports; - }; - - useEffect(() => { - console.log("scanning imports"); - project.components.forEach((component) => { - if (projectType === "python" && component.extension === "py") { - const imports = getImports(component.content); - console.log(imports); - // const importNames = imports.map((importArr) => importArr[1]); - const hasSkulptOnlyModules = imports.some((name) => - SKULPT_ONLY_MODULES.includes(name), - ); - if (hasSkulptOnlyModules) { - console.log("using skulpt"); - setUsePyodide(false); - } else { - console.log("using pyodide"); - setUsePyodide(true); - } - } - }); - }, [project]); + // }, [project, codeRunTriggered]); if (projectType === "html") { return HtmlRunner; - } else if (usePyodide) { - return PyodideRunner; + // } else if (usePyodide) { + // return PyodideRunner; } else { return PythonRunner; } diff --git a/yarn.lock b/yarn.lock index 66a80f77b..f61e0f34a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2629,7 +2629,6 @@ __metadata: eslint-plugin-react-hooks: ^4.2.0 eslint-plugin-testing-library: ^3.9.2 eslint-webpack-plugin: ^2.5.2 - filbert: ^0.1.20 file-loader: 6.1.1 file-saver: ^2.0.5 fs-extra: ^9.0.1 @@ -8798,13 +8797,6 @@ __metadata: languageName: node linkType: hard -"filbert@npm:^0.1.20": - version: 0.1.20 - resolution: "filbert@npm:0.1.20" - checksum: ad709094016811f8f308dcaa41d860bad181b77808577dd7872434ea43a90464a0c8114fd87a9d8e6f9797b9f005d959a7b855de1bc7316437573f02fef4f981 - languageName: node - linkType: hard - "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" From f0ba6c331dfb2394cfd97dff37f11287d1fd4590 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Wed, 28 Feb 2024 12:10:40 +0000 Subject: [PATCH 05/59] tidying for linter --- src/components/Editor/Output/Output.jsx | 18 +----- .../Editor/Runners/RunnerFactory.jsx | 59 +------------------ 2 files changed, 3 insertions(+), 74 deletions(-) diff --git a/src/components/Editor/Output/Output.jsx b/src/components/Editor/Output/Output.jsx index 65b6eaa86..60c882815 100644 --- a/src/components/Editor/Output/Output.jsx +++ b/src/components/Editor/Output/Output.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; import { useSelector } from "react-redux"; import ExternalFiles from "../../ExternalFiles/ExternalFiles"; import RunnerFactory from "../Runners/RunnerFactory"; @@ -9,26 +9,12 @@ const Output = () => { const isEmbedded = useSelector((state) => state.editor.isEmbedded); const searchParams = new URLSearchParams(window.location.search); const isBrowserPreview = searchParams.get("browserPreview") === "true"; - // const pythonInterpreter = useSelector( - // (state) => state.runner.pythonInterpreter, - // ); - - // const [usePyodide, setUsePyodide] = useState(true); - - // useEffect(() => { - // if (pythonInterpreter === "skulpt") { - // setUsePyodide(false); - // } - // }, [pythonInterpreter]); return ( <>
- + {isEmbedded && !isBrowserPreview ? : null}
diff --git a/src/components/Editor/Runners/RunnerFactory.jsx b/src/components/Editor/Runners/RunnerFactory.jsx index adf3c5832..66ca46585 100644 --- a/src/components/Editor/Runners/RunnerFactory.jsx +++ b/src/components/Editor/Runners/RunnerFactory.jsx @@ -1,68 +1,11 @@ -import React, { useEffect, useState } from "react"; -import PyodideRunner from "./PyodideRunner/PyodideRunner"; +import React from "react"; import PythonRunner from "./PythonRunner/PythonRunner"; import HtmlRunner from "./HtmlRunner/HtmlRunner"; -import { useSelector } from "react-redux"; - -const SKULPT_ONLY_MODULES = ["p5", "py5", "sense_hat"]; const RunnerFactory = ({ projectType }) => { const Runner = () => { - // const project = useSelector((state) => state.editor.project); - // const codeRunTriggered = useSelector( - // (state) => state.editor.codeRunTriggered, - // ); - // const [usePyodide, setUsePyodide] = useState(false); - - // const getImports = (code) => { - // const importRegex = - // /^(from\s+([a-zA-Z0-9_\.]+)(\s+import\s+([a-zA-Z0-9_\.]+))?)|^(import\s+([a-zA-Z0-9_\.]+))/gm; - // const matches = code.match(importRegex); - // const imports = matches - // ? matches.map( - // (match) => - // match - // .split(/from|import/) - // .filter(Boolean) - // .map((s) => s.trim())[0], - // ) - // : []; - // return imports; - // }; - - // useEffect(() => { - // console.log("scanning imports"); - // project.components.forEach((component) => { - // if ( - // projectType === "python" && - // component.extension === "py" && - // !codeRunTriggered - // ) { - // try { - // const imports = getImports(component.content); - // console.log(imports); - // // const importNames = imports.map((importArr) => importArr[1]); - // const hasSkulptOnlyModules = imports.some((name) => - // SKULPT_ONLY_MODULES.includes(name), - // ); - // if (hasSkulptOnlyModules) { - // console.log("using skulpt"); - // setUsePyodide(false); - // } else { - // console.log("using pyodide"); - // setUsePyodide(true); - // } - // } catch (error) { - // console.error("Error occurred while getting imports:", error); - // } - // } - // }); - // }, [project, codeRunTriggered]); - if (projectType === "html") { return HtmlRunner; - // } else if (usePyodide) { - // return PyodideRunner; } else { return PythonRunner; } From 825961e6e061151c97fdd8627a98467cae97cc28 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Wed, 28 Feb 2024 12:13:49 +0000 Subject: [PATCH 06/59] tidying to clarify approach in pr --- src/app/store.js | 2 -- .../Editor/Runners/PyodideRunner/PyodideRunner.jsx | 5 ----- .../Editor/Runners/PyodideRunner/PyodideWorker.js | 7 ------- src/redux/RunnerSlice.js | 13 ------------- src/redux/reducers/runnerReducers.js | 5 ----- 5 files changed, 32 deletions(-) delete mode 100644 src/redux/RunnerSlice.js delete mode 100644 src/redux/reducers/runnerReducers.js diff --git a/src/app/store.js b/src/app/store.js index 6290ba4b1..aa1691a78 100644 --- a/src/app/store.js +++ b/src/app/store.js @@ -1,14 +1,12 @@ import { configureStore } from "@reduxjs/toolkit"; import EditorReducer from "../redux/EditorSlice"; import InstructionsReducer from "../redux/InstructionsSlice"; -import RunnerReducer from "../redux/RunnerSlice"; import { reducer, loadUser } from "redux-oidc"; import userManager from "../utils/userManager"; const store = configureStore({ reducer: { editor: EditorReducer, - runner: RunnerReducer, instructions: InstructionsReducer, auth: reducer, }, diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx index 0f64af9a1..28e3eab66 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx @@ -20,7 +20,6 @@ import OutputViewToggle from "../PythonRunner/OutputViewToggle"; import { SettingsContext } from "../../../../utils/settings"; import RunnerControls from "../../../RunButton/RunnerControls"; import PyodideWorker from "worker-loader!./PyodideWorker.js"; -import { switchToSkulpt } from "../../../../redux/RunnerSlice"; const PyodideRunner = ({ active }) => { const pyodideWorker = useMemo(() => new PyodideWorker(), []); @@ -72,10 +71,6 @@ const PyodideRunner = ({ active }) => { case "handleSenseHatEvent": handleSenseHatEvent(data.type); break; - case "switchToSkulpt": - console.log("switching to skulpt"); - dispatch(switchToSkulpt()); - break; default: throw new Error(`Unsupported method: ${data.method}`); } diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js b/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js index 8f7562235..3e703fde2 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js +++ b/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js @@ -72,13 +72,6 @@ const checkIfStopped = () => { const withSupportForPackages = async (python, runPythonFn) => { const imports = await pyodide._api.pyodide_code.find_imports(python).toJs(); - console.log(imports); - const skulptOnlyModules = ["p5", "py5", "sense_hat"]; - if (imports.some((name) => skulptOnlyModules.includes(name))) { - console.log("skulpt only modules detected"); - postMessage({ method: "switchToSkulpt" }); - return; - } await Promise.all(imports.map((name) => loadDependency(name))); checkIfStopped(); diff --git a/src/redux/RunnerSlice.js b/src/redux/RunnerSlice.js deleted file mode 100644 index c66cff007..000000000 --- a/src/redux/RunnerSlice.js +++ /dev/null @@ -1,13 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -import { reducers } from "./reducers/runnerReducers"; - -export const RunnerSlice = createSlice({ - name: "runner", - initialState: { - pythonInterpreter: "pyodide", - }, - reducers, -}); - -export const { switchToSkulpt } = RunnerSlice.actions; -export default RunnerSlice.reducer; diff --git a/src/redux/reducers/runnerReducers.js b/src/redux/reducers/runnerReducers.js deleted file mode 100644 index 9f277c081..000000000 --- a/src/redux/reducers/runnerReducers.js +++ /dev/null @@ -1,5 +0,0 @@ -export const switchToSkulpt = (state) => { - state.pythonInterpreter = "skulpt"; -}; - -export const reducers = { switchToSkulpt }; From e4354ed6406e150b008444653e9796d42ba66043 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Wed, 28 Feb 2024 17:19:09 +0000 Subject: [PATCH 07/59] refactoring to fix things --- .../Runners/PyodideRunner/PyodideRunner.jsx | 5 +++-- .../Runners/PythonRunner/PythonRunner.jsx | 2 -- .../Runners/PythonRunner/SkulptRunner.jsx | 20 ++++++++++--------- .../Runners/PythonRunner/VisualOutputPane.jsx | 1 + 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx index 28e3eab66..990a4385f 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx @@ -45,7 +45,7 @@ const PyodideRunner = ({ active }) => { const showVisualTab = queryParams.get("show_visual_tab") === "true"; const [hasVisual, setHasVisual] = useState(showVisualTab || senseHatAlways); const [visuals, setVisuals] = useState([]); - const [showRunner, setShowRunner] = useState(true); + const [showRunner, setShowRunner] = useState(false); useEffect(() => { pyodideWorker.onmessage = ({ data }) => { @@ -79,6 +79,7 @@ const PyodideRunner = ({ active }) => { useEffect(() => { if (codeRunTriggered && active) { + console.log("running with pyodide"); handleRun(); } }, [codeRunTriggered]); @@ -281,7 +282,7 @@ const PyodideRunner = ({ active }) => { return (
{isSplitView ? ( <> diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index 5ce8be3bc..0c90d25fb 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -30,12 +30,10 @@ const PythonRunner = () => { }; useEffect(() => { - console.log("scanning imports"); project.components.forEach((component) => { if (component.extension === "py" && !codeRunTriggered) { try { const imports = getImports(component.content); - console.log(imports); const hasSkulptOnlyModules = imports.some((name) => SKULPT_ONLY_MODULES.includes(name), ); diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx index c0cc80e6d..e50323b68 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx @@ -79,7 +79,7 @@ const SkulptRunner = ({ active }) => { queryParams.get("show_visual_tab") === "true" || senseHatAlwaysEnabled, ); - const [showRunner, setShowRunner] = useState(false); + const [showRunner, setShowRunner] = useState(true); const getInput = () => { const pageInput = document.getElementById("input"); @@ -90,9 +90,16 @@ const SkulptRunner = ({ active }) => { }; useEffect(() => { - if (codeRunTriggered && active) { + if (codeRunTriggered && active && showRunner) { + console.log("running with skulpt"); runCode(); } + }, [codeRunTriggered, showRunner]); + + useEffect(() => { + if (codeRunTriggered) { + setShowRunner(active); + } }, [codeRunTriggered]); useEffect(() => { @@ -374,16 +381,11 @@ const SkulptRunner = ({ active }) => { } } - useEffect(() => { - if (codeRunTriggered) { - setShowRunner(active); - } - }, [codeRunTriggered]); - return (
{isSplitView ? ( <> diff --git a/src/components/Editor/Runners/PythonRunner/VisualOutputPane.jsx b/src/components/Editor/Runners/PythonRunner/VisualOutputPane.jsx index a39b95b4a..37dfd5eb3 100644 --- a/src/components/Editor/Runners/PythonRunner/VisualOutputPane.jsx +++ b/src/components/Editor/Runners/PythonRunner/VisualOutputPane.jsx @@ -36,6 +36,7 @@ const VisualOutputPane = () => { Sk.p5 = {}; Sk.p5.sketch = "p5Sketch"; + console.log(document.getElementById("p5Sketch")); Sk.p5.assets = projectImages; (Sk.pygal || (Sk.pygal = {})).outputCanvas = pygalOutput.current; From 534871c12dc24cc3cca25a82f2d63b7328cff8e8 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Wed, 28 Feb 2024 17:33:52 +0000 Subject: [PATCH 08/59] tidying --- src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx | 2 +- src/components/Editor/Runners/PythonRunner/PythonRunner.jsx | 2 -- src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx index 990a4385f..1b25979e5 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx @@ -282,7 +282,7 @@ const PyodideRunner = ({ active }) => { return (
{isSplitView ? ( <> diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index 0c90d25fb..051adaee9 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -38,10 +38,8 @@ const PythonRunner = () => { SKULPT_ONLY_MODULES.includes(name), ); if (hasSkulptOnlyModules) { - console.log("using skulpt"); setUsePyodide(false); } else { - console.log("using pyodide"); setUsePyodide(true); } } catch (error) { diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx index e50323b68..fcd705cf6 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx @@ -384,8 +384,7 @@ const SkulptRunner = ({ active }) => { return (
{isSplitView ? ( <> From fb17e4df2d0a094b3cff821e49d0e3c918b2c718 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 29 Feb 2024 09:13:31 +0000 Subject: [PATCH 09/59] tidying --- src/components/Editor/Runners/PythonRunner/PythonRunner.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index 051adaee9..4703d4bf9 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -52,7 +52,6 @@ const PythonRunner = () => { <> - {/* {usePyodide ? : } */} ); }; From ff0587f30371c6fd3b6815e07517bf6435a7008a Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 4 Apr 2024 14:09:26 +0100 Subject: [PATCH 10/59] fixing shikm assets --- .../Runners/PythonRunner/SkulptRunner.jsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx index fcd705cf6..a6cff5a5e 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx @@ -24,31 +24,31 @@ import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints"; const externalLibraries = { "./pygal/__init__.js": { - path: `${process.env.PUBLIC_URL}/shims/pygal/pygal.js`, + path: `${process.env.ASSETS_URL}/shims/pygal/pygal.js`, dependencies: [ "https://cdnjs.cloudflare.com/ajax/libs/highcharts/6.0.2/highcharts.js", "https://cdnjs.cloudflare.com/ajax/libs/highcharts/6.0.2/js/highcharts-more.js", ], }, "./py5/__init__.js": { - path: `${process.env.PUBLIC_URL}/shims/processing/py5/py5-shim.js`, - dependencies: [`${process.env.PUBLIC_URL}/libraries/processing/p5/p5.js`], + path: `${process.env.ASSETS_URL}/shims/processing/py5/py5-shim.js`, + dependencies: [`${process.env.ASSETS_URL}/libraries/processing/p5/p5.js`], }, "./py5_imported/__init__.js": { - path: `${process.env.PUBLIC_URL}/shims/processing/py5_imported_mode/py5_imported.js`, + path: `${process.env.ASSETS_URL}/shims/processing/py5_imported_mode/py5_imported.js`, }, "./py5_imported_mode.py": { - path: `${process.env.PUBLIC_URL}/shims/processing/py5_imported_mode/py5_imported_mode.py`, + path: `${process.env.ASSETS_URL}/shims/processing/py5_imported_mode/py5_imported_mode.py`, }, "./p5/__init__.js": { - path: `${process.env.PUBLIC_URL}/shims/processing/p5/p5-shim.js`, - dependencies: [`${process.env.PUBLIC_URL}/libraries/processing/p5/p5.js`], + path: `${process.env.ASSETS_URL}/shims/processing/p5/p5-shim.js`, + dependencies: [`${process.env.ASSETS_URL}/libraries/processing/p5/p5.js`], }, "./_internal_sense_hat/__init__.js": { - path: `${process.env.PUBLIC_URL}/shims/sense_hat/_internal_sense_hat.js`, + path: `${process.env.ASSETS_URL}/shims/sense_hat/_internal_sense_hat.js`, }, "./sense_hat.py": { - path: `${process.env.PUBLIC_URL}/shims/sense_hat/sense_hat_blob.py`, + path: `${process.env.ASSETS_URL}/shims/sense_hat/sense_hat_blob.py`, }, }; From beaad382704633ac7bd2cac3adde79ecfe231967 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 4 Apr 2024 14:44:34 +0100 Subject: [PATCH 11/59] initial attempt at explaining incompatible modules --- .../Editor/Runners/PythonRunner/SkulptRunner.jsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx index a6cff5a5e..90857edbf 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx @@ -21,6 +21,7 @@ import OutputViewToggle from "./OutputViewToggle"; import { SettingsContext } from "../../../../utils/settings"; import RunnerControls from "../../../RunButton/RunnerControls"; import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints"; +import { error } from "highcharts"; const externalLibraries = { "./pygal/__init__.js": { @@ -284,6 +285,7 @@ const SkulptRunner = ({ active }) => { const handleError = (err) => { let errorMessage; + let explanation; if (err.message === t("output.errors.interrupted")) { errorMessage = err.message; } else { @@ -294,12 +296,19 @@ const SkulptRunner = ({ active }) => { const lineNumber = err.traceback[0].lineno; const fileName = err.traceback[0].filename.replace(/^\.\//, ""); + if (errorType === "ImportError") { + const moduleName = errorDetails.replace(/No module named /, ""); + explanation = `This may be because ${moduleName} cannot currently be used with p5 or sense_hat.`; + } + let userId; if (user?.profile) { userId = user.profile?.user; } - errorMessage = `${errorType}: ${errorDetails} on line ${lineNumber} of ${fileName}`; + errorMessage = `${errorType}: ${errorDetails} on line ${lineNumber} of ${fileName}${ + explanation ? `. ${explanation}` : "" + }`; createError(projectIdentifier, userId, { errorType, errorMessage, From 901cd1dc84ae818d43092255e6bbf7d55dae763b Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 4 Apr 2024 16:03:00 +0100 Subject: [PATCH 12/59] Fixing snapshot and skulptrunner tests --- .../Runners/PythonRunner/SkulptRunner.jsx | 1 - .../Runners/PythonRunner/SkulptRunner.test.js | 46 +++++++++--------- .../__snapshots__/EmbeddedViewer.test.js.snap | 48 ++++++++++++++++++- 3 files changed, 70 insertions(+), 25 deletions(-) diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx index 90857edbf..b2c84f611 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx @@ -21,7 +21,6 @@ import OutputViewToggle from "./OutputViewToggle"; import { SettingsContext } from "../../../../utils/settings"; import RunnerControls from "../../../RunButton/RunnerControls"; import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints"; -import { error } from "highcharts"; const externalLibraries = { "./pygal/__init__.js": { diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner.test.js b/src/components/Editor/Runners/PythonRunner/SkulptRunner.test.js index e28c6f019..4a6fead06 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner.test.js +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner.test.js @@ -55,7 +55,7 @@ describe("Testing basic input span functionality", () => { store = mockStore(initialState); render( - + , ); input = document.getElementById("input"); @@ -109,7 +109,7 @@ test("Input box not there when input function not called", () => { const store = mockStore(initialState); render( - + , ); expect(document.getElementById("input")).toBeNull(); @@ -140,7 +140,7 @@ describe("Testing stopping the code run with input", () => { store = mockStore(initialState); render( - + , ); }); @@ -189,7 +189,7 @@ describe("When in split view, no visual libraries used and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -233,7 +233,7 @@ describe("When in split view, py5 imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -280,7 +280,7 @@ describe("When in split view, py5_imported imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -323,7 +323,7 @@ describe("When in split view, pygal imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -366,7 +366,7 @@ describe("When in split view, turtle imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -409,7 +409,7 @@ describe("When in split view, sense_hat imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -453,7 +453,7 @@ describe("When in tabbed view, no visual libraries used and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -497,7 +497,7 @@ describe("When in tabbed view, py5 imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -544,7 +544,7 @@ describe("When in tabbed view, py5_imported imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -587,7 +587,7 @@ describe("When in tabbed view, pygal imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -630,7 +630,7 @@ describe("When in tabbed view, turtle imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -673,7 +673,7 @@ describe("When in tabbed view, sense_hat imported and code run", () => { store = mockStore(initialState); ({ queryByText } = render( - + , )); }); @@ -714,7 +714,7 @@ test("When embedded in split view with visual output does not render output view const store = mockStore(initialState); render( - + , ); expect(screen.queryByRole("button")).not.toBeInTheDocument(); @@ -737,7 +737,7 @@ test("When embedded in split view with no visual output does not render output v const store = mockStore(initialState); render( - + , ); expect(screen.queryByRole("button")).not.toBeInTheDocument(); @@ -759,7 +759,7 @@ test("When embedded in tabbed view does not render output view toggle", () => { const store = mockStore(initialState); render( - + , ); expect(screen.queryByRole("button")).not.toBeInTheDocument(); @@ -781,7 +781,7 @@ test("Tabbed view has text and visual tabs with same parent element", () => { const store = mockStore(initialState); render( - + , ); expect( @@ -805,7 +805,7 @@ test("Split view has text and visual tabs with different parent elements", () => const store = mockStore(initialState); render( - + , ); expect(screen.getByText("output.visualOutput").parentElement).not.toEqual( @@ -833,7 +833,7 @@ describe("When font size is set", () => { - + , ); @@ -872,7 +872,7 @@ describe("When on desktop", () => { const store = mockStore(initialState); render( - + , ); }); @@ -908,7 +908,7 @@ describe("When on mobile and not embedded", () => { const store = mockStore(initialState); render( - + , ); }); diff --git a/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap b/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap index 143f17570..2514c05ba 100644 --- a/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap +++ b/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap @@ -13,7 +13,8 @@ exports[`Renders without crashing 1`] = ` class="proj-runner-container" > +
+
+
+
    + +
+
+
+
+          
+
+
From 88ce726ada2029e43a48963ab267fee2f0199c35 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 4 Apr 2024 16:08:23 +0100 Subject: [PATCH 13/59] fixing pyodiderunner tests --- .../PyodideRunner/PyodideRunner.test.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.test.js b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.test.js index 9b215b0c0..fe0f125ea 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.test.js +++ b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.test.js @@ -30,7 +30,7 @@ describe("When first loaded", () => { const store = mockStore(initialState); render( - , + , , ); }); @@ -55,7 +55,7 @@ describe("When a code run has been triggered", () => { }); render( - , + , , ); }); @@ -94,7 +94,7 @@ describe("When the code has been stopped", () => { }); render( - , + , , ); }); @@ -112,7 +112,7 @@ describe("When loading pyodide", () => { store = mockStore(initialState); render( - , + , , ); @@ -131,7 +131,7 @@ describe("When pyodide has loaded", () => { store = mockStore(initialState); render( - , + , , ); @@ -151,7 +151,7 @@ describe("When input is required", () => { store = mockStore(initialState); render( - , + , , ); @@ -192,7 +192,7 @@ describe("When output is received", () => { store = mockStore(initialState); render( - , + , , ); @@ -215,7 +215,7 @@ describe("When visual output is received", () => { store = mockStore(initialState); render( - , + , , ); @@ -247,7 +247,7 @@ describe("When an error is received", () => { store = mockStore(initialState); render( - , + , , ); @@ -279,7 +279,7 @@ describe("When the code run is interrupted", () => { store = mockStore(initialState); render( - , + , , ); From ea622af37439c2d3e3cba8ac35d5a599acbf879a Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 4 Apr 2024 16:51:22 +0100 Subject: [PATCH 14/59] fixing py5 imported mode --- .../Editor/Runners/PythonRunner/PythonRunner.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index 4703d4bf9..9b08306cf 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -3,8 +3,9 @@ import React, { useEffect, useState } from "react"; import PyodideRunner from "../PyodideRunner/PyodideRunner"; import SkulptRunner from "./SkulptRunner"; import { useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; -const SKULPT_ONLY_MODULES = ["p5", "py5", "sense_hat"]; +const SKULPT_ONLY_MODULES = ["p5", "py5", "py5_imported", "sense_hat"]; const PythonRunner = () => { const project = useSelector((state) => state.editor.project); @@ -12,6 +13,7 @@ const PythonRunner = () => { (state) => state.editor.codeRunTriggered, ); const [usePyodide, setUsePyodide] = useState(false); + const { t } = useTranslation(); const getImports = (code) => { const importRegex = @@ -26,6 +28,9 @@ const PythonRunner = () => { .map((s) => s.trim())[0], ) : []; + if (code.includes(`# ${t("input.comment.py5")}`)) { + imports.push("py5"); + } return imports; }; From 95b85abb3b78f8d01d361b306ba0a9fd9a075332 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 4 Apr 2024 17:21:57 +0100 Subject: [PATCH 15/59] fixing cypress tests --- cypress/e2e/missionZero-wc.cy.js | 8 ++++++-- .../Editor/Runners/PythonRunner/SkulptRunner.jsx | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cypress/e2e/missionZero-wc.cy.js b/cypress/e2e/missionZero-wc.cy.js index 3db6aee39..481f96fda 100644 --- a/cypress/e2e/missionZero-wc.cy.js +++ b/cypress/e2e/missionZero-wc.cy.js @@ -137,9 +137,13 @@ it("picks up calls to input()", () => { cy.get("editor-wc") .shadow() .find("div[class=cm-content]") - .invoke("text", "input()"); + .invoke("text", "from sense_hat import SenseHat\ninput()"); cy.get("editor-wc").shadow().find(".btn--run").click(); - cy.get("editor-wc").shadow().contains("Text output").click(); + cy.get("editor-wc") + .shadow() + .find("div[class='pythonrunner-container skulptrunner']") + .contains("Text output") + .click(); cy.get("editor-wc") .shadow() .find("span[contenteditable=true]") diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx index b2c84f611..720f53121 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx @@ -391,7 +391,7 @@ const SkulptRunner = ({ active }) => { return (
{isSplitView ? ( From ef868e0152d1454b337df1b7fb9aa09c1f83e353 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 4 Apr 2024 17:27:05 +0100 Subject: [PATCH 16/59] updating snapshot --- .../EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap b/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap index 2514c05ba..f52c1c61e 100644 --- a/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap +++ b/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap @@ -58,7 +58,7 @@ exports[`Renders without crashing 1`] = `
Date: Fri, 5 Apr 2024 09:37:59 +0100 Subject: [PATCH 17/59] fixing linting warning --- .../Runners/PythonRunner/PythonRunner.jsx | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index 9b08306cf..af7fd38d8 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -15,26 +15,26 @@ const PythonRunner = () => { const [usePyodide, setUsePyodide] = useState(false); const { t } = useTranslation(); - const getImports = (code) => { - const importRegex = - /^(from\s+([a-zA-Z0-9_.]+)(\s+import\s+([a-zA-Z0-9_.]+))?)|^(import\s+([a-zA-Z0-9_.]+))/gm; - const matches = code.match(importRegex); - const imports = matches - ? matches.map( - (match) => - match - .split(/from|import/) - .filter(Boolean) - .map((s) => s.trim())[0], - ) - : []; - if (code.includes(`# ${t("input.comment.py5")}`)) { - imports.push("py5"); - } - return imports; - }; - useEffect(() => { + const getImports = (code) => { + const importRegex = + /^(from\s+([a-zA-Z0-9_.]+)(\s+import\s+([a-zA-Z0-9_.]+))?)|^(import\s+([a-zA-Z0-9_.]+))/gm; + const matches = code.match(importRegex); + const imports = matches + ? matches.map( + (match) => + match + .split(/from|import/) + .filter(Boolean) + .map((s) => s.trim())[0], + ) + : []; + if (code.includes(`# ${t("input.comment.py5")}`)) { + imports.push("py5"); + } + return imports; + }; + project.components.forEach((component) => { if (component.extension === "py" && !codeRunTriggered) { try { @@ -52,7 +52,7 @@ const PythonRunner = () => { } } }); - }, [project, codeRunTriggered]); + }, [project, codeRunTriggered, t]); return ( <> From 610f6ad085427833218e339538e2a458b237f1a3 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Fri, 5 Apr 2024 09:43:01 +0100 Subject: [PATCH 18/59] updating changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4d38e114..f93464eec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - Unit tests for `pyodide` runner (#976) +- Dynamic switching between `pyodide` and `skulpt` based on user imports (#937) + +### Changed + +- Runner defaults to `pyodide` (#937) ## [0.22.2] - 2024-03-18 From e399def9748998fa801f7f694a5e4d3d15e40322 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Fri, 5 Apr 2024 16:31:03 +0100 Subject: [PATCH 19/59] fixing the input function --- src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx index 2c95ea9ad..f51bd2ed8 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx @@ -122,7 +122,7 @@ const PyodideRunner = ({ active }) => { const { content, ctrlD } = await getInputContent(element); const encoder = new TextEncoder(); - const bytes = encoder.encode(content + "\r\n"); + const bytes = encoder.encode(content + "\n"); const previousLength = stdinBuffer.current[0]; stdinBuffer.current.set(bytes, previousLength); From 797a559f9e65505ca3a969ad6ff814c04717dea3 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Fri, 5 Apr 2024 16:46:12 +0100 Subject: [PATCH 20/59] trying to fix cypress --- cypress/e2e/spec-wc.cy.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/spec-wc.cy.js b/cypress/e2e/spec-wc.cy.js index df5560847..d20fb4c7d 100644 --- a/cypress/e2e/spec-wc.cy.js +++ b/cypress/e2e/spec-wc.cy.js @@ -76,7 +76,10 @@ it("does not render astro pi component if sense hat unimported", () => { .find("div[class=cm-content]") .invoke("text", "import sense_hat"); cy.get("editor-wc").shadow().find(".btn--run").click(); - cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke("text", ""); + cy.get("editor-wc") + .shadow() + .find("div[class=cm-content]") + .invoke("text", "import p5"); cy.get("editor-wc").shadow().find(".btn--run").click(); cy.get("editor-wc").shadow().contains("Visual output").click(); cy.get("editor-wc").shadow().find("#root").should("not.contain", "yaw"); From 691f9fb111685ade349e9b44f6f068744e5b6c46 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Mon, 8 Apr 2024 11:59:51 +0100 Subject: [PATCH 21/59] patching pygal tooltip formatter --- .../Editor/Runners/PyodideRunner/PyodideWorker.js | 7 ++++--- .../Editor/Runners/PyodideRunner/VisualOutputPane.jsx | 10 +++++++++- src/components/Editor/Runners/PyodideRunner/pygal.js | 4 +--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js b/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js index 2c6cff4ae..44ed06510 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js +++ b/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js @@ -51,12 +51,12 @@ const runPython = async (python) => { stopped = false; await pyodide.runPythonAsync(` old_input = input - + def patched_input(prompt=False): if (prompt): print(prompt) return old_input() - + __builtins__.input = patched_input `); @@ -178,8 +178,9 @@ const vendoredPackages = { pygal: { before: () => { pyodide.registerJsModule("pygal", { ...pygal }); - pygal.config.renderChart = (content) => + pygal.config.renderChart = (content) => { postMessage({ method: "handleVisual", origin: "pygal", content }); + }; }, after: () => {}, }, diff --git a/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx b/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx index 7f05887b7..9bad49974 100644 --- a/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx +++ b/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx @@ -44,8 +44,16 @@ const showVisual = (visual, output) => { }, }, }, + tooltip: { + ...visual.content.tooltip, + formatter: + visual.content.chart.type === "pie" + ? function () { + return this.key + ": " + this.y; + } + : null, + }, }; - Highcharts.chart(output.current, chartContent); break; case "turtle": diff --git a/src/components/Editor/Runners/PyodideRunner/pygal.js b/src/components/Editor/Runners/PyodideRunner/pygal.js index 63cd94247..c1f095def 100644 --- a/src/components/Editor/Runners/PyodideRunner/pygal.js +++ b/src/components/Editor/Runners/PyodideRunner/pygal.js @@ -437,9 +437,7 @@ class _Pie extends Chart { } } chart.tooltip = { - formatter: function () { - return this.key + ": " + this.y; - }, + // A patch for the tooltip formatter function has been added in VisualOutputPane.jsx because functions cannot be passed through using postMessage. }; chart.plotOptions = { pie: { From b1683fe1af3426733ee7780aca5d796ffd8c142c Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Mon, 8 Apr 2024 13:05:26 +0100 Subject: [PATCH 22/59] couple of regex patches to eliminate some edge cases --- .../Editor/Runners/PythonRunner/PythonRunner.jsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index af7fd38d8..b8e37f2e7 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -17,9 +17,14 @@ const PythonRunner = () => { useEffect(() => { const getImports = (code) => { + const codeWithoutMultilineStrings = code.replace( + /'''[\s\S]*?'''|"""[\s\S]*?"""/gm, + "", + ); const importRegex = - /^(from\s+([a-zA-Z0-9_.]+)(\s+import\s+([a-zA-Z0-9_.]+))?)|^(import\s+([a-zA-Z0-9_.]+))/gm; - const matches = code.match(importRegex); + /(?<=^\s*)(from\s+([a-zA-Z0-9_.]+)(\s+import\s+([a-zA-Z0-9_.]+))?)|(?<=^\s*)(import\s+([a-zA-Z0-9_.]+))/gm; + const matches = codeWithoutMultilineStrings.match(importRegex); + console.log(matches); const imports = matches ? matches.map( (match) => @@ -30,7 +35,7 @@ const PythonRunner = () => { ) : []; if (code.includes(`# ${t("input.comment.py5")}`)) { - imports.push("py5"); + imports.push("py5_imported"); } return imports; }; From f76ea51b896fbfc8fd70937db3bfebbb148b1c84 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Mon, 8 Apr 2024 14:49:51 +0100 Subject: [PATCH 23/59] tidying --- .../PyodideRunner/PyodideRunner.jsx | 16 +++++++-------- .../PyodideRunner/PyodideRunner.test.js | 0 .../PyodideRunner/PyodideWorker.js | 0 .../PyodideRunner/PyodideWorker.mock.js | 0 .../PyodideRunner/VisualOutputPane.jsx | 2 +- .../PyodideRunner/VisualOutputPane.test.js | 0 .../PyodideRunner/_internal_sense_hat.js | 0 .../{ => PythonRunner}/PyodideRunner/pygal.js | 0 .../Runners/PythonRunner/PythonRunner.jsx | 4 ++-- .../{ => SkulptRunner}/SkulptRunner.jsx | 20 +++++++++---------- .../{ => SkulptRunner}/SkulptRunner.test.js | 6 +++--- .../Runners/PythonRunner/VisualOutputPane.jsx | 1 - 12 files changed, 24 insertions(+), 25 deletions(-) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/PyodideRunner.jsx (95%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/PyodideRunner.test.js (100%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/PyodideWorker.js (100%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/PyodideWorker.mock.js (100%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/VisualOutputPane.jsx (97%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/VisualOutputPane.test.js (100%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/_internal_sense_hat.js (100%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/pygal.js (100%) rename src/components/Editor/Runners/PythonRunner/{ => SkulptRunner}/SkulptRunner.jsx (96%) rename src/components/Editor/Runners/PythonRunner/{ => SkulptRunner}/SkulptRunner.test.js (99%) diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx similarity index 95% rename from src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx index f51bd2ed8..97478f5bf 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx @@ -1,7 +1,7 @@ /* eslint import/no-webpack-loader-syntax: off */ /* eslint-disable react-hooks/exhaustive-deps */ -import "../../../../assets/stylesheets/PythonRunner.scss"; +import "../../../../../assets/stylesheets/PythonRunner.scss"; import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; @@ -9,16 +9,16 @@ import { setError, codeRunHandled, loadingRunner, -} from "../../../../redux/EditorSlice"; +} from "../../../../../redux/EditorSlice"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import { useMediaQuery } from "react-responsive"; -import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints"; -import ErrorMessage from "../../ErrorMessage/ErrorMessage"; -import { createError } from "../../../../utils/apiCallHandler"; +import { MOBILE_MEDIA_QUERY } from "../../../../../utils/mediaQueryBreakpoints"; +import ErrorMessage from "../../../ErrorMessage/ErrorMessage"; +import { createError } from "../../../../../utils/apiCallHandler"; import VisualOutputPane from "./VisualOutputPane"; -import OutputViewToggle from "../PythonRunner/OutputViewToggle"; -import { SettingsContext } from "../../../../utils/settings"; -import RunnerControls from "../../../RunButton/RunnerControls"; +import OutputViewToggle from "../OutputViewToggle"; +import { SettingsContext } from "../../../../../utils/settings"; +import RunnerControls from "../../../../RunButton/RunnerControls"; import PyodideWorker from "worker-loader!./PyodideWorker.js"; const PyodideRunner = ({ active }) => { diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.test.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.test.js similarity index 100% rename from src/components/Editor/Runners/PyodideRunner/PyodideRunner.test.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.test.js diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.js similarity index 100% rename from src/components/Editor/Runners/PyodideRunner/PyodideWorker.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.js diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideWorker.mock.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.mock.js similarity index 100% rename from src/components/Editor/Runners/PyodideRunner/PyodideWorker.mock.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.mock.js diff --git a/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.jsx similarity index 97% rename from src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.jsx index 9bad49974..10b5295c0 100644 --- a/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef } from "react"; import { useSelector } from "react-redux"; -import AstroPiModel from "../../../AstroPiModel/AstroPiModel"; +import AstroPiModel from "../../../../AstroPiModel/AstroPiModel"; import Highcharts from "highcharts"; const VisualOutputPane = ({ visuals, setVisuals }) => { diff --git a/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.test.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.test.js similarity index 100% rename from src/components/Editor/Runners/PyodideRunner/VisualOutputPane.test.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.test.js diff --git a/src/components/Editor/Runners/PyodideRunner/_internal_sense_hat.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/_internal_sense_hat.js similarity index 100% rename from src/components/Editor/Runners/PyodideRunner/_internal_sense_hat.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/_internal_sense_hat.js diff --git a/src/components/Editor/Runners/PyodideRunner/pygal.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/pygal.js similarity index 100% rename from src/components/Editor/Runners/PyodideRunner/pygal.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/pygal.js diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index b8e37f2e7..58ddf11bc 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; -import PyodideRunner from "../PyodideRunner/PyodideRunner"; -import SkulptRunner from "./SkulptRunner"; +import PyodideRunner from "./PyodideRunner/PyodideRunner"; +import SkulptRunner from "./SkulptRunner/SkulptRunner"; import { useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx similarity index 96% rename from src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx rename to src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx index 720f53121..1e1ca2e67 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx @@ -1,5 +1,5 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import "../../../../assets/stylesheets/PythonRunner.scss"; +import "../../../../../assets/stylesheets/PythonRunner.scss"; import React, { useContext, useEffect, useRef, useState } from "react"; import { useSelector, useDispatch } from "react-redux"; import { useTranslation } from "react-i18next"; @@ -12,15 +12,15 @@ import { stopDraw, setSenseHatEnabled, triggerDraw, -} from "../../../../redux/EditorSlice"; -import ErrorMessage from "../../ErrorMessage/ErrorMessage"; -import { createError } from "../../../../utils/apiCallHandler"; -import store from "../../../../app/store"; -import VisualOutputPane from "./VisualOutputPane"; -import OutputViewToggle from "./OutputViewToggle"; -import { SettingsContext } from "../../../../utils/settings"; -import RunnerControls from "../../../RunButton/RunnerControls"; -import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints"; +} from "../../../../../redux/EditorSlice"; +import ErrorMessage from "../../../ErrorMessage/ErrorMessage"; +import { createError } from "../../../../../utils/apiCallHandler"; +import store from "../../../../../app/store"; +import VisualOutputPane from "../VisualOutputPane"; +import OutputViewToggle from "../OutputViewToggle"; +import { SettingsContext } from "../../../../../utils/settings"; +import RunnerControls from "../../../../RunButton/RunnerControls"; +import { MOBILE_MEDIA_QUERY } from "../../../../../utils/mediaQueryBreakpoints"; const externalLibraries = { "./pygal/__init__.js": { diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner.test.js b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.test.js similarity index 99% rename from src/components/Editor/Runners/PythonRunner/SkulptRunner.test.js rename to src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.test.js index 4a6fead06..6969223c4 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner.test.js +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.test.js @@ -8,10 +8,10 @@ import { codeRunHandled, setError, triggerDraw, -} from "../../../../redux/EditorSlice"; -import { SettingsContext } from "../../../../utils/settings"; +} from "../../../../../redux/EditorSlice"; +import { SettingsContext } from "../../../../../utils/settings"; import { matchMedia, setMedia } from "mock-match-media"; -import { MOBILE_BREAKPOINT } from "../../../../utils/mediaQueryBreakpoints"; +import { MOBILE_BREAKPOINT } from "../../../../../utils/mediaQueryBreakpoints"; let mockMediaQuery = (query) => { return matchMedia(query).matches; diff --git a/src/components/Editor/Runners/PythonRunner/VisualOutputPane.jsx b/src/components/Editor/Runners/PythonRunner/VisualOutputPane.jsx index 37dfd5eb3..a39b95b4a 100644 --- a/src/components/Editor/Runners/PythonRunner/VisualOutputPane.jsx +++ b/src/components/Editor/Runners/PythonRunner/VisualOutputPane.jsx @@ -36,7 +36,6 @@ const VisualOutputPane = () => { Sk.p5 = {}; Sk.p5.sketch = "p5Sketch"; - console.log(document.getElementById("p5Sketch")); Sk.p5.assets = projectImages; (Sk.pygal || (Sk.pygal = {})).outputCanvas = pygalOutput.current; From fdc914dac098ca1ca43f956437d9e0a025dd6b58 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Mon, 8 Apr 2024 15:22:09 +0100 Subject: [PATCH 24/59] moving pyodiderunner back for now to avoid conflicts --- .../PyodideRunner/PyodideRunner.jsx | 14 +++--- .../PyodideRunner/PyodideRunner.test.js | 0 .../PyodideRunner/PyodideWorker.js | 0 .../PyodideRunner/PyodideWorker.mock.js | 0 .../PyodideRunner/VisualOutputPane.jsx | 2 +- .../PyodideRunner/VisualOutputPane.test.js | 0 .../PyodideRunner/_internal_sense_hat.js | 0 .../{PythonRunner => }/PyodideRunner/pygal.js | 0 .../Runners/PythonRunner/PythonRunner.jsx | 6 ++- .../Runners/PythonRunner/PythonRunner.test.js | 46 +++++++++++++++++++ 10 files changed, 59 insertions(+), 9 deletions(-) rename src/components/Editor/Runners/{PythonRunner => }/PyodideRunner/PyodideRunner.jsx (96%) rename src/components/Editor/Runners/{PythonRunner => }/PyodideRunner/PyodideRunner.test.js (100%) rename src/components/Editor/Runners/{PythonRunner => }/PyodideRunner/PyodideWorker.js (100%) rename src/components/Editor/Runners/{PythonRunner => }/PyodideRunner/PyodideWorker.mock.js (100%) rename src/components/Editor/Runners/{PythonRunner => }/PyodideRunner/VisualOutputPane.jsx (97%) rename src/components/Editor/Runners/{PythonRunner => }/PyodideRunner/VisualOutputPane.test.js (100%) rename src/components/Editor/Runners/{PythonRunner => }/PyodideRunner/_internal_sense_hat.js (100%) rename src/components/Editor/Runners/{PythonRunner => }/PyodideRunner/pygal.js (100%) create mode 100644 src/components/Editor/Runners/PythonRunner/PythonRunner.test.js diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx similarity index 96% rename from src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx rename to src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx index 97478f5bf..db154244c 100644 --- a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx @@ -9,16 +9,16 @@ import { setError, codeRunHandled, loadingRunner, -} from "../../../../../redux/EditorSlice"; +} from "../../../../redux/EditorSlice"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import { useMediaQuery } from "react-responsive"; -import { MOBILE_MEDIA_QUERY } from "../../../../../utils/mediaQueryBreakpoints"; -import ErrorMessage from "../../../ErrorMessage/ErrorMessage"; -import { createError } from "../../../../../utils/apiCallHandler"; +import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints"; +import ErrorMessage from "../../ErrorMessage/ErrorMessage"; +import { createError } from "../../../../utils/apiCallHandler"; import VisualOutputPane from "./VisualOutputPane"; -import OutputViewToggle from "../OutputViewToggle"; -import { SettingsContext } from "../../../../../utils/settings"; -import RunnerControls from "../../../../RunButton/RunnerControls"; +import OutputViewToggle from "../PythonRunner/OutputViewToggle"; +import { SettingsContext } from "../../../../utils/settings"; +import RunnerControls from "../../../RunButton/RunnerControls"; import PyodideWorker from "worker-loader!./PyodideWorker.js"; const PyodideRunner = ({ active }) => { diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.test.js b/src/components/Editor/Runners/PyodideRunner/PyodideRunner.test.js similarity index 100% rename from src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.test.js rename to src/components/Editor/Runners/PyodideRunner/PyodideRunner.test.js diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.js b/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js similarity index 100% rename from src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.js rename to src/components/Editor/Runners/PyodideRunner/PyodideWorker.js diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.mock.js b/src/components/Editor/Runners/PyodideRunner/PyodideWorker.mock.js similarity index 100% rename from src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.mock.js rename to src/components/Editor/Runners/PyodideRunner/PyodideWorker.mock.js diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.jsx b/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx similarity index 97% rename from src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.jsx rename to src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx index 10b5295c0..9bad49974 100644 --- a/src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.jsx +++ b/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef } from "react"; import { useSelector } from "react-redux"; -import AstroPiModel from "../../../../AstroPiModel/AstroPiModel"; +import AstroPiModel from "../../../AstroPiModel/AstroPiModel"; import Highcharts from "highcharts"; const VisualOutputPane = ({ visuals, setVisuals }) => { diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.test.js b/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.test.js similarity index 100% rename from src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.test.js rename to src/components/Editor/Runners/PyodideRunner/VisualOutputPane.test.js diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/_internal_sense_hat.js b/src/components/Editor/Runners/PyodideRunner/_internal_sense_hat.js similarity index 100% rename from src/components/Editor/Runners/PythonRunner/PyodideRunner/_internal_sense_hat.js rename to src/components/Editor/Runners/PyodideRunner/_internal_sense_hat.js diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/pygal.js b/src/components/Editor/Runners/PyodideRunner/pygal.js similarity index 100% rename from src/components/Editor/Runners/PythonRunner/PyodideRunner/pygal.js rename to src/components/Editor/Runners/PyodideRunner/pygal.js diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index 58ddf11bc..839be7bad 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; -import PyodideRunner from "./PyodideRunner/PyodideRunner"; +import PyodideRunner from "../PyodideRunner/PyodideRunner"; import SkulptRunner from "./SkulptRunner/SkulptRunner"; import { useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; @@ -15,6 +15,10 @@ const PythonRunner = () => { const [usePyodide, setUsePyodide] = useState(false); const { t } = useTranslation(); + useEffect(() => { + console.log("usePyodide", usePyodide); + }, [usePyodide]); + useEffect(() => { const getImports = (code) => { const codeWithoutMultilineStrings = code.replace( diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js b/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js new file mode 100644 index 000000000..f46861bc8 --- /dev/null +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js @@ -0,0 +1,46 @@ +import { render } from "@testing-library/react"; +import { Provider } from "react-redux"; +import configureStore from "redux-mock-store"; +import PythonRunner from "./PythonRunner"; + +// describe("When in tabbed view", () => { +let store; + +// beforeEach(() => { +const middlewares = []; +const mockStore = configureStore(middlewares); +const initialState = { + editor: { + project: { + components: [ + { + name: "main", + extension: "py", + content: "", + }, + ], + }, + }, + auth: {}, +}; + +const renderRunnerWithCode = (code = "") => { + let state = initialState; + state.editor.project.components[0].content = code; + store = mockStore(state); + render( + + + , + ); +}; + +test("Renders with Pyodide runner initially", () => { + renderRunnerWithCode(); + expect(document.querySelector(".pyodiderunner")).toHaveStyle("display: flex"); + expect(document.querySelector(".skulptrunner")).toHaveStyle("display: none"); +}); + +// test("Pyodide is used when no Skulpt-only modules are imported", () => { +// renderRunnerWithCode("import math"); +// }); From 1b82f4232c6885c5eacf3d7383ef47eaf24095e8 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Mon, 8 Apr 2024 15:44:36 +0100 Subject: [PATCH 25/59] tidying --- .../PyodideRunner/PyodideRunner.jsx | 14 +++++++------- .../PyodideRunner/PyodideRunner.test.js | 2 +- .../PyodideRunner/PyodideWorker.js | 0 .../PyodideRunner/PyodideWorker.mock.js | 0 .../PyodideRunner/PyodideWorker.test.js | 0 .../PyodideRunner/VisualOutputPane.jsx | 2 +- .../PyodideRunner/VisualOutputPane.test.js | 0 .../PyodideRunner/_internal_sense_hat.js | 0 .../{ => PythonRunner}/PyodideRunner/pygal.js | 0 .../Editor/Runners/PythonRunner/PythonRunner.jsx | 2 +- .../Runners/PythonRunner/PythonRunner.test.js | 7 ++++--- src/utils/setupTests.js | 2 +- 12 files changed, 15 insertions(+), 14 deletions(-) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/PyodideRunner.jsx (96%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/PyodideRunner.test.js (99%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/PyodideWorker.js (100%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/PyodideWorker.mock.js (100%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/PyodideWorker.test.js (100%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/VisualOutputPane.jsx (97%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/VisualOutputPane.test.js (100%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/_internal_sense_hat.js (100%) rename src/components/Editor/Runners/{ => PythonRunner}/PyodideRunner/pygal.js (100%) diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx similarity index 96% rename from src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx index c490e508e..295f3ace9 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx @@ -9,16 +9,16 @@ import { setError, codeRunHandled, loadingRunner, -} from "../../../../redux/EditorSlice"; +} from "../../../../../redux/EditorSlice"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import { useMediaQuery } from "react-responsive"; -import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints"; -import ErrorMessage from "../../ErrorMessage/ErrorMessage"; -import { createError } from "../../../../utils/apiCallHandler"; +import { MOBILE_MEDIA_QUERY } from "../../../../../utils/mediaQueryBreakpoints"; +import ErrorMessage from "../../../ErrorMessage/ErrorMessage"; +import { createError } from "../../../../../utils/apiCallHandler"; import VisualOutputPane from "./VisualOutputPane"; -import OutputViewToggle from "../PythonRunner/OutputViewToggle"; -import { SettingsContext } from "../../../../utils/settings"; -import RunnerControls from "../../../RunButton/RunnerControls"; +import OutputViewToggle from "../OutputViewToggle"; +import { SettingsContext } from "../../../../../utils/settings"; +import RunnerControls from "../../../../RunButton/RunnerControls"; const PyodideRunner = ({ active }) => { const pyodideWorker = useMemo( diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.test.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.test.js similarity index 99% rename from src/components/Editor/Runners/PyodideRunner/PyodideRunner.test.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.test.js index fe0f125ea..b410eefcb 100644 --- a/src/components/Editor/Runners/PyodideRunner/PyodideRunner.test.js +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.test.js @@ -4,7 +4,7 @@ import configureStore from "redux-mock-store"; import PyodideRunner from "./PyodideRunner"; import { Provider } from "react-redux"; import PyodideWorker, { postMessage } from "./PyodideWorker.mock.js"; -import { setError } from "../../../../redux/EditorSlice.js"; +import { setError } from "../../../../../redux/EditorSlice.js"; jest.mock("fs"); diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideWorker.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.js similarity index 100% rename from src/components/Editor/Runners/PyodideRunner/PyodideWorker.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.js diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideWorker.mock.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.mock.js similarity index 100% rename from src/components/Editor/Runners/PyodideRunner/PyodideWorker.mock.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.mock.js diff --git a/src/components/Editor/Runners/PyodideRunner/PyodideWorker.test.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.test.js similarity index 100% rename from src/components/Editor/Runners/PyodideRunner/PyodideWorker.test.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.test.js diff --git a/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.jsx similarity index 97% rename from src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.jsx index 9bad49974..10b5295c0 100644 --- a/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef } from "react"; import { useSelector } from "react-redux"; -import AstroPiModel from "../../../AstroPiModel/AstroPiModel"; +import AstroPiModel from "../../../../AstroPiModel/AstroPiModel"; import Highcharts from "highcharts"; const VisualOutputPane = ({ visuals, setVisuals }) => { diff --git a/src/components/Editor/Runners/PyodideRunner/VisualOutputPane.test.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.test.js similarity index 100% rename from src/components/Editor/Runners/PyodideRunner/VisualOutputPane.test.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.test.js diff --git a/src/components/Editor/Runners/PyodideRunner/_internal_sense_hat.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/_internal_sense_hat.js similarity index 100% rename from src/components/Editor/Runners/PyodideRunner/_internal_sense_hat.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/_internal_sense_hat.js diff --git a/src/components/Editor/Runners/PyodideRunner/pygal.js b/src/components/Editor/Runners/PythonRunner/PyodideRunner/pygal.js similarity index 100% rename from src/components/Editor/Runners/PyodideRunner/pygal.js rename to src/components/Editor/Runners/PythonRunner/PyodideRunner/pygal.js diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index 839be7bad..c998b906d 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; -import PyodideRunner from "../PyodideRunner/PyodideRunner"; +import PyodideRunner from "./PyodideRunner/PyodideRunner"; import SkulptRunner from "./SkulptRunner/SkulptRunner"; import { useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js b/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js index f46861bc8..9fcddc614 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js @@ -41,6 +41,7 @@ test("Renders with Pyodide runner initially", () => { expect(document.querySelector(".skulptrunner")).toHaveStyle("display: none"); }); -// test("Pyodide is used when no Skulpt-only modules are imported", () => { -// renderRunnerWithCode("import math"); -// }); +test("Pyodide is used when no Skulpt-only modules are imported", () => { + renderRunnerWithCode("import math"); + expect(document.querySelector(".pyodiderunner")).toHaveStyle("display: flex"); +}); diff --git a/src/utils/setupTests.js b/src/utils/setupTests.js index 47c421347..d5c955c03 100644 --- a/src/utils/setupTests.js +++ b/src/utils/setupTests.js @@ -3,7 +3,7 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import "@testing-library/jest-dom"; -import PyodideWorker from "../components/Editor/Runners/PyodideRunner/PyodideWorker.mock.js"; +import PyodideWorker from "../components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.mock.js"; /* global globalThis */ globalThis.IS_REACT_ACT_ENVIRONMENT = true; From 68d85dc716511bbba74a611e39866dba7f79916f Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Tue, 9 Apr 2024 11:20:47 +0100 Subject: [PATCH 26/59] changing and fixing tests --- .../PythonRunner/PyodideRunner/PyodideRunner.jsx | 2 +- .../Editor/Runners/PythonRunner/PythonRunner.jsx | 7 +------ .../Editor/Runners/PythonRunner/PythonRunner.test.js | 2 +- .../PythonRunner/SkulptRunner/SkulptRunner.jsx | 2 +- .../PythonRunner/SkulptRunner/SkulptRunner.test.js | 2 +- .../__snapshots__/EmbeddedViewer.test.js.snap | 4 ++-- .../WebComponentProject/WebComponentProject.test.js | 12 ++++++++++-- 7 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx index 295f3ace9..d5df5f3bd 100644 --- a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx @@ -47,7 +47,7 @@ const PyodideRunner = ({ active }) => { const showVisualTab = queryParams.get("show_visual_tab") === "true"; const [hasVisual, setHasVisual] = useState(showVisualTab || senseHatAlways); const [visuals, setVisuals] = useState([]); - const [showRunner, setShowRunner] = useState(false); + const [showRunner, setShowRunner] = useState(true); useEffect(() => { pyodideWorker.onmessage = ({ data }) => { diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index c998b906d..4275b212c 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -12,13 +12,9 @@ const PythonRunner = () => { const codeRunTriggered = useSelector( (state) => state.editor.codeRunTriggered, ); - const [usePyodide, setUsePyodide] = useState(false); + const [usePyodide, setUsePyodide] = useState(true); const { t } = useTranslation(); - useEffect(() => { - console.log("usePyodide", usePyodide); - }, [usePyodide]); - useEffect(() => { const getImports = (code) => { const codeWithoutMultilineStrings = code.replace( @@ -28,7 +24,6 @@ const PythonRunner = () => { const importRegex = /(?<=^\s*)(from\s+([a-zA-Z0-9_.]+)(\s+import\s+([a-zA-Z0-9_.]+))?)|(?<=^\s*)(import\s+([a-zA-Z0-9_.]+))/gm; const matches = codeWithoutMultilineStrings.match(importRegex); - console.log(matches); const imports = matches ? matches.map( (match) => diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js b/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js index 9fcddc614..a2c37c8f5 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js @@ -42,6 +42,6 @@ test("Renders with Pyodide runner initially", () => { }); test("Pyodide is used when no Skulpt-only modules are imported", () => { - renderRunnerWithCode("import math"); + renderRunnerWithCode("import p5"); expect(document.querySelector(".pyodiderunner")).toHaveStyle("display: flex"); }); diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx index 1e1ca2e67..c3397db5b 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx @@ -79,7 +79,7 @@ const SkulptRunner = ({ active }) => { queryParams.get("show_visual_tab") === "true" || senseHatAlwaysEnabled, ); - const [showRunner, setShowRunner] = useState(true); + const [showRunner, setShowRunner] = useState(false); const getInput = () => { const pageInput = document.getElementById("input"); diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.test.js b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.test.js index 6969223c4..663062c4d 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.test.js +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.test.js @@ -130,7 +130,6 @@ describe("Testing stopping the code run with input", () => { ], image_list: [], }, - codeRunTriggered: true, codeRunStopped: true, }, auth: { @@ -141,6 +140,7 @@ describe("Testing stopping the code run with input", () => { render( + , ); }); diff --git a/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap b/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap index f52c1c61e..2b27c7c6b 100644 --- a/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap +++ b/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap @@ -14,7 +14,7 @@ exports[`Renders without crashing 1`] = ` >