Build a Cash Register Project - Tests 13, 17, and 19 seem to be bugged

Tell us what’s happening:

Every single other test is succeeding, but these 3 I mentioned in the title are very inconsistent. In one run, 2 of these might pass and the other might fail (in most cases for me it’s been #17), in another run it might be a combination of 2 of these tests that fail instead. I figure it’s must a bug, right? Do I just have to keep running the tests and pray to succeed?

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">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Cash Register</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <div>
    <input id="cash" />
    <button id="purchase-btn">Purchase</button>
  </div>
  <p id="price">Total: </p>
  <p id>Change due:</p>
  <p id="change-due"></p>
  <body>
    <script src="./script.js"></script>
  </body>
</html>

/* file: script.js */
let price = 19.5;
let cid = [["PENNY", 0.5], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]];

// In pennies to avoid potential floating point errors
const currencyUnit = {
    "PENNY": 1,
    "NICKEL": 5,
    "DIME": 10,
    "QUARTER": 25,
    "ONE": 100,
    "FIVE": 500,
    "TEN": 1000,
    "TWENTY": 2000,
    "ONE HUNDRED": 10000
  };

const total = document.getElementById("price");
const purchaseBtn = document.getElementById("purchase-btn");
const changeDue = document.getElementById("change-due");

const isChangeEnough = (cashInRegister, changeInCents) => {
  let availableChange = 0;
  for (const [curr, val] of cashInRegister) {
    console.log(val)
    if(currencyUnit[curr] <= changeInCents) {
      availableChange += val * 100;
    } else {
      break;
    }
  }
  console.log("Available change is ", availableChange, ", and change in cents is ", changeInCents)
  return availableChange >= changeInCents;
}

total.innerText += " " + price;
purchaseBtn.addEventListener("click", () => {
  let totalChange = 0;
  for (const item of cid) {
    totalChange += item[1] * 100;
  }
  const cash = document.getElementById("cash").value;
  if (!cash) {
    return;
  }
  //convert to cents to maintain consistency
  const cashVal = Number(cash) * 100;
  // Not enough cash for purchase
  if (cashVal < (price * 100)) {
    alert("Customer does not have enough money to purchase the item")
  // Cash matches price - no change needed
  } else if (cashVal === (price * 100)) {
    changeDue.innerText = "No change due - customer paid with exact cash";
  // Change needs to be given
  } else {
    let change = cashVal - (price * 100);
    // Insufficient change
    console.log(change)
    if (change > totalChange || !isChangeEnough(cid, change)) {
      changeDue.innerText = "Status: INSUFFICIENT_FUNDS";
    // Cash in register is exactly the same as change needed to be given
    } else if (change === totalChange) {
      changeDue.innerText = "Status: CLOSED";
      for (let i = cid.length - 1; i >= 0; i--) {
        if (cid[i][1] !== 0){
          changeDue.innerText += ` ${cid[i][0]}: \$${cid[i][1]}`;
        }
      }
    // Change with cash remaining in the register
    } else {
      const changeGiven = [];
      // Copy original cid array and reverse it
      const cidReversed = [...cid].reverse();
      for (let [currency, amount] of cidReversed) {
        const unitValue = currencyUnit[currency];
        let currencyAvailable = amount * 100;
        let currencyUsed = 0;
        
        while (change >= unitValue && currencyAvailable > 0) {
          change -= unitValue;
          currencyAvailable -= unitValue;
          currencyUsed += unitValue;
        }
        if (currencyUsed > 0) {
          changeGiven.push([currency, currencyUsed / 100]);
        }
      }
      changeDue.innerText = "Status: OPEN";
      for (let [currency, amount] of changeGiven) {
        changeDue.innerText += ` ${currency}: \$${amount}`;
      }
    }
  }
});

Your browser information:

User Agent is: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0

Challenge Information:

Build a Cash Register Project - Build a Cash Register

What’s this floating around in the global space for?

Is change declared for this scope?

It’s just a simple <p> element to hold the value of “price”. I wanted to be able to see that value at a glance. Does it matter for the test where that specific statement is located?

I guess, technically? change is declared right under the first else statement:

// Change needs to be given
  } else {
    let change = cashVal - (price * 100);

cashVal is declared a block outside:

const cash = document.getElementById("cash").value;
  if (!cash) {
    return;
  }
  //convert to cents to maintain consistency
  const cashVal = Number(cash) * 100;

That will just be wrong for manual re-runs, so it is odd to see.

Ah, I see it now. You have more nesting than this problem needs, making it harder to trace your logic.

This is not the only way to have insufficient funds. You can also have insufficient funds if there exists no way to create the exact change from the drawer.

But this is exactly what I’m doing in the isChangeEnough function:

const isChangeEnough = (cashInRegister, changeInCents) => {
  let availableChange = 0;
  for (const [curr, val] of cashInRegister) {
    console.log(val)
    if(currencyUnit[curr] <= changeInCents) {
      availableChange += val * 100;
    } else {
      break;
    }
  }
  console.log("Available change is ", availableChange, ", and change in cents is ", changeInCents)
  return availableChange >= changeInCents;
}

It checks every currency with value lower than or equal to the change required, and returns true if the total change up to the maximum value you can give as change is greater than or equal to the change needed

Ah yes.

Again, you’ve got way too many pieces here

Wait no, this still does not handle the issue I talked about.

If you have a single $20 bill, then you cannot make $0.05 in change.

It does though? I don’t think my code is that hard to follow, even if admittedly I can clean it up quite a bit, but here goes:

// In pennies to avoid potential floating point errors
const currencyUnit = {
    "PENNY": 1,
    "NICKEL": 5,
    "DIME": 10,
    "QUARTER": 25,
    "ONE": 100,
    "FIVE": 500,
    "TEN": 1000,
    "TWENTY": 2000,
    "ONE HUNDRED": 10000
  };

This is an object that maps each denomination to its value in cents. I call the above function, passing the current cid array to cashInRegister and the calculated change in cents to changeInCents. I then loop over all the nested arrays in cid, starting from the bottom-most denomination, the pennies. I check what that denomination’s value is in cents, and compare it to the change I have to give back.

if(currencyUnit[curr] <= changeInCents) {
      availableChange += val * 100;
    } else {
      break;
    }

So, if I have to give back 5 cents in change, and I have, say, 1 penny (1 cent) and a 20 dollar (2000 cents) bill, this code will add 1 cent to the availableChange I have, then 0 nickels, and then, when it reaches the dimes, it sees that its value in cents (25) is greater than the change I can give back (5 cents), and ends the loop there. The code will then return false, because it sees that my available change in only 1, which is less than the requested change.

Your code is wrong. The tests are correct. This test suite has been checked against hundreds of thousands of solutions. It’s just a question of finding your bug. I will dig in more and try to unravel your code later but I have to start my commute now.

add this below your code in the JS file to mimic what the tests do: (note, add all the code I give here, not pieces)

console.log("\nTest #9");
price = 11.95;
document.querySelector("#cash").value = 11.95;
document.querySelector("#purchase-btn").click();
console.log("actual", document.querySelector("#change-due").innerText);
console.log("expected", "No change due - customer paid with exact cash");

console.log("\nTest #11");
price = 19.5;
document.querySelector("#cash").value = 20;
cid = [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]];
document.querySelector("#purchase-btn").click();
console.log("actual", document.querySelector("#change-due").innerText);
console.log("expected", "Status: OPEN QUARTER: $0.5");

console.log("\nTest #12");
price = 3.26;
document.querySelector("#cash").value = 100;
cid = [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]];
document.querySelector("#purchase-btn").click();
console.log("actual", document.querySelector("#change-due").innerText);
console.log("expected", "Status: OPEN TWENTY: $60 TEN: $20 FIVE: $15 ONE: $1 QUARTER: $0.5 DIME: $0.2 PENNY: $0.04");

console.log("\nTest #14");
price = 19.5;
document.querySelector("#cash").value = 20;
cid = [["PENNY", 0.01], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]];
document.querySelector("#purchase-btn").click();
console.log("actual", document.querySelector("#change-due").innerText);
console.log("expected", "Status: INSUFFICIENT_FUNDS");

console.log("\nTest #16");
price = 19.5;
document.querySelector("#cash").value = 20;
cid = [["PENNY", 0.01], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 1], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]];
document.querySelector("#purchase-btn").click();
console.log("actual", document.querySelector("#change-due").innerText);
console.log("expected", "Status: INSUFFICIENT_FUNDS");

console.log("\nTest #18");
price = 19.5;
document.querySelector("#cash").value = 20;
cid = [["PENNY", 0.5], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]];
document.querySelector("#purchase-btn").click();
console.log("actual", document.querySelector("#change-due").innerText);
console.log("expected", "Status: CLOSED PENNY: $0.5");

Here one situation in which your code fails, add it below the others to check:

price = 71.7;
document.querySelector("#cash").value = 100;
cid = [["PENNY",0.02],["NICKEL",0],["DIME",0.1],["QUARTER",0],["ONE",13],["FIVE",70],["TEN",60],["TWENTY",60],["ONE HUNDRED",1200]]];
document.querySelector("#purchase-btn").click();
console.log("actual", document.querySelector("#change-due").innerText);

it should give correct change, but it say insufficient funds

and the opposite for this one, it should say insufficient funds:

price = 71.59;
document.querySelector("#cash").value = 80;
cid = [["PENNY",0.12],["NICKEL",0],["DIME",0.3],["QUARTER",0.25],["ONE",0],["FIVE",45],["TEN",40],["TWENTY",140],["ONE HUNDRED",600]];
document.querySelector("#purchase-btn").click();
console.log("actual", document.querySelector("#change-due").innerText);
1 Like

The first case on my machine doesn’t return that there are insufficient funds, but I think I found the bug with the second test case. It will overcount the fives and assume there’s enough change, even though there’s not. Thanks for the feedback! I also hope these tests become a bit clearer in the future,

we are not using this challenge in the full stack curriculum so it is unlikely that it is going to have changes in the future