import { describe, it, expect, beforeEach } from 'vitest'; import { readFileSync } from 'fs'; import { resolve } from 'path'; import { useInputBarStore } from '../../src/store/inputBar'; function createMockFile(name: string, type: string, sizeInBytes?: number): File { const content = sizeInBytes ? new Uint8Array(sizeInBytes) : new Uint8Array([0]); return new File([content], name, { type }); } describe('Bug Fix Verification — Previous Bugs', () => { describe('BUG-001: GenerationCard model display (was hardcoded)', () => { it('should use task.model dynamically in GenerationCard source', () => { const src = readFileSync( resolve(__dirname, '../../src/components/GenerationCard.tsx'), 'utf-8' ); // Should NOT contain a bare hardcoded "Seedance 2.0" in the meta section // Instead should reference task.model expect(src).toContain('task.model'); // The ternary pattern confirms dynamic rendering expect(src).toMatch(/task\.model\s*===\s*'seedance_2\.0'/); }); }); describe('BUG-002: File size validation', () => { it('UniversalUpload should enforce image <20MB and video <100MB limits', () => { const src = readFileSync( resolve(__dirname, '../../src/components/UniversalUpload.tsx'), 'utf-8' ); expect(src).toContain('20 * 1024 * 1024'); expect(src).toContain('100 * 1024 * 1024'); expect(src).toContain('图片文件不能超过20MB'); expect(src).toContain('视频文件不能超过100MB'); }); it('InputBar drag-drop should enforce file size limits', () => { const src = readFileSync( resolve(__dirname, '../../src/components/InputBar.tsx'), 'utf-8' ); expect(src).toContain('20 * 1024 * 1024'); expect(src).toContain('100 * 1024 * 1024'); }); it('KeyframeUpload should enforce image <20MB limit', () => { const src = readFileSync( resolve(__dirname, '../../src/components/KeyframeUpload.tsx'), 'utf-8' ); expect(src).toContain('20 * 1024 * 1024'); expect(src).toContain('图片文件不能超过20MB'); }); }); describe('Original BUG-001: canSubmit selector (was stale reference)', () => { it('Toolbar should call canSubmit() as invocation, not reference', () => { const src = readFileSync( resolve(__dirname, '../../src/components/Toolbar.tsx'), 'utf-8' ); // Must be s.canSubmit() — the function call, not s.canSubmit (reference) expect(src).toMatch(/s\.canSubmit\(\)/); }); }); describe('Original BUG-002: drag-drop audio filter', () => { it('InputBar drag-drop should only accept image/* and video/*', () => { const src = readFileSync( resolve(__dirname, '../../src/components/InputBar.tsx'), 'utf-8' ); // Filter should only allow image and video, not audio expect(src).toContain("f.type.startsWith('image/')"); expect(src).toContain("f.type.startsWith('video/')"); }); }); }); describe('File Upload Validation — Store Level', () => { beforeEach(() => { useInputBarStore.getState().reset(); }); it('should accept image files in universal mode', () => { const file = createMockFile('photo.jpg', 'image/jpeg'); useInputBarStore.getState().addReferences([file]); expect(useInputBarStore.getState().references).toHaveLength(1); expect(useInputBarStore.getState().references[0].type).toBe('image'); }); it('should accept video files in universal mode', () => { const file = createMockFile('clip.mp4', 'video/mp4'); useInputBarStore.getState().addReferences([file]); expect(useInputBarStore.getState().references).toHaveLength(1); expect(useInputBarStore.getState().references[0].type).toBe('video'); }); it('should accept only image files for first/last frames', () => { useInputBarStore.getState().switchMode('keyframe'); const file = createMockFile('frame.jpg', 'image/jpeg'); useInputBarStore.getState().setFirstFrame(file); expect(useInputBarStore.getState().firstFrame).not.toBeNull(); expect(useInputBarStore.getState().firstFrame!.type).toBe('image'); }); it('keyframe file input should only accept image/*', () => { const src = readFileSync( resolve(__dirname, '../../src/components/KeyframeUpload.tsx'), 'utf-8' ); // Both inputs should have accept="image/*" const matches = src.match(/accept="image\/\*"/g); expect(matches).not.toBeNull(); expect(matches!.length).toBe(2); }); }); describe('PRD Compliance — Code Structure Checks', () => { it('should have file upload accept limited to image/video in UniversalUpload', () => { const src = readFileSync( resolve(__dirname, '../../src/components/UniversalUpload.tsx'), 'utf-8' ); expect(src).toContain('accept="image/*,video/*"'); }); it('should have URL.revokeObjectURL calls for memory cleanup', () => { const storeSrc = readFileSync( resolve(__dirname, '../../src/store/inputBar.ts'), 'utf-8' ); const revokeCount = (storeSrc.match(/URL\.revokeObjectURL/g) || []).length; // Should have revokeObjectURL in: removeReference, clearReferences, setFirstFrame, setLastFrame, switchMode(x2), reset expect(revokeCount).toBeGreaterThanOrEqual(5); }); it('should preserve prompt text across mode switches', () => { useInputBarStore.getState().setPrompt('my test prompt'); useInputBarStore.getState().switchMode('keyframe'); expect(useInputBarStore.getState().prompt).toBe('my test prompt'); useInputBarStore.getState().switchMode('universal'); expect(useInputBarStore.getState().prompt).toBe('my test prompt'); }); }); describe('Dead Code Audit — Audio Dead Code Cleaned Up', () => { it('types/index.ts UploadedFile.type no longer includes audio', () => { const src = readFileSync( resolve(__dirname, '../../src/types/index.ts'), 'utf-8' ); // Audio type was cleaned up in a previous dev session const hasAudioType = src.includes("'audio'"); expect(hasAudioType).toBe(false); // Verified: audio dead code has been removed }); it('inputBar.ts addReferences no longer classifies audio files', () => { const src = readFileSync( resolve(__dirname, '../../src/store/inputBar.ts'), 'utf-8' ); const hasAudioClassification = src.includes("'audio'"); expect(hasAudioClassification).toBe(false); // Verified: audio dead code has been removed }); });