129 lines
4.2 KiB
TypeScript
129 lines
4.2 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
|
|
|
|
import uploaderUtils from './uploader-utils'
|
|
|
|
const {
|
|
clampOneLevel,
|
|
composeName,
|
|
extOf,
|
|
createNoResumeFingerprint,
|
|
createNoResumeStorage,
|
|
fmt,
|
|
isDetailedError,
|
|
isImageFile,
|
|
isLikelyMobileUA,
|
|
isVideoFile,
|
|
normalizeRows,
|
|
sanitizeDesc,
|
|
sanitizeFolderName,
|
|
stemOf,
|
|
} = uploaderUtils
|
|
|
|
describe('Uploader helpers', () => {
|
|
let logSpy: ReturnType<typeof vi.spyOn>
|
|
|
|
beforeAll(() => {
|
|
logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
})
|
|
|
|
afterAll(() => {
|
|
logSpy.mockRestore()
|
|
})
|
|
|
|
it('sanitizes description and folder names', () => {
|
|
expect(sanitizeDesc(' Hello world! ')).toBe('Hello_world_')
|
|
expect(sanitizeDesc('')).toBe('upload')
|
|
expect(sanitizeFolderName(' /my folder/with/depth ')).toBe('my_folder')
|
|
expect(sanitizeFolderName('bad#$name')).toBe('bad_name')
|
|
})
|
|
|
|
it('computes extension, stem, and composed final name', () => {
|
|
expect(extOf('clip.MP4')).toBe('mp4')
|
|
expect(extOf('noext')).toBe('')
|
|
expect(stemOf('My.Video.File.mp4')).toBe('My_Video_File')
|
|
expect(composeName('2026-04-09', 'Trip Clip', 'My.Video.File.mp4')).toBe(
|
|
'2026.04.09.Trip_Clip.My_Video_File.mp4',
|
|
)
|
|
})
|
|
|
|
it('clamps one-level paths and normalizes row payload shapes', () => {
|
|
expect(clampOneLevel('/photos/2026/trip/')).toBe('photos')
|
|
expect(clampOneLevel('')).toBe('')
|
|
|
|
const rows = normalizeRows([
|
|
{ Name: 'a', Path: 'a', IsDir: true, Size: 12, Mtime: 55 },
|
|
{ name: 'b', path: 'b', is_dir: false, size: 3, mtime: 99 },
|
|
null,
|
|
] as any)
|
|
|
|
expect(rows[0]).toEqual({ name: 'a', path: 'a', is_dir: true, size: 12, mtime: 55 })
|
|
expect(rows[1]).toEqual({ name: 'b', path: 'b', is_dir: false, size: 3, mtime: 99 })
|
|
expect(rows[2]).toEqual({ name: '', path: '', is_dir: false, size: 0, mtime: 0 })
|
|
})
|
|
|
|
it('detects video/image files by type and extension', () => {
|
|
const mkFile = (name: string, type: string) => new File(['x'], name, { type })
|
|
expect(isVideoFile(mkFile('x.mp4', 'video/mp4'))).toBe(true)
|
|
expect(isVideoFile(mkFile('x.mkv', ''))).toBe(true)
|
|
expect(isVideoFile(mkFile('x.txt', 'text/plain'))).toBe(false)
|
|
|
|
expect(isImageFile(mkFile('x.jpg', 'image/jpeg'))).toBe(true)
|
|
expect(isImageFile(mkFile('x.heic', ''))).toBe(true)
|
|
expect(isImageFile(mkFile('x.mp4', 'video/mp4'))).toBe(false)
|
|
})
|
|
|
|
it('identifies detailed tus errors and mobile UA heuristics', () => {
|
|
expect(isDetailedError({ originalRequest: {} })).toBe(true)
|
|
expect(isDetailedError({ originalResponse: {} })).toBe(true)
|
|
expect(isDetailedError({})).toBe(false)
|
|
|
|
expect(typeof isLikelyMobileUA()).toBe('boolean')
|
|
})
|
|
|
|
it('covers the mobile UA and size formatting branches', () => {
|
|
const originalMatchMedia = window.matchMedia
|
|
Object.defineProperty(window, 'matchMedia', {
|
|
configurable: true,
|
|
value: () => ({ matches: true }),
|
|
})
|
|
expect(isLikelyMobileUA()).toBe(true)
|
|
|
|
Object.defineProperty(window, 'matchMedia', {
|
|
configurable: true,
|
|
value: () => ({ matches: false }),
|
|
})
|
|
expect(isLikelyMobileUA()).toBe(false)
|
|
|
|
if (originalMatchMedia) {
|
|
Object.defineProperty(window, 'matchMedia', {
|
|
configurable: true,
|
|
value: originalMatchMedia,
|
|
})
|
|
} else {
|
|
delete (window as any).matchMedia
|
|
}
|
|
|
|
expect(fmt(1024 * 1024 * 1024)).toBe('1.0 GB')
|
|
})
|
|
|
|
it('formats sizes and exposes no-resume upload helpers', async () => {
|
|
expect(fmt(0)).toBe('0 B')
|
|
expect(fmt(1536)).toBe('1.5 KB')
|
|
|
|
const storage = createNoResumeStorage()
|
|
await expect(storage.addUpload({})).resolves.toBeUndefined()
|
|
await expect(storage.removeUpload({})).resolves.toBeUndefined()
|
|
await expect(storage.listUploads()).resolves.toEqual([])
|
|
await expect(storage.findUploadsByFingerprint('abc')).resolves.toEqual([])
|
|
await expect(createNoResumeFingerprint()).resolves.toMatch(/^noresume-/)
|
|
})
|
|
|
|
it('returns false without a window and normalizes non-array rows', () => {
|
|
const originalWindow = window
|
|
vi.stubGlobal('window', undefined as any)
|
|
expect(isLikelyMobileUA()).toBe(false)
|
|
vi.stubGlobal('window', originalWindow as any)
|
|
expect(normalizeRows('bad input' as any)).toEqual([])
|
|
})
|
|
})
|