175 lines
8.0 KiB
JavaScript
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.');
|