WIP: Vendoo 1.44.0 – UX Foundation & Guided Workspaces #29
@@ -0,0 +1,174 @@
|
||||
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.');
|
||||
Reference in New Issue
Block a user