Files
vendoo/tools/verify-command-palette-registry-1.44.0.mjs

175 lines
8.0 KiB
JavaScript

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 commandJs = read('public/command-platform.js');
const navigationJs = read('public/global-navigation-ux.js');
const navigationCss = read('public/global-navigation-ux.css');
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(commandJs, [
'window.VendooCommands',
'aliases',
'visible',
'enabled',
'disabledReason',
'evaluatePermission',
'resolveCommand',
'scoreCommand',
'scoreField',
'favoriteIndex',
'recentIndex',
'includeDisabled',
'includeHidden',
'command-blocked',
'canExecute',
], 'Command Registry Zustands- und Ranking-Vertrag fehlt');
includesAll(navigationJs, [
'getRegistry',
'window.VendooCommands',
'registry.list',
'registry.execute',
'registry.subscribe',
"source: 'command-palette'",
'aria-activedescendant',
'aria-disabled',
'aria-live',
'previousFocus',
'restoreFocus',
'isEditableTarget',
"event.key !== 'Tab'",
"event.key === 'Home'",
"event.key === 'End'",
'Favoriten',
'Zuletzt geöffnet',
'Nicht verfügbar',
], 'Registry-basierte Command Palette unvollständig');
includesAll(navigationCss, [
'.ux-global-search-status',
'.ux-global-search-section',
'.ux-global-search-result-icon',
'.ux-global-search-badge',
'.ux-global-search-result.is-disabled',
'[aria-disabled="true"]',
'@media(max-width:620px)',
'@media(prefers-reduced-motion:reduce)',
':focus-visible',
'var(--',
], 'Command Palette CSS-Vertrag fehlt');
includesAll(loader, [
'loadCommandPlatformAssets',
'loadGlobalNavigationAssets',
'loadCommandPlatformAssets();',
'loadGlobalNavigationAssets();',
], 'Asset-Ladereihenfolge fehlt');
expect(loader.indexOf('loadCommandPlatformAssets();') < loader.indexOf('loadGlobalNavigationAssets();'), 'Command Registry muss vor der Palette geladen werden');
expect(!navigationJs.includes('VIEW_LABELS'), 'Command Palette darf keine zweite statische View-Metadatenquelle führen');
expect(!navigationJs.includes('collectCommands'), 'Command Palette darf Commands nicht aus einer parallelen Liste sammeln');
expect(!/activateView\s*\(\s*command\.view/.test(navigationJs), 'Palette darf Registry-Ausführung nicht durch direkte Navigation umgehen');
expect(!/\.innerHTML\s*=/.test(commandJs + navigationJs), 'Command Platform und Palette dürfen innerHTML nicht setzen');
expect(!/\bfetch\s*\(/.test(commandJs + navigationJs), 'Command Platform und Palette dürfen keine Netzwerkaufrufe ausführen');
expect(!/\b(?:POST|PUT|PATCH|DELETE)\b/.test(commandJs + navigationJs), 'Command Platform und Palette dürfen keine HTTP-Schreiboperationen implementieren');
expect(!/confirm\s*\(/.test(commandJs + navigationJs), 'Command Palette darf keine kritischen Aktionen bestätigen');
expect(!/setInterval\s*\(/.test(commandJs + navigationJs), 'Command Platform und Palette dürfen kein Polling starten');
expect(!/Vendoo(?:ModuleManager|App|Api)\.(?:publish|delete|remove|install|enable|disable)/i.test(navigationJs), 'Palette darf produktive Aktionen nicht direkt ausführen');
function verifyRegistryRuntime() {
const storage = new Map();
const events = [];
let activeView = 'dashboard';
const navItems = new Map();
const makeNavItem = view => ({
dataset: { tab: view },
classList: { contains: name => name === 'active' ? activeView === view : false },
getAttribute: () => 'false',
closest: () => ({ setAttribute() {} }),
click: () => { activeView = view; },
});
for (const view of ['dashboard', 'today', 'generator', 'history', 'quality-center', 'publish', 'inventory', 'templates', 'media-library', 'image-factory', 'flux-studio', 'extensions', 'settings', 'admin', 'fees', 'trash']) navItems.set(view, makeNavItem(view));
const windowObject = {
dispatchEvent(event) { events.push(event); return true; },
addEventListener() {},
VendooPermissions: undefined,
};
const documentObject = {
documentElement: { dataset: {} },
querySelector(selector) {
if (selector === '.sidebar-nav .nav-item.active[data-tab]') return navItems.get(activeView);
if (selector === '.tab-content.active' || selector === '.sidebar-nav') return null;
const match = selector.match(/^\.sidebar-nav \.nav-item\[data-tab="(.+)"\]$/);
return match ? navItems.get(match[1]) || null : null;
},
getElementById() { return null; },
createElement() { throw new Error('DOM-Erzeugung ist im Registry-Runtime-Test nicht erwartet.'); },
addEventListener() {},
};
const context = vm.createContext({
window: windowObject,
document: documentObject,
localStorage: {
getItem(key) { return storage.get(key) ?? null; },
setItem(key, value) { storage.set(key, String(value)); },
},
CustomEvent: class CustomEvent { constructor(type, init = {}) { this.type = type; this.detail = init.detail; } },
MutationObserver: class MutationObserver { observe() {} disconnect() {} },
CSS: { escape: value => String(value) },
history: { back() {}, forward() {} },
console,
});
vm.runInContext(commandJs, context, { filename: 'public/command-platform.js' });
const commands = windowObject.VendooCommands;
expect(commands && typeof commands.list === 'function', 'Runtime: VendooCommands.list fehlt');
expect(commands?.list({ query: 'home' })[0]?.id === 'view:dashboard', 'Runtime: Aliassuche findet Dashboard nicht');
commands.register({
id: 'test:disabled',
title: 'Spezialprüfung',
description: 'Prüft einen externen Dienst',
aliases: ['Schnelltest'],
keywords: ['Diagnose'],
enabled: false,
disabledReason: 'Dienst nicht verbunden.',
execute: () => true,
});
const disabled = commands.list({ query: 'Schnelltest' })[0];
expect(disabled?.id === 'test:disabled', 'Runtime: Aliasranking liefert falschen Command');
expect(disabled?.enabled === false && disabled?.disabledReason === 'Dienst nicht verbunden.', 'Runtime: deaktivierter Zustand oder Begründung fehlt');
expect(commands.execute('test:disabled') === false, 'Runtime: deaktivierter Command wurde ausgeführt');
expect(events.some(event => event.type === 'vendoo:command-blocked' && event.detail?.commandId === 'test:disabled'), 'Runtime: command-blocked Event fehlt');
commands.register({ id: 'test:permission', title: 'Geheimer Bereich', permission: 'admin', execute: () => true });
expect(!commands.list({ query: 'Geheimer' }).some(command => command.id === 'test:permission'), 'Runtime: Permission ohne Resolver muss deny-by-default sein');
windowObject.VendooPermissions = { can: permission => permission === 'admin' };
expect(commands.list({ query: 'Geheimer' }).some(command => command.id === 'test:permission'), 'Runtime: Permission-Resolver wird nicht ausgewertet');
commands.toggleFavorite('test:disabled');
expect(commands.list()[0]?.id === 'test:disabled', 'Runtime: Favorit wird ohne Suchbegriff nicht priorisiert');
expect(commands.execute('view:history', { source: 'runtime-test' }) === true, 'Runtime: sichtbarer Navigationscommand konnte nicht ausgeführt werden');
expect(windowObject.VendooNavigation.recents[0] === 'history', 'Runtime: erfolgreicher Navigationscommand wird nicht als Recent erfasst');
}
verifyRegistryRuntime();
expect(pkg.scripts?.['verify:command-palette-registry'] === 'node tools/verify-command-palette-registry-1.44.0.mjs', 'verify:command-palette-registry fehlt');
expect(pkg.scripts?.['verify:all']?.includes('npm run verify:command-palette-registry'), 'verify:all enthält Command-Palette-Registry-Gate nicht');
if (failures.length) {
console.error('Command Palette Registry Gate fehlgeschlagen:');
failures.forEach(failure => console.error(`- ${failure}`));
process.exit(1);
}
console.log('Command Palette Registry Gate bestanden.');