@scratch/scratch-media-lib-scripts 13.2.0-build-media-scripts
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/LICENSE +661 -0
- package/package.json +50 -0
- package/schemas/media-collection-schema.json +214 -0
- package/scripts/build_media_libraries.js +307 -0
- package/scripts/tests/build_media_libraries.test.js +929 -0
- package/scripts/tests/fixtures/Abby-a.svg +30 -0
- package/scripts/tests/fixtures/Abby-b.svg +28 -0
- package/scripts/tests/fixtures/Arctic.png +0 -0
- package/scripts/tests/fixtures/Pop.wav +0 -0
- package/scripts/tests/helpers/input-helpers.js +142 -0
|
@@ -0,0 +1,929 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const { buildLibraries } = require('../build_media_libraries');
|
|
5
|
+
const fixturesDir = path.resolve(__dirname, 'fixtures'); // Directory for test fixtures
|
|
6
|
+
|
|
7
|
+
const { generateAsset, generateSprite, createNewBackdrop, createNewSound, createNewCostume } = require('./helpers/input-helpers');
|
|
8
|
+
|
|
9
|
+
const tempInputDir = path.join(os.tmpdir(), 'media-lib-test-input');
|
|
10
|
+
|
|
11
|
+
describe('buildLibraries', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Clean and recreate temp input directory before each test
|
|
14
|
+
if (fs.existsSync(tempInputDir)) {
|
|
15
|
+
fs.rmSync(tempInputDir, { recursive: true, force: true });
|
|
16
|
+
}
|
|
17
|
+
fs.mkdirSync(tempInputDir, { recursive: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterAll(() => {
|
|
21
|
+
// Cleanup temp input and output directories after all tests
|
|
22
|
+
if (fs.existsSync(tempInputDir)) {
|
|
23
|
+
fs.rmSync(tempInputDir, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('Should return empty collections for empty input directory', () => {
|
|
28
|
+
const result = buildLibraries({ inputDir: tempInputDir });
|
|
29
|
+
expect(result).toEqual({
|
|
30
|
+
sprites: [],
|
|
31
|
+
backdrops: [],
|
|
32
|
+
costumes: [],
|
|
33
|
+
sounds: []
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('Should process a backdrop correctly', () => {
|
|
38
|
+
const {
|
|
39
|
+
backdropName,
|
|
40
|
+
backdrop,
|
|
41
|
+
backdropMd5ext,
|
|
42
|
+
backdropTags,
|
|
43
|
+
backdropExt
|
|
44
|
+
} = createNewBackdrop();
|
|
45
|
+
|
|
46
|
+
generateAsset(
|
|
47
|
+
tempInputDir,
|
|
48
|
+
'backdrops',
|
|
49
|
+
backdrop, {
|
|
50
|
+
name: backdropName,
|
|
51
|
+
ext: backdropExt,
|
|
52
|
+
tags: backdropTags
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const result = buildLibraries({ inputDir: tempInputDir });
|
|
57
|
+
|
|
58
|
+
expect(result.backdrops).toHaveLength(1);
|
|
59
|
+
expect(result.backdrops[0].name).toBe(backdropName);
|
|
60
|
+
expect(result.backdrops[0].md5ext).toBe(backdropMd5ext);
|
|
61
|
+
expect(result.backdrops[0].tags).toEqual(backdropTags);
|
|
62
|
+
expect(result.backdrops[0].dataFormat).toBe(backdropExt);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('Should process a sound correctly', () => {
|
|
66
|
+
const {
|
|
67
|
+
soundName,
|
|
68
|
+
sound,
|
|
69
|
+
soundMd5ext,
|
|
70
|
+
soundTags,
|
|
71
|
+
soundExt,
|
|
72
|
+
soundRate,
|
|
73
|
+
soundSampleCount
|
|
74
|
+
} = createNewSound();
|
|
75
|
+
|
|
76
|
+
generateAsset(
|
|
77
|
+
tempInputDir,
|
|
78
|
+
'sounds',
|
|
79
|
+
sound, {
|
|
80
|
+
name: soundName,
|
|
81
|
+
ext: soundExt,
|
|
82
|
+
tags: soundTags,
|
|
83
|
+
rate: soundRate,
|
|
84
|
+
sampleCount: soundSampleCount
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const result = buildLibraries({ inputDir: tempInputDir });
|
|
89
|
+
|
|
90
|
+
expect(result.sounds).toHaveLength(1);
|
|
91
|
+
expect(result.sounds[0].name).toBe(soundName);
|
|
92
|
+
expect(result.sounds[0].md5ext).toBe(soundMd5ext);
|
|
93
|
+
expect(result.sounds[0].tags).toEqual(soundTags);
|
|
94
|
+
expect(result.sounds[0].rate).toBe(soundRate);
|
|
95
|
+
expect(result.sounds[0].sampleCount).toBe(soundSampleCount);
|
|
96
|
+
expect(result.sounds[0].dataFormat).toBe(soundExt);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('Should process a costume correctly', () => {
|
|
100
|
+
const {
|
|
101
|
+
costumeName,
|
|
102
|
+
costume,
|
|
103
|
+
costumeMd5ext,
|
|
104
|
+
costumeTags,
|
|
105
|
+
costumeExt
|
|
106
|
+
} = createNewCostume();
|
|
107
|
+
|
|
108
|
+
generateAsset(
|
|
109
|
+
tempInputDir,
|
|
110
|
+
'costumes',
|
|
111
|
+
costume, {
|
|
112
|
+
name: costumeName,
|
|
113
|
+
ext: costumeExt,
|
|
114
|
+
tags: costumeTags
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const result = buildLibraries({ inputDir: tempInputDir });
|
|
119
|
+
|
|
120
|
+
expect(result.costumes).toHaveLength(1);
|
|
121
|
+
expect(result.costumes[0].name).toBe(costumeName);
|
|
122
|
+
expect(result.costumes[0].md5ext).toBe(costumeMd5ext);
|
|
123
|
+
expect(result.costumes[0].tags).toEqual(costumeTags);
|
|
124
|
+
expect(result.costumes[0].dataFormat).toBe(costumeExt);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('Should process a sprite and fill costumes correctly', () => {
|
|
128
|
+
const spriteName = 'Abby';
|
|
129
|
+
const {
|
|
130
|
+
costumeName,
|
|
131
|
+
costume,
|
|
132
|
+
costumeMd5ext,
|
|
133
|
+
costumeExt,
|
|
134
|
+
costumeBitmapResolution,
|
|
135
|
+
costumeRotationCenterX,
|
|
136
|
+
costumeRotationCenterY
|
|
137
|
+
} = createNewCostume();
|
|
138
|
+
|
|
139
|
+
generateSprite(
|
|
140
|
+
tempInputDir,
|
|
141
|
+
{
|
|
142
|
+
[costumeName]: {
|
|
143
|
+
ext: costumeExt,
|
|
144
|
+
data: costume,
|
|
145
|
+
jsonOptions: {
|
|
146
|
+
bitmapResolution: costumeBitmapResolution,
|
|
147
|
+
rotationCenterX: costumeRotationCenterX,
|
|
148
|
+
rotationCenterY: costumeRotationCenterY
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{},
|
|
153
|
+
{
|
|
154
|
+
name: spriteName
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const result = buildLibraries({ inputDir: tempInputDir });
|
|
159
|
+
|
|
160
|
+
expect(result.sprites).toHaveLength(1);
|
|
161
|
+
expect(result.sprites[0].name).toBe(spriteName);
|
|
162
|
+
expect(result.sprites[0].costumes).toHaveLength(1);
|
|
163
|
+
expect(result.sprites[0].costumes[0].name).toBe(costumeName);
|
|
164
|
+
expect(result.sprites[0].costumes[0].md5ext).toBe(costumeMd5ext);
|
|
165
|
+
expect(result.sprites[0].costumes[0].bitmapResolution).toBe(costumeBitmapResolution);
|
|
166
|
+
expect(result.sprites[0].costumes[0].rotationCenterX).toBe(costumeRotationCenterX);
|
|
167
|
+
expect(result.sprites[0].costumes[0].rotationCenterY).toBe(costumeRotationCenterY);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('Should process a sprite and fill sounds correctly', () => {
|
|
171
|
+
const spriteName = 'Abby';
|
|
172
|
+
|
|
173
|
+
const {
|
|
174
|
+
soundName,
|
|
175
|
+
sound,
|
|
176
|
+
soundMd5ext,
|
|
177
|
+
soundExt,
|
|
178
|
+
soundRate,
|
|
179
|
+
soundSampleCount
|
|
180
|
+
} = createNewSound();
|
|
181
|
+
|
|
182
|
+
generateSprite(
|
|
183
|
+
tempInputDir,
|
|
184
|
+
{},
|
|
185
|
+
{
|
|
186
|
+
[soundName]: {
|
|
187
|
+
ext: soundExt,
|
|
188
|
+
data: sound,
|
|
189
|
+
jsonOptions: {
|
|
190
|
+
rate: soundRate,
|
|
191
|
+
sampleCount: soundSampleCount
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: spriteName
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const result = buildLibraries({ inputDir: tempInputDir });
|
|
201
|
+
|
|
202
|
+
expect(result.sprites).toHaveLength(1);
|
|
203
|
+
expect(result.sprites[0].name).toBe(spriteName);
|
|
204
|
+
|
|
205
|
+
expect(result.sprites[0].sounds).toHaveLength(1);
|
|
206
|
+
expect(result.sprites[0].sounds[0].name).toBe(soundName);
|
|
207
|
+
expect(result.sprites[0].sounds[0].md5ext).toBe(soundMd5ext);
|
|
208
|
+
expect(result.sprites[0].sounds[0].rate).toBe(soundRate);
|
|
209
|
+
expect(result.sprites[0].sounds[0].sampleCount).toBe(soundSampleCount);
|
|
210
|
+
expect(result.sprites[0].sounds[0].dataFormat).toBe(soundExt);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('Should preserve additional properties on sprites', () => {
|
|
214
|
+
const spriteName = 'Abby';
|
|
215
|
+
const variables = {
|
|
216
|
+
myVariable: {
|
|
217
|
+
value: 10
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const blocks = {
|
|
222
|
+
moveSteps: {
|
|
223
|
+
inputs: {
|
|
224
|
+
STEPS: {
|
|
225
|
+
name: 'steps',
|
|
226
|
+
value: 10
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
generateSprite(
|
|
233
|
+
tempInputDir,
|
|
234
|
+
{},
|
|
235
|
+
{},
|
|
236
|
+
{
|
|
237
|
+
name: spriteName,
|
|
238
|
+
isStage: true,
|
|
239
|
+
tags: ['people', 'person', 'drawing'],
|
|
240
|
+
variables,
|
|
241
|
+
blocks
|
|
242
|
+
},
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const result = buildLibraries({ inputDir: tempInputDir });
|
|
246
|
+
|
|
247
|
+
expect(result.sprites).toHaveLength(1);
|
|
248
|
+
expect(result.sprites[0].name).toBe(spriteName);
|
|
249
|
+
expect(result.sprites[0].isStage).toBe(true);
|
|
250
|
+
expect(result.sprites[0].tags).toEqual(['people', 'person', 'drawing']);
|
|
251
|
+
expect(result.sprites[0].variables).toEqual(variables);
|
|
252
|
+
expect(result.sprites[0].blocks).toEqual(blocks);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('Should inherit costume tags from sprites', () => {
|
|
256
|
+
const spriteName = 'Abby';
|
|
257
|
+
const spriteTags = [
|
|
258
|
+
'people',
|
|
259
|
+
'person',
|
|
260
|
+
'drawing'
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
const {
|
|
264
|
+
costumeName,
|
|
265
|
+
costume,
|
|
266
|
+
costumeExt,
|
|
267
|
+
costumeBitmapResolution,
|
|
268
|
+
costumeRotationCenterX,
|
|
269
|
+
costumeRotationCenterY
|
|
270
|
+
} = createNewCostume();
|
|
271
|
+
|
|
272
|
+
generateSprite(
|
|
273
|
+
tempInputDir,
|
|
274
|
+
{
|
|
275
|
+
[costumeName]: {
|
|
276
|
+
ext: costumeExt,
|
|
277
|
+
data: costume,
|
|
278
|
+
jsonOptions: {
|
|
279
|
+
bitmapResolution: costumeBitmapResolution,
|
|
280
|
+
rotationCenterX: costumeRotationCenterX,
|
|
281
|
+
rotationCenterY: costumeRotationCenterY
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
{},
|
|
286
|
+
{
|
|
287
|
+
name: spriteName,
|
|
288
|
+
tags: spriteTags
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const result = buildLibraries({ inputDir: tempInputDir });
|
|
293
|
+
|
|
294
|
+
expect(result.sprites).toHaveLength(1);
|
|
295
|
+
expect(result.sprites[0].name).toBe(spriteName);
|
|
296
|
+
expect(result.sprites[0].tags).toEqual(spriteTags);
|
|
297
|
+
expect(result.sprites[0].costumes).toHaveLength(1);
|
|
298
|
+
expect(result.sprites[0].costumes[0].name).toBe(costumeName);
|
|
299
|
+
expect(result.sprites[0].costumes[0].tags).toEqual(spriteTags); // Inherited from sprite
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test('Should NOT inherit costume tags from sprites if costume already has tags', () => {
|
|
303
|
+
const spriteName = 'Abby';
|
|
304
|
+
const spriteTags = [
|
|
305
|
+
'people',
|
|
306
|
+
'person',
|
|
307
|
+
'drawing'
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
const costumeTags = [
|
|
311
|
+
'cat',
|
|
312
|
+
'pet'
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
const {
|
|
316
|
+
costumeName,
|
|
317
|
+
costume,
|
|
318
|
+
costumeExt,
|
|
319
|
+
costumeBitmapResolution,
|
|
320
|
+
costumeRotationCenterX,
|
|
321
|
+
costumeRotationCenterY
|
|
322
|
+
} = createNewCostume({ tags: costumeTags });
|
|
323
|
+
|
|
324
|
+
generateSprite(
|
|
325
|
+
tempInputDir,
|
|
326
|
+
{
|
|
327
|
+
[costumeName]: {
|
|
328
|
+
ext: costumeExt,
|
|
329
|
+
data: costume,
|
|
330
|
+
jsonOptions: {
|
|
331
|
+
bitmapResolution: costumeBitmapResolution,
|
|
332
|
+
rotationCenterX: costumeRotationCenterX,
|
|
333
|
+
rotationCenterY: costumeRotationCenterY,
|
|
334
|
+
tags: costumeTags
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
{},
|
|
339
|
+
{
|
|
340
|
+
name: spriteName,
|
|
341
|
+
tags: spriteTags
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const result = buildLibraries({ inputDir: tempInputDir });
|
|
346
|
+
expect(result.sprites).toHaveLength(1);
|
|
347
|
+
expect(result.sprites[0].name).toBe(spriteName);
|
|
348
|
+
expect(result.sprites[0].tags).toEqual(spriteTags);
|
|
349
|
+
expect(result.sprites[0].costumes).toHaveLength(1);
|
|
350
|
+
expect(result.sprites[0].costumes[0].name).toBe(costumeName);
|
|
351
|
+
expect(result.sprites[0].costumes[0].tags).toEqual(costumeTags); // Should NOT inherit tags since costume already has its own tags
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test('Should NOT inherit sound tags from sprites', () => {
|
|
355
|
+
const spriteName = 'Abby';
|
|
356
|
+
const spriteTags = [
|
|
357
|
+
'people',
|
|
358
|
+
'person',
|
|
359
|
+
'drawing'
|
|
360
|
+
];
|
|
361
|
+
|
|
362
|
+
const {
|
|
363
|
+
soundName,
|
|
364
|
+
sound,
|
|
365
|
+
soundExt,
|
|
366
|
+
soundRate,
|
|
367
|
+
soundSampleCount
|
|
368
|
+
} = createNewSound();
|
|
369
|
+
|
|
370
|
+
generateSprite(
|
|
371
|
+
tempInputDir,
|
|
372
|
+
{},
|
|
373
|
+
{
|
|
374
|
+
[soundName]: {
|
|
375
|
+
ext: soundExt,
|
|
376
|
+
data: sound,
|
|
377
|
+
jsonOptions: {
|
|
378
|
+
rate: soundRate,
|
|
379
|
+
sampleCount: soundSampleCount
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
name: spriteName,
|
|
385
|
+
tags: spriteTags
|
|
386
|
+
}
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const result = buildLibraries({ inputDir: tempInputDir });
|
|
390
|
+
|
|
391
|
+
expect(result.sprites).toHaveLength(1);
|
|
392
|
+
expect(result.sprites[0].name).toBe(spriteName);
|
|
393
|
+
expect(result.sprites[0].tags).toEqual(spriteTags);
|
|
394
|
+
expect(result.sprites[0].sounds).toHaveLength(1);
|
|
395
|
+
expect(result.sprites[0].sounds[0].name).toBe(soundName);
|
|
396
|
+
expect(result.sprites[0].sounds[0].tags).toEqual([]); // Should NOT inherit tags
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('Should fill global costumes from sprites if fillAssetsFromSprites is true', () => {
|
|
400
|
+
const spriteName = 'Abby';
|
|
401
|
+
const spriteTags = [
|
|
402
|
+
'people',
|
|
403
|
+
'person',
|
|
404
|
+
'drawing'
|
|
405
|
+
];
|
|
406
|
+
const {
|
|
407
|
+
costumeName,
|
|
408
|
+
costume,
|
|
409
|
+
costumeExt,
|
|
410
|
+
costumeMd5ext,
|
|
411
|
+
costumeBitmapResolution,
|
|
412
|
+
costumeRotationCenterX,
|
|
413
|
+
costumeRotationCenterY
|
|
414
|
+
} = createNewCostume();
|
|
415
|
+
|
|
416
|
+
const {
|
|
417
|
+
soundName,
|
|
418
|
+
sound,
|
|
419
|
+
soundExt,
|
|
420
|
+
soundMd5ext,
|
|
421
|
+
soundRate,
|
|
422
|
+
soundSampleCount
|
|
423
|
+
} = createNewSound();
|
|
424
|
+
|
|
425
|
+
generateSprite(
|
|
426
|
+
tempInputDir,
|
|
427
|
+
{
|
|
428
|
+
[costumeName]: {
|
|
429
|
+
ext: costumeExt,
|
|
430
|
+
data: costume,
|
|
431
|
+
jsonOptions: {
|
|
432
|
+
bitmapResolution: costumeBitmapResolution,
|
|
433
|
+
rotationCenterX: costumeRotationCenterX,
|
|
434
|
+
rotationCenterY: costumeRotationCenterY
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
[soundName]: {
|
|
440
|
+
ext: soundExt,
|
|
441
|
+
data: sound,
|
|
442
|
+
jsonOptions: {
|
|
443
|
+
rate: soundRate,
|
|
444
|
+
sampleCount: soundSampleCount
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
name: spriteName,
|
|
450
|
+
tags: spriteTags
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
const result = buildLibraries({ inputDir: tempInputDir, fillAssetsFromSprites: true });
|
|
455
|
+
|
|
456
|
+
expect(result.sprites).toHaveLength(1);
|
|
457
|
+
expect(result.costumes).toHaveLength(1); // Should be added to global costumes
|
|
458
|
+
expect(result.sounds).toHaveLength(1); // Should be added to global sounds
|
|
459
|
+
expect(result.costumes[0].name).toBe(costumeName);
|
|
460
|
+
expect(result.costumes[0].md5ext).toBe(costumeMd5ext);
|
|
461
|
+
expect(result.costumes[0].bitmapResolution).toBe(costumeBitmapResolution);
|
|
462
|
+
expect(result.costumes[0].rotationCenterX).toBe(costumeRotationCenterX);
|
|
463
|
+
expect(result.costumes[0].rotationCenterY).toBe(costumeRotationCenterY);
|
|
464
|
+
expect(result.costumes[0].tags).toEqual(spriteTags); // Inherited from sprite
|
|
465
|
+
expect(result.sounds[0].name).toBe(soundName);
|
|
466
|
+
expect(result.sounds[0].md5ext).toBe(soundMd5ext);
|
|
467
|
+
expect(result.sounds[0].rate).toBe(soundRate);
|
|
468
|
+
expect(result.sounds[0].sampleCount).toBe(soundSampleCount);
|
|
469
|
+
expect(result.sounds[0].dataFormat).toBe(soundExt);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
test('Should NOT fill global assets from sprites if fillAssetsFromSprites is false', () => {
|
|
473
|
+
const spriteName = 'Abby';
|
|
474
|
+
const spriteTags = [
|
|
475
|
+
'people',
|
|
476
|
+
'person',
|
|
477
|
+
'drawing'
|
|
478
|
+
];
|
|
479
|
+
const {
|
|
480
|
+
costumeName,
|
|
481
|
+
costume,
|
|
482
|
+
costumeExt,
|
|
483
|
+
costumeBitmapResolution,
|
|
484
|
+
costumeRotationCenterX,
|
|
485
|
+
costumeRotationCenterY
|
|
486
|
+
} = createNewCostume();
|
|
487
|
+
|
|
488
|
+
const {
|
|
489
|
+
soundName,
|
|
490
|
+
sound,
|
|
491
|
+
soundExt,
|
|
492
|
+
soundRate,
|
|
493
|
+
soundSampleCount
|
|
494
|
+
} = createNewSound();
|
|
495
|
+
|
|
496
|
+
generateSprite(
|
|
497
|
+
tempInputDir,
|
|
498
|
+
{
|
|
499
|
+
[costumeName]: {
|
|
500
|
+
ext: costumeExt,
|
|
501
|
+
data: costume,
|
|
502
|
+
jsonOptions: {
|
|
503
|
+
bitmapResolution: costumeBitmapResolution,
|
|
504
|
+
rotationCenterX: costumeRotationCenterX,
|
|
505
|
+
rotationCenterY: costumeRotationCenterY
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
[soundName]: {
|
|
511
|
+
ext: soundExt,
|
|
512
|
+
data: sound,
|
|
513
|
+
jsonOptions: {
|
|
514
|
+
rate: soundRate,
|
|
515
|
+
sampleCount: soundSampleCount
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
name: spriteName,
|
|
521
|
+
tags: spriteTags
|
|
522
|
+
}
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
const result = buildLibraries({ inputDir: tempInputDir, fillAssetsFromSprites: false });
|
|
526
|
+
|
|
527
|
+
expect(result.sprites).toHaveLength(1);
|
|
528
|
+
expect(result.sounds).toHaveLength(0); // Should be empty
|
|
529
|
+
expect(result.costumes).toHaveLength(0); // Should be empty
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test('Should fill sounds from global libraries even when fillAssetsFromSprites is false', () => {
|
|
533
|
+
const spriteName = 'Abby';
|
|
534
|
+
const soundNameInSprite = 'Pop-sprite';
|
|
535
|
+
const {
|
|
536
|
+
soundName: soundNameInAsset,
|
|
537
|
+
sound,
|
|
538
|
+
soundExt,
|
|
539
|
+
soundMd5ext,
|
|
540
|
+
soundRate,
|
|
541
|
+
soundSampleCount
|
|
542
|
+
} = createNewSound({ name: 'Pop-asset' });
|
|
543
|
+
|
|
544
|
+
generateAsset(
|
|
545
|
+
tempInputDir,
|
|
546
|
+
'sounds',
|
|
547
|
+
sound,
|
|
548
|
+
{
|
|
549
|
+
name: soundNameInAsset,
|
|
550
|
+
ext: soundExt,
|
|
551
|
+
tags: [],
|
|
552
|
+
rate: soundRate,
|
|
553
|
+
sampleCount: soundSampleCount,
|
|
554
|
+
}
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
generateSprite(
|
|
558
|
+
tempInputDir,
|
|
559
|
+
{},
|
|
560
|
+
{
|
|
561
|
+
[soundNameInSprite]: {
|
|
562
|
+
ext: soundExt,
|
|
563
|
+
data: sound,
|
|
564
|
+
jsonOptions: {
|
|
565
|
+
rate: soundRate,
|
|
566
|
+
sampleCount: soundSampleCount
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
name: spriteName,
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
const result = buildLibraries({ inputDir: tempInputDir, fillAssetsFromSprites: false });
|
|
576
|
+
|
|
577
|
+
expect(result.sprites).toHaveLength(1);
|
|
578
|
+
expect(result.sprites[0].sounds).toHaveLength(1);
|
|
579
|
+
expect(result.sprites[0].sounds[0].name).toBe(soundNameInSprite);
|
|
580
|
+
expect(result.sprites[0].sounds[0].md5ext).toBe(soundMd5ext);
|
|
581
|
+
expect(result.sounds).toHaveLength(1); // Should still include sound from global library
|
|
582
|
+
expect(result.sounds[0].name).toBe(soundNameInAsset);
|
|
583
|
+
expect(result.sounds[0].md5ext).toBe(soundMd5ext);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
test('Should handle duplicate asset names in different sprites by including only one in the global library', () => {
|
|
587
|
+
const spriteName1 = 'Abby 1';
|
|
588
|
+
const spriteName2 = 'Abby 2';
|
|
589
|
+
|
|
590
|
+
const {
|
|
591
|
+
costumeName,
|
|
592
|
+
costume,
|
|
593
|
+
costumeMd5ext
|
|
594
|
+
} = createNewCostume();
|
|
595
|
+
|
|
596
|
+
generateSprite(
|
|
597
|
+
tempInputDir,
|
|
598
|
+
{
|
|
599
|
+
[costumeName]: {
|
|
600
|
+
ext: costumeMd5ext,
|
|
601
|
+
data: costume,
|
|
602
|
+
jsonOptions: {}
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
{},
|
|
606
|
+
{
|
|
607
|
+
name: spriteName1
|
|
608
|
+
}
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
generateSprite(
|
|
612
|
+
tempInputDir,
|
|
613
|
+
{
|
|
614
|
+
[costumeName]: {
|
|
615
|
+
ext: costumeMd5ext,
|
|
616
|
+
data: costume,
|
|
617
|
+
jsonOptions: {}
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
{},
|
|
621
|
+
{
|
|
622
|
+
name: spriteName2
|
|
623
|
+
}
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
const result = buildLibraries({ inputDir: tempInputDir, fillAssetsFromSprites: true });
|
|
627
|
+
|
|
628
|
+
expect(result.sprites).toHaveLength(2);
|
|
629
|
+
expect(result.costumes).toHaveLength(1); // Only one costume should be in global lib since names and files match
|
|
630
|
+
expect(result.costumes[0].name).toBe(costumeName);
|
|
631
|
+
expect(result.costumes[0].md5ext).toBe(costumeMd5ext);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test('Should deduplicate assets with the same name, ignoring casing', () => {
|
|
635
|
+
const spriteName1 = 'Abby 1';
|
|
636
|
+
const spriteName2 = 'Abby 2';
|
|
637
|
+
|
|
638
|
+
const {
|
|
639
|
+
soundName: soundName1,
|
|
640
|
+
sound,
|
|
641
|
+
soundExt,
|
|
642
|
+
soundRate,
|
|
643
|
+
soundSampleCount
|
|
644
|
+
} = createNewSound({ name: 'Pop' });
|
|
645
|
+
|
|
646
|
+
const soundName2 = 'pop';
|
|
647
|
+
|
|
648
|
+
generateSprite(
|
|
649
|
+
tempInputDir,
|
|
650
|
+
{},
|
|
651
|
+
{
|
|
652
|
+
[soundName1]: {
|
|
653
|
+
ext: soundExt,
|
|
654
|
+
data: sound,
|
|
655
|
+
jsonOptions: {
|
|
656
|
+
rate: soundRate,
|
|
657
|
+
sampleCount: soundSampleCount
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
name: spriteName1
|
|
663
|
+
}
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
generateSprite(
|
|
667
|
+
tempInputDir,
|
|
668
|
+
{},
|
|
669
|
+
{
|
|
670
|
+
[soundName2]: {
|
|
671
|
+
ext: soundExt,
|
|
672
|
+
data: sound,
|
|
673
|
+
jsonOptions: {
|
|
674
|
+
rate: soundRate,
|
|
675
|
+
sampleCount: soundSampleCount
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
name: spriteName2
|
|
681
|
+
}
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
const result = buildLibraries({ inputDir: tempInputDir, fillAssetsFromSprites: true });
|
|
685
|
+
|
|
686
|
+
expect(result.sprites).toHaveLength(2);
|
|
687
|
+
expect(result.sounds).toHaveLength(1); // Only one sound should be in global lib since names match ignoring case
|
|
688
|
+
expect(result.sounds[0].name).toBe(soundName1); // Should keep the first one encountered
|
|
689
|
+
expect(result.sprites[0].sounds[0].name).toBe(soundName1); // Sprites should still refer to the original name
|
|
690
|
+
expect(result.sprites[1].sounds[0].name).toBe(soundName2);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test('Should handle multiple sprites with same costume name but different files', () => {
|
|
694
|
+
const spriteName1 = 'Abby 1';
|
|
695
|
+
const spriteName2 = 'Abby 2';
|
|
696
|
+
|
|
697
|
+
const {
|
|
698
|
+
costumeName,
|
|
699
|
+
costumeExt,
|
|
700
|
+
costumeMd5ext: costumeMd5ext1,
|
|
701
|
+
costume: costume1
|
|
702
|
+
} = createNewCostume({ name: 'commonCostume' });
|
|
703
|
+
|
|
704
|
+
const {
|
|
705
|
+
costumeMd5ext: costumeMd5ext2,
|
|
706
|
+
costume: costume2
|
|
707
|
+
} = createNewCostume({
|
|
708
|
+
name: 'commonCostume',
|
|
709
|
+
path: path.join(fixturesDir, 'Abby-b.svg'),
|
|
710
|
+
md5ext: '920f14335615fff9b8c55fccb8971984.svg'
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
generateSprite(
|
|
714
|
+
tempInputDir,
|
|
715
|
+
{
|
|
716
|
+
[costumeName]: {
|
|
717
|
+
ext: costumeExt,
|
|
718
|
+
data: costume1,
|
|
719
|
+
jsonOptions: {}
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
{},
|
|
723
|
+
{
|
|
724
|
+
name: spriteName1
|
|
725
|
+
}
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
generateSprite(
|
|
729
|
+
tempInputDir,
|
|
730
|
+
{
|
|
731
|
+
[costumeName]: {
|
|
732
|
+
ext: costumeExt,
|
|
733
|
+
data: costume2,
|
|
734
|
+
jsonOptions: {}
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
{},
|
|
738
|
+
{
|
|
739
|
+
name: spriteName2
|
|
740
|
+
}
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
const result = buildLibraries({ inputDir: tempInputDir, fillAssetsFromSprites: true });
|
|
744
|
+
|
|
745
|
+
expect(result.sprites).toHaveLength(2);
|
|
746
|
+
expect(result.sprites[0].costumes).toHaveLength(1);
|
|
747
|
+
expect(result.sprites[0].costumes[0].name).toBe(costumeName);
|
|
748
|
+
expect(result.sprites[0].costumes[0].md5ext).toBe(costumeMd5ext1);
|
|
749
|
+
expect(result.sprites[1].costumes).toHaveLength(1);
|
|
750
|
+
expect(result.sprites[1].costumes[0].name).toBe(costumeName);
|
|
751
|
+
expect(result.sprites[1].costumes[0].md5ext).toBe(costumeMd5ext2);
|
|
752
|
+
|
|
753
|
+
expect(result.costumes).toHaveLength(2); // Both costumes should be in global lib since the key-name pair differs due to different md5ext, even though names are the same
|
|
754
|
+
|
|
755
|
+
const costumeMd5exts = result.costumes.map(c => c.md5ext);
|
|
756
|
+
|
|
757
|
+
expect(costumeMd5exts).toContain(costumeMd5ext1);
|
|
758
|
+
expect(costumeMd5exts).toContain(costumeMd5ext2);
|
|
759
|
+
|
|
760
|
+
result.costumes.forEach(c => {
|
|
761
|
+
expect(c.name).toBe(costumeName);
|
|
762
|
+
});
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
test('Should handle multiple sprites with same asset md5key but different names', () => {
|
|
766
|
+
const spriteName1 = 'Abby 1';
|
|
767
|
+
const spriteName2 = 'Abby 2';
|
|
768
|
+
|
|
769
|
+
const {
|
|
770
|
+
soundName: soundName1,
|
|
771
|
+
soundExt,
|
|
772
|
+
sound,
|
|
773
|
+
soundMd5ext,
|
|
774
|
+
soundRate,
|
|
775
|
+
soundSampleCount
|
|
776
|
+
} = createNewSound({ name: 'Pop-a' });
|
|
777
|
+
|
|
778
|
+
const soundName2 = 'Pop-b';
|
|
779
|
+
|
|
780
|
+
generateSprite(
|
|
781
|
+
tempInputDir,
|
|
782
|
+
{},
|
|
783
|
+
{
|
|
784
|
+
[soundName1]: {
|
|
785
|
+
ext: soundExt,
|
|
786
|
+
data: sound,
|
|
787
|
+
jsonOptions: {
|
|
788
|
+
rate: soundRate,
|
|
789
|
+
sampleCount: soundSampleCount
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
name: spriteName1
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
|
|
798
|
+
generateSprite(
|
|
799
|
+
tempInputDir,
|
|
800
|
+
{},
|
|
801
|
+
{
|
|
802
|
+
[soundName2]: {
|
|
803
|
+
ext: soundExt,
|
|
804
|
+
data: sound,
|
|
805
|
+
jsonOptions: {
|
|
806
|
+
rate: soundRate,
|
|
807
|
+
sampleCount: soundSampleCount
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
name: spriteName2
|
|
813
|
+
}
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
const result = buildLibraries({ inputDir: tempInputDir, fillAssetsFromSprites: true });
|
|
817
|
+
|
|
818
|
+
expect(result.sprites).toHaveLength(2);
|
|
819
|
+
expect(result.sprites[0].sounds).toHaveLength(1);
|
|
820
|
+
expect(result.sprites[0].sounds[0].name).toBe(soundName1);
|
|
821
|
+
expect(result.sprites[0].sounds[0].md5ext).toBe(soundMd5ext);
|
|
822
|
+
expect(result.sprites[1].sounds).toHaveLength(1);
|
|
823
|
+
expect(result.sprites[1].sounds[0].name).toBe(soundName2);
|
|
824
|
+
expect(result.sprites[1].sounds[0].md5ext).toBe(soundMd5ext);
|
|
825
|
+
|
|
826
|
+
expect(result.sounds).toHaveLength(2); // Both sounds should be in global lib since names are different, even though files are the same
|
|
827
|
+
const soundNames = result.sounds.map(s => s.name);
|
|
828
|
+
expect(soundNames).toContain(soundName1);
|
|
829
|
+
expect(soundNames).toContain(soundName2);
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
test('Should always extract dataFormat from file extension', () => {
|
|
833
|
+
const spriteName = 'Abby';
|
|
834
|
+
const {
|
|
835
|
+
sound,
|
|
836
|
+
soundName,
|
|
837
|
+
soundExt,
|
|
838
|
+
soundMd5ext,
|
|
839
|
+
soundRate,
|
|
840
|
+
soundSampleCount
|
|
841
|
+
} = createNewSound({ name: 'Pop' });
|
|
842
|
+
|
|
843
|
+
generateSprite(
|
|
844
|
+
tempInputDir,
|
|
845
|
+
{},
|
|
846
|
+
{
|
|
847
|
+
[soundName]: {
|
|
848
|
+
ext: soundExt,
|
|
849
|
+
data: sound,
|
|
850
|
+
jsonOptions: {
|
|
851
|
+
rate: soundRate,
|
|
852
|
+
sampleCount: soundSampleCount,
|
|
853
|
+
dataFormat: 'adpcm',
|
|
854
|
+
format: 'adpcm' // Also include format for good measure to test that we ignore it as well
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
name: spriteName
|
|
860
|
+
}
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
const result = buildLibraries({ inputDir: tempInputDir, fillAssetsFromSprites: true });
|
|
864
|
+
|
|
865
|
+
expect(result.sprites).toHaveLength(1);
|
|
866
|
+
expect(result.sprites[0].name).toBe(spriteName);
|
|
867
|
+
expect(result.sprites[0].sounds).toHaveLength(1);
|
|
868
|
+
expect(result.sprites[0].sounds[0].name).toBe(soundName);
|
|
869
|
+
expect(result.sprites[0].sounds[0].md5ext).toBe(soundMd5ext);
|
|
870
|
+
expect(result.sprites[0].sounds[0].rate).toBe(soundRate);
|
|
871
|
+
expect(result.sprites[0].sounds[0].sampleCount).toBe(soundSampleCount);
|
|
872
|
+
expect(result.sprites[0].sounds[0].dataFormat).toBe(soundExt); // Should use file extension, not provided dataFormat or format
|
|
873
|
+
expect(result.sprites[0].sounds[0].format).toBeUndefined(); // format should be removed from final output
|
|
874
|
+
|
|
875
|
+
expect(result.sounds).toHaveLength(1);
|
|
876
|
+
expect(result.sounds[0].name).toBe(soundName);
|
|
877
|
+
expect(result.sounds[0].md5ext).toBe(soundMd5ext);
|
|
878
|
+
expect(result.sounds[0].rate).toBe(soundRate);
|
|
879
|
+
expect(result.sounds[0].sampleCount).toBe(soundSampleCount);
|
|
880
|
+
expect(result.sounds[0].dataFormat).toBe(soundExt); // Should use file extension, not provided dataFormat or format
|
|
881
|
+
expect(result.sounds[0].format).toBeUndefined(); // format should be removed from final output
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
test('Should handle invalid JSON files gracefully', () => {
|
|
885
|
+
const spriteName = 'InvalidSprite';
|
|
886
|
+
const spriteDir = path.join(tempInputDir, 'sprites', spriteName);
|
|
887
|
+
|
|
888
|
+
fs.mkdirSync(spriteDir, { recursive: true });
|
|
889
|
+
fs.writeFileSync(path.join(spriteDir, `${spriteName}.json5`), '{ invalid json }'); // Invalid JSON
|
|
890
|
+
|
|
891
|
+
const result = buildLibraries({ inputDir: tempInputDir, fillAssetsFromSprites: true });
|
|
892
|
+
|
|
893
|
+
expect(result.sprites).toHaveLength(0); // Should skip invalid sprite
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
test('Should handle missing files gracefully', () => {
|
|
897
|
+
const spriteName = 'MissingFilesSprite';
|
|
898
|
+
const costumeName = 'NonExistentCostume';
|
|
899
|
+
const soundName = 'NonExistentSound';
|
|
900
|
+
|
|
901
|
+
const spriteDir = path.join(tempInputDir, 'sprites', spriteName);
|
|
902
|
+
fs.mkdirSync(spriteDir, { recursive: true });
|
|
903
|
+
const jsonContent = {
|
|
904
|
+
'$schema': '../../../schemas/media-collection-schema.json',
|
|
905
|
+
sprites: [{
|
|
906
|
+
name: spriteName,
|
|
907
|
+
costumes: [costumeName],
|
|
908
|
+
sounds: [soundName]
|
|
909
|
+
}],
|
|
910
|
+
costumes: [{
|
|
911
|
+
name: costumeName,
|
|
912
|
+
file: `${costumeName}.svg`
|
|
913
|
+
}],
|
|
914
|
+
sounds: [{
|
|
915
|
+
name: soundName,
|
|
916
|
+
file: `${soundName}.wav`
|
|
917
|
+
}]
|
|
918
|
+
};
|
|
919
|
+
fs.writeFileSync(path.join(spriteDir, `${spriteName}.json5`), JSON.stringify(jsonContent));
|
|
920
|
+
|
|
921
|
+
const result = buildLibraries({ inputDir: tempInputDir, fillAssetsFromSprites: true });
|
|
922
|
+
expect(result.sprites).toHaveLength(1);
|
|
923
|
+
expect(result.sprites[0].name).toBe(spriteName);
|
|
924
|
+
expect(result.sprites[0].costumes).toHaveLength(0); // Should skip missing costume
|
|
925
|
+
expect(result.sprites[0].sounds).toHaveLength(0); // Should skip missing sound
|
|
926
|
+
expect(result.costumes).toHaveLength(0); // Should not include missing costume in global library
|
|
927
|
+
expect(result.sounds).toHaveLength(0); // Should not include missing sound in global library
|
|
928
|
+
});
|
|
929
|
+
});
|