Skip to content

Commit 3aa172f

Browse files
committed
Set up scratch to use the new API routes
As part of this I've passed the project id and api url as params into the iframe. I've used a seperate param to the existing api endpoint so it can be configured independently. This is useful for testing scratch locally in the editor UI without a dependency on the api project.
1 parent 5fffa86 commit 3aa172f

8 files changed

Lines changed: 109 additions & 8 deletions

File tree

src/components/Editor/Project/ScratchContainer.jsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
import React from "react";
2+
import { useSelector } from "react-redux";
23

34
export default function ScratchContainer() {
5+
const projectIdentifier = useSelector(
6+
(state) => state.editor.project.identifier,
7+
);
8+
const scratchApiEndpoint = useSelector(
9+
(state) => state.editor.scratchApiEndpoint,
10+
);
11+
12+
const queryParams = new URLSearchParams();
13+
queryParams.set("project_id", projectIdentifier);
14+
queryParams.set("api_url", scratchApiEndpoint);
15+
16+
const iframeSrcUrl = `${
17+
process.env.ASSETS_URL
18+
}/scratch.html?${queryParams.toString()}`;
19+
420
return (
521
<iframe
6-
src={`${process.env.ASSETS_URL}/scratch.html`}
22+
src={iframeSrcUrl}
723
title={"Scratch"}
824
style={{
925
width: "100%",
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import configureStore from "redux-mock-store";
2+
import { render, screen } from "@testing-library/react";
3+
import React from "react";
4+
import { Provider } from "react-redux";
5+
import ScratchContainer from "./ScratchContainer";
6+
7+
describe("ScratchContainer", () => {
8+
test("renders iframe with src built from project_id and api_url", () => {
9+
const mockStore = configureStore([]);
10+
const store = mockStore({
11+
editor: {
12+
project: {
13+
identifier: "project-123",
14+
},
15+
scratchApiEndpoint: "https://api.example.com/v1",
16+
},
17+
});
18+
19+
render(
20+
<Provider store={store}>
21+
<ScratchContainer />
22+
</Provider>,
23+
);
24+
25+
const iframe = screen.getByTitle("Scratch");
26+
expect(iframe).toBeInTheDocument();
27+
28+
const url = new URL(iframe.getAttribute("src"));
29+
expect(url.pathname).toBe("/scratch.html");
30+
expect(url.searchParams.get("project_id")).toBe("project-123");
31+
expect(url.searchParams.get("api_url")).toBe("https://api.example.com/v1");
32+
});
33+
});

src/containers/WebComponentLoader.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
setSenseHatAlwaysEnabled,
66
setLoadRemixDisabled,
77
setReactAppApiEndpoint,
8+
setScratchApiEndpoint,
89
setReadOnly,
910
} from "../redux/EditorSlice";
1011
import WebComponentProject from "../components/WebComponentProject/WebComponentProject";
@@ -50,6 +51,7 @@ const WebComponentLoader = (props) => {
5051
sidebarPlugins = [],
5152
projectNameEditable = false,
5253
reactAppApiEndpoint = process.env.REACT_APP_API_ENDPOINT,
54+
scratchApiEndpoint = process.env.REACT_APP_API_ENDPOINT,
5355
readOnly = false,
5456
senseHatAlwaysEnabled = false,
5557
showSavePrompt = false,
@@ -164,6 +166,10 @@ const WebComponentLoader = (props) => {
164166
dispatch(setReactAppApiEndpoint(reactAppApiEndpoint));
165167
}, [reactAppApiEndpoint, dispatch]);
166168

169+
useEffect(() => {
170+
dispatch(setScratchApiEndpoint(scratchApiEndpoint));
171+
}, [scratchApiEndpoint, dispatch]);
172+
167173
useEffect(() => {
168174
dispatch(setSenseHatAlwaysEnabled(senseHatAlwaysEnabled));
169175
}, [senseHatAlwaysEnabled, dispatch]);

src/containers/WebComponentLoader.test.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
setReadOnly,
99
setSenseHatAlwaysEnabled,
1010
setReactAppApiEndpoint,
11+
setScratchApiEndpoint,
1112
} from "../redux/EditorSlice";
1213
import { setInstructions } from "../redux/InstructionsSlice";
1314
import { setUser } from "../redux/WebComponentAuthSlice";
@@ -24,7 +25,7 @@ jest.mock("../hooks/useProjectPersistence", () => ({
2425
useProjectPersistence: jest.fn(),
2526
}));
2627

27-
const mockedChangeLanguage = jest.fn(() => new Promise(() => {}));
28+
const mockedChangeLanguage = jest.fn(() => new Promise(() => { }));
2829

2930
jest.mock("react-i18next", () => ({
3031
useTranslation: () => {
@@ -108,6 +109,45 @@ describe("When initially rendered", () => {
108109
expect(mockedChangeLanguage).toHaveBeenCalledWith("es-LA");
109110
});
110111

112+
describe("scratch API endpoint", () => {
113+
describe("when scratch API endpoint isn't set", () => {
114+
beforeEach(() => {
115+
render(
116+
<Provider store={store}>
117+
<WebComponentLoader />
118+
</Provider>,
119+
);
120+
});
121+
122+
test("it defaults to env", () => {
123+
expect(store.getActions()).toEqual(
124+
expect.arrayContaining([
125+
setScratchApiEndpoint("http://localhost:3009"),
126+
]),
127+
);
128+
});
129+
});
130+
131+
describe("when scratch API endpoint is set", () => {
132+
beforeEach(() => {
133+
render(
134+
<Provider store={store}>
135+
<WebComponentLoader
136+
scratchApiEndpoint="http://local.dev"
137+
theme="light"
138+
/>
139+
</Provider>,
140+
);
141+
});
142+
143+
test("it uses the specified prop", () => {
144+
expect(store.getActions()).toEqual(
145+
expect.arrayContaining([setScratchApiEndpoint("http://local.dev")]),
146+
);
147+
});
148+
});
149+
});
150+
111151
describe("react app API endpoint", () => {
112152
describe("when react app API endpoint isn't set", () => {
113153
beforeEach(() => {

src/redux/EditorSlice.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ export const EditorSlice = createSlice({
238238
setReactAppApiEndpoint: (state, action) => {
239239
state.reactAppApiEndpoint = action.payload;
240240
},
241+
setScratchApiEndpoint: (state, action) => {
242+
state.scratchApiEndpoint = action.payload;
243+
},
241244
triggerDraw: (state) => {
242245
state.drawTriggered = true;
243246
},
@@ -442,6 +445,7 @@ export const {
442445
setSenseHatEnabled,
443446
setLoadRemixDisabled,
444447
setReactAppApiEndpoint,
448+
setScratchApiEndpoint,
445449
stopCodeRun,
446450
stopDraw,
447451
triggerCodeRun,

src/scratch.jsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ if (process.env.NODE_ENV === "production" && typeof window === "object") {
1818
window.onbeforeunload = () => true;
1919
}
2020

21-
const defaultProjectId = "cool-id.json";
22-
const projectId = appTarget.dataset.projectId || defaultProjectId;
21+
const searchParams = new URLSearchParams(window.location.search);
22+
const projectId = searchParams.get("project_id");
23+
const apiUrl = searchParams.get("api_url");
2324

2425
const defaultLocale = "en";
2526
const locale = appTarget.dataset.locale || defaultLocale;
@@ -55,8 +56,8 @@ root.render(
5556
projectId={projectId}
5657
locale={locale}
5758
menuBarHidden={true}
58-
projectHost={`${process.env.ASSETS_URL}/api/projects`}
59-
assetHost={`${process.env.ASSETS_URL}/api/assets`}
59+
projectHost={`${apiUrl}/api/scratch/projects`}
60+
assetHost={`${apiUrl}/api/scratch/assets`}
6061
basePath={`${process.env.ASSETS_URL}/scratch-gui/`}
6162
onUpdateProjectId={handleUpdateProjectId}
6263
onShowCreatingRemixAlert={handleRemixingStarted}

src/web-component.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class WebComponent extends HTMLElement {
6565
"output_split_view",
6666
"project_name_editable",
6767
"react_app_api_endpoint",
68+
"scratch_api_endpoint",
6869
"read_only",
6970
"sense_hat_always_enabled",
7071
"show_save_prompt",

webpack.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ module.exports = {
151151
"/pyodide/shims/_internal_sense_hat.js",
152152
"/pyodide/shims/pygal.js",
153153
"/PyodideWorker.js",
154-
"/scratch.html",
155-
].includes(req.url)
154+
].includes(req.url) ||
155+
req.url.startsWith("/scratch.html")
156156
) {
157157
res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
158158
}

0 commit comments

Comments
 (0)