/** * Wails Mock Bridge — эмулирует window['go']['api']['App'] для тестового окружения. * * Каждый метод возвращает Promise с данными, совместимыми с Wails-контрактом. * Состояние мутабельно — тесты могут менять его между сценариями. */ (function () { if (window.__wailsMockReady) return; // ── Mutable state ────────────────────────────────────────────────── var pluginStates = { 'verstak.platform-test': { status: 'loaded', enabled: true, manifest: { schemaVersion: 1, id: 'verstak.platform-test', name: 'Platform Test', version: '0.1.0', apiVersion: '0.1.0', description: 'Runtime test plugin for verifying the Verstak platform.', source: 'official', icon: '🧪', provides: ['verstak/platform-test/v1', 'verstak/diagnostics/v1'], requires: ['verstak/core/plugin-manager/v1', 'verstak/core/capability-registry/v1'], optionalRequires: ['verstak/core/vault/v1', 'verstak/core/sync/v1', 'verstak/core/files/v1', 'verstak/core/workbench/v1'], permissions: ['vault.read', 'events.publish', 'events.subscribe', 'ui.register', 'commands.register', 'storage.namespace', 'files.read', 'files.write', 'files.delete', 'workbench.open'], frontend: { entry: 'frontend/dist/index.js' }, contributes: { views: [ { id: 'verstak.platform-test.diagnostics', title: 'Platform Diagnostics', icon: '🧪', component: 'DiagnosticsPanel' } ], commands: [ { id: 'verstak.platform-test.run-tests', title: 'Run Platform Tests', handler: 'runAllTests' }, { id: 'verstak.platform-test.show-version', title: 'Show Version Info', handler: 'showVersion' } ], sidebarItems: [ { id: 'verstak.platform-test.sidebar', title: 'Platform Test', icon: '🧪', view: 'verstak.platform-test.diagnostics', position: 100 } ], statusBarItems: [ { id: 'verstak.platform-test.status', label: '🧪 All Tests Pass', position: 'right', handler: 'openDiagnostics' } ], settingsPanels: [ { id: 'verstak.platform-test.settings', title: 'Platform Test Settings', icon: '🧪', component: 'PlatformTestSettings' } ], openProviders: [ { id: 'verstak.platform-test.markdown-diagnostic', title: 'Platform Test Markdown Diagnostic', priority: 100, component: 'MarkdownDiagnosticProvider', supports: [ { kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['generic-markdown', 'notes-markdown'] }, { kind: 'vault-file', extensions: ['.txt', '.log'], mime: ['text/plain'], contexts: ['generic-text'] } ] } ] } }, rootPath: '/tmp/verstak-test/plugins/platform-test', error: '' } }; var vaultStatus = { status: 'open', path: '/tmp/verstak-test/vault', vaultId: 'test-vault-001' }; var vaultPluginState = { enabledPlugins: ['verstak.platform-test'], disabledPlugins: [], desiredPlugins: [{ id: 'verstak.platform-test', version: '0.1.0', source: 'official' }] }; var appSettings = { currentVaultPath: '/tmp/verstak-test/vault', recentVaults: [] }; var workbenchPreferences = {}; var openedResources = []; var pluginSettings = { 'verstak.platform-test': { savedText: 'initial value' } }; var vaultFiles = makeDefaultVaultFiles(); var workspaceTree = makeDefaultWorkspaceTree(); var reloadResponseMode = 'tuple'; // ── Helpers ──────────────────────────────────────────────────────── function makeDefaultWorkspaceTree() { return { status: 'initialized', currentNodeId: 'case-alpha', nodes: [ { id: 'space-main', parentId: '', type: 'space', title: 'Main Space', status: 'active', order: 1 }, { id: 'case-alpha', parentId: 'space-main', type: 'case', title: 'Alpha Case', status: 'active', order: 1 }, { id: 'case-beta', parentId: 'space-main', type: 'case', title: 'Beta Case', status: 'active', order: 2 } ] }; } function cloneWorkspaceTree() { return { status: workspaceTree.status, currentNodeId: workspaceTree.currentNodeId, nodes: workspaceTree.nodes.map(function (n) { return Object.assign({}, n); }) }; } function makeDefaultVaultFiles() { return { '': { type: 'folder', modifiedAt: new Date().toISOString() } }; } function normalizeVaultPath(relativePath, allowRoot) { var p = String(relativePath || ''); if (p.indexOf('\x00') !== -1) return { error: 'invalid-path: null-byte' }; if (p.indexOf('\\') !== -1) return { error: 'invalid-path: backslash not allowed' }; if (p.indexOf('./') === 0) p = p.slice(2); if (!allowRoot && !p) return { error: 'invalid-path: empty path' }; if (p.charAt(0) === '/' || /^[A-Za-z]:/.test(p)) return { error: 'invalid-path: absolute path rejected' }; var parts = p.split('/').filter(Boolean); if (parts.indexOf('..') !== -1) return { error: 'invalid-path: path-traversal' }; if (parts[0] && parts[0].toLowerCase() === '.verstak') return { error: 'reserved-path: .verstak is internal' }; return { path: parts.join('/') }; } function parentPath(path) { var idx = path.lastIndexOf('/'); return idx === -1 ? '' : path.slice(0, idx); } function baseName(path) { var idx = path.lastIndexOf('/'); return idx === -1 ? path : path.slice(idx + 1); } function fileEntry(path, node) { var name = path ? baseName(path) : ''; var ext = ''; var dot = name.lastIndexOf('.'); if (dot > 0) ext = name.slice(dot + 1); return { name: name, relativePath: path, type: node.type, size: node.type === 'file' ? (node.content || '').length : 0, modifiedAt: node.modifiedAt || new Date().toISOString(), extension: ext, isHidden: name.charAt(0) === '.', isReserved: false, canRead: node.type === 'file' || node.type === 'folder', canWrite: node.type === 'file' || node.type === 'folder' }; } function requirePluginPermission(pluginId, permission) { var s = pluginStates[pluginId]; if (!s || !s.enabled || (s.status !== 'loaded' && s.status !== 'degraded')) { return 'plugin not enabled and loaded'; } if (!s.manifest.permissions || s.manifest.permissions.indexOf(permission) === -1) { return 'plugin lacks required permission ' + permission; } if (vaultStatus.status !== 'open') return 'vault-not-open'; return ''; } function makePlugin(id) { var s = pluginStates[id]; if (!s) return null; return { manifest: s.manifest, status: s.status, enabled: s.enabled, rootPath: s.rootPath, error: s.error }; } function allPlugins() { return Object.keys(pluginStates).map(makePlugin).filter(Boolean); } function allCapabilities() { var caps = []; caps.push({ name: 'verstak/core/plugin-manager/v1', description: 'Plugin management', pluginId: 'verstak-desktop', status: 'stable' }); caps.push({ name: 'verstak/core/capability-registry/v1', description: 'Capability registry', pluginId: 'verstak-desktop', status: 'stable' }); caps.push({ name: 'verstak/core/files/v1', description: 'Files API', pluginId: 'verstak-desktop', status: 'stable' }); caps.push({ name: 'verstak/core/workbench/v1', description: 'Workbench routing', pluginId: 'verstak-desktop', status: 'stable' }); for (var id in pluginStates) { var s = pluginStates[id]; if (s.status === 'loaded' && s.enabled && s.manifest && s.manifest.provides) { s.manifest.provides.forEach(function (p) { caps.push({ name: p, description: '', pluginId: id, status: 'stable' }); }); } } return caps; } function allPermissions() { return [ { name: 'vault.read', description: 'Read vault data', dangerous: false }, { name: 'events.publish', description: 'Publish events', dangerous: false }, { name: 'events.subscribe', description: 'Subscribe to events', dangerous: false }, { name: 'ui.register', description: 'Register UI contributions', dangerous: false }, { name: 'commands.register', description: 'Register commands', dangerous: false }, { name: 'storage.namespace', description: 'Access plugin storage', dangerous: false }, { name: 'files.read', description: 'Read vault files', dangerous: false }, { name: 'files.write', description: 'Write vault files', dangerous: true }, { name: 'files.delete', description: 'Trash vault files', dangerous: true }, { name: 'workbench.open', description: 'Request Workbench open/edit routing', dangerous: false } ]; } function allContributions() { var views = [], commands = [], sidebarItems = [], statusBarItems = [], settingsPanels = [], openProviders = []; for (var id in pluginStates) { var s = pluginStates[id]; var c = (s.manifest && s.manifest.contributes) || {}; if (c.views) c.views.forEach(function (v) { views.push(Object.assign({}, v, { pluginId: id })); }); if (c.commands) c.commands.forEach(function (cmd) { commands.push(Object.assign({}, cmd, { pluginId: id })); }); if (c.sidebarItems) c.sidebarItems.forEach(function (sb) { sidebarItems.push(Object.assign({}, sb, { pluginId: id })); }); if (c.statusBarItems) c.statusBarItems.forEach(function (st) { statusBarItems.push(Object.assign({}, st, { pluginId: id })); }); if (c.settingsPanels) c.settingsPanels.forEach(function (sp) { settingsPanels.push(Object.assign({}, sp, { pluginId: id })); }); if (c.openProviders) c.openProviders.forEach(function (op) { openProviders.push(Object.assign({}, op, { pluginId: id })); }); } return { views: views, commands: commands, sidebarItems: sidebarItems, statusBarItems: statusBarItems, settingsPanels: settingsPanels, openProviders: openProviders }; } function requestExtension(request) { if (request && request.extension) { var explicit = String(request.extension).toLowerCase(); return explicit.charAt(0) === '.' ? explicit : '.' + explicit; } var p = String((request && request.path) || '').toLowerCase(); var slash = p.lastIndexOf('/'); var name = slash === -1 ? p : p.slice(slash + 1); var dot = name.lastIndexOf('.'); return dot > 0 ? name.slice(dot) : ''; } function requestContextName(request) { var ctx = (request && request.context) || {}; if (ctx.notesMode || ctx.isInsideNotesFolder || ctx.sourceView === 'notes') return 'notes-markdown'; var ext = requestExtension(request); if (ext === '.md' || ext === '.markdown') return 'generic-markdown'; return 'generic-text'; } function providerSupports(provider, request) { var ext = requestExtension(request); var contextName = requestContextName(request); return (provider.supports || []).some(function (support) { if (support.kind && support.kind !== request.kind) return false; if (support.extensions && support.extensions.length && support.extensions.map(function (e) { return String(e).toLowerCase(); }).indexOf(ext) === -1) return false; if (support.contexts && support.contexts.length && support.contexts.indexOf(contextName) === -1) return false; return true; }); } function selectOpenProvider(request) { var providers = allContributions().openProviders.filter(function (provider) { var s = pluginStates[provider.pluginId]; return s && s.enabled && (s.status === 'loaded' || s.status === 'degraded') && providerSupports(provider, request); }); providers.sort(function (a, b) { var byPriority = (b.priority || 0) - (a.priority || 0); if (byPriority) return byPriority; return String(a.id).localeCompare(String(b.id)); }); return providers[0] || null; } function openWorkbenchResource(pluginId, request, forcedMode) { var s = pluginStates[pluginId]; if (!s || !s.enabled || (s.status !== 'loaded' && s.status !== 'degraded')) { return Promise.resolve([{}, 'plugin not enabled and loaded']); } if (!s.manifest.permissions || s.manifest.permissions.indexOf('workbench.open') === -1) { return Promise.resolve([{}, 'plugin lacks required permission workbench.open']); } var normalized = Object.assign({}, request || {}); normalized.kind = normalized.kind || 'vault-file'; normalized.mode = forcedMode || normalized.mode || 'view'; normalized.extension = requestExtension(normalized); normalized.context = Object.assign({}, normalized.context || {}, { sourcePluginId: pluginId }); var provider = selectOpenProvider(normalized); if (!provider) { return Promise.resolve([{ status: 'no-provider', request: normalized, message: 'no open provider for resource' }, '']); } var result = { status: 'opened', providerId: provider.id, providerPluginId: provider.pluginId, providerComponent: provider.component, request: normalized }; openedResources.push(Object.assign({ id: provider.id + ':' + openedResources.length, openedAt: new Date().toISOString() }, result)); return Promise.resolve([result, '']); } function platformTestBundle() { return [ "(function(){", "var DiagnosticsPanel={", "mount:function(containerEl,props,api){", "containerEl.innerHTML='';", "containerEl.__ptCleanup=[];", "function track(fn){if(typeof fn==='function')containerEl.__ptCleanup.push(fn);}", "var root=document.createElement('div');", "root.className='pt-root';", "var title=document.createElement('h2');", "title.className='pt-plugin-name';", "title.textContent='Platform Diagnostics';", "var pluginId=document.createElement('p');", "pluginId.className='pt-plugin-id';", "pluginId.textContent=api.pluginId;", "var status=document.createElement('div');", "status.className='pt-badge pt-badge-success';", "status.textContent='Frontend Bundle Loaded';", "var saved=document.createElement('div');", "saved.className='pt-card pt-saved-setting';", "saved.textContent='Saved setting: loading...';", "var cap=document.createElement('div');", "cap.className='pt-capability-result';", "cap.textContent='Capabilities: loading...';", "api.capabilities.list().then(function(caps){cap.textContent='Capabilities: '+caps.length+' available';});", "api.settings.read('savedText').then(function(value){saved.textContent='Saved setting: '+(value||'');});", "var input=document.createElement('input');", "input.className='pt-setting-input';", "input.setAttribute('aria-label','Saved setting');", "input.value='changed value';", "var button=document.createElement('button');", "button.className='btn btn-primary pt-save-setting';", "button.textContent='Save Setting';", "button.addEventListener('click',function(){api.settings.write('savedText',input.value).then(function(){saved.textContent='Saved setting: '+input.value;});});", "api.capabilities.has('verstak/platform-test/v1').then(function(ok){status.textContent='Frontend Bundle Loaded | capability '+(ok?'available':'missing');});", "var command=document.createElement('div');", "command.className='pt-command-result';", "command.textContent='Command: registering...';", "api.commands.register('verstak.platform-test.show-version',function(){return {version:'0.1.0',source:'bundled-frontend'};}).then(function(unregister){track(unregister);return api.commands.execute('verstak.platform-test.show-version',{});}).then(function(result){status.setAttribute('data-command-status',result.status||'');command.textContent='Command: '+result.status+' '+result.result.version+' from '+result.result.source;});", "var eventResult=document.createElement('div');", "eventResult.className='pt-event-result';", "eventResult.textContent='Event: subscribing...';", "api.events.subscribe('verstak.platform-test.echo',function(event){eventResult.textContent='Event: received '+event.payload.message;eventResult.setAttribute('data-event-status','received');}).then(function(unsubscribe){track(unsubscribe);return api.events.publish('verstak.platform-test.echo',{message:'hello-event'});});", "var filesResult=document.createElement('div');", "filesResult.className='pt-files-result';", "filesResult.textContent='Files: running...';", "var filesError=document.createElement('div');", "filesError.className='pt-files-error-result';", "filesError.textContent='Files error path: checking...';", "var workbenchResult=document.createElement('div');", "workbenchResult.className='pt-workbench-result';", "workbenchResult.textContent='Workbench: ready';", "function makeWorkbenchButton(cls,label,request){var b=document.createElement('button');b.className='btn btn-primary '+cls;b.textContent=label;b.addEventListener('click',function(){workbenchResult.textContent='Workbench: opening...';api.workbench.editResource(request).then(function(result){workbenchResult.textContent='Workbench: opened '+result.request.path+' with '+(result.providerId||'no-provider');workbenchResult.setAttribute('data-workbench-status',result.status==='opened'?'ok':result.status);}).catch(function(err){workbenchResult.textContent='Workbench error: '+(err&&err.message?err.message:String(err));workbenchResult.setAttribute('data-workbench-status','error');});});return b;}", "var textWorkbenchButton=makeWorkbenchButton('pt-open-workbench-text','Open Text Diagnostic',{kind:'vault-file',path:'Docs/todo.txt',extension:'.txt',mime:'text/plain',context:{sourceView:'files'}});", "var markdownWorkbenchButton=makeWorkbenchButton('pt-open-workbench-markdown','Open Markdown Diagnostic',{kind:'vault-file',path:'Docs/readme.md',extension:'.md',context:{sourceView:'files'}});", "var notesWorkbenchButton=makeWorkbenchButton('pt-open-workbench-notes','Open Notes Diagnostic',{kind:'vault-file',path:'Notes/Overview.md',extension:'.md',context:{sourceView:'notes',isInsideNotesFolder:true,notesMode:true}});", "api.files.createFolder('PlatformTest').catch(function(e){if(String(e).indexOf('conflict')===-1)throw e;}).then(function(){return api.files.writeText('PlatformTest/files-api.txt','hello files',{createIfMissing:true,overwrite:true});}).then(function(){return api.files.readText('PlatformTest/files-api.txt');}).then(function(text){if(text!=='hello files')throw new Error('read mismatch');return api.files.list('PlatformTest');}).then(function(entries){if(!entries.some(function(e){return e.relativePath==='PlatformTest/files-api.txt';}))throw new Error('list missing file');return api.files.move('PlatformTest/files-api.txt','PlatformTest/files-api-moved.txt',{overwrite:true});}).then(function(){return api.files.trash('PlatformTest/files-api-moved.txt');}).then(function(){filesResult.textContent='Files: wrote/read/listed/moved/trashed';filesResult.setAttribute('data-files-status','ok');}).catch(function(err){filesResult.textContent='Files error: '+(err&&err.message?err.message:String(err));filesResult.setAttribute('data-files-status','error');});", "api.files.readText('.verstak/vault.json').then(function(){filesError.textContent='Files error path: unexpectedly allowed';filesError.setAttribute('data-files-error-status','error');}).catch(function(err){var message=err&&err.message?err.message:String(err);if(message.indexOf('reserved-path')===-1&&message.indexOf('.verstak')===-1){filesError.textContent='Files error path: wrong error '+message;filesError.setAttribute('data-files-error-status','error');return;}filesError.textContent='Files error path: rejected reserved-path';filesError.setAttribute('data-files-error-status','expected');});", "root.appendChild(title);", "root.appendChild(pluginId);", "root.appendChild(status);", "root.appendChild(saved);", "root.appendChild(input);", "root.appendChild(button);", "root.appendChild(cap);", "root.appendChild(command);", "root.appendChild(eventResult);", "root.appendChild(filesResult);", "root.appendChild(filesError);", "root.appendChild(textWorkbenchButton);", "root.appendChild(markdownWorkbenchButton);", "root.appendChild(notesWorkbenchButton);", "root.appendChild(workbenchResult);", "containerEl.appendChild(root);", "},", "unmount:function(containerEl){while(containerEl.__ptCleanup&&containerEl.__ptCleanup.length){containerEl.__ptCleanup.pop()();}containerEl.innerHTML='';}", "};", "var MarkdownDiagnosticProvider={", "mount:function(containerEl,props,api){", "containerEl.innerHTML='';", "var root=document.createElement('div');", "root.className='pt-root pt-workbench-result';", "root.setAttribute('data-workbench-status','ok');", "var req=(props&&props.request)||{};", "var ctx=(req.context&&req.context.notesMode)||false?'notes-markdown':((req.extension==='.md'||req.extension==='.markdown')?'generic-markdown':'generic-text');", "root.setAttribute('data-resource-path',req.path||'');", "root.setAttribute('data-resource-mode',req.mode||'');", "root.setAttribute('data-resource-context',ctx);", "root.textContent='Workbench: opened '+(req.path||'')+' with '+((props&&props.providerId)||'')+' mode='+(req.mode||'')+' context='+ctx;", "containerEl.appendChild(root);", "},", "unmount:function(containerEl){containerEl.innerHTML='';}", "};", "var PlatformTestSettings={", "mount:function(containerEl,props,api){", "containerEl.innerHTML='
'+api.pluginId+'