test: Replace static tests with comprehensive programmatic test suite
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
+162
-108
@@ -145,35 +145,165 @@ function checkDisplay(expected, message) {
|
|||||||
|
|
||||||
// --- Test Cases ---
|
// --- Test Cases ---
|
||||||
|
|
||||||
test('Addition', () => {
|
const testNumbers = [
|
||||||
press('1+2=');
|
0, 1, -1, 5, -5, 0.5, -0.5, 123, -123, 1.23, -1.23,
|
||||||
checkDisplay('3');
|
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', () => {
|
for (const op in trigOps) {
|
||||||
press('9-5=');
|
for (const angle of trigAngles) {
|
||||||
checkDisplay('4');
|
// 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', () => {
|
// --- Specific Functionality Tests ---
|
||||||
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');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Clear and All Clear', () => {
|
test('Clear and All Clear', () => {
|
||||||
press('123+');
|
press('123+');
|
||||||
@@ -191,54 +321,10 @@ test('Clear and All Clear', () => {
|
|||||||
checkDisplay('1');
|
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', () => {
|
test('Backspace', () => {
|
||||||
press('123B');
|
press('123.45B');
|
||||||
|
checkDisplay('123.4');
|
||||||
|
press('BB');
|
||||||
checkDisplay('12');
|
checkDisplay('12');
|
||||||
press('B');
|
press('B');
|
||||||
checkDisplay('1');
|
checkDisplay('1');
|
||||||
@@ -255,6 +341,11 @@ test('Repeated equals', () => {
|
|||||||
checkDisplay('8');
|
checkDisplay('8');
|
||||||
press('=');
|
press('=');
|
||||||
checkDisplay('11');
|
checkDisplay('11');
|
||||||
|
press('R'); press('R');
|
||||||
|
press('10-2=');
|
||||||
|
checkDisplay('8');
|
||||||
|
press('=');
|
||||||
|
checkDisplay('6');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Operator change', () => {
|
test('Operator change', () => {
|
||||||
@@ -267,42 +358,5 @@ test('Long number with separators', () => {
|
|||||||
checkDisplay('1,234,567');
|
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
|
// Run all the defined tests
|
||||||
runTests();
|
runTests();
|
||||||
|
|||||||
Reference in New Issue
Block a user