summary-generator-supplement.test.js
1 /** 2 * Supplement tests for src/utils/summary-generator.js 3 * 4 * Covers uncovered functions: 5 * - generatePipelineCompletion: builds items from stageResults, calls generateSummary 6 * - generateGradeDistribution: filters zero-count grades, computes total 7 * - generateSummary: renders all color types (success/info/warn/error/none) 8 * - displayProgress: formats progress bar (edge cases: 0%, 100%) 9 */ 10 11 import { test, describe } from 'node:test'; 12 import assert from 'node:assert/strict'; 13 import { 14 generateSummary, 15 generateStageCompletion, 16 generatePipelineCompletion, 17 generateGradeDistribution, 18 displayProgress, 19 } from '../../src/utils/summary-generator.js'; 20 21 // Suppress console.log output during tests 22 const origLog = console.log; 23 { 24 // Use a separate noop to avoid polluting test output 25 } 26 27 describe('generateSummary', () => { 28 test('does not throw for empty items array', () => { 29 // Capture and suppress output 30 const logs = []; 31 const prev = console.log; 32 console.log = (...args) => logs.push(args.join('')); 33 34 assert.doesNotThrow(() => generateSummary('Test Title', [])); 35 36 console.log = prev; 37 assert.ok( 38 logs.some(l => l.includes('Test Title')), 39 'Should print title' 40 ); 41 }); 42 43 test('renders items with all color types without throwing', () => { 44 const prev = console.log; 45 console.log = () => {}; 46 47 assert.doesNotThrow(() => 48 generateSummary('Colors', [ 49 { label: 'Success item', value: 10, type: 'success' }, 50 { label: 'Info item', value: 20, type: 'info' }, 51 { label: 'Warning item', value: 5, type: 'warn' }, 52 { label: 'Error item', value: 1, type: 'error' }, 53 { label: 'No type item', value: 0 }, // no type → no color 54 ]) 55 ); 56 57 console.log = prev; 58 }); 59 60 test('respects custom width option', () => { 61 const lines = []; 62 const prev = console.log; 63 console.log = (...args) => lines.push(args.join('')); 64 65 generateSummary('Wide', [{ label: 'Test', value: 42 }], { width: 80 }); 66 67 console.log = prev; 68 // Border should have 80 chars of ═ 69 const border = lines.find(l => l.includes('═'.repeat(80))); 70 assert.ok(border, 'Should use custom width of 80'); 71 }); 72 }); 73 74 describe('generateStageCompletion', () => { 75 test('includes duration when stats.duration is set', () => { 76 const lines = []; 77 const prev = console.log; 78 console.log = (...args) => lines.push(args.join('')); 79 80 generateStageCompletion('Assets', { 81 processed: 10, 82 succeeded: 8, 83 failed: 2, 84 skipped: 0, 85 duration: 5000, 86 }); 87 88 console.log = prev; 89 assert.ok( 90 lines.some(l => l.includes('5.00s')), 91 'Should include duration' 92 ); 93 }); 94 95 test('omits duration when stats.duration is falsy', () => { 96 const lines = []; 97 const prev = console.log; 98 console.log = (...args) => lines.push(args.join('')); 99 100 generateStageCompletion('Scoring', { processed: 5, succeeded: 5, failed: 0, skipped: 0 }); 101 102 console.log = prev; 103 assert.ok(!lines.some(l => l.includes('Duration')), 'Should not include duration'); 104 }); 105 106 test('marks failed items as error type when failed > 0', () => { 107 // Just verify it doesn't throw 108 const prev = console.log; 109 console.log = () => {}; 110 111 assert.doesNotThrow(() => 112 generateStageCompletion('Enrich', { 113 processed: 10, 114 succeeded: 7, 115 failed: 3, 116 skipped: 0, 117 duration: 2000, 118 }) 119 ); 120 121 console.log = prev; 122 }); 123 }); 124 125 describe('generatePipelineCompletion', () => { 126 test('renders pipeline summary with multiple stages', () => { 127 const lines = []; 128 const prev = console.log; 129 console.log = (...args) => lines.push(args.join('')); 130 131 generatePipelineCompletion( 132 [ 133 { stage: 'Keywords', stats: { processed: 100, succeeded: 100, failed: 0 } }, 134 { stage: 'Scoring', stats: { processed: 80, succeeded: 75, failed: 5 } }, 135 { stage: 'Proposals', stats: { processed: 60, succeeded: 60, failed: 0 } }, 136 ], 137 30000 138 ); 139 140 console.log = prev; 141 assert.ok( 142 lines.some(l => l.includes('Pipeline Complete')), 143 'Should print Pipeline Complete' 144 ); 145 assert.ok( 146 lines.some(l => l.includes('30.00s')), 147 'Should include total duration' 148 ); 149 }); 150 151 test('marks stages with failures as error type', () => { 152 const prev = console.log; 153 console.log = () => {}; 154 155 // Should not throw even with failed stages 156 assert.doesNotThrow(() => 157 generatePipelineCompletion( 158 [{ stage: 'Enrich', stats: { processed: 10, succeeded: 5, failed: 5 } }], 159 10000 160 ) 161 ); 162 163 console.log = prev; 164 }); 165 166 test('handles empty stage results', () => { 167 const prev = console.log; 168 console.log = () => {}; 169 170 assert.doesNotThrow(() => generatePipelineCompletion([], 0)); 171 172 console.log = prev; 173 }); 174 }); 175 176 describe('generateGradeDistribution', () => { 177 test('only shows grades with count > 0', () => { 178 const lines = []; 179 const prev = console.log; 180 console.log = (...args) => lines.push(args.join('')); 181 182 generateGradeDistribution({ 'A+': 5, A: 10, B: 20, F: 3 }); 183 184 console.log = prev; 185 assert.ok( 186 lines.some(l => l.includes('Grade A+')), 187 'A+ should appear' 188 ); 189 assert.ok( 190 lines.some(l => l.includes('Grade A')), 191 'A should appear' 192 ); 193 // A- has 0 count so should not appear 194 assert.ok(!lines.some(l => l.includes('Grade A-')), 'A- should not appear'); 195 }); 196 197 test('shows correct total', () => { 198 const lines = []; 199 const prev = console.log; 200 console.log = (...args) => lines.push(args.join('')); 201 202 generateGradeDistribution({ B: 15, C: 10, D: 5 }); 203 204 console.log = prev; 205 assert.ok( 206 lines.some(l => l.includes('30')), 207 'Total should be 30' 208 ); 209 }); 210 211 test('handles empty distribution', () => { 212 const prev = console.log; 213 console.log = () => {}; 214 215 assert.doesNotThrow(() => generateGradeDistribution({})); 216 217 console.log = prev; 218 }); 219 220 test('handles A+/A/A- as success type, B+/B/B- as info, C/D as warn, F as error', () => { 221 const prev = console.log; 222 console.log = () => {}; 223 224 // All grades — just verify no throw 225 assert.doesNotThrow(() => 226 generateGradeDistribution({ 227 'A+': 1, 228 A: 1, 229 'A-': 1, 230 'B+': 1, 231 B: 1, 232 'B-': 1, 233 'C+': 1, 234 C: 1, 235 'C-': 1, 236 'D+': 1, 237 D: 1, 238 'D-': 1, 239 F: 1, 240 }) 241 ); 242 243 console.log = prev; 244 }); 245 }); 246 247 describe('displayProgress', () => { 248 test('does not throw for 0% progress', () => { 249 const prev = console.log; 250 console.log = () => {}; 251 252 assert.doesNotThrow(() => displayProgress(0, 100, 'Starting')); 253 254 console.log = prev; 255 }); 256 257 test('does not throw for 100% progress', () => { 258 const prev = console.log; 259 console.log = () => {}; 260 261 assert.doesNotThrow(() => displayProgress(100, 100, 'Done')); 262 263 console.log = prev; 264 }); 265 266 test('does not throw for mid-progress', () => { 267 const prev = console.log; 268 console.log = () => {}; 269 270 assert.doesNotThrow(() => displayProgress(50, 100, 'Halfway')); 271 272 console.log = prev; 273 }); 274 });