From a1d2fbd96bb6713c3172198d71e94274af409aee Mon Sep 17 00:00:00 2001 From: naoNao89 <90588855+naoNao89@users.noreply.github.com> Date: Thu, 31 Jul 2025 05:29:42 +0700 Subject: [Editor] Prevent excessive project creation from invalid routes Race conditions in useEffect hook caused navigation to invalid project IDs to create multiple 'Untitled Project' entries. Users reported accumulating 885+ projects due to this bug. This change adds global invalid project ID tracking across component remounts and prevents race conditions through: - Global invalidProjectIds Set in project store with tracking functions - isInitializingRef to prevent duplicate initialization attempts - Proper useEffect cleanup with cancellation tokens - Specific error handling for 'not found' vs recoverable errors - Removed problematic activeProject?.id dependency Testing: - Add comprehensive test suite (15 tests) with Vitest + React Testing Library - Include integration tests demonstrating bug and validating fix - Update CI pipeline to run automated tests Fixes #182 --- apps/web/scripts/test-excessive-projects.ts | 205 ++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 apps/web/scripts/test-excessive-projects.ts (limited to 'apps/web/scripts') diff --git a/apps/web/scripts/test-excessive-projects.ts b/apps/web/scripts/test-excessive-projects.ts new file mode 100644 index 00000000..a8451ddd --- /dev/null +++ b/apps/web/scripts/test-excessive-projects.ts @@ -0,0 +1,205 @@ +#!/usr/bin/env bun + +/** + * CLI Test Runner: Excessive Project Creation Bug + * + * This script runs comprehensive tests to validate the excessive project creation bug + * and can be executed from the command line like `cargo test`. + * + * Usage: + * bun run scripts/test-excessive-projects.ts + * bun run test:excessive-projects + */ + +import { spawn } from 'child_process' +import { existsSync } from 'fs' +import path from 'path' + +interface TestResult { + name: string + passed: boolean + duration: number + error?: string +} + +class ExcessiveProjectsTestRunner { + private results: TestResult[] = [] + private startTime: number = 0 + + constructor() { + console.log('๐Ÿงช Excessive Project Creation Bug - Test Runner') + console.log('=' .repeat(60)) + } + + async runAllTests(): Promise { + this.startTime = Date.now() + + try { + // Check if test files exist + await this.validateTestEnvironment() + + // Run unit tests + await this.runUnitTests() + + // Run integration tests + await this.runIntegrationTests() + + // Run specific excessive projects tests + await this.runExcessiveProjectsTests() + + // Generate report + this.generateReport() + + } catch (error) { + console.error('โŒ Test runner failed:', error) + process.exit(1) + } + } + + private async validateTestEnvironment(): Promise { + console.log('๐Ÿ” Validating test environment...') + + const requiredFiles = [ + 'src/__tests__/setup.ts', + 'src/__tests__/excessive-projects.test.ts', + 'src/__tests__/editor-page-integration.test.tsx', + 'vitest.config.ts' + ] + + for (const file of requiredFiles) { + if (!existsSync(path.join(process.cwd(), file))) { + throw new Error(`Required test file not found: ${file}`) + } + } + + console.log('โœ… Test environment validated') + } + + private async runUnitTests(): Promise { + console.log('\n๐Ÿ“‹ Running Unit Tests...') + + const result = await this.runVitest([ + 'src/__tests__/excessive-projects.test.ts', + '--reporter=verbose' + ]) + + this.results.push({ + name: 'Unit Tests - Excessive Projects Logic', + passed: result.success, + duration: result.duration, + error: result.error + }) + } + + private async runIntegrationTests(): Promise { + console.log('\n๐Ÿ”— Running Integration Tests...') + + const result = await this.runVitest([ + 'src/__tests__/editor-page-integration.test.tsx', + '--reporter=verbose' + ]) + + this.results.push({ + name: 'Integration Tests - Editor Page Component', + passed: result.success, + duration: result.duration, + error: result.error + }) + } + + private async runExcessiveProjectsTests(): Promise { + console.log('\n๐Ÿšจ Running Excessive Projects Bug Tests...') + + const result = await this.runVitest([ + 'src/__tests__/excessive-projects.test.ts', + 'src/__tests__/editor-page-integration.test.tsx', + '--reporter=verbose', + '--run' + ]) + + this.results.push({ + name: 'Bug Reproduction Tests - All Scenarios', + passed: result.success, + duration: result.duration, + error: result.error + }) + } + + private async runVitest(args: string[]): Promise<{ success: boolean; duration: number; error?: string }> { + const startTime = Date.now() + + return new Promise((resolve) => { + const vitestProcess = spawn('bunx', ['vitest', ...args], { + stdio: 'inherit', + cwd: process.cwd() + }) + + vitestProcess.on('close', (code) => { + const duration = Date.now() - startTime + resolve({ + success: code === 0, + duration, + error: code !== 0 ? `Process exited with code ${code}` : undefined + }) + }) + + vitestProcess.on('error', (error) => { + const duration = Date.now() - startTime + resolve({ + success: false, + duration, + error: error.message + }) + }) + }) + } + + private generateReport(): void { + const totalDuration = Date.now() - this.startTime + const passedTests = this.results.filter(r => r.passed).length + const totalTests = this.results.length + + console.log('\n' + '='.repeat(60)) + console.log('๐Ÿ“Š TEST RESULTS SUMMARY') + console.log('='.repeat(60)) + + this.results.forEach((result, index) => { + const status = result.passed ? 'โœ… PASS' : 'โŒ FAIL' + const duration = `${result.duration}ms` + + console.log(`${index + 1}. ${status} ${result.name} (${duration})`) + + if (result.error) { + console.log(` Error: ${result.error}`) + } + }) + + console.log('\n' + '-'.repeat(60)) + console.log(`๐Ÿ“ˆ Overall: ${passedTests}/${totalTests} tests passed`) + console.log(`โฑ๏ธ Total duration: ${totalDuration}ms`) + + if (passedTests === totalTests) { + console.log('๐ŸŽ‰ All tests passed! Bug reproduction confirmed.') + } else { + console.log('โš ๏ธ Some tests failed. Check the output above.') + process.exit(1) + } + + console.log('\n๐Ÿ’ก To run individual test suites:') + console.log(' bun run test src/__tests__/excessive-projects.test.ts') + console.log(' bun run test src/__tests__/editor-page-integration.test.tsx') + console.log('\n๐Ÿ”ง To run with coverage:') + console.log(' bun run test:coverage') + } +} + +// CLI execution +if (import.meta.main) { + const runner = new ExcessiveProjectsTestRunner() + runner.runAllTests().catch((error) => { + console.error('Fatal error:', error) + process.exit(1) + }) +} + +export { ExcessiveProjectsTestRunner } -- cgit v1.3.1