@robotical/martyblocksjr 4.2.0 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/editions/free/src/app.bundle.js +1 -1
- package/editions/free/src/assets/ui/viewOnCompact.png +0 -0
- package/editions/free/src/assets/ui/viewOnCompactWideLabel.png +0 -0
- package/editions/free/src/assets/ui/viewOnTargetCompact.png +0 -0
- package/editions/free/src/css/editor.css +96 -0
- package/editions/free/src/css/editorleftpanel.css +21 -22
- package/editions/free/src/css/editorstage.css +62 -0
- package/editions/free/src/css/paintlook.css +23 -0
- package/editions/free/src/localizations/bg.json +1 -0
- package/editions/free/src/localizations/ca.json +1 -0
- package/editions/free/src/localizations/cs.json +1 -0
- package/editions/free/src/localizations/cy.json +1 -0
- package/editions/free/src/localizations/da.json +1 -0
- package/editions/free/src/localizations/de.json +1 -0
- package/editions/free/src/localizations/el.json +1 -0
- package/editions/free/src/localizations/en.json +1 -0
- package/editions/free/src/localizations/es.json +1 -0
- package/editions/free/src/localizations/fi.json +1 -0
- package/editions/free/src/localizations/fr.json +1 -0
- package/editions/free/src/localizations/it.json +1 -0
- package/editions/free/src/localizations/ja.json +1 -0
- package/editions/free/src/localizations/ko.json +1 -0
- package/editions/free/src/localizations/nl.json +1 -0
- package/editions/free/src/localizations/no.json +1 -0
- package/editions/free/src/localizations/pl.json +1 -0
- package/editions/free/src/localizations/pt-br.json +1 -0
- package/editions/free/src/localizations/pt.json +1 -0
- package/editions/free/src/localizations/sv.json +1 -0
- package/editions/free/src/localizations/th.json +1 -0
- package/editions/free/src/localizations/tr.json +1 -0
- package/editions/free/src/localizations/uk.json +1 -0
- package/editions/free/src/localizations/zh-cn.json +1 -0
- package/editions/free/src/localizations/zh-tw.json +1 -0
- package/editions/free/src/media.json +3 -0
- package/package.json +1 -1
- package/tests/e2e/accessibility.e2e.test.js +19 -2
- package/tests/e2e/animated-sprite-paint-disabled.e2e.test.js +358 -0
- package/tests/e2e/background-paint-active-selection.e2e.test.js +162 -0
- package/tests/e2e/blocks-jr-hidden-blocks.e2e.test.js +262 -0
- package/tests/e2e/chromium-79-smoke.test.js +19 -10
- package/tests/e2e/delete-last-sprite-mode.e2e.test.js +162 -0
- package/tests/e2e/duplicate-page.e2e.test.js +414 -0
- package/tests/e2e/marty-script-activation-paths.e2e.test.js +593 -0
- package/tests/e2e/microbit-extension-project-reload.e2e.test.js +6 -1
- package/tests/e2e/paint-editor-cancel.e2e.test.js +274 -0
- package/tests/e2e/project-load-progress-loop.e2e.test.js +6 -1
- package/tests/e2e/project-save-stall.e2e.test.js +230 -0
- package/tests/e2e/sprite-watermark-grayscale.e2e.test.js +269 -0
- package/tests/e2e/top-toolbar-tooltips.e2e.test.js +152 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
2
|
+
import puppeteer from "puppeteer";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import http from "http";
|
|
5
|
+
|
|
6
|
+
const PORT = 3020;
|
|
7
|
+
const HOST = `http://localhost:${PORT}`;
|
|
8
|
+
|
|
9
|
+
let server;
|
|
10
|
+
let sharedBrowser;
|
|
11
|
+
|
|
12
|
+
const waitForServer = () =>
|
|
13
|
+
new Promise((resolve, reject) => {
|
|
14
|
+
const started = Date.now();
|
|
15
|
+
|
|
16
|
+
const check = () => {
|
|
17
|
+
http.get(HOST, response => {
|
|
18
|
+
response.resume();
|
|
19
|
+
resolve();
|
|
20
|
+
}).on("error", error => {
|
|
21
|
+
if (Date.now() - started > 10000) {
|
|
22
|
+
reject(error);
|
|
23
|
+
} else {
|
|
24
|
+
setTimeout(check, 100);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
check();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const openPage = async pathname => {
|
|
33
|
+
const browser = await sharedBrowser.createIncognitoBrowserContext();
|
|
34
|
+
const page = await browser.newPage();
|
|
35
|
+
const errors = [];
|
|
36
|
+
|
|
37
|
+
page.on("pageerror", error => errors.push(error.message));
|
|
38
|
+
page.on("console", message => {
|
|
39
|
+
if (message.type() === "error") {
|
|
40
|
+
errors.push(message.text());
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await page.goto(`${HOST}${pathname}`, {
|
|
45
|
+
waitUntil: "networkidle2",
|
|
46
|
+
timeout: 30000
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return { browser, page, errors };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const waitForEditorReady = async page => {
|
|
53
|
+
await page.waitForFunction(() => {
|
|
54
|
+
const backdrop = document.getElementById("backdrop");
|
|
55
|
+
return window.ScratchJr &&
|
|
56
|
+
ScratchJr.stage &&
|
|
57
|
+
ScratchJr.stage.currentPage &&
|
|
58
|
+
document.getElementById("martyMode") &&
|
|
59
|
+
document.getElementById("emptypage") &&
|
|
60
|
+
backdrop &&
|
|
61
|
+
window.getComputedStyle(backdrop).display === "none" &&
|
|
62
|
+
!ScratchJr.onHold &&
|
|
63
|
+
ScratchJr.getActiveScript();
|
|
64
|
+
}, { timeout: 30000 });
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const createProjectFromHome = async page => {
|
|
68
|
+
await page.waitForSelector("#newproject .card-action-open", { timeout: 30000 });
|
|
69
|
+
await Promise.all([
|
|
70
|
+
page.waitForNavigation({ waitUntil: "networkidle2", timeout: 30000 }),
|
|
71
|
+
page.click("#newproject .card-action-open")
|
|
72
|
+
]);
|
|
73
|
+
await waitForEditorReady(page);
|
|
74
|
+
return page.evaluate(() => ScratchJr.currentProject);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const getActiveScriptState = async page =>
|
|
78
|
+
page.evaluate(() => {
|
|
79
|
+
const script = ScratchJr.getActiveScript();
|
|
80
|
+
const blocks = script.owner.getBlocks();
|
|
81
|
+
return {
|
|
82
|
+
pageId: ScratchJr.stage.currentPage.id,
|
|
83
|
+
pageIds: ScratchJr.stage.getPagesID(),
|
|
84
|
+
isMartyModeEnabled: ScratchJr.isMartyModeEnabled,
|
|
85
|
+
spriteId: ScratchJr.stage.currentPage.currentSpriteName,
|
|
86
|
+
scriptId: script.id,
|
|
87
|
+
scriptVisibility: window.getComputedStyle(script).visibility,
|
|
88
|
+
blockTypes: blocks.map(block => block.blocktype),
|
|
89
|
+
visibleBlockTypes: blocks
|
|
90
|
+
.filter(block => window.getComputedStyle(block.div).visibility !== "hidden")
|
|
91
|
+
.map(block => block.blocktype)
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const setMartyMode = async (page, enabled) => {
|
|
96
|
+
const currentMode = await page.evaluate(() => ScratchJr.isMartyModeEnabled);
|
|
97
|
+
if (currentMode !== enabled) {
|
|
98
|
+
await page.click("#martyMode");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await page.waitForFunction(expected => {
|
|
102
|
+
if (ScratchJr.isMartyModeEnabled !== expected || !ScratchJr.getActiveScript()) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const spriteId = ScratchJr.stage.currentPage.currentSpriteName || "";
|
|
106
|
+
const isBirdsEye = spriteId.indexOf(ScratchJr.BIRDS_EYE_SPRITE_NAME) > -1;
|
|
107
|
+
return expected ? isBirdsEye : !isBirdsEye;
|
|
108
|
+
}, {}, enabled);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const reactivateCurrentMartyScript = async page => {
|
|
112
|
+
const pageId = await page.evaluate(() => ScratchJr.stage.currentPage.id);
|
|
113
|
+
await setMartyMode(page, false);
|
|
114
|
+
await setMartyMode(page, true);
|
|
115
|
+
await page.waitForFunction(expectedPageId => {
|
|
116
|
+
const script = ScratchJr.getActiveScript();
|
|
117
|
+
return ScratchJr.stage.currentPage.id === expectedPageId &&
|
|
118
|
+
script &&
|
|
119
|
+
window.getComputedStyle(script).visibility === "visible";
|
|
120
|
+
}, {}, pageId);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const createSecondMartyPage = async page => {
|
|
124
|
+
await setMartyMode(page, true);
|
|
125
|
+
const pageOne = await getActiveScriptState(page);
|
|
126
|
+
const pageCount = pageOne.pageIds.length;
|
|
127
|
+
|
|
128
|
+
await page.click("#emptypage");
|
|
129
|
+
await page.waitForFunction((firstPageId, count) =>
|
|
130
|
+
ScratchJr.stage.pages.length === count + 1 &&
|
|
131
|
+
ScratchJr.stage.currentPage.id !== firstPageId &&
|
|
132
|
+
ScratchJr.isMartyModeEnabled &&
|
|
133
|
+
ScratchJr.stage.currentPage.currentSpriteName &&
|
|
134
|
+
ScratchJr.stage.currentPage.currentSpriteName.indexOf(ScratchJr.BIRDS_EYE_SPRITE_NAME) > -1 &&
|
|
135
|
+
!ScratchJr.onHold &&
|
|
136
|
+
ScratchJr.getActiveScript(),
|
|
137
|
+
{}, pageOne.pageId, pageCount);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
pageOne,
|
|
141
|
+
pageTwo: await getActiveScriptState(page)
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const selectPage = async (page, pageId) => {
|
|
146
|
+
await page.click(`.pagethumb[data-owner="${pageId}"]`);
|
|
147
|
+
await page.waitForFunction(id =>
|
|
148
|
+
ScratchJr.stage.currentPage.id === id && ScratchJr.getActiveScript(),
|
|
149
|
+
{}, pageId);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const dragBlockToCanvas = async (page, categorySelector, blockSelector, blockType) => {
|
|
153
|
+
const blockCount = await page.evaluate(() => ScratchJr.getBlocks().length);
|
|
154
|
+
await page.click(categorySelector);
|
|
155
|
+
await page.waitForSelector(blockSelector, { visible: true });
|
|
156
|
+
|
|
157
|
+
const source = await page.$eval(blockSelector, node => {
|
|
158
|
+
const rect = node.getBoundingClientRect();
|
|
159
|
+
return {
|
|
160
|
+
x: rect.left + rect.width / 2,
|
|
161
|
+
y: rect.top + rect.height / 2
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
const target = await page.$eval("#scripts", node => {
|
|
165
|
+
const rect = node.getBoundingClientRect();
|
|
166
|
+
return {
|
|
167
|
+
x: rect.left + Math.min(220, rect.width / 2),
|
|
168
|
+
y: rect.top + Math.min(170, rect.height / 2)
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await page.mouse.move(source.x, source.y);
|
|
173
|
+
await page.mouse.down();
|
|
174
|
+
await page.mouse.move(target.x, target.y, { steps: 12 });
|
|
175
|
+
await page.mouse.up();
|
|
176
|
+
|
|
177
|
+
await page.waitForFunction((before, type) =>
|
|
178
|
+
ScratchJr.getBlocks().length > before &&
|
|
179
|
+
ScratchJr.getBlocks().some(block => block.blocktype === type),
|
|
180
|
+
{}, blockCount, blockType);
|
|
181
|
+
|
|
182
|
+
return getActiveScriptState(page);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const dragMartyBlock = page =>
|
|
186
|
+
dragBlockToCanvas(page, "#marty-motion", "#martyStepForward_block", "martyStepForward");
|
|
187
|
+
|
|
188
|
+
const dragSpriteBlock = page =>
|
|
189
|
+
dragBlockToCanvas(page, "#sprite-motion", "#forward_block", "forward");
|
|
190
|
+
|
|
191
|
+
const expectVisibleBlock = (state, blockType, pathName) => {
|
|
192
|
+
expect(state.blockTypes, `${pathName}: ${JSON.stringify(state)}`).toContain(blockType);
|
|
193
|
+
expect(state.scriptVisibility, `${pathName}: ${JSON.stringify(state)}`).toBe("visible");
|
|
194
|
+
expect(state.visibleBlockTypes, `${pathName}: ${JSON.stringify(state)}`).toContain(blockType);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const readPersistedProject = async (page, projectId) =>
|
|
198
|
+
page.evaluate(id => new Promise(resolve => {
|
|
199
|
+
window.OS.query(
|
|
200
|
+
{ stmt: "select * from projects where id = ?", values: [Number(id)] },
|
|
201
|
+
result => {
|
|
202
|
+
const rows = JSON.parse(result);
|
|
203
|
+
if (!rows.length) {
|
|
204
|
+
resolve(null);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const row = Object.fromEntries(Object.entries(rows[0])
|
|
208
|
+
.map(([key, value]) => [key.toLowerCase(), value]));
|
|
209
|
+
resolve(typeof row.json === "string" ? JSON.parse(row.json) : row.json);
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
}), projectId);
|
|
213
|
+
|
|
214
|
+
const saveProject = async (page, projectId, currentPageId) => {
|
|
215
|
+
await page.evaluate(() => new Promise(resolve => {
|
|
216
|
+
ScratchJr.changed = true;
|
|
217
|
+
ScratchJr.saveProject(null, resolve);
|
|
218
|
+
}));
|
|
219
|
+
|
|
220
|
+
const started = Date.now();
|
|
221
|
+
while (Date.now() - started < 15000) {
|
|
222
|
+
const project = await readPersistedProject(page, projectId);
|
|
223
|
+
if (project && project.currentPage === currentPageId && project.pages.length === 2) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
227
|
+
}
|
|
228
|
+
throw new Error("Timed out waiting for the multi-page Marty project to persist");
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
describe("Marty script activation paths", () => {
|
|
232
|
+
beforeAll(async () => {
|
|
233
|
+
server = spawn("python3", ["-m", "http.server", String(PORT), "--directory", "editions/free/src"], {
|
|
234
|
+
stdio: "ignore"
|
|
235
|
+
});
|
|
236
|
+
await waitForServer();
|
|
237
|
+
sharedBrowser = await puppeteer.launch({
|
|
238
|
+
headless: true,
|
|
239
|
+
args: ["--no-sandbox"]
|
|
240
|
+
});
|
|
241
|
+
}, 30000);
|
|
242
|
+
|
|
243
|
+
afterAll(async () => {
|
|
244
|
+
if (sharedBrowser) {
|
|
245
|
+
await sharedBrowser.close();
|
|
246
|
+
}
|
|
247
|
+
if (server) {
|
|
248
|
+
server.kill();
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("keeps the Marty mode sidebar card and full label stable after block drops", async () => {
|
|
253
|
+
const { browser, page, errors } = await openPage("/editor.html?mode=edit");
|
|
254
|
+
try {
|
|
255
|
+
await waitForEditorReady(page);
|
|
256
|
+
const spriteCardLayout = await page.evaluate(() => {
|
|
257
|
+
const card = document.querySelector("#spritecc .spritethumb[aria-pressed=\"true\"]");
|
|
258
|
+
const label = card && card.querySelector(".sname");
|
|
259
|
+
if (!card || !label) {
|
|
260
|
+
throw new Error("Selected sprite card layout was not found");
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
cardWidth: card.getBoundingClientRect().width,
|
|
264
|
+
labelWidth: label.getBoundingClientRect().width
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await setMartyMode(page, true);
|
|
269
|
+
const getMartyCardLayout = () => page.evaluate(() => {
|
|
270
|
+
const card = document.getElementById("martyModeSidebarCard");
|
|
271
|
+
const label = card && card.querySelector(".marty-mode-card-label");
|
|
272
|
+
if (!card || !label) {
|
|
273
|
+
throw new Error("Marty mode sidebar card layout was not found");
|
|
274
|
+
}
|
|
275
|
+
const cardStyle = window.getComputedStyle(card);
|
|
276
|
+
const labelStyle = window.getComputedStyle(label);
|
|
277
|
+
const cardRect = card.getBoundingClientRect();
|
|
278
|
+
const labelRect = label.getBoundingClientRect();
|
|
279
|
+
return {
|
|
280
|
+
className: card.className,
|
|
281
|
+
cardWidth: cardRect.width,
|
|
282
|
+
cardLeft: cardRect.left,
|
|
283
|
+
cardRight: cardRect.right,
|
|
284
|
+
labelText: label.textContent,
|
|
285
|
+
labelWidth: labelRect.width,
|
|
286
|
+
labelLeft: labelRect.left,
|
|
287
|
+
labelRight: labelRect.right,
|
|
288
|
+
labelClientWidth: label.clientWidth,
|
|
289
|
+
labelScrollWidth: label.scrollWidth,
|
|
290
|
+
hasBrush: Boolean(card.querySelector(".brush")),
|
|
291
|
+
backgroundImage: cardStyle.backgroundImage,
|
|
292
|
+
backgroundSize: cardStyle.backgroundSize,
|
|
293
|
+
overflow: labelStyle.overflow,
|
|
294
|
+
textOverflow: labelStyle.textOverflow,
|
|
295
|
+
whiteSpace: labelStyle.whiteSpace
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
const martyCardLayout = await getMartyCardLayout();
|
|
299
|
+
|
|
300
|
+
expect(martyCardLayout.cardWidth).toBeCloseTo(spriteCardLayout.cardWidth, 1);
|
|
301
|
+
expect(martyCardLayout.labelWidth).toBeGreaterThan(spriteCardLayout.labelWidth);
|
|
302
|
+
expect(martyCardLayout.labelLeft).toBeGreaterThanOrEqual(martyCardLayout.cardLeft);
|
|
303
|
+
expect(martyCardLayout.labelRight).toBeLessThanOrEqual(martyCardLayout.cardRight);
|
|
304
|
+
expect(martyCardLayout.labelText).toBe("Marty Mode");
|
|
305
|
+
expect(martyCardLayout.hasBrush).toBe(false);
|
|
306
|
+
expect(martyCardLayout.backgroundImage).toContain("viewOnCompactWideLabel.png");
|
|
307
|
+
expect(martyCardLayout.backgroundSize).toBe("100% 100%");
|
|
308
|
+
expect(martyCardLayout).toMatchObject({
|
|
309
|
+
overflow: "hidden",
|
|
310
|
+
textOverflow: "ellipsis",
|
|
311
|
+
whiteSpace: "nowrap"
|
|
312
|
+
});
|
|
313
|
+
expect(martyCardLayout.labelScrollWidth).toBeLessThanOrEqual(martyCardLayout.labelClientWidth);
|
|
314
|
+
|
|
315
|
+
await dragMartyBlock(page);
|
|
316
|
+
expect(await getMartyCardLayout()).toEqual(martyCardLayout);
|
|
317
|
+
expect(errors).toEqual([]);
|
|
318
|
+
} finally {
|
|
319
|
+
await browser.close();
|
|
320
|
+
}
|
|
321
|
+
}, 60000);
|
|
322
|
+
|
|
323
|
+
it("activates the script after duplicating a page in Marty mode", async () => {
|
|
324
|
+
const { browser, page, errors } = await openPage("/editor.html?mode=edit");
|
|
325
|
+
try {
|
|
326
|
+
await waitForEditorReady(page);
|
|
327
|
+
await setMartyMode(page, true);
|
|
328
|
+
const source = await getActiveScriptState(page);
|
|
329
|
+
|
|
330
|
+
await page.evaluate(pageId => ScratchJr.stage.duplicatePage(pageId), source.pageId);
|
|
331
|
+
await page.waitForFunction((pageId, count) =>
|
|
332
|
+
ScratchJr.stage.pages.length === count + 1 &&
|
|
333
|
+
ScratchJr.stage.currentPage.id !== pageId &&
|
|
334
|
+
!ScratchJr.stage.duplicatingPage &&
|
|
335
|
+
!ScratchJr.onHold &&
|
|
336
|
+
ScratchJr.getActiveScript(),
|
|
337
|
+
{}, source.pageId, source.pageIds.length);
|
|
338
|
+
|
|
339
|
+
expectVisibleBlock(await dragMartyBlock(page), "martyStepForward", "duplicate page");
|
|
340
|
+
expect(errors).toEqual([]);
|
|
341
|
+
} finally {
|
|
342
|
+
await browser.close();
|
|
343
|
+
}
|
|
344
|
+
}, 60000);
|
|
345
|
+
|
|
346
|
+
it("activates the remaining page script after deleting the current page", async () => {
|
|
347
|
+
const { browser, page, errors } = await openPage("/editor.html?mode=edit");
|
|
348
|
+
try {
|
|
349
|
+
await waitForEditorReady(page);
|
|
350
|
+
const { pageOne, pageTwo } = await createSecondMartyPage(page);
|
|
351
|
+
|
|
352
|
+
await page.evaluate(pageId => ScratchJr.stage.deletePage(pageId), pageTwo.pageId);
|
|
353
|
+
await page.waitForFunction(pageId =>
|
|
354
|
+
ScratchJr.stage.pages.length === 1 &&
|
|
355
|
+
ScratchJr.stage.currentPage.id === pageId &&
|
|
356
|
+
ScratchJr.getActiveScript(),
|
|
357
|
+
{}, pageOne.pageId);
|
|
358
|
+
|
|
359
|
+
expectVisibleBlock(await dragMartyBlock(page), "martyStepForward", "delete current page");
|
|
360
|
+
expect(errors).toEqual([]);
|
|
361
|
+
} finally {
|
|
362
|
+
await browser.close();
|
|
363
|
+
}
|
|
364
|
+
}, 60000);
|
|
365
|
+
|
|
366
|
+
it("activates scripts after undoing and redoing page navigation", async () => {
|
|
367
|
+
const { browser, page, errors } = await openPage("/editor.html?mode=edit");
|
|
368
|
+
try {
|
|
369
|
+
await waitForEditorReady(page);
|
|
370
|
+
const { pageOne, pageTwo } = await createSecondMartyPage(page);
|
|
371
|
+
await selectPage(page, pageOne.pageId);
|
|
372
|
+
await reactivateCurrentMartyScript(page);
|
|
373
|
+
|
|
374
|
+
await page.click("#id_undo");
|
|
375
|
+
await page.waitForFunction(pageId => ScratchJr.stage.currentPage.id === pageId,
|
|
376
|
+
{}, pageTwo.pageId);
|
|
377
|
+
const undoState = await getActiveScriptState(page);
|
|
378
|
+
|
|
379
|
+
await reactivateCurrentMartyScript(page);
|
|
380
|
+
await page.click("#id_redo");
|
|
381
|
+
await page.waitForFunction(pageId => ScratchJr.stage.currentPage.id === pageId,
|
|
382
|
+
{}, pageOne.pageId);
|
|
383
|
+
const redoState = await dragMartyBlock(page);
|
|
384
|
+
|
|
385
|
+
expect({
|
|
386
|
+
undo: undoState.scriptVisibility,
|
|
387
|
+
redo: redoState.scriptVisibility
|
|
388
|
+
}).toEqual({ undo: "visible", redo: "visible" });
|
|
389
|
+
expectVisibleBlock(redoState, "martyStepForward", "redo page navigation");
|
|
390
|
+
expect(errors).toEqual([]);
|
|
391
|
+
} finally {
|
|
392
|
+
await browser.close();
|
|
393
|
+
}
|
|
394
|
+
}, 60000);
|
|
395
|
+
|
|
396
|
+
it("activates scripts after undoing and redoing page ordering", async () => {
|
|
397
|
+
const { browser, page, errors } = await openPage("/editor.html?mode=edit");
|
|
398
|
+
try {
|
|
399
|
+
await waitForEditorReady(page);
|
|
400
|
+
const { pageOne, pageTwo } = await createSecondMartyPage(page);
|
|
401
|
+
|
|
402
|
+
await page.evaluate(pageId => {
|
|
403
|
+
const thumb = document.querySelector(`.pagethumb[data-owner="${pageId}"]`);
|
|
404
|
+
thumb.dispatchEvent(new KeyboardEvent("keydown", {
|
|
405
|
+
key: "ArrowLeft",
|
|
406
|
+
shiftKey: true,
|
|
407
|
+
bubbles: true,
|
|
408
|
+
cancelable: true
|
|
409
|
+
}));
|
|
410
|
+
}, pageTwo.pageId);
|
|
411
|
+
await page.waitForFunction(pageId => ScratchJr.stage.getPagesID()[0] === pageId,
|
|
412
|
+
{}, pageTwo.pageId);
|
|
413
|
+
|
|
414
|
+
await page.click("#id_undo");
|
|
415
|
+
await page.waitForFunction((firstId, currentId) =>
|
|
416
|
+
ScratchJr.stage.getPagesID()[0] === firstId &&
|
|
417
|
+
ScratchJr.stage.currentPage.id === currentId,
|
|
418
|
+
{}, pageOne.pageId, pageTwo.pageId);
|
|
419
|
+
const undoState = await getActiveScriptState(page);
|
|
420
|
+
|
|
421
|
+
await reactivateCurrentMartyScript(page);
|
|
422
|
+
await page.click("#id_redo");
|
|
423
|
+
await page.waitForFunction((firstId, currentId) =>
|
|
424
|
+
ScratchJr.stage.getPagesID()[0] === firstId &&
|
|
425
|
+
ScratchJr.stage.currentPage.id === currentId,
|
|
426
|
+
{}, pageTwo.pageId, pageTwo.pageId);
|
|
427
|
+
const redoState = await dragMartyBlock(page);
|
|
428
|
+
|
|
429
|
+
expect({
|
|
430
|
+
undo: undoState.scriptVisibility,
|
|
431
|
+
redo: redoState.scriptVisibility
|
|
432
|
+
}).toEqual({ undo: "visible", redo: "visible" });
|
|
433
|
+
expectVisibleBlock(redoState, "martyStepForward", "redo page ordering");
|
|
434
|
+
expect(errors).toEqual([]);
|
|
435
|
+
} finally {
|
|
436
|
+
await browser.close();
|
|
437
|
+
}
|
|
438
|
+
}, 60000);
|
|
439
|
+
|
|
440
|
+
it("activates scripts when undoing and redoing a page deletion", async () => {
|
|
441
|
+
const { browser, page, errors } = await openPage("/editor.html?mode=edit");
|
|
442
|
+
try {
|
|
443
|
+
await waitForEditorReady(page);
|
|
444
|
+
const { pageOne, pageTwo } = await createSecondMartyPage(page);
|
|
445
|
+
|
|
446
|
+
await page.evaluate(pageId => ScratchJr.stage.deletePage(pageId), pageTwo.pageId);
|
|
447
|
+
await page.waitForFunction(pageId =>
|
|
448
|
+
ScratchJr.stage.pages.length === 1 && ScratchJr.stage.currentPage.id === pageId,
|
|
449
|
+
{}, pageOne.pageId);
|
|
450
|
+
await reactivateCurrentMartyScript(page);
|
|
451
|
+
|
|
452
|
+
await page.click("#id_undo");
|
|
453
|
+
await page.waitForFunction(() =>
|
|
454
|
+
ScratchJr.stage.pages.length === 2 &&
|
|
455
|
+
document.getElementById("id_redo").className.indexOf("enable") > -1 &&
|
|
456
|
+
ScratchJr.getActiveScript());
|
|
457
|
+
await page.waitFor(1000);
|
|
458
|
+
const undoState = await getActiveScriptState(page);
|
|
459
|
+
|
|
460
|
+
await page.click("#id_redo");
|
|
461
|
+
await page.waitForFunction(pageId =>
|
|
462
|
+
ScratchJr.stage.pages.length === 1 &&
|
|
463
|
+
ScratchJr.stage.currentPage.id === pageId &&
|
|
464
|
+
ScratchJr.getActiveScript(),
|
|
465
|
+
{}, pageOne.pageId);
|
|
466
|
+
await page.waitFor(500);
|
|
467
|
+
const redoState = await (await page.evaluate(() => ScratchJr.isMartyModeEnabled) ?
|
|
468
|
+
dragMartyBlock(page) : dragSpriteBlock(page));
|
|
469
|
+
|
|
470
|
+
expect({
|
|
471
|
+
undo: undoState.scriptVisibility,
|
|
472
|
+
redo: redoState.scriptVisibility
|
|
473
|
+
}).toEqual({
|
|
474
|
+
undo: "visible",
|
|
475
|
+
redo: "visible"
|
|
476
|
+
});
|
|
477
|
+
expectVisibleBlock(
|
|
478
|
+
redoState,
|
|
479
|
+
redoState.isMartyModeEnabled ? "martyStepForward" : "forward",
|
|
480
|
+
"redo page deletion"
|
|
481
|
+
);
|
|
482
|
+
expect(errors).toEqual([]);
|
|
483
|
+
} finally {
|
|
484
|
+
await browser.close();
|
|
485
|
+
}
|
|
486
|
+
}, 60000);
|
|
487
|
+
|
|
488
|
+
it("activates the destination script after a go-to-page transition", async () => {
|
|
489
|
+
const { browser, page, errors } = await openPage("/editor.html?mode=edit");
|
|
490
|
+
try {
|
|
491
|
+
await waitForEditorReady(page);
|
|
492
|
+
const { pageOne } = await createSecondMartyPage(page);
|
|
493
|
+
|
|
494
|
+
await page.evaluate(() => ScratchJr.stage.gotoPage(1));
|
|
495
|
+
await page.waitForFunction(pageId => ScratchJr.stage.currentPage.id === pageId,
|
|
496
|
+
{}, pageOne.pageId);
|
|
497
|
+
|
|
498
|
+
expectVisibleBlock(await dragMartyBlock(page), "martyStepForward", "go to page");
|
|
499
|
+
expect(errors).toEqual([]);
|
|
500
|
+
} finally {
|
|
501
|
+
await browser.close();
|
|
502
|
+
}
|
|
503
|
+
}, 60000);
|
|
504
|
+
|
|
505
|
+
it("activates the destination script after fullscreen page navigation", async () => {
|
|
506
|
+
const { browser, page, errors } = await openPage("/editor.html?mode=edit");
|
|
507
|
+
try {
|
|
508
|
+
await waitForEditorReady(page);
|
|
509
|
+
const { pageOne } = await createSecondMartyPage(page);
|
|
510
|
+
|
|
511
|
+
await page.click("#full");
|
|
512
|
+
await page.waitForFunction(() => ScratchJr.inFullscreen);
|
|
513
|
+
await page.$eval("#prevpage", button => button.click());
|
|
514
|
+
await page.waitForFunction(pageId => ScratchJr.stage.currentPage.id === pageId,
|
|
515
|
+
{}, pageOne.pageId);
|
|
516
|
+
await page.$eval("#full", button => button.click());
|
|
517
|
+
await page.waitForFunction(() => !ScratchJr.inFullscreen);
|
|
518
|
+
|
|
519
|
+
expectVisibleBlock(await dragMartyBlock(page), "martyStepForward", "fullscreen navigation");
|
|
520
|
+
expect(errors).toEqual([]);
|
|
521
|
+
} finally {
|
|
522
|
+
await browser.close();
|
|
523
|
+
}
|
|
524
|
+
}, 60000);
|
|
525
|
+
|
|
526
|
+
it("activates the saved current script after reloading a multi-page Marty project", async () => {
|
|
527
|
+
const { browser, page, errors } = await openPage("/home.html?place=home");
|
|
528
|
+
try {
|
|
529
|
+
const projectId = await createProjectFromHome(page);
|
|
530
|
+
const { pageOne } = await createSecondMartyPage(page);
|
|
531
|
+
await selectPage(page, pageOne.pageId);
|
|
532
|
+
await reactivateCurrentMartyScript(page);
|
|
533
|
+
expectVisibleBlock(await dragMartyBlock(page), "martyStepForward", "pre-save state");
|
|
534
|
+
await saveProject(page, projectId, pageOne.pageId);
|
|
535
|
+
|
|
536
|
+
await page.reload({ waitUntil: "networkidle2", timeout: 30000 });
|
|
537
|
+
await waitForEditorReady(page);
|
|
538
|
+
await page.waitForFunction(pageId =>
|
|
539
|
+
ScratchJr.stage.currentPage.id === pageId && ScratchJr.isMartyModeEnabled,
|
|
540
|
+
{}, pageOne.pageId);
|
|
541
|
+
|
|
542
|
+
expectVisibleBlock(await dragMartyBlock(page), "martyStepForward", "project reload");
|
|
543
|
+
expect(errors).toEqual([]);
|
|
544
|
+
} finally {
|
|
545
|
+
await browser.close();
|
|
546
|
+
}
|
|
547
|
+
}, 120000);
|
|
548
|
+
|
|
549
|
+
it("reactivates the original script after duplicate-page error recovery", async () => {
|
|
550
|
+
const { browser, page, errors } = await openPage("/editor.html?mode=edit");
|
|
551
|
+
try {
|
|
552
|
+
await waitForEditorReady(page);
|
|
553
|
+
await setMartyMode(page, false);
|
|
554
|
+
const source = await getActiveScriptState(page);
|
|
555
|
+
|
|
556
|
+
const recovery = await page.evaluate(pageId => {
|
|
557
|
+
const pages = ScratchJr.stage.pages;
|
|
558
|
+
const originalPush = pages.push;
|
|
559
|
+
pages.push = function () {
|
|
560
|
+
throw new Error("injected duplicate-page failure");
|
|
561
|
+
};
|
|
562
|
+
try {
|
|
563
|
+
ScratchJr.stage.duplicatePage(pageId);
|
|
564
|
+
return { caught: false };
|
|
565
|
+
} catch (error) {
|
|
566
|
+
return {
|
|
567
|
+
caught: true,
|
|
568
|
+
message: error.message,
|
|
569
|
+
currentPageId: ScratchJr.stage.currentPage.id
|
|
570
|
+
};
|
|
571
|
+
} finally {
|
|
572
|
+
pages.push = originalPush;
|
|
573
|
+
}
|
|
574
|
+
}, source.pageId);
|
|
575
|
+
|
|
576
|
+
expect(recovery).toMatchObject({
|
|
577
|
+
caught: true,
|
|
578
|
+
message: "injected duplicate-page failure",
|
|
579
|
+
currentPageId: source.pageId
|
|
580
|
+
});
|
|
581
|
+
await page.waitForFunction(pageId =>
|
|
582
|
+
ScratchJr.stage.currentPage.id === pageId &&
|
|
583
|
+
!ScratchJr.stage.duplicatingPage &&
|
|
584
|
+
!ScratchJr.onHold,
|
|
585
|
+
{}, source.pageId);
|
|
586
|
+
|
|
587
|
+
expectVisibleBlock(await dragSpriteBlock(page), "forward", "duplicate error recovery");
|
|
588
|
+
expect(errors).toEqual([]);
|
|
589
|
+
} finally {
|
|
590
|
+
await browser.close();
|
|
591
|
+
}
|
|
592
|
+
}, 60000);
|
|
593
|
+
});
|
|
@@ -119,7 +119,12 @@ async function enableMartyMode(page) {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
async function enableMicroBitExtension(page) {
|
|
122
|
-
await page
|
|
122
|
+
await page.$eval("#addExtensionButton", (button) => {
|
|
123
|
+
if (!button.disabled || window.getComputedStyle(button).display !== "none") {
|
|
124
|
+
throw new Error("Expected the extensions entry point to remain hidden and disabled");
|
|
125
|
+
}
|
|
126
|
+
button.onclick();
|
|
127
|
+
});
|
|
123
128
|
await page.waitForSelector("#extensionsLibrary.fade.in", { timeout: 30_000 });
|
|
124
129
|
await page.click("#microBitExtensionCard");
|
|
125
130
|
await page.waitForFunction(
|