c++bug修复
This commit is contained in:
166
scratch-gui/scripts/test-devtool-control.cjs
Normal file
166
scratch-gui/scripts/test-devtool-control.cjs
Normal file
@@ -0,0 +1,166 @@
|
||||
// Run: npm run test:devtool
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const vm = require('vm');
|
||||
const babel = require('@babel/core');
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const controlPath = 'src/playground/devtool-control.js';
|
||||
const compile = file => babel.transformFileSync(path.join(root, file), {
|
||||
configFile: false, babelrc: false, presets: ['@babel/preset-env', '@babel/preset-react']
|
||||
}).code;
|
||||
const controlCode = compile(controlPath);
|
||||
const homeCode = compile('src/playground/home.js');
|
||||
let checks = 0;
|
||||
function test(name, action) {
|
||||
action();
|
||||
checks++;
|
||||
console.log(`PASS ${name}`);
|
||||
}
|
||||
|
||||
function page(storage = new Map(), {enabled = true, brokenStorage = false, immediate = false} = {}) {
|
||||
const starts = [];
|
||||
const effects = [];
|
||||
function detector(options) {
|
||||
starts.push(options);
|
||||
// Simulate a library probe firing during initialization with F12 already open.
|
||||
if (immediate) options.ondevtoolopen('already-open');
|
||||
}
|
||||
detector.isSuspend = false;
|
||||
const window = {
|
||||
location: {href: '/editor.html'}, onbeforeunload: () => {},
|
||||
cppCodeStopRun: () => effects.push('stop C++'),
|
||||
loadCppProject: () => effects.push('clear C++'),
|
||||
pythonCodeStopRun: () => effects.push('stop Python'),
|
||||
loadPythonProject: () => effects.push('clear Python'),
|
||||
unityInstance: {Quit: () => effects.push('quit engine')}
|
||||
};
|
||||
const sessionStorage = {
|
||||
getItem(key) {if (brokenStorage) throw new Error('storage denied'); return storage.get(key) || null;},
|
||||
setItem(key, value) {if (brokenStorage) throw new Error('storage denied'); storage.set(key, value);}
|
||||
};
|
||||
const sandbox = {
|
||||
exports: {}, window, sessionStorage,
|
||||
localStorage: {getItem: () => '/index.html'},
|
||||
vm: {
|
||||
stopAll: () => effects.push('stop Scratch'),
|
||||
editingTarget: {blocks: {deleteAllBlocks: () => effects.push('clear Scratch')}},
|
||||
emitWorkspaceUpdate: () => effects.push('update workspace')
|
||||
},
|
||||
require(name) {
|
||||
if (name === 'disable-devtool') return detector;
|
||||
if (name === './config.js') return {disabledev: enabled};
|
||||
if (name === './devtool-control.js') return api;
|
||||
throw new Error(`Unexpected import: ${name}`);
|
||||
}
|
||||
};
|
||||
const context = vm.createContext(sandbox);
|
||||
vm.runInContext(controlCode, context, {filename: controlPath});
|
||||
const api = sandbox.exports;
|
||||
return {api, starts, effects, detector, window, context};
|
||||
}
|
||||
|
||||
// Use the actual options and destructive callback from each editor mode.
|
||||
function stageOptions(file) {
|
||||
const source = fs.readFileSync(path.join(root, file), 'utf8');
|
||||
const ast = babel.parseSync(source, {configFile: false, babelrc: false, parserOpts: {plugins: ['jsx']}});
|
||||
let options;
|
||||
function visit(node) {
|
||||
if (!node || typeof node !== 'object') return;
|
||||
if (node.type === 'ImportDeclaration') {
|
||||
assert.notStrictEqual(node.source.value, 'disable-devtool', `${file} bypasses shared control`);
|
||||
}
|
||||
if (node.type === 'CallExpression' && node.callee.name === 'initDisableDevtool') options = node.arguments[0];
|
||||
for (const value of Object.values(node)) {
|
||||
if (Array.isArray(value)) value.forEach(visit);
|
||||
else if (value && typeof value === 'object') visit(value);
|
||||
}
|
||||
}
|
||||
visit(ast);
|
||||
assert(options, `${file} must initialize through shared control`);
|
||||
// Also verify the entire JSX file remains compatible with project Babel presets.
|
||||
compile(file);
|
||||
return source.slice(options.start, options.end);
|
||||
}
|
||||
|
||||
const storage = new Map();
|
||||
test('home exposes stopDev and persists it for navigation', () => {
|
||||
const home = page(storage);
|
||||
vm.runInContext(homeCode, home.context);
|
||||
home.window.stopDev();
|
||||
assert.strictEqual(storage.get('stopDev'), '1');
|
||||
assert.strictEqual(home.detector.isSuspend, true);
|
||||
});
|
||||
|
||||
for (const mode of ['stage-unity', 'stage-python-unity', 'stage-cpp-unity']) {
|
||||
const optionSource = stageOptions(`src/components/${mode}/stage-unity.jsx`);
|
||||
test(`${mode}: home -> editor with F12 open does not start detection or clear code`, () => {
|
||||
const editor = page(storage, {immediate: true});
|
||||
const options = vm.runInContext(`(${optionSource})`, editor.context);
|
||||
editor.api.initDisableDevtool(options);
|
||||
assert.strictEqual(editor.starts.length, 0);
|
||||
assert.strictEqual(editor.detector.isSuspend, true);
|
||||
assert.strictEqual(editor.window.location.href, '/editor.html');
|
||||
assert.deepStrictEqual(editor.effects, []);
|
||||
// A refresh or another mode initialization in this tab must stay stopped.
|
||||
editor.api.initDisableDevtool(options);
|
||||
assert.strictEqual(editor.starts.length, 0);
|
||||
});
|
||||
test(`${mode}: stopDev blocks an already queued destructive callback`, () => {
|
||||
const editor = page();
|
||||
const options = vm.runInContext(`(${optionSource})`, editor.context);
|
||||
editor.api.initDisableDevtool(options);
|
||||
assert.strictEqual(editor.starts.length, 1);
|
||||
const callback = editor.starts[0];
|
||||
editor.window.stopDev();
|
||||
assert.strictEqual(callback.ignore(), true);
|
||||
callback.ondevtoolopen('queued');
|
||||
assert.strictEqual(editor.window.location.href, '/editor.html');
|
||||
assert.deepStrictEqual(editor.effects, []);
|
||||
});
|
||||
}
|
||||
|
||||
test('normal session retains default detection and redirect', () => {
|
||||
const normal = page();
|
||||
normal.api.initDisableDevtool();
|
||||
assert.strictEqual(normal.starts.length, 1);
|
||||
assert.strictEqual(normal.starts[0].ignore(), false);
|
||||
normal.starts[0].ondevtoolopen();
|
||||
assert.strictEqual(normal.window.location.href, '/index.html');
|
||||
});
|
||||
test('custom options, ignore and callback arguments remain intact', () => {
|
||||
const normal = page();
|
||||
const seen = [];
|
||||
const next = () => {};
|
||||
normal.api.initDisableDevtool({interval: 123, ignore: () => true, ondevtoolopen: (...args) => seen.push(args)});
|
||||
assert.strictEqual(normal.starts[0].interval, 123);
|
||||
assert.strictEqual(normal.starts[0].ignore(), true);
|
||||
normal.starts[0].ondevtoolopen(7, next);
|
||||
assert.deepStrictEqual(seen, [[7, next]]);
|
||||
});
|
||||
test('disabled configuration does not initialize; stopDev remains available', () => {
|
||||
const development = page(new Map(), {enabled: false});
|
||||
development.api.initDisableDevtool();
|
||||
assert.strictEqual(development.starts.length, 0);
|
||||
assert.strictEqual(typeof development.window.stopDev, 'function');
|
||||
});
|
||||
test('blocked storage still permits immediate stop on current page', () => {
|
||||
const broken = page(new Map(), {brokenStorage: true});
|
||||
broken.api.initDisableDevtool();
|
||||
broken.window.stopDev();
|
||||
assert.strictEqual(broken.detector.isSuspend, true);
|
||||
assert.strictEqual(broken.starts[0].ignore(), true);
|
||||
broken.starts[0].ondevtoolopen();
|
||||
broken.api.initDisableDevtool();
|
||||
assert.strictEqual(broken.starts.length, 1);
|
||||
assert.strictEqual(broken.window.location.href, '/editor.html');
|
||||
});
|
||||
for (const file of ['src/playground/competition.js', 'src/playground/competition_sum.js']) {
|
||||
const optionsSource = stageOptions(file);
|
||||
test(`${file}: same stopDev state is honored`, () => {
|
||||
const competition = page(storage, {immediate: true});
|
||||
competition.api.initDisableDevtool(vm.runInContext(`(${optionsSource})`, competition.context));
|
||||
assert.strictEqual(competition.starts.length, 0);
|
||||
});
|
||||
}
|
||||
console.log(`${checks} devtool regression checks passed`);
|
||||
Reference in New Issue
Block a user