@robotical/martyblocksjr 4.0.5 → 4.0.8

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.
@@ -247,6 +247,59 @@
247
247
  margin: 0px ${css_vh(2.34)} 0px;
248
248
  }
249
249
 
250
+ #library.marty-mode-library .addsprite {
251
+ display: none;
252
+ }
253
+
254
+ .marty-mode-card {
255
+ background: url('../assets/ui/viewOn.png') no-repeat left top;
256
+ background-size: 100%;
257
+ position: relative;
258
+ display: inline-block;
259
+ width: ${css_vh(29.95)};
260
+ height: ${css_vh(10.16)};
261
+ margin: 0px;
262
+ margin-bottom: ${css_vh(-0.39)};
263
+ padding: 0px;
264
+ }
265
+
266
+ .marty-mode-card-icon {
267
+ position: absolute;
268
+ top: ${css_vh(0.65)};
269
+ left: ${css_vh(1.04)};
270
+ width: ${css_vh(8.33)};
271
+ height: ${css_vh(8.33)};
272
+ margin: ${css_vh(0.00)};
273
+ padding: ${css_vh(0.00)};
274
+ overflow: hidden;
275
+ }
276
+
277
+ .marty-mode-card-canvas,
278
+ .marty-mode-card-image {
279
+ width: 100%;
280
+ height: 100%;
281
+ display: block;
282
+ }
283
+
284
+ .marty-mode-card-label {
285
+ position: absolute;
286
+ text-align: center;
287
+ top: ${css_vh(3.91)};
288
+ left: ${css_vh(8.46)};
289
+ margin: 0px;
290
+ padding: 0px;
291
+ line-height: ${css_vh(2.60)};
292
+ white-space: nowrap;
293
+ text-overflow: ellipsis;
294
+ overflow: hidden;
295
+ font-family: 'Lato Regular';
296
+ font-weight: bold;
297
+ font-size: ${css_vh(1.69)};
298
+ width: ${css_vh(13.54)};
299
+ cursor: default;
300
+ color: #F9A737;
301
+ }
302
+
250
303
 
251
304
  /* sprites thumbnails */
252
305
  .spritethumb {
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@robotical/martyblocksjr",
3
- "version": "4.0.5",
3
+ "version": "4.0.8",
4
4
  "description": "ScratchJr",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/robotical/scratchjr"
8
+ },
5
9
  "publishConfig": {
6
10
  "registry": "https://registry.npmjs.org/",
7
11
  "access": "public"
@@ -0,0 +1,259 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ vi.mock('@/editor/ScratchJr', () => ({
4
+ default: {
5
+ isMartyModeEnabled: false,
6
+ stage: {
7
+ currentPage: {}
8
+ }
9
+ }
10
+ }));
11
+ vi.mock('@/editor/blocks/BlockSpecs', () => ({ default: {} }));
12
+ vi.mock('@/editor/ui/Alert', () => ({ default: {} }));
13
+ vi.mock('@/editor/ui/Project', () => ({ default: {} }));
14
+ vi.mock('@/editor/ProjectCloud', () => ({ default: {} }));
15
+ vi.mock('@/editor/ui/Thumbs', () => ({ default: {} }));
16
+ vi.mock('@/editor/ui/Palette', () => ({ default: {} }));
17
+ vi.mock('@/editor/ui/Grid', () => ({ default: {} }));
18
+ vi.mock('@/editor/engine/Stage', () => ({ default: {} }));
19
+ vi.mock('@/editor/ui/ScriptsPane', () => ({ default: {} }));
20
+ vi.mock('@/editor/ui/Undo', () => ({ default: {} }));
21
+ vi.mock('@/editor/ui/Library', () => ({ default: {} }));
22
+ vi.mock('@/tablet/OS', () => ({ default: {} }));
23
+ vi.mock('@/tablet/IO', () => ({ default: {} }));
24
+ vi.mock('@/tablet/MediaLib', () => ({ default: {} }));
25
+ vi.mock('@/painteditor/Paint', () => ({ default: {} }));
26
+ vi.mock('@/utils/Events', () => ({ default: {} }));
27
+ vi.mock('@/utils/Localization', () => ({
28
+ default: {
29
+ localize: key => key
30
+ }
31
+ }));
32
+ vi.mock('@/utils/ScratchAudio', () => ({ default: {} }));
33
+ vi.mock('@/utils/cloudLocalStore', () => ({
34
+ addStoredCloudId: vi.fn(),
35
+ getStoredCloudIds: vi.fn(() => []),
36
+ removeStoredCloudId: vi.fn(),
37
+ touchStoredCloudId: vi.fn()
38
+ }));
39
+ vi.mock('@/utils/goToLink', () => ({ default: vi.fn() }));
40
+ vi.mock('@/utils/lib', () => ({
41
+ frame: null,
42
+ gn: vi.fn(),
43
+ CSSTransition: vi.fn(),
44
+ localx: vi.fn(),
45
+ newHTML: vi.fn(),
46
+ newButton: vi.fn(),
47
+ scaleMultiplier: 1,
48
+ fullscreenScaleMultiplier: 1,
49
+ getIdFor: vi.fn(),
50
+ isTablet: false,
51
+ newDiv: vi.fn(),
52
+ newTextInput: vi.fn(),
53
+ isAndroid: false,
54
+ getDocumentWidth: vi.fn(() => 1024),
55
+ getDocumentHeight: vi.fn(() => 768),
56
+ setProps: vi.fn(),
57
+ globalx: vi.fn()
58
+ }));
59
+ vi.mock('@/utils/accessibility', () => ({
60
+ closeDialog: vi.fn(),
61
+ openDialog: vi.fn(),
62
+ registerDialog: vi.fn(),
63
+ setMainLandmark: vi.fn(),
64
+ setPressedState: vi.fn(),
65
+ setSelectedState: vi.fn()
66
+ }));
67
+ vi.mock('@/html-svgs/cog', () => ({ cogSvg: '<svg></svg>' }));
68
+ vi.mock('@/html-svgs/sprite', () => ({ spriteSvg: '<svg></svg>' }));
69
+ vi.mock('@/html-svgs/marty', () => ({ martySvg: '<svg></svg>' }));
70
+ vi.mock('@/html-svgs/sprite-deselected', () => ({ spriteDeselectedSvg: '<svg></svg>' }));
71
+ vi.mock('@/html-svgs/marty-deselected', () => ({ martyDeselectedSvg: '<svg></svg>' }));
72
+ vi.mock('@/html-svgs/marty_toggle_on', () => ({ martyToggleOn: '<svg></svg>' }));
73
+ vi.mock('@/html-svgs/sprite_toggle_on', () => ({ spriteToggleOn: '<svg></svg>' }));
74
+ vi.mock('@/html-svgs/battery-svg', () => ({ batterySvg: () => '<svg id="battery"></svg>' }));
75
+ vi.mock('@/html-svgs/signal-svg', () => ({ signalSvg: () => '<svg id="signal"></svg>' }));
76
+ vi.mock('@/utils/raft-subscription-helpers', () => ({
77
+ createRaftConnectionIssueDetectedHelper: () => ({
78
+ subscribe: vi.fn(),
79
+ unsubscribe: vi.fn()
80
+ }),
81
+ createRaftConnectionIssueResolvedHelper: () => ({
82
+ subscribe: vi.fn(),
83
+ unsubscribe: vi.fn()
84
+ }),
85
+ raftVerifiedSubscriptionHelper: () => ({
86
+ subscribe: vi.fn(),
87
+ unsubscribe: vi.fn()
88
+ }),
89
+ raftDisconnectedSubscriptionHelper: raft => {
90
+ const helper = {
91
+ subscribe: vi.fn(callback => {
92
+ raft.__disconnectCallback = callback;
93
+ }),
94
+ unsubscribe: vi.fn(() => {
95
+ raft.__disconnectedUnsubscribed = true;
96
+ })
97
+ };
98
+ return helper;
99
+ }
100
+ }));
101
+ vi.mock('@/utils/truncate-string', () => ({ truncateString: value => value }));
102
+ vi.mock('@/editor/ui/Trace', () => ({
103
+ default: {
104
+ clear: vi.fn()
105
+ }
106
+ }));
107
+
108
+ describe('Marty connection UI', () => {
109
+ let UI;
110
+ let ScratchJr;
111
+ let warnSpy;
112
+
113
+ beforeEach(async () => {
114
+ vi.useFakeTimers();
115
+ vi.clearAllMocks();
116
+ global.window = {
117
+ applicationManager: {
118
+ disconnectGeneric: vi.fn(raft => {
119
+ raft.__disconnectCallback();
120
+ })
121
+ },
122
+ martyManager: {
123
+ addMarty: vi.fn(),
124
+ wireMartyWithBlocks: vi.fn(() => {
125
+ throw new Error('metadata not ready');
126
+ }),
127
+ removeMarty: vi.fn(),
128
+ setMartySensorAvailability: vi.fn()
129
+ }
130
+ };
131
+ warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
132
+ const [uiModule, scratchJrModule] = await Promise.all([
133
+ import('@/editor/ui/UI.js'),
134
+ import('@/editor/ScratchJr')
135
+ ]);
136
+ UI = uiModule.default;
137
+ ScratchJr = scratchJrModule.default;
138
+ ScratchJr.isMartyModeEnabled = false;
139
+ ScratchJr.stage = {
140
+ currentPage: {}
141
+ };
142
+ });
143
+
144
+ afterEach(() => {
145
+ warnSpy.mockRestore();
146
+ vi.runOnlyPendingTimers();
147
+ vi.useRealTimers();
148
+ delete global.window;
149
+ });
150
+
151
+ it('installs the Marty disconnect action before sensor and block metadata are ready', () => {
152
+ const button = createConnectionButton();
153
+ const oldOnClick = vi.fn();
154
+ button.onclick = oldOnClick;
155
+ const raft = createMartyRaft();
156
+
157
+ UI.setupMartyConnectionButton(button, raft);
158
+
159
+ button.onclick();
160
+ vi.advanceTimersByTime(1000);
161
+
162
+ expect(window.applicationManager.disconnectGeneric).toHaveBeenCalledWith(raft);
163
+ expect(window.martyManager.removeMarty).toHaveBeenCalledWith(raft);
164
+ expect(button.onclick).toBe(oldOnClick);
165
+ expect(button.classList.contains('connectButtonConnected')).toBe(false);
166
+ expect(button.querySelector('.iconButtonContainer').classList.contains('notConnectedButtonContainer')).toBe(true);
167
+ expect(raft.__disconnectedUnsubscribed).toBe(true);
168
+ });
169
+
170
+ it('switches from Sprite mode to Marty mode after Marty connects', () => {
171
+ const button = createConnectionButton();
172
+ button.onclick = vi.fn();
173
+ const raft = createMartyRaft();
174
+
175
+ UI.setupMartyConnectionButton(button, raft);
176
+
177
+ expect(ScratchJr.isMartyModeEnabled).toBe(true);
178
+ });
179
+
180
+ it('still disconnects Marty after switching back to Sprite mode', () => {
181
+ const button = createConnectionButton();
182
+ const oldOnClick = vi.fn();
183
+ button.onclick = oldOnClick;
184
+ const raft = createMartyRaft();
185
+
186
+ UI.setupMartyConnectionButton(button, raft);
187
+ UI.setMartyModeEnabled(false);
188
+
189
+ expect(ScratchJr.isMartyModeEnabled).toBe(false);
190
+
191
+ button.onclick();
192
+ vi.advanceTimersByTime(1000);
193
+
194
+ expect(window.applicationManager.disconnectGeneric).toHaveBeenCalledWith(raft);
195
+ expect(window.martyManager.removeMarty).toHaveBeenCalledWith(raft);
196
+ expect(button.onclick).toBe(oldOnClick);
197
+ expect(button.classList.contains('connectButtonConnected')).toBe(false);
198
+ expect(button.querySelector('.iconButtonContainer').classList.contains('notConnectedButtonContainer')).toBe(true);
199
+ });
200
+ });
201
+
202
+ function createMartyRaft() {
203
+ return {
204
+ id: 'marty-1',
205
+ getFriendlyName: () => 'Marty One',
206
+ getBatteryStrength: () => 70,
207
+ getRSSI: () => -45,
208
+ publishedDataAnalyser: {
209
+ connectedSensors: undefined
210
+ }
211
+ };
212
+ }
213
+
214
+ function createConnectionButton() {
215
+ const button = new FakeElement();
216
+ button.children['.iconButtonContainer'] = new FakeElement(['iconButtonContainer', 'notConnectedButtonContainer']);
217
+ button.children['.batteryIndicatorContainer'] = new FakeElement(['batteryIndicatorContainer']);
218
+ button.children['.signalIndicatorContainer'] = new FakeElement(['signalIndicatorContainer']);
219
+ button.children['.raftNameConnectButton'] = new FakeElement(['raftNameConnectButton']);
220
+ return button;
221
+ }
222
+
223
+ class FakeElement {
224
+ constructor(classes = []) {
225
+ this.classList = new FakeClassList(classes);
226
+ this.children = {};
227
+ this.style = {};
228
+ this.attributes = {};
229
+ this.textContent = '';
230
+ this.innerHTML = '';
231
+ this.onclick = null;
232
+ }
233
+
234
+ querySelector(selector) {
235
+ return this.children[selector] || null;
236
+ }
237
+
238
+ setAttribute(name, value) {
239
+ this.attributes[name] = value;
240
+ }
241
+ }
242
+
243
+ class FakeClassList {
244
+ constructor(classes = []) {
245
+ this.classes = new Set(classes);
246
+ }
247
+
248
+ add(...classes) {
249
+ classes.forEach(className => this.classes.add(className));
250
+ }
251
+
252
+ remove(...classes) {
253
+ classes.forEach(className => this.classes.delete(className));
254
+ }
255
+
256
+ contains(className) {
257
+ return this.classes.has(className);
258
+ }
259
+ }
package/android/README.md DELETED
@@ -1 +0,0 @@
1
- ScratchJr - Android Studio project containing Android build of Scratch Jr.
package/bin/readme.md DELETED
@@ -1,3 +0,0 @@
1
- Uses python2 and needs librsvg2-bin installed in the system (not python)
2
- Sometimes the version of librsvg2-bin changes (after updates etc) so make sure you update the path in convert-svg-to-png.py
3
- ```