Writing Unit Tests For Metric-Imperial Converter Project

I did write those check* functions myself. If I need to, I could write more helper functions. Right now I just want to understand why, when I give it an input where both the number and the unit are invalid, it only says, “invalid number”. I also haven’t tried to break it more after the latest changes to the code so there might be cases where it messes up on the input individually.

@DanCouper I tried this:

  const checkNumber = (input) => {
    const result = input.match(/^(?<num>\d*(\.\d+)?(\/\d+(\.\d+)?)?)(?<unit>([a-z]+))$/i);
    if (!result) {
      return false;
    }
    return true;
  }

  const checkNumberAndUnit = (input) => {
    const result = checkNumber(input);
    if (result) {
      if (!/(mi|L|km|kg|gal|lbs)$/i.test(input)) {
        return false;
      } else {
        return true;
      }
    }
  }

And it still says “invalid number” only, even when both the number and the unit are wrong. What am I doing wrong here?

I changed the implementation of checkNumberAndUnit and tried it again, but it’s still not working right.

Originally I had if (!checkNumber(input)) { do this } else if (!checkNumberAndUnit(input)) { do this } but in that situation the else if condition wasn’t even running.

Now when I’ve switched them, the checkNumberAndUnit condition runs and it says “invalid number and unit” even if the unit is valid.

I can’t tell what I’m missing. Hopefully someone can help me.

Edit: Sorry, I forgot to post the updated code. Doing that now:

function ConvertHandler() {
  const checkNumber = (input) => {
    const result = input.match(/^(?<num>\d*(\.\d+)?(\/\d+(\.\d+)?)?)(?<unit>([a-z]+))$/i);
    if (!result) {
      return false;
    }
    return true;
  }

  const checkNumberAndUnit = (input) => {
    const isNumberValid = checkNumber(input);
    if (isNumberValid) {
      const result = input.match(/^(?<num>\d*(\.\d+)?(\/\d+(\.\d+)?)?)(?<unit>([a-z]+))$/i);
      const unit = result.groups["unit"];
      if (unit.toLowerCase() !== "lbs" && unit.toLowerCase() !== "gal" && unit.toLowerCase() !== "l" &&
      unit.toLowerCase() !== "km" && unit.toLowerCase() !== "kg" && unit.toLowerCase() !== "mi") {
        return false;
      } else {
        return true;
      }
    }
  }

  this.getNum = function(input) {
    if (/^(km|kg|L|gal|lbs|mi)$/i.test(input)) {
      // We only got a unit, so the number should be 1
      return 1;
    }
    
    if (!checkNumberAndUnit(input)) {
      throw new Error("invalid number and unit");
    } else if (!checkNumber(input)) {
      throw new Error("invalid number");
    }

    let number = 0;
    const result = input.match(/^(?<num>\d*(\.\d+)?(\/\d+(\.\d+)?)?)(?<unit>([a-z]+))$/i);

    // if there was a match, result is truthy
    if (result) {
      number = result.groups["num"];

      // check if we've got a fraction (indexOf returns -1 when the character is not found)
      if (number.toString().indexOf("/") !== -1) {
        const numbers = number.toString().split("/");

        // if there are more than two elements in the numbers array, it's invalid
        // because this means it's a double (or more) fraction
        if (numbers.length > 2) {
          throw new Error("invalid number");
        } else if (numbers.length === 2) {
          const numerator = numbers[0];
          const denominator = numbers[1];
          number = Number((numerator / denominator).toString());
        }
      } else {
        number = Number(result.groups["num"]);
      }
    } else {
      throw new Error("invalid number");
    }

    return number;
  };

  this.getUnit = function(input) {
    const result = input.match(/^(?<num>\d*(\.\d+)?(\/\d+(\.\d+)?)?)(?<unit>([a-z]+))$/i);
    let unit = result.groups["unit"];
    if (unit === "l" || unit === "L") {
      return "L";
    }
    return unit.toLowerCase();
  };
  
  this.getReturnUnit = function(initUnit) {
    let result;
    switch (initUnit.toString()) {
      case "mi":
        result = "km";
        break;
      case "gal":
        result = "L";
        break;
      case "km":
        result = "mi";
        break;
      case "lbs":
        result = "kg";
        break;
      case "kg":
        result = "lbs";
        break;
      case "L":
        result = "gal";
        break;
      default:
        throw new Error("invalid unit");
    }
    
    return result;
  };

  this.spellOutUnit = function(unit) {
    let result;
    if (unit) {
      switch (unit.toString()) {
        case "mi":
          result = "miles";
          break;
        case "gal":
          result = "gallons";
          break;
        case "km":
          result = "kilometers";
          break;
        case "lbs":
          result = "pounds";
          break;
        case "kg":
          result = "kilograms";
          break;
        case "L":
          result = "liters";
          break;
        default: 
          throw new Error("invalid unit");
      }
      
      return result;
    }
  };
  
  this.convert = function(initNum, initUnit) {
    const galToL = 3.78541;
      const lbsToKg = 0.453592;
      const miToKm = 1.60934;
      let result;
      switch (initUnit.toString()) {
        case "gal":
          result = initNum * galToL;
          break;
        case "lbs":
          result = initNum * lbsToKg;
          break;
        case "mi":
          result = initNum * miToKm;
          break;
        case "L":
          result = initNum / galToL;
          break;
        case "kg":
          result = initNum / lbsToKg;
          break;
        case "km":
          result = initNum / miToKm;
          break;
      }

      if (result) {
        result = result.toFixed(5);
    
        return result;
      }
  };
  
  this.getString = function(initNum, initUnit, returnNum, returnUnit) {
    let result = `${initNum} ${this.spellOutUnit(initUnit)} converts to ${returnNum} ${this.spellOutUnit(returnUnit)}`;
    
    return result;
  };
  
}

module.exports = ConvertHandler;

Okay, so with this:

  const checkNumber = (input) => {
    return /^\d*(\.\d+)?(\/\d+(\.\d+)?)?/i.test(input);
  }

  const checkUnit = (input) => {
    const result = input.match(/^(?<num>\d*(\.\d+)?(\/\d+(\.\d+)?)?)(?<unit>(km|kg|L|gal|lbs|mi))$/i);
    if (!result) {
      return false;
    }

    //return /(km|kg|L|gal|lbs|mi)$/i.test(input);
    const unit = result.groups["unit"];
    if (unit.toLowerCase() !== "mi" && unit.toLowerCase() !== "km" &&
    unit.toLowerCase() !== "lbs" && unit.toLowerCase() !== "kg" &&
    unit.toLowerCase() !== "gal" && unit.toLowerCase() !== "l") {
      return false;
    }
    return true;
  }

  const checkNumberAndUnit = (input) => {
    return (checkNumber(input) && checkUnit(input));
  };

  const verifyInput = (input) => {
    if (!checkNumberAndUnit(input)) {
      throw new Error("invalid number and unit");
    }

    if (!checkNumber(input)) {
      throw new Error("invalid number");
    }

    if (!checkUnit(input)) {
      throw new Error("invalid unit");
    }
    return true;
  }

  this.getNum = function(input) {
    if (/^(km|kg|L|gal|lbs|mi)$/i.test(input)) {
      // We only got a unit, so the number should be 1
      return 1;
    } 

    if (verifyInput(input)) {
      let number = 0;
      const result = input.match(/^(?<num>\d*(\.\d+)?(\/\d+(\.\d+)?)?)(?<unit>([a-z]+))$/i);
      if (result) {
        number = result.groups["num"];

        // check if we've got a fraction (indexOf returns -1 when the character is not found)
        if (number.toString().indexOf("/") !== -1) {
          const numbers = number.toString().split("/");

          // if there are more than two elements in the numbers array, it's invalid
          // because this means it's a double (or more) fraction
          if (numbers.length > 2) {
            throw new Error("invalid number");
          } else if (numbers.length === 2) {
            const numerator = numbers[0];
            const denominator = numbers[1];
            number = Number((numerator / denominator).toString());

            return number;
          }
        } else {
          number = Number(result.groups["num"]);

          return number;
        }
      }
    }
  };

I managed to fix the issue of only the number being reported as invalid when both the number and the unit are invalid, but there’s still another problem. When the number is wrong but the unit is fine, it still says “invalid number and input” when it should say “invalid number” only.

Please help. Thanks.

Edit:
@DanCouper I think I may need a different regex, or at least two separate regexes along with that one: one to check the number and one to check the unit. If the ones I’m using for those aren’t good.

Here’s the latest version of convertHandler.js that I have.

With the changes I made to the code now, I get an error saying

object null is not iterable (cannot read property Symbol(Symbol.iterator))

And of the console.log calls to see which check* function ran, I get these two:

line 21: test
line 11: test

Line 21 is in checkNumberAndUnit and line 16 is inside checkUnit.

It seems unit inside checkUnit is becoming null.

What I’m trying to do here is extract the unit from the string so I can check it. But is this the wrong way to do it? The console.log call after the line where I’m calling String.match doesn’t even run, so I think the error about a null object not being iterable is coming from the line that’s calling String.match. I understand why, though.

It correctly reports the number being invalid in input like 3/4/5lbs but when I give it 3/4/5in it gives that I think is coming from checkUnit.

Also, what would be the best place to try to check if both the unit and the number are wrong? Maybe calling checkUnit and checkNumberAndUnit inside getNum is a bad idea.

Please help. Thanks.

It’ll be this evening before I can get this done, but I’ll put together this app for myself so I can run FCCs tests against it: I don’t understand why you’re having to split things up the way you’re doing or why you need extra regexes, or why there’s such an issue checking in the other methods. As I say, I don’t think the structure of the skeleton provided is good, it forces you to write non-optimal/redundant code, but it should still work fine (clean input → run regex, which gives you the number if number is valid + a possibly invalid unit → check the unit, if valid then run conversion → get the expected output of {inputNum, inputUnit, returnNum, returnUnit, string }).

As I say I’ll write a version so I can run the standard tests against it

The trouble I’m having is with validating the input. Currently just the unit, it seems.

Edit: Actually just the unit when both it and the number are wrong. I don’t get why. What am I doing here?

@DanCouper I’ve done what I could think of, so I’ll wait for you to try it on your end. Get back to me whenever you’re ready. Thanks.

You said it’d be by that evening. Are you still not done? Or did you not try? Like I said, I’ve already done what I could and don’t know what I could be doing wrong.