import assert from 'node:assert/strict' import { renderToStaticMarkup } from 'react-dom/server' import { downloadImageSafely, ImagePreview } from '../src/renderer/src/workbuddy/images/ImagePreview' import { MarkdownImage } from '../src/renderer/src/workbuddy/images/MarkdownImage' import { PreviewIcon } from '../src/renderer/src/workbuddy/images/PreviewIcon' import { clampPreviewIndex, isSafeImageUrl, makeImagePreviewRequest, resolveImageUrl, requiresHostImageResolution, stepPreviewIndex } from '../src/renderer/src/workbuddy/images/image-url' async function testSafeImageSchemesAndResolution(): Promise { for (const url of [ 'data:image/png;base64,AA==', 'https://example.test/image.png', 'HTTP://example.test/image.png', 'blob:https://example.test/id', 'file:///C:/images/photo.png', 'local-file://image/photo.png' ]) assert.equal(isSafeImageUrl(url), true, `${url} should be safe`) for (const url of [ 'javascript:alert(1)', 'data:text/html,unsafe', '../images/photo.png', 'not a URL' ]) assert.equal(isSafeImageUrl(url), false, `${url} should be rejected`) let resolverCalls = 0 assert.equal(await resolveImageUrl('https://example.test/image.png', async () => { resolverCalls++ return 'data:image/png;base64,AA==' }), 'https://example.test/image.png') assert.equal(resolverCalls, 0, 'safe sources should bypass host resolution') assert.equal(await resolveImageUrl('C:\\workspace\\photo.png', async (source) => { assert.equal(source, 'C:\\workspace\\photo.png') return 'data:image/png;base64,BB==' }), 'data:image/png;base64,BB==') assert.equal(requiresHostImageResolution('file:///C:/workspace/photo.png'), true) assert.equal(requiresHostImageResolution('local-file://workspace/photo.png'), true) assert.equal(requiresHostImageResolution('https://example.test/photo.png'), false) let hostCalls = 0 assert.equal(await resolveImageUrl('file:///C:/workspace/photo.png', async () => { hostCalls++ return 'data:image/png;base64,CC==' }, true), 'data:image/png;base64,CC==') assert.equal(hostCalls, 1, 'preferred host resolution should run for local file schemes') assert.equal(await resolveImageUrl('https://example.test/photo.png', async () => { hostCalls++ return 'data:image/png;base64,DD==' }, true), 'https://example.test/photo.png') assert.equal(hostCalls, 1, 'preferred host resolution should not intercept remote images') assert.equal(await resolveImageUrl('/unsafe/path.png', async () => 'javascript:alert(1)'), undefined) assert.equal(await resolveImageUrl('/no-resolver.png'), undefined) await assert.rejects(resolveImageUrl('/reject.png', async () => { throw new Error('resolver failed') }), /resolver failed/) } function testSafePreviewFilteringAndIndex(): void { assert.deepEqual(makeImagePreviewRequest([ 'https://example.test/one.png', '../unsafe.png', 'data:image/png;base64,AA==', 'javascript:alert(1)' ], 'data:image/png;base64,AA=='), { urlList: ['https://example.test/one.png', 'data:image/png;base64,AA=='], initialIndex: 1 }) assert.deepEqual(makeImagePreviewRequest(['../one.png'], 'data:image/png;base64,AA=='), { urlList: [], initialIndex: 0 }) assert.equal(clampPreviewIndex(-3, 4), 0) assert.equal(clampPreviewIndex(9, 4), 3) assert.equal(clampPreviewIndex(1, 0), 0) assert.equal(stepPreviewIndex(1, 4, -1), 0) assert.equal(stepPreviewIndex(2, 4, 1), 3) } async function testRejectedImageDownloadIsReported(): Promise { let message = '' await downloadImageSafely(async () => { throw new Error('disk unavailable') }, 'https://example.test/image.png', (value) => { message = value }) assert.equal(message, '下载失败:disk unavailable') } function testMarkdownImageServerFallback(): void { const html = renderToStaticMarkup( 'data:image/png;base64,AA=='} /> ) assert.match(html, /class="[^"]*cr-image-block-fallback[^"]*"/) assert.match(html, /aria-busy="true"/) assert.match(html, /role="img"/) assert.match(html, />source diagram<\/span>/) const source = 'C:\\workspace\\source-only.png' const sourceFallback = renderToStaticMarkup() assert.ok(sourceFallback.includes(source)) } function testPreviewDialogServerStructure(): void { const html = renderToStaticMarkup( undefined} onDownload={() => undefined} /> ) assert.match(html, /role="dialog"/) assert.match(html, /aria-modal="true"/) assert.match(html, /src="https:\/\/example\.test\/two\.png"/) assert.match(html, /crossorigin="use-credentials"/) assert.match(html, /aria-label="上一张"/) assert.match(html, /aria-label="下一张"/) assert.match(html, /aria-label="下载图片"/) assert.match(html, /2\/2/) } function testRecoveredPreviewIconVectors(): void { const actualSize = renderToStaticMarkup() assert.match(actualSize, /d="M2\.5 0L1\.25 0L1\.25 1L0 1L0 2\.25/) const zoomIn = renderToStaticMarkup() assert.match(zoomIn, /viewBox="0 0 16 16"/) assert.match(zoomIn, /transform="matrix\(1 0 0 1 0\.882352 0\.882294\)"/) const close = renderToStaticMarkup() assert.match(close, /viewBox="0 0 12 12"/) assert.match(close, /d="M9\.375 2\.625L2\.625 9\.375"/) } async function main(): Promise { await testSafeImageSchemesAndResolution() testSafePreviewFilteringAndIndex() await testRejectedImageDownloadIsReported() testMarkdownImageServerFallback() testPreviewDialogServerStructure() testRecoveredPreviewIconVectors() console.log('image-renderer-regression: all checks passed') } void main().catch((error: unknown) => { console.error('image-renderer-regression failed:', error) process.exitCode = 1 })