#1 GitView: hide/disable 'open in editor' for external linked worktrees (path outside session workspace is rejected by the fence); add isWithinWorkspace client helper for the gate. #2 DiffTab: untracked diff read resolves against diff.repoRoot (child-repo paths are repo-root-relative, not cwd-relative); fs.read route threads selectedRepoOf into resolveGitPath so the host resolves the right base. #3 GitView: chooseRepo handler mirrors chooseWorktree — clears status/ history/log and bumps generation before async refresh, preventing stale rows from acting on the newly selected repository. #4 git.ts: repoRoots/repoRoot compare roots via pathIdentity() (platform- aware) instead of raw includes(), fixing win32 forward/backslash mismatch that silently fell back to roots[0]. #5 markdown-images: mask fenced code blocks and inline code spans before rewriting; only rewrite reference definitions whose label is actually referenced by image syntax (not plain links). +3 tests. #6 package.json: downgrade unrun ^0.3.1 → ^0.2.39 (0.3.x requires Node ^22.13, incompatible with declared engines >=20). #7 market-manifest.spec: shell:true on win32 so execFileSync resolves the pnpm .cmd shim. #8 smoke.spec: build html-route URL via encodeHtmlUrl() (production encoder) so the Windows drive separator is correct. #9 index.ts git.worktrees route: thread selectedRepoOf so a workspace container's selected child repo's linked worktrees appear.
101 lines
5.0 KiB
TypeScript
101 lines
5.0 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { rewriteLocalImageUrls } from '../src/client/markdown-images.ts'
|
|
import type { SessionScope } from '../src/client/api.ts'
|
|
|
|
const ORIGIN = 'http://127.0.0.1:3080'
|
|
const scope: SessionScope = { sessionId: 'abc', cwd: '/repo' }
|
|
|
|
describe('rewriteLocalImageUrls', () => {
|
|
it('resolves relative destinations against the opened file directory', () => {
|
|
const md = '\n'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/docs/readme.md', ORIGIN)
|
|
expect(out).toContain(``)
|
|
expect(out).toContain(``)
|
|
})
|
|
|
|
it('expects the rewritten destination to be an absolute http URL MarkdownText accepts', () => {
|
|
const out = rewriteLocalImageUrls('', scope, '/repo/readme.md', ORIGIN)
|
|
const url = out.match(/\]\((http[^)]+)\)/)?.[1]
|
|
expect(url).toBeDefined()
|
|
expect(new URL(url as string).protocol).toBe('http:')
|
|
})
|
|
|
|
it('passes absolute local paths through unchanged', () => {
|
|
const md = ''
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/docs/readme.md', ORIGIN)
|
|
expect(out).toContain(``)
|
|
})
|
|
|
|
it('keeps remote http(s)/data/mailto destinations untouched', () => {
|
|
const md = '\n\n'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
|
|
expect(out).toContain('')
|
|
expect(out).toContain('')
|
|
expect(out).toContain('')
|
|
})
|
|
|
|
it('leaves in-page anchors untouched', () => {
|
|
const md = '\n\n[link](#top)'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
|
|
expect(out).toContain('')
|
|
})
|
|
|
|
it('rewrites reference-style image definitions', () => {
|
|
const md = '![logo][logo]\n\n[logo]: ./logo.svg'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
|
|
expect(out).toContain(`[logo]: ${ORIGIN}/sidebar/file?sessionId=abc&path=%2Frepo%2Flogo.svg&cwd=%2Frepo`)
|
|
// The inline reference itself is left for MarkdownText to combine.
|
|
expect(out).toContain('![logo][logo]')
|
|
})
|
|
|
|
it('normalizes `.`/`..` segments in relative destinations', () => {
|
|
const md = '\n'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/docs/sub/readme.md', ORIGIN)
|
|
expect(out).toContain(``)
|
|
expect(out).toContain(``)
|
|
expect(out).not.toContain('/.')
|
|
})
|
|
|
|
it('drops the cwd param when the scope has no cwd', () => {
|
|
const out = rewriteLocalImageUrls('', { sessionId: 'abc' }, '/repo/readme.md', ORIGIN)
|
|
expect(out).toContain(`sessionId=abc&path=%2Frepo%2Fimg.png`)
|
|
expect(out).not.toContain('cwd=')
|
|
})
|
|
|
|
it('handles windows absolute paths as local (not remote)', () => {
|
|
const md = ''
|
|
const out = rewriteLocalImageUrls(md, scope, 'C:\\repo\\docs\\readme.md', ORIGIN)
|
|
// A Windows drive path is absolute (not a remote scheme) → rewritten.
|
|
const url = out.match(/\]\(([^)]+)\)/)?.[1] as string
|
|
expect(url).toContain('/sidebar/file?')
|
|
expect(decodeURIComponent(url)).toContain('C:\\repo\\img.png')
|
|
})
|
|
|
|
it('does not rewrite image-looking text inside fenced code blocks', () => {
|
|
const md = '```\n\n```\n'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
|
|
// The fenced example must be preserved verbatim; only the real image is rewritten.
|
|
expect(out).toContain('```\n\n```')
|
|
expect(out).toContain('/sidebar/file?')
|
|
})
|
|
|
|
it('does not rewrite image-looking text inside inline code spans', () => {
|
|
const md = 'Use `` syntax.\n'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
|
|
expect(out).toContain('``')
|
|
expect(out).toContain('/sidebar/file?')
|
|
})
|
|
|
|
it('does not rewrite a reference definition used only by a plain link', () => {
|
|
// The `[manual][docs]` link references `[docs]: ./docs.md`, but that
|
|
// definition must NOT be redirected to /sidebar/file because it is not
|
|
// referenced by any image. Only image-referenced definitions are rewritten.
|
|
const md = '[manual][docs]\n\n![pic][pic-def]\n\n[docs]: ./docs.md\n[pic-def]: ./pic.png'
|
|
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
|
|
expect(out).toContain('[docs]: ./docs.md')
|
|
expect(out).not.toMatch(/\[docs\]:.*\/sidebar\/file/)
|
|
// The image-referenced definition IS rewritten.
|
|
expect(out).toMatch(/\[pic-def\]:.*\/sidebar\/file/)
|
|
})
|
|
})
|