diff --git a/README.md b/README.md index ec7436aa1..cc733a51d 100644 --- a/README.md +++ b/README.md @@ -48,12 +48,22 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d The repo includes the Editor Web Component which shares components with the editor application but has a separate build process. -### `yarn stat:wc` +### Embedding -Runs the web component in development mode. -Open [http://localhost:3001](http://localhost:3001) to view it in the browser. +The web component can be included in a page by using the `` HTML element. It takes the following attributes -There is no production build setup for the web component at present. +* `code`: A preset blob of code to show in the editor pane. +* `sense_hat_always_enabled`: Show the Astro Pi Sense HAT emulator on page load + +### `yarn start:wc` + +Runs the web component in development mode. Open [http://localhost:3001](http://localhost:3001) to view it in the browser. + +**NB** You need to have the main `yarn start` process running too. + +It is possible to add query strings to control how the web component is configured. Any HTML attribute can be set on the query string, including `class`, `style` etc. + +For example, to load the page with the Sense Hat always showing, add [`?sense_hat_always_enabled` to the URL](http://localhost:3001?sense_hat_always_enabled) ## Review apps diff --git a/cypress/e2e/missionZero-wc.cy.js b/cypress/e2e/missionZero-wc.cy.js index f772a3b42..cfd425c31 100644 --- a/cypress/e2e/missionZero-wc.cy.js +++ b/cypress/e2e/missionZero-wc.cy.js @@ -1,7 +1,22 @@ const baseUrl = "http://localhost:3001" beforeEach(() => { - cy.visit(baseUrl) + cy.visit(`${baseUrl}?sense_hat_always_enabled=true`) +}) + +it("defaults to the visual output tab", () => { + const runnerContainer = cy.get("editor-wc").shadow().find('.proj-runner-container') + runnerContainer.find('.react-tabs__tab--selected').should("contain", "Visual Output") +}) + +it("renders the astro pi component on page load", () => { + cy.get("editor-wc").shadow().find("#root").should("contain", "yaw") +}) + +it("keeps astro pi component if code run without sense hat imported", () => { + cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', '') + cy.get("editor-wc").shadow().find(".btn--run").click() + cy.get("editor-wc").shadow().find("#root").should("contain", "yaw") }) it("loads the sense hat library", () => { @@ -57,15 +72,17 @@ it("confirms LEDs used when single led set", () => { it("confirms LEDs used when display set", () => { cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'from sense_hat import SenseHat\nsense = SenseHat()\nsense.set_pixels([[100,0,0]] * 64)') cy.get("editor-wc").shadow().find(".btn--run").click() + cy.scrollTo('bottom') cy.get("#results").should("contain", '"usedLEDs":true') }) -// it("picks up calls to input()", () => { -// cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'input()') -// cy.get("editor-wc").shadow().find(".btn--run").click() -// cy.get("editor-wc").shadow().find("span[contenteditable=true]").type('{enter}') -// cy.get("#results").should("contain", '"noInputEvents":false') -// }) +it("picks up calls to input()", () => { + cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'input()') + cy.get("editor-wc").shadow().find(".btn--run").click() + cy.get("editor-wc").shadow().contains('Text Output').click() + cy.get("editor-wc").shadow().find("span[contenteditable=true]").type('{enter}') + cy.get("#results").should("contain", '"noInputEvents":false') +}) it("picks up calls to wait for motion", () => { cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'from sense_hat import SenseHat\nsense = SenseHat()\nsense.motion.wait_for_motion()') diff --git a/cypress/e2e/spec-wc.cy.js b/cypress/e2e/spec-wc.cy.js index 6f2ffcb3f..b0d278174 100644 --- a/cypress/e2e/spec-wc.cy.js +++ b/cypress/e2e/spec-wc.cy.js @@ -8,8 +8,34 @@ it("renders the web component", () => { cy.get("editor-wc").shadow().find("button").should("contain", "Run") }) +it("defaults to the text output tab", () => { + const runnerContainer = cy.get("editor-wc").shadow().find('.proj-runner-container') + runnerContainer.find('.react-tabs__tab--selected').should("contain", "Text Output") +}) + it("runs the python code", () => { cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'print("Hello world")') cy.get("editor-wc").shadow().find(".btn--run").click() cy.get("editor-wc").shadow().find(".pythonrunner-console-output-line").should("contain", "Hello world") }) + +it("does not render the astro pi component on page load", () => { + cy.get("editor-wc").shadow().contains('Visual Output').click() + cy.get("editor-wc").shadow().find("#root").should("not.contain", "yaw") +}) + +it("renders astro pi component if sense hat imported", () => { + cy.get("editor-wc").shadow().find("div[class=cm-content]").invoke('text', 'import sense_hat') + 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("contain", "yaw") +}) + +it("does not render astro pi component if sense hat unimported", () => { + cy.get("editor-wc").shadow().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(".btn--run").click() + cy.get("editor-wc").shadow().contains('Visual Output').click() + cy.get("editor-wc").shadow().find("#root").should("not.contain", "yaw") +}) diff --git a/src/components/Editor/EditorSlice.js b/src/components/Editor/EditorSlice.js index 12a3b8c4a..7209b940c 100644 --- a/src/components/Editor/EditorSlice.js +++ b/src/components/Editor/EditorSlice.js @@ -13,6 +13,7 @@ export const EditorSlice = createSlice({ codeRunStopped: false, projectList: [], projectListLoaded: false, + senseHatAlwaysEnabled: false, }, reducers: { updateImages: (state, action) => { @@ -38,6 +39,9 @@ export const EditorSlice = createSlice({ setProjectLoaded: (state, action) => { state.projectLoaded = action.payload; }, + setSenseHatAlwaysEnabled: (state, action) => { + state.senseHatAlwaysEnabled = action.payload; + }, triggerDraw: (state) => { state.drawTriggered = true; }, @@ -99,6 +103,7 @@ export const { setProjectList, setProjectListLoaded, setProjectLoaded, + setSenseHatAlwaysEnabled, stopCodeRun, stopDraw, triggerCodeRun, diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.js b/src/components/Editor/Runners/PythonRunner/PythonRunner.js index 067f0e0d6..368911663 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.js +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.js @@ -16,11 +16,11 @@ const PythonRunner = () => { 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 outputCanvas = useRef(); const output = useRef(); const pygalOutput = useRef(); const p5Output = useRef(); - const senseHatContainer = useRef(); const dispatch = useDispatch(); const [senseHatEnabled, setSenseHatEnabled] = useState(false); @@ -97,7 +97,6 @@ const PythonRunner = () => { if (x==="./_internal_sense_hat/__init__.js") { setSenseHatEnabled(true) - senseHatContainer.current.hidden=false } let localProjectFiles = projectCode.filter((component) => component.name !== 'main').map((component) => `./${component.name}.py`); @@ -226,7 +225,8 @@ const PythonRunner = () => { output.current.innerHTML = ''; pygalOutput.current.innerHTML = ''; p5Output.current.innerHTML = ''; - senseHatContainer.current.hidden = true + + setSenseHatEnabled(false) var prog = projectCode[0].content; @@ -299,7 +299,7 @@ const PythonRunner = () => { return (
- + Visual Output Text Output @@ -312,7 +312,7 @@ const PythonRunner = () => {
- +
{senseHatEnabled || senseHatAlwaysEnabled ?:null}
diff --git a/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap b/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap index f9ed07ef3..1693e08d3 100644 --- a/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap +++ b/src/components/EmbeddedViewer/__snapshots__/EmbeddedViewer.test.js.snap @@ -91,7 +91,6 @@ exports[`Renders without crashing 1`] = ` />
diff --git a/src/components/WebComponent/WebComponentLoader/WebComponentLoader.js b/src/components/WebComponent/WebComponentLoader/WebComponentLoader.js index ea0dc3039..441ebf05f 100644 --- a/src/components/WebComponent/WebComponentLoader/WebComponentLoader.js +++ b/src/components/WebComponent/WebComponentLoader/WebComponentLoader.js @@ -1,11 +1,11 @@ import React, { useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux' -import { setProject, setProjectLoaded } from '../../Editor/EditorSlice'; +import { setProject, setProjectLoaded, setSenseHatAlwaysEnabled } from '../../Editor/EditorSlice'; import WebComponentProject from '../Project/WebComponentProject'; const ProjectComponentLoader = (props) => { const projectLoaded = useSelector((state) => state.editor.projectLoaded); - const { code } = props; + const { code, sense_hat_always_enabled } = props; const dispatch = useDispatch() useEffect(() => { @@ -13,6 +13,7 @@ const ProjectComponentLoader = (props) => { type: 'python', components: [{ name: 'main', extension: 'py', content: code }] } + dispatch(setSenseHatAlwaysEnabled(typeof sense_hat_always_enabled !== 'undefined')) dispatch(setProject(proj)) dispatch(setProjectLoaded(true)) }, []); diff --git a/src/web-component.html b/src/web-component.html index 84a41f024..e3c142742 100644 --- a/src/web-component.html +++ b/src/web-component.html @@ -6,13 +6,18 @@ Editor Web component -

diff --git a/src/web-component.js b/src/web-component.js index 3fa91a49d..6eebf9a1f 100644 --- a/src/web-component.js +++ b/src/web-component.js @@ -20,7 +20,7 @@ class WebComponent extends HTMLElement { } static get observedAttributes() { - return ['code']; + return ['code', 'sense_hat_always_enabled']; } attributeChangedCallback(name, _oldVal, newVal) {