@robotical/martyblocksjr 3.5.26 → 3.5.28
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.
|
@@ -1078,7 +1078,7 @@
|
|
|
1078
1078
|
"MARTY_JRBLOCKS3_STEP_3_TEXT": "Now add a green flag block to start off our code",
|
|
1079
1079
|
"MARTY_JRBLOCKS3_STEP_4_TEXT": "Now add an end block to finish our code",
|
|
1080
1080
|
"MARTY_JRBLOCKS3_STEP_5_TEXT": "Waiting is really important in programming <b/>and</b> in the real world. <br/><br/>In programming, waiting helps us do the right tasks at the right time, giving us control over when things happen.<br/><br/> In the real world, waiting can help keep us safe - like at traffic lights!",
|
|
1081
|
-
"MARTY_JRBLOCKS3_STEP_6_TEXT": "Let’s learn about the wait function and turn Marty into a traffic light. <br/><br/>Here is the basic sequence of a traffic light: red means wait, orange means wait then get ready, green means go!",
|
|
1081
|
+
"MARTY_JRBLOCKS3_STEP_6_TEXT": "Let’s learn about the wait function and turn Marty into a traffic light. <br/><br/>Here is the basic sequence of a traffic light: <span style='color:red'>red</span> means wait, <span style='color:orange'>orange</span> means wait then get ready, <span style='color:green'>green</span> means go!",
|
|
1082
1082
|
"MARTY_JRBLOCKS3_STEP_7_TEXT": "We need to turn Marty’s eyes red, orange and green. Select three of the LED eye colour blocks, and make one red, one orange and one green.",
|
|
1083
1083
|
"MARTY_JRBLOCKS3_STEP_8_TEXT": "Let’s start on red, turn to orange and then green. Based on our traffic light example, what should our code do at each colour?<br/><br/>Here's our code sequence:",
|
|
1084
1084
|
"MARTY_JRBLOCKS3_STEP_9_TEXT": "Place two <b/>wait</b> blocks as shown here, one after the red LED eye block, and one after the orange LED eye block. <br/><br/>Change the arguments so Marty waits for 5 seconds at red, and 2 seconds at orange.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robotical/martyblocksjr",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.28",
|
|
4
4
|
"description": "ScratchJr",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
"start": "npx serve --listen tcp://0.0.0.0:3011 ./editions/free/src",
|
|
16
16
|
"build:copy": "npm run build-dev && cp -f src/build/bundles/* editions/free/src",
|
|
17
17
|
"build-prod:copy": "npm run build && cp -f src/build/bundles/* editions/free/src",
|
|
18
|
-
"build:start": "npm run build:copy && npm start"
|
|
18
|
+
"build:start": "npm run build:copy && npm start",
|
|
19
|
+
"test": "vitest",
|
|
20
|
+
"test:e2e": "vitest run tests/e2e"
|
|
19
21
|
},
|
|
20
22
|
"author": "Massachusetts Institute of Technology",
|
|
21
23
|
"license": "BSD-3-Clause",
|
|
@@ -39,6 +41,7 @@
|
|
|
39
41
|
"eslint": "^1.10.3",
|
|
40
42
|
"expose-loader": "^3.0.0",
|
|
41
43
|
"strip-sourcemap-loader": "0.0.1",
|
|
44
|
+
"vitest": "^4.0.14",
|
|
42
45
|
"webpack": "^5.73.0",
|
|
43
46
|
"webpack-cli": "^4.10.0",
|
|
44
47
|
"webpack-notifier": "^1.6.0"
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import ApplicationManagerMock from '@/utils/ApplicationManagerMock';
|
|
3
|
+
|
|
4
|
+
const noop = () => {};
|
|
5
|
+
|
|
6
|
+
vi.mock('@/editor/ScratchJr', () => {
|
|
7
|
+
const runtime = {
|
|
8
|
+
threadsRunning: [],
|
|
9
|
+
restartThread: vi.fn((spr, block) => ({ spr, firstBlock: block, isRunning: true })),
|
|
10
|
+
isScriptRunningForThatBlock: vi.fn(() => false)
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
default: {
|
|
15
|
+
runtime,
|
|
16
|
+
stage: null,
|
|
17
|
+
startCurrentPageStrips: vi.fn(),
|
|
18
|
+
stopStrips: vi.fn(),
|
|
19
|
+
updateRunStopButtons: vi.fn()
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
vi.mock('@/utils/ScratchAudio', () => ({
|
|
25
|
+
default: {
|
|
26
|
+
sndFX: vi.fn()
|
|
27
|
+
}
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
vi.mock('@/editor/ui/UI', () => ({ default: {} }));
|
|
31
|
+
vi.mock('@/editor/ui/Grid', () => ({ default: { size: 24 } }));
|
|
32
|
+
vi.mock('@/utils/lib', () => ({
|
|
33
|
+
gn: () => null,
|
|
34
|
+
rgbToHex: value => value
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
describe('Marty movement blocks', () => {
|
|
38
|
+
let Prims;
|
|
39
|
+
let MartyBlocks;
|
|
40
|
+
let mockMarty;
|
|
41
|
+
let consoleSpy;
|
|
42
|
+
let appManager;
|
|
43
|
+
|
|
44
|
+
beforeEach(async () => {
|
|
45
|
+
vi.resetModules();
|
|
46
|
+
vi.clearAllMocks();
|
|
47
|
+
vi.useFakeTimers();
|
|
48
|
+
global.window = { navigator: { userAgent: '' } };
|
|
49
|
+
global.document = { documentElement: {} };
|
|
50
|
+
consoleSpy = vi.spyOn(console, 'log').mockImplementation(noop);
|
|
51
|
+
|
|
52
|
+
const [primsModule, martyBlocksModule] = await Promise.all([
|
|
53
|
+
import('@/editor/engine/Prims.js'),
|
|
54
|
+
import('@/marty/MartyBlocks.js')
|
|
55
|
+
]);
|
|
56
|
+
Prims = primsModule.default;
|
|
57
|
+
MartyBlocks = martyBlocksModule.default;
|
|
58
|
+
Prims.init();
|
|
59
|
+
|
|
60
|
+
appManager = new ApplicationManagerMock();
|
|
61
|
+
window.applicationManager = appManager;
|
|
62
|
+
mockMarty = createMartyFromApplicationManagerMock(appManager);
|
|
63
|
+
Prims.martyBlocks = new MartyBlocks(mockMarty);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
consoleSpy.mockRestore();
|
|
68
|
+
vi.runAllTimers();
|
|
69
|
+
vi.useRealTimers();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('keeps one-to-one steps for a simple walk cycle', () => {
|
|
73
|
+
const head = buildBlocks([
|
|
74
|
+
['martyStepForward', 1],
|
|
75
|
+
['martyStepLeft', 1],
|
|
76
|
+
['martyStepRight', 1],
|
|
77
|
+
['martyStepBackward', 1]
|
|
78
|
+
]);
|
|
79
|
+
const sprite = new TestSprite();
|
|
80
|
+
runScript(Prims, head, sprite);
|
|
81
|
+
|
|
82
|
+
expect(sentCommands(mockMarty)).toEqual([
|
|
83
|
+
'traj/step/1/?moveTime=1500&stepLength=25',
|
|
84
|
+
'traj/sidestep/1/?side=0&moveTime=1500&stepLength=35',
|
|
85
|
+
'traj/sidestep/1/?side=1&moveTime=1500&stepLength=35',
|
|
86
|
+
'traj/step/1/?moveTime=1500&stepLength=-25'
|
|
87
|
+
]);
|
|
88
|
+
expect(loggedActions(consoleSpy)).toEqual([
|
|
89
|
+
'stepForward',
|
|
90
|
+
'stepLeft',
|
|
91
|
+
'stepRight',
|
|
92
|
+
'stepBackward'
|
|
93
|
+
]);
|
|
94
|
+
expect(sprite.xcoor).toBeCloseTo(0, 5);
|
|
95
|
+
expect(sprite.ycoor).toBeCloseTo(0, 5);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('turn blocks respect their arguments and complete the sequence', () => {
|
|
99
|
+
const head = buildBlocks([
|
|
100
|
+
['martyTurnLeft', 1],
|
|
101
|
+
['martyTurnRight', 1]
|
|
102
|
+
]);
|
|
103
|
+
const sprite = new TestSprite();
|
|
104
|
+
runScript(Prims, head, sprite);
|
|
105
|
+
|
|
106
|
+
expect(sentCommands(mockMarty)).toEqual([
|
|
107
|
+
'traj/step/1/?moveTime=1500&turn=10&stepLength=1',
|
|
108
|
+
'traj/step/1/?moveTime=1500&turn=-10&stepLength=1'
|
|
109
|
+
]);
|
|
110
|
+
expect(loggedActions(consoleSpy)).toEqual([
|
|
111
|
+
'turnLeft',
|
|
112
|
+
'turnRight'
|
|
113
|
+
]);
|
|
114
|
+
expect(sprite.angle).toBeCloseTo(0, 5);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('does not resend commands when previous timeouts fire mid-sequence', () => {
|
|
118
|
+
const head = buildBlocks([
|
|
119
|
+
['martyStepForward', 1],
|
|
120
|
+
['martyStepLeft', 1],
|
|
121
|
+
['martyStepRight', 1],
|
|
122
|
+
['martyStepBackward', 1]
|
|
123
|
+
]);
|
|
124
|
+
const sprite = new TestSprite();
|
|
125
|
+
runScript(Prims, head, sprite, { advanceMsPerTick: 200 });
|
|
126
|
+
|
|
127
|
+
expect(sentCommands(mockMarty)).toEqual([
|
|
128
|
+
'traj/step/1/?moveTime=1500&stepLength=25',
|
|
129
|
+
'traj/sidestep/1/?side=0&moveTime=1500&stepLength=35',
|
|
130
|
+
'traj/sidestep/1/?side=1&moveTime=1500&stepLength=35',
|
|
131
|
+
'traj/step/1/?moveTime=1500&stepLength=-25'
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
function runScript(Prims, head, sprite, options = {}) {
|
|
137
|
+
const { advanceMsPerTick = 0 } = options;
|
|
138
|
+
const strip = {
|
|
139
|
+
spr: sprite,
|
|
140
|
+
thisblock: head,
|
|
141
|
+
firstBlock: head,
|
|
142
|
+
oldblock: null,
|
|
143
|
+
stack: [],
|
|
144
|
+
firstTime: true,
|
|
145
|
+
count: -1,
|
|
146
|
+
waitTimer: 0,
|
|
147
|
+
distance: -1,
|
|
148
|
+
vector: { x: 0, y: 0 },
|
|
149
|
+
called: [],
|
|
150
|
+
isRunning: true,
|
|
151
|
+
cmdSent: false
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
let ticks = 0;
|
|
155
|
+
const maxTicks = 10000;
|
|
156
|
+
|
|
157
|
+
while (strip.thisblock && ticks < maxTicks) {
|
|
158
|
+
if (strip.waitTimer > 0) {
|
|
159
|
+
strip.waitTimer -= 1;
|
|
160
|
+
ticks += 1;
|
|
161
|
+
if (advanceMsPerTick) {
|
|
162
|
+
vi.advanceTimersByTime(advanceMsPerTick);
|
|
163
|
+
}
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const prim = Prims.table[strip.thisblock.blocktype];
|
|
167
|
+
if (!prim) {
|
|
168
|
+
throw new Error(`No prim registered for ${strip.thisblock.blocktype}`);
|
|
169
|
+
}
|
|
170
|
+
prim(strip);
|
|
171
|
+
ticks += 1;
|
|
172
|
+
if (advanceMsPerTick) {
|
|
173
|
+
vi.advanceTimersByTime(advanceMsPerTick);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (ticks >= maxTicks) {
|
|
178
|
+
throw new Error('Script execution did not finish');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return strip;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function buildBlocks(sequence) {
|
|
185
|
+
const blocks = sequence.map(([type, arg]) => new TestBlock(type, arg));
|
|
186
|
+
for (let i = 0; i < blocks.length - 1; i += 1) {
|
|
187
|
+
blocks[i].next = blocks[i + 1];
|
|
188
|
+
blocks[i + 1].previousBlock = blocks[i];
|
|
189
|
+
}
|
|
190
|
+
return blocks[0] || null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
class TestBlock {
|
|
194
|
+
constructor(type, argValue) {
|
|
195
|
+
this.blocktype = type;
|
|
196
|
+
this.argValue = argValue;
|
|
197
|
+
this.next = null;
|
|
198
|
+
this.previousBlock = null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
getArgValue() {
|
|
202
|
+
return this.argValue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
findFirst() {
|
|
206
|
+
let node = this;
|
|
207
|
+
while (node.previousBlock) {
|
|
208
|
+
node = node.previousBlock;
|
|
209
|
+
}
|
|
210
|
+
return node;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
closeBalloon() {
|
|
214
|
+
// no-op stub for compatibility with Thread.stop
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
class TestSprite {
|
|
219
|
+
constructor() {
|
|
220
|
+
this.xcoor = 0;
|
|
221
|
+
this.ycoor = 0;
|
|
222
|
+
this.angle = 0;
|
|
223
|
+
this.speed = 1;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
setPos(x, y) {
|
|
227
|
+
this.xcoor = x;
|
|
228
|
+
this.ycoor = y;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
setHeading(angle) {
|
|
232
|
+
this.angle = angle;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
closeBalloon() {
|
|
236
|
+
// no-op stub for compatibility with Thread.stop
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function createMartyFromApplicationManagerMock(appManager) {
|
|
241
|
+
const raft = appManager.getTheCurrentlySelectedDeviceOrFirstOfItsKind('Marty');
|
|
242
|
+
raft.sendRestMessage = vi.fn();
|
|
243
|
+
raft.getRaftVersion = () => '2.0.0';
|
|
244
|
+
raft.publishedDataAnalyser = {
|
|
245
|
+
eventsMap: {
|
|
246
|
+
colourSensed: {
|
|
247
|
+
red: 'red',
|
|
248
|
+
green: 'green',
|
|
249
|
+
blue: 'blue',
|
|
250
|
+
purple: 'purple',
|
|
251
|
+
yellow: 'yellow',
|
|
252
|
+
air: 'air',
|
|
253
|
+
unclear: 'unclear'
|
|
254
|
+
},
|
|
255
|
+
objectSense: {
|
|
256
|
+
near: 'near',
|
|
257
|
+
none: 'none'
|
|
258
|
+
},
|
|
259
|
+
lightSense: {
|
|
260
|
+
none: 'light-none',
|
|
261
|
+
mid: 'light-mid',
|
|
262
|
+
high: 'light-high'
|
|
263
|
+
},
|
|
264
|
+
noiseSense: {
|
|
265
|
+
high: 'noise-high',
|
|
266
|
+
low: 'noise-low'
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
on: vi.fn()
|
|
270
|
+
};
|
|
271
|
+
return raft;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function sentCommands(fakeMarty) {
|
|
275
|
+
return fakeMarty.sendRestMessage.mock.calls.map(args => args[0]);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function loggedActions(spy) {
|
|
279
|
+
return spy.mock.calls
|
|
280
|
+
.filter(call => typeof call[0] === 'string' && call[0].startsWith('[MartyBlocks]'))
|
|
281
|
+
.map(call => call[1]?.action);
|
|
282
|
+
}
|
package/vitest.config.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { defineConfig } = require('vitest/config');
|
|
3
|
+
|
|
4
|
+
module.exports = defineConfig({
|
|
5
|
+
resolve: {
|
|
6
|
+
alias: {
|
|
7
|
+
'@': path.resolve(__dirname, './src')
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
test: {
|
|
11
|
+
environment: 'node',
|
|
12
|
+
include: ['tests/**/*.test.js']
|
|
13
|
+
}
|
|
14
|
+
});
|