🛠️ Calculator: Fixing the Clear Key

Hey ,

I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.

Please upgrade (use this link) access this content.

I'm super eager to help you learn more!

🛠️ Calculator: Fixing the Clear Key

resetCalculator clicks the clear key twice, so it needs the clear key to work. That’s the next thing we have to do. We’ll start by uncommenting the part to handle the clear key.

function Calculator () {
  // Declare variables

  const calculator = {
    // ...
    // Methods

    handleClick (event) {
      // ...

      switch (buttonType) {
        case 'clear': handleClearKey(calculator, button); break
        // ...
      }

      // ...
    }
  }

  // Add Event Listeners
  // Return the calculator
}

We need the handleClearKey function to handle the clear key. This is what we wrote previously:

function handleClearKey (calculator, button) {
  const { previousButtonType } = calculator.dataset
  display.textContent = '0'
  button.textContent = 'AC'
  if (previousButtonType === 'clear') {
    delete calculator.dataset.firstValue
    delete calculator.dataset.operator
    delete calculator.dataset.modifierValue
  }
}

Like before, we can create a handleClearKey method to handle clear key.

function Calculator () {
  // Declare variables

  const calculator = {
    // ...

    handleClearKey () {
       // ...
    }

    // Other methods
    // Listener callbacks
  }

  // Add Event Listeners
  // Return the calculator
}

We’ll copy everything we wrote into this method first.

function Calculator () {
  // Declare variables
  const calculator = {
    // ...

    handleClearKey (calculator, button) {
      const { previousButtonType } = calculator.dataset
      display.textContent = '0'
      button.textContent = 'AC'
      if (previousButtonType === 'clear') {
        delete calculator.dataset.firstValue
        delete calculator.dataset.operator
        delete calculator.dataset.modifierValue
      }
    }

    // Other methods
    // Listener callbacks
  }

  // Add Event Listeners
  // Return the calculator
}

Here, we need to change all instances of calculator to calculatorElement. We do this becausecalculatorElement points to the HTML element.

function Calculator () {
  // Declare variables

  const calculator = {
    // ...

    handleClearKey (calculatorElement, button) {
      const { previousButtonType } = calculatorElement.dataset
      display.textContent = '0'
      button.textContent = 'AC'
      if (previousButtonType === 'clear') {
        delete calculatorElement.dataset.firstValue
        delete calculatorElement.dataset.operator
        delete calculatorElement.dataset.modifierValue
      }
    }

    // Other methods
    // Listener callbacks
  }

  // Add Event Listeners
  // Return the calculator
}

Next, we know calculatorElement is already in the lexical scope. We don’t need to pass it into handleClearKey, so we can omit this parameter.

function Calculator () {
  // Declare variables

  const calculator = {
    // ...

    handleClearKey (button) {
      // ...
    },

    handleClick (event) {
      // ...
      switch (buttonType) {
        // ...
        case 'clear': calculator.handleClearKey(); break
        // ...
      }
      // ...
    }

    // Other methods
    // Listener callbacks
  }

  // Add Event Listeners
  // Return the calculator
}

The button in handleClearKey refers to the clear button. We already declared a clearButton upfront, so we can use it immediately. There’s no need pass button into handleClearKey.

function Calculator () {
  // Declare variables

  const calculator = {
    // ...

    handleClearKey () {
      // ...
      clearButton.textContent = 'AC'
      // ...
    }

    // Other methods
    // Listener callbacks
  }

  // Add Event Listeners
  // Return the calculator
}

Next, we can use the displayValue setter function to reset the calculators display.

function Calculator () {
  // Declare variables

  const calculator = {
    // ...

    handleClearKey () {
      // ...
      calculator.displayValue = '0'
      // ...
    }

    // Other methods
    // Listener callbacks
  }

  // Add Event Listeners
  // Return the calculator
}

Finally, we can use handleClearKey like this:

function Calculator () {
  // Declare variables
  const calculator = {
    // ...
    // Methods

    handleClick (event) {
      // ...

      switch (buttonType) {
        case 'clear': calculator.handleClearKey(); break
        // ...
      }

      // ...
    }
  }

  // Add Event Listeners
  // Return the calculator
}

If you refresh the page at this point, you shouldn’t see errors anymore. All tests would have passed because we fixed the clear key.

Next is to test whether clear is actually working.

Testing Clear Key

When we tested the clear key previously, we grouped two tests into one:

const testClearKey = _ => {
  // Before calculation
  pressKeys('5', 'clear')
  console.assert(getDisplayValue() === '0', 'Clear before calculation')
  console.assert(calculator.querySelector('[data-key="clear"]').textContent === 'AC', 'Clear once, should show AC')
  resetCalculator()

  // After calculator
  pressKeys('5', 'times', '9', 'equal', 'clear')
  const { firstValue, operator } = calculator.dataset
  console.assert(firstValue, 'Clear once;  should have first value')
  console.assert(operator, 'Clear once;  should have operator value')
  resetCalculator()
}

This does too much.

We want to separate each test into one function.

function testClear () {
  pressKeys('5', 'clear')
  console.assert(getDisplayValue() === '0', 'Clear before calculation')
  console.assert(calculator.querySelector('[data-key="clear"]').textContent === 'AC', 'Clear once, should show AC')
  resetCalculator()
}

function testClearAfterCalculation () {
  pressKeys('5', 'times', '9', 'equal', 'clear')
  const { firstValue, operator } = calculator.dataset
  console.assert(firstValue, 'Clear once;  should have first value')
  console.assert(operator, 'Clear once;  should have operator value')
  resetCalculator()
}

Next, we need to make the necessary adjustments like we did with runTest:

  • We change calculator to calculator.element
  • We change pressKeys to calculator.pressKeys
  • We change getDisplayValue to calculator.displayValue
  • We change resetCalculator to calculator.resetCalculator
function testClear () {
  calculator.pressKeys('5', 'clear')
  console.assert(calculator.displayValue === '0', 'Clear before calculation')
  console.assert(calculator.element.querySelector('[data-key="clear"]').textContent === 'AC', 'Clear once, should show AC')
  calculator.resetCalculator()
}

function testClearAfterCalculation () {
  calculator.pressKeys('5', 'times', '9', 'equal', 'clear')
  const { firstValue, operator } = calculator.element.dataset
  console.assert(firstValue, 'Clear once;  should have first value')
  console.assert(operator, 'Clear once;  should have operator value')
  calculator.resetCalculator()
}

We can simplify testClear by declaring clearButton upfront.

const clearButton = calculator.element.querySelector('[data-key="clear"]')

function testClear () {
  // ...
  console.assert(clearButton.textContent === 'AC', 'Clear once, should show AC')
  // ...
}

We can also make testClearAfterCalculation more robust by ensuring two things:

  1. Clear button says AC.
  2. Display value is 0.
function testClearAfterCalculation () {
  // ...
  console.assert(calculator.displayValue === '0', 'Clear after calculation: Display is 0')
  console.assert(clearButton.textContent === 'AC', 'Clear after calculation: Clear Button is AC')
  // ...
  calculator.resetCalculator()
}

Finally we can standardise the test message in testClearAfterCalculation… (This is optional, but I think it’s nice to standardise messages).

function testClearAfterCalculation () {
  calculator.pressKeys('5', 'times', '9', 'equal', 'clear')
  const { firstValue, operator } = calculator.element.dataset

  console.assert(calculator.displayValue === '0', 'Clear after calculation: Display is 0')
  console.assert(clearButton.textContent === 'AC', 'Clear after calculation: Clear Button is AC')
  console.assert(firstValue, 'Clear After Calculation: firstValue should remain')
  console.assert(operator, 'Clear After Calculation: operator should remain')

  calculator.resetCalculator()
}

Next, we can run these two tests:

testClear()
testClearAfterCalculation()

The first test should pass, but the second test should fail.

testClearAfterCalculation fails.

It’s obvious why the second test failed – we did not handle operator and equal keys yet, so we won’t be able to make a calculation.

We’ll handle operator and equal keys in the next lesson. But first, I want to talk about resetting the calculator.

Resetting the calculator

When we reset the calculator, we need to ensure there are no firstValue, operator, and modifierValue. This was done in the first version of resetCalculator.

const resetCalculator = _ => {
  pressKeys('clear', 'clear')
  console.assert(getDisplayValue() === '0', 'Clear calculator')
  console.assert(!calculator.dataset.firstValue, 'No first value')
  console.assert(!calculator.dataset.operator, 'No operator value')
  console.assert(!calculator.dataset.modifierValue, 'No operator value')
}

We removed the console.assert parts from resetCalculator because we shouldn’t run tests in the actual code. We need to add these statements back in our tests.

We’ll create a new function called testFullClear for this test.

function testFullClear () {
  // ...
}

We’ll start by copying the console.assert statements in.

function testFullClear () {
  console.assert(getDisplayValue() === '0', 'Clear calculator')
  console.assert(!calculator.dataset.firstValue, 'No first value')
  console.assert(!calculator.dataset.operator, 'No operator value')
  console.assert(!calculator.dataset.modifierValue, 'No operator value')
}

We need to change the same few things here:

  1. Change calculator to calculator.element
  2. Change getDisplayValue to calculator.displayValue
function testFullClear () {
  console.assert(calculator.displayValue === '0', 'Clear calculator')
  console.assert(!calculator.element.dataset.firstValue, 'No first value')
  console.assert(!calculator.element.dataset.operator, 'No operator value')
  console.assert(!calculator.element.dataset.modifierValue, 'No operator value')
}

We can standardise the test messages to include a Full Clear: prefix. This tells us any errors that arise from these console.assert statements originate from testFullClear.

function testFullClear () {
  console.assert(calculator.displayValue === '0', 'Full Clear: Display is 0')
  console.assert(!calculator.dataset.firstValue, 'Full Clear:No first value')
  console.assert(!calculator.dataset.operator, 'Full Clear: No operator value')
  console.assert(!calculator.dataset.modifierValue, 'Full Clear: No operator value')
}

Next, testFullClear would only make sense after a calculation. We need to include a calculation before the tests.

function testFullClear () {
  calculator.pressKeys('5', 'times', '9', 'equal')
  // ...
}

Finally, we use the resetCalculator method to reset the calculator. We need to test this method.

function testFullClear () {
  calculator.pressKeys('5', 'times', '9', 'equal')
  calculator.resetCalculator()

  // ...
}

One final step: We need to run the test:

testFullClear()

No errors should arise from testFullClear at this point because we haven’t implemented operator and equal keys yet.

That’s all we need to do to put tests in place.

The hardest part for the refactor is now over. The rest is easy. We’ll continue to handle the rest of the keys next lesson.