diff --git a/CHANGELOG.md b/CHANGELOG.md
index ff853b266..ec1db3ed5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## Unreleased
+
+## [0.28.14] - 2025-01-06
+
+### Fixed
+
+- Support for the `outputPanels` attribute in the `SkulptRunner` (#1158)
+
## [0.28.13] - 2024-12-18
### Changed
@@ -1019,7 +1027,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Events in Web Component indicating whether Mission Zero criteria have been met (#113)
-[unreleased]: https://github.com/RaspberryPiFoundation/editor-ui/compare/v0.28.13...HEAD
+[unreleased]: https://github.com/RaspberryPiFoundation/editor-ui/compare/v0.28.14...HEAD
+[0.28.14]: https://github.com/RaspberryPiFoundation/editor-ui/releases/tag/v0.28.14
[0.28.13]: https://github.com/RaspberryPiFoundation/editor-ui/releases/tag/v0.28.13
[0.28.12]: https://github.com/RaspberryPiFoundation/editor-ui/releases/tag/v0.28.12
[0.28.11]: https://github.com/RaspberryPiFoundation/editor-ui/releases/tag/v0.28.11
diff --git a/package.json b/package.json
index 2d05fb030..bbda89f88 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@raspberrypifoundation/editor-ui",
- "version": "0.28.13",
+ "version": "0.28.14",
"private": true,
"dependencies": {
"@apollo/client": "^3.7.8",
diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx
index cc34e65cb..48babc88b 100644
--- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx
+++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx
@@ -15,7 +15,7 @@ const SKULPT_ONLY_MODULES = [
"turtle",
];
-const PythonRunner = () => {
+const PythonRunner = ({ outputPanels = ["text", "visual"] }) => {
const dispatch = useDispatch();
const project = useSelector((state) => state.editor.project);
@@ -93,7 +93,10 @@ const PythonRunner = () => {
return (
<>
-
+
>
);
};
diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js b/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js
index 7a93dd81b..b92f9cf3f 100644
--- a/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js
+++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.test.js
@@ -1,4 +1,4 @@
-import { render } from "@testing-library/react";
+import { render, within } from "@testing-library/react";
import { Provider } from "react-redux";
import { act } from "react-dom/test-utils";
import PythonRunner from "./PythonRunner";
@@ -56,110 +56,151 @@ const updateRunner = ({
store.dispatch(setSenseHatAlwaysEnabled(senseHatAlwaysEnabled));
});
};
+describe("PythonRunner with default props", () => {
+ beforeEach(() => {
+ window.crossOriginIsolated = true;
+ render(
+
+
+ ,
+ );
+ updateRunner({ code: "print('some loaded code')" });
+ });
-beforeEach(() => {
- window.crossOriginIsolated = true;
- render(
-
-
- ,
- );
- updateRunner({ code: "print('some loaded code')" });
-});
+ test("Renders with Pyodide runner initially", () => {
+ updateRunner({});
+ expect(
+ document.querySelector(".skulptrunner--active"),
+ ).not.toBeInTheDocument();
+ expect(
+ document.querySelector(".pyodiderunner--active"),
+ ).toBeInTheDocument();
+ });
-test("Renders with Pyodide runner initially", () => {
- updateRunner({});
- expect(
- document.querySelector(".skulptrunner--active"),
- ).not.toBeInTheDocument();
- expect(document.querySelector(".pyodiderunner--active")).toBeInTheDocument();
-});
+ test("Uses pyodide when no skulpt-only modules are imported", () => {
+ updateRunner({ code: "import math" });
+ expect(
+ document.querySelector(".skulptrunner--active"),
+ ).not.toBeInTheDocument();
+ expect(
+ document.querySelector(".pyodiderunner--active"),
+ ).toBeInTheDocument();
+ });
-test("Uses pyodide when no skulpt-only modules are imported", () => {
- updateRunner({ code: "import math" });
- expect(
- document.querySelector(".skulptrunner--active"),
- ).not.toBeInTheDocument();
- expect(document.querySelector(".pyodiderunner--active")).toBeInTheDocument();
-});
+ test("Uses skulpt when skulpt-only modules are imported", () => {
+ updateRunner({ code: "import p5" });
+ expect(
+ document.querySelector(".pyodiderunner--active"),
+ ).not.toBeInTheDocument();
+ expect(document.querySelector(".skulptrunner--active")).toBeInTheDocument();
+ });
-test("Uses skulpt when skulpt-only modules are imported", () => {
- updateRunner({ code: "import p5" });
- expect(
- document.querySelector(".pyodiderunner--active"),
- ).not.toBeInTheDocument();
- expect(document.querySelector(".skulptrunner--active")).toBeInTheDocument();
-});
+ test("Uses skulpt when senseHatAlwaysEnabled is true", () => {
+ updateRunner({ code: "import math", senseHatAlwaysEnabled: true });
+ expect(
+ document.querySelector(".pyodiderunner--active"),
+ ).not.toBeInTheDocument();
+ expect(document.querySelector(".skulptrunner--active")).toBeInTheDocument();
+ });
-test("Uses skulpt when senseHatAlwaysEnabled is true", () => {
- updateRunner({ code: "import math", senseHatAlwaysEnabled: true });
- expect(
- document.querySelector(".pyodiderunner--active"),
- ).not.toBeInTheDocument();
- expect(document.querySelector(".skulptrunner--active")).toBeInTheDocument();
-});
+ test("Uses skulpt if not cross origin isolated", () => {
+ window.crossOriginIsolated = false;
+ updateRunner({ code: "import math" });
+ expect(
+ document.querySelector(".pyodiderunner--active"),
+ ).not.toBeInTheDocument();
+ expect(document.querySelector(".skulptrunner--active")).toBeInTheDocument();
+ });
-test("Uses skulpt if not cross origin isolated", () => {
- window.crossOriginIsolated = false;
- updateRunner({ code: "import math" });
- expect(
- document.querySelector(".pyodiderunner--active"),
- ).not.toBeInTheDocument();
- expect(document.querySelector(".skulptrunner--active")).toBeInTheDocument();
-});
+ test("Switches runners if the import has a from clause", () => {
+ updateRunner({ code: "from p5 import *" });
+ expect(document.querySelector(".skulptrunner--active")).toBeInTheDocument();
+ expect(
+ document.querySelector(".pyodiderunner--active"),
+ ).not.toBeInTheDocument();
+ });
-test("Switches runners if the import has a from clause", () => {
- updateRunner({ code: "from p5 import *" });
- expect(document.querySelector(".skulptrunner--active")).toBeInTheDocument();
- expect(
- document.querySelector(".pyodiderunner--active"),
- ).not.toBeInTheDocument();
-});
+ test("Switches runners if the import is indented", () => {
+ updateRunner({ code: " import p5" });
+ expect(document.querySelector(".skulptrunner--active")).toBeInTheDocument();
+ expect(
+ document.querySelector(".pyodiderunner--active"),
+ ).not.toBeInTheDocument();
+ });
-test("Switches runners if the import is indented", () => {
- updateRunner({ code: " import p5" });
- expect(document.querySelector(".skulptrunner--active")).toBeInTheDocument();
- expect(
- document.querySelector(".pyodiderunner--active"),
- ).not.toBeInTheDocument();
-});
+ test("Uses skulpt if the py5 magic comment is used", () => {
+ updateRunner({ code: "# input.comment.py5" });
+ expect(document.querySelector(".skulptrunner--active")).toBeInTheDocument();
+ expect(
+ document.querySelector(".pyodiderunner--active"),
+ ).not.toBeInTheDocument();
+ });
-test("Uses skulpt if the py5 magic comment is used", () => {
- updateRunner({ code: "# input.comment.py5" });
- expect(document.querySelector(".skulptrunner--active")).toBeInTheDocument();
- expect(
- document.querySelector(".pyodiderunner--active"),
- ).not.toBeInTheDocument();
-});
+ test("Does not switch runners while the code is running", () => {
+ updateRunner({ code: "import p5", codeRunTriggered: true });
+ expect(
+ document.querySelector(".pyodiderunner--active"),
+ ).toBeInTheDocument();
+ expect(
+ document.querySelector(".skulptrunner--active"),
+ ).not.toBeInTheDocument();
+ });
-test("Does not switch runners while the code is running", () => {
- updateRunner({ code: "import p5", codeRunTriggered: true });
- expect(document.querySelector(".pyodiderunner--active")).toBeInTheDocument();
- expect(
- document.querySelector(".skulptrunner--active"),
- ).not.toBeInTheDocument();
-});
+ test("Does not switch runners if the import is in a comment", () => {
+ updateRunner({ code: "# import p5" });
+ expect(
+ document.querySelector(".pyodiderunner--active"),
+ ).toBeInTheDocument();
+ expect(
+ document.querySelector(".skulptrunner--active"),
+ ).not.toBeInTheDocument();
+ });
-test("Does not switch runners if the import is in a comment", () => {
- updateRunner({ code: "# import p5" });
- expect(document.querySelector(".pyodiderunner--active")).toBeInTheDocument();
- expect(
- document.querySelector(".skulptrunner--active"),
- ).not.toBeInTheDocument();
-});
+ test("Does not switch runners if the import is in a string", () => {
+ updateRunner({ code: 'print("import p5")' });
+ expect(
+ document.querySelector(".pyodiderunner--active"),
+ ).toBeInTheDocument();
+ expect(
+ document.querySelector(".skulptrunner--active"),
+ ).not.toBeInTheDocument();
+ });
-test("Does not switch runners if the import is in a string", () => {
- updateRunner({ code: 'print("import p5")' });
- expect(document.querySelector(".pyodiderunner--active")).toBeInTheDocument();
- expect(
- document.querySelector(".skulptrunner--active"),
- ).not.toBeInTheDocument();
+ test("Does not switch runners if the import is in a multiline string", () => {
+ updateRunner({ code: '"""\nimport p5\n"""' });
+ expect(
+ document.querySelector(".pyodiderunner--active"),
+ ).toBeInTheDocument();
+ expect(
+ document.querySelector(".skulptrunner--active"),
+ ).not.toBeInTheDocument();
+ });
+
+ test("Renders the text output in the skulpt runner", () => {
+ updateRunner({ code: "import p5" });
+ const skulptRunner = document.querySelector(".skulptrunner");
+ expect(
+ within(skulptRunner).queryByText("output.textOutput"),
+ ).toBeInTheDocument();
+ });
});
-test("Does not switch runners if the import is in a multiline string", () => {
- updateRunner({ code: '"""\nimport p5\n"""' });
- expect(document.querySelector(".pyodiderunner--active")).toBeInTheDocument();
- expect(
- document.querySelector(".skulptrunner--active"),
- ).not.toBeInTheDocument();
+describe("PythonRunner with output panels set to visual only", () => {
+ beforeEach(() => {
+ window.crossOriginIsolated = true;
+ render(
+
+
+ ,
+ );
+ updateRunner({ code: "print('some loaded code')" });
+ });
+
+ test("Does not render text output in the skulpt runner", () => {
+ updateRunner({ code: "import p5" });
+ const skulptRunner = document.querySelector(".skulptrunner");
+ expect(
+ within(skulptRunner).queryByText("output.textOutput"),
+ ).not.toBeInTheDocument();
+ });
});