diff --git a/calculator/test.js b/calculator/test.js index b6a58c8..41323f9 100644 --- a/calculator/test.js +++ b/calculator/test.js @@ -145,35 +145,165 @@ function checkDisplay(expected, message) { // --- Test Cases --- -test('Addition', () => { - press('1+2='); - checkDisplay('3'); +const testNumbers = [ + 0, 1, -1, 5, -5, 0.5, -0.5, 123, -123, 1.23, -1.23, + 1234567, -1234567, 99999999, -99999999, + 1e-6, -1e-6, Math.PI, Math.E +]; + +// Helper to press a number, handling negatives +function pressNumber(n) { + const s = String(n); + if (s.startsWith('-')) { + press(s.substring(1)); + buttonPress('N'); + } else { + press(s); + } +} + +// --- Generated Tests: Binary Operations --- +const binaryOps = { + '+': (a, b) => a + b, + '-': (a, b) => a - b, + '*': (a, b) => a * b, + '/': (a, b) => a / b, +}; + +for (const op in binaryOps) { + for (const a of testNumbers) { + for (const b of testNumbers) { + if (op === '/' && b === 0) { + test(`Binary Edge Case: ${a} / 0`, () => { + pressNumber(a); + buttonPress('/'); + pressNumber(0); + buttonPress('='); + checkDisplay(a === 0 ? NaN : (a > 0 ? Infinity : -Infinity)); + }); + continue; + } + + test(`Binary: ${a} ${op} ${b}`, () => { + pressNumber(a); + buttonPress(op); + pressNumber(b); + buttonPress('='); + const expected = binaryOps[op](a, b); + checkDisplay(expected); + }); + } + } +} + +// --- Generated Tests: Unary Operations --- +const unaryOps = { + 'r': { name: 'sqrt', fn: Math.sqrt }, + 's': { name: 'x^2', fn: (a) => a * a }, + 'i': { name: '1/x', fn: (a) => 1 / a }, + '%': { name: '%', fn: (a) => a / 100 }, +}; + +for (const op in unaryOps) { + for (const a of testNumbers) { + if (op === 'r' && a < 0) { + test(`Unary Edge Case: sqrt(${a})`, () => { + pressNumber(a); + buttonPress('r'); + checkDisplay(NaN); + }); + continue; + } + + test(`Unary: ${unaryOps[op].name}(${a})`, () => { + pressNumber(a); + buttonPress(op); + const expected = unaryOps[op].fn(a); + checkDisplay(expected); + }); + } +} + +// --- Generated Tests: Chaining Operations --- +// To keep runtime sane, we'll pick a smaller subset for chaining +const chainNumbers = [0, 1, -5, 0.5, 10, 123]; +const chainOps = ['+', '-', '*', '/']; + +for (const a of chainNumbers) { + for (const b of chainNumbers) { + for (const c of chainNumbers) { + for (const op1 of chainOps) { + for (const op2 of chainOps) { + test(`Chaining: ${a} ${op1} ${b} ${op2} ${c}`, () => { + pressNumber(a); + buttonPress(op1); + pressNumber(b); + buttonPress(op2); // This will execute the first operation + pressNumber(c); + buttonPress('='); // This will execute the second operation + // Calculator does sequential evaluation: (a op1 b) op2 c + const expected = binaryOps[op2](binaryOps[op1](a, b), c); + checkDisplay(expected); + }); + } + } + } + } +} + +// --- Generated Tests: Trigonometry --- +const trigOps = { + 'sin': { fn: Math.sin, name: 'sin' }, + 'cos': { fn: Math.cos, name: 'cos' }, + 'tan': { fn: Math.tan, name: 'tan' }, +}; +const trigAngles = [0, 30, 45, 60, 90, 180, 270, 360, Math.PI/6, Math.PI/4, Math.PI/2, Math.PI, 3*Math.PI/2, 2*Math.PI]; + +// Test in DEG mode (default) +for (const op in trigOps) { + for (const angle of trigAngles) { + test(`Trig (deg): ${trigOps[op].name}(${angle})`, () => { + pressNumber(angle); + buttonPress(op); + const expected = trigOps[op].fn(angle * Math.PI / 180); + checkDisplay(expected); + }); + } +} + +// Test in RAD mode +test('Switch to RAD mode', () => { + buttonPress('angleMode'); + assert.strictEqual(scientificOperators.angleMode.val, 'rad'); }); -test('Subtraction', () => { - press('9-5='); - checkDisplay('4'); +for (const op in trigOps) { + for (const angle of trigAngles) { + // tan(pi/2) is Infinity, which the calculator can handle + if (op === 'tan' && (Math.abs(angle - Math.PI/2) < 1e-9 || Math.abs(angle - 3*Math.PI/2) < 1e-9)) { + test(`Trig Edge Case (rad): tan(${angle})`, () => { + pressNumber(angle); + buttonPress('tan'); + checkDisplay(Infinity); + }); + continue; + } + test(`Trig (rad): ${trigOps[op].name}(${angle})`, () => { + pressNumber(angle); + buttonPress(op); + const expected = trigOps[op].fn(angle); + checkDisplay(expected); + }); + } +} + +test('Switch back to DEG mode for subsequent tests', () => { + buttonPress('angleMode'); + assert.strictEqual(scientificOperators.angleMode.val, 'deg'); }); -test('Multiplication', () => { - press('3*4='); - checkDisplay('12'); -}); -test('Division', () => { - press('10/2='); - checkDisplay('5'); -}); - -test('Chained operations (no precedence)', () => { - press('2+3*4='); // (2+3)*4 = 20 - checkDisplay('20'); -}); - -test('Decimal numbers', () => { - press('1.5+2.5='); - checkDisplay('4'); -}); +// --- Specific Functionality Tests --- test('Clear and All Clear', () => { press('123+'); @@ -191,54 +321,10 @@ test('Clear and All Clear', () => { checkDisplay('1'); }); - -test('Negative result', () => { - press('5-10='); - checkDisplay('-5'); -}); - -test('Negative input', () => { - press('5*'); - press('2'); - press('N'); // make it -2 - press('='); - checkDisplay('-10'); -}); - -test('Division by zero', () => { - press('5/0='); - checkDisplay('INF'); -}); - -test('Percentage', () => { - press('200*5%='); - checkDisplay('10'); -}); - -test('Square root', () => { - press('16'); - press('r'); - checkDisplay('4'); - press('='); // pressing equals after unary op - checkDisplay('4'); -}); - -test('Square', () => { - press('5'); - press('s'); - checkDisplay('25'); - press('='); - checkDisplay('25'); -}); - -test('Inverse', () => { - press('4'); - press('i'); - checkDisplay('0.25'); -}); - test('Backspace', () => { - press('123B'); + press('123.45B'); + checkDisplay('123.4'); + press('BB'); checkDisplay('12'); press('B'); checkDisplay('1'); @@ -255,6 +341,11 @@ test('Repeated equals', () => { checkDisplay('8'); press('='); checkDisplay('11'); + press('R'); press('R'); + press('10-2='); + checkDisplay('8'); + press('='); + checkDisplay('6'); }); test('Operator change', () => { @@ -267,42 +358,5 @@ test('Long number with separators', () => { checkDisplay('1,234,567'); }); -test('Sine (deg)', () => { - press('30'); - buttonPress('sin'); - checkDisplay('0.5'); - press('R'); press('R'); - press('90'); - buttonPress('sin'); - checkDisplay('1'); - press('R'); press('R'); - press('180'); - buttonPress('sin'); - checkDisplay('0'); -}); - -test('Cosine (deg)', () => { - press('60'); - buttonPress('cos'); - checkDisplay('0.5'); - press('R'); press('R'); - press('0'); - buttonPress('cos'); - checkDisplay('1'); - press('R'); press('R'); - press('90'); - buttonPress('cos'); - checkDisplay('0'); -}); - -test('Angle mode switch to radians and back', () => { - buttonPress('angleMode'); // now rad - // The buttonPress function doesn't return the state, but we can check the object it modifies - assert.strictEqual(scientificOperators.angleMode.val, 'rad'); - buttonPress('angleMode'); // back to deg - assert.strictEqual(scientificOperators.angleMode.val, 'deg'); -}); - - // Run all the defined tests runTests();