Build a Cash Register Project - Build a Cash Register

Tell us what’s happening:

I’m not sure why test 19 is failing as it’s not telling me what it tested that failed. Step 18, which meets the description of test 19, passes. This thread mentioned the tests not reloading the page so I did the test mentioned in that thread without reloading and it passed, at least according to my eyes.

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">
    <title>Cash Register</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <input id="cash" />
    <button id="purchase-btn">Purchase</button>
    <div id="change-due"></div>
    <script src="script.js"></script>
  </body>
</html>
/* file: styles.css */

/* file: script.js */
let price = 19.5;
let cid = [
  ['PENNY', 0],
  ['NICKEL', 1.5],
  ['DIME', 0],
  ['QUARTER', 0],
  ['ONE', 0],
  ['FIVE', 0],
  ['TEN', 0],
  ['TWENTY', 0],
  ['ONE HUNDRED', 0]
];
let cidValue = [
  0.01,
  0.05,
  0.10,
  0.25,
  1,
  5,
  10,
  20,
  100
];

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

const purchase = () => {
  if (cash.value < price) {
    alert("Customer does not have enough money to purchase the item");
  } else if (cash.value == price) {
    changeDue.textContent = "No change due - customer paid with exact cash";
  } else {
    let change = cash.value - price;
    const totalCid = cid.reduce((acc, arr) => (acc + arr[1]), 0).toFixed(2);
    let status = "";
    if (totalCid < change) {
      status = "Status: INSUFFICIENT_FUNDS";
    } else {
      if (totalCid == change) {
        status = "Status: CLOSED";
      } else {
        status = "Status: OPEN";
      }

      for (let i = cid.length - 1; i >= 0; i--) {
        let inRegister = cid[i][1];
        let billValue = cidValue[i];
        if (inRegister > 0 && change >= billValue) {
          let amount = 0;
          const amountNeeded = Math.floor(change / billValue);
          const amountInRegister = Math.floor(inRegister / billValue);
          if (amountInRegister >= amountNeeded) {
            amount = amountNeeded * billValue;
          } else {
            amount = amountInRegister * billValue;
          }

          change = (change - amount).toFixed(2);
          status += ` ${cid[i][0]}: $${amount}`;
        }
      }

      if (change != 0) {
        status = "Status: INSUFFICIENT_FUNDS";
      }
    }

    changeDue.textContent = status;
  }
};
purchaseBtn.addEventListener("click", purchase);

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0

Challenge Information:

Build a Cash Register Project - Build a Cash Register

Hi there! You should not declare any variable within the global scope, except the above two.

Thanks for the tip. Is that including the getElementById calls? Either way, I moved the two getElementById calls into the purchase() function, removed the purchase-btn getElementById call and added an onclick instead. Now there is no other global variables and step 19 still fails.

Also was that specified anywhere? As if that’s a requirement it should be specified as there’s no indication.

New code:

let price = 19.5;
let cid = [
  ['PENNY', 0],
  ['NICKEL', 1.5],
  ['DIME', 0],
  ['QUARTER', 0],
  ['ONE', 0],
  ['FIVE', 0],
  ['TEN', 0],
  ['TWENTY', 0],
  ['ONE HUNDRED', 0]
];

const purchase = () => {
  const cidValue = [
    0.01,
    0.05,
    0.10,
    0.25,
    1,
    5,
    10,
    20,
    100
  ];

  const cash = document.getElementById("cash");
  const changeDue = document.getElementById("change-due");

  if (cash.value < price) {
    alert("Customer does not have enough money to purchase the item");
  } else if (cash.value == price) {
    changeDue.textContent = "No change due - customer paid with exact cash";
  } else {
    let change = cash.value - price;
    const totalCid = cid.reduce((acc, arr) => (acc + arr[1]), 0).toFixed(2);
    let status = "";
    if (totalCid < change) {
      status = "Status: INSUFFICIENT_FUNDS";
    } else {
      if (totalCid == change) {
        status = "Status: CLOSED";
      } else {
        status = "Status: OPEN";
      }

      for (let i = cid.length - 1; i >= 0; i--) {
        let inRegister = cid[i][1];
        let billValue = cidValue[i];
        if (inRegister > 0 && change >= billValue) {
          let amount = 0;
          const amountNeeded = Math.floor(change / billValue);
          const amountInRegister = Math.floor(inRegister / billValue);
          if (amountInRegister >= amountNeeded) {
            amount = amountNeeded * billValue;
          } else {
            amount = amountInRegister * billValue;
          }

          change = (change - amount).toFixed(2);
          status += ` ${cid[i][0]}: $${amount}`;
        }
      }

      if (change != 0) {
        status = "Status: INSUFFICIENT_FUNDS";
      }
    }

    changeDue.textContent = status;
  }
};

use parseFoat() on cash.value, totalCid and change.

I thought this was it, but it failed again. I hit submit with the changes and it passed, but then I hit submit again and it failed. Makes it seem like you’re on the right track and it is something to do with the float numbers.

Since the error message is so generic and it’s not telling me what test failed, I did some digging and found the md from GitHub. I took the hints section and modified the assert to use console.assert instead of assert.deepEqual and created a copy of my purchase function to return an object that would pass the assert.

I then tied the testing of this to the purchase button. I see this result when mashing purchase:

...
Tests passed
Potential infinite loop detected on line 77. Tests may fail if this is not changed.
Tests passed
...

Now, line 77 is the checkCashRegister() function. This is a function, not a loop. As far as loops, I believe there are only two. One per purchase()/checkCashRegister() function. Since one is a copy of the other, they both use for (let i = cid.length - 1; i >= 0; i--) {. This shouldn’t result in an infinite loop as i is being set to the length, checking if it’s not negative, and decrementing. Even if cid.length was 0, i would start at -1 and the loop wouldn’t run.

Anyway, I do not get the infinite loop console message when I do not run the assert tests and mash the purchase so I do not think this is the cause of the tests passing. And running the asserts show that my code is passing on consecutive calls. Also, the asserts are passing with mashing.

I really wish it would tell you what it’s testing that fails. It’s like looking for a needle in a haystack.

This is my code with the asserts:

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]
];

const purchase = () => {
  const cidValue = [
    0.01,
    0.05,
    0.10,
    0.25,
    1,
    5,
    10,
    20,
    100
  ];

  const changeDue = document.getElementById("change-due");
  const cash = parseFloat(document.getElementById("cash").value);

  // Prevent infinite loop
  if (cid.length <= 0) {
    return;
  }

  if (cash < price) {
    alert("Customer does not have enough money to purchase the item");
  } else if (cash === price) {
    changeDue.textContent = "No change due - customer paid with exact cash";
  } else {
    let change = cash - price;
    const totalCid = cid.reduce((acc, arr) => (acc + arr[1]), 0).toFixed(2);
    let status = "";
    if (parseFloat(totalCid) < parseFloat(change)) {
      status = "Status: INSUFFICIENT_FUNDS";
    } else {
      if (parseFloat(totalCid) === parseFloat(change)) {
        status = "Status: CLOSED";
      } else {
        status = "Status: OPEN";
      }

      for (let i = cid.length - 1; i >= 0; i--) {
        let inRegister = cid[i][1];
        let billValue = cidValue[i];
        if (inRegister > 0 && change >= billValue) {
          let amount = 0;
          const amountNeeded = Math.floor(change / billValue);
          const amountInRegister = Math.floor(inRegister / billValue);
          if (amountInRegister >= amountNeeded) {
            amount = amountNeeded * billValue;
          } else {
            amount = amountInRegister * billValue;
          }

          change = (change - amount).toFixed(2);
          status += ` ${cid[i][0]}: $${amount}`;
        }
      }

      if (parseFloat(change) !== 0) {
        status = "Status: INSUFFICIENT_FUNDS";
      }
    }

    changeDue.textContent = status;
  }

  runTests();
};

/* Testing */
const checkCashRegister = (price, cash, cid) => {
  const cidValue = [
    0.01,
    0.05,
    0.10,
    0.25,
    1,
    5,
    10,
    20,
    100
  ];

  // Prevent infinite loop
  if (cid.length <= 0) {
    return;
  }

  if (cash < price) {
    alert("Customer does not have enough money to purchase the item");
  } else if (cash === price) {
    changeDue.textContent = "No change due - customer paid with exact cash";
  } else {
    let change = cash - price;
    const totalCid = cid.reduce((acc, arr) => (acc + arr[1]), 0).toFixed(2);
    let status = {
      status: "",
      change: []
    };
    if (parseFloat(totalCid) < parseFloat(change)) {
      // status = "Status: INSUFFICIENT_FUNDS";
      status.status = "INSUFFICIENT_FUNDS";
    } else {
      if (parseFloat(totalCid) === parseFloat(change)) {
        // status = "Status: CLOSED";
        status.status = "CLOSED";
      } else {
        // status = "Status: OPEN";
        status.status = "OPEN";
      }

      for (let i = cid.length - 1; i >= 0; i--) {
        let inRegister = cid[i][1];
        let billValue = cidValue[i];
        if (inRegister > 0 && change >= billValue) {
          let amount = 0;
          const amountNeeded = Math.floor(change / billValue);
          const amountInRegister = Math.floor(inRegister / billValue);
          if (amountInRegister >= amountNeeded) {
            amount = amountNeeded * billValue;
          } else {
            amount = amountInRegister * billValue;
          }

          change = (change - amount).toFixed(2);
          //status += ` ${cid[i][0]}: $${amount}`;
          status.change.push([cid[i][0], amount]);
        }
      }

      if (parseFloat(change) !== 0) {
        //status = "Status: INSUFFICIENT_FUNDS";
        status.status = "INSUFFICIENT_FUNDS";
        status.change = [];
      }
    }

    //changeDue.textContent = status;
    return status;
  }
};

const runTests = () => {
  console.assert(
  checkCashRegister(19.5, 20, [
    ['PENNY', 1.01],
    ['NICKEL', 2.05],
    ['DIME', 3.1],
    ['QUARTER', 4.25],
    ['ONE', 90],
    ['FIVE', 55],
    ['TEN', 20],
    ['TWENTY', 60],
    ['ONE HUNDRED', 100]
  ]),
  { status: 'OPEN', change: [['QUARTER', 0.5]] }
);
console.assert(
  checkCashRegister(19.5, 20, [
    ['PENNY', 1.01],
    ['NICKEL', 2.05],
    ['DIME', 3.1],
    ['QUARTER', 4.25],
    ['ONE', 90],
    ['FIVE', 55],
    ['TEN', 20],
    ['TWENTY', 60],
    ['ONE HUNDRED', 100]
  ]),
  { status: 'OPEN', change: [['QUARTER', 0.5]] }
);
console.assert(
  checkCashRegister(3.26, 100, [
    ['PENNY', 1.01],
    ['NICKEL', 2.05],
    ['DIME', 3.1],
    ['QUARTER', 4.25],
    ['ONE', 90],
    ['FIVE', 55],
    ['TEN', 20],
    ['TWENTY', 60],
    ['ONE HUNDRED', 100]
  ]),
  {
    status: 'OPEN',
    change: [
      ['TWENTY', 60],
      ['TEN', 20],
      ['FIVE', 15],
      ['ONE', 1],
      ['QUARTER', 0.5],
      ['DIME', 0.2],
      ['PENNY', 0.04]
    ]
  }
);
console.assert(
  checkCashRegister(19.5, 20, [
    ['PENNY', 0.01],
    ['NICKEL', 0],
    ['DIME', 0],
    ['QUARTER', 0],
    ['ONE', 0],
    ['FIVE', 0],
    ['TEN', 0],
    ['TWENTY', 0],
    ['ONE HUNDRED', 0]
  ]),
  { status: 'INSUFFICIENT_FUNDS', change: [] }
);
console.assert(
  checkCashRegister(19.5, 20, [
    ['PENNY', 0.01],
    ['NICKEL', 0],
    ['DIME', 0],
    ['QUARTER', 0],
    ['ONE', 1],
    ['FIVE', 0],
    ['TEN', 0],
    ['TWENTY', 0],
    ['ONE HUNDRED', 0]
  ]),
  { status: 'INSUFFICIENT_FUNDS', change: [] }
);
console.assert(
  checkCashRegister(19.5, 20, [
    ['PENNY', 0.5],
    ['NICKEL', 0],
    ['DIME', 0],
    ['QUARTER', 0],
    ['ONE', 0],
    ['FIVE', 0],
    ['TEN', 0],
    ['TWENTY', 0],
    ['ONE HUNDRED', 0]
  ]),
  {
    status: 'CLOSED',
    change: [
      ['PENNY', 0.5],
      ['NICKEL', 0],
      ['DIME', 0],
      ['QUARTER', 0],
      ['ONE', 0],
      ['FIVE', 0],
      ['TEN', 0],
      ['TWENTY', 0],
      ['ONE HUNDRED', 0]
    ]
  }
);
console.log("Tests passed");
};

Wait a minute. I realized my asserts were not working properly, and while fixing them I noticed the final test:

assert.deepEqual(
  checkCashRegister(19.5, 20, [
    ['PENNY', 0.5],
    ['NICKEL', 0],
    ['DIME', 0],
    ['QUARTER', 0],
    ['ONE', 0],
    ['FIVE', 0],
    ['TEN', 0],
    ['TWENTY', 0],
    ['ONE HUNDRED', 0]
  ]),
  {
    status: 'CLOSED',
    change: [
      ['PENNY', 0.5],
      ['NICKEL', 0],
      ['DIME', 0],
      ['QUARTER', 0],
      ['ONE', 0],
      ['FIVE', 0],
      ['TEN', 0],
      ['TWENTY', 0],
      ['ONE HUNDRED', 0]
    ]
  }
);

It is expecting a return of 0’s for every denomination? But, we are told to only return when there is actual change. The initial assert only returns QUARTER and not every denomination.

you found the wrong .md file, you may notice that the challenge description doesn’t match, and that cash register is a function, not an app

If you want the challenge file, it is this one: freeCodeCamp/curriculum/challenges/english/15-javascript-algorithms-and-data-structures-22/build-a-cash-register-project/build-a-cash-register.md at main · freeCodeCamp/freeCodeCamp · GitHub

Thank you for posting the correct challenge file. The random aspect of the testing here showed me the error that was happening. My solution was to use a correction factor when doing the math instead of relying on the float. I noticed I was sometimes getting “INSUFFICIENT FUNDS” on the final test during some of the random tests when it was expecting “CLOSED”. This was due to rounding errors.

Example:

total: 27.29, change: 27.29
TEN amountNeeded: 2, amountInRegister: 2, change: 7.29, amount: 20.00
ONE amountNeeded: 7, amountInRegister: 5, change: 2.29, amount: 5.00
QUARTER amountNeeded: 9, amountInRegister: 8, change: 0.29, amount: 2.00
PENNY amountNeeded: 28, amountInRegister: 28, change: 0.01, amount: 0.28
INF1... change: 0.01

In this case, the error was on the PENNY. The amount needed and in register both should have been 29, but 0.29 / 0.01 was producing 28.99999 instead of 29.

Numerous attempts would shift this problem around from the dime, nickel, penny, etc. So, in the end, it was the floating points, and the solution was to use a correction factor when doing the math.

usually when dealing with money to avoid the floating point error, a solution is to use a whole number of cents.
So, instead of having $0.10 + $0.20 giving $0.30000000000000004, you do 10¢ + 20¢ giving 30¢

1 Like

That is a good tip and something I will keep in mind in the future. It is essentially what I did with the correction factor. I kept everything in decimal, but multiplied everything by 100 in a couple areas which removed the decimal when it came to the math. Eventually dividing by 100 when it came to the end result.

Using a variable to hold the factor means I only need to modify one line if needed instead of modifying multiple points in the code.