mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-08 22:39:36 +08:00
* fix: prevent OOM when handling large base64 image data - Add memory-safe parseDataUrl utility using string operations instead of regex - Truncate large base64 data in ErrorBlock detail modal to prevent freezing - Update ImageViewer, FileStorage, messageConverter to use shared parseDataUrl - Deprecate parseDataUrlMediaType in favor of shared utility - Add GB support to formatFileSize - Add comprehensive unit tests for parseDataUrl (18 tests) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: simplify parseDataUrl API to return DataUrlParts | null - Change return type from discriminated union to simple nullable type - Update all call sites to use optional chaining (?.) - Update tests to use toBeNull() for failure cases - More idiomatic and consistent with codebase patterns (e.g., parseJSON) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
139 lines
4.3 KiB
TypeScript
139 lines
4.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { isBase64ImageDataUrl, isDataUrl, parseDataUrl } from '../utils'
|
|
|
|
describe('parseDataUrl', () => {
|
|
it('parses a standard base64 image data URL', () => {
|
|
const result = parseDataUrl('data:image/png;base64,iVBORw0KGgo=')
|
|
expect(result).toEqual({
|
|
mediaType: 'image/png',
|
|
isBase64: true,
|
|
data: 'iVBORw0KGgo='
|
|
})
|
|
})
|
|
|
|
it('parses a base64 data URL with additional parameters', () => {
|
|
const result = parseDataUrl('data:image/jpeg;name=foo;base64,/9j/4AAQ')
|
|
expect(result).toEqual({
|
|
mediaType: 'image/jpeg',
|
|
isBase64: true,
|
|
data: '/9j/4AAQ'
|
|
})
|
|
})
|
|
|
|
it('parses a plain text data URL (non-base64)', () => {
|
|
const result = parseDataUrl('data:text/plain,Hello%20World')
|
|
expect(result).toEqual({
|
|
mediaType: 'text/plain',
|
|
isBase64: false,
|
|
data: 'Hello%20World'
|
|
})
|
|
})
|
|
|
|
it('parses a data URL with empty media type', () => {
|
|
const result = parseDataUrl('data:;base64,SGVsbG8=')
|
|
expect(result).toEqual({
|
|
mediaType: undefined,
|
|
isBase64: true,
|
|
data: 'SGVsbG8='
|
|
})
|
|
})
|
|
|
|
it('returns null for non-data URLs', () => {
|
|
const result = parseDataUrl('https://example.com/image.png')
|
|
expect(result).toBeNull()
|
|
})
|
|
|
|
it('returns null for malformed data URL without comma', () => {
|
|
const result = parseDataUrl('data:image/png;base64')
|
|
expect(result).toBeNull()
|
|
})
|
|
|
|
it('handles empty string', () => {
|
|
const result = parseDataUrl('')
|
|
expect(result).toBeNull()
|
|
})
|
|
|
|
it('handles large base64 data without performance issues', () => {
|
|
// Simulate a 4K image base64 string (about 1MB)
|
|
const largeData = 'A'.repeat(1024 * 1024)
|
|
const dataUrl = `data:image/png;base64,${largeData}`
|
|
|
|
const start = performance.now()
|
|
const result = parseDataUrl(dataUrl)
|
|
const duration = performance.now() - start
|
|
|
|
expect(result).not.toBeNull()
|
|
expect(result?.mediaType).toBe('image/png')
|
|
expect(result?.isBase64).toBe(true)
|
|
expect(result?.data).toBe(largeData)
|
|
// Should complete in under 10ms (string operations are fast)
|
|
expect(duration).toBeLessThan(10)
|
|
})
|
|
|
|
it('parses SVG data URL', () => {
|
|
const result = parseDataUrl('data:image/svg+xml;base64,PHN2Zz4=')
|
|
expect(result).toEqual({
|
|
mediaType: 'image/svg+xml',
|
|
isBase64: true,
|
|
data: 'PHN2Zz4='
|
|
})
|
|
})
|
|
|
|
it('parses JSON data URL', () => {
|
|
const result = parseDataUrl('data:application/json,{"key":"value"}')
|
|
expect(result).toEqual({
|
|
mediaType: 'application/json',
|
|
isBase64: false,
|
|
data: '{"key":"value"}'
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('isDataUrl', () => {
|
|
it('returns true for valid data URLs', () => {
|
|
expect(isDataUrl('data:image/png;base64,ABC')).toBe(true)
|
|
expect(isDataUrl('data:text/plain,hello')).toBe(true)
|
|
expect(isDataUrl('data:,simple')).toBe(true)
|
|
})
|
|
|
|
it('returns false for non-data URLs', () => {
|
|
expect(isDataUrl('https://example.com')).toBe(false)
|
|
expect(isDataUrl('file:///path/to/file')).toBe(false)
|
|
expect(isDataUrl('')).toBe(false)
|
|
})
|
|
|
|
it('returns false for malformed data URLs', () => {
|
|
expect(isDataUrl('data:')).toBe(false)
|
|
expect(isDataUrl('data:image/png')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('isBase64ImageDataUrl', () => {
|
|
it('returns true for base64 image data URLs', () => {
|
|
expect(isBase64ImageDataUrl('data:image/png;base64,ABC')).toBe(true)
|
|
expect(isBase64ImageDataUrl('data:image/jpeg;base64,/9j/')).toBe(true)
|
|
expect(isBase64ImageDataUrl('data:image/gif;base64,R0lG')).toBe(true)
|
|
expect(isBase64ImageDataUrl('data:image/webp;base64,UklG')).toBe(true)
|
|
})
|
|
|
|
it('returns false for non-base64 image data URLs', () => {
|
|
expect(isBase64ImageDataUrl('data:image/svg+xml,<svg></svg>')).toBe(false)
|
|
})
|
|
|
|
it('returns false for non-image data URLs', () => {
|
|
expect(isBase64ImageDataUrl('data:text/plain;base64,SGVsbG8=')).toBe(false)
|
|
expect(isBase64ImageDataUrl('data:application/json,{}')).toBe(false)
|
|
})
|
|
|
|
it('returns false for regular URLs', () => {
|
|
expect(isBase64ImageDataUrl('https://example.com/image.png')).toBe(false)
|
|
expect(isBase64ImageDataUrl('file:///image.png')).toBe(false)
|
|
})
|
|
|
|
it('returns false for malformed data URLs', () => {
|
|
expect(isBase64ImageDataUrl('data:image/png')).toBe(false)
|
|
expect(isBase64ImageDataUrl('')).toBe(false)
|
|
})
|
|
})
|