Fix App Calculator issues

- Javascript rounded float issues
- Reset after equals wasn't done properly
- Introduce unit tests 🤗
pull/323/head
fredericrous 2020-04-18 16:30:05 +01:00
parent 745e4d38a9
commit aa168c3d5c
4 changed files with 369 additions and 55 deletions

View File

@ -1264,8 +1264,8 @@
"name": "Calculator", "name": "Calculator",
"shortName":"Calculator", "shortName":"Calculator",
"icon": "calculator.png", "icon": "calculator.png",
"version":"0.01", "version":"0.02",
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus. Push button1 and 3 to navigate up/down, tap right or left to navigate the sides, push button 2 to select.", "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
"tags": "app,tool", "tags": "app,tool",
"storage": [ "storage": [
{"name":"calculator.app.js","url":"app.js"}, {"name":"calculator.app.js","url":"app.js"},

View File

@ -1 +1,2 @@
0.01: New App! 0.01: New App!
0.02: fix precision rounding issue + no reset when equals pressed

View File

@ -144,19 +144,57 @@ function drawKey(name, k, selected) {
g.drawString(k.val || name, k.xy[0] + RIGHT_MARGIN + rMargin, k.xy[1] + BOTTOM_MARGIN + bMargin); g.drawString(k.val || name, k.xy[0] + RIGHT_MARGIN + rMargin, k.xy[1] + BOTTOM_MARGIN + bMargin);
} }
function getIntWithPrecision(x) {
var xStr = x.toString();
var xRadix = xStr.indexOf('.');
var xPrecision = xRadix === -1 ? 0 : xStr.length - xRadix - 1;
return {
num: Number(xStr.replace('.', '')),
p: xPrecision
};
}
function multiply(x, y) {
var xNum = getIntWithPrecision(x);
var yNum = getIntWithPrecision(y);
return xNum.num * yNum.num / Math.pow(10, xNum.p + yNum.p);
}
function divide(x, y) {
var xNum = getIntWithPrecision(x);
var yNum = getIntWithPrecision(y);
return xNum.num / yNum.num / Math.pow(10, xNum.p - yNum.p);
}
function sum(x, y) {
let xNum = getIntWithPrecision(x);
let yNum = getIntWithPrecision(y);
let diffPrecision = Math.abs(xNum.p - yNum.p);
if (diffPrecision > 0) {
if (xNum.p > yNum.p) {
yNum.num = yNum.num * Math.pow(10, diffPrecision);
} else {
xNum.num = xNum.num * Math.pow(10, diffPrecision);
}
}
return (xNum.num + yNum.num) / Math.pow(10, Math.max(xNum.p, yNum.p));
}
function subtract(x, y) {
return sum(x, -y);
}
function doMath(x, y, operator) { function doMath(x, y, operator) {
// might not be a number due to display of dot "." algo
x = Number(x);
y = Number(y);
switch (operator) { switch (operator) {
case '/': case '/':
return x / y; return divide(x, y);
case '*': case '*':
return x * y; return multiply(x, y);
case '+': case '+':
return x + y; return sum(x, y);
case '-': case '-':
return x - y; return subtract(x, y);
} }
} }
@ -204,7 +242,7 @@ function displayOutput(num) {
} }
len = (num + '').length; len = (num + '').length;
if (numNumeric < 0) { if (numNumeric < 0 || (numNumeric === 0 && 1/numNumeric === -Infinity)) {
// minus is not available in font 7x11Numeric7Seg, we use Vector // minus is not available in font 7x11Numeric7Seg, we use Vector
g.setFont('Vector', 20); g.setFont('Vector', 20);
g.drawString('-', 220 - (len * 15), 10); g.drawString('-', 220 - (len * 15), 10);
@ -214,18 +252,33 @@ function displayOutput(num) {
} }
g.drawString(num, 220 - (len * 15) + minusMarge, 10); g.drawString(num, 220 - (len * 15) + minusMarge, 10);
} }
var wasPressedEquals = false;
var hasPressedNumber = false;
function calculatorLogic(x) { function calculatorLogic(x) {
if (hasPressedEquals) { if (wasPressedEquals && hasPressedNumber !== false) {
currNumber = results;
prevNumber = null; prevNumber = null;
operator = null; currNumber = hasPressedNumber;
results = null; wasPressedEquals = false;
isDecimal = null; hasPressedNumber = false;
displayOutput(currNumber); return;
hasPressedEquals = false;
} }
if (prevNumber != null && currNumber != null && operator != null) { 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 // we execute the calculus only when there was a previous number entered before and an operator
results = doMath(prevNumber, currNumber, operator); results = doMath(prevNumber, currNumber, operator);
operator = x; operator = x;
@ -255,8 +308,10 @@ function buttonPress(val) {
operator = null; operator = null;
} else { } else {
keys.R.val = 'AC'; keys.R.val = 'AC';
drawKey('R', keys.R); drawKey('R', keys.R, true);
} }
wasPressedEquals = false;
hasPressedNumber = false;
displayOutput(0); displayOutput(0);
break; break;
case '%': case '%':
@ -265,11 +320,12 @@ function buttonPress(val) {
} else if (currNumber != null) { } else if (currNumber != null) {
displayOutput(currNumber /= 100); displayOutput(currNumber /= 100);
} }
hasPressedNumber = false;
break; break;
case 'N': case 'N':
if (results != null) { if (results != null) {
displayOutput(results *= -1); displayOutput(results *= -1);
} else if (currNumber != null) { } else {
displayOutput(currNumber *= -1); displayOutput(currNumber *= -1);
} }
break; break;
@ -278,6 +334,7 @@ function buttonPress(val) {
case '-': case '-':
case '+': case '+':
calculatorLogic(val); calculatorLogic(val);
hasPressedNumber = false;
break; break;
case '.': case '.':
keys.R.val = 'C'; keys.R.val = 'C';
@ -290,18 +347,24 @@ function buttonPress(val) {
results = doMath(prevNumber, currNumber, operator); results = doMath(prevNumber, currNumber, operator);
prevNumber = results; prevNumber = results;
displayOutput(results); displayOutput(results);
hasPressedEquals = true; hasPressedEquals = 1;
} }
hasPressedNumber = false;
break; break;
default: default:
keys.R.val = 'C'; keys.R.val = 'C';
drawKey('R', keys.R); drawKey('R', keys.R);
const is0Negative = (currNumber === 0 && 1/currNumber === -Infinity);
if (isDecimal) { if (isDecimal) {
currNumber = currNumber == null ? 0 + '.' + val : currNumber + '.' + val; currNumber = currNumber == null || hasPressedEquals === 1 ? 0 + '.' + val : currNumber + '.' + val;
isDecimal = false; isDecimal = false;
} else { } else {
currNumber = currNumber == null ? val : currNumber + val; currNumber = currNumber == null || hasPressedEquals === 1 ? val : (is0Negative ? '-' + val : currNumber + val);
} }
if (hasPressedEquals === 1) {
hasPressedEquals = 2;
}
hasPressedNumber = currNumber;
displayOutput(currNumber); displayOutput(currNumber);
break; break;
} }
@ -315,38 +378,15 @@ for (var k in keys) {
g.setFont('7x11Numeric7Seg', 2.8); g.setFont('7x11Numeric7Seg', 2.8);
g.drawString('0', 205, 10); g.drawString('0', 205, 10);
function moveDirection(d) {
setWatch(function() {
drawKey(selected, keys[selected]);
// key 0 is 2 keys wide, go up to 1 if it was previously selected
if (selected == '0' && prevSelected === '1') {
prevSelected = selected;
selected = '1';
} else {
prevSelected = selected;
selected = keys[selected].trbl[0];
}
drawKey(selected, keys[selected], true);
}, BTN1, {repeat: true, debounce: 100});
setWatch(function() {
drawKey(selected, keys[selected]); drawKey(selected, keys[selected]);
prevSelected = selected; prevSelected = selected;
selected = keys[selected].trbl[2]; selected = (d === 0 && selected == '0' && prevSelected === '1') ? '1' : keys[selected].trbl[d];
drawKey(selected, keys[selected], true); drawKey(selected, keys[selected], true);
}, BTN3, {repeat: true, debounce: 100}); }
Bangle.on('touch', function(direction) { setWatch(_ => moveDirection(0), BTN1, {repeat: true, debounce: 100});
drawKey(selected, keys[selected]); setWatch(_ => moveDirection(2), BTN3, {repeat: true, debounce: 100});
prevSelected = selected; setWatch(_ => moveDirection(3), BTN4, {repeat: true, debounce: 100});
if (direction == 1) { setWatch(_ => moveDirection(1), BTN5, {repeat: true, debounce: 100});
selected = keys[selected].trbl[3]; setWatch(_ => buttonPress(selected), BTN2, {repeat: true, debounce: 100});
} else if (direction == 2) {
selected = keys[selected].trbl[1];
}
drawKey(selected, keys[selected], true);
});
setWatch(function() {
buttonPress(selected);
}, BTN2, {repeat: true, debounce: 100});

273
apps/calculator/tests.html Normal file
View File

@ -0,0 +1,273 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Calculator tests</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/7.1.0/mocha.min.css">
<style>
#header {
margin: 60px 50px;
font: 1em "Helvetica Neue",Helvetica,Arial,sans-serif;
font-weight: 200;
}
</style>
</head>
<body>
<div id="header"></div>
<div id="mocha"></div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mocha/7.1.0/mocha.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.2.0/chai.min.js"></script>
<script>
// mocks
const _ = () => {}
setWatch = _
drawKey = _
currentOutput = 0;
g = {
setFont: _,
drawString: n => currentOutput = n,
setColor: _,
fillRect: _,
clear: _
}
_graphics = function () {
this.setFontCustom = _
}
Graphics = _graphics
BTN1 = 1
BTN2 = 2
BTN3 = 3
BTN4 = 4
BTN5 = 5
Bangle = {
on: _
}
Terminal = { println: console.log }
</script>
<script src="app.js"></script>
<script>
header`
// Unit tests for the BangleJS's Calculator app
`
mocha.setup({ui:'bdd'})
chai.should()
var expect = chai.expect
const sequencePress = x => x.split('').forEach(y => buttonPress(y))
const sequenceReset = _ => [...Array(2)].forEach(x => buttonPress('R'))
describe("Simple arithmetic", function(){
it("multiplication", function(){
multiply(1.4,2.4).should.equal(3.36)
})
it("division", function(){
divide(4.4,2).should.equal(2.2)
})
it("sum", function(){
sum(4.1,2).should.equal(6.1)
})
it("subtract", function(){
subtract(4.1,2).should.equal(2.1)
})
})
describe("Simple Operation with Reset", function(){
it("Simple addition", function(){
sequencePress("50+3=")
currentOutput.should.equal(53)
})
it("Reset output 'C' then 'AC'", function(){
sequenceReset()
currentOutput.should.equal(0)
})
it("Complex calculus", function(){
sequenceReset()
sequencePress("3*3+3-2/2=")
currentOutput.should.equal(5)
})
it("Change operator", function(){
sequenceReset()
sequencePress("3*+/3=")
currentOutput.should.equal(1)
})
})
describe("Operations on Double-s", function(){
it("Simple addition", function(){
sequenceReset()
sequencePress("1.3+1.7=")
currentOutput.should.equal(3)
})
it("some calculation", function(){
sequenceReset()
sequencePress("1.3+1.7*2.22/2=")
currentOutput.should.equal(3.33)
})
it("No corrupt opposed to what javascript Number would", function(){
sequenceReset()
sequencePress("1.3+1.7*2.2/2=")
currentOutput.should.equal(3.3)
})
it("Complex calcul", function(){
sequenceReset()
sequencePress("48/.2/")
currentOutput.should.equal(240)
})
})
describe("Negative Operations", function(){
it("Negative on first number", function(){
sequenceReset()
sequencePress("50N+3=")
currentOutput.should.equal(-47)
})
it("Substract negative", function(){
sequenceReset()
sequencePress("50N-3=")
currentOutput.should.equal(-53)
})
it("Negative before number is typed", function(){
sequenceReset()
sequencePress("N50-3=")
currentOutput.should.equal(-53)
})
it("Negative addition on second number", function(){
sequenceReset()
sequencePress("50-N33=")
currentOutput.should.equal(83)
})
it("Negative zero", function(){
sequenceReset()
sequencePress("N")
currentOutput.should.equal(-0)
sequenceReset()
sequencePress("0N")
currentOutput.should.equal(-0)
sequenceReset()
sequencePress("N0")
currentOutput.should.equal('-0')
sequenceReset()
sequencePress("0N")
currentOutput.should.equal(-0)
sequencePress("N0")
currentOutput.should.equal('0')
})
})
describe("Zero division", function(){
it("Divide 0 by 0", function(){
sequenceReset()
sequencePress("0/0=")
currentOutput.should.equal('NOT A NUMBER')
})
it("Divde N by 0", function(){
sequenceReset()
sequencePress("1/0=")
currentOutput.should.equal('INFINITY')
})
it("Divde -N by 0", function(){
sequenceReset()
sequencePress("N1/0=")
currentOutput.should.equal('-INFINITY')
})
})
describe("Press equals '='", function(){
it("should display result when new operation button is pressed", function(){
sequenceReset()
sequencePress("5+6+")
currentOutput.should.equal(11)
sequenceReset()
sequencePress("5-6*4/2/")
currentOutput.should.equal(-2)
})
it("New operation after '='", function(){
sequenceReset()
sequencePress("5+4=5")
currentOutput.should.equal('5')
sequenceReset()
sequencePress("N5+4*3-3/-1=5")
currentOutput.should.equal('5')
})
it("Double '=' repeats last operation", function(){
sequenceReset()
sequencePress("2+2==")
currentOutput.should.equal(6)
})
it("New operation applied to calculated result", function(){
sequenceReset()
sequencePress("9*9=*9=")
currentOutput.should.equal(729)
})
it("Turn result negative, do addition", function(){
sequenceReset()
sequencePress("9*9=N+1=")
currentOutput.should.equal(-80)
})
it("New operation after '=' dissociated from previous one", function(){
sequenceReset()
sequencePress("9*9=9*")
currentOutput.should.equal('9')
sequenceReset()
sequencePress("9*9=99+1=*2=")
currentOutput.should.equal(200)
})
})
describe("Memory", function(){
it("Reset 1st number with 'C'", function(){
sequenceReset()
sequencePress("50R3+6=")
currentOutput.should.equal(9)
})
it("Reset 2nd number with 'C'", function(){
sequenceReset()
sequencePress("50+3R+6=")
currentOutput.should.equal(56)
})
it("Complex calcul", function(){
sequenceReset()
sequencePress("/3*3+3R-+2/2=")
currentOutput.should.equal(5.5)
})
})
mocha.run()
function header(str) { document.getElementById('header').innerHTML = str[0].replace(/\n/, '').replace(/\n/g, '<br>') }
</script>
</body>
</html>