Build a Cash Register Project - Build a Cash Register

Tell us what’s happening:

I manually tested all the test cases, and they passed, but I can’t seem to pass the last seven test cases when running the automated tests. Appreciate any help!

  • Failed:When price is less than the value in the #cash element, total cash in drawer cid is greater than the change due, individual denomination amounts allows for returning change due, and the #purchase-btn element is clicked, the value in the #change-due element should be "Status: OPEN" with required change due in coins and bills sorted in highest to lowest order.

  • Failed:When price is 19.5, the value in the #cash element is 20, cid is [["PENNY", 0.01], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]], and the #purchase-btn element is clicked, the value in the #change-due element should be "Status: INSUFFICIENT_FUNDS"

  • Failed:When price is less than the value in the #cash element, total cash in drawer cid is greater than change due, individual denomination amounts make impossible to return needed change, and the #purchase-btn element is clicked, the value in the #change-due element should be "Status: INSUFFICIENT_FUNDS"

  • Failed:When price is 19.5, the value in the #cash element is 20, cid is [["PENNY", 0.01], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 1], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]], and the #purchase-btn element is clicked, the value in the #change-due element should be "Status: INSUFFICIENT_FUNDS".

  • Failed:When price is less than the value in the #cash element, total cash in drawer cid is less than the change due, and the #purchase-btn element is clicked, the value in the #change-due element should be "Status: INSUFFICIENT_FUNDS".

  • Failed:When price is 19.5, the value in the #cash element is 20, cid is [["PENNY", 0.5], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]], and the #purchase-btn element is clicked, the value in the #change-due element should be "Status: CLOSED PENNY: $0.5".

  • Failed:When price is less than the value in the #cash element, total cash in drawer cid is equal to change due, and the #purchase-btn element is clicked, the value in the #change-due element should be "Status: CLOSED" with change due in coins and bills sorted in highest to lowest order.

Your code so far

<!-- file: index.html -->
<!DOCTYPE = html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="styles.css" />
    <title>Cash Register</title>
  </head>
  <body>
    <h1>Cash Register Project</h1>
    <div class="container">
      <div id="change-due"></div>
      <label for="cash">Enter Cash from Customer:</label>
      <input type="number" id="cash" />
      <button id="purchase-btn">Purchase</button>
      <div class="cash-container">
        <div class="wrapper">
          <div>
            <p id="total">
              Total:
              <span id="price"></span>
            </p>
            <img src="https://www.svgrepo.com/show/141588/cash-register.svg" alt="" />
          </div>
          <div>
            <label>Change in Drawer:</label>
            <p>
              Pennies:
              <span></span>
            </p>
            <p>
              Nickels:
              <span></span>
            </p>
            <p>
              Dimes:
              <span></span>
            </p>
            <p>
              Quarters:
              <span></span>
            </p>
            <p>
              Ones:
              <span></span>
            </p>
            <p>
              Fives:
              <span></span>
            </p>
            <p>
              Tens:
              <span></span>
            </p>
            <p>
              Twenties:
              <span></span>
            </p>
            <p>
              Hundreds:
              <span></span>
            </p>
          </div>
        </div>
      </div>
    </div>
    <script src="script.js"></script>
  </body>
</html>

/* file: script.js */
const changedueElement = document.getElementById("change-due")
const cashInput = document.getElementById("cash")
const purchaseBtn = document.getElementById("purchase-btn")
const priceElement = document.getElementById("price")
const changeInDrawElements = document.querySelectorAll("label ~ p > span")

let price = 3.26
let cid = [
  ["PENNY", 1.01],
  ["NICKEL", 2.05],
  ["DIME", 3.1],
  ["QUARTER", 4.25],
  ["ONE", 90],
  ["FIVE", 55],
  ["TEN", 20],
  ["TWENTY", 60],
  ["ONE HUNDRED", 100],
]
const currencyValues = [
  ["Penny", 0.01],
  ["Nickel", 0.05],
  ["Dime", 0.1],
  ["Quarter", 0.25],
  ["One", 1],
  ["Five", 5],
  ["Ten", 10],
  ["Twenty", 20],
  ["One Hundred", 100],
]

class Currency {
  constructor(name, amount, faceValue) {
    this.name = name
    this._faceValue = faceValue
    this.amount = amount || 0
  }
  getFaceValue() {
    return this._faceValue
  }
  hasSufficientAmount() {
    return parseFloat((this.amount - this._faceValue).toFixed(2)) > 0
  }
}

class CashDrawer {
  constructor(cid, currencyValues) {
    this.currencies = []
    for (let i = 0; i < cid.length; i++) {
      this.currencies.push(
        new Currency(currencyValues[i][0], cid[i][1], currencyValues[i][1])
      )
    }
  }

  getTotalAmount() {
    let total = 0
    this.currencies.forEach((currency) => {
      total += currency.amount
    })
    return parseFloat(total.toFixed(2))
  }

  getCurrencyArray() {
    return this.currencies
  }

  calculateChange(cashReceived, itemPrice) {
    let changeNeeded = parseFloat((cashReceived - itemPrice).toFixed(2))
    console.log("changeNeeded: $", changeNeeded)
    //- "Status: INSUFFICIENT_FUNDS" if cash-in-drawer is less than the change due
    console.log("total change amount in the drawer: $" + this.getTotalAmount())
    if (this.getTotalAmount() < changeNeeded) {
      return { canBreakChange: false, status: "INSUFFICIENT_FUNDS" }
    }

    let changeArray = []
    let currencies = this.getCurrencyArray()
    for (let i = currencies.length - 1; i >= 0; i--) {
      let currency = currencies[i]
      let faceValue = currency.getFaceValue()
      let currencyName = currency.name
      let currencyTotalAmount = 0
      while (changeNeeded >= faceValue && currency.amount >= faceValue) {
        // console.log(`ChangeNeeded: ${changeNeeded} - ${faceValue}`)
        changeNeeded = parseFloat((changeNeeded - faceValue).toFixed(2))
        currency.amount = parseFloat((currency.amount - faceValue).toFixed(2))
        currencyTotalAmount = parseFloat(
          (currencyTotalAmount + faceValue).toFixed(2)
        )
      }
      if (currencyTotalAmount > 0) {
        changeArray.push([currencyName, currencyTotalAmount])
      }
    }

    if (changeNeeded > 0) {
      return { canBreakChange: false, status: "INSUFFICIENT_FUNDS" }
    } else if (this.getTotalAmount() === 0) {
      return { canBreakChange: true, status: "CLOSED", changeArray }
    } else {
      return { canBreakChange: true, status: "OPEN", changeArray }
    }
  }
}

window.onload = () => init()

const resetCashInput = () => (cashInput.value = "")

const updateChangeInDrawer = () => {
  for (let i = 0; i < cid.length; i++) {
    changeInDrawElements[i].textContent = `$${
      cashDrawer.getCurrencyArray()[i].amount
    }`
  }
}

const updateStatus = (text) => {
  changedueElement.innerHTML = `<p>${text}</p>`
  changedueElement.style.display = "block"
}

const updateChangeDue = (changeArray) => {
  for (let i = 0; i < changeArray.length; i++) {
    changedueElement.innerHTML += `<p>${changeArray[i][0].toUpperCase()}: $${
      changeArray[i][1]
    }</p>`
  }
  changedueElement.style.display = "block"
}

const resetChangeDueElement = () => {
  changedueElement.innerHTML = ""
  changedueElement.style.display = "none"
}

const isCashInputValid = (cash) => {
  if (cash === 0) {
    alert("Please enter a valid number")
    resetCashInput()
    return false
  } else if (cash < price) {
    alert("Customer does not have enough money to purchase the item")
    resetCashInput()
    return false
  } else if (cash === price) {
    updateStatus("No change due - customer paid with exact cash")
    resetCashInput()
    return false
  } else {
    // console.log(`Cash input: ${cash} is valid`)
    return true
  }
}

const purchase = (cash) => {
  console.log(
    "Customer handed $" + Number(cash) + "\nItem price is:  $" + price
  )

  resetChangeDueElement()
  cash = Number(cash)

  if (!isCashInputValid(cash)) {
    return
  }
  // todo: call your new breakChange function
  const result = cashDrawer.calculateChange(cash, price)

  console.log(result)
  if (!result.canBreakChange) {
    updateStatus(`Status: ${result.status}`)
    return
  }

  updateStatus(`Status: ${result.status}`)
  updateChangeDue(result.changeArray)
  updateChangeInDrawer()

  return
}

purchaseBtn.addEventListener("click", () => {
  purchase(cashInput.value)
  resetCashInput()
})

cashInput.addEventListener("keydown", (e) => {
  if (e.key === "Enter") {
    purchase(cashInput.value)
    resetCashInput()
  }
  if (e.key === "Escape") {
    resetCashInput()
  }
})

const cashDrawer = new CashDrawer(cid, currencyValues)

const init = () => {
  // set total price
  priceElement.textContent = `$${price}`

  // load change in drawer
  updateChangeInDrawer()
}

/* file: styles.css */
:root {
  --background-color: #9ec8b9;
  --input-color: #c8eadf;
  --font-color: #092635;
  --button-color: #5c8374;
  --button-color-hover: #64907f;
  --button2-color: #1b4242;
  --color: #000000;
}

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  background-color: var(--background-color);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-family: Arial, Helvetica, sans-serif;
  color: var(--font-color);
}

/** Title */
h1 {
  margin: 36px;
  text-align: center;
}

.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 400px;
  max-width: 800px;
}

.container * {
  margin: 0 0 10px 0;
}

#change-due {
  display: none;
}

#change-due p {
  margin: 6px;
}

/** cash input */
#cash {
  border: none;
  border-radius: 10px;
  padding: 4px 4px 4px 8px;
  font-size: 1.1rem;
  width: 40%;
  background-color: var(--input-color);
  color: var(--font-color);
}

#purchase-btn {
  padding: 6px 10px 6px 10px;
  font-size: 1.1rem;
  border: none;
  border-radius: 10px;
  background-color: var(--button-color);
  color: var(--font-color);
  cursor: pointer;
}

#purchase-btn:hover {
  background-color: var(--button-color-hover);
}

#purchase-btn:active {
  background-color: var(--button-color);
}

.cash-container {
  margin: 10px 0 0 0;
}

#total {
  text-align: center;
  font-size: 1.1rem;
}

.wrapper {
  display: flex;
}

.wrapper div:first-child {
  width: 200px;
  margin: 0 20px 0 0;
}

.wrapper div:nth-child(2) {
  width: 200px;
}

.wrapper div:nth-child(2) label {
  margin: 0;
  font-weight: bold;
}

.wrapper div:nth-child(2) p {
  margin: 8px 0 0 24px;
}

@media (max-width: 420px) {
  img {
    display: none;
  }

  .wrapper {
    flex-direction: column;
  }

  .wrapper div:first-child {
    width: 200px;
    margin: 0;
  }
}

Your browser information:

User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36

Challenge Information:

Build a Cash Register Project - Build a Cash Register

Don’t put this line of code in the global scope. It will only run once at the start when your code is loaded and never again for any testcase.

Thank you for your reply! I declared a cashDrawer in the global scope and instantiated the CashDrawer class in the init method. However, the test result remains the same.

please show your updated code

If your unit method only gets called on load, then it should not have any functional code in it. The tests run in sequence and do not reload the page in between.

Please reconsider your placement of the CashDrawer instantiation.

I removed the onload function and moved the CashDrawer instantiation to the end of the script file, but the last 7 test cases still didn’t pass. Is there a way for me to see how my code is being tested so that I can refactor it accordingly?

/* file: script.js */
const changedueElement = document.getElementById("change-due")
const cashInput = document.getElementById("cash")
const purchaseBtn = document.getElementById("purchase-btn")
const priceElement = document.getElementById("price")
const changeInDrawElements = document.querySelectorAll("label ~ p > span")
let cashDrawer

let price = 3.26
let cid = [
  ["PENNY", 1.01],
  ["NICKEL", 2.05],
  ["DIME", 3.1],
  ["QUARTER", 4.25],
  ["ONE", 90],
  ["FIVE", 55],
  ["TEN", 20],
  ["TWENTY", 60],
  ["ONE HUNDRED", 100],
]
const currencyValues = [
  ["Penny", 0.01],
  ["Nickel", 0.05],
  ["Dime", 0.1],
  ["Quarter", 0.25],
  ["One", 1],
  ["Five", 5],
  ["Ten", 10],
  ["Twenty", 20],
  ["One Hundred", 100],
]

class Currency {
  constructor(name, amount, faceValue) {
    this.name = name
    this._faceValue = faceValue
    this.amount = amount || 0
  }
  getFaceValue() {
    return this._faceValue
  }
  hasSufficientAmount() {
    return parseFloat((this.amount - this._faceValue).toFixed(2)) > 0
  }
}

class CashDrawer {
  constructor(cid, currencyValues) {
    this.currencies = []
    for (let i = 0; i < cid.length; i++) {
      this.currencies.push(
        new Currency(currencyValues[i][0], cid[i][1], currencyValues[i][1])
      )
    }
  }

  getTotalAmount() {
    let total = 0
    this.currencies.forEach((currency) => {
      total += currency.amount
    })
    return parseFloat(total.toFixed(2))
  }

  getCurrencyArray() {
    return this.currencies
  }

  calculateChange(cashReceived, itemPrice) {
    let changeNeeded = parseFloat((cashReceived - itemPrice).toFixed(2))
    console.log("changeNeeded: $", changeNeeded)
    //- "Status: INSUFFICIENT_FUNDS" if cash-in-drawer is less than the change due
    console.log("total change amount in the drawer: $" + this.getTotalAmount())
    if (this.getTotalAmount() < changeNeeded) {
      return { canBreakChange: false, status: "INSUFFICIENT_FUNDS" }
    }

    let changeArray = []
    let currencies = this.getCurrencyArray()
    for (let i = currencies.length - 1; i >= 0; i--) {
      let currency = currencies[i]
      let faceValue = currency.getFaceValue()
      let currencyName = currency.name
      let currencyTotalAmount = 0
      while (changeNeeded >= faceValue && currency.amount >= faceValue) {
        // console.log(`ChangeNeeded: ${changeNeeded} - ${faceValue}`)
        changeNeeded = parseFloat((changeNeeded - faceValue).toFixed(2))
        currency.amount = parseFloat((currency.amount - faceValue).toFixed(2))
        currencyTotalAmount = parseFloat(
          (currencyTotalAmount + faceValue).toFixed(2)
        )
      }
      if (currencyTotalAmount > 0) {
        changeArray.push([currencyName, currencyTotalAmount])
      }
    }

    if (changeNeeded > 0) {
      return { canBreakChange: false, status: "INSUFFICIENT_FUNDS" }
    } else if (this.getTotalAmount() === 0) {
      return { canBreakChange: true, status: "CLOSED", changeArray }
    } else {
      return { canBreakChange: true, status: "OPEN", changeArray }
    }
  }
}

const resetCashInput = () => (cashInput.value = "")

const updateChangeInDrawer = () => {
  for (let i = 0; i < cid.length; i++) {
    changeInDrawElements[i].textContent = `$${
      cashDrawer.getCurrencyArray()[i].amount
    }`
  }
}

const updateStatus = (text) => {
  changedueElement.innerHTML = `<p>${text}</p>`
  changedueElement.style.display = "block"
}

const updateChangeDue = (changeArray) => {
  for (let i = 0; i < changeArray.length; i++) {
    changedueElement.innerHTML += `<p>${changeArray[i][0].toUpperCase()}: $${
      changeArray[i][1]
    }</p>`
  }
  changedueElement.style.display = "block"
}

const resetChangeDueElement = () => {
  changedueElement.innerHTML = ""
  changedueElement.style.display = "none"
}

const isCashInputValid = (cash) => {
  if (cash === 0) {
    alert("Please enter a valid number")
    resetCashInput()
    return false
  } else if (cash < price) {
    alert("Customer does not have enough money to purchase the item")
    resetCashInput()
    return false
  } else if (cash === price) {
    updateStatus("No change due - customer paid with exact cash")
    resetCashInput()
    return false
  } else {
    return true
  }
}

const purchase = (cash) => {
  console.log(
    "Customer handed $" + Number(cash) + "\nItem price is:  $" + price
  )

  resetChangeDueElement()
  cash = Number(cash)

  if (!isCashInputValid(cash)) {
    return
  }
  const result = cashDrawer.calculateChange(cash, price)

  console.log(result)
  if (!result.canBreakChange) {
    updateStatus(`Status: ${result.status}`)
    return
  }

  updateStatus(`Status: ${result.status}`)
  updateChangeDue(result.changeArray)
  updateChangeInDrawer()

  return
}

purchaseBtn.addEventListener("click", () => {
  purchase(cashInput.value)
  resetCashInput()
})

cashInput.addEventListener("keydown", (e) => {
  if (e.key === "Enter") {
    purchase(cashInput.value)
    resetCashInput()
  }
  if (e.key === "Escape") {
    resetCashInput()
  }
})

cashDrawer = new CashDrawer(cid, currencyValues)
priceElement.textContent = `$${price}`
updateChangeInDrawer()

The CashDrawer is still being created in the global scope.

You can simulate a test by adding code at the end of your file to change the price and the cid and the value of the cash element . Then trigger the purchase click.