Files
testing-tool/utils/__tests__/base64Converter.test.ts
T
LingandRX 7167a43763 Develop (#25)
核心功能新增
工具套件扩展
JSON 工具(差异比较、格式化、转 YAML/TOML、压缩)
Base64 转换器(文本/文件/图像编解码)
Markdown ↔ HTML 双向转换
JWT 解析器
文本统计工具
二维码生成/解析(替换库以减小体积)
表单工具(已移除)
表单映射、智能填充、高亮定位功能已移除
架构与工程改进
技术栈升级
引入 @webext-core/messaging 重构消息通信
添加 @dnd-kit 支持功能列表拖拽排序
引入 i18next 实现中英文国际化
使用 MUI 主题系统替代硬编码样式,支持暗色模式
代码质量
统一组件导出为默认导出
提取公共组件(TextInputArea、SwitchButtonGroup、PageHeader、DecodeResultPaper)
使用 useStorageState 钩子统一管理存储状态
添加 PageErrorBoundary 页面级错误边界
实现组件懒加载优化首屏性能
关键重构
路由系统重构 — 支持独立标签页模式,合并路由与功能配置
状态管理优化 — 存储状态增加快照机制、验证器和防抖处理
剪贴板与打印工具 — 统一为 async/await 形式,修复重复触发问题
存储清理器 — 使用 TextEncoder 准确计算 UTF-8 字节数,支持 TB 单位
测试与 CI
测试覆盖 — 为新增工具、公共组件、边界场景补充单元测试
CI 精简 — 移除 Firefox 测试,仅保留 Chrome;修正类型检查命令
2026-05-20 19:55:49 +08:00

359 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from 'vitest';
import {
textToBase64,
base64ToText,
isValidBase64,
isFileSizeValid,
isSupportedImageType,
isSupportedImageExtension,
extractMimeTypeFromDataUri,
formatFileSize,
base64ToBytes,
sniffMimeFromBytes,
base64ToBlob,
MAX_FILE_SIZE,
} from '@/utils/base64Converter';
describe('textToBase64', () => {
it('应该编码 ASCII 文本', () => {
const result = textToBase64('Hello, World!');
expect(result.output).toBe('SGVsbG8sIFdvcmxkIQ==');
expect(result.originalBytes).toBe(13);
});
it('应该编码中文文本', () => {
const result = textToBase64('你好世界');
expect(result.output).toBeTruthy();
// 验证可以正确解码回来
const decoded = base64ToText(result.output);
expect(decoded).toBe('你好世界');
});
it('应该编码空字符串', () => {
const result = textToBase64('');
expect(result.output).toBe('');
expect(result.originalBytes).toBe(0);
});
it('应该编码包含特殊字符的文本', () => {
const text = 'line1\nline2\ttab';
const result = textToBase64(text);
const decoded = base64ToText(result.output);
expect(decoded).toBe(text);
});
it('应该编码 Unicode 表情符号', () => {
const text = '🎉🚀';
const result = textToBase64(text);
const decoded = base64ToText(result.output);
expect(decoded).toBe(text);
});
});
describe('base64ToText', () => {
it('应该解码标准 Base64 字符串', () => {
const result = base64ToText('SGVsbG8sIFdvcmxkIQ==');
expect(result).toBe('Hello, World!');
});
it('应该解码中文 Base64', () => {
const encoded = textToBase64('测试文本');
const decoded = base64ToText(encoded.output);
expect(decoded).toBe('测试文本');
});
it('应该对无效 Base64 抛出错误', () => {
expect(() => base64ToText('这不是base64!!!')).toThrow();
});
it('应该修剪输入字符串的空白', () => {
const result = base64ToText(' SGVsbG8= ');
expect(result).toBe('Hello');
});
it('应该自动剥离 data:<mime>;base64, 前缀后再解码', () => {
// "hello" -> base64 "aGVsbG8="
const result = base64ToText('data:text/plain;base64,aGVsbG8=');
expect(result).toBe('hello');
});
it('应该处理带参数(如 charset)的 data URI 前缀', () => {
const result = base64ToText('data:text/plain;charset=utf-8;base64,aGVsbG8=');
expect(result).toBe('hello');
});
it('应该剥离带空白的 data URI 前缀', () => {
const result = base64ToText(' data:text/plain;base64,aGVsbG8= ');
expect(result).toBe('hello');
});
it('应该对二进制(如 PNG)数据抛出更清晰的错误', () => {
// PNG 文件签名 89 50 4E 47 0D 0A 1A 0A 的 Base64 编码
const pngSignatureBase64 = 'iVBORw0KGgo=';
expect(() => base64ToText(pngSignatureBase64)).toThrow(/binary|二进制|image|图像/i);
});
it('应该对带 data:image/png 前缀的 PNG 数据抛出二进制错误', () => {
const pngDataUri = 'data:image/png;base64,iVBORw0KGgo=';
expect(() => base64ToText(pngDataUri)).toThrow(/binary|二进制|image|图像/i);
});
});
describe('isValidBase64', () => {
it('应该返回 true 对于合法的 Base64', () => {
expect(isValidBase64('SGVsbG8=')).toBe(true);
expect(isValidBase64('SGVsbG8sIFdvcmxkIQ==')).toBe(true);
});
it('应该返回 false 对于空字符串', () => {
expect(isValidBase64('')).toBe(false);
});
it('应该返回 false 对于非法字符', () => {
expect(isValidBase64('SGVsbG8!')).toBe(false);
});
it('应该返回 false 对于长度不是 4 的倍数', () => {
expect(isValidBase64('SGVsbG8')).toBe(false); // 7 chars, not divisible by 4
});
});
describe('isFileSizeValid', () => {
it('应该对正常大小的文件返回 true', () => {
expect(isFileSizeValid(1024)).toBe(true);
expect(isFileSizeValid(MAX_FILE_SIZE)).toBe(true);
});
it('应该对 0 字节返回 false', () => {
expect(isFileSizeValid(0)).toBe(false);
});
it('应该对超出限制的文件返回 false', () => {
expect(isFileSizeValid(MAX_FILE_SIZE + 1)).toBe(false);
});
it('应该对负数返回 false', () => {
expect(isFileSizeValid(-1)).toBe(false);
});
});
describe('isSupportedImageType', () => {
it('应该识别支持的图像类型', () => {
expect(isSupportedImageType('image/png')).toBe(true);
expect(isSupportedImageType('image/jpeg')).toBe(true);
expect(isSupportedImageType('image/webp')).toBe(true);
expect(isSupportedImageType('image/gif')).toBe(true);
expect(isSupportedImageType('image/svg+xml')).toBe(true);
});
it('应该拒绝不支持的 MIME 类型', () => {
expect(isSupportedImageType('application/pdf')).toBe(false);
expect(isSupportedImageType('text/plain')).toBe(false);
expect(isSupportedImageType('video/mp4')).toBe(false);
});
});
describe('isSupportedImageExtension', () => {
it('应该识别支持的图像扩展名', () => {
expect(isSupportedImageExtension('photo.png')).toBe(true);
expect(isSupportedImageExtension('photo.JPG')).toBe(true);
expect(isSupportedImageExtension('photo.webp')).toBe(true);
expect(isSupportedImageExtension('photo.gif')).toBe(true);
});
it('应该拒绝不支持的扩展名', () => {
expect(isSupportedImageExtension('document.pdf')).toBe(false);
expect(isSupportedImageExtension('video.mp4')).toBe(false);
});
});
describe('extractMimeTypeFromDataUri', () => {
it('应该从 data URI 中提取 MIME 类型', () => {
expect(extractMimeTypeFromDataUri('data:image/png;base64,iVBOR')).toBe('image/png');
expect(extractMimeTypeFromDataUri('data:text/plain;base64,SGVsbG8=')).toBe('text/plain');
});
it('应该从带参数的 data URI 中提取 MIME 类型', () => {
expect(extractMimeTypeFromDataUri('data:text/plain;charset=utf-8;base64,SGVsbG8=')).toBe(
'text/plain',
);
expect(extractMimeTypeFromDataUri('data:image/svg+xml;base64,PHN2Zw==')).toBe('image/svg+xml');
});
it('应该对无效的 data URI 返回默认类型', () => {
expect(extractMimeTypeFromDataUri('invalid')).toBe('application/octet-stream');
});
});
describe('formatFileSize', () => {
it('应该格式化字节', () => {
expect(formatFileSize(0)).toBe('0 B');
expect(formatFileSize(512)).toBe('512 B');
});
it('应该格式化 KB', () => {
expect(formatFileSize(1024)).toBe('1.0 KB');
expect(formatFileSize(1536)).toBe('1.5 KB');
});
it('应该格式化 MB', () => {
expect(formatFileSize(1048576)).toBe('1.00 MB');
});
it('应该格式化 GB', () => {
expect(formatFileSize(1073741824)).toBe('1.00 GB');
});
});
describe('base64ToBytes', () => {
it('应该解码标准 ASCII Base64 为字节序列', () => {
const bytes = base64ToBytes('aGVsbG8=');
expect(Array.from(bytes)).toEqual([0x68, 0x65, 0x6c, 0x6c, 0x6f]);
});
it('应该正确解码 PNG 文件签名', () => {
// PNG 签名:89 50 4E 47 0D 0A 1A 0A
const bytes = base64ToBytes('iVBORw0KGgo=');
expect(Array.from(bytes)).toEqual([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
});
it('应该返回空 Uint8Array 对于空字符串输入', () => {
const bytes = base64ToBytes('');
expect(bytes.length).toBe(0);
});
});
describe('sniffMimeFromBytes', () => {
it('应该识别 PNG', () => {
const bytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
expect(sniffMimeFromBytes(bytes)).toEqual({ mime: 'image/png', ext: '.png' });
});
it('应该识别 JPEG', () => {
const bytes = new Uint8Array([0xff, 0xd8, 0xff, 0xe0]);
expect(sniffMimeFromBytes(bytes)).toEqual({ mime: 'image/jpeg', ext: '.jpg' });
});
it('应该识别 GIF', () => {
const bytes = new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61]);
expect(sniffMimeFromBytes(bytes)).toEqual({ mime: 'image/gif', ext: '.gif' });
});
it('应该识别 BMP', () => {
const bytes = new Uint8Array([0x42, 0x4d, 0x36, 0x00, 0x00, 0x00]);
expect(sniffMimeFromBytes(bytes)).toEqual({ mime: 'image/bmp', ext: '.bmp' });
});
it('应该识别 WebPRIFF....WEBP 复合签名)', () => {
const bytes = new Uint8Array([
0x52,
0x49,
0x46,
0x46, // "RIFF"
0x24,
0x00,
0x00,
0x00, // 文件大小占位
0x57,
0x45,
0x42,
0x50, // "WEBP"
0x56,
0x50,
0x38,
0x20, // "VP8 " 子块
]);
expect(sniffMimeFromBytes(bytes)).toEqual({ mime: 'image/webp', ext: '.webp' });
});
it('不应将 WAVRIFF 容器但非 WEBP)识别为 WebP', () => {
const bytes = new Uint8Array([
0x52,
0x49,
0x46,
0x46, // "RIFF"
0x24,
0x00,
0x00,
0x00,
0x57,
0x41,
0x56,
0x45, // "WAVE"
]);
expect(sniffMimeFromBytes(bytes)).toBeNull();
});
it('应该识别 PDF', () => {
const bytes = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d]);
expect(sniffMimeFromBytes(bytes)).toEqual({ mime: 'application/pdf', ext: '.pdf' });
});
it('应该识别 ZIP', () => {
const bytes = new Uint8Array([0x50, 0x4b, 0x03, 0x04]);
expect(sniffMimeFromBytes(bytes)).toEqual({ mime: 'application/zip', ext: '.zip' });
});
it('应该对未匹配的字节返回 null', () => {
const bytes = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
expect(sniffMimeFromBytes(bytes)).toBeNull();
});
it('应该对过短的字节返回 null', () => {
const bytes = new Uint8Array([0x89]);
expect(sniffMimeFromBytes(bytes)).toBeNull();
});
});
describe('base64ToBlob', () => {
it('应该优先使用 data URI 中的 MIME 类型', () => {
const result = base64ToBlob('data:application/json;base64,eyJhIjoxfQ==');
expect(result.mimeType).toBe('application/json');
expect(result.blob).toBeInstanceOf(Blob);
expect(result.blob.size).toBe(7);
});
it('应该通过魔数识别 PNG', () => {
const result = base64ToBlob('iVBORw0KGgo=');
expect(result.mimeType).toBe('image/png');
expect(result.suggestedExtension).toBe('.png');
});
it('应该通过魔数识别 PDF', () => {
// "%PDF-" + 一些字节
const result = base64ToBlob('JVBERi0K');
expect(result.mimeType).toBe('application/pdf');
expect(result.suggestedExtension).toBe('.pdf');
});
it('应该对未匹配的纯 Base64 回退为 application/octet-stream + .bin', () => {
const result = base64ToBlob('AAECAwQF');
expect(result.mimeType).toBe('application/octet-stream');
expect(result.suggestedExtension).toBe('.bin');
});
it('应该 trim 前后空白', () => {
const result = base64ToBlob(' iVBORw0KGgo= ');
expect(result.mimeType).toBe('image/png');
});
it('应该对非法 Base64 抛出 Invalid Base64 string', () => {
expect(() => base64ToBlob('这不是 base64!')).toThrow('Invalid Base64 string');
});
it('应该返回原始 Base64(已去除 data URI 前缀)', () => {
const result = base64ToBlob('data:image/png;base64,iVBORw0KGgo=');
expect(result.rawBase64).toBe('iVBORw0KGgo=');
});
it('应该处理带参数的 data URI 前缀', () => {
const result = base64ToBlob('data:text/plain;charset=utf-8;base64,aGVsbG8=');
expect(result.rawBase64).toBe('aGVsbG8=');
expect(result.mimeType).toBe('text/plain');
});
it('blob 大小应该等于解码后的字节数', () => {
const result = base64ToBlob('aGVsbG8=');
expect(result.blob.size).toBe(5);
});
});