compute-score.test.js
1 import { test, describe } from 'node:test'; 2 import assert from 'node:assert/strict'; 3 import { computeScoreFromFactors, computeGrade } from '../../src/score.js'; 4 5 describe('computeScoreFromFactors', () => { 6 test('computes weighted score from factor scores', () => { 7 const factors = { 8 headline_quality: { score: 8, reasoning: 'Good' }, 9 value_proposition: { score: 7, reasoning: 'OK' }, 10 unique_selling_proposition: { score: 6, reasoning: 'Weak' }, 11 call_to_action: { score: 9, reasoning: 'Great' }, 12 urgency_messaging: { score: 3, reasoning: 'Missing' }, 13 hook_engagement: { score: 7, reasoning: 'Good' }, 14 trust_signals: { score: 5, reasoning: 'Basic' }, 15 imagery_design: { score: 8, reasoning: 'Nice' }, 16 offer_clarity: { score: 8, reasoning: 'Clear' }, 17 contextual_appropriateness: { score: 7, reasoning: 'Fits' }, 18 }; 19 20 const score = computeScoreFromFactors(factors); 21 // Manual: (8*.15)+(7*.14)+(6*.13)+(9*.13)+(3*.10)+(7*.09)+(5*.11)+(8*.08)+(8*.04)+(7*.03) 22 // = 1.2 + 0.98 + 0.78 + 1.17 + 0.30 + 0.63 + 0.55 + 0.64 + 0.32 + 0.21 = 6.78 23 // 6.78 * 10 = 67.8 24 assert.equal(score, 67.8); 25 }); 26 27 test('returns 0 for all-zero scores', () => { 28 const factors = { 29 headline_quality: { score: 0 }, 30 value_proposition: { score: 0 }, 31 unique_selling_proposition: { score: 0 }, 32 call_to_action: { score: 0 }, 33 urgency_messaging: { score: 0 }, 34 hook_engagement: { score: 0 }, 35 trust_signals: { score: 0 }, 36 imagery_design: { score: 0 }, 37 offer_clarity: { score: 0 }, 38 contextual_appropriateness: { score: 0 }, 39 }; 40 assert.equal(computeScoreFromFactors(factors), 0); 41 }); 42 43 test('returns 100 for all-perfect scores', () => { 44 const factors = { 45 headline_quality: { score: 10 }, 46 value_proposition: { score: 10 }, 47 unique_selling_proposition: { score: 10 }, 48 call_to_action: { score: 10 }, 49 urgency_messaging: { score: 10 }, 50 hook_engagement: { score: 10 }, 51 trust_signals: { score: 10 }, 52 imagery_design: { score: 10 }, 53 offer_clarity: { score: 10 }, 54 contextual_appropriateness: { score: 10 }, 55 }; 56 assert.equal(computeScoreFromFactors(factors), 100); 57 }); 58 59 test('handles missing factors gracefully (defaults to 0)', () => { 60 const factors = { 61 headline_quality: { score: 10 }, 62 // all others missing 63 }; 64 // 10 * 0.15 * 10 = 15 65 assert.equal(computeScoreFromFactors(factors), 15); 66 }); 67 68 test('returns null for null/undefined input', () => { 69 assert.equal(computeScoreFromFactors(null), null); 70 assert.equal(computeScoreFromFactors(undefined), null); 71 }); 72 }); 73 74 describe('computeGrade — standard academic scale', () => { 75 test('A+ for 97-100', () => { 76 assert.equal(computeGrade(100), 'A+'); 77 assert.equal(computeGrade(97), 'A+'); 78 }); 79 80 test('A for 93-96', () => { 81 assert.equal(computeGrade(96), 'A'); 82 assert.equal(computeGrade(93), 'A'); 83 }); 84 85 test('A- for 90-92', () => { 86 assert.equal(computeGrade(92), 'A-'); 87 assert.equal(computeGrade(90), 'A-'); 88 }); 89 90 test('B+ for 87-89', () => { 91 assert.equal(computeGrade(89), 'B+'); 92 assert.equal(computeGrade(87), 'B+'); 93 }); 94 95 test('B for 83-86', () => { 96 assert.equal(computeGrade(86), 'B'); 97 assert.equal(computeGrade(83), 'B'); 98 }); 99 100 test('B- for 80-82', () => { 101 assert.equal(computeGrade(82), 'B-'); 102 assert.equal(computeGrade(80), 'B-'); 103 }); 104 105 test('C+ for 77-79', () => { 106 assert.equal(computeGrade(79), 'C+'); 107 assert.equal(computeGrade(77), 'C+'); 108 }); 109 110 test('C for 73-76', () => { 111 assert.equal(computeGrade(76), 'C'); 112 assert.equal(computeGrade(73), 'C'); 113 }); 114 115 test('C- for 70-72', () => { 116 assert.equal(computeGrade(72), 'C-'); 117 assert.equal(computeGrade(70), 'C-'); 118 }); 119 120 test('D+ for 67-69', () => { 121 assert.equal(computeGrade(69), 'D+'); 122 assert.equal(computeGrade(67), 'D+'); 123 }); 124 125 test('D for 63-66', () => { 126 assert.equal(computeGrade(66), 'D'); 127 assert.equal(computeGrade(63), 'D'); 128 }); 129 130 test('D- for 60-62', () => { 131 assert.equal(computeGrade(62), 'D-'); 132 assert.equal(computeGrade(60), 'D-'); 133 }); 134 135 test('F for 0-59', () => { 136 assert.equal(computeGrade(59), 'F'); 137 assert.equal(computeGrade(0), 'F'); 138 }); 139 140 test('F for negative scores', () => { 141 assert.equal(computeGrade(-1), 'F'); 142 assert.equal(computeGrade(-100), 'F'); 143 }); 144 145 test('F for null/undefined', () => { 146 assert.equal(computeGrade(null), 'F'); 147 assert.equal(computeGrade(undefined), 'F'); 148 }); 149 150 test('proposal threshold boundary: 82 is B- (gets proposals), 83 is B (skipped)', () => { 151 assert.equal(computeGrade(82), 'B-'); 152 assert.equal(computeGrade(83), 'B'); 153 }); 154 });