fix(links): preserve workspace openers after lookup refusals

This commit is contained in:
Devvyn committed 2026-10-28 23:03:45 +08:00
1 parent 97d53f6621
commit fd0e52496d
7 files changed
+80 -29

No files matched your search

+49 -3
View File
@@ -259,6 +259,27 @@ function linksOn(lines: string[], row: number, cols = 80, wrappedRows = new Set<
}
describe('terminal link provider', () => {
it.each(['svg', 'markdown', 'avif', 'bmp', 'ico', 'zip'])(
'keeps the workspace opener for a hard-wrapped .%s rejected by the attachment registry',
async (extension) => {
for (const row of [1, 2]) {
const path = `/home/me/proj/out/system_architecture.${extension}`;
const { app, links } = linksOn(
['Saved the diagram (/home/me/proj/out/system_archi', ` tecture.${extension}).`],
row,
120
);
app.sessions = new Map([['s1', { workingDir: '/home/me/proj' }]]);
app._registerExternalPreview.mockResolvedValue({ status: 400, error: 'Unsupported attachment type' });
await links[0].activate({}, links[0].text);
const opener = extension === 'zip' ? app.openLogViewerWindow : app.openFilePreview;
expect(opener).toHaveBeenCalledWith(path, 's1');
expect(app._registerExternalPreview.mock.calls).toEqual([[path, 's1']]);
expect(app.showToast).not.toHaveBeenCalled();
}
}
);
it.each([
[
['Saved the screenshot (/home/me/proj/out/ghost1b_game', ' play.png).'],
@@ -310,11 +331,36 @@ describe('terminal link provider', () => {
app._registerExternalPreview.mockResolvedValue({ status, error: 'Lookup refused' });
await links[0].activate({}, links[0].text);
expect(app._registerExternalPreview).toHaveBeenCalledTimes(1);
expect(app.openFilePreview).not.toHaveBeenCalled();
expect(app.openLogViewerWindow).not.toHaveBeenCalled();
expect(app.showToast).toHaveBeenCalledWith('Lookup refused', 'error');
expect(app.openLogViewerWindow).toHaveBeenCalledWith('/mnt/data/KoboeReader.conf', 's1');
expect(app.showToast).not.toHaveBeenCalled();
});
it('keeps the original opener when the lookup cannot reach the server', async () => {
const { app, links } = linksOn(['Saved (/mnt/data/Kobo', ' eReader.conf).'], 1, 120);
app._registerExternalPreview.mockResolvedValue({ error: 'Connection lost' });
await links[0].activate({}, links[0].text);
expect(app._registerExternalPreview).toHaveBeenCalledTimes(1);
expect(app.openLogViewerWindow).toHaveBeenCalledWith('/mnt/data/KoboeReader.conf', 's1');
});
it.each([400, 403, 404, 500, 503])(
'does not open an unregistered spaced alternative after HTTP %s',
async (status) => {
const { app, links } = linksOn(['Saved (/mnt/data/Kobo', ' eReader.conf).'], 1, 120);
app._registerExternalPreview
.mockResolvedValueOnce({ status: 404, error: 'File not found' })
.mockResolvedValueOnce({ status, error: 'Alternative unavailable' });
await links[0].activate({}, links[0].text);
expect(app._registerExternalPreview.mock.calls).toEqual([
['/mnt/data/KoboeReader.conf', 's1'],
['/mnt/data/Kobo eReader.conf', 's1'],
]);
expect(app.openFilePreview).not.toHaveBeenCalled();
expect(app.openLogViewerWindow).not.toHaveBeenCalled();
expect(app.showToast).toHaveBeenCalledWith('Alternative unavailable', 'error');
}
);
it('keeps the session selected at click time while a path lookup is pending', async () => {
const { app, links } = linksOn(['Saved (/mnt/data/Kobo', ' eReader.conf).'], 1, 120);
const pending = links[0].activate({}, links[0].text);
+21 -16
View File
@@ -5,7 +5,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { chromium, type Browser } from 'playwright';
const publicDir = resolve(import.meta.dirname, '../src/web/public');
const path = '/mnt/user/Scratch/Kobo/Set-KoboCovers/Kobo eReader.conf';
const basePath = '/mnt/user/Scratch/Kobo/Set-KoboCovers/Kobo eReader.conf';
describe('wrapped configuration file links (Chromium)', () => {
let browser: Browser;
@@ -17,12 +17,15 @@ describe('wrapped configuration file links (Chromium)', () => {
});
it.each([
{ wrap: 'soft', originalExists: false },
{ wrap: 'hard', originalExists: false },
{ wrap: 'hard', originalExists: true },
{ wrap: 'soft', originalExists: false, extension: 'conf', registrationStatus: 200 },
{ wrap: 'hard', originalExists: false, extension: 'conf', registrationStatus: 200 },
{ wrap: 'hard', originalExists: true, extension: 'conf', registrationStatus: 200 },
{ wrap: 'hard', originalExists: true, extension: 'svg', registrationStatus: 400 },
{ wrap: 'hard', originalExists: true, extension: 'markdown', registrationStatus: 400 },
])(
'opens the existing file from both rows: $wrap, original exists: $originalExists',
async ({ wrap, originalExists }) => {
'opens the existing .$extension file from both rows: $wrap, original exists: $originalExists',
async ({ wrap, originalExists, extension, registrationStatus }) => {
const path = basePath.replace('.conf', `.${extension}`);
const original = path.replace('Kobo eReader', 'KoboeReader');
const expected = wrap === 'hard' && originalExists ? original : path;
const lookups: string[] = [];
@@ -39,11 +42,13 @@ describe('wrapped configuration file links (Chromium)', () => {
expect(body.notify).toBe(false);
lookups.push(body.path);
const exists = body.path === path || (originalExists && body.path === original);
const status = exists ? registrationStatus : 404;
await route.fulfill({
status: exists ? 200 : 404,
json: exists
? { success: true, data: { attachmentId: 'att-fixture' } }
: { success: false, error: 'File not found' },
status,
json:
status === 200
? { success: true, data: { attachmentId: 'att-fixture' } }
: { success: false, error: status === 400 ? 'Unsupported attachment type' : 'File not found' },
});
});
await page.goto('http://fixture.test/');
@@ -54,28 +59,28 @@ describe('wrapped configuration file links (Chromium)', () => {
await page.addScriptTag({ content: readFileSync(resolve(publicDir, file), 'utf8') });
}
await page.evaluate(
async ({ path, wrap }) => {
async ({ path, wrap, workingDir }) => {
const w = window as any;
const first = `Found it: prepared a corrected configuration (${path.slice(0, -' eReader.conf'.length)}`;
const boundary = path.lastIndexOf(' ');
const first = `Found it: prepared a corrected configuration (${path.slice(0, boundary)}`;
const terminal = new w.Terminal({ cols: first.length + (wrap === 'soft' ? 1 : 20), rows: 6 });
terminal.open(document.getElementById('terminal'));
w.opened = [];
w.app = Object.assign(new w.CodemanApp(), {
terminal,
activeSessionId: 'fixture',
sessions: new Map([['fixture', { workingDir: '/workspace' }]]),
_isExternalPreviewPath: () => true,
sessions: new Map([['fixture', { workingDir }]]),
openFilePreview: (file: string) => w.opened.push(file),
openLogViewerWindow: (file: string) => w.opened.push(`log:${file}`),
openFolderGrid: (file: string) => w.opened.push(`folder:${file}`),
showToast: (message: string) => w.opened.push(`error:${message}`),
});
w.app.registerFilePathLinkProvider();
const text = `${first}${wrap === 'soft' ? ' ' : '\r\n '}eReader.conf).`;
const text = `${first}${wrap === 'soft' ? ' ' : '\r\n '}${path.slice(boundary + 1)}).`;
await new Promise<void>((done) => terminal.write(text, done));
await new Promise<void>((done) => requestAnimationFrame(() => requestAnimationFrame(() => done())));
},
{ path, wrap }
{ path, wrap, workingDir: registrationStatus === 400 ? '/mnt/user/Scratch/Kobo' : '/workspace' }
);
expect(lookups).toEqual([]);