@robotical/martyblocksjr 4.1.1 → 4.2.0
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/.firebase/hosting.ZWRpdGlvbnMvZnJlZS9zcmM.cache +599 -534
- package/editions/free/src/app.bundle.js +1 -1
- package/editions/free/src/assets/blockicons/microbitbuttona.svg +33 -0
- package/editions/free/src/assets/blockicons/microbitbuttonab.svg +33 -0
- package/editions/free/src/assets/blockicons/microbitbuttonb.svg +33 -0
- package/editions/free/src/assets/blockicons/microbitdisplayclear.svg +29 -0
- package/editions/free/src/assets/blockicons/microbitdisplayhappy.svg +36 -0
- package/editions/free/src/assets/blockicons/microbitdisplayheart.svg +42 -0
- package/editions/free/src/assets/blockicons/microbitdisplaytext.svg +29 -0
- package/editions/free/src/assets/blockicons/microbitgesturejumped.svg +40 -0
- package/editions/free/src/assets/blockicons/microbitgesturemoved.svg +41 -0
- package/editions/free/src/assets/blockicons/microbitgestureshaken.svg +45 -0
- package/editions/free/src/assets/blockicons/microbittiltany.svg +45 -0
- package/editions/free/src/assets/blockicons/microbittiltbackward.svg +39 -0
- package/editions/free/src/assets/blockicons/microbittiltforward.svg +39 -0
- package/editions/free/src/assets/blockicons/microbittiltleft.svg +39 -0
- package/editions/free/src/assets/blockicons/microbittiltright.svg +39 -0
- package/editions/free/src/assets/categories/MicroBitLooksOff.svg +17 -0
- package/editions/free/src/assets/categories/MicroBitLooksOn.svg +17 -0
- package/editions/free/src/assets/categories/MicroBitStartOff.svg +17 -0
- package/editions/free/src/assets/categories/MicroBitStartOn.svg +17 -0
- package/editions/free/src/css/editor.css +243 -1
- package/editions/free/src/css/editorleftpanel.css +70 -9
- package/editions/free/src/localizations/bg.json +9 -1
- package/editions/free/src/localizations/ca.json +9 -1
- package/editions/free/src/localizations/cs.json +9 -1
- package/editions/free/src/localizations/cy.json +9 -1
- package/editions/free/src/localizations/da.json +9 -1
- package/editions/free/src/localizations/de.json +9 -1
- package/editions/free/src/localizations/el.json +9 -1
- package/editions/free/src/localizations/en.json +9 -1
- package/editions/free/src/localizations/es.json +9 -1
- package/editions/free/src/localizations/fi.json +9 -1
- package/editions/free/src/localizations/fr.json +9 -1
- package/editions/free/src/localizations/it.json +9 -1
- package/editions/free/src/localizations/ja.json +9 -1
- package/editions/free/src/localizations/ko.json +9 -1
- package/editions/free/src/localizations/nl.json +9 -1
- package/editions/free/src/localizations/no.json +9 -1
- package/editions/free/src/localizations/pl.json +9 -1
- package/editions/free/src/localizations/pt-br.json +9 -1
- package/editions/free/src/localizations/pt.json +9 -1
- package/editions/free/src/localizations/sv.json +9 -1
- package/editions/free/src/localizations/th.json +9 -1
- package/editions/free/src/localizations/tr.json +9 -1
- package/editions/free/src/localizations/uk.json +9 -1
- package/editions/free/src/localizations/zh-cn.json +9 -1
- package/editions/free/src/localizations/zh-tw.json +9 -1
- package/editions/free/src/media.json +12 -0
- package/editions/free/src/pnglibrary/BeeSprite1.png +0 -0
- package/editions/free/src/svglibrary/BeeSprite1.svg +4 -0
- package/editions/free/src/svglibrary/BeeSprite2.svg +4 -0
- package/editions/free/src/svglibrary/BeeSprite3.svg +4 -0
- package/package.json +1 -1
- package/tests/e2e/chromium-79-smoke.test.js +226 -0
- package/tests/e2e/marty-connection-ui.e2e.test.js +56 -0
- package/tests/e2e/microbit-blocks.test.js +162 -0
- package/tests/e2e/microbit-extension-project-reload.e2e.test.js +304 -0
- package/tests/e2e/microbit-web-bluetooth.test.js +131 -0
- package/tests/e2e/page-background-loading.test.js +152 -0
- package/tests/e2e/project-load-progress-loop.e2e.test.js +305 -0
- package/vitest.config.js +2 -3
|
@@ -122,6 +122,232 @@ describe("Chromium 79 smoke test", () => {
|
|
|
122
122
|
60_000
|
|
123
123
|
);
|
|
124
124
|
|
|
125
|
+
it(
|
|
126
|
+
"hides micro:bit controls until the extension is added",
|
|
127
|
+
async () => {
|
|
128
|
+
const { browser, page, errors } = await openPage("/editor.html?mode=edit");
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await page.waitForSelector("#addExtensionButton", { timeout: 30_000 });
|
|
132
|
+
|
|
133
|
+
const initialState = await page.evaluate(() => {
|
|
134
|
+
const addButton = document.getElementById("addExtensionButton");
|
|
135
|
+
const microBitButton = document.getElementById("microBitConnectionButton");
|
|
136
|
+
return {
|
|
137
|
+
addButtonDisplay: window.getComputedStyle(addButton).display,
|
|
138
|
+
addButtonAriaHidden: addButton.getAttribute("aria-hidden"),
|
|
139
|
+
addButtonDisabled: addButton.disabled,
|
|
140
|
+
microBitButtonDisplay: window.getComputedStyle(microBitButton).display,
|
|
141
|
+
microBitButtonAriaHidden: microBitButton.getAttribute("aria-hidden"),
|
|
142
|
+
microBitButtonDisabled: microBitButton.disabled,
|
|
143
|
+
rightCategoryIds: Array.from(document.querySelectorAll("#selectorsright .category-selector-button")).map(
|
|
144
|
+
(button) => button.id
|
|
145
|
+
),
|
|
146
|
+
extensionEnabled: document.getElementById("connectionButtonsArea").classList.contains("extensionEnabled"),
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(initialState).toEqual({
|
|
151
|
+
addButtonDisplay: "flex",
|
|
152
|
+
addButtonAriaHidden: "false",
|
|
153
|
+
addButtonDisabled: false,
|
|
154
|
+
microBitButtonDisplay: "none",
|
|
155
|
+
microBitButtonAriaHidden: "true",
|
|
156
|
+
microBitButtonDisabled: true,
|
|
157
|
+
rightCategoryIds: ["cog-start", "cog-looks", "cog-sound"],
|
|
158
|
+
extensionEnabled: false,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await page.click("#addExtensionButton");
|
|
162
|
+
await page.waitForSelector("#extensionsLibrary.fade.in", { timeout: 30_000 });
|
|
163
|
+
const libraryState = await page.evaluate(() => ({
|
|
164
|
+
title: document.getElementById("extensionsLibraryTitle").textContent.trim(),
|
|
165
|
+
hasMicroBitCard: Boolean(document.getElementById("microBitExtensionCard")),
|
|
166
|
+
microBitCardAction: document.getElementById("microBitExtensionCardAction").textContent.trim(),
|
|
167
|
+
microBitStillHidden:
|
|
168
|
+
window.getComputedStyle(document.getElementById("microBitConnectionButton")).display === "none",
|
|
169
|
+
}));
|
|
170
|
+
|
|
171
|
+
expect(libraryState).toEqual({
|
|
172
|
+
title: "Extensions",
|
|
173
|
+
hasMicroBitCard: true,
|
|
174
|
+
microBitCardAction: "+",
|
|
175
|
+
microBitStillHidden: true,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await page.click("#microBitExtensionCard");
|
|
179
|
+
await page.waitForFunction(
|
|
180
|
+
() => {
|
|
181
|
+
const microBitButton = document.getElementById("microBitConnectionButton");
|
|
182
|
+
const library = document.getElementById("extensionsLibrary");
|
|
183
|
+
return microBitButton
|
|
184
|
+
&& window.getComputedStyle(microBitButton).display !== "none"
|
|
185
|
+
&& library
|
|
186
|
+
&& !library.classList.contains("in");
|
|
187
|
+
},
|
|
188
|
+
{ timeout: 30_000 }
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const enabledState = await page.evaluate(() => {
|
|
192
|
+
const addButton = document.getElementById("addExtensionButton");
|
|
193
|
+
const microBitButton = document.getElementById("microBitConnectionButton");
|
|
194
|
+
return {
|
|
195
|
+
addButtonDisplay: window.getComputedStyle(addButton).display,
|
|
196
|
+
addButtonAriaHidden: addButton.getAttribute("aria-hidden"),
|
|
197
|
+
addButtonDisabled: addButton.disabled,
|
|
198
|
+
microBitButtonDisplay: window.getComputedStyle(microBitButton).display,
|
|
199
|
+
microBitButtonAriaHidden: microBitButton.getAttribute("aria-hidden"),
|
|
200
|
+
microBitButtonDisabled: microBitButton.disabled,
|
|
201
|
+
rightCategoryIds: Array.from(document.querySelectorAll("#selectorsright .category-selector-button")).map(
|
|
202
|
+
(button) => button.id
|
|
203
|
+
),
|
|
204
|
+
selectedRightCategoryIds: Array.from(
|
|
205
|
+
document.querySelectorAll('#selectorsright .category-selector-button[aria-pressed="true"]')
|
|
206
|
+
).map((button) => button.id),
|
|
207
|
+
paletteBlockTypes: Array.from(document.querySelectorAll("#palette > div")).map(
|
|
208
|
+
(block) => block.owner && block.owner.blocktype
|
|
209
|
+
),
|
|
210
|
+
extensionEnabled: document.getElementById("connectionButtonsArea").classList.contains("extensionEnabled"),
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
expect(enabledState).toEqual({
|
|
215
|
+
addButtonDisplay: "flex",
|
|
216
|
+
addButtonAriaHidden: "false",
|
|
217
|
+
addButtonDisabled: false,
|
|
218
|
+
microBitButtonDisplay: "flex",
|
|
219
|
+
microBitButtonAriaHidden: "false",
|
|
220
|
+
microBitButtonDisabled: false,
|
|
221
|
+
rightCategoryIds: ["cog-start", "cog-looks", "cog-sound", "microbit-start", "microbit-looks"],
|
|
222
|
+
selectedRightCategoryIds: ["microbit-start"],
|
|
223
|
+
paletteBlockTypes: [
|
|
224
|
+
"microbitbuttonpressed",
|
|
225
|
+
"microbitgesture",
|
|
226
|
+
"microbittilted",
|
|
227
|
+
],
|
|
228
|
+
extensionEnabled: true,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await page.click("#microbit-looks");
|
|
232
|
+
await page.waitForFunction(
|
|
233
|
+
() => Array.from(document.querySelectorAll("#palette > div")).some(
|
|
234
|
+
(block) => block.owner && block.owner.blocktype === "microbitdisplaycustom"
|
|
235
|
+
),
|
|
236
|
+
{ timeout: 30_000 }
|
|
237
|
+
);
|
|
238
|
+
const looksPaletteBlockTypes = await page.evaluate(() =>
|
|
239
|
+
Array.from(document.querySelectorAll("#palette > div")).map((block) => block.owner && block.owner.blocktype)
|
|
240
|
+
);
|
|
241
|
+
expect(looksPaletteBlockTypes).toEqual([
|
|
242
|
+
"microbitdisplayheart",
|
|
243
|
+
"microbitdisplayhappy",
|
|
244
|
+
"microbitdisplaycustom",
|
|
245
|
+
"microbitdisplaytext",
|
|
246
|
+
"microbitdisplayclear",
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
const customMatrixState = await page.evaluate(() => {
|
|
250
|
+
const scripts = window.ScratchJr.getActiveScript().owner;
|
|
251
|
+
const block = scripts.insertKeyboardBlock(null, [[
|
|
252
|
+
"microbitdisplaycustom",
|
|
253
|
+
"00000/00000/00000/00000/00000",
|
|
254
|
+
50,
|
|
255
|
+
50,
|
|
256
|
+
]]);
|
|
257
|
+
block.arg.pressMicroBitMatrixEditor({
|
|
258
|
+
preventDefault() {},
|
|
259
|
+
stopPropagation() {},
|
|
260
|
+
});
|
|
261
|
+
const cells = Array.from(document.querySelectorAll(".microbitMatrixCell"));
|
|
262
|
+
cells[12].dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true }));
|
|
263
|
+
return {
|
|
264
|
+
argValue: block.getArgValue(),
|
|
265
|
+
cellCount: cells.length,
|
|
266
|
+
menuOpen: Boolean(document.querySelector(".microbitMatrixMenu")),
|
|
267
|
+
activeCells: Array.from(document.querySelectorAll(".microbitMatrixCell.on")).length,
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
expect(customMatrixState).toEqual({
|
|
271
|
+
argValue: "00000/00000/00100/00000/00000",
|
|
272
|
+
cellCount: 25,
|
|
273
|
+
menuOpen: true,
|
|
274
|
+
activeCells: 1,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const blocksAfterInsert = await page.evaluate(() => {
|
|
278
|
+
const scripts = window.ScratchJr.getActiveScript().owner;
|
|
279
|
+
scripts.insertKeyboardBlock(null, [["microbitdisplayclear", "null", 20, 20]]);
|
|
280
|
+
return window.ScratchJr.getBlocks().map((block) => block.blocktype);
|
|
281
|
+
});
|
|
282
|
+
expect(blocksAfterInsert).toContain("microbitdisplayclear");
|
|
283
|
+
|
|
284
|
+
await page.click("#addExtensionButton");
|
|
285
|
+
await page.waitForSelector("#extensionsLibrary.fade.in", { timeout: 30_000 });
|
|
286
|
+
const loadedLibraryState = await page.evaluate(() => ({
|
|
287
|
+
microBitCardAction: document.getElementById("microBitExtensionCardAction").textContent.trim(),
|
|
288
|
+
microBitCardLoaded: document.getElementById("microBitExtensionCard").classList.contains("loaded"),
|
|
289
|
+
}));
|
|
290
|
+
expect(loadedLibraryState).toEqual({
|
|
291
|
+
microBitCardAction: "-",
|
|
292
|
+
microBitCardLoaded: true,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
await page.click("#microBitExtensionCard");
|
|
296
|
+
await page.waitForSelector("#microBitExtensionRemoveWarning.show", { timeout: 30_000 });
|
|
297
|
+
await page.click("#microBitExtensionRemoveCancel");
|
|
298
|
+
await page.waitForFunction(
|
|
299
|
+
() => !document.getElementById("microBitExtensionRemoveWarning").classList.contains("show"),
|
|
300
|
+
{ timeout: 30_000 }
|
|
301
|
+
);
|
|
302
|
+
const cancelUnloadState = await page.evaluate(() => ({
|
|
303
|
+
microBitButtonDisplay: window.getComputedStyle(document.getElementById("microBitConnectionButton")).display,
|
|
304
|
+
hasMicroBitBlock: window.ScratchJr.getBlocks().some((block) => block.blocktype === "microbitdisplayclear"),
|
|
305
|
+
}));
|
|
306
|
+
expect(cancelUnloadState).toEqual({
|
|
307
|
+
microBitButtonDisplay: "flex",
|
|
308
|
+
hasMicroBitBlock: true,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await page.click("#microBitExtensionCard");
|
|
312
|
+
await page.waitForSelector("#microBitExtensionRemoveWarning.show", { timeout: 30_000 });
|
|
313
|
+
await page.click("#microBitExtensionRemoveConfirm");
|
|
314
|
+
await page.waitForFunction(
|
|
315
|
+
() => window.getComputedStyle(document.getElementById("microBitConnectionButton")).display === "none"
|
|
316
|
+
&& !document.querySelector("#selectorsright #microbit-start")
|
|
317
|
+
&& !document.getElementById("extensionsLibrary").classList.contains("in"),
|
|
318
|
+
{ timeout: 30_000 }
|
|
319
|
+
);
|
|
320
|
+
const unloadedState = await page.evaluate(() => ({
|
|
321
|
+
addButtonDisplay: window.getComputedStyle(document.getElementById("addExtensionButton")).display,
|
|
322
|
+
microBitButtonDisplay: window.getComputedStyle(document.getElementById("microBitConnectionButton")).display,
|
|
323
|
+
extensionsLibraryOpen: document.getElementById("extensionsLibrary").classList.contains("in"),
|
|
324
|
+
microBitCardAction: document.getElementById("microBitExtensionCardAction").textContent.trim(),
|
|
325
|
+
microBitCardLoaded: document.getElementById("microBitExtensionCard").classList.contains("loaded"),
|
|
326
|
+
rightCategoryIds: Array.from(document.querySelectorAll("#selectorsright .category-selector-button")).map(
|
|
327
|
+
(button) => button.id
|
|
328
|
+
),
|
|
329
|
+
hasMicroBitBlock: window.ScratchJr.getBlocks().some((block) => block.blocktype.indexOf("microbit") === 0),
|
|
330
|
+
extensionEnabled: document.getElementById("connectionButtonsArea").classList.contains("extensionEnabled"),
|
|
331
|
+
}));
|
|
332
|
+
expect(unloadedState).toEqual({
|
|
333
|
+
addButtonDisplay: "flex",
|
|
334
|
+
microBitButtonDisplay: "none",
|
|
335
|
+
extensionsLibraryOpen: false,
|
|
336
|
+
microBitCardAction: "+",
|
|
337
|
+
microBitCardLoaded: false,
|
|
338
|
+
rightCategoryIds: ["cog-start", "cog-looks", "cog-sound"],
|
|
339
|
+
hasMicroBitBlock: false,
|
|
340
|
+
extensionEnabled: false,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
expect(errors).toEqual([]);
|
|
344
|
+
} finally {
|
|
345
|
+
await browser.close();
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
60_000
|
|
349
|
+
);
|
|
350
|
+
|
|
125
351
|
it(
|
|
126
352
|
"opens tutorials from the book tab and returns there from tutorial mode",
|
|
127
353
|
async () => {
|
|
@@ -65,6 +65,7 @@ vi.mock('@/utils/accessibility', () => ({
|
|
|
65
65
|
setSelectedState: vi.fn()
|
|
66
66
|
}));
|
|
67
67
|
vi.mock('@/html-svgs/cog', () => ({ cogSvg: '<svg></svg>' }));
|
|
68
|
+
vi.mock('@/html-svgs/microbit', () => ({ microBitSvg: '<svg></svg>' }));
|
|
68
69
|
vi.mock('@/html-svgs/sprite', () => ({ spriteSvg: '<svg></svg>' }));
|
|
69
70
|
vi.mock('@/html-svgs/marty', () => ({ martySvg: '<svg></svg>' }));
|
|
70
71
|
vi.mock('@/html-svgs/sprite-deselected', () => ({ spriteDeselectedSvg: '<svg></svg>' }));
|
|
@@ -126,6 +127,11 @@ describe('Marty connection UI', () => {
|
|
|
126
127
|
}),
|
|
127
128
|
removeMarty: vi.fn(),
|
|
128
129
|
setMartySensorAvailability: vi.fn()
|
|
130
|
+
},
|
|
131
|
+
microBitManager: {
|
|
132
|
+
addMicroBit: vi.fn(),
|
|
133
|
+
wireMicroBitWithBlocks: vi.fn(),
|
|
134
|
+
removeMicroBit: vi.fn()
|
|
129
135
|
}
|
|
130
136
|
};
|
|
131
137
|
warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
@@ -242,6 +248,37 @@ describe('Marty connection UI', () => {
|
|
|
242
248
|
expect(button.classList.contains('connectButtonConnected')).toBe(true);
|
|
243
249
|
expect(button.querySelector('.iconButtonContainer').classList.contains('connectedButtonContainer')).toBe(true);
|
|
244
250
|
});
|
|
251
|
+
|
|
252
|
+
it('uses a friendly message when the micro:bit chooser is cancelled', () => {
|
|
253
|
+
const message = UI.getMicroBitConnectionErrorMessage(
|
|
254
|
+
new DOMException('User cancelled the requestDevice() chooser.', 'NotFoundError')
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(message).toBe('No micro:bit was selected. Click Connect again when you are ready.');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('registers micro:bit blocks and restores the button after direct disconnect', () => {
|
|
261
|
+
const button = createConnectionButton();
|
|
262
|
+
const oldOnClick = vi.fn();
|
|
263
|
+
button.onclick = oldOnClick;
|
|
264
|
+
const microBit = createMicroBit();
|
|
265
|
+
|
|
266
|
+
UI.setupMicroBitConnectionButton(button, microBit);
|
|
267
|
+
|
|
268
|
+
expect(window.microBitManager.addMicroBit).toHaveBeenCalledWith(microBit);
|
|
269
|
+
expect(window.microBitManager.wireMicroBitWithBlocks).toHaveBeenCalledWith('microbit-1');
|
|
270
|
+
expect(button.classList.contains('connectButtonConnected')).toBe(true);
|
|
271
|
+
|
|
272
|
+
button.onclick();
|
|
273
|
+
vi.advanceTimersByTime(1000);
|
|
274
|
+
|
|
275
|
+
expect(microBit.disconnect).toHaveBeenCalled();
|
|
276
|
+
expect(window.microBitManager.removeMicroBit).toHaveBeenCalledWith(microBit);
|
|
277
|
+
expect(button.onclick).toBe(oldOnClick);
|
|
278
|
+
expect(button.classList.contains('connectButtonConnected')).toBe(false);
|
|
279
|
+
expect(button.querySelector('.iconButtonContainer').classList.contains('notConnectedButtonContainer')).toBe(true);
|
|
280
|
+
expect(microBit.__disconnectUnsubscribed).toBe(true);
|
|
281
|
+
});
|
|
245
282
|
});
|
|
246
283
|
|
|
247
284
|
function createMartyRaft() {
|
|
@@ -265,6 +302,25 @@ function createConnectionButton() {
|
|
|
265
302
|
return button;
|
|
266
303
|
}
|
|
267
304
|
|
|
305
|
+
function createMicroBit() {
|
|
306
|
+
const microBit = {
|
|
307
|
+
id: 'microbit-1',
|
|
308
|
+
getFriendlyName: () => 'micro:bit One',
|
|
309
|
+
disconnect: vi.fn(() => {
|
|
310
|
+
if (microBit.__disconnectCallback) {
|
|
311
|
+
microBit.__disconnectCallback(microBit);
|
|
312
|
+
}
|
|
313
|
+
}),
|
|
314
|
+
addDisconnectListener: vi.fn(callback => {
|
|
315
|
+
microBit.__disconnectCallback = callback;
|
|
316
|
+
return () => {
|
|
317
|
+
microBit.__disconnectUnsubscribed = true;
|
|
318
|
+
};
|
|
319
|
+
})
|
|
320
|
+
};
|
|
321
|
+
return microBit;
|
|
322
|
+
}
|
|
323
|
+
|
|
268
324
|
class FakeElement {
|
|
269
325
|
constructor(classes = []) {
|
|
270
326
|
this.classList = new FakeClassList(classes);
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const { mockPrims } = vi.hoisted(() => ({
|
|
4
|
+
mockPrims: {
|
|
5
|
+
OnMicroBitEvent: vi.fn()
|
|
6
|
+
}
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock('@/editor/engine/Prims', () => ({
|
|
10
|
+
default: mockPrims
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock('../../src/editor/engine/Prims', () => ({
|
|
14
|
+
default: mockPrims
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe('MicroBitBlocks', () => {
|
|
18
|
+
let MicroBitBlocks;
|
|
19
|
+
let Prims;
|
|
20
|
+
let sensorListener;
|
|
21
|
+
let blocks;
|
|
22
|
+
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
sensorListener = null;
|
|
26
|
+
blocks = null;
|
|
27
|
+
const [blocksModule, primsModule] = await Promise.all([
|
|
28
|
+
import('@/microbit/MicroBitBlocks.js'),
|
|
29
|
+
import('@/editor/engine/Prims')
|
|
30
|
+
]);
|
|
31
|
+
MicroBitBlocks = blocksModule.default;
|
|
32
|
+
Prims = primsModule.default;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
if (blocks) {
|
|
37
|
+
blocks.destroy();
|
|
38
|
+
}
|
|
39
|
+
vi.useRealTimers();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('emits individual button events after the combo window', () => {
|
|
43
|
+
vi.useFakeTimers();
|
|
44
|
+
blocks = new MicroBitBlocks(createMicroBit());
|
|
45
|
+
|
|
46
|
+
sensorListener(
|
|
47
|
+
createSensors({buttonA: 1}),
|
|
48
|
+
createSensors({buttonA: 0})
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(Prims.OnMicroBitEvent).not.toHaveBeenCalled();
|
|
52
|
+
|
|
53
|
+
vi.advanceTimersByTime(120);
|
|
54
|
+
|
|
55
|
+
expect(Prims.OnMicroBitEvent).toHaveBeenCalledWith('microbitbuttona');
|
|
56
|
+
expect(Prims.OnMicroBitEvent).not.toHaveBeenCalledWith('microbitbuttonab');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('emits A+B button events when both buttons are pressed together', () => {
|
|
60
|
+
blocks = new MicroBitBlocks(createMicroBit());
|
|
61
|
+
|
|
62
|
+
sensorListener(
|
|
63
|
+
createSensors({buttonA: 1, buttonB: 1}),
|
|
64
|
+
createSensors({buttonA: 0, buttonB: 0})
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(Prims.OnMicroBitEvent).toHaveBeenCalledWith('microbitbuttonab');
|
|
68
|
+
expect(Prims.OnMicroBitEvent).not.toHaveBeenCalledWith('microbitbuttona');
|
|
69
|
+
expect(Prims.OnMicroBitEvent).not.toHaveBeenCalledWith('microbitbuttonb');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('promotes a near-simultaneous second button press to A+B', () => {
|
|
73
|
+
vi.useFakeTimers();
|
|
74
|
+
blocks = new MicroBitBlocks(createMicroBit());
|
|
75
|
+
|
|
76
|
+
sensorListener(
|
|
77
|
+
createSensors({buttonA: 1}),
|
|
78
|
+
createSensors({buttonA: 0})
|
|
79
|
+
);
|
|
80
|
+
vi.advanceTimersByTime(60);
|
|
81
|
+
sensorListener(
|
|
82
|
+
createSensors({buttonA: 1, buttonB: 1}),
|
|
83
|
+
createSensors({buttonA: 1, buttonB: 0})
|
|
84
|
+
);
|
|
85
|
+
vi.advanceTimersByTime(120);
|
|
86
|
+
|
|
87
|
+
expect(Prims.OnMicroBitEvent).toHaveBeenCalledTimes(1);
|
|
88
|
+
expect(Prims.OnMicroBitEvent).toHaveBeenCalledWith('microbitbuttonab');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('emits gesture events on gesture bit transitions', () => {
|
|
92
|
+
blocks = new MicroBitBlocks(createMicroBit());
|
|
93
|
+
|
|
94
|
+
sensorListener(
|
|
95
|
+
createSensors({gestureState: 1}),
|
|
96
|
+
createSensors({gestureState: 0})
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(Prims.OnMicroBitEvent).toHaveBeenCalledWith('microbitgestureshaken');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('emits tilt events on every tilted sensor update', () => {
|
|
103
|
+
blocks = new MicroBitBlocks(createMicroBit());
|
|
104
|
+
|
|
105
|
+
sensorListener(
|
|
106
|
+
createSensors({tiltX: -180}),
|
|
107
|
+
createSensors({tiltX: 0})
|
|
108
|
+
);
|
|
109
|
+
sensorListener(
|
|
110
|
+
createSensors({tiltX: -180}),
|
|
111
|
+
createSensors({tiltX: -180})
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(Prims.OnMicroBitEvent).toHaveBeenCalledWith('microbittiltany');
|
|
115
|
+
expect(Prims.OnMicroBitEvent).toHaveBeenCalledWith('microbittiltleft');
|
|
116
|
+
expect(Prims.OnMicroBitEvent).toHaveBeenCalledTimes(4);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('sends matrix rows for display symbols', () => {
|
|
120
|
+
const microBit = createMicroBit();
|
|
121
|
+
blocks = new MicroBitBlocks(microBit);
|
|
122
|
+
|
|
123
|
+
blocks.displaySymbol('microbitdisplayheart');
|
|
124
|
+
|
|
125
|
+
expect(microBit.displayMatrix).toHaveBeenCalledWith(new Uint8Array([10, 31, 31, 14, 4]));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('sends matrix rows for custom display patterns', () => {
|
|
129
|
+
const microBit = createMicroBit();
|
|
130
|
+
blocks = new MicroBitBlocks(microBit);
|
|
131
|
+
|
|
132
|
+
blocks.displayPattern('10000/01000/00100/00010/00001');
|
|
133
|
+
|
|
134
|
+
expect(microBit.displayMatrix).toHaveBeenCalledWith(new Uint8Array([1, 2, 4, 8, 16]));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
function createMicroBit(sensors = createSensors()) {
|
|
138
|
+
return {
|
|
139
|
+
sensors,
|
|
140
|
+
addSensorListener: vi.fn(callback => {
|
|
141
|
+
sensorListener = callback;
|
|
142
|
+
return vi.fn();
|
|
143
|
+
}),
|
|
144
|
+
isConnected: () => true,
|
|
145
|
+
displayMatrix: vi.fn(),
|
|
146
|
+
displayText: vi.fn(),
|
|
147
|
+
clearDisplay: vi.fn()
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function createSensors(overrides = {}) {
|
|
152
|
+
return {
|
|
153
|
+
tiltX: 0,
|
|
154
|
+
tiltY: 0,
|
|
155
|
+
buttonA: 0,
|
|
156
|
+
buttonB: 0,
|
|
157
|
+
touchPins: [0, 0, 0],
|
|
158
|
+
gestureState: 0,
|
|
159
|
+
...overrides
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
});
|