- web/: React + Vite + TypeScript 前端 - backend/: Django + DRF + SimpleJWT 后端 - prototype/: HTML 设计原型 - docs/: PRD 和设计评审文档 - test: 单元测试 + E2E 极限测试
168 lines
6.4 KiB
TypeScript
168 lines
6.4 KiB
TypeScript
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
|
|
});
|
|
});
|