Skip to content

Commit 0d37b19

Browse files
authored
Merge pull request #68 from TurboWarp/platform
Add platform concept
2 parents 3837380 + aa5015d commit 0d37b19

File tree

5 files changed

+205
-29
lines changed

5 files changed

+205
-29
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ const run = async () => {
4545
// is not stable and may change without warning.
4646
logCallback: (message) => {
4747
console.log(message);
48-
}
48+
},
49+
// Values: 'scratch' (default), 'turbowarp'
50+
platform: 'scratch'
4951
};
5052
// To use the above options, just supply as the second argument when you call sb3fix:
5153
await sb3fix.fixZip(brokenZip, options);

src/sb3fix.js

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ License, v. 2.0. If a copy of the MPL was not distributed with this
88
file, You can obtain one at https://mozilla.org/MPL/2.0/.
99
*/
1010

11+
/**
12+
* @typedef {'scratch'|'turbowarp'} Platform
13+
*/
14+
1115
/**
1216
* @typedef Options
17+
* @property {Platform} [platform] Defaults to 'scratch'.
1318
* @property {(message: string) => void} [logCallback]
1419
*/
1520

@@ -19,6 +24,35 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/.
1924
*/
2025
const isObject = (obj) => !!obj && typeof obj === 'object';
2126

27+
/**
28+
* @typedef PlatformInfo
29+
* @property {boolean} [allowsNonScalarVariables]
30+
*/
31+
32+
/**
33+
* @type {Record<Platform, PlatformInfo>}
34+
*/
35+
const platforms = {
36+
scratch: {},
37+
turbowarp: {
38+
allowsNonScalarVariables: true
39+
}
40+
};
41+
42+
/**
43+
* @param {Options} options
44+
* @returns {PlatformInfo}
45+
*/
46+
const getPlatform = (options) => {
47+
if (options && Object.prototype.hasOwnProperty.call(options, 'platform')) {
48+
if (Object.prototype.hasOwnProperty.call(platforms, options.platform)) {
49+
return platforms[options.platform];
50+
}
51+
throw new Error(`Unknown platform: ${options.platform}`);
52+
}
53+
return platforms.scratch;
54+
};
55+
2256
const BUILTIN_EXTENSIONS = [
2357
'control',
2458
'data',
@@ -70,6 +104,8 @@ const getKnownExtensions = (project) => {
70104
* @returns {object} Fixed project.json object. If the `data` argument was an object, this will point to the same object.
71105
*/
72106
const fixJSON = (data, options = {}) => {
107+
const platform = getPlatform(options);
108+
73109
/**
74110
* @param {string} message
75111
*/
@@ -94,10 +130,12 @@ const fixJSON = (data, options = {}) => {
94130
variable[0] = String(variable[0]);
95131
}
96132

97-
const value = variable[1];
98-
if (typeof value !== 'number' && typeof value !== 'string' && typeof value !== 'boolean') {
99-
log(`variable ${id} value was not a Scratch-compatible value`);
100-
variable[1] = String(variable[1]);
133+
if (!platform.allowsNonScalarVariables) {
134+
const value = variable[1];
135+
if (typeof value !== 'number' && typeof value !== 'string' && typeof value !== 'boolean') {
136+
log(`variable ${id} value was not a Scratch-compatible value`);
137+
variable[1] = String(variable[1]);
138+
}
101139
}
102140
};
103141

@@ -602,5 +640,6 @@ const fixZip = async (data, options = {}) => {
602640

603641
module.exports = {
604642
fixJSON,
605-
fixZip
643+
fixZip,
644+
platforms
606645
};
1.99 KB
Binary file not shown.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
checking target 0
2+
list nf(jE2GO9Mw*c7)~RxKv value was not an array
3+
checking target 1
4+
list 3.vwk($=q-/jw5_[2z)2 index 0 was not a Scratch-compatible value

tests/snapshots.js

Lines changed: 154 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,57 @@ const sb3fix = require('../src/sb3fix');
88

99
const inputDirectory = path.join(__dirname, 'samples');
1010
const outputDirectory = path.join(__dirname, 'expected-output');
11+
12+
/** @type {Array<import('../src/sb3fix').Platform>} */
13+
const nonScratchPlatforms = Object.keys(sb3fix.platforms).filter(i => i !== 'scratch');
14+
1115
const testcases = fs.readdirSync(inputDirectory)
1216
.filter(i => i.endsWith('.sb3') || i.endsWith('.sprite3'))
13-
.sort()
14-
.map(i => ({
15-
name: i,
16-
inputPath: path.join(inputDirectory, i),
17-
outputProjectPath: path.join(outputDirectory, i),
18-
outputLogPath: path.join(outputDirectory, i.replace(/\.(?:sb|sprite)3$/, '.txt')),
19-
}));
20-
21-
const runTestcase = async (testcase) => {
17+
.sort();
18+
19+
/**
20+
* @typedef Result
21+
* @property {Uint8Array} result
22+
* @property {string} log
23+
*/
24+
25+
/**
26+
* @param {string} testcase
27+
* @param {import('../src/sb3fix').Platform} platform
28+
* @returns {string}
29+
*/
30+
const getOutputProjectPath = (testcase, platform) => {
31+
if (platform === 'scratch') {
32+
return path.join(outputDirectory, testcase);
33+
}
34+
return path.join(outputDirectory, platform, testcase);
35+
};
36+
37+
/**
38+
*
39+
* @param {string} testcase
40+
* @param {import('../src/sb3fix').Platform} platform
41+
* @returns {string}
42+
*/
43+
const getOutputLogPath = (testcase, platform) => {
44+
const name = testcase.replace(/\.(?:sb|sprite)3$/, '.txt');
45+
if (platform === 'scratch') {
46+
return path.join(outputDirectory, name);
47+
}
48+
return path.join(outputDirectory, platform, name);
49+
};
50+
51+
/**
52+
* @param {string} testcase
53+
* @param {import('../src/sb3fix').Platform} platform
54+
* @returns {Promise<Result>}
55+
*/
56+
const runTestcase = async (testcase, platform) => {
2257
const logs = [];
23-
const data = await fsPromises.readFile(testcase.inputPath);
58+
const inputPath = path.join(inputDirectory, testcase);
59+
const data = await fsPromises.readFile(inputPath);
2460
const result = await sb3fix.fixZip(data, {
61+
platform,
2562
logCallback: (message) => {
2663
logs.push(message);
2764
}
@@ -32,35 +69,129 @@ const runTestcase = async (testcase) => {
3269
};
3370
};
3471

72+
/**
73+
* @param {Uint8Array} a
74+
* @param {Uint8Array} b
75+
* @returns {boolean}
76+
*/
77+
const areArraysShallowEqual = (a, b) => {
78+
if (a.length !== b.length) {
79+
return false;
80+
}
81+
for (let i = 0; i < a.length; i++) {
82+
if (a[i] !== b[i]) {
83+
return false;
84+
}
85+
}
86+
return true;
87+
};
88+
89+
/**
90+
* @param {Result} resultA
91+
* @param {Result} resultB
92+
* @returns {boolean}
93+
*/
94+
const isIdenticalResult = (resultA, resultB) => (
95+
resultA.log === resultB.log &&
96+
areArraysShallowEqual(resultA.result, resultB.result)
97+
);
98+
99+
/**
100+
* @param {Result} resultA
101+
* @param {Result} resultB
102+
*/
103+
const assertIdenticalResults = (resultA, resultB) => {
104+
assert.strictEqual(resultA.log, resultB.log, 'log');
105+
assert.deepStrictEqual(resultA.result, resultB.result, 'project');
106+
};
107+
35108
const validate = async () => {
36109
for (const testcase of testcases) {
37-
await nodeTest(testcase, async (t) => {
38-
const expectedFixedProject = new Uint8Array(await fsPromises.readFile(testcase.outputProjectPath));
39-
const expectedLog = await fsPromises.readFile(testcase.outputLogPath, 'utf-8');
40-
const result = await runTestcase(testcase);
110+
await nodeTest(testcase, async () => {
111+
/** @type {Result|null} */
112+
let expectedScratchResult = null;
41113

42-
assert.equal(result.log, expectedLog, 'log');
43-
assert.deepStrictEqual(result.result, expectedFixedProject, 'project');
114+
await nodeTest("scratch", async () => {
115+
expectedScratchResult = await readResult(testcase, 'scratch');
116+
assert.notStrictEqual(expectedScratchResult, null, 'scratch expected result exists');
117+
118+
const actualResult = await runTestcase(testcase, 'scratch');
119+
assertIdenticalResults(expectedScratchResult, actualResult);
120+
});
121+
122+
for (const platform of nonScratchPlatforms) {
123+
await nodeTest(platform, async () => {
124+
const expectedResult = await readResult(testcase, platform) || expectedScratchResult;
125+
const actualResult = await runTestcase(testcase, platform);
126+
assertIdenticalResults(expectedResult, actualResult);
127+
});
128+
}
44129
});
45130
}
46131
};
47132

133+
/**
134+
* @param {string} testcase
135+
* @param {import('../src/sb3fix').Platform} platform
136+
* @returns {Promise<Result|null>}
137+
*/
138+
const readResult = async (testcase, platform) => {
139+
const outputProjectPath = getOutputProjectPath(testcase, platform);
140+
const outputLogPath = getOutputLogPath(testcase, platform);
141+
142+
try {
143+
const result = new Uint8Array(await fsPromises.readFile(outputProjectPath));
144+
const log = await fsPromises.readFile(outputLogPath, 'utf8');
145+
return {
146+
result,
147+
log
148+
};
149+
} catch (e) {
150+
if (e.code === 'ENOENT') {
151+
return null;
152+
}
153+
throw e;
154+
}
155+
};
156+
157+
/**
158+
* @param {string} testcase
159+
* @param {import('../src/sb3fix').Platform} platform
160+
* @param {Result} result
161+
*/
162+
const writeResult = async (testcase, platform, result) => {
163+
const outputProjectPath = getOutputProjectPath(testcase, platform);
164+
const outputLogPath = getOutputLogPath(testcase, platform);
165+
166+
const directory = path.dirname(outputProjectPath);
167+
await fsPromises.mkdir(directory, {
168+
recursive: true
169+
});
170+
171+
await fsPromises.writeFile(outputProjectPath, result.result);
172+
await fsPromises.writeFile(outputLogPath, result.log);
173+
};
174+
48175
const update = async () => {
49176
console.time('Updated snapshots');
50177

51178
await fsPromises.rm(outputDirectory, {
52179
force: true,
53180
recursive: true
54181
});
55-
await fsPromises.mkdir(outputDirectory, {
56-
recursive: true
57-
});
58182

59183
for (const testcase of testcases) {
60-
console.log(`${testcase.name} ...`);
61-
const result = await runTestcase(testcase);
62-
await fsPromises.writeFile(testcase.outputProjectPath, result.result);
63-
await fsPromises.writeFile(testcase.outputLogPath, result.log);
184+
console.log(`${testcase} ...`);
185+
186+
const scratchResult = await runTestcase(testcase, 'scratch');
187+
await writeResult(testcase, 'scratch', scratchResult);
188+
189+
for (const platform of nonScratchPlatforms) {
190+
const nonScratchResult = await runTestcase(testcase, platform);
191+
if (!isIdenticalResult(scratchResult, nonScratchResult)) {
192+
await writeResult(testcase, platform, nonScratchResult);
193+
}
194+
}
64195
}
65196

66197
console.timeEnd('Updated snapshots');

0 commit comments

Comments
 (0)