test: cover secret workbench navigation

This commit is contained in:
mirivlad 2026-07-14 19:14:01 +08:00
parent fc82b3e6b1
commit fe64e1f822
3 changed files with 142 additions and 5 deletions

View File

@ -36,6 +36,69 @@ test.describe('F: Default Editor Plugin', () => {
await expect(textarea).toHaveValue('Buy groceries\nWrite tests'); await expect(textarea).toHaveValue('Buy groceries\nWrite tests');
}); });
test('secret link opens its exact secret and closing it restores the note preview', async ({ page }) => {
const notePath = 'Notes/Secret Link.md';
const noteContent = '# Secret link\n\n[Target secret](verstak-secret://target.secret)';
await page.evaluate(async ({ notePath, noteContent }) => {
const writeError = await window.go.api.App.WriteVaultTextFile(
'verstak.platform-test',
notePath,
noteContent,
{ createIfMissing: true, overwrite: true },
);
if (writeError) throw new Error(writeError);
const [result, openError] = await window.go.api.App.OpenWorkbenchResource('verstak.platform-test', {
kind: 'vault-file',
path: notePath,
extension: '.md',
context: { sourceView: 'notes', isInsideNotesFolder: true, notesMode: true },
});
if (openError) throw new Error(openError);
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
}, { notePath, noteContent });
const note = page.locator('[data-editor-mode="notes-markdown"]');
await expect(note).toBeVisible({ timeout: 10000 });
await expect(note.locator('[data-preview]')).toContainText('Target secret');
await note.locator('.secret-link').click();
const secrets = page.locator('.secrets-root');
await expect(secrets).toBeVisible({ timeout: 10000 });
await expect(secrets.locator('.secrets-item.active .secrets-item-title')).toHaveText('Target secret');
await expect(secrets.locator('.secrets-card h2')).toHaveText('Target secret');
await page.locator('.workbench-host .close-btn').click();
await expect(note).toBeVisible({ timeout: 10000 });
await expect(note.locator('[data-preview]')).toContainText('Target secret');
await expect(note.locator('[data-editor-textarea]')).toHaveCount(0);
await expect(note.locator('[data-save-state]')).toHaveText('');
const storedContent = await page.evaluate(async ({ notePath }) => {
const [content, readError] = await window.go.api.App.ReadVaultTextFile('verstak.platform-test', notePath);
if (readError) throw new Error(readError);
return content;
}, { notePath });
expect(storedContent).toBe(noteContent);
});
test('unavailable secret does not fall back to the first secret', async ({ page }) => {
await page.evaluate(async () => {
const [result, openError] = await window.go.api.App.OpenWorkbenchResource('verstak.default-editor', {
kind: 'secret',
path: 'missing.secret',
mode: 'view',
});
if (openError) throw new Error(openError);
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
});
const secrets = page.locator('.secrets-root');
await expect(secrets).toBeVisible({ timeout: 10000 });
await expect(secrets.locator('.secrets-item.active')).toHaveCount(0);
await expect(secrets.locator('.secrets-status.error')).toContainText(/unavailable|недоступен/i);
});
test('editor supports markdown toolbar split save reopen and revert', async ({ page }) => { test('editor supports markdown toolbar split save reopen and revert', async ({ page }) => {
await page.evaluate(async () => { await page.evaluate(async () => {
const err = await window.go.api.App.WriteVaultTextFile( const err = await window.go.api.App.WriteVaultTextFile(

View File

@ -4,6 +4,9 @@
* Каждый метод возвращает Promise с данными, совместимыми с Wails-контрактом. * Каждый метод возвращает Promise с данными, совместимыми с Wails-контрактом.
* Состояние мутабельно тесты могут менять его между сценариями. * Состояние мутабельно тесты могут менять его между сценариями.
*/ */
import defaultEditorSource from '../../../../../verstak-official-plugins/plugins/default-editor/frontend/src/index.js?raw';
import secretsSource from '../../../../../verstak-official-plugins/plugins/secrets/frontend/src/index.js?raw';
(function () { (function () {
if (window.__wailsMockReady) return; if (window.__wailsMockReady) return;
@ -360,6 +363,7 @@
'verstak.platform-test': { savedText: 'initial value' } 'verstak.platform-test': { savedText: 'initial value' }
}; };
var pluginData = {}; var pluginData = {};
var secretRecords = makeDefaultSecretRecords();
var vaultFiles = makeDefaultVaultFiles(); var vaultFiles = makeDefaultVaultFiles();
var externalOpens = []; var externalOpens = [];
var trashEntries = []; var trashEntries = [];
@ -404,6 +408,27 @@
return JSON.parse(JSON.stringify(value)); return JSON.parse(JSON.stringify(value));
} }
function makeDefaultSecretRecords() {
return [
{
id: 'first.secret',
title: 'First secret',
username: 'first-user',
value: 'first-value',
scope: { kind: 'global' },
updatedAt: '2026-07-14T00:00:00Z'
},
{
id: 'target.secret',
title: 'Target secret',
username: 'target-user',
value: 'target-value',
scope: { kind: 'global' },
updatedAt: '2026-07-14T00:00:00Z'
}
];
}
function builtInWorkspaceTemplates() { function builtInWorkspaceTemplates() {
return [ return [
{ {
@ -573,8 +598,15 @@
icon: 'key-round', icon: 'key-round',
provides: ['secret-store', 'secrets.read-ui', 'secrets.write-ui'], provides: ['secret-store', 'secrets.read-ui', 'secrets.write-ui'],
permissions: ['secrets.read', 'secrets.write', 'ui.register'], permissions: ['secrets.read', 'secrets.write', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' }, frontend: { entry: 'frontend/src/index.js' },
contributes: { contributes: {
openProviders: [{
id: 'verstak.secrets.secret',
title: 'Secrets',
priority: 100,
component: 'SecretsView',
supports: [{ kind: 'secret', modes: ['view'] }]
}],
workspaceItems: [{ id: 'verstak.secrets.workspace', title: 'Secrets', icon: 'key-round', component: 'SecretsView' }] workspaceItems: [{ id: 'verstak.secrets.workspace', title: 'Secrets', icon: 'key-round', component: 'SecretsView' }]
} }
}, },
@ -3328,6 +3360,43 @@
GetPluginLocalization: function (pluginId, locale) { GetPluginLocalization: function (pluginId, locale) {
return Promise.resolve([mockPluginCatalog(pluginId, locale), '']); return Promise.resolve([mockPluginCatalog(pluginId, locale), '']);
}, },
PluginSecretsStatus: function () {
return Promise.resolve([{ initialized: true, unlocked: true }, '']);
},
PluginSecretsUnlock: function () {
return Promise.resolve('');
},
PluginSecretsList: function () {
return Promise.resolve([secretRecords.map(function (record) {
var listed = cloneJson(record);
delete listed.value;
return listed;
}), '']);
},
PluginSecretsRead: function (_pluginId, secretID) {
var record = secretRecords.find(function (item) { return item.id === secretID; });
if (!record) return Promise.resolve([{}, 'not-found: secret ' + secretID]);
return Promise.resolve([cloneJson(record), '']);
},
PluginSecretsWrite: function (_pluginId, nextRecord) {
var record = Object.assign({}, nextRecord || {});
if (!record.id) return Promise.resolve([{}, 'secret id is required']);
record.scope = record.scope || { kind: 'global' };
record.updatedAt = new Date().toISOString();
var index = secretRecords.findIndex(function (item) { return item.id === record.id; });
if (index === -1) secretRecords.push(record);
else secretRecords[index] = record;
return Promise.resolve([cloneJson(record), '']);
},
PluginSecretsDelete: function (_pluginId, secretID) {
secretRecords = secretRecords.filter(function (record) { return record.id !== secretID; });
return Promise.resolve('');
},
PluginSecretsCopyLink: function (_pluginId, secretID) {
var record = secretRecords.find(function (item) { return item.id === secretID; });
if (!record) return Promise.resolve(['', 'not-found: secret ' + secretID]);
return Promise.resolve(['[' + (record.title || record.id) + '](verstak-secret://' + encodeURIComponent(record.id) + ')', '']);
},
ReadPluginSettings: function (pluginId) { ReadPluginSettings: function (pluginId) {
return Promise.resolve([Object.assign({}, pluginSettings[pluginId] || {}), '']); return Promise.resolve([Object.assign({}, pluginSettings[pluginId] || {}), '']);
}, },
@ -3455,8 +3524,8 @@
if (pluginId === 'verstak.platform-test' && assetPath === 'frontend/dist/index.js') { if (pluginId === 'verstak.platform-test' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(platformTestBundle()); return Promise.resolve(platformTestBundle());
} }
if (pluginId === 'verstak.default-editor' && assetPath === 'frontend/dist/index.js') { if (pluginId === 'verstak.default-editor') {
return Promise.resolve(defaultEditorBundle()); return Promise.resolve(defaultEditorSource);
} }
if (pluginId === 'verstak.files' && assetPath === 'frontend/dist/index.js') { if (pluginId === 'verstak.files' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(filesPluginBundle()); return Promise.resolve(filesPluginBundle());
@ -3479,8 +3548,8 @@
if (pluginId === 'verstak.todo' && assetPath === 'frontend/dist/index.js') { if (pluginId === 'verstak.todo' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(todoBundle()); return Promise.resolve(todoBundle());
} }
if (pluginId === 'verstak.secrets' && assetPath === 'frontend/dist/index.js') { if (pluginId === 'verstak.secrets') {
return Promise.resolve(simplePluginBundle('verstak.secrets', 'SecretsView', 'secrets-root', 'Secrets')); return Promise.resolve(secretsSource);
} }
if (pluginId === 'verstak.search' && assetPath === 'frontend/dist/index.js') { if (pluginId === 'verstak.search' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(searchPluginBundle()); return Promise.resolve(searchPluginBundle());
@ -4164,6 +4233,7 @@
workbenchPreferences = {}; workbenchPreferences = {};
openedResources = []; openedResources = [];
pluginSettings = { 'verstak.platform-test': { savedText: 'initial value' } }; pluginSettings = { 'verstak.platform-test': { savedText: 'initial value' } };
secretRecords = makeDefaultSecretRecords();
vaultFiles = makeDefaultVaultFiles(); vaultFiles = makeDefaultVaultFiles();
externalOpens = []; externalOpens = [];
trashEntries = []; trashEntries = [];

View File

@ -1,3 +1,4 @@
import { resolve } from 'node:path';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte'; import { svelte } from '@sveltejs/vite-plugin-svelte';
@ -13,6 +14,9 @@ export default defineConfig(({ mode }) => {
server: { server: {
port: isTest ? 5174 : 5173, port: isTest ? 5174 : 5173,
strictPort: true, strictPort: true,
fs: {
allow: [resolve(__dirname, '..', '..')],
},
}, },
optimizeDeps: { optimizeDeps: {
include: [ include: [