Skip to content

Commit 73981ea

Browse files
authored
Merge pull request #937 from /issues/936-Spike_Investigate_using_pyodide_dynamically
Spike: Investigate using pyodide dynamically
2 parents 5197fac + d9b38f3 commit 73981ea

File tree

23 files changed

+1830
-1386
lines changed

23 files changed

+1830
-1386
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1010

1111
- Support to enable embedding iframes in HTML projects from in-house domains (#985)
1212
- Unit tests for `pyodide` runner (#976)
13+
- Dynamic switching between `pyodide` and `skulpt` based on user imports (#937)
14+
15+
### Changed
16+
17+
- Runner defaults to `pyodide` (#937)
1318

1419
## [0.22.2] - 2024-03-18
1520

cypress/e2e/missionZero-wc.cy.js

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,7 @@ it("loads the sense hat library", () => {
3434
});
3535

3636
it("sets initial criteria correctly", () => {
37-
cy.get("editor-wc")
38-
.shadow()
39-
.find("div[class=cm-content]")
40-
.invoke("text", "from sense_hat import SenseHat");
37+
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke("text", "");
4138
cy.get("editor-wc").shadow().find(".btn--run").click();
4239
cy.get("#results").should(
4340
"contain",
@@ -100,7 +97,10 @@ it("resets criteria correctly", () => {
10097
);
10198
cy.get("editor-wc").shadow().find(".btn--run").contains("Run").click();
10299
cy.get("#results").should("contain", '"readPressure":true');
103-
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke("text", "");
100+
cy.get("editor-wc")
101+
.shadow()
102+
.find("div[class=cm-content]")
103+
.invoke("text", "from sense_hat import SenseHat");
104104
cy.get("editor-wc").shadow().find(".btn--run").contains("Run").click();
105105
cy.get("#results").should(
106106
"contain",
@@ -139,7 +139,13 @@ it("picks up calls to input()", () => {
139139
.find("div[class=cm-content]")
140140
.invoke("text", "input()");
141141
cy.get("editor-wc").shadow().find(".btn--run").click();
142-
cy.get("editor-wc").shadow().contains("Text output").click();
142+
cy.get("editor-wc")
143+
.shadow()
144+
.find(
145+
"div[class='pythonrunner-container skulptrunner skulptrunner--active']",
146+
)
147+
.contains("Text output")
148+
.click();
143149
cy.get("editor-wc")
144150
.shadow()
145151
.find("span[contenteditable=true]")
@@ -174,7 +180,7 @@ it("does not return null duration if no change in focus", () => {
174180
.find("div[class=cm-content]")
175181
.invoke(
176182
"text",
177-
'from sense_hat import SenseHat\nsense = SenseHat()\nsense.send_message("a")',
183+
'from sense_hat import SenseHat\nsense = SenseHat()\nsense.show_message("a")',
178184
);
179185
cy.get("editor-wc").shadow().find(".btn--run").click();
180186
cy.get("#results").should("not.contain", '"duration":null');
@@ -186,7 +192,7 @@ it("does not return null duration if focus changed before code run", () => {
186192
.find("div[class=cm-content]")
187193
.invoke(
188194
"text",
189-
'from sense_hat import SenseHat\nsense = SenseHat()\nsense.send_message("a")',
195+
'from sense_hat import SenseHat\nsense = SenseHat()\nsense.show_message("a")',
190196
);
191197
cy.window().blur();
192198
cy.window().focus();
@@ -200,7 +206,7 @@ it("returns duration of null if focus is lost", () => {
200206
.find("div[class=cm-content]")
201207
.invoke(
202208
"text",
203-
'from sense_hat import SenseHat\nsense = SenseHat()\nsense.send_message("a")',
209+
'from sense_hat import SenseHat\nsense = SenseHat()\nsense.show_message("a")',
204210
);
205211
cy.get("editor-wc").shadow().find(".btn--run").click();
206212
cy.window().blur();
@@ -214,7 +220,7 @@ it("does not return duration of null if code rerun after focus lost", () => {
214220
.find("div[class=cm-content]")
215221
.invoke(
216222
"text",
217-
'from sense_hat import SenseHat\nsense = SenseHat()\nsense.send_message("a")',
223+
'from sense_hat import SenseHat\nsense = SenseHat()\nsense.show_message("a")',
218224
);
219225
cy.get("editor-wc").shadow().find(".btn--run").click();
220226
cy.window().blur();

cypress/e2e/spec-wc.cy.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ it("does not render astro pi component if sense hat unimported", () => {
7676
.find("div[class=cm-content]")
7777
.invoke("text", "import sense_hat");
7878
cy.get("editor-wc").shadow().find(".btn--run").click();
79-
cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke("text", "");
79+
cy.get("editor-wc")
80+
.shadow()
81+
.find("div[class=cm-content]")
82+
.invoke("text", "import p5");
8083
cy.get("editor-wc").shadow().find(".btn--run").click();
8184
cy.get("editor-wc").shadow().contains("Visual output").click();
8285
cy.get("editor-wc").shadow().find("#root").should("not.contain", "yaw");

src/assets/stylesheets/ErrorMessage.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
color: #7e0305;
66
background-color: #fde2e1;
77
padding: $space-0-75 $space-1-25;
8+
overflow-y: auto;
89

910
&__content {
1011
padding: 0;

src/components/Editor/Output/Output.jsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,12 @@ const Output = () => {
99
const isEmbedded = useSelector((state) => state.editor.isEmbedded);
1010
const searchParams = new URLSearchParams(window.location.search);
1111
const isBrowserPreview = searchParams.get("browserPreview") === "true";
12-
const usePyodide = searchParams.get("pyodide") === "true";
1312

1413
return (
1514
<>
1615
<ExternalFiles />
1716
<div className="proj-runner-container">
18-
<RunnerFactory
19-
projectType={project.project_type}
20-
usePyodide={usePyodide}
21-
/>
17+
<RunnerFactory projectType={project.project_type} />
2218
{isEmbedded && !isBrowserPreview ? <RunBar embedded /> : null}
2319
</div>
2420
</>

src/components/Editor/Runners/PyodideRunner/PyodideRunner.jsx renamed to src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
/* eslint import/no-webpack-loader-syntax: off */
22
/* eslint-disable react-hooks/exhaustive-deps */
33

4-
import "../../../../assets/stylesheets/PythonRunner.scss";
4+
import "../../../../../assets/stylesheets/PythonRunner.scss";
55
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
66
import { useDispatch, useSelector } from "react-redux";
77
import { useTranslation } from "react-i18next";
88
import {
99
setError,
1010
codeRunHandled,
1111
loadingRunner,
12-
} from "../../../../redux/EditorSlice";
12+
} from "../../../../../redux/EditorSlice";
1313
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
1414
import { useMediaQuery } from "react-responsive";
15-
import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints";
16-
import ErrorMessage from "../../ErrorMessage/ErrorMessage";
17-
import { createError } from "../../../../utils/apiCallHandler";
15+
import { MOBILE_MEDIA_QUERY } from "../../../../../utils/mediaQueryBreakpoints";
16+
import ErrorMessage from "../../../ErrorMessage/ErrorMessage";
17+
import { createError } from "../../../../../utils/apiCallHandler";
1818
import VisualOutputPane from "./VisualOutputPane";
19-
import OutputViewToggle from "../PythonRunner/OutputViewToggle";
20-
import { SettingsContext } from "../../../../utils/settings";
21-
import RunnerControls from "../../../RunButton/RunnerControls";
19+
import OutputViewToggle from "../OutputViewToggle";
20+
import { SettingsContext } from "../../../../../utils/settings";
21+
import RunnerControls from "../../../../RunButton/RunnerControls";
2222

23-
const PyodideRunner = () => {
23+
const PyodideRunner = ({ active }) => {
2424
const pyodideWorker = useMemo(
2525
() => new Worker("./PyodideWorker.js", { type: "module" }),
2626
[],
@@ -47,6 +47,7 @@ const PyodideRunner = () => {
4747
const showVisualTab = queryParams.get("show_visual_tab") === "true";
4848
const [hasVisual, setHasVisual] = useState(showVisualTab || senseHatAlways);
4949
const [visuals, setVisuals] = useState([]);
50+
const [showRunner, setShowRunner] = useState(active);
5051

5152
useEffect(() => {
5253
pyodideWorker.onmessage = ({ data }) => {
@@ -79,13 +80,20 @@ const PyodideRunner = () => {
7980
}, []);
8081

8182
useEffect(() => {
82-
if (codeRunTriggered) {
83+
if (codeRunTriggered && active) {
84+
console.log("running with pyodide");
8385
handleRun();
8486
}
8587
}, [codeRunTriggered]);
8688

8789
useEffect(() => {
88-
if (codeRunStopped) {
90+
if (codeRunTriggered) {
91+
setShowRunner(active);
92+
}
93+
}, [codeRunTriggered]);
94+
95+
useEffect(() => {
96+
if (codeRunStopped && active) {
8997
handleStop();
9098
}
9199
}, [codeRunStopped]);
@@ -116,7 +124,7 @@ const PyodideRunner = () => {
116124
const { content, ctrlD } = await getInputContent(element);
117125

118126
const encoder = new TextEncoder();
119-
const bytes = encoder.encode(content + "\r\n");
127+
const bytes = encoder.encode(content + "\n");
120128

121129
const previousLength = stdinBuffer.current[0];
122130
stdinBuffer.current.set(bytes, previousLength);
@@ -278,7 +286,12 @@ const PyodideRunner = () => {
278286
};
279287

280288
return (
281-
<div className={`pythonrunner-container`}>
289+
<div
290+
className={`pythonrunner-container pyodiderunner${
291+
active ? " pyodiderunner--active" : ""
292+
}`}
293+
style={{ display: showRunner ? "flex" : "none" }}
294+
>
282295
{isSplitView ? (
283296
<>
284297
{hasVisual && (

src/components/Editor/Runners/PyodideRunner/PyodideRunner.test.js renamed to src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.test.js

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import configureStore from "redux-mock-store";
44
import PyodideRunner from "./PyodideRunner";
55
import { Provider } from "react-redux";
66
import PyodideWorker, { postMessage } from "./PyodideWorker.mock.js";
7-
import { setError } from "../../../../redux/EditorSlice.js";
7+
import { setError } from "../../../../../redux/EditorSlice.js";
88

99
jest.mock("fs");
1010

@@ -25,19 +25,25 @@ const initialState = {
2525
auth: {},
2626
};
2727

28-
describe("When first loaded", () => {
28+
describe("When active and first loaded", () => {
2929
beforeEach(() => {
3030
const store = mockStore(initialState);
3131
render(
3232
<Provider store={store}>
33-
<PyodideRunner />,
33+
<PyodideRunner active={true} />,
3434
</Provider>,
3535
);
3636
});
3737

3838
test("it renders successfully", () => {
3939
expect(screen.queryByText("output.textOutput")).toBeInTheDocument();
4040
});
41+
42+
test("it has style display: flex", () => {
43+
expect(document.querySelector(".pyodiderunner")).toHaveStyle(
44+
"display: flex",
45+
);
46+
});
4147
});
4248

4349
describe("When a code run has been triggered", () => {
@@ -55,7 +61,7 @@ describe("When a code run has been triggered", () => {
5561
});
5662
render(
5763
<Provider store={store}>
58-
<PyodideRunner />,
64+
<PyodideRunner active={true} />,
5965
</Provider>,
6066
);
6167
});
@@ -94,7 +100,7 @@ describe("When the code has been stopped", () => {
94100
});
95101
render(
96102
<Provider store={store}>
97-
<PyodideRunner />,
103+
<PyodideRunner active={true} />,
98104
</Provider>,
99105
);
100106
});
@@ -112,7 +118,7 @@ describe("When loading pyodide", () => {
112118
store = mockStore(initialState);
113119
render(
114120
<Provider store={store}>
115-
<PyodideRunner />,
121+
<PyodideRunner active={true} />,
116122
</Provider>,
117123
);
118124

@@ -131,7 +137,7 @@ describe("When pyodide has loaded", () => {
131137
store = mockStore(initialState);
132138
render(
133139
<Provider store={store}>
134-
<PyodideRunner />,
140+
<PyodideRunner active={true} />,
135141
</Provider>,
136142
);
137143

@@ -151,7 +157,7 @@ describe("When input is required", () => {
151157
store = mockStore(initialState);
152158
render(
153159
<Provider store={store}>
154-
<PyodideRunner />,
160+
<PyodideRunner active={true} />,
155161
</Provider>,
156162
);
157163

@@ -192,7 +198,7 @@ describe("When output is received", () => {
192198
store = mockStore(initialState);
193199
render(
194200
<Provider store={store}>
195-
<PyodideRunner />,
201+
<PyodideRunner active={true} />,
196202
</Provider>,
197203
);
198204

@@ -215,7 +221,7 @@ describe("When visual output is received", () => {
215221
store = mockStore(initialState);
216222
render(
217223
<Provider store={store}>
218-
<PyodideRunner />,
224+
<PyodideRunner active={true} />,
219225
</Provider>,
220226
);
221227

@@ -247,7 +253,7 @@ describe("When an error is received", () => {
247253
store = mockStore(initialState);
248254
render(
249255
<Provider store={store}>
250-
<PyodideRunner />,
256+
<PyodideRunner active={true} />,
251257
</Provider>,
252258
);
253259

@@ -279,7 +285,7 @@ describe("When the code run is interrupted", () => {
279285
store = mockStore(initialState);
280286
render(
281287
<Provider store={store}>
282-
<PyodideRunner />,
288+
<PyodideRunner active={true} />,
283289
</Provider>,
284290
);
285291

@@ -302,3 +308,38 @@ describe("When the code run is interrupted", () => {
302308
);
303309
});
304310
});
311+
312+
describe("When not active and first loaded", () => {
313+
beforeEach(() => {
314+
const store = mockStore(initialState);
315+
render(
316+
<Provider store={store}>
317+
<PyodideRunner active={false} />,
318+
</Provider>,
319+
);
320+
});
321+
322+
test("it renders with display: none", () => {
323+
expect(document.querySelector(".pyodiderunner")).toHaveStyle(
324+
"display: none",
325+
);
326+
});
327+
});
328+
329+
describe("When not active and code run triggered", () => {
330+
beforeEach(() => {
331+
const store = mockStore({
332+
...initialState,
333+
editor: { ...initialState.editor, codeRunTriggered: true },
334+
});
335+
render(
336+
<Provider store={store}>
337+
<PyodideRunner active={false} />,
338+
</Provider>,
339+
);
340+
});
341+
342+
test("it does not send a message to the worker to run the python code", () => {
343+
expect(postMessage).not.toHaveBeenCalled();
344+
});
345+
});

src/components/Editor/Runners/PyodideRunner/PyodideWorker.js renamed to src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideWorker.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,9 @@ const vendoredPackages = {
183183
pygal: {
184184
before: () => {
185185
pyodide.registerJsModule("pygal", { ...pygal });
186-
pygal.config.renderChart = (content) =>
186+
pygal.config.renderChart = (content) => {
187187
postMessage({ method: "handleVisual", origin: "pygal", content });
188+
};
188189
},
189190
after: () => {},
190191
},

0 commit comments

Comments
 (0)