Skip to content
Merged
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- Add `project_name_editable` attribute to web component (#1009)
- Fires custom event when the theme changes (#1015)
- Add `output_only` attribute to web component (#1019 & originally #782)
- Add `assets_identifier` attribute to web component (#1019 & originally #901)
- Enhance `code` attribute on web component to override project main component content (#1019 & originally #901)
- Add `runCode`, `stopCode` & `rerunCode` methods to web component (#1019 & originally #899)
- Send error details in "editor-runCompleted" event (#1019 & originally #915)
- Return error details to web component (#1019 & originally #915)
- Add `output_panels` attribute to web component (#1019 & originally #909)

### Changed

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,13 @@ The repo includes the Editor Web Component which shares components with the edit

The web component can be included in a page by using the `<editor-wc>` HTML element. It takes the following attributes

- `code`: A preset blob of code to show in the editor pane.
- `code`: A preset blob of code to show in the editor pane (overrides content of `main.py`/`index.html`)
- `sense_hat_always_enabled`: Show the Astro Pi Sense HAT emulator on page load
- `load_remix_disabled`: Do not load a logged-in user's remixed version of the project specified by `identifier` even if one exists (defaults to `false`)
- `project_name_editable`: Allow the user to edit the project name in the project bar (defaults to `false`)
- `output_only`: Only display the output panel (defaults to `false`)
- `assets_identifier`: Load assets (not code) from this project identifier
- `output_panels`: Array of panel names to display (defaults to `["text", "visual"]`)

### `yarn start:wc`

Expand Down
41 changes: 41 additions & 0 deletions cypress/e2e/spec-wc-block-to-text.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const code = `
from p5 import *

img = None

def preload():
global img
img = load_image("moon.png")

def setup():
size(400, 200)

def draw():
fill("green")
text_size(100)
text("PASS", 125, 110)
image(img, 10, 25, 100 , 100)
no_loop()

run()
`;

const params = new URLSearchParams();
params.set("assets_identifier", "encoded-art-starter");
params.set("host_styles", JSON.stringify("#wc { min-height: 200px }"));
params.set("code", code);
params.set("output_only", true);
params.set("output_panels", JSON.stringify(["visual"]));

const baseUrl = `http://localhost:3001?${params.toString()}`;

it("runs p5 sketch including image loaded from project ", () => {
cy.visit(baseUrl);

cy.get("editor-wc").shadow().find(".embedded-viewer").should("exist");
cy.get("editor-wc").shadow().find(".proj").should("not.exist");

cy.get("button").contains("Run code").click();

cy.get("#results").should("contain", '{"errorDetails":{}}');
});
2 changes: 1 addition & 1 deletion src/assets/stylesheets/EmbeddedViewer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
}
}

.--light {
#app.--light {
.embedded-viewer {
background-color: $rpf-white;
}
Expand Down
2 changes: 1 addition & 1 deletion src/assets/stylesheets/InternalStyles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
@use "./SaveStatus" as *;
@use "./ContextMenu" as *;
@use "./FilePanel" as *; // needs to be below Button
@use "./EmbeddedViewer" as *;

:host {
font-size: 1.6rem;
Expand Down Expand Up @@ -118,4 +119,3 @@ button:focus-visible {
.rpf-button--secondary {
border-color: $rpf-navy-800;}
}

4 changes: 4 additions & 0 deletions src/assets/stylesheets/PythonRunner.scss
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,7 @@
}
}
}

.--light .output-panel--single, .--dark .output-panel--single {
border-block-end: none;
}
4 changes: 4 additions & 0 deletions src/assets/stylesheets/Tabs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@
padding: 0 $space-0-25 0 0;
}

&__tab-container--hidden {
display: none;
}

&__tab-panel--selected {
flex: 1;
display: flex;
Expand Down
20 changes: 15 additions & 5 deletions src/components/Editor/Output/Output.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,32 @@ import ExternalFiles from "../../ExternalFiles/ExternalFiles";
import RunnerFactory from "../Runners/RunnerFactory";
import RunBar from "../../RunButton/RunBar";

const Output = () => {
const Output = ({
embedded = false,
browserPreview = false,
outputPanels = ["text", "visual"],
}) => {
const project = useSelector((state) => state.editor.project);
const isEmbedded = useSelector((state) => state.editor.isEmbedded);
const isEmbedded =
useSelector((state) => state.editor.isEmbedded) || embedded;
const searchParams = new URLSearchParams(window.location.search);
const isBrowserPreview = searchParams.get("browserPreview") === "true";
const isBrowserPreview =
searchParams.get("browserPreview") === "true" || browserPreview;
const usePyodide = searchParams.get("pyodide") === "true";
const webComponent = useSelector((state) => state.editor.webComponent);

return (
<>
<ExternalFiles />
<div className="proj-runner-container">
<div className="proj-runner-container" data-testid="output">
<RunnerFactory
projectType={project.project_type}
usePyodide={usePyodide}
outputPanels={outputPanels}
/>
{isEmbedded && !isBrowserPreview ? <RunBar embedded /> : null}
{!webComponent && isEmbedded && !isBrowserPreview && (
<RunBar embedded />
)}
</div>
</>
);
Expand Down
167 changes: 114 additions & 53 deletions src/components/Editor/Output/Output.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,86 +6,147 @@ import configureStore from "redux-mock-store";
import Output from "./Output";
import { MemoryRouter } from "react-router-dom";

let mockBrowserPreview = "false";

jest
.spyOn(URLSearchParams.prototype, "get")
.mockImplementation((key) =>
key === "browserPreview" ? mockBrowserPreview : null,
);

const user = {
access_token: "39a09671-be55-4847-baf5-8919a0c24a25",
profile: {
user: "b48e70e2-d9ed-4a59-aee5-fc7cf09dbfaf",
},
};

test("Component renders", () => {
const middlewares = [];
const mockStore = configureStore(middlewares);
const initialState = {
editor: {
project: {
components: [],
},
},
auth: {
user,
},
};
const store = mockStore(initialState);
const { container } = render(
<Provider store={store}>
<MemoryRouter>
<Output />
</MemoryRouter>
</Provider>,
);
expect(container.lastChild).toHaveClass("proj-runner-container");
});

describe("When embedded", () => {
let store;
let store;
let mockStore;
let initialState;

describe("Output component", () => {
beforeEach(() => {
const middlewares = [];
const mockStore = configureStore(middlewares);
const initialState = {
mockStore = configureStore(middlewares);
initialState = {
editor: {
project: {
components: [],
},
isEmbedded: true,
},
auth: {
user,
},
};
store = mockStore(initialState);
});

test("Shows run bar when not browser preview", () => {
render(
test("renders", () => {
const store = mockStore(initialState);
const { container } = render(
<Provider store={store}>
<MemoryRouter>
<Output />
</MemoryRouter>
</Provider>,
);
expect(screen.queryByText("runButton.run")).toBeInTheDocument();
expect(container.lastChild).toHaveClass("proj-runner-container");
});

describe("when isEmbedded state is true", () => {
beforeEach(() => {
initialState.editor.isEmbedded = true;
store = mockStore(initialState);
});

test("shows run bar", () => {
render(
<Provider store={store}>
<MemoryRouter>
<Output />
</MemoryRouter>
</Provider>,
);
expect(screen.queryByText("runButton.run")).toBeInTheDocument();
});

describe("when webComponent state is true", () => {
beforeEach(() => {
initialState.editor.webComponent = true;
store = mockStore(initialState);
});

test("does not show run bar", () => {
render(
<Provider store={store}>
<MemoryRouter>
<Output />
</MemoryRouter>
</Provider>,
);
expect(screen.queryByText("runButton.run")).not.toBeInTheDocument();
});
});

describe("when browserPreview property is true", () => {
test("does not show run bar", () => {
render(
<Provider store={store}>
<MemoryRouter>
<Output browserPreview={true} />
</MemoryRouter>
</Provider>,
);
expect(screen.queryByText("runButton.run")).not.toBeInTheDocument();
});
});

describe("when browserPreview is true in query string", () => {
let originalLocation;

beforeEach(() => {
originalLocation = window.location;
delete window.location;
window.location = { search: "?browserPreview=true" };
});

afterEach(() => {
window.location = originalLocation;
});

test("does not show run bar", () => {
render(
<Provider store={store}>
<MemoryRouter>
<Output />
</MemoryRouter>
</Provider>,
);
expect(screen.queryByText("runButton.run")).not.toBeInTheDocument();
});
});
});

// TODO: Get this test working
// test("Does not show run bar when browser preview", () => {
// mockBrowserPreview = "true";
// render(
// <Provider store={store}>
// <MemoryRouter>
// <Output />
// </MemoryRouter>
// </Provider>,
// );
// expect(screen.queryByText("runButton.run")).not.toBeInTheDocument();
// });
describe("when isEmbedded state is false", () => {
beforeEach(() => {
initialState.editor.isEmbedded = false;
store = mockStore(initialState);
});

test("does not show run bar", () => {
render(
<Provider store={store}>
<MemoryRouter>
<Output />
</MemoryRouter>
</Provider>,
);
expect(screen.queryByText("runButton.run")).not.toBeInTheDocument();
});

describe("when embedded property is true", () => {
test("shows run bar", () => {
render(
<Provider store={store}>
<MemoryRouter>
<Output embedded={true} />
</MemoryRouter>
</Provider>,
);
expect(screen.queryByText("runButton.run")).toBeInTheDocument();
});
});
});
});
2 changes: 1 addition & 1 deletion src/components/Editor/Project/Project.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const Project = (props) => {
}, []);

return (
<div className="proj">
<div className="proj" data-testid="project">
<div
className={classnames("proj-container", {
"proj-container--wc": webComponent,
Expand Down
Loading