csa / test /unit-image-paths.mjs
ricebug's picture
Upload 76 files
ca8ab2d verified
/**
* test/unit-image-paths.mjs
*
* 单元测试:图片路径提取与本地路径识别
* 运行方式:node test/unit-image-paths.mjs
*/
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(` ✅ ${name}`);
passed++;
} catch (e) {
console.error(` ❌ ${name}`);
console.error(` ${e.message}`);
failed++;
}
}
function assert(condition, msg) {
if (!condition) throw new Error(msg || 'Assertion failed');
}
function assertEqual(a, b, msg) {
const as = JSON.stringify(a), bs = JSON.stringify(b);
if (as !== bs) throw new Error(msg || `Expected ${bs}, got ${as}`);
}
function normalizeFileUrlToLocalPath(url) {
if (!url.startsWith('file:///')) return url;
const rawPath = url.slice('file:///'.length);
let decodedPath = rawPath;
try {
decodedPath = decodeURIComponent(rawPath);
} catch {
// 忽略非法编码,保留原始路径
}
return /^[A-Za-z]:[\\/]/.test(decodedPath)
? decodedPath
: '/' + decodedPath;
}
function extractImageUrlsFromText(text) {
const urls = [];
const fileRe = /file:\/\/\/([^\s"')\]]+\.(?:jpg|jpeg|png|gif|webp|bmp|svg))/gi;
for (const m of text.matchAll(fileRe)) {
const normalizedPath = normalizeFileUrlToLocalPath(`file:///${m[1]}`);
urls.push(normalizedPath);
}
const httpRe = /(https?:\/\/[^\s"')\]]+\.(?:jpg|jpeg|png|gif|webp|bmp|svg)(?:\?[^\s"')\]]*)?)/gi;
for (const m of text.matchAll(httpRe)) {
if (!urls.includes(m[1])) urls.push(m[1]);
}
const localRe = /(?:^|[\s"'(\[,:])((?:\/(?!\/)|[A-Za-z]:[\\/])[^\s"')\]]+\.(?:jpg|jpeg|png|gif|webp|bmp|svg))/gi;
for (const m of text.matchAll(localRe)) {
const localPath = m[1].trim();
const fullMatch = m[0];
const matchStart = m.index ?? 0;
const pathOffsetInMatch = fullMatch.lastIndexOf(localPath);
const pathStart = matchStart + Math.max(pathOffsetInMatch, 0);
const beforePath = text.slice(Math.max(0, pathStart - 12), pathStart);
if (/file:\/\/\/[A-Za-z]:$/i.test(beforePath)) continue;
if (localPath.startsWith('//')) continue;
if (!urls.includes(localPath)) urls.push(localPath);
}
return [...new Set(urls)];
}
function isLocalPath(imageUrl) {
return /^(\/|~\/|[A-Za-z]:[\\/])/.test(imageUrl);
}
console.log('\n📦 [1] 协议相对 URL 排除\n');
test('不提取 //example.com/image.jpg', () => {
const text = 'look //example.com/image.jpg and https://example.com/real.jpg';
const urls = extractImageUrlsFromText(text);
assertEqual(urls, ['https://example.com/real.jpg']);
});
console.log('\n📦 [2] file:// Windows 路径归一化\n');
test('file:///C:/Users/name/a.jpg → C:/Users/name/a.jpg', () => {
const text = 'please inspect file:///C:/Users/name/a.jpg';
const urls = extractImageUrlsFromText(text);
assertEqual(urls, ['C:/Users/name/a.jpg']);
});
test('file:///Users/name/a.jpg → /Users/name/a.jpg', () => {
const text = 'please inspect file:///Users/name/a.jpg';
const urls = extractImageUrlsFromText(text);
assertEqual(urls, ['/Users/name/a.jpg']);
});
test('直接 image block 的 file:// URL 也能归一化', () => {
assertEqual(
normalizeFileUrlToLocalPath('file:///C:/Users/name/a.jpg'),
'C:/Users/name/a.jpg'
);
assertEqual(
normalizeFileUrlToLocalPath('file:///Users/name/a.jpg'),
'/Users/name/a.jpg'
);
});
console.log('\n📦 [3] Windows 本地路径识别\n');
test('提取 C:\\Users\\name\\a.jpg', () => {
const text = '看看这张图 C:\\Users\\name\\a.jpg';
const urls = extractImageUrlsFromText(text);
assertEqual(urls, ['C:\\Users\\name\\a.jpg']);
});
test('提取 C:/Users/name/a.jpg', () => {
const text = '看看这张图 C:/Users/name/a.jpg';
const urls = extractImageUrlsFromText(text);
assertEqual(urls, ['C:/Users/name/a.jpg']);
});
test('Windows 路径被视为本地文件', () => {
assert(isLocalPath('C:\\Users\\name\\a.jpg'), 'backslash path should be local');
assert(isLocalPath('C:/Users/name/a.jpg'), 'slash path should be local');
assert(isLocalPath(normalizeFileUrlToLocalPath('file:///C:/Users/name/a.jpg')), 'normalized file URL should be local');
assert(isLocalPath(normalizeFileUrlToLocalPath('file:///Users/name/a.jpg')), 'normalized unix file URL should be local');
});
console.log('\n' + '═'.repeat(55));
console.log(` 结果: ${passed} 通过 / ${failed} 失败 / ${passed + failed} 总计`);
console.log('═'.repeat(55) + '\n');
if (failed > 0) process.exit(1);