6e790832f5
Template tests / tests (push) Failing after 21s
- Editor topbar reworked: Back | Capture ▾ (full screen/window/region/ delay/paste/import/session) | Save | Export | Share (.sfgz) | More ▾ (rename, guide placeholders, backups, linked guide, shortcuts, settings) - New dialogs: backups & snapshots (undoable restore), guide/global placeholder editor, keyboard-shortcuts reference, template manager (rename/duplicate/delete/share/import .sfglt) - Export dialog: editable per-format options generated from exporter defaults, save-as-template, preview opens the file in the default viewer and keeps the dialog open for tweaking - export:defaults IPC + preload entry - CSS for blocks panel, focused-view sliders, export options, rows - ipc-surface test: every preload channel has a main handler; renderer api.*/dialogs.* usage stays within the exposed surface (60 tests) - CHANGELOG/README updated; prompt2.md checklist fully ticked Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
78 lines
3.2 KiB
JavaScript
78 lines
3.2 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
/**
|
|
* Wiring guard: every IPC channel the renderer can invoke through the
|
|
* preload bridge must have a handler registered in the main process, and
|
|
* renderer code must only call APIs the bridge actually exposes. This
|
|
* compares extracted channel/identifier sets — it exercises the real
|
|
* contract between the three layers rather than matching arbitrary text.
|
|
*/
|
|
|
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|
const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
|
|
|
function invokeChannels(src) {
|
|
return new Set([...src.matchAll(/invoke\('([^']+)'\)/g)].map((m) => m[1]));
|
|
}
|
|
|
|
function handledChannels(src) {
|
|
return new Set([...src.matchAll(/\bh\('([^']+)'/g)].map((m) => m[1]));
|
|
}
|
|
|
|
test('every preload invoke channel has a main-process handler', () => {
|
|
const preload = invokeChannels(read('app/preload.js'));
|
|
const handlers = handledChannels(read('app/main.js'));
|
|
assert.ok(preload.size >= 30, `expected a substantial API surface, got ${preload.size}`);
|
|
const missing = [...preload].filter((ch) => !handlers.has(ch));
|
|
assert.deepEqual(missing, [], `preload channels without handlers: ${missing.join(', ')}`);
|
|
});
|
|
|
|
test('renderer api.* usage stays within the preload surface', () => {
|
|
// Build the exposed api shape from preload.js: top-level groups and members.
|
|
const preloadSrc = read('app/preload.js');
|
|
const apiBody = preloadSrc.slice(preloadSrc.indexOf('const api = {'));
|
|
const groups = new Map();
|
|
let currentGroup = null;
|
|
for (const line of apiBody.split('\n')) {
|
|
const g = /^ (\w+): \{/.exec(line);
|
|
if (g) { currentGroup = g[1]; groups.set(currentGroup, new Set()); continue; }
|
|
const member = /^ (\w+):/.exec(line);
|
|
if (member && currentGroup) groups.get(currentGroup).add(member[1]);
|
|
if (/^ \},/.test(line)) currentGroup = null;
|
|
}
|
|
assert.ok(groups.size >= 10, 'preload should expose multiple API groups');
|
|
|
|
// Every api.<group>.<member>( call in renderer code must exist.
|
|
const offenders = [];
|
|
for (const file of ['app.js', 'editor.js', 'dialogs.js']) {
|
|
const src = read(`app/renderer/${file}`);
|
|
for (const m of src.matchAll(/\bapi\.(\w+)\.(\w+)\(/g)) {
|
|
const [, group, member] = m;
|
|
if (!groups.has(group) || !groups.get(group).has(member)) {
|
|
offenders.push(`${file}: api.${group}.${member}`);
|
|
}
|
|
}
|
|
}
|
|
assert.deepEqual(offenders, [], `renderer calls missing from preload: ${offenders.join(', ')}`);
|
|
});
|
|
|
|
test('renderer dialogs.* usage matches the StepForgeDialogs export', () => {
|
|
const dialogsSrc = read('app/renderer/dialogs.js');
|
|
const exportBlock = /window\.StepForgeDialogs = \{([\s\S]*?)\};/.exec(dialogsSrc)[1];
|
|
const exported = new Set([...exportBlock.matchAll(/(\w+),/g)].map((m) => m[1]));
|
|
|
|
const offenders = [];
|
|
for (const file of ['app.js', 'editor.js']) {
|
|
const src = read(`app/renderer/${file}`);
|
|
for (const m of src.matchAll(/\bdialogs\.(\w+)\(/g)) {
|
|
if (!exported.has(m[1])) offenders.push(`${file}: dialogs.${m[1]}`);
|
|
}
|
|
}
|
|
assert.deepEqual(offenders, [], `dialog calls missing from export: ${offenders.join(', ')}`);
|
|
});
|