WIP: Vendoo 1.44.0 – UX Foundation & Guided Workspaces #29
@@ -0,0 +1,184 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import vm from 'node:vm';
|
||||
|
||||
const root = process.cwd();
|
||||
const read = relative => fs.readFileSync(path.join(root, relative), 'utf8');
|
||||
const adapters = read('public/safe-action-adapters.js');
|
||||
const loader = read('public/module-extension-workspace.js');
|
||||
const pkg = JSON.parse(read('package.json'));
|
||||
const failures = [];
|
||||
const expect = (condition, message) => { if (!condition) failures.push(message); };
|
||||
const includesAll = (source, values, label) => values.forEach(value => expect(source.includes(value), `${label}: ${value}`));
|
||||
|
||||
includesAll(adapters, [
|
||||
'uxSafeActionAdaptersReady',
|
||||
'uxSafeActionAdaptersRegistered',
|
||||
'window.VendooActions',
|
||||
'actions.registerMany',
|
||||
"surfaces: ['command-palette']",
|
||||
"danger: 'none'",
|
||||
'vendoo:actions-ready',
|
||||
'vendoo:safe-action-adapters-ready',
|
||||
'workspace:modules:refresh',
|
||||
'workspace:modules:filter-all',
|
||||
'workspace:modules:filter-problems',
|
||||
'workspace:articles:open-current',
|
||||
'workspace:articles:view-cards',
|
||||
'workspace:articles:view-list',
|
||||
'workspace:media:open-current-preview',
|
||||
'workspace:media:density-comfortable',
|
||||
'workspace:media:reset-filters',
|
||||
'#module-manager-refresh',
|
||||
'[data-article-view-choice="cards"]',
|
||||
'[data-article-view-choice="list"]',
|
||||
'[data-media-density-option="comfortable"]',
|
||||
'.ux-media-filter-reset',
|
||||
'focusin',
|
||||
'pointerdown',
|
||||
], 'Safe Action Adapter-Vertrag fehlt');
|
||||
|
||||
includesAll(loader, [
|
||||
'loadCommandPlatformAssets',
|
||||
'loadActionRegistryAssets',
|
||||
'loadGlobalNavigationAssets',
|
||||
'loadSafeActionAdapters',
|
||||
'safe-action-adapters.js?v=1.44.0',
|
||||
'data-ux-safe-action-adapters',
|
||||
'loadSafeActionAdapters();',
|
||||
], 'Safe-Action-Loader fehlt');
|
||||
expect(loader.indexOf('loadActionRegistryAssets();') < loader.indexOf('loadSafeActionAdapters();'), 'Action Registry muss vor Safe Action Adapters geladen werden');
|
||||
expect(loader.indexOf('loadGlobalNavigationAssets();') < loader.indexOf('loadSafeActionAdapters();'), 'Globale Navigation muss vor Safe Action Adapters geladen werden');
|
||||
|
||||
expect(pkg.scripts?.['verify:safe-action-adapters'] === 'node tools/verify-safe-action-adapters-1.44.0.mjs', 'verify:safe-action-adapters fehlt');
|
||||
expect(pkg.scripts?.['verify:all']?.includes('npm run verify:safe-action-adapters'), 'verify:all enthält Safe-Action-Gate nicht');
|
||||
|
||||
expect(!/\.innerHTML\s*=/.test(adapters), 'Safe Action Adapters dürfen innerHTML nicht setzen');
|
||||
expect(!/\bfetch\s*\(/.test(adapters), 'Safe Action Adapters dürfen keine Netzwerkaufrufe ausführen');
|
||||
expect(!/\bconfirm\s*\(/.test(adapters), 'Safe Action Adapters dürfen window.confirm nicht verwenden');
|
||||
expect(!/setInterval\s*\(/.test(adapters), 'Safe Action Adapters dürfen kein Polling starten');
|
||||
expect(!/Vendoo(?:ModuleManager|App|Api)\.(?:publish|delete|remove|install|enable|disable|start|stop)/i.test(adapters), 'Safe Action Adapters dürfen keine produktive Fachlogik direkt aufrufen');
|
||||
expect(!/danger:\s*['"](?:caution|destructive)['"]/.test(adapters), 'Dieser Slice darf keine vorsichtigen oder destruktiven Actions registrieren');
|
||||
|
||||
function element({ id = '', selector = '', parent = null } = {}) {
|
||||
return {
|
||||
id,
|
||||
selector,
|
||||
hidden: false,
|
||||
disabled: false,
|
||||
isConnected: true,
|
||||
clicks: 0,
|
||||
classList: { contains: () => false },
|
||||
getAttribute: () => null,
|
||||
click() { this.clicks += 1; },
|
||||
closest(query) {
|
||||
if (query === '#history-list .history-row' && parent?.kind === 'article-row') return parent;
|
||||
if (query === '[data-media-preview]' && this.selector === '[data-media-preview]') return this;
|
||||
if (query === '.history-row' && parent?.kind === 'article-row') return parent;
|
||||
if (query === '.media-card' && parent?.kind === 'media-card') return parent;
|
||||
return null;
|
||||
},
|
||||
querySelector(query) { return parent?.querySelector?.(query) || null; },
|
||||
};
|
||||
}
|
||||
|
||||
function verifyRuntime() {
|
||||
const registered = [];
|
||||
const documentListeners = new Map();
|
||||
const windowListeners = new Map();
|
||||
const events = [];
|
||||
const rootElement = { dataset: {} };
|
||||
|
||||
const moduleSection = element({ id: 'admin-modules-panel' });
|
||||
const historySection = element({ id: 'history' });
|
||||
const mediaSection = element({ id: 'media-library' });
|
||||
const refreshButton = element({ selector: '#module-manager-refresh' });
|
||||
const moduleAll = element({ selector: '[data-ux-module-group="modules"][data-ux-module-filter="all"]' });
|
||||
const moduleProblems = element({ selector: '[data-ux-module-group="modules"][data-ux-module-filter="problems"]' });
|
||||
const cardsButton = element({ selector: '[data-article-view-choice="cards"]' });
|
||||
const listButton = element({ selector: '[data-article-view-choice="list"]' });
|
||||
const densityButton = element({ selector: '[data-media-density-option="comfortable"]' });
|
||||
const mediaReset = element({ selector: '.ux-media-filter-reset' });
|
||||
|
||||
const articleTitle = element({ selector: '.history-article-title' });
|
||||
const articleRow = {
|
||||
kind: 'article-row',
|
||||
hidden: false,
|
||||
disabled: false,
|
||||
isConnected: true,
|
||||
getAttribute: () => null,
|
||||
querySelector: query => query === '.history-article-title' ? articleTitle : null,
|
||||
};
|
||||
const articleTarget = element({ parent: articleRow });
|
||||
|
||||
const mediaPreview = element({ selector: '[data-media-preview]' });
|
||||
const selectorMap = new Map([
|
||||
['#module-manager-refresh', refreshButton],
|
||||
['[data-ux-module-group="modules"][data-ux-module-filter="all"]', moduleAll],
|
||||
['[data-ux-module-group="modules"][data-ux-module-filter="problems"]', moduleProblems],
|
||||
['[data-article-view-choice="cards"]', cardsButton],
|
||||
['[data-article-view-choice="list"]', listButton],
|
||||
['[data-media-density-option="comfortable"]', densityButton],
|
||||
['.ux-media-filter-reset', mediaReset],
|
||||
]);
|
||||
const sectionMap = new Map([
|
||||
['admin-modules-panel', moduleSection],
|
||||
['history', historySection],
|
||||
['media-library', mediaSection],
|
||||
]);
|
||||
|
||||
const documentObject = {
|
||||
documentElement: rootElement,
|
||||
addEventListener(type, listener) { documentListeners.set(type, listener); },
|
||||
getElementById(id) { return sectionMap.get(id) || null; },
|
||||
querySelector(selector) { return selectorMap.get(selector) || null; },
|
||||
};
|
||||
const windowObject = {
|
||||
VendooActions: {
|
||||
registerMany(actions) { registered.push(...actions); },
|
||||
refresh() {},
|
||||
},
|
||||
getComputedStyle: () => ({ display: 'block', visibility: 'visible' }),
|
||||
addEventListener(type, listener) { windowListeners.set(type, listener); },
|
||||
dispatchEvent(event) { events.push(event); return true; },
|
||||
};
|
||||
|
||||
const context = vm.createContext({
|
||||
window: windowObject,
|
||||
document: documentObject,
|
||||
CustomEvent: class CustomEvent { constructor(type, init = {}) { this.type = type; this.detail = init.detail; } },
|
||||
console,
|
||||
});
|
||||
vm.runInContext(adapters, context, { filename: 'public/safe-action-adapters.js' });
|
||||
|
||||
expect(registered.length === 9, `Runtime: erwartet 9 Adapter-Actions, erhalten ${registered.length}`);
|
||||
expect(new Set(registered.map(action => action.id)).size === registered.length, 'Runtime: Action-IDs sind nicht eindeutig');
|
||||
expect(registered.every(action => action.danger === 'none'), 'Runtime: nicht sichere Gefahrstufe registriert');
|
||||
expect(registered.every(action => action.surfaces?.includes('command-palette')), 'Runtime: Command-Palette-Surface fehlt');
|
||||
|
||||
const byId = id => registered.find(action => action.id === id);
|
||||
expect(byId('workspace:modules:refresh')?.handler() === true && refreshButton.clicks === 1, 'Runtime: Modul-Refresh delegiert nicht an bestehenden Button');
|
||||
expect(byId('workspace:modules:filter-problems')?.handler() === true && moduleProblems.clicks === 1, 'Runtime: Problemfilter delegiert nicht an bestehenden Button');
|
||||
expect(byId('workspace:articles:view-list')?.handler() === true && listButton.clicks === 1, 'Runtime: Listenansicht delegiert nicht an bestehenden Button');
|
||||
expect(byId('workspace:media:density-comfortable')?.handler() === true && densityButton.clicks === 1, 'Runtime: Galeriedichte delegiert nicht an bestehenden Button');
|
||||
expect(byId('workspace:media:reset-filters')?.handler() === true && mediaReset.clicks === 1, 'Runtime: Medienfilter-Reset delegiert nicht an bestehenden Button');
|
||||
|
||||
documentListeners.get('focusin')?.({ target: articleTarget });
|
||||
expect(byId('workspace:articles:open-current')?.enabled() === true, 'Runtime: fokussierter Artikel wird nicht als ausführbar erkannt');
|
||||
expect(byId('workspace:articles:open-current')?.handler() === true && articleTitle.clicks === 1, 'Runtime: Artikel öffnen delegiert nicht an bestehenden Titel-Handler');
|
||||
|
||||
documentListeners.get('pointerdown')?.({ target: mediaPreview });
|
||||
expect(byId('workspace:media:open-current-preview')?.enabled() === true, 'Runtime: fokussierte Medienvorschau wird nicht als ausführbar erkannt');
|
||||
expect(byId('workspace:media:open-current-preview')?.handler() === true && mediaPreview.clicks === 1, 'Runtime: Medienvorschau delegiert nicht an bestehenden Preview-Handler');
|
||||
expect(events.some(event => event.type === 'vendoo:safe-action-adapters-ready' && event.detail?.count === 9), 'Runtime: Ready-Event fehlt');
|
||||
}
|
||||
|
||||
verifyRuntime();
|
||||
|
||||
if (failures.length) {
|
||||
console.error('Safe Action Adapter Gate fehlgeschlagen:');
|
||||
failures.forEach(failure => console.error(`- ${failure}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Safe Action Adapter Gate bestanden.');
|
||||
Reference in New Issue
Block a user