Skip to content

Commit ea95998

Browse files
committed
small cleanup
1 parent 01de928 commit ea95998

File tree

3 files changed

+161
-42
lines changed

3 files changed

+161
-42
lines changed

src/scratch.test.js

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ jest.mock("./components/ScratchEditor/ScratchIntegrationHOC.jsx", () => ({
44
__esModule: true,
55
default: (WrappedComponent) => WrappedComponent,
66
}));
7+
const mockScratchProjectSave = jest.fn();
8+
jest.mock("./utils/scratchProjectSave.js", () => ({
9+
__esModule: true,
10+
default: (params) => mockScratchProjectSave(params),
11+
}));
712
jest.mock("@scratch/scratch-gui", () => {
813
const MockGui = jest.fn(() => null);
914
MockGui.setAppElement = jest.fn();
@@ -72,6 +77,8 @@ describe("scratch handshake retries", () => {
7277
jest.useFakeTimers();
7378
jest.resetModules();
7479
mockRenderRoot.mockReset();
80+
mockScratchProjectSave.mockReset();
81+
mockScratchProjectSave.mockResolvedValue({ id: "project-123" });
7582
process.env = {
7683
...originalEnv,
7784
ASSETS_URL: "https://assets.example.com",
@@ -156,16 +163,14 @@ describe("scratch handshake retries", () => {
156163
"Authorization",
157164
"token-123",
158165
);
159-
expect(scratchStorage.scratchFetch.scratchFetch).toHaveBeenCalledWith(
160-
"https://api.example.com/api/scratch/projects/project-123?title=Saved+from+test",
161-
{
162-
method: "put",
163-
body: '{"targets":[]}',
164-
headers: {
165-
"Content-Type": "application/json",
166-
},
167-
credentials: "include",
168-
},
166+
expect(mockScratchProjectSave).toHaveBeenCalledWith(
167+
expect.objectContaining({
168+
scratchFetchApi: scratchStorage.scratchFetch,
169+
apiUrl: "https://api.example.com",
170+
currentProjectId: "project-123",
171+
vmState: '{"targets":[]}',
172+
params: { title: "Saved from test" },
173+
}),
169174
);
170175
});
171176

@@ -190,27 +195,25 @@ describe("scratch handshake retries", () => {
190195
};
191196

192197
scratchGuiElement.props.onStorageInit(scratchStorage);
193-
const response = await scratchGuiElement.props.onUpdateProjectData(
198+
await scratchGuiElement.props.onUpdateProjectData(
194199
undefined,
195200
'{"targets":[]}',
196201
{ originalId: "source-project", isRemix: 1, title: "Created from test" },
197202
);
198203

199-
expect(scratchStorage.scratchFetch.scratchFetch).toHaveBeenCalledWith(
200-
"https://api.example.com/api/scratch/projects/?original_id=source-project&is_remix=1&title=Created+from+test",
201-
{
202-
method: "post",
203-
body: '{"targets":[]}',
204-
headers: {
205-
"Content-Type": "application/json",
204+
expect(mockScratchProjectSave).toHaveBeenCalledWith(
205+
expect.objectContaining({
206+
scratchFetchApi: scratchStorage.scratchFetch,
207+
apiUrl: "https://api.example.com",
208+
currentProjectId: undefined,
209+
vmState: '{"targets":[]}',
210+
params: {
211+
originalId: "source-project",
212+
isRemix: 1,
213+
title: "Created from test",
206214
},
207-
credentials: "include",
208-
},
215+
}),
209216
);
210-
expect(response).toEqual({
211-
"content-name": "created-project-id",
212-
id: "created-project-id",
213-
});
214217
});
215218

216219
test("keeps retrying when auth is required but token is missing", () => {

src/utils/scratchProjectSave.js

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
const buildQueryString = (params = {}) => {
1+
const buildScratchProjectSaveRequest = ({
2+
apiUrl,
3+
currentProjectId,
4+
params = {},
5+
}) => {
6+
const creatingProject =
7+
currentProjectId === null || typeof currentProjectId === "undefined";
28
const searchParams = new URLSearchParams(
39
Object.entries({
410
original_id: params.originalId,
@@ -7,9 +13,32 @@ const buildQueryString = (params = {}) => {
713
title: params.title,
814
}).filter(([, value]) => value !== undefined),
915
);
10-
1116
const queryString = searchParams.toString();
12-
return queryString ? `?${queryString}` : "";
17+
const baseUrl = creatingProject
18+
? `${apiUrl}/api/scratch/projects/`
19+
: `${apiUrl}/api/scratch/projects/${currentProjectId}`;
20+
21+
return {
22+
creatingProject,
23+
method: creatingProject ? "post" : "put",
24+
url: queryString ? `${baseUrl}?${queryString}` : baseUrl,
25+
};
26+
};
27+
28+
const normalizeScratchProjectSaveResponse = async ({
29+
response,
30+
creatingProject,
31+
currentProjectId,
32+
}) => {
33+
if (response.status !== 200) {
34+
throw response.status;
35+
}
36+
37+
const body = await response.json();
38+
return {
39+
...body,
40+
id: creatingProject ? body["content-name"] : currentProjectId,
41+
};
1342
};
1443

1544
const scratchProjectSave = async ({
@@ -19,29 +48,25 @@ const scratchProjectSave = async ({
1948
vmState,
2049
params,
2150
}) => {
22-
const creatingProject =
23-
currentProjectId === null || typeof currentProjectId === "undefined";
24-
const queryString = buildQueryString(params);
25-
const url = creatingProject
26-
? `${apiUrl}/api/scratch/projects/${queryString}`
27-
: `${apiUrl}/api/scratch/projects/${currentProjectId}${queryString}`;
28-
51+
const { creatingProject, method, url } = buildScratchProjectSaveRequest({
52+
apiUrl,
53+
currentProjectId,
54+
params,
55+
});
2956
const response = await scratchFetchApi.scratchFetch(url, {
30-
method: creatingProject ? "post" : "put",
57+
method,
3158
body: vmState,
3259
headers: {
3360
"Content-Type": "application/json",
3461
},
3562
credentials: "include",
3663
});
3764

38-
if (response.status !== 200) {
39-
throw response.status;
40-
}
41-
42-
const body = await response.json();
43-
body.id = creatingProject ? body["content-name"] : currentProjectId;
44-
return body;
65+
return normalizeScratchProjectSaveResponse({
66+
response,
67+
creatingProject,
68+
currentProjectId,
69+
});
4570
};
4671

4772
export default scratchProjectSave;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import scratchProjectSave from "./scratchProjectSave";
2+
3+
describe("scratchProjectSave", () => {
4+
const buildScratchFetchApi = () => ({
5+
scratchFetch: jest.fn(),
6+
});
7+
8+
test("updates an existing project through scratchFetch", async () => {
9+
const scratchFetchApi = buildScratchFetchApi();
10+
scratchFetchApi.scratchFetch.mockResolvedValue({
11+
status: 200,
12+
json: jest.fn().mockResolvedValue({ ok: true }),
13+
});
14+
15+
const response = await scratchProjectSave({
16+
scratchFetchApi,
17+
apiUrl: "https://api.example.com",
18+
currentProjectId: "project-123",
19+
vmState: '{"targets":[]}',
20+
params: { title: "Saved from test" },
21+
});
22+
23+
expect(scratchFetchApi.scratchFetch).toHaveBeenCalledWith(
24+
"https://api.example.com/api/scratch/projects/project-123?title=Saved+from+test",
25+
{
26+
method: "put",
27+
body: '{"targets":[]}',
28+
headers: {
29+
"Content-Type": "application/json",
30+
},
31+
credentials: "include",
32+
},
33+
);
34+
expect(response).toEqual({ ok: true, id: "project-123" });
35+
});
36+
37+
test("creates a project and returns the created id", async () => {
38+
const scratchFetchApi = buildScratchFetchApi();
39+
scratchFetchApi.scratchFetch.mockResolvedValue({
40+
status: 200,
41+
json: jest
42+
.fn()
43+
.mockResolvedValue({ "content-name": "created-project-id" }),
44+
});
45+
46+
const response = await scratchProjectSave({
47+
scratchFetchApi,
48+
apiUrl: "https://api.example.com",
49+
currentProjectId: undefined,
50+
vmState: '{"targets":[]}',
51+
params: {
52+
originalId: "source-project",
53+
isRemix: 1,
54+
title: "Created from test",
55+
},
56+
});
57+
58+
expect(scratchFetchApi.scratchFetch).toHaveBeenCalledWith(
59+
"https://api.example.com/api/scratch/projects/?original_id=source-project&is_remix=1&title=Created+from+test",
60+
{
61+
method: "post",
62+
body: '{"targets":[]}',
63+
headers: {
64+
"Content-Type": "application/json",
65+
},
66+
credentials: "include",
67+
},
68+
);
69+
expect(response).toEqual({
70+
"content-name": "created-project-id",
71+
id: "created-project-id",
72+
});
73+
});
74+
75+
test("rejects with the response status when the save fails", async () => {
76+
const scratchFetchApi = buildScratchFetchApi();
77+
scratchFetchApi.scratchFetch.mockResolvedValue({
78+
status: 401,
79+
json: jest.fn(),
80+
});
81+
82+
await expect(
83+
scratchProjectSave({
84+
scratchFetchApi,
85+
apiUrl: "https://api.example.com",
86+
currentProjectId: "project-123",
87+
vmState: '{"targets":[]}',
88+
}),
89+
).rejects.toBe(401);
90+
});
91+
});

0 commit comments

Comments
 (0)