There are 2 things that surprised me while using my iPad, but not immediately:

  1. No headphones jack
  2. No default calculator app

I discovered I was not the only one surprised by this.

This short article is about number 2: how I developed a port of the iOS calculator for the iPad.




Implementation choices

The port had to work on iOS (iPad and iPhone), and any other browser ― Desktop or mobile.

Native, web, or PWA

Since we wanted a full-screen experience and an icon, I opted for a Progressive Web Application 1. This allows us to install the app and get an almost native feeling.

getcalculator.app

A PWA 1 can be installed on any device. In the image, getcalculator.app is being installed on the iPad.




Programming language

The language used to implement it has been Typescript2, compiled with parcel. No framework was necessary, since we wanted a small size, and the application is small enough.




User Interface

The UI is extremely similar, but not pixel perfect. In some details, like the buttons, I have opted for simplicity instead of an exact match on the gliph 3.

There are other details where we tried to reproduce the original as exactly as possible:

  • The short animations when you press a button.
  • Reducing the size of the digits in display as you press more numbers.




Testing

Testing has been a priority for this project. While the port is for sure not perfect ― for starters, it only includes the vertical calculator without scientific mode ― it was a goal to keep it as close as possible to the original.

To make sure that it was as close as possible to the original, I added lots of tests. 23869, to be precise.

I started with tests to check the common operations and results. I wrote 2 kinds of test:

Whole state tests

These tests represent the entire state of the calculator:

  • The first line in out is the output.
  • The following lines are the calculator buttons.
//...
{  
    in: "6",  
 desc: "deal with AC to C",  
 out: [  
	["6"],  
	["C", "±", "%", "÷"],  
	["7", "8", "9", "×"],  
	["4", "5", "6", "-"],  
	["1", "2", "3", "+"],  
	["0", ",", "="],  
 ]  
},

The test represents the status of all the buttons. In the code above, when we press 6, the AC button changes to C.

Simple tests

Tests representing the whole state were unnecesary to test certain properties, so soon I added a simplified version as well.

{  
    in: "5 6 - =",  
 desc: "ref",  
 out: [  
        ["0"],  
 ]  
}

The test above checks that 56 minus = (equal) is 0.

getcalculator.app

Testing improves confidence in the behaviour of the port.

Generating tests

Inspired by Netflix’s Chaos Monkey, I programmatically generated random press tests to replicate the original implementation as well as possible.




By testing random sequences of button presses, we can find new tests, uncovering behaviours we were not aware of. ― J. Mejuto, Mejuto.co




A small program systematically generated all the permutations for sequences of a certain length, which we optimized.

Optimizations

The tests are defined in json, and all kinds of optimizations applied.

  • Any sequence of button presses ending in AC has to output 0.
  • An expression multiplied by 0 is automatically 0.

  • A sequence without digits (just operators, like % + - =) is 0.

This allowed it to generate 23869 tests in a short amount of time.

getcalculator.app

The native iOS calculator (left). The ported PWA 1 version at getcalculator.app (right).




The end result

In the end we got a pretty accurate calculator port that you can add to your ipad.

getcalculator.app

The native iOS calculator (left). The ported PWA 1 version at getcalculator.app (right).

You can see more screenshots in the calculator’s product hunt page.


📢 P.S. When not porting applications, I do contract work and create detailed full-stack video courses.


Thanks for reading


Footnotes

  1. Progressive Web application 2 3 4

  2. The type system of Typescript makes it a good choice to be warned about errors at compile-time. 

  3. The button gliph for +-, for example, is not exactly the same. This was an intentional choice to not include additional external fonts and dependencies.