feat: add weekly curriculum cover image upload and display

This commit is contained in:
2026-09-23 16:03:33 +09:00
parent a7c9cdc16e
commit 656b1bf62b
23 changed files with 723 additions and 95 deletions
+67
View File
@@ -0,0 +1,67 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const ts = require('typescript');
// Exercise effect cleanup and asynchronous IPC responses without starting Electron.
const slots = [];
let cursor = 0;
let effects = [];
const react = {
useRef(value) { const i = cursor++; return slots[i] ??= { current: value }; },
useState(value) {
const i = cursor++;
if (!(i in slots)) slots[i] = value;
return [slots[i], next => { slots[i] = typeof next === 'function' ? next(slots[i]) : next; }];
},
useEffect(fn, deps) {
const i = cursor++;
const previous = slots[i];
if (previous && deps.every((dep, index) => Object.is(dep, previous.deps[index]))) return;
effects.push(() => { previous?.cleanup?.(); slots[i] = { deps, cleanup: fn() }; });
}
};
const requests = [];
const context = { exports: {}, require: name => { assert.equal(name, 'react'); return react; }, window: {
remoteWorkspace: { moduleFiles: { content: (...args) => new Promise((resolve, reject) => requests.push({ args, resolve, reject })) } }
} };
const source = fs.readFileSync(path.join(__dirname, '../src/renderer/src/features/workspace/useRemoteEditorFile.ts'), 'utf8');
vm.runInNewContext(ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS } }).outputText, context);
const options = {
enabled: true, targetKey: 'platform', getTarget: () => ({}), module: { name: 'Test', folderName: 'test' },
path: null, files: {}, dirty: {},
setFiles(fn) { options.files = fn(options.files); },
setSaved(fn) { options.saved = fn(options.saved ?? {}); }
};
function render() {
cursor = 0;
const result = context.exports.useRemoteEditorFile(options);
const pending = effects; effects = []; pending.forEach(fn => fn());
return result;
}
const tick = () => new Promise(resolve => setImmediate(resolve));
(async () => {
render(); assert.equal(requests.length, 0);
options.path = 'modules/Test/first.py';
assert.equal(render().loading, true); assert.equal(requests.length, 1);
render(); assert.equal(requests.length, 1);
options.path = 'modules/Test/second.py'; render();
requests[0].resolve({ content: 'old response' }); await tick();
assert.equal(options.files['modules/Test/first.py'], undefined);
requests[1].resolve({ content: 'second' }); await tick();
assert.equal(render().loading, false);
assert.equal(options.files[options.path], 'second');
options.path = null; render(); options.path = 'modules/Test/second.py'; render();
assert.equal(requests.length, 2);
options.path = 'modules/Test/fail.py'; render();
requests[2].reject(new Error('offline')); await tick();
assert.ok(render().error);
render().retry(); render(); assert.equal(requests.length, 4);
options.dirty[options.path] = true; options.files[options.path] = 'unsaved'; render();
requests[3].resolve({ content: 'server copy' }); await tick();
assert.equal(options.files[options.path], 'unsaved');
options.path = 'modules/Test/image.png'; render(); assert.equal(requests.length, 4);
options.enabled = false; options.path = 'modules/Test/local.py'; render(); assert.equal(requests.length, 4);
console.log('Remote editor click loading, caching, cancellation, retry and dirty-file protection tests passed.');
})().catch(error => { console.error(error); process.exitCode = 1; });