@robotical/martyblocksjr 4.2.1 → 4.2.3
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/viewOnCompactWideLabel.png +0 -0
- package/editions/free/src/css/editor.css +10 -0
- package/editions/free/src/css/editorleftpanel.css +20 -14
- package/editions/free/src/inapp/about.html +55 -26
- package/editions/free/src/inapp/blocks.html +16 -459
- package/editions/free/src/inapp/images/interface.png +0 -0
- package/editions/free/src/inapp/images/paint.png +0 -0
- package/editions/free/src/inapp/interface.html +136 -123
- package/editions/free/src/inapp/style/about.css +333 -21
- package/editions/free/src/inapp/style/blocks.css +423 -82
- package/editions/free/src/inapp/style/interface.css +178 -453
- package/editions/free/src/localizations/bg.json +36 -2
- package/editions/free/src/localizations/ca.json +36 -2
- package/editions/free/src/localizations/cs.json +36 -2
- package/editions/free/src/localizations/cy.json +36 -2
- package/editions/free/src/localizations/da.json +36 -2
- package/editions/free/src/localizations/de.json +36 -2
- package/editions/free/src/localizations/el.json +36 -2
- package/editions/free/src/localizations/en.json +114 -7
- package/editions/free/src/localizations/es.json +36 -2
- package/editions/free/src/localizations/fi.json +36 -2
- package/editions/free/src/localizations/fr.json +36 -2
- package/editions/free/src/localizations/it.json +36 -2
- package/editions/free/src/localizations/ja.json +36 -2
- package/editions/free/src/localizations/ko.json +36 -2
- package/editions/free/src/localizations/nl.json +36 -2
- package/editions/free/src/localizations/no.json +36 -2
- package/editions/free/src/localizations/pl.json +36 -2
- package/editions/free/src/localizations/pt-br.json +36 -2
- package/editions/free/src/localizations/pt.json +36 -2
- package/editions/free/src/localizations/sv.json +36 -2
- package/editions/free/src/localizations/th.json +36 -2
- package/editions/free/src/localizations/tr.json +36 -2
- package/editions/free/src/localizations/uk.json +36 -2
- package/editions/free/src/localizations/zh-cn.json +36 -2
- package/editions/free/src/localizations/zh-tw.json +36 -2
- package/package.json +1 -1
- package/scripts/a11y-structure.mjs +9 -2
- package/tests/BlockGuideRegistry.test.js +60 -0
- package/tests/MartyManager.test.js +88 -0
- package/tests/e2e/accessibility.e2e.test.js +154 -10
- package/tests/e2e/animated-sprite-paint-disabled.e2e.test.js +90 -2
- package/tests/e2e/blocks-jr-hidden-blocks.e2e.test.js +84 -0
- package/tests/e2e/chromium-79-smoke.test.js +12 -17
- package/tests/e2e/marty-script-activation-paths.e2e.test.js +71 -0
- package/tests/e2e/microbit-extension-project-reload.e2e.test.js +1 -6
- package/tests/e2e/project-load-progress-loop.e2e.test.js +1 -6
- package/tests/e2e/sidebar-scrollbars.e2e.test.js +179 -0
|
@@ -138,9 +138,11 @@ async function getFocusStyles(page, selector) {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
function expectVisibleFocusIndicator(focusStyles) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
const hasVisibleOutline = focusStyles.outlineStyle !== 'none' &&
|
|
142
|
+
focusStyles.outlineWidth > 0 &&
|
|
143
|
+
focusStyles.outlineColor !== 'rgba(0, 0, 0, 0)';
|
|
144
|
+
const hasVisibleBoxShadow = focusStyles.boxShadow && focusStyles.boxShadow !== 'none';
|
|
145
|
+
expect(hasVisibleOutline || hasVisibleBoxShadow).toBe(true);
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
async function expectNamedControls(page, selectors) {
|
|
@@ -231,7 +233,7 @@ describe('Accessibility shell audit', () => {
|
|
|
231
233
|
await new Promise((resolve) => setTimeout(resolve, 1200));
|
|
232
234
|
await page.click('#booktab');
|
|
233
235
|
await page.waitForSelector('iframe#htmlcontents', { timeout: 30_000 });
|
|
234
|
-
expect(await page.$eval('iframe#htmlcontents', (element) => element.getAttribute('title'))).toBe('About
|
|
236
|
+
expect(await page.$eval('iframe#htmlcontents', (element) => element.getAttribute('title'))).toBe('About Blocks Jr');
|
|
235
237
|
|
|
236
238
|
expectNoAxeViolations(await runAxe(page));
|
|
237
239
|
expect(errors).toEqual([]);
|
|
@@ -250,11 +252,25 @@ describe('Accessibility shell audit', () => {
|
|
|
250
252
|
});
|
|
251
253
|
|
|
252
254
|
const guideCases = [
|
|
255
|
+
{
|
|
256
|
+
path: 'inapp/about.html',
|
|
257
|
+
title: 'About Blocks Jr',
|
|
258
|
+
focusSelector: '#skip-link',
|
|
259
|
+
buttonSelectors: [],
|
|
260
|
+
decorativeImageSelector: '.about-hero-art img'
|
|
261
|
+
},
|
|
253
262
|
{
|
|
254
263
|
path: 'inapp/interface.html',
|
|
255
264
|
title: 'Interface Guide',
|
|
256
265
|
focusSelector: '#interface-button-save',
|
|
257
|
-
buttonSelectors: [
|
|
266
|
+
buttonSelectors: [
|
|
267
|
+
'#interface-button-save',
|
|
268
|
+
'#interface-button-extensions',
|
|
269
|
+
'#interface-button-stage',
|
|
270
|
+
'#interface-button-green-flag',
|
|
271
|
+
'#interface-button-duplicate-page',
|
|
272
|
+
'#interface-button-zoom'
|
|
273
|
+
],
|
|
258
274
|
decorativeImageSelector: '.ipad-project-view'
|
|
259
275
|
},
|
|
260
276
|
{
|
|
@@ -267,9 +283,13 @@ describe('Accessibility shell audit', () => {
|
|
|
267
283
|
{
|
|
268
284
|
path: 'inapp/blocks.html',
|
|
269
285
|
title: 'Blocks Guide',
|
|
270
|
-
focusSelector: '#
|
|
271
|
-
buttonSelectors: [
|
|
272
|
-
|
|
286
|
+
focusSelector: '#blocks-guide-tab-marty',
|
|
287
|
+
buttonSelectors: [
|
|
288
|
+
'#blocks-guide-tab-marty',
|
|
289
|
+
'#blocks-guide-tab-sprite',
|
|
290
|
+
'#blocks-guide-tab-cog'
|
|
291
|
+
],
|
|
292
|
+
decorativeImageSelector: '.block-guide-icon'
|
|
273
293
|
}
|
|
274
294
|
];
|
|
275
295
|
|
|
@@ -279,12 +299,14 @@ describe('Accessibility shell audit', () => {
|
|
|
279
299
|
await page.goto(`${HOST}/${guideCase.path}`, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
|
280
300
|
await waitForTitle(page, guideCase.title);
|
|
281
301
|
await page.waitForSelector('#skip-link');
|
|
282
|
-
await page.waitForSelector('#content
|
|
302
|
+
await page.waitForSelector('#content');
|
|
283
303
|
|
|
284
304
|
expect((await page.title()).includes(guideCase.title)).toBe(true);
|
|
285
305
|
expect(await page.$eval('html', (element) => element.lang)).toBe('en');
|
|
286
306
|
expect(await page.$eval('#skip-link', (element) => element.getAttribute('href'))).toBe('#content');
|
|
287
|
-
expect(await page.$eval('#content', (element) =>
|
|
307
|
+
expect(await page.$eval('#content', (element) => {
|
|
308
|
+
return element.tagName.toLowerCase() === 'main' || element.getAttribute('role') === 'main';
|
|
309
|
+
})).toBe(true);
|
|
288
310
|
|
|
289
311
|
if (guideCase.buttonSelectors.length > 0) {
|
|
290
312
|
await expectNamedControls(page, guideCase.buttonSelectors);
|
|
@@ -298,6 +320,127 @@ describe('Accessibility shell audit', () => {
|
|
|
298
320
|
await tabToSelector(page, guideCase.focusSelector);
|
|
299
321
|
expectVisibleFocusIndicator(await getFocusStyles(page, guideCase.focusSelector));
|
|
300
322
|
|
|
323
|
+
if (guideCase.path === 'inapp/interface.html') {
|
|
324
|
+
const buttonIds = await page.$$eval('.interface-button', (buttons) => {
|
|
325
|
+
return buttons.map((button) => button.id);
|
|
326
|
+
});
|
|
327
|
+
expect(buttonIds).toHaveLength(24);
|
|
328
|
+
expect(await page.$$eval('.interface-connector', (connectors) => connectors.length)).toBe(24);
|
|
329
|
+
expect(await page.$eval('#interface-key-header', (element) => element.textContent)).toBe('1 | Save');
|
|
330
|
+
|
|
331
|
+
for (let index = 0; index < buttonIds.length; index += 1) {
|
|
332
|
+
const selector = `#${buttonIds[index]}`;
|
|
333
|
+
await page.click(selector);
|
|
334
|
+
const selectedFeature = await page.evaluate((buttonSelector) => {
|
|
335
|
+
const button = document.querySelector(buttonSelector);
|
|
336
|
+
const connector = document.querySelector(
|
|
337
|
+
`.interface-connector[data-guide-key="${button.getAttribute('data-guide-key')}"]`
|
|
338
|
+
);
|
|
339
|
+
const number = button.querySelector('span');
|
|
340
|
+
return {
|
|
341
|
+
ariaCurrent: button.getAttribute('aria-current'),
|
|
342
|
+
ariaLabel: button.getAttribute('aria-label'),
|
|
343
|
+
buttonBackground: window.getComputedStyle(button).backgroundColor,
|
|
344
|
+
connectorIsSelected: connector.classList.contains('interface-connector-selected'),
|
|
345
|
+
connectorLineLength: connector.querySelector('line').getTotalLength(),
|
|
346
|
+
description: document.querySelector('#interface-key-description').textContent,
|
|
347
|
+
header: document.querySelector('#interface-key-header').textContent,
|
|
348
|
+
numberColor: window.getComputedStyle(number).color,
|
|
349
|
+
numberText: number.textContent,
|
|
350
|
+
selectedConnectorCount: document.querySelectorAll(
|
|
351
|
+
'.interface-connector-selected'
|
|
352
|
+
).length,
|
|
353
|
+
selectedCount: document.querySelectorAll('.interface-button[aria-current="page"]').length
|
|
354
|
+
};
|
|
355
|
+
}, selector);
|
|
356
|
+
|
|
357
|
+
expect(selectedFeature.header).toBe(selectedFeature.ariaLabel);
|
|
358
|
+
expect(selectedFeature.header.startsWith(`${index + 1} | `)).toBe(true);
|
|
359
|
+
expect(selectedFeature.header).not.toContain('String missing:');
|
|
360
|
+
expect(selectedFeature.description.trim().length).toBeGreaterThan(0);
|
|
361
|
+
expect(selectedFeature.description).not.toContain('String missing:');
|
|
362
|
+
expect(selectedFeature.ariaCurrent).toBe('page');
|
|
363
|
+
expect(selectedFeature.numberText).toBe(String(index + 1));
|
|
364
|
+
expect(selectedFeature.numberColor).not.toBe(selectedFeature.buttonBackground);
|
|
365
|
+
expect(selectedFeature.connectorIsSelected).toBe(true);
|
|
366
|
+
expect(selectedFeature.connectorLineLength).toBeGreaterThan(0);
|
|
367
|
+
expect(selectedFeature.selectedConnectorCount).toBe(1);
|
|
368
|
+
expect(selectedFeature.selectedCount).toBe(1);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
await page.click('#interface-button-extensions');
|
|
372
|
+
expect(await page.$eval('#interface-key-header', (element) => element.textContent)).toBe('3 | Extensions');
|
|
373
|
+
expect(await page.$eval('#interface-key-description', (element) => element.textContent)).toContain('micro:bit');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (guideCase.path === 'inapp/blocks.html') {
|
|
377
|
+
const inventory = await page.evaluate(() => {
|
|
378
|
+
const modeCounts = {};
|
|
379
|
+
document.querySelectorAll('[data-guide-mode-panel]').forEach((panel) => {
|
|
380
|
+
modeCounts[panel.getAttribute('data-guide-mode-panel')] =
|
|
381
|
+
panel.querySelectorAll('.block-guide-card').length;
|
|
382
|
+
});
|
|
383
|
+
return {
|
|
384
|
+
activeMode: document.querySelector('[role="tab"][aria-selected="true"]')
|
|
385
|
+
.getAttribute('data-guide-mode'),
|
|
386
|
+
extensionCount: document.querySelectorAll(
|
|
387
|
+
'#blocks-guide-extension .block-guide-card'
|
|
388
|
+
).length,
|
|
389
|
+
modeCounts,
|
|
390
|
+
missingCopy: document.body.textContent.includes('String missing:'),
|
|
391
|
+
tabCount: document.querySelectorAll('[role="tab"]').length
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
expect(inventory).toEqual({
|
|
395
|
+
activeMode: 'marty',
|
|
396
|
+
extensionCount: 8,
|
|
397
|
+
modeCounts: {
|
|
398
|
+
marty: 44,
|
|
399
|
+
sprite: 31,
|
|
400
|
+
cog: 19
|
|
401
|
+
},
|
|
402
|
+
missingCopy: false,
|
|
403
|
+
tabCount: 3
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
await page.click('#blocks-guide-tab-sprite');
|
|
407
|
+
expect(await page.$eval('#blocks-guide-panel-sprite', (panel) => panel.hidden)).toBe(false);
|
|
408
|
+
expect(await page.$eval('#blocks-guide-panel-marty', (panel) => panel.hidden)).toBe(true);
|
|
409
|
+
|
|
410
|
+
await page.focus('#blocks-guide-tab-sprite');
|
|
411
|
+
await page.keyboard.press('ArrowRight');
|
|
412
|
+
expect(await page.$eval('#blocks-guide-tab-cog', (tab) => tab.getAttribute('aria-selected'))).toBe('true');
|
|
413
|
+
expect(await page.$eval('#blocks-guide-panel-cog', (panel) => panel.hidden)).toBe(false);
|
|
414
|
+
|
|
415
|
+
for (const modeId of ['marty', 'sprite', 'cog']) {
|
|
416
|
+
await page.evaluate(() => {
|
|
417
|
+
window.scrollTo(0, document.documentElement.scrollHeight);
|
|
418
|
+
});
|
|
419
|
+
await page.click(`#blocks-guide-tab-${modeId}`);
|
|
420
|
+
const selectedMode = await page.evaluate((expectedMode) => {
|
|
421
|
+
const tab = document.querySelector(`[data-guide-mode="${expectedMode}"]`);
|
|
422
|
+
const panel = document.querySelector(`[data-guide-mode-panel="${expectedMode}"]`);
|
|
423
|
+
const firstCard = panel.querySelector('.block-guide-card');
|
|
424
|
+
const tabsShell = document.querySelector('.blocks-guide-tabs-shell');
|
|
425
|
+
const cardRect = firstCard.getBoundingClientRect();
|
|
426
|
+
const tabsRect = tabsShell.getBoundingClientRect();
|
|
427
|
+
return {
|
|
428
|
+
activeMode: document.querySelector('[role="tab"][aria-selected="true"]')
|
|
429
|
+
.getAttribute('data-guide-mode'),
|
|
430
|
+
firstCardVisible: cardRect.top < window.innerHeight && cardRect.bottom > tabsRect.bottom,
|
|
431
|
+
panelHidden: panel.hidden,
|
|
432
|
+
tabSelected: tab.getAttribute('aria-selected')
|
|
433
|
+
};
|
|
434
|
+
}, modeId);
|
|
435
|
+
expect(selectedMode).toEqual({
|
|
436
|
+
activeMode: modeId,
|
|
437
|
+
firstCardVisible: true,
|
|
438
|
+
panelHidden: false,
|
|
439
|
+
tabSelected: 'true'
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
301
444
|
await activateSkipLink(page, 'content');
|
|
302
445
|
|
|
303
446
|
expectNoAxeViolations(await runAxe(page));
|
|
@@ -378,6 +521,7 @@ describe('Accessibility shell audit', () => {
|
|
|
378
521
|
const motionButton = document.getElementById('sprite-motion');
|
|
379
522
|
return motionButton && motionButton.getAttribute('aria-pressed') === 'true';
|
|
380
523
|
}, { timeout: 5_000 });
|
|
524
|
+
expect((await getFocusStyles(page, '#sprite-motion')).outlineStyle).toBe('none');
|
|
381
525
|
await page.waitForSelector('#forward_block[role="button"]', { timeout: 30_000 });
|
|
382
526
|
|
|
383
527
|
await expectNamedControls(page, ['#forward_block', '#back_block']);
|
|
@@ -72,7 +72,94 @@ async function openEditor() {
|
|
|
72
72
|
return { browser, page, errors };
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
async function dragBlockToScripts(page, categorySelector, blockSelector, blockType) {
|
|
76
|
+
const blockCount = await page.evaluate(() => window.ScratchJr.getBlocks().length);
|
|
77
|
+
await page.click(categorySelector);
|
|
78
|
+
await page.waitForSelector(blockSelector, { visible: true });
|
|
79
|
+
|
|
80
|
+
const source = await page.$eval(blockSelector, (node) => {
|
|
81
|
+
const rect = node.getBoundingClientRect();
|
|
82
|
+
return {
|
|
83
|
+
x: rect.left + rect.width / 2,
|
|
84
|
+
y: rect.top + rect.height / 2,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
const target = await page.$eval("#scripts", (node) => {
|
|
88
|
+
const rect = node.getBoundingClientRect();
|
|
89
|
+
return {
|
|
90
|
+
x: rect.left + Math.min(220, rect.width / 2),
|
|
91
|
+
y: rect.top + Math.min(170, rect.height / 2),
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await page.mouse.move(source.x, source.y);
|
|
96
|
+
await page.mouse.down();
|
|
97
|
+
await page.mouse.move(target.x, target.y, { steps: 12 });
|
|
98
|
+
await page.mouse.up();
|
|
99
|
+
|
|
100
|
+
await page.waitForFunction(
|
|
101
|
+
(before, type) => window.ScratchJr.getBlocks().length > before
|
|
102
|
+
&& window.ScratchJr.getBlocks().some((block) => block.blocktype === type),
|
|
103
|
+
{ timeout: 30_000 },
|
|
104
|
+
blockCount,
|
|
105
|
+
blockType
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function getSelectedSpritePaintState(page) {
|
|
110
|
+
return page.evaluate(() => {
|
|
111
|
+
const sprite = window.ScratchJr && window.ScratchJr.getSprite();
|
|
112
|
+
const thumb = sprite && sprite.thumbnail;
|
|
113
|
+
const brush = thumb && thumb.querySelector(".brush");
|
|
114
|
+
if (!sprite || !thumb || !brush) {
|
|
115
|
+
throw new Error("Selected sprite paint controls were not found");
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
md5: sprite.md5,
|
|
119
|
+
thumbClass: thumb.className,
|
|
120
|
+
brushDisplay: window.getComputedStyle(brush).display,
|
|
121
|
+
blockTypes: window.ScratchJr.getBlocks().map((block) => block.blocktype),
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
describe("sprite paint editing availability", () => {
|
|
127
|
+
it(
|
|
128
|
+
"keeps Marty paint editing disabled after dropping a block",
|
|
129
|
+
async () => {
|
|
130
|
+
const { browser, page, errors } = await openEditor();
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const beforeDrop = await getSelectedSpritePaintState(page);
|
|
134
|
+
expect(beforeDrop.md5).toBe("Sprites_Marty.svg");
|
|
135
|
+
expect(beforeDrop.thumbClass).toContain("martynoneditable");
|
|
136
|
+
expect(beforeDrop.brushDisplay).toBe("none");
|
|
137
|
+
|
|
138
|
+
await dragBlockToScripts(page, "#sprite-motion", "#forward_block", "forward");
|
|
139
|
+
|
|
140
|
+
const afterDrop = await getSelectedSpritePaintState(page);
|
|
141
|
+
expect(afterDrop.thumbClass).toContain("martynoneditable");
|
|
142
|
+
expect(afterDrop.brushDisplay).toBe("none");
|
|
143
|
+
expect(afterDrop.blockTypes).toContain("forward");
|
|
144
|
+
|
|
145
|
+
const paintFrameVisible = await page.evaluate(() => {
|
|
146
|
+
const brush = window.ScratchJr.getSprite().thumbnail.querySelector(".brush");
|
|
147
|
+
brush.dispatchEvent(new PointerEvent("pointerdown", {
|
|
148
|
+
bubbles: true,
|
|
149
|
+
cancelable: true,
|
|
150
|
+
pointerType: "mouse",
|
|
151
|
+
}));
|
|
152
|
+
return window.getComputedStyle(document.getElementById("paintframe")).display !== "none";
|
|
153
|
+
});
|
|
154
|
+
expect(paintFrameVisible).toBe(false);
|
|
155
|
+
expect(errors).toEqual([]);
|
|
156
|
+
} finally {
|
|
157
|
+
await browser.close();
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
60_000
|
|
161
|
+
);
|
|
162
|
+
|
|
76
163
|
it(
|
|
77
164
|
"disables paint editing for the animated bee in the library and sprite panel",
|
|
78
165
|
async () => {
|
|
@@ -231,7 +318,8 @@ describe("animated sprite paint editing", () => {
|
|
|
231
318
|
expect(layoutState.selectedBackgroundSize).toBe("100% 100%");
|
|
232
319
|
expect(layoutState.name.right).toBeLessThanOrEqual(layoutState.brush.left + 0.5);
|
|
233
320
|
expect(layoutState.brush.left).toBeGreaterThanOrEqual(layoutState.selected.left);
|
|
234
|
-
expect(layoutState.
|
|
321
|
+
expect(layoutState.selected.right - layoutState.brush.right)
|
|
322
|
+
.toBeGreaterThanOrEqual(layoutState.brush.width * 0.1);
|
|
235
323
|
expect(layoutState.brush.top).toBeGreaterThanOrEqual(layoutState.selected.top);
|
|
236
324
|
expect(layoutState.brush.bottom).toBeLessThanOrEqual(layoutState.selected.bottom);
|
|
237
325
|
|
|
@@ -164,6 +164,14 @@ const getSpriteScriptState = async (page, spriteId) =>
|
|
|
164
164
|
};
|
|
165
165
|
}, spriteId);
|
|
166
166
|
|
|
167
|
+
const getMartySensorPaletteState = page =>
|
|
168
|
+
page.evaluate(() => ({
|
|
169
|
+
colour: Boolean(document.getElementById("martycoloursensed_block")),
|
|
170
|
+
obstacle: Boolean(document.getElementById("martyobstaclesensed_block")),
|
|
171
|
+
light: Boolean(document.getElementById("martylightsensed_block")),
|
|
172
|
+
noise: Boolean(document.getElementById("martynoisesensed_block"))
|
|
173
|
+
}));
|
|
174
|
+
|
|
167
175
|
describe("Blocks Jr hidden block drop regression", () => {
|
|
168
176
|
beforeAll(async () => {
|
|
169
177
|
server = spawn("python3", ["-m", "http.server", String(PORT), "--directory", "editions/free/src"], {
|
|
@@ -259,4 +267,80 @@ describe("Blocks Jr hidden block drop regression", () => {
|
|
|
259
267
|
await browser.close();
|
|
260
268
|
}
|
|
261
269
|
}, 60000);
|
|
270
|
+
|
|
271
|
+
it("always shows standard Marty sensors and gates optional sensor blocks", async () => {
|
|
272
|
+
const { browser, page, errors } = await openEditor();
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
await enableMartyMode(page);
|
|
276
|
+
await page.click("#marty-start");
|
|
277
|
+
await page.waitForSelector("#martycoloursensed_block", { visible: true });
|
|
278
|
+
await page.waitForSelector("#martyobstaclesensed_block", { visible: true });
|
|
279
|
+
|
|
280
|
+
const unpairedState = await getMartySensorPaletteState(page);
|
|
281
|
+
expect(unpairedState).toEqual({
|
|
282
|
+
colour: true,
|
|
283
|
+
obstacle: true,
|
|
284
|
+
light: false,
|
|
285
|
+
noise: false
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
await page.evaluate(() => {
|
|
289
|
+
window.sensorPolicyTestManager = window.martyManager;
|
|
290
|
+
window.martyManager = undefined;
|
|
291
|
+
window.Palette.selectCategory(window.Palette.numcat);
|
|
292
|
+
});
|
|
293
|
+
const managerUnavailableState = await getMartySensorPaletteState(page);
|
|
294
|
+
expect(managerUnavailableState).toEqual({
|
|
295
|
+
colour: true,
|
|
296
|
+
obstacle: true,
|
|
297
|
+
light: false,
|
|
298
|
+
noise: false
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
await page.evaluate(() => {
|
|
302
|
+
window.martyManager = window.sensorPolicyTestManager;
|
|
303
|
+
delete window.sensorPolicyTestManager;
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
await page.evaluate(() => {
|
|
307
|
+
window.martyManager.addMarty({ id: "sensor-policy-test" });
|
|
308
|
+
window.martyManager.setMartySensorAvailability("sensor-policy-test", {
|
|
309
|
+
colour: false,
|
|
310
|
+
obstacle: false,
|
|
311
|
+
light: true,
|
|
312
|
+
noise: false
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
await page.waitForSelector("#martylightsensed_block", { visible: true });
|
|
316
|
+
|
|
317
|
+
const lightSensorState = await getMartySensorPaletteState(page);
|
|
318
|
+
expect(lightSensorState).toEqual({
|
|
319
|
+
colour: true,
|
|
320
|
+
obstacle: true,
|
|
321
|
+
light: true,
|
|
322
|
+
noise: false
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
await page.evaluate(() => {
|
|
326
|
+
window.martyManager.setMartySensorAvailability("sensor-policy-test", {
|
|
327
|
+
light: false,
|
|
328
|
+
noise: true
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
await page.waitForSelector("#martynoisesensed_block", { visible: true });
|
|
332
|
+
await page.waitForFunction(() => !document.getElementById("martylightsensed_block"));
|
|
333
|
+
|
|
334
|
+
const noiseSensorState = await getMartySensorPaletteState(page);
|
|
335
|
+
expect(noiseSensorState).toEqual({
|
|
336
|
+
colour: true,
|
|
337
|
+
obstacle: true,
|
|
338
|
+
light: false,
|
|
339
|
+
noise: true
|
|
340
|
+
});
|
|
341
|
+
expect(errors).toEqual([]);
|
|
342
|
+
} finally {
|
|
343
|
+
await browser.close();
|
|
344
|
+
}
|
|
345
|
+
}, 60000);
|
|
262
346
|
});
|
|
@@ -108,13 +108,8 @@ async function assertTutorialOpened(page, tutorialId) {
|
|
|
108
108
|
expect(tutorialState.tutorialTitle.length).toBeGreaterThan(0);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
async function
|
|
112
|
-
await page
|
|
113
|
-
if (!button.disabled || window.getComputedStyle(button).display !== "none") {
|
|
114
|
-
throw new Error("Expected the extensions entry point to remain hidden and disabled");
|
|
115
|
-
}
|
|
116
|
-
button.onclick();
|
|
117
|
-
});
|
|
111
|
+
async function openExtensionsLibrary(page) {
|
|
112
|
+
await page.click("#addExtensionButton");
|
|
118
113
|
}
|
|
119
114
|
|
|
120
115
|
describe("Chromium 79 smoke test", () => {
|
|
@@ -132,7 +127,7 @@ describe("Chromium 79 smoke test", () => {
|
|
|
132
127
|
);
|
|
133
128
|
|
|
134
129
|
it(
|
|
135
|
-
"keeps the extension entry
|
|
130
|
+
"keeps the extension entry visible and reveals micro:bit controls only while loaded",
|
|
136
131
|
async () => {
|
|
137
132
|
const { browser, page, errors } = await openPage("/editor.html?mode=edit");
|
|
138
133
|
|
|
@@ -157,9 +152,9 @@ describe("Chromium 79 smoke test", () => {
|
|
|
157
152
|
});
|
|
158
153
|
|
|
159
154
|
expect(initialState).toEqual({
|
|
160
|
-
addButtonDisplay: "
|
|
161
|
-
addButtonAriaHidden: "
|
|
162
|
-
addButtonDisabled:
|
|
155
|
+
addButtonDisplay: "flex",
|
|
156
|
+
addButtonAriaHidden: "false",
|
|
157
|
+
addButtonDisabled: false,
|
|
163
158
|
microBitButtonDisplay: "none",
|
|
164
159
|
microBitButtonAriaHidden: "true",
|
|
165
160
|
microBitButtonDisabled: true,
|
|
@@ -167,7 +162,7 @@ describe("Chromium 79 smoke test", () => {
|
|
|
167
162
|
extensionEnabled: false,
|
|
168
163
|
});
|
|
169
164
|
|
|
170
|
-
await
|
|
165
|
+
await openExtensionsLibrary(page);
|
|
171
166
|
await page.waitForSelector("#extensionsLibrary.fade.in", { timeout: 30_000 });
|
|
172
167
|
const libraryState = await page.evaluate(() => ({
|
|
173
168
|
title: document.getElementById("extensionsLibraryTitle").textContent.trim(),
|
|
@@ -221,9 +216,9 @@ describe("Chromium 79 smoke test", () => {
|
|
|
221
216
|
});
|
|
222
217
|
|
|
223
218
|
expect(enabledState).toEqual({
|
|
224
|
-
addButtonDisplay: "
|
|
225
|
-
addButtonAriaHidden: "
|
|
226
|
-
addButtonDisabled:
|
|
219
|
+
addButtonDisplay: "flex",
|
|
220
|
+
addButtonAriaHidden: "false",
|
|
221
|
+
addButtonDisabled: false,
|
|
227
222
|
microBitButtonDisplay: "flex",
|
|
228
223
|
microBitButtonAriaHidden: "false",
|
|
229
224
|
microBitButtonDisabled: false,
|
|
@@ -290,7 +285,7 @@ describe("Chromium 79 smoke test", () => {
|
|
|
290
285
|
});
|
|
291
286
|
expect(blocksAfterInsert).toContain("microbitdisplayclear");
|
|
292
287
|
|
|
293
|
-
await
|
|
288
|
+
await openExtensionsLibrary(page);
|
|
294
289
|
await page.waitForSelector("#extensionsLibrary.fade.in", { timeout: 30_000 });
|
|
295
290
|
const loadedLibraryState = await page.evaluate(() => ({
|
|
296
291
|
microBitCardAction: document.getElementById("microBitExtensionCardAction").textContent.trim(),
|
|
@@ -339,7 +334,7 @@ describe("Chromium 79 smoke test", () => {
|
|
|
339
334
|
extensionEnabled: document.getElementById("connectionButtonsArea").classList.contains("extensionEnabled"),
|
|
340
335
|
}));
|
|
341
336
|
expect(unloadedState).toEqual({
|
|
342
|
-
addButtonDisplay: "
|
|
337
|
+
addButtonDisplay: "flex",
|
|
343
338
|
microBitButtonDisplay: "none",
|
|
344
339
|
extensionsLibraryOpen: false,
|
|
345
340
|
microBitCardAction: "+",
|
|
@@ -249,6 +249,77 @@ describe("Marty script activation paths", () => {
|
|
|
249
249
|
}
|
|
250
250
|
});
|
|
251
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
|
+
|
|
252
323
|
it("activates the script after duplicating a page in Marty mode", async () => {
|
|
253
324
|
const { browser, page, errors } = await openPage("/editor.html?mode=edit");
|
|
254
325
|
try {
|
|
@@ -119,12 +119,7 @@ async function enableMartyMode(page) {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
async function enableMicroBitExtension(page) {
|
|
122
|
-
await page
|
|
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
|
-
});
|
|
122
|
+
await page.click("#addExtensionButton");
|
|
128
123
|
await page.waitForSelector("#extensionsLibrary.fade.in", { timeout: 30_000 });
|
|
129
124
|
await page.click("#microBitExtensionCard");
|
|
130
125
|
await page.waitForFunction(
|
|
@@ -110,12 +110,7 @@ async function enableMartyMode(page) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
async function enableMicroBitExtension(page) {
|
|
113
|
-
await page
|
|
114
|
-
if (!button.disabled || window.getComputedStyle(button).display !== "none") {
|
|
115
|
-
throw new Error("Expected the extensions entry point to remain hidden and disabled");
|
|
116
|
-
}
|
|
117
|
-
button.onclick();
|
|
118
|
-
});
|
|
113
|
+
await page.click("#addExtensionButton");
|
|
119
114
|
await page.waitForSelector("#extensionsLibrary.fade.in", { timeout: 30_000 });
|
|
120
115
|
await page.click("#microBitExtensionCard");
|
|
121
116
|
await page.waitForFunction(
|