Files
apes-sidebar/tests/markdown-images.spec.ts
T
HuanLinOTO 751fb954a2 fix: address Copilot review (9 comments)
#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.
2026-08-23 22:02:47 +08:00

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 = '![a](./img.png)\n![b](images/b.jpg)'
const out = rewriteLocalImageUrls(md, scope, '/repo/docs/readme.md', ORIGIN)
expect(out).toContain(`![a](${ORIGIN}/sidebar/file?sessionId=abc&path=%2Frepo%2Fdocs%2Fimg.png&cwd=%2Frepo)`)
expect(out).toContain(`![b](${ORIGIN}/sidebar/file?sessionId=abc&path=%2Frepo%2Fdocs%2Fimages%2Fb.jpg&cwd=%2Frepo)`)
})
it('expects the rewritten destination to be an absolute http URL MarkdownText accepts', () => {
const out = rewriteLocalImageUrls('![a](./img.png)', 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 = '![a](/repo/assets/b.png)'
const out = rewriteLocalImageUrls(md, scope, '/repo/docs/readme.md', ORIGIN)
expect(out).toContain(`![a](${ORIGIN}/sidebar/file?sessionId=abc&path=%2Frepo%2Fassets%2Fb.png&cwd=%2Frepo)`)
})
it('keeps remote http(s)/data/mailto destinations untouched', () => {
const md = '![a](https://example.com/x.jpg)\n![b](http://e.com/y.png)\n![c](data:image/png;base64,AAA)'
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
expect(out).toContain('![a](https://example.com/x.jpg)')
expect(out).toContain('![b](http://e.com/y.png)')
expect(out).toContain('![c](data:image/png;base64,AAA)')
})
it('leaves in-page anchors untouched', () => {
const md = '![a](#section)\n\n[link](#top)'
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
expect(out).toContain('![a](#section)')
})
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 = '![a](./img.png)\n![b](../assets/b.png)'
const out = rewriteLocalImageUrls(md, scope, '/repo/docs/sub/readme.md', ORIGIN)
expect(out).toContain(`![a](${ORIGIN}/sidebar/file?sessionId=abc&path=%2Frepo%2Fdocs%2Fsub%2Fimg.png&cwd=%2Frepo)`)
expect(out).toContain(`![b](${ORIGIN}/sidebar/file?sessionId=abc&path=%2Frepo%2Fdocs%2Fassets%2Fb.png&cwd=%2Frepo)`)
expect(out).not.toContain('/.')
})
it('drops the cwd param when the scope has no cwd', () => {
const out = rewriteLocalImageUrls('![a](./img.png)', { 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 = '![a](C:\\repo\\img.png)'
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![alt](./img.png)\n```\n![real](./real.png)'
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![alt](./img.png)\n```')
expect(out).toContain('/sidebar/file?')
})
it('does not rewrite image-looking text inside inline code spans', () => {
const md = 'Use `![alt](./img.png)` syntax.\n![real](./real.png)'
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
expect(out).toContain('`![alt](./img.png)`')
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/)
})
})