Compare commits
73 Commits
14bda5c29f
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ea5212778 | |||
| 5dd0272350 | |||
| 8de4d70c8b | |||
| 7a3e5cd868 | |||
| 8ed03143bd | |||
| 95cc91781d | |||
| 1a2fa25ea7 | |||
| df0a2c03fc | |||
| d2c4be15d0 | |||
| 3937109edb | |||
| 98fdddac5d | |||
| 73cd819e94 | |||
| abe931d61d | |||
| 0bf1220c62 | |||
| e32b274e5c | |||
| 2088081bb8 | |||
| 2754d4f52a | |||
| 04efe1b4d9 | |||
| 728687ab37 | |||
| 77288c92af | |||
| e8a361210d | |||
| 255984e52f | |||
| 20ce84ed89 | |||
| 31531ff118 | |||
| 37867a095b | |||
| 83a50fa06a | |||
| ab8f7e78b8 | |||
| 6fc30311f3 | |||
| a59b92572b | |||
| 2c91af6759 | |||
| 6ae1b173bd | |||
| f94d5c2105 | |||
| 913156ca3c | |||
| a5adeb379c | |||
| 281187fd91 | |||
| f5abf6eb48 | |||
| 810e43bcfd | |||
| 1c511d3441 | |||
| 3b66810ba1 | |||
| ebb1550fc7 | |||
| 6b7285a017 | |||
| 85185dbbaa | |||
| 5cb5ee604b | |||
| e57e09eb17 | |||
| f3fdf4e5ab | |||
| 2106a4298e | |||
| ca2265e8aa | |||
| b86f6720bc | |||
| 8c7c488967 | |||
| 3507d8938e | |||
| 9738c76140 | |||
| f19a77d06f | |||
| 739b337135 | |||
| 68aa187f71 | |||
| b369cb0c3b | |||
| 212cbdb9b7 | |||
| a714c57b2b | |||
| 6ac882699a | |||
| 9b48f7342a | |||
| 8b5ac36284 | |||
| 3c18e0326d | |||
| 2400ebd145 | |||
| 22e36c447d | |||
| 37de23ecfe | |||
| 2a9f8ce857 | |||
| a113e10ae5 | |||
| 197d393553 | |||
| 8fd4b6e45a | |||
| 36120b877f | |||
| 69330e03cb | |||
| 4c506c7913 | |||
| 8275abdb57 | |||
| 1c433fc56b |
+30
-15
@@ -18,7 +18,10 @@
|
|||||||
let stopWatchTimer = null;
|
let stopWatchTimer = null;
|
||||||
|
|
||||||
let myMessage = "";
|
let myMessage = "";
|
||||||
let temperature = "";
|
let temperature = "?";
|
||||||
|
let temp_old = true;
|
||||||
|
let feels_like = "?";
|
||||||
|
let weather_old = true;
|
||||||
|
|
||||||
let drawTimer = null;
|
let drawTimer = null;
|
||||||
|
|
||||||
@@ -48,9 +51,9 @@
|
|||||||
}
|
}
|
||||||
let Ttxt1 = timeToText(Tt1);
|
let Ttxt1 = timeToText(Tt1);
|
||||||
|
|
||||||
g.clearRect(0, y-60, w, y-34);
|
g.clearRect(0, y+61, w, y+88);
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.setFontAlign(0, 0).setFont("Vector", 26).drawString("S1: " + Ttxt1, x, y-45);
|
g.setFontAlign(0, 0).setFont("Vector", 26).drawString(Ttxt1, x, y+74);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stopWatch.start2 || stopWatch.elapsed2) {
|
if (stopWatch.start2 || stopWatch.elapsed2) {
|
||||||
@@ -67,9 +70,9 @@
|
|||||||
}
|
}
|
||||||
let Ttxt2 = timeToText(Tt2);
|
let Ttxt2 = timeToText(Tt2);
|
||||||
|
|
||||||
g.clearRect(0, y+61, w, y+88);
|
g.clearRect(0, y-60, w, y-34);
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.setFontAlign(0, 0).setFont("Vector", 26).drawString("S2: " + Ttxt2, x, y+76);
|
g.setFontAlign(0, 0).setFont("Vector", 26).drawString(Ttxt2, x, y-45);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,25 +93,32 @@
|
|||||||
var utcHour = Math.floor(utc / 3600);
|
var utcHour = Math.floor(utc / 3600);
|
||||||
var utcMinute = Math.floor((utc % 3600) / 60);
|
var utcMinute = Math.floor((utc % 3600) / 60);
|
||||||
var utcStr = utcHour.toString().padStart(2, '0') + ":" + utcMinute.toString().padStart(2, '0');
|
var utcStr = utcHour.toString().padStart(2, '0') + ":" + utcMinute.toString().padStart(2, '0');
|
||||||
g.setFontAlign(0, 0).setFont("Vector", 36).drawString(utcStr, x-25, y+43);
|
g.setFontAlign(0, 0).setFont("Vector", 36).drawString(utcStr, x-32, y+43);
|
||||||
|
|
||||||
var tz_offset = date.toString().indexOf("GMT");
|
//var tz_offset = date.toString().indexOf("GMT");
|
||||||
var tz = date.toString().substring(tz_offset+3, tz_offset+6);
|
//var tz = date.toString().substring(tz_offset+3, tz_offset+6);
|
||||||
g.setFontAlign(0, 0).setFont("Vector", 24).drawString(tz, x+60, y+43);
|
//g.setFontAlign(0, 0).setFont("Vector", 24).drawString(tz, x+60, y+43);
|
||||||
|
|
||||||
|
if (temp_old) g.setColor("#888");
|
||||||
|
g.setFontAlign(0, 0).setFont("Vector", 26).drawString(temperature, x+53, y+43);
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
|
||||||
// Show date and day of week
|
// Show date and day of week
|
||||||
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
var dateStr = date.getDate() + " " + days[date.getDay()] + " " + temperature;
|
var dateStr = date.getDate() + " " + days[date.getDay()];
|
||||||
|
|
||||||
// don't draw date string if stopwatch 2 is running
|
// don't draw date string if stopwatch 1 is running
|
||||||
if (!stopWatch.start2 && !stopWatch.elapsed2) {
|
if (!stopWatch.start1 && !stopWatch.elapsed1) {
|
||||||
g.setFontAlign(0, 0).setFont("Vector", 26).drawString(dateStr, x, y+76);
|
g.setFontAlign(0, 0).setFont("Vector", 26).drawString(dateStr, x-32, y+74);
|
||||||
|
if (weather_old) g.setColor("#888");
|
||||||
|
g.setFontAlign(0, 0).setFont("Vector", 26).drawString(feels_like, x+53, y+74);
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
//var wrapped = g.wrapString(myMessage, g.getWidth()-10).join("\n");
|
//var wrapped = g.wrapString(myMessage, g.getWidth()-10).join("\n");
|
||||||
|
|
||||||
// don't draw message if stopwatch 1 is running
|
// don't draw message if stopwatch 2 is running
|
||||||
if (!stopWatch.start1 && !stopWatch.elapsed1) {
|
if (!stopWatch.start2 && !stopWatch.elapsed2) {
|
||||||
g.setFontAlign(0, 0).setFont("Vector", 26).drawString(myMessage, x, y-45);
|
g.setFontAlign(0, 0).setFont("Vector", 26).drawString(myMessage, x, y-45);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +132,9 @@
|
|||||||
let result = JSON.parse(event.resp);
|
let result = JSON.parse(event.resp);
|
||||||
myMessage = result.context;
|
myMessage = result.context;
|
||||||
temperature = result.temperature;
|
temperature = result.temperature;
|
||||||
|
temp_old = result.temp_old;
|
||||||
|
feels_like = result.feels_like;
|
||||||
|
weather_old = result.weather_old;
|
||||||
if (watchState == STATE_IDLE) {
|
if (watchState == STATE_IDLE) {
|
||||||
if (paintFace) paintFace();
|
if (paintFace) paintFace();
|
||||||
}
|
}
|
||||||
@@ -353,6 +366,8 @@
|
|||||||
console.log("Pulling new menu...");
|
console.log("Pulling new menu...");
|
||||||
menu = null;
|
menu = null;
|
||||||
require("Storage").writeJSON("menu.json", menu);
|
require("Storage").writeJSON("menu.json", menu);
|
||||||
|
} else if (menuCommand == "commands,util,calc,") {
|
||||||
|
load("calculator.app.js");
|
||||||
} else if (menuCommand == "states,stop watch,start1,") {
|
} else if (menuCommand == "states,stop watch,start1,") {
|
||||||
startSW1();
|
startSW1();
|
||||||
} else if (menuCommand == "states,stop watch,start2,") {
|
} else if (menuCommand == "states,stop watch,start2,") {
|
||||||
|
|||||||
@@ -0,0 +1,736 @@
|
|||||||
|
/**
|
||||||
|
* BangleJS Calculator
|
||||||
|
*
|
||||||
|
* Original Author: Frederic Rousseau https://github.com/fredericrous
|
||||||
|
* Created: April 2020
|
||||||
|
*
|
||||||
|
* Contributors: thyttan https://github.com/thyttan
|
||||||
|
*/
|
||||||
|
|
||||||
|
g.clear();
|
||||||
|
require("FontDylex7x13").add(Graphics);
|
||||||
|
|
||||||
|
var DEFAULT_SELECTION_NUMBERS = '5';
|
||||||
|
var RESULT_HEIGHT = 40;
|
||||||
|
var COLORS = {
|
||||||
|
// [normal, selected]
|
||||||
|
DEFAULT: ['#7F8183', '#A6A6A7'],
|
||||||
|
OPERATOR: ['#F99D1C', '#CA7F2A'],
|
||||||
|
SPECIAL: ['#65686C', '#7F8183']
|
||||||
|
};
|
||||||
|
|
||||||
|
var KEY_AREA = [0, RESULT_HEIGHT, g.getWidth(), g.getHeight()];
|
||||||
|
|
||||||
|
var screen, screenColor;
|
||||||
|
var globalGrid = [4, 5];
|
||||||
|
var swipeEnabled;
|
||||||
|
|
||||||
|
var numbersGrid = [3, 4];
|
||||||
|
var numbers = {
|
||||||
|
'0': {grid: [1, 3], globalGrid: [1, 4], trbl: '2.0B'},
|
||||||
|
'.': {grid: [2, 3], globalGrid: [2, 4], trbl: '3=.0'},
|
||||||
|
'1': {grid: [0, 2], globalGrid: [0, 3], trbl: '42B1'},
|
||||||
|
'2': {grid: [1, 2], globalGrid: [1, 3], trbl: '5301'},
|
||||||
|
'3': {grid: [2, 2], globalGrid: [2, 3], trbl: '6+.2'},
|
||||||
|
'4': {grid: [0, 1], globalGrid: [0, 2], trbl: '7514'},
|
||||||
|
'5': {grid: [1, 1], globalGrid: [1, 2], trbl: '8624'},
|
||||||
|
'6': {grid: [2, 1], globalGrid: [2, 2], trbl: '9-35'},
|
||||||
|
'7': {grid: [0, 0], globalGrid: [0, 1], trbl: 'R847'},
|
||||||
|
'8': {grid: [1, 0], globalGrid: [1, 1], trbl: 'N957'},
|
||||||
|
'9': {grid: [2, 0], globalGrid: [2, 1], trbl: 'p*68'},
|
||||||
|
'B': {grid: [0, 3], globalGrid: [0, 4], trbl: '10BB', val: '<-', color: COLORS.SPECIAL},
|
||||||
|
};
|
||||||
|
|
||||||
|
var operatorsGrid = [2, 3];
|
||||||
|
var operators = {
|
||||||
|
'+': {grid: [0, 0], globalGrid: [3, 3], trbl: '-+=3'},
|
||||||
|
'-': {grid: [1, 0], globalGrid: [3, 2], trbl: '*-+6'},
|
||||||
|
'*': {grid: [0, 1], globalGrid: [3, 1], trbl: '/*-9'},
|
||||||
|
'/': {grid: [1, 1], globalGrid: [3, 0], trbl: '//*p'},
|
||||||
|
'=': {grid: [1, 2], globalGrid: [3, 4], trbl: '+==.'},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.HWVERSION!=1) {
|
||||||
|
operatorsGrid = [2, 4];
|
||||||
|
operators['='].grid = [1, 3];
|
||||||
|
operators.r = {grid: [0, 2], val: 'sqrt'};
|
||||||
|
operators.s = {grid: [1, 2], val: 'x^2'};
|
||||||
|
operators.i = {grid: [0, 3], val: '1/x'};
|
||||||
|
}
|
||||||
|
|
||||||
|
var scientificOperatorsGrid = [2, 4];
|
||||||
|
var scientificOperators = {
|
||||||
|
'sin': {grid: [0, 0], val: 'sin'},
|
||||||
|
'cos': {grid: [1, 0], val: 'cos'},
|
||||||
|
'tan': {grid: [0, 1], val: 'tan'},
|
||||||
|
'angleMode': {grid: [1, 1], val: 'deg'},
|
||||||
|
'log': {grid: [0, 2], val: 'log'},
|
||||||
|
'tenpow': {grid: [1, 2], val: '10^x'},
|
||||||
|
'ln': {grid: [0, 3], val: 'ln'},
|
||||||
|
'epow': {grid: [1, 3], val: 'e^x'},
|
||||||
|
};
|
||||||
|
|
||||||
|
var specialsGrid = [2, 2];
|
||||||
|
var specials = {
|
||||||
|
'R': {grid: [0, 0], globalGrid: [0, 0], trbl: 'RN7R', val: 'AC'},
|
||||||
|
'N': {grid: [1, 0], globalGrid: [1, 0], trbl: 'Np8R', val: '+/-'},
|
||||||
|
'p': {grid: [0, 1], globalGrid: [2, 0], trbl: 'p/9N', val: 'Pi'},
|
||||||
|
'send': {grid: [1, 1], val: 'send'},
|
||||||
|
};
|
||||||
|
|
||||||
|
var selected = DEFAULT_SELECTION_NUMBERS;
|
||||||
|
var prevSelected = DEFAULT_SELECTION_NUMBERS;
|
||||||
|
var prevNumber = null;
|
||||||
|
var currNumber = null;
|
||||||
|
var operator = null;
|
||||||
|
var results = null;
|
||||||
|
var isDecimal = false;
|
||||||
|
var hasPressedEquals = false;
|
||||||
|
var angleMode = 'deg';
|
||||||
|
var prevExpression = null;
|
||||||
|
var currExpression = "";
|
||||||
|
|
||||||
|
function prepareScreen(screen, grid, defaultColor) {
|
||||||
|
for (var k in screen) {
|
||||||
|
if (screen.hasOwnProperty(k)) {
|
||||||
|
screen[k].color = screen[k].color || defaultColor;
|
||||||
|
var position = [];
|
||||||
|
var xGrid = (KEY_AREA[2]-KEY_AREA[0])/grid[0];
|
||||||
|
var yGrid = (KEY_AREA[3]-KEY_AREA[1])/grid[1];
|
||||||
|
if (swipeEnabled) {
|
||||||
|
position[0] = KEY_AREA[0]+xGrid*screen[k].grid[0];
|
||||||
|
position[1] = KEY_AREA[1]+yGrid*screen[k].grid[1];
|
||||||
|
} else {
|
||||||
|
position[0] = KEY_AREA[0]+xGrid*screen[k].globalGrid[0];
|
||||||
|
position[1] = KEY_AREA[1]+yGrid*screen[k].globalGrid[1];
|
||||||
|
}
|
||||||
|
position[2] = position[0]+xGrid-1;
|
||||||
|
position[3] = position[1]+yGrid-1;
|
||||||
|
screen[k].xy = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawKey(name, k, selected) {
|
||||||
|
var color = k.color || COLORS.DEFAULT;
|
||||||
|
g.setColor(color[selected ? 1 : 0]);
|
||||||
|
g.setFont('Dylex7x13', 2).setFontAlign(0,0);
|
||||||
|
g.fillRect(k.xy[0], k.xy[1], k.xy[2], k.xy[3]);
|
||||||
|
g.setColor(0);
|
||||||
|
g.drawString(k.val || name, (k.xy[0] + k.xy[2])/2, (k.xy[1] + k.xy[3])/2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawKeys() {
|
||||||
|
g.setColor(screenColor[0]);
|
||||||
|
g.fillRect(KEY_AREA[0], KEY_AREA[1], KEY_AREA[2], KEY_AREA[3]);
|
||||||
|
for (var k in screen) {
|
||||||
|
if (screen.hasOwnProperty(k)) {
|
||||||
|
drawKey(k, screen[k], k == selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function drawGlobal() {
|
||||||
|
screen = {};
|
||||||
|
screenColor = COLORS.DEFAULT;
|
||||||
|
prepareScreen(numbers, globalGrid, COLORS.DEFAULT);
|
||||||
|
for (var k in numbers) {
|
||||||
|
screen[k] = numbers[k];
|
||||||
|
}
|
||||||
|
prepareScreen(operators, globalGrid, COLORS.OPERATOR);
|
||||||
|
for (var k in operators) {
|
||||||
|
screen[k] = operators[k];
|
||||||
|
}
|
||||||
|
prepareScreen(specials, globalGrid, COLORS.SPECIAL);
|
||||||
|
for (var k in specials) {
|
||||||
|
screen[k] = specials[k];
|
||||||
|
}
|
||||||
|
drawKeys();
|
||||||
|
}
|
||||||
|
function drawNumbers() {
|
||||||
|
screen = numbers;
|
||||||
|
screenColor = COLORS.DEFAULT;
|
||||||
|
drawKeys();
|
||||||
|
}
|
||||||
|
function drawOperators() {
|
||||||
|
screen = operators;
|
||||||
|
screenColor =COLORS.OPERATOR;
|
||||||
|
drawKeys();
|
||||||
|
}
|
||||||
|
function drawScientificOperators() {
|
||||||
|
screen = scientificOperators;
|
||||||
|
screenColor =COLORS.OPERATOR;
|
||||||
|
drawKeys();
|
||||||
|
}
|
||||||
|
function drawSpecials() {
|
||||||
|
screen = specials;
|
||||||
|
screenColor = COLORS.SPECIAL;
|
||||||
|
drawKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function toExponential(num, precision) {
|
||||||
|
if (num === 0) {
|
||||||
|
let z = "0";
|
||||||
|
if (precision > 0) z += "." + "0".repeat(precision);
|
||||||
|
return z + "e+0";
|
||||||
|
}
|
||||||
|
var sign = "";
|
||||||
|
if (num < 0) {
|
||||||
|
sign = "-";
|
||||||
|
num = -num;
|
||||||
|
}
|
||||||
|
var exp = Math.floor(Math.log(num) / Math.LN10);
|
||||||
|
var mantissa = num / Math.pow(10, exp);
|
||||||
|
|
||||||
|
var mantissaFixed = mantissa.toFixed(precision);
|
||||||
|
if (parseFloat(mantissaFixed) >= 10) {
|
||||||
|
mantissaFixed = (mantissa/10).toFixed(precision);
|
||||||
|
exp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sign + mantissaFixed + "e" + (exp >= 0 ? "+" : "") + exp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixFloat(n) {
|
||||||
|
if (Math.abs(n) < 1e-10) return 0;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSeparators(s) {
|
||||||
|
var parts = s.split(".");
|
||||||
|
var intPart = parts[0];
|
||||||
|
var sign = "";
|
||||||
|
if (intPart[0] === "-") {
|
||||||
|
sign = "-";
|
||||||
|
intPart = intPart.slice(1);
|
||||||
|
}
|
||||||
|
var result = "";
|
||||||
|
while (intPart.length > 3) {
|
||||||
|
result = "," + intPart.slice(-3) + result;
|
||||||
|
intPart = intPart.slice(0, -3);
|
||||||
|
}
|
||||||
|
result = intPart + result;
|
||||||
|
parts[0] = sign + result;
|
||||||
|
return parts.join(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
function doMath(x, y, operator) {
|
||||||
|
x = parseFloat(x);
|
||||||
|
y = parseFloat(y);
|
||||||
|
switch (operator) {
|
||||||
|
case '/':
|
||||||
|
return x / y;
|
||||||
|
case '*':
|
||||||
|
return x * y;
|
||||||
|
case '+':
|
||||||
|
return x + y;
|
||||||
|
case '-':
|
||||||
|
return x - y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayOutput(num) {
|
||||||
|
g.setBgColor(0).clearRect(0, 0, g.getWidth(), RESULT_HEIGHT-1);
|
||||||
|
g.setColor(-1);
|
||||||
|
g.setFont('Dylex7x13', 2);
|
||||||
|
if (num === Infinity || num === -Infinity || isNaN(num)) {
|
||||||
|
// handle division by 0
|
||||||
|
if (num === Infinity) {
|
||||||
|
num = 'INF';
|
||||||
|
} else if (num === -Infinity) {
|
||||||
|
num = '-INF';
|
||||||
|
} else {
|
||||||
|
num = 'NaN';
|
||||||
|
}
|
||||||
|
specials.R.val = 'AC';
|
||||||
|
if (!swipeEnabled) drawKey('R', specials.R);
|
||||||
|
} else {
|
||||||
|
// might not be a number due to display of dot "."
|
||||||
|
var numNumeric = Number(num);
|
||||||
|
|
||||||
|
if (typeof num === 'string') {
|
||||||
|
if (num.indexOf('.') !== -1) {
|
||||||
|
// display a 0 before a lonely dot
|
||||||
|
if (numNumeric == 0) {
|
||||||
|
num = '0.';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// remove preceding 0
|
||||||
|
while (num.length > 1 && num[0] === '0')
|
||||||
|
num = num.substr(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var numStr = num.toString();
|
||||||
|
var displayStr;
|
||||||
|
|
||||||
|
if (typeof num === 'number' && (numStr.indexOf('e') > -1 || (num !== 0 && Math.abs(num) < 1e-6))) {
|
||||||
|
// try to format as scientific notation
|
||||||
|
let precision = 10; // start with high precision
|
||||||
|
while (precision >= 0) {
|
||||||
|
let scientificStr = toExponential(num, precision).replace("e", "E");
|
||||||
|
if (g.stringWidth(scientificStr) <= g.getWidth() - 20) {
|
||||||
|
displayStr = scientificStr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
precision--;
|
||||||
|
}
|
||||||
|
if (precision < 0) { // if it still doesn't fit
|
||||||
|
displayStr = toExponential(num, 0).replace("e", "E");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
displayStr = addSeparators(numStr);
|
||||||
|
}
|
||||||
|
num = displayStr;
|
||||||
|
|
||||||
|
// final check for truncation
|
||||||
|
if (g.stringWidth(num) > g.getWidth() - 20) {
|
||||||
|
while(g.stringWidth(num+'...') > g.getWidth() - 20 && num.length > 1) {
|
||||||
|
num = num.slice(0,-1);
|
||||||
|
}
|
||||||
|
num += '...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.setFontAlign(1,0);
|
||||||
|
g.drawString(num, g.getWidth()-20, RESULT_HEIGHT/2);
|
||||||
|
if (operator) {
|
||||||
|
g.setFont('Dylex7x13', 2).setFontAlign(1,0);
|
||||||
|
g.drawString(operator, g.getWidth()-1, RESULT_HEIGHT/2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var wasPressedEquals = false;
|
||||||
|
var hasPressedNumber = false;
|
||||||
|
function calculatorLogic(x) {
|
||||||
|
if (wasPressedEquals && hasPressedNumber !== false) {
|
||||||
|
prevNumber = null;
|
||||||
|
currNumber = hasPressedNumber;
|
||||||
|
wasPressedEquals = false;
|
||||||
|
hasPressedNumber = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasPressedEquals) {
|
||||||
|
if (hasPressedNumber) {
|
||||||
|
prevNumber = null;
|
||||||
|
hasPressedNumber = false;
|
||||||
|
operator = null;
|
||||||
|
} else {
|
||||||
|
currNumber = null;
|
||||||
|
prevNumber = results;
|
||||||
|
}
|
||||||
|
hasPressedEquals = false;
|
||||||
|
wasPressedEquals = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currNumber == null && operator != null && '/*-+'.indexOf(x) !== -1) {
|
||||||
|
operator = x;
|
||||||
|
displayOutput(prevNumber);
|
||||||
|
} else if (prevNumber != null && currNumber != null && operator != null) {
|
||||||
|
// we execute the calculus only when there was a previous number entered before and an operator
|
||||||
|
let oldOperator = operator;
|
||||||
|
results = doMath(prevNumber, currNumber, operator);
|
||||||
|
operator = x;
|
||||||
|
prevNumber = results;
|
||||||
|
prevExpression = "(" + prevExpression + " " + oldOperator + " " + currExpression + ")";
|
||||||
|
currNumber = null;
|
||||||
|
currExpression = "";
|
||||||
|
displayOutput(results);
|
||||||
|
} else if (prevNumber == null && currNumber != null && operator == null) {
|
||||||
|
// no operator yet, save the current number for later use when an operator is pressed
|
||||||
|
operator = x;
|
||||||
|
prevNumber = currNumber;
|
||||||
|
prevExpression = currExpression;
|
||||||
|
currNumber = null;
|
||||||
|
currExpression = "";
|
||||||
|
displayOutput(prevNumber);
|
||||||
|
} else if (prevNumber == null && currNumber == null && operator == null) {
|
||||||
|
displayOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buttonPress(val) {
|
||||||
|
switch (val) {
|
||||||
|
case 'R':
|
||||||
|
currNumber = null;
|
||||||
|
results = null;
|
||||||
|
isDecimal = false;
|
||||||
|
hasPressedEquals = false;
|
||||||
|
if (specials.R.val == 'AC') {
|
||||||
|
prevNumber = null;
|
||||||
|
operator = null;
|
||||||
|
prevExpression = null;
|
||||||
|
currExpression = "";
|
||||||
|
} else {
|
||||||
|
specials.R.val = 'AC';
|
||||||
|
drawKey('R', specials.R, true);
|
||||||
|
}
|
||||||
|
wasPressedEquals = false;
|
||||||
|
hasPressedNumber = false;
|
||||||
|
displayOutput(0);
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
specials.R.val = 'C';
|
||||||
|
if (!swipeEnabled) drawKey('R', specials.R);
|
||||||
|
currNumber = Math.PI;
|
||||||
|
if (hasPressedEquals === 1) {
|
||||||
|
hasPressedEquals = 2;
|
||||||
|
}
|
||||||
|
hasPressedNumber = currNumber;
|
||||||
|
currExpression = currNumber.toString();
|
||||||
|
displayOutput(currNumber);
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
if (results != null) {
|
||||||
|
results = Math.sqrt(results);
|
||||||
|
displayOutput(results);
|
||||||
|
prevExpression = "sqrt(" + prevExpression + ")";
|
||||||
|
} else if (currNumber != null) {
|
||||||
|
currNumber = Math.sqrt(currNumber);
|
||||||
|
displayOutput(currNumber);
|
||||||
|
currExpression = "sqrt(" + currExpression + ")";
|
||||||
|
}
|
||||||
|
hasPressedNumber = false;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
if (results != null) {
|
||||||
|
results = results * results;
|
||||||
|
displayOutput(results);
|
||||||
|
prevExpression = "(" + prevExpression + ")^2";
|
||||||
|
} else if (currNumber != null) {
|
||||||
|
var num = parseFloat(currNumber);
|
||||||
|
currNumber = num * num;
|
||||||
|
displayOutput(currNumber);
|
||||||
|
currExpression = "(" + currExpression + ")^2";
|
||||||
|
}
|
||||||
|
hasPressedNumber = false;
|
||||||
|
break;
|
||||||
|
case 'sin':
|
||||||
|
if (results != null) {
|
||||||
|
let angle = results;
|
||||||
|
if (angleMode === 'deg') {
|
||||||
|
angle = angle * Math.PI / 180;
|
||||||
|
}
|
||||||
|
results = fixFloat(Math.sin(angle));
|
||||||
|
displayOutput(results);
|
||||||
|
prevExpression = "sin(" + prevExpression + ")";
|
||||||
|
} else if (currNumber != null) {
|
||||||
|
let angle = currNumber;
|
||||||
|
if (angleMode === 'deg') {
|
||||||
|
angle = angle * Math.PI / 180;
|
||||||
|
}
|
||||||
|
currNumber = fixFloat(Math.sin(angle));
|
||||||
|
displayOutput(currNumber);
|
||||||
|
currExpression = "sin(" + currExpression + ")";
|
||||||
|
}
|
||||||
|
hasPressedNumber = false;
|
||||||
|
break;
|
||||||
|
case 'cos':
|
||||||
|
if (results != null) {
|
||||||
|
let angle = results;
|
||||||
|
if (angleMode === 'deg') {
|
||||||
|
angle = angle * Math.PI / 180;
|
||||||
|
}
|
||||||
|
results = fixFloat(Math.cos(angle));
|
||||||
|
displayOutput(results);
|
||||||
|
prevExpression = "cos(" + prevExpression + ")";
|
||||||
|
} else if (currNumber != null) {
|
||||||
|
let angle = currNumber;
|
||||||
|
if (angleMode === 'deg') {
|
||||||
|
angle = angle * Math.PI / 180;
|
||||||
|
}
|
||||||
|
currNumber = fixFloat(Math.cos(angle));
|
||||||
|
displayOutput(currNumber);
|
||||||
|
currExpression = "cos(" + currExpression + ")";
|
||||||
|
}
|
||||||
|
hasPressedNumber = false;
|
||||||
|
break;
|
||||||
|
case 'tan':
|
||||||
|
if (results != null) {
|
||||||
|
let angle = results;
|
||||||
|
if (angleMode === 'deg') {
|
||||||
|
angle = angle * Math.PI / 180;
|
||||||
|
}
|
||||||
|
results = fixFloat(Math.tan(angle));
|
||||||
|
displayOutput(results);
|
||||||
|
prevExpression = "tan(" + prevExpression + ")";
|
||||||
|
} else if (currNumber != null) {
|
||||||
|
let angle = currNumber;
|
||||||
|
if (angleMode === 'deg') {
|
||||||
|
angle = angle * Math.PI / 180;
|
||||||
|
}
|
||||||
|
currNumber = fixFloat(Math.tan(angle));
|
||||||
|
displayOutput(currNumber);
|
||||||
|
currExpression = "tan(" + currExpression + ")";
|
||||||
|
}
|
||||||
|
hasPressedNumber = false;
|
||||||
|
break;
|
||||||
|
case 'log':
|
||||||
|
if (results != null) {
|
||||||
|
results = Math.log(results) / Math.LN10;
|
||||||
|
displayOutput(results);
|
||||||
|
prevExpression = "log(" + prevExpression + ")";
|
||||||
|
} else if (currNumber != null) {
|
||||||
|
currNumber = Math.log(currNumber) / Math.LN10;
|
||||||
|
displayOutput(currNumber);
|
||||||
|
currExpression = "log(" + currExpression + ")";
|
||||||
|
}
|
||||||
|
hasPressedNumber = false;
|
||||||
|
break;
|
||||||
|
case 'tenpow':
|
||||||
|
if (results != null) {
|
||||||
|
results = Math.pow(10, results);
|
||||||
|
displayOutput(results);
|
||||||
|
prevExpression = "10^(" + prevExpression + ")";
|
||||||
|
} else if (currNumber != null) {
|
||||||
|
currNumber = Math.pow(10, currNumber);
|
||||||
|
displayOutput(currNumber);
|
||||||
|
currExpression = "10^(" + currExpression + ")";
|
||||||
|
}
|
||||||
|
hasPressedNumber = false;
|
||||||
|
break;
|
||||||
|
case 'ln':
|
||||||
|
if (results != null) {
|
||||||
|
results = Math.log(results);
|
||||||
|
displayOutput(results);
|
||||||
|
prevExpression = "ln(" + prevExpression + ")";
|
||||||
|
} else if (currNumber != null) {
|
||||||
|
currNumber = Math.log(currNumber);
|
||||||
|
displayOutput(currNumber);
|
||||||
|
currExpression = "ln(" + currExpression + ")";
|
||||||
|
}
|
||||||
|
hasPressedNumber = false;
|
||||||
|
break;
|
||||||
|
case 'epow':
|
||||||
|
if (results != null) {
|
||||||
|
results = Math.exp(results);
|
||||||
|
displayOutput(results);
|
||||||
|
prevExpression = "e^(" + prevExpression + ")";
|
||||||
|
} else if (currNumber != null) {
|
||||||
|
currNumber = Math.exp(currNumber);
|
||||||
|
displayOutput(currNumber);
|
||||||
|
currExpression = "e^(" + currExpression + ")";
|
||||||
|
}
|
||||||
|
hasPressedNumber = false;
|
||||||
|
break;
|
||||||
|
case 'angleMode':
|
||||||
|
if (angleMode === 'rad') {
|
||||||
|
angleMode = 'deg';
|
||||||
|
} else {
|
||||||
|
angleMode = 'rad';
|
||||||
|
}
|
||||||
|
scientificOperators.angleMode.val = angleMode;
|
||||||
|
drawKey('angleMode', scientificOperators.angleMode);
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
if (results != null) {
|
||||||
|
results = 1 / results;
|
||||||
|
displayOutput(results);
|
||||||
|
prevExpression = "1/(" + prevExpression + ")";
|
||||||
|
} else if (currNumber != null) {
|
||||||
|
currNumber = 1 / parseFloat(currNumber);
|
||||||
|
displayOutput(currNumber);
|
||||||
|
currExpression = "1/(" + currExpression + ")";
|
||||||
|
}
|
||||||
|
hasPressedNumber = false;
|
||||||
|
break;
|
||||||
|
case 'N':
|
||||||
|
if (results != null && !hasPressedNumber) {
|
||||||
|
if (results === 0) {
|
||||||
|
results = (1/results === -Infinity || Object.is(results, -0)) ? 0 : -0;
|
||||||
|
} else {
|
||||||
|
results *= -1;
|
||||||
|
}
|
||||||
|
displayOutput(results);
|
||||||
|
prevExpression = "-(" + prevExpression + ")";
|
||||||
|
} else {
|
||||||
|
if (currNumber === null) currNumber = '0';
|
||||||
|
var num = parseFloat(currNumber);
|
||||||
|
if (num === 0) {
|
||||||
|
// Toggle between 0 and -0
|
||||||
|
currNumber = (1/currNumber === -Infinity || Object.is(currNumber, -0)) ? '0' : -0;
|
||||||
|
} else {
|
||||||
|
currNumber = num * -1;
|
||||||
|
}
|
||||||
|
displayOutput(currNumber);
|
||||||
|
currExpression = "-(" + currExpression + ")";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'B':
|
||||||
|
Bangle.buzz(30);
|
||||||
|
if (isDecimal) {
|
||||||
|
isDecimal = false;
|
||||||
|
displayOutput(currNumber);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (currNumber != null) {
|
||||||
|
currNumber = currNumber.toString();
|
||||||
|
if (currNumber.length > 1) {
|
||||||
|
currNumber = currNumber.slice(0, -1);
|
||||||
|
} else {
|
||||||
|
currNumber = '0';
|
||||||
|
}
|
||||||
|
// if we removed a decimal point
|
||||||
|
if (currNumber.indexOf('.') === -1) {
|
||||||
|
isDecimal = false;
|
||||||
|
}
|
||||||
|
hasPressedNumber = currNumber;
|
||||||
|
currExpression = currNumber;
|
||||||
|
displayOutput(currNumber);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
case '*':
|
||||||
|
case '-':
|
||||||
|
case '+':
|
||||||
|
calculatorLogic(val);
|
||||||
|
hasPressedNumber = false;
|
||||||
|
if (swipeEnabled) drawNumbers();
|
||||||
|
break;
|
||||||
|
case '.':
|
||||||
|
Bangle.buzz(30);
|
||||||
|
if (currNumber != null && currNumber.toString().indexOf('.') !== -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
specials.R.val = 'C';
|
||||||
|
if (!swipeEnabled) drawKey('R', specials.R);
|
||||||
|
isDecimal = true;
|
||||||
|
displayOutput(currNumber == null ? 0 + '.' : currNumber + '.');
|
||||||
|
break;
|
||||||
|
case '=':
|
||||||
|
if (prevNumber != null && currNumber != null && operator != null) {
|
||||||
|
let cExpr = currExpression;
|
||||||
|
if (hasPressedEquals && cExpr === "") {
|
||||||
|
cExpr = currNumber.toString();
|
||||||
|
}
|
||||||
|
prevExpression = prevExpression + " " + operator + " " + cExpr;
|
||||||
|
results = doMath(prevNumber, currNumber, operator);
|
||||||
|
prevNumber = results;
|
||||||
|
currExpression = "";
|
||||||
|
displayOutput(results);
|
||||||
|
hasPressedEquals = 1;
|
||||||
|
}
|
||||||
|
hasPressedNumber = false;
|
||||||
|
break;
|
||||||
|
case 'send':
|
||||||
|
let logStr;
|
||||||
|
if (hasPressedEquals) {
|
||||||
|
logStr = prevExpression + " = " + results;
|
||||||
|
} else if (operator && prevExpression) { // Incomplete binary operation, e.g. "5 +" or "5 + 3"
|
||||||
|
logStr = prevExpression + " " + operator;
|
||||||
|
if (currExpression) {
|
||||||
|
logStr += " " + currExpression;
|
||||||
|
}
|
||||||
|
} else if (results) { // Result of a unary op on a previous result
|
||||||
|
logStr = prevExpression + " = " + results;
|
||||||
|
} else if (currNumber) { // A number has been entered, or a unary op on it
|
||||||
|
if (currExpression && currExpression !== currNumber.toString()) {
|
||||||
|
logStr = currExpression + " = " + currNumber;
|
||||||
|
} else {
|
||||||
|
logStr = currNumber.toString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logStr = results ? results.toString() : "0";
|
||||||
|
}
|
||||||
|
console.log(logStr);
|
||||||
|
if (Bangle.http){
|
||||||
|
const options = {timeout:3000, method: "post", body: logStr};
|
||||||
|
Bangle.http("https://tbot.tannercollin.com/banglecalc", options).then(event => {
|
||||||
|
Bangle.buzz();
|
||||||
|
console.log("Successfully sent");
|
||||||
|
}).catch((e)=>{
|
||||||
|
g.setBgColor(0).clearRect(0, 0, g.getWidth(), RESULT_HEIGHT-1);
|
||||||
|
g.setColor(-1);
|
||||||
|
g.setFont('Dylex7x13', 2);
|
||||||
|
g.setFontAlign(1,0);
|
||||||
|
g.drawString('ERROR', g.getWidth()-20, RESULT_HEIGHT/2);
|
||||||
|
if (operator) {
|
||||||
|
g.setFont('Dylex7x13', 2).setFontAlign(1,0);
|
||||||
|
g.drawString(operator, g.getWidth()-1, RESULT_HEIGHT/2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
Bangle.buzz(30);
|
||||||
|
specials.R.val = 'C';
|
||||||
|
if (!swipeEnabled) drawKey('R', specials.R);
|
||||||
|
const is0Negative = (currNumber === 0 && 1/currNumber === -Infinity);
|
||||||
|
if (isDecimal) {
|
||||||
|
currNumber = currNumber == null || hasPressedEquals === 1 ? '0.' + val : parseInt(currNumber, 10) + '.' + val;
|
||||||
|
isDecimal = false;
|
||||||
|
} else {
|
||||||
|
currNumber = currNumber == null || hasPressedEquals === 1 ? val : (is0Negative ? '-' + val : currNumber + val);
|
||||||
|
// remove preceding 0 on integers
|
||||||
|
if (typeof currNumber === 'string' && currNumber.length > 1 && currNumber[0] === '0' && currNumber.indexOf('.') === -1) {
|
||||||
|
currNumber = currNumber.substr(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasPressedEquals === 1) {
|
||||||
|
hasPressedEquals = 2;
|
||||||
|
}
|
||||||
|
hasPressedNumber = currNumber;
|
||||||
|
currExpression = currNumber;
|
||||||
|
displayOutput(currNumber);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveDirection(d) {
|
||||||
|
drawKey(selected, screen[selected]);
|
||||||
|
prevSelected = selected;
|
||||||
|
selected = (d === 0 && selected == '0' && prevSelected === '1') ? '1' : screen[selected].trbl[d];
|
||||||
|
drawKey(selected, screen[selected], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.HWVERSION==1) {
|
||||||
|
setWatch(_ => moveDirection(0), BTN1, {repeat: true, debounce: 100});
|
||||||
|
setWatch(_ => moveDirection(2), BTN3, {repeat: true, debounce: 100});
|
||||||
|
setWatch(_ => moveDirection(3), BTN4, {repeat: true, debounce: 100});
|
||||||
|
setWatch(_ => moveDirection(1), BTN5, {repeat: true, debounce: 100});
|
||||||
|
setWatch(_ => buttonPress(selected), BTN2, {repeat: true, debounce: 100});
|
||||||
|
swipeEnabled = false;
|
||||||
|
drawGlobal();
|
||||||
|
} else { // touchscreen?
|
||||||
|
selected = "NONE";
|
||||||
|
swipeEnabled = true;
|
||||||
|
prepareScreen(numbers, numbersGrid, COLORS.DEFAULT);
|
||||||
|
prepareScreen(operators, operatorsGrid, COLORS.OPERATOR);
|
||||||
|
prepareScreen(scientificOperators, scientificOperatorsGrid, COLORS.OPERATOR);
|
||||||
|
prepareScreen(specials, specialsGrid, COLORS.SPECIAL);
|
||||||
|
drawNumbers();
|
||||||
|
|
||||||
|
Bangle.setUI({
|
||||||
|
mode : 'custom',
|
||||||
|
back : load, // Clicking physical button or pressing upper left corner turns off (where red back button would be)
|
||||||
|
touch : (n,e)=>{
|
||||||
|
for (var key in screen) {
|
||||||
|
if (typeof screen[key] == "undefined") break;
|
||||||
|
var r = screen[key].xy;
|
||||||
|
if (e.x>=r[0] && e.y>=r[1] && e.x<r[2] && e.y<r[3]) {
|
||||||
|
//print("Press "+key);
|
||||||
|
buttonPress(""+key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
swipe : (LR, UD) => {
|
||||||
|
if (UD !== 0) {
|
||||||
|
drawNumbers();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (LR === 1) { // right
|
||||||
|
if (screen === scientificOperators) drawOperators();
|
||||||
|
else if (screen === operators) drawNumbers();
|
||||||
|
else if (screen === numbers) drawSpecials();
|
||||||
|
else if (screen === specials) drawNumbers();
|
||||||
|
}
|
||||||
|
if (LR === -1) { // left
|
||||||
|
if (screen === numbers) drawOperators();
|
||||||
|
else if (screen === operators) drawScientificOperators();
|
||||||
|
else if (screen === specials) drawNumbers();
|
||||||
|
else if (screen === scientificOperators) drawNumbers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
displayOutput(0);
|
||||||
@@ -0,0 +1,669 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
// --- Mock Bangle.js environment ---
|
||||||
|
let mock_display_str = "";
|
||||||
|
let mock_ui_callbacks = {};
|
||||||
|
let drawn_since_clear = false;
|
||||||
|
global.last_display_num = null;
|
||||||
|
|
||||||
|
// Mock 'g' (graphics)
|
||||||
|
global.g = {
|
||||||
|
_col: 0,
|
||||||
|
_bg: 0,
|
||||||
|
_font: "6x8",
|
||||||
|
_font_size: 1,
|
||||||
|
clear: () => { drawn_since_clear = false; },
|
||||||
|
clearRect: () => { drawn_since_clear = false; },
|
||||||
|
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 * 4.7 * (global.g._font_size || 1), // approximation to match watch
|
||||||
|
getWidth: () => 176,
|
||||||
|
getHeight: () => 176,
|
||||||
|
drawString: (s) => {
|
||||||
|
if (!drawn_since_clear) {
|
||||||
|
mock_display_str = String(s);
|
||||||
|
drawn_since_clear = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFontHeight: () => 8 * (global.g._font_size || 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
global.Graphics = {};
|
||||||
|
|
||||||
|
// Mock Bangle.js-specific process.env properties
|
||||||
|
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 = () => {};
|
||||||
|
|
||||||
|
// --- End Mock ---
|
||||||
|
|
||||||
|
|
||||||
|
// --- Load calculator app ---
|
||||||
|
// We inject a "spy" to capture the raw value passed to displayOutput,
|
||||||
|
// making our tests independent of UI formatting.
|
||||||
|
let calculatorCode = fs.readFileSync(path.join(__dirname, 'calculator.app.js'), 'utf8');
|
||||||
|
// 1. Rename the original function so we can call it.
|
||||||
|
calculatorCode = calculatorCode.replace(
|
||||||
|
'function displayOutput(num)',
|
||||||
|
'function _original_displayOutput(num)'
|
||||||
|
);
|
||||||
|
// 2. Prepend our spy that captures the value and then calls the original.
|
||||||
|
const spyCode = `
|
||||||
|
var displayOutput = function(num) {
|
||||||
|
global.last_display_num = num;
|
||||||
|
_original_displayOutput(num);
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
const processedCode = spyCode + calculatorCode;
|
||||||
|
|
||||||
|
const wrappedCode = `(function(require) { ${processedCode}; return { buttonPress, scientificOperators }; })`;
|
||||||
|
const getAppFns = eval(wrappedCode);
|
||||||
|
const { buttonPress, scientificOperators } = getAppFns((name) => {
|
||||||
|
if (name === "FontDylex7x13") {
|
||||||
|
return { add: () => {} };
|
||||||
|
}
|
||||||
|
return require(name);
|
||||||
|
});
|
||||||
|
// --- 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 (global.last_display_num !== 0) {
|
||||||
|
console.error(`[FAIL] ${t.name} - Failed to reset state. Last number is: "${global.last_display_num}"`);
|
||||||
|
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) {
|
||||||
|
const actual = global.last_display_num;
|
||||||
|
|
||||||
|
if (typeof expected === 'number' && isNaN(expected)) {
|
||||||
|
assert.ok(typeof actual === 'number' && isNaN(actual), message || `Expected NaN, got ${actual}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coerce to numbers for comparison to handle string vs number differences ('5' vs 5)
|
||||||
|
// and use a tolerance for floating point values.
|
||||||
|
const actualNum = parseFloat(actual);
|
||||||
|
const expectedNum = parseFloat(expected);
|
||||||
|
|
||||||
|
if (!isNaN(actualNum) && !isNaN(expectedNum)) {
|
||||||
|
const tolerance = 1e-9;
|
||||||
|
if (Math.abs(expectedNum - actualNum) < tolerance || actualNum === expectedNum) { // second part handles Infinity
|
||||||
|
return; // The numbers are close enough.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to strict equality for non-numeric strings or when numeric check fails
|
||||||
|
assert.strictEqual(actual, expected, message);
|
||||||
|
}
|
||||||
|
// --- End Test Framework ---
|
||||||
|
|
||||||
|
|
||||||
|
// --- Test Cases ---
|
||||||
|
|
||||||
|
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,
|
||||||
|
];
|
||||||
|
|
||||||
|
const edgeCaseNumbers = [
|
||||||
|
-0,
|
||||||
|
Number.MAX_SAFE_INTEGER,
|
||||||
|
Number.MIN_SAFE_INTEGER,
|
||||||
|
Math.PI, Math.E,
|
||||||
|
0.000001, -0.000001, 0.00000001, -0.00000001,
|
||||||
|
1000000, -1000000, 100000000, -100000000,
|
||||||
|
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) {
|
||||||
|
let s = String(n);
|
||||||
|
// If string representation is in scientific notation, convert to decimal string
|
||||||
|
if (s.includes('e')) {
|
||||||
|
s = n.toFixed(20).replace(/0+$/, '').replace(/\.$/, '');
|
||||||
|
}
|
||||||
|
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 finiteTestNumbers) {
|
||||||
|
for (const b of finiteTestNumbers) {
|
||||||
|
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: 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 },
|
||||||
|
's': { name: 'x^2', fn: (a) => a * a },
|
||||||
|
'i': { name: '1/x', fn: (a) => 1 / a },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const op in unaryOps) {
|
||||||
|
for (const a of finiteTestNumbers) {
|
||||||
|
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, -1, 5, -5, 0.5, -0.5, 10, -10, 123, -123, 1.23];
|
||||||
|
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, 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) {
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const op in trigOps) {
|
||||||
|
for (const angle of trigAngles) {
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// --- Specific Functionality Tests ---
|
||||||
|
|
||||||
|
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('Backspace', () => {
|
||||||
|
press('123.45B');
|
||||||
|
checkDisplay('123.4');
|
||||||
|
press('B');
|
||||||
|
checkDisplay('123.');
|
||||||
|
press('B');
|
||||||
|
checkDisplay('123');
|
||||||
|
press('B');
|
||||||
|
checkDisplay('12');
|
||||||
|
press('B');
|
||||||
|
checkDisplay('1');
|
||||||
|
press('B');
|
||||||
|
checkDisplay('0');
|
||||||
|
press('B');
|
||||||
|
checkDisplay('0');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Number input chaos: decimals and backspace', () => {
|
||||||
|
// Multiple decimals
|
||||||
|
press('1.2.3');
|
||||||
|
checkDisplay('1.23', 'Second decimal point should be ignored');
|
||||||
|
press('R'); press('R');
|
||||||
|
|
||||||
|
// Multiple leading decimals
|
||||||
|
press('..123');
|
||||||
|
checkDisplay('0.123', 'Multiple leading decimal points should result in 0.123');
|
||||||
|
press('R'); press('R');
|
||||||
|
|
||||||
|
// Backspace over decimal and re-add
|
||||||
|
press('1.23B');
|
||||||
|
checkDisplay('1.2', 'Backspace once');
|
||||||
|
press('B');
|
||||||
|
checkDisplay('1.', 'Backspace twice');
|
||||||
|
press('.');
|
||||||
|
checkDisplay('1.', 'Adding decimal again should have no effect');
|
||||||
|
press('B');
|
||||||
|
checkDisplay('1', 'Backspace a third time');
|
||||||
|
press('.');
|
||||||
|
checkDisplay('1.', 'Should be able to add decimal back');
|
||||||
|
press('45');
|
||||||
|
checkDisplay('1.45', 'Can add numbers after new decimal');
|
||||||
|
press('.');
|
||||||
|
checkDisplay('1.45', 'Another decimal should be ignored');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Number input chaos: leading zeros', () => {
|
||||||
|
press('01');
|
||||||
|
checkDisplay('1', 'Leading zero on integer should be replaced');
|
||||||
|
press('R'); press('R');
|
||||||
|
|
||||||
|
press('000123');
|
||||||
|
checkDisplay('123', 'Multiple leading zeros on integer should be replaced');
|
||||||
|
press('R'); press('R');
|
||||||
|
|
||||||
|
press('0.123');
|
||||||
|
checkDisplay('0.123', 'Single zero before decimal is kept');
|
||||||
|
press('R'); press('R');
|
||||||
|
|
||||||
|
press('00.123');
|
||||||
|
checkDisplay('0.123', 'Multiple zeros before decimal are collapsed to one');
|
||||||
|
press('R'); press('R');
|
||||||
|
|
||||||
|
press('123');
|
||||||
|
press('R'); // C
|
||||||
|
checkDisplay(0, 'Clear should result in 0');
|
||||||
|
press('007');
|
||||||
|
checkDisplay('7', 'Can input number with leading zeros after a clear');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Leading zeros are visually removed during input', () => {
|
||||||
|
press('0');
|
||||||
|
assert.strictEqual(global.last_display_num, '0', 'Input: 0');
|
||||||
|
press('0');
|
||||||
|
assert.strictEqual(global.last_display_num, '0', 'Second 0 should not create "00"');
|
||||||
|
press('7');
|
||||||
|
assert.strictEqual(global.last_display_num, '7', '0 should be replaced by 7');
|
||||||
|
press('R'); press('R');
|
||||||
|
|
||||||
|
press('0');
|
||||||
|
press('0');
|
||||||
|
press('.');
|
||||||
|
assert.strictEqual(global.last_display_num, '0.', 'Input: 00. -> "0."');
|
||||||
|
press('4');
|
||||||
|
assert.strictEqual(global.last_display_num, '0.4', 'Input: 00.4 -> "0.4"');
|
||||||
|
press('R'); press('R');
|
||||||
|
|
||||||
|
// After a calculation, starting a new number
|
||||||
|
press('1+2=');
|
||||||
|
checkDisplay(3);
|
||||||
|
press('0');
|
||||||
|
assert.strictEqual(global.last_display_num, '0', 'Post-calc: 0');
|
||||||
|
press('0');
|
||||||
|
assert.strictEqual(global.last_display_num, '0', 'Post-calc: 00 -> "0"');
|
||||||
|
press('5');
|
||||||
|
assert.strictEqual(global.last_display_num, '5', 'Post-calc: 005 -> "5"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Repeated equals', () => {
|
||||||
|
press('2+3=');
|
||||||
|
checkDisplay('5');
|
||||||
|
press('=');
|
||||||
|
checkDisplay('8');
|
||||||
|
press('=');
|
||||||
|
checkDisplay('11');
|
||||||
|
press('R'); press('R');
|
||||||
|
press('10-2=');
|
||||||
|
checkDisplay('8');
|
||||||
|
press('=');
|
||||||
|
checkDisplay('6');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Operator change', () => {
|
||||||
|
press('10+-*/2=');
|
||||||
|
checkDisplay('5'); // 10/2=5
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Long number input', () => {
|
||||||
|
press('1234567');
|
||||||
|
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('5'); // after typing 5, display should be 5
|
||||||
|
press('=');
|
||||||
|
checkDisplay(-0); // -0 * 5 = -0
|
||||||
|
buttonPress('R'); buttonPress('R');
|
||||||
|
press('5*0N='); // 5 * -0 = -0
|
||||||
|
checkDisplay(-0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pi button', () => {
|
||||||
|
press('p');
|
||||||
|
checkDisplay(Math.PI);
|
||||||
|
press('*2=');
|
||||||
|
checkDisplay(Math.PI * 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pi replaces current number entry', () => {
|
||||||
|
press('123p');
|
||||||
|
checkDisplay(Math.PI);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Operation with Pi', () => {
|
||||||
|
press('5*p=');
|
||||||
|
checkDisplay(5 * Math.PI);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pi as second operand', () => {
|
||||||
|
press('2+p=');
|
||||||
|
checkDisplay(2 + Math.PI);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Chaining operations with Pi', () => {
|
||||||
|
press('p*2+3=');
|
||||||
|
checkDisplay(Math.PI * 2 + 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Unary operations on Pi', () => {
|
||||||
|
press('p');
|
||||||
|
checkDisplay(Math.PI);
|
||||||
|
press('r'); // sqrt
|
||||||
|
checkDisplay(Math.sqrt(Math.PI));
|
||||||
|
press('R'); press('R'); // AC
|
||||||
|
press('p');
|
||||||
|
press('s'); // x^2
|
||||||
|
checkDisplay(Math.PI * Math.PI);
|
||||||
|
press('R'); press('R'); // AC
|
||||||
|
press('p');
|
||||||
|
press('i'); // 1/x
|
||||||
|
checkDisplay(1 / Math.PI);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Trig functions with Pi (rad)', () => {
|
||||||
|
buttonPress('angleMode'); // switch to RAD
|
||||||
|
assert.strictEqual(scientificOperators.angleMode.val, 'rad');
|
||||||
|
|
||||||
|
press('p');
|
||||||
|
buttonPress('sin');
|
||||||
|
checkDisplay(0); // Math.sin(Math.PI) is ~0, calculator snaps to 0
|
||||||
|
press('R'); press('R');
|
||||||
|
|
||||||
|
press('p');
|
||||||
|
buttonPress('cos');
|
||||||
|
checkDisplay(-1);
|
||||||
|
press('R'); press('R');
|
||||||
|
|
||||||
|
press('p');
|
||||||
|
buttonPress('tan');
|
||||||
|
checkDisplay(0); // Math.tan(Math.PI) is ~0, calculator snaps to 0
|
||||||
|
|
||||||
|
buttonPress('angleMode'); // switch back to DEG
|
||||||
|
assert.strictEqual(scientificOperators.angleMode.val, 'deg');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pi after equals', () => {
|
||||||
|
press('1+2=');
|
||||||
|
checkDisplay(3);
|
||||||
|
press('p');
|
||||||
|
checkDisplay(Math.PI);
|
||||||
|
press('+1=');
|
||||||
|
checkDisplay(Math.PI + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pi with negative operator', () => {
|
||||||
|
press('pN');
|
||||||
|
checkDisplay(-Math.PI);
|
||||||
|
press('*2=');
|
||||||
|
checkDisplay(-Math.PI * 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Negative number times Pi', () => {
|
||||||
|
press('2N*p=');
|
||||||
|
checkDisplay(-2 * Math.PI);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run all the defined tests
|
||||||
|
runTests();
|
||||||
Reference in New Issue
Block a user