**docs: remove outdated statistics-related documentation and add standards**

- **Removed Files**:
  - Deleted outdated statistics documentation files (`statistics.md`, `statistics-flush-solution.md`, `statistics-summary.md`) to clean up the repository and avoid confusion.

- **Added Standards**:
  - Introduced `DOCUMENTATION_STANDARDS.md` to outline naming conventions and troubleshooting practices for more consistent and maintainable project documentation.

- **Tests**:
  - Added a new test file `edge-cases.test.ts` to verify handling of edge cases, ensuring robust behavior against boundary values and invalid inputs.

**Purpose**: Cleans up deprecated documentation while introducing concrete standards for maintaining and updating documentation. Enhances test coverage for unusual or boundary inputs, improving overall system resilience.
This commit is contained in:
David Snelling 2025-07-28 16:00:05 -07:00
parent 3337c9f78e
commit 94c88e128c
41 changed files with 5583 additions and 498 deletions

View file

@ -0,0 +1,219 @@
import { Reporter, File, Task, TaskResult, Vitest, TaskResultPack, RunnerTaskEventPack as TaskEventPack } from 'vitest'
/**
* PrettyReporter - A visually appealing reporter for Vitest
*
* This reporter creates a visually enhanced summary of test results
* with colors, symbols, and formatted output.
*/
export default class PrettyReporter implements Reporter {
private startTime: number = 0
private testResults: Map<string, { passed: number, failed: number, skipped: number, duration: number }> = new Map()
private totalTests: { passed: number, failed: number, skipped: number } = { passed: 0, failed: 0, skipped: 0 }
private failedTests: Array<{ file: string, name: string, error: string }> = []
private ctx: Vitest | undefined
onInit(ctx: Vitest): void {
this.ctx = ctx
this.startTime = Date.now()
console.log('\n🧠 \x1b[36mBrainy Test Suite\x1b[0m - Starting tests...\n')
}
onFinished(files?: File[] | undefined): void {
const duration = Date.now() - this.startTime
this.printSummary(duration)
}
onTaskUpdate(packs: TaskResultPack[], events: TaskEventPack[]): void {
for (const [id, result, meta] of packs) {
if (result) {
// Find the task in the state manager
if (this.ctx?.state) {
try {
// We need to handle the entity type carefully
const entity = this.ctx.state.getReportedEntity(id as unknown as Task)
// Skip if entity doesn't exist or doesn't have the right properties
if (!entity || typeof entity !== 'object') continue
// Check if it has the necessary properties to be treated as a Task
if ('type' in entity &&
'name' in entity &&
'file' in entity &&
entity.file &&
typeof entity.file === 'object' &&
'filepath' in entity.file) {
// Process the task with the right type
this.processTask({
id: id,
name: entity.name as string,
type: entity.type as any,
file: entity.file as any,
mode: (entity as any).mode || 'run',
result: result
} as unknown as Task)
}
} catch (error) {
console.warn(`Error processing task ${id}:`, error)
}
}
}
}
}
onCollected(files?: File[] | undefined): void {
if (files) {
for (const file of files) {
this.testResults.set(file.filepath, { passed: 0, failed: 0, skipped: 0, duration: 0 })
// Count tests in each file
if (file.tasks) {
this.countTestsInTasks(file.tasks)
}
}
}
}
private countTestsInTasks(tasks: Task[]): void {
for (const task of tasks) {
if (task.type === 'test') {
this.processTask(task)
} else if (task.tasks) {
// Recursively process nested tasks (like in describe blocks)
this.countTestsInTasks(task.tasks)
}
}
}
private processTask(task: Task): void {
if (!task || !task.file) return
// For test collection phase, we just want to count the test
if (task.type === 'test') {
// Make sure we have a valid file path
if (!task.file.filepath) return
const fileResult = this.testResults.get(task.file.filepath) || {
passed: 0, failed: 0, skipped: 0, duration: 0
}
// During collection, we don't have results yet, so just count the test
// The actual pass/fail status will be updated during task updates
if (!task.result) {
// Just count it as a test, state will be updated later
console.log(`Counting test: ${task.name} in ${task.file.name}`)
} else if (task.result.state === 'pass') {
fileResult.passed++
this.totalTests.passed++
console.log(`Passed test: ${task.name}`)
} else if (task.result.state === 'fail') {
fileResult.failed++
this.totalTests.failed++
this.failedTests.push({
file: task.file.name,
name: task.name,
error: task.result?.errors?.[0]?.message || 'Unknown error'
})
console.log(`Failed test: ${task.name}`)
} else if (task.mode === 'skip' || task.result.state === 'skip') {
fileResult.skipped++
this.totalTests.skipped++
console.log(`Skipped test: ${task.name}`)
}
if (task.result?.duration) {
fileResult.duration += task.result.duration
}
this.testResults.set(task.file.filepath, fileResult)
}
}
private printSummary(duration: number): void {
const durationStr = this.formatDuration(duration)
const totalTests = this.totalTests.passed + this.totalTests.failed + this.totalTests.skipped
console.log('\n')
console.log('━'.repeat(80))
console.log(`\n\x1b[1m\x1b[36m📊 TEST SUMMARY REPORT\x1b[0m`)
console.log('━'.repeat(80))
// Print overall stats
console.log(`\n\x1b[1mTest Run Completed in:\x1b[0m ${durationStr}`)
console.log(`\x1b[1mDate:\x1b[0m ${new Date().toLocaleString()}`)
console.log(`\x1b[1mTotal Test Files:\x1b[0m ${this.testResults.size}`)
console.log(`\x1b[1mTotal Tests:\x1b[0m ${totalTests}`)
// Print test status counts with colors and symbols
console.log(`\n\x1b[1mResults:\x1b[0m`)
console.log(` \x1b[32m✓ Passed:\x1b[0m ${this.totalTests.passed}`)
console.log(` \x1b[31m✗ Failed:\x1b[0m ${this.totalTests.failed}`)
console.log(` \x1b[33m○ Skipped:\x1b[0m ${this.totalTests.skipped}`)
// Print file results in a table format
console.log('\n\x1b[1mTest Files:\x1b[0m')
console.log('┌' + '─'.repeat(50) + '┬' + '─'.repeat(10) + '┬' + '─'.repeat(10) + '┬' + '─'.repeat(10) + '┐')
console.log('│ \x1b[1mFile\x1b[0m' + ' '.repeat(46) + '│ \x1b[1mPassed\x1b[0m' + ' '.repeat(4) + '│ \x1b[1mFailed\x1b[0m' + ' '.repeat(4) + '│ \x1b[1mSkipped\x1b[0m' + ' '.repeat(3) + '│')
console.log('├' + '─'.repeat(50) + '┼' + '─'.repeat(10) + '┼' + '─'.repeat(10) + '┼' + '─'.repeat(10) + '┤')
// Sort files by name for consistent output
const sortedFiles = Array.from(this.testResults.entries()).sort((a, b) => {
const fileNameA = a[0].split('/').pop() || ''
const fileNameB = b[0].split('/').pop() || ''
return fileNameA.localeCompare(fileNameB)
})
for (const [filepath, results] of sortedFiles) {
const fileName = filepath.split('/').pop() || filepath
const truncatedName = this.truncateString(fileName, 48)
const passedStr = `\x1b[32m${results.passed}\x1b[0m`
const failedStr = results.failed > 0 ? `\x1b[31m${results.failed}\x1b[0m` : `${results.failed}`
const skippedStr = results.skipped > 0 ? `\x1b[33m${results.skipped}\x1b[0m` : `${results.skipped}`
console.log(`${truncatedName}${' '.repeat(50 - truncatedName.length)}${passedStr}${' '.repeat(10 - passedStr.length + 9)}${failedStr}${' '.repeat(10 - failedStr.length + 9)}${skippedStr}${' '.repeat(10 - skippedStr.length + 9)}`)
}
console.log('└' + '─'.repeat(50) + '┴' + '─'.repeat(10) + '┴' + '─'.repeat(10) + '┴' + '─'.repeat(10) + '┘')
// Print failed tests if any
if (this.failedTests.length > 0) {
console.log('\n\x1b[1m\x1b[31m❌ Failed Tests:\x1b[0m')
for (let i = 0; i < this.failedTests.length; i++) {
const { file, name, error } = this.failedTests[i]
console.log(`\n ${i + 1}. \x1b[1m${file}\x1b[0m: ${name}`)
console.log(` \x1b[31m${error}\x1b[0m`)
}
}
// Print final status
console.log('\n')
if (this.totalTests.failed > 0) {
console.log('\x1b[41m\x1b[37m FAILED \x1b[0m Some tests failed. Check the report above for details.')
} else {
console.log('\x1b[42m\x1b[30m PASSED \x1b[0m All tests passed successfully!')
}
console.log('\n')
}
private formatDuration(ms: number): string {
if (ms < 1000) {
return `${ms}ms`
}
const seconds = Math.floor(ms / 1000)
const minutes = Math.floor(seconds / 60)
const remainingSeconds = seconds % 60
if (minutes === 0) {
return `${seconds}.${Math.floor((ms % 1000) / 100)}s`
}
return `${minutes}m ${remainingSeconds}s`
}
private truncateString(str: string, maxLength: number): string {
if (str.length <= maxLength) {
return str
}
return str.substring(0, maxLength - 3) + '...'
}
}

View file

@ -0,0 +1,210 @@
import { Reporter, File, Task, TaskResult, Vitest, TaskResultPack, RunnerTaskEventPack as TaskEventPack } from 'vitest'
/**
* PrettySummaryReporter - A visually appealing summary reporter for Vitest
*
* This reporter creates a visually enhanced summary of test results
* with colors, symbols, and formatted output at the end of the test run.
*/
export default class PrettySummaryReporter implements Reporter {
private startTime: number = 0
private testFiles: string[] = []
private testCounts = {
total: 0,
passed: 0,
failed: 0,
skipped: 0
}
private fileResults: Map<string, { passed: number, failed: number, skipped: number }> = new Map()
private failedTests: Array<{ file: string, name: string, error: string }> = []
private ctx: Vitest | undefined
onInit(ctx: Vitest): void {
this.ctx = ctx
this.startTime = Date.now()
console.log('\n🧠 \x1b[36mBrainy Test Suite\x1b[0m - Starting tests...\n')
}
onCollected(files?: File[]): void {
if (files && files.length > 0) {
for (const file of files) {
this.testFiles.push(file.filepath)
// Initialize file results
const fileResult = { passed: 0, failed: 0, skipped: 0 }
this.fileResults.set(file.filepath, fileResult)
// Count tests in this file and update total count
const countTests = (tasks: Task[]) => {
for (const task of tasks) {
if (task.type === 'test') {
// Count the test based on its mode
this.testCounts.total++
if (task.mode === 'skip') {
fileResult.skipped++
this.testCounts.skipped++
} else {
// Initially mark as passed, will be updated if it fails
fileResult.passed++
this.testCounts.passed++
}
}
// Check if task is a Suite which has tasks property
if ('tasks' in task && Array.isArray(task.tasks) && task.tasks.length > 0) {
countTests(task.tasks)
}
}
}
if (file.tasks) {
countTests(file.tasks)
}
}
}
}
onFinished(files?: File[]): void {
const duration = Date.now() - this.startTime
this.printSummary(duration)
}
onTaskUpdate(packs: TaskResultPack[], events: TaskEventPack[]): void {
for (const [id, result, meta] of packs) {
if (!result) continue
// Find the task in the state manager
if (!this.ctx?.state) continue
try {
const entity = this.ctx.state.getReportedEntity(id as unknown as Task)
if (!entity || !('type' in entity) || entity.type !== 'test') continue
// Safely access file properties
if (!('file' in entity) || !entity.file) continue
const file = entity.file as { filepath: string; name: string }
const filepath = file.filepath
const fileName = file.name
// Get file results (should already be initialized in onCollected)
if (!this.fileResults.has(filepath)) {
// If for some reason the file wasn't processed in onCollected, initialize it now
this.fileResults.set(filepath, { passed: 0, failed: 0, skipped: 0 })
if (!this.testFiles.includes(filepath)) {
this.testFiles.push(filepath)
}
}
const fileResult = this.fileResults.get(filepath)!
// Only handle failures - we already counted passes during collection
if (result.state === 'fail') {
// Update counts: decrement passed, increment failed
fileResult.passed--
fileResult.failed++
this.testCounts.passed--
this.testCounts.failed++
// Track the failed test
this.failedTests.push({
file: fileName,
name: entity.name,
error: result.errors?.[0]?.message || 'Unknown error'
})
}
} catch (error) {
console.warn(`Error processing task ${id}:`, error)
}
}
}
private printSummary(duration: number): void {
const durationStr = this.formatDuration(duration)
console.log('\n')
console.log('━'.repeat(80))
console.log(`\n\x1b[1m\x1b[36m📊 TEST SUMMARY REPORT\x1b[0m`)
console.log('━'.repeat(80))
// Print overall stats
console.log(`\n\x1b[1mTest Run Completed in:\x1b[0m ${durationStr}`)
console.log(`\x1b[1mDate:\x1b[0m ${new Date().toLocaleString()}`)
console.log(`\x1b[1mTotal Test Files:\x1b[0m ${this.testFiles.length}`)
console.log(`\x1b[1mTotal Tests:\x1b[0m ${this.testCounts.total}`)
// Print test status counts with colors and symbols
console.log(`\n\x1b[1mResults:\x1b[0m`)
console.log(` \x1b[32m✓ Passed:\x1b[0m ${this.testCounts.passed}`)
console.log(` \x1b[31m✗ Failed:\x1b[0m ${this.testCounts.failed}`)
console.log(` \x1b[33m○ Skipped:\x1b[0m ${this.testCounts.skipped}`)
// Print file results in a table format
console.log('\n\x1b[1mTest Files:\x1b[0m')
console.log('┌' + '─'.repeat(50) + '┬' + '─'.repeat(10) + '┬' + '─'.repeat(10) + '┬' + '─'.repeat(10) + '┐')
console.log('│ \x1b[1mFile\x1b[0m' + ' '.repeat(46) + '│ \x1b[1mPassed\x1b[0m' + ' '.repeat(4) + '│ \x1b[1mFailed\x1b[0m' + ' '.repeat(4) + '│ \x1b[1mSkipped\x1b[0m' + ' '.repeat(3) + '│')
console.log('├' + '─'.repeat(50) + '┼' + '─'.repeat(10) + '┼' + '─'.repeat(10) + '┼' + '─'.repeat(10) + '┤')
// Sort files by name for consistent output
const sortedFiles = Array.from(this.fileResults.entries()).sort((a, b) => {
const fileNameA = a[0].split('/').pop() || ''
const fileNameB = b[0].split('/').pop() || ''
return fileNameA.localeCompare(fileNameB)
})
for (const [filepath, results] of sortedFiles) {
const fileName = filepath.split('/').pop() || filepath
const truncatedName = this.truncateString(fileName, 48)
const passedStr = `\x1b[32m${results.passed}\x1b[0m`
const failedStr = results.failed > 0 ? `\x1b[31m${results.failed}\x1b[0m` : `${results.failed}`
const skippedStr = results.skipped > 0 ? `\x1b[33m${results.skipped}\x1b[0m` : `${results.skipped}`
console.log(`${truncatedName}${' '.repeat(50 - truncatedName.length)}${passedStr}${' '.repeat(10 - passedStr.length + 9)}${failedStr}${' '.repeat(10 - failedStr.length + 9)}${skippedStr}${' '.repeat(10 - skippedStr.length + 9)}`)
}
console.log('└' + '─'.repeat(50) + '┴' + '─'.repeat(10) + '┴' + '─'.repeat(10) + '┴' + '─'.repeat(10) + '┘')
// Print failed tests if any
if (this.failedTests.length > 0) {
console.log('\n\x1b[1m\x1b[31m❌ Failed Tests:\x1b[0m')
for (let i = 0; i < this.failedTests.length; i++) {
const { file, name, error } = this.failedTests[i]
console.log(`\n ${i + 1}. \x1b[1m${file}\x1b[0m: ${name}`)
console.log(` \x1b[31m${error}\x1b[0m`)
}
}
// Print final status
console.log('\n')
if (this.testCounts.failed > 0) {
console.log('\x1b[41m\x1b[37m FAILED \x1b[0m Some tests failed. Check the report above for details.')
} else if (this.testCounts.total === 0) {
console.log('\x1b[43m\x1b[30m WARNING \x1b[0m No tests were run!')
} else {
console.log('\x1b[42m\x1b[30m PASSED \x1b[0m All tests passed successfully!')
}
console.log('\n')
}
private formatDuration(ms: number): string {
if (ms < 1000) {
return `${ms}ms`
}
const seconds = Math.floor(ms / 1000)
const minutes = Math.floor(seconds / 60)
const remainingSeconds = seconds % 60
if (minutes === 0) {
return `${seconds}.${Math.floor((ms % 1000) / 100)}s`
}
return `${minutes}m ${remainingSeconds}s`
}
private truncateString(str: string, maxLength: number): string {
if (str.length <= maxLength) {
return str
}
return str.substring(0, maxLength - 3) + '...'
}
}