test: Add full simulation tests for calculator
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
|
||||
// --- Mock Bangle.js environment ---
|
||||
let mock_display_str = "";
|
||||
let mock_ui_callbacks = {};
|
||||
|
||||
// Mock 'g' (graphics)
|
||||
global.g = {
|
||||
_col: 0,
|
||||
_bg: 0,
|
||||
_font: "6x8",
|
||||
_font_size: 1,
|
||||
clear: () => {},
|
||||
clearRect: () => {},
|
||||
setColor: (c) => { global.g._col = c; return global.g; },
|
||||
setBgColor: (c) => { global.g._bg = c; return global.g; },
|
||||
fillRect: () => {},
|
||||
setFont: function(font, size) {
|
||||
if (font && font.includes(":")) {
|
||||
const parts = font.split(":");
|
||||
this._font = parts[0];
|
||||
this._font_size = parseInt(parts[1], 10);
|
||||
} else {
|
||||
this._font = font;
|
||||
if (size) this._font_size = size;
|
||||
}
|
||||
return this;
|
||||
},
|
||||
setFontAlign: () => {},
|
||||
stringWidth: (s) => s.length * 8 * (global.g._font_size || 1), // simple approximation
|
||||
getWidth: () => 176,
|
||||
getHeight: () => 176,
|
||||
drawString: (s) => { mock_display_str = String(s); },
|
||||
getFontHeight: () => 8 * (global.g._font_size || 1)
|
||||
};
|
||||
|
||||
global.Graphics = {};
|
||||
|
||||
// Mock 'process'
|
||||
global.process = {
|
||||
env: { HWVERSION: 2 } // Simulate Bangle.js 2
|
||||
};
|
||||
|
||||
// Mock 'Bangle'
|
||||
global.Bangle = {
|
||||
setUI: (callbacks) => { mock_ui_callbacks = callbacks; },
|
||||
http: () => new Promise(resolve => resolve({resp: "{}"})),
|
||||
buzz: () => {},
|
||||
};
|
||||
|
||||
// Mock 'load' (to exit app)
|
||||
global.load = () => {};
|
||||
|
||||
// Mock 'require' for fonts
|
||||
const originalRequire = require;
|
||||
global.require = (name) => {
|
||||
if (name === "FontDylex7x13") {
|
||||
return { add: () => {} };
|
||||
}
|
||||
return originalRequire(name);
|
||||
};
|
||||
// --- End Mock ---
|
||||
|
||||
|
||||
// --- Load calculator app ---
|
||||
const calculatorCode = fs.readFileSync(path.join(__dirname, 'calculator.app.js'), 'utf8');
|
||||
eval(calculatorCode);
|
||||
// --- End Load ---
|
||||
|
||||
|
||||
// --- Test Framework ---
|
||||
const test_suite = [];
|
||||
let tests_passed = 0;
|
||||
let tests_failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
test_suite.push({ name, fn });
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
test_suite.forEach(t => {
|
||||
// Reset state before each test
|
||||
buttonPress('R'); // C
|
||||
buttonPress('R'); // AC
|
||||
if (mock_display_str !== '0') {
|
||||
console.error(`[FAIL] ${t.name} - Failed to reset state. Display is: "${mock_display_str}"`);
|
||||
tests_failed++;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
t.fn();
|
||||
console.log(`[PASS] ${t.name}`);
|
||||
tests_passed++;
|
||||
} catch (e) {
|
||||
console.error(`[FAIL] ${t.name}`);
|
||||
console.error(e);
|
||||
tests_failed++;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`\nTests finished. Passed: ${tests_passed}, Failed: ${tests_failed}.`);
|
||||
if (tests_failed > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function press(buttons) {
|
||||
for (const button of buttons) {
|
||||
buttonPress(button);
|
||||
}
|
||||
}
|
||||
|
||||
function checkDisplay(expected, message) {
|
||||
assert.strictEqual(mock_display_str, String(expected), message);
|
||||
}
|
||||
// --- End Test Framework ---
|
||||
|
||||
|
||||
// --- Test Cases ---
|
||||
|
||||
test('Addition', () => {
|
||||
press('1+2=');
|
||||
checkDisplay('3');
|
||||
});
|
||||
|
||||
test('Subtraction', () => {
|
||||
press('9-5=');
|
||||
checkDisplay('4');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
test('Clear and All Clear', () => {
|
||||
press('123+');
|
||||
checkDisplay('123');
|
||||
press('R'); // Clear
|
||||
checkDisplay('0');
|
||||
press('456');
|
||||
checkDisplay('456');
|
||||
press('=');
|
||||
checkDisplay('579'); // 123 + 456
|
||||
press('R'); // Clear
|
||||
checkDisplay('0');
|
||||
buttonPress('R'); // All Clear
|
||||
press('+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', () => {
|
||||
press('123B');
|
||||
checkDisplay('12');
|
||||
press('B');
|
||||
checkDisplay('1');
|
||||
press('B');
|
||||
checkDisplay('0');
|
||||
press('B');
|
||||
checkDisplay('0');
|
||||
});
|
||||
|
||||
test('Repeated equals', () => {
|
||||
press('2+3=');
|
||||
checkDisplay('5');
|
||||
press('=');
|
||||
checkDisplay('8');
|
||||
press('=');
|
||||
checkDisplay('11');
|
||||
});
|
||||
|
||||
test('Operator change', () => {
|
||||
press('10+-*/2=');
|
||||
checkDisplay('5'); // 10/2=5
|
||||
});
|
||||
|
||||
test('Long number with separators', () => {
|
||||
press('1234567');
|
||||
checkDisplay('1,234,567');
|
||||
});
|
||||
|
||||
test('Sine (deg)', () => {
|
||||
press('30');
|
||||
press('sin');
|
||||
checkDisplay('0.5');
|
||||
press('R'); press('R');
|
||||
press('90');
|
||||
press('sin');
|
||||
checkDisplay('1');
|
||||
press('R'); press('R');
|
||||
press('180');
|
||||
press('sin');
|
||||
checkDisplay('0');
|
||||
});
|
||||
|
||||
test('Cosine (deg)', () => {
|
||||
press('60');
|
||||
press('cos');
|
||||
checkDisplay('0.5');
|
||||
press('R'); press('R');
|
||||
press('0');
|
||||
press('cos');
|
||||
checkDisplay('1');
|
||||
press('R'); press('R');
|
||||
press('90');
|
||||
press('cos');
|
||||
checkDisplay('0');
|
||||
});
|
||||
|
||||
test('Angle mode switch to radians and back', () => {
|
||||
press('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');
|
||||
press('angleMode'); // back to deg
|
||||
assert.strictEqual(scientificOperators.angleMode.val, 'deg');
|
||||
});
|
||||
|
||||
|
||||
// Run all the defined tests
|
||||
runTests();
|
||||
|
||||
Reference in New Issue
Block a user