From 0bf1220c624749f04d3d85ae71447c0e65ef9d94 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Sat, 14 Mar 2026 17:31:55 -0600 Subject: [PATCH] test: Add extensive edge case tests and expand number generation Co-authored-by: aider (gemini/gemini-2.5-pro) --- calculator/test.js | 144 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 136 insertions(+), 8 deletions(-) diff --git a/calculator/test.js b/calculator/test.js index 191999e..8d43962 100644 --- a/calculator/test.js +++ b/calculator/test.js @@ -161,12 +161,42 @@ function checkDisplay(expected, message) { // --- Test Cases --- -const testNumbers = [ - 0, 1, -1, 5, -5, 0.5, -0.5, 123, -123, 1.23, -1.23, +const basicNumbers = [ + 0, 1, -1, 2, -2, 5, -5, 10, -10, 0.1, -0.1, 0.2, -0.2, 0.5, -0.5, + 123, -123, 1.23, -1.23, 1234567, -1234567, 99999999, -99999999, - 1e-6, -1e-6, Math.PI, Math.E ]; +const edgeCaseNumbers = [ + -0, + Number.MAX_SAFE_INTEGER, + Number.MIN_SAFE_INTEGER, + Math.PI, Math.E, + 1e-6, -1e-6, 1e-8, -1e-8, + 1e6, -1e6, 1e8, -1e8, + 0.1+0.2, // floating point fun + 1/3, 2/3, 0.9999999, 1.0000001 +]; + +// Generate some more numbers to reach the target count +const generatedNumbers = []; +for (let i = 1; i <= 20; i++) { + // integers + generatedNumbers.push(i * 123); + generatedNumbers.push(i * -123); + // floats + generatedNumbers.push(i / 10); + generatedNumbers.push(i / -10); + // small floats + generatedNumbers.push(i / 1000); + generatedNumbers.push(i / -1000); +} + +const testNumbers = [...new Set([...basicNumbers, ...edgeCaseNumbers, ...generatedNumbers])]; +// We need to filter out Infinity/-Infinity for pressNumber helper as they can't be typed. +// They can only appear as results, which we test for separately. +const finiteTestNumbers = testNumbers.filter(n => isFinite(n)); + // Helper to press a number, handling negatives function pressNumber(n) { const s = String(n); @@ -187,8 +217,8 @@ const binaryOps = { }; for (const op in binaryOps) { - for (const a of testNumbers) { - for (const b of testNumbers) { + for (const a of finiteTestNumbers) { + for (const b of finiteTestNumbers) { if (op === '/' && b === 0) { test(`Binary Edge Case: ${a} / 0`, () => { pressNumber(a); @@ -212,6 +242,85 @@ for (const op in binaryOps) { } } +// --- Generated Tests: Operations with non-finite results --- +const smallFiniteTestNumbers = [1, -1, 5, -5, 0, 100, -100]; // for speed + +// Test operations that result in Infinity, -Infinity, or NaN, and then continue with another operation +for (const a of smallFiniteTestNumbers) { + // a / 0 -> Infinity/-Infinity/NaN + test(`Non-finite chaining: (${a} / 0) + 5`, () => { + pressNumber(a); + press('/0='); + const intermediate = a/0; + checkDisplay(intermediate); + press('+5='); + checkDisplay(intermediate + 5); + }); + + test(`Non-finite chaining: (${a} / 0) * 5`, () => { + pressNumber(a); + press('/0='); + const intermediate = a/0; + checkDisplay(intermediate); + press('*5='); + checkDisplay(intermediate * 5); + }); + + test(`Non-finite chaining: (${a} / 0) * 0`, () => { + pressNumber(a); + press('/0='); + const intermediate = a/0; + checkDisplay(intermediate); + press('*0='); + checkDisplay(intermediate * 0); // Should be NaN + }); + + test(`Non-finite chaining: (${a} / 0) / 5`, () => { + pressNumber(a); + press('/0='); + const intermediate = a/0; + checkDisplay(intermediate); + press('/5='); + checkDisplay(intermediate / 5); + }); + + test(`Non-finite chaining: (${a} / 0) / 0`, () => { + pressNumber(a); + press('/0='); + const intermediate = a/0; + checkDisplay(intermediate); + press('/0='); + checkDisplay(intermediate / 0); + }); +} +// 0/0 = NaN. Test operations on NaN +test(`Non-finite chaining: (0 / 0) + 5`, () => { + press('0/0='); + checkDisplay(NaN); + press('+5='); + checkDisplay(NaN + 5); // anything + NaN is NaN +}); +test(`Non-finite chaining: (0 / 0) * 5`, () => { + press('0/0='); + checkDisplay(NaN); + press('*5='); + checkDisplay(NaN * 5); +}); +test(`Non-finite chaining: (0 / 0) / 5`, () => { + press('0/0='); + checkDisplay(NaN); + press('/5='); + checkDisplay(NaN / 5); +}); +test(`Non-finite chaining: sqrt(negative) then op`, () => { + press('9N'); // -9 + press('r'); // sqrt + checkDisplay(NaN); + press('+1='); + checkDisplay(NaN); +}); + + // --- Generated Tests: Unary Operations --- const unaryOps = { 'r': { name: 'sqrt', fn: Math.sqrt }, @@ -221,7 +330,7 @@ const unaryOps = { }; for (const op in unaryOps) { - for (const a of testNumbers) { + for (const a of finiteTestNumbers) { if (op === 'r' && a < 0) { test(`Unary Edge Case: sqrt(${a})`, () => { pressNumber(a); @@ -242,7 +351,7 @@ for (const op in unaryOps) { // --- 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 chainNumbers = [0, 1, -1, 5, -5, 0.5, -0.5, 10, -10, 123, -123, 1.23]; const chainOps = ['+', '-', '*', '/']; for (const a of chainNumbers) { @@ -273,7 +382,7 @@ const trigOps = { '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]; +const trigAngles = [0, 15, 30, 45, 60, 75, 90, 180, 270, 360, -30, -45, -90, -180, 450, 720, Math.PI/6, Math.PI/4, Math.PI/3, Math.PI/2, Math.PI, 3*Math.PI/2, 2*Math.PI, -Math.PI/2, -Math.PI]; // Test in DEG mode (default) for (const op in trigOps) { @@ -369,5 +478,24 @@ test('Long number input', () => { checkDisplay('1234567'); }); +test('Input starting with decimal point', () => { + press('.5'); + checkDisplay('0.5'); + press('*2='); + checkDisplay('1'); +}); + +test('Negative zero handling', () => { + press('0N'); // get -0 + checkDisplay(-0); + press('*5'); + checkDisplay(-0); + press('='); + checkDisplay(-0); + buttonPress('R'); buttonPress('R'); + press('5*-0='); + checkDisplay(-0); +}); + // Run all the defined tests runTests();