mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
6407ef5d97
Local rendered-HTML annotate sessions get a Refresh action beside Hide tools: the document is re-fetched through /api/doc, the sandboxed viewer remounts, annotations are re-anchored and the ones that no longer match are reported while their comments are kept, and stale diff and share state is reset. Maintainer additions on top of the contributor's work: share-link invalidation no longer keys on the resolver's identity, /api/plan and /api/share-html serve a local root HTML file from its current bytes on both runtimes so a reload does not revert the page under the annotations, the Refresh button keeps keyboard focus via aria-disabled, and the tests were hardened. Verified end to end in a real browser. Thanks @leoreisdias. AI-assisted (Claude) under maintainer direction.
138 lines
4.2 KiB
TypeScript
138 lines
4.2 KiB
TypeScript
import { afterEach, describe, expect, test } from 'bun:test';
|
|
import {
|
|
fetchHtmlDocumentSnapshot,
|
|
fetchSourceDocumentSnapshot,
|
|
probeSourceSave,
|
|
} from './sourceDocumentClient';
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
function mockFetch(response: Response | Error) {
|
|
globalThis.fetch = (async () => {
|
|
if (response instanceof Error) throw response;
|
|
return response;
|
|
}) as typeof fetch;
|
|
}
|
|
|
|
afterEach(() => {
|
|
globalThis.fetch = originalFetch;
|
|
});
|
|
|
|
describe('source document client', () => {
|
|
test('probes source-save metadata from /api/doc', async () => {
|
|
mockFetch(Response.json({
|
|
sourceSave: {
|
|
enabled: true,
|
|
kind: 'local-text-file',
|
|
scope: 'folder-file',
|
|
path: '/repo/docs/a.md',
|
|
basename: 'a.md',
|
|
language: 'markdown',
|
|
hash: 'sha256:after',
|
|
mtimeMs: 1000,
|
|
size: 6,
|
|
eol: 'lf',
|
|
},
|
|
}));
|
|
|
|
expect(await probeSourceSave('/repo/docs/a.md')).toEqual({
|
|
status: 'ok',
|
|
sourceSave: {
|
|
enabled: true,
|
|
kind: 'local-text-file',
|
|
scope: 'folder-file',
|
|
path: '/repo/docs/a.md',
|
|
basename: 'a.md',
|
|
language: 'markdown',
|
|
hash: 'sha256:after',
|
|
mtimeMs: 1000,
|
|
size: 6,
|
|
eol: 'lf',
|
|
},
|
|
});
|
|
});
|
|
|
|
test('distinguishes missing and unavailable source probes', async () => {
|
|
mockFetch(new Response('missing', { status: 404 }));
|
|
expect(await probeSourceSave('/repo/docs/missing.md')).toEqual({ status: 'missing' });
|
|
|
|
mockFetch(new Error('network'));
|
|
expect(await probeSourceSave('/repo/docs/a.md')).toEqual({ status: 'unavailable' });
|
|
});
|
|
|
|
test('fetches markdown source snapshots and rejects html documents', async () => {
|
|
mockFetch(Response.json({
|
|
markdown: 'after\n',
|
|
sourceSave: {
|
|
enabled: true,
|
|
kind: 'local-text-file',
|
|
scope: 'folder-file',
|
|
path: '/repo/docs/a.md',
|
|
basename: 'a.md',
|
|
language: 'markdown',
|
|
hash: 'sha256:after',
|
|
mtimeMs: 1000,
|
|
size: 6,
|
|
eol: 'lf',
|
|
},
|
|
}));
|
|
expect(await fetchSourceDocumentSnapshot('/repo/docs/a.md')).toEqual({
|
|
status: 'ok',
|
|
snapshot: {
|
|
markdown: 'after\n',
|
|
sourceSave: {
|
|
enabled: true,
|
|
kind: 'local-text-file',
|
|
scope: 'folder-file',
|
|
path: '/repo/docs/a.md',
|
|
basename: 'a.md',
|
|
language: 'markdown',
|
|
hash: 'sha256:after',
|
|
mtimeMs: 1000,
|
|
size: 6,
|
|
eol: 'lf',
|
|
},
|
|
},
|
|
});
|
|
|
|
mockFetch(Response.json({ markdown: '<p>after</p>', renderAs: 'html' }));
|
|
expect(await fetchSourceDocumentSnapshot('/repo/docs/a.html')).toEqual({ status: 'unavailable' });
|
|
});
|
|
|
|
test('distinguishes missing and unavailable source snapshots', async () => {
|
|
mockFetch(new Response('missing', { status: 404 }));
|
|
expect(await fetchSourceDocumentSnapshot('/repo/docs/missing.md')).toEqual({ status: 'missing' });
|
|
|
|
mockFetch(new Error('network'));
|
|
expect(await fetchSourceDocumentSnapshot('/repo/docs/a.md')).toEqual({ status: 'unavailable' });
|
|
});
|
|
|
|
test('fetches a rendered html snapshot without requiring source-save support', async () => {
|
|
mockFetch(Response.json({
|
|
rawHtml: '<main>after</main>',
|
|
filepath: '/repo/docs/a.html',
|
|
renderAs: 'html',
|
|
sourceSave: { enabled: false, reason: 'html-render' },
|
|
}));
|
|
|
|
expect(await fetchHtmlDocumentSnapshot('/repo/docs/a.html')).toEqual({
|
|
status: 'ok',
|
|
snapshot: {
|
|
rawHtml: '<main>after</main>',
|
|
filepath: '/repo/docs/a.html',
|
|
},
|
|
});
|
|
});
|
|
|
|
test('keeps the current html available when refresh cannot load a valid snapshot', async () => {
|
|
mockFetch(new Response('missing', { status: 404 }));
|
|
expect(await fetchHtmlDocumentSnapshot('/repo/docs/missing.html')).toEqual({ status: 'missing' });
|
|
|
|
mockFetch(Response.json({ markdown: '# Not HTML', renderAs: 'markdown' }));
|
|
expect(await fetchHtmlDocumentSnapshot('/repo/docs/a.html')).toEqual({ status: 'unavailable' });
|
|
|
|
mockFetch(new Error('network'));
|
|
expect(await fetchHtmlDocumentSnapshot('/repo/docs/a.html')).toEqual({ status: 'unavailable' });
|
|
});
|
|
});
|