Files
freeCodeCamp/curriculum/challenges/english/blocks/lab-smart-pantry-restocker/69a5f35669099ed52f8563b1.md
T
2026-05-23 20:37:33 +00:00

9.8 KiB
Raw Blame History

id, title, challengeType, dashedName
id title challengeType dashedName
69a5f35669099ed52f8563b1 Build a Smart Pantry Restocker 26 lab-smart-pantry-restocker

--description--

In this lab, you will build a small pantry management program using basic JavaScript concepts like arrays, objects, loops, and conditionals.

You will simulate receiving a shipment of pantry items, deciding what to do with each item, and organizing the results for storage.

Objective: Fulfill the user stories below and get all the tests to pass to complete the lab.

User Stories:

  1. You should implement a parseShipment(rawData) function that takes an array of strings and returns an array of objects with { sku, name, qty, expires, zone } properties.

  2. You should implement a planRestock(pantry, shipment) function that compares the current pantry with the incoming shipment and returns an array of actions in the form { type: "restock" | "discard" | "donate", item }.

  3. You should implement a groupByZone(actions) function that groups the actions into storage zones based on each items zone property.

  4. You should implement a clonePantry(pantry) function that returns a deep copy of the pantry so planning changes do not affect the original list. A deep copy means creating a new array with new objects, so modifying the copy does not change the original pantry.

  5. You should use all of the functions together to process a shipment and log the final grouped result object to the console.

--before-each--

const spy = __helpers.spyOn(console, "log");
const getLogs = () => spy.calls.map(call => call?.[0]);

--hints--

You should define a function named parseShipment that accepts one array of strings called rawData as parameter.

assert.isFunction(parseShipment);
assert.lengthOf(parseShipment, 1);

Your parseShipment function should convert shipment strings in the array into objects with the properties: sku, name, qty, expires, and zone.

const rawData = ["A10|Tomatoes|5|2027-01-01|fridge"];
const result = parseShipment(rawData);

assert.isArray(result);
assert.isObject(result[0]);
assert.deepEqual(result[0], {
  sku: "A10",
  name: "Tomatoes",
  qty: 5,
  expires: "2027-01-01",
  zone: "fridge"
});

Duplicate SKUs in the shipment should be ignored.

const rawData = [
  "C32|Eggs|3|2027-01-01",
  "C32|Eggs|3|2027-01-01"
];

const result = parseShipment(rawData);

assert.lengthOf(result, 1);
assert.strictEqual(result[0].sku, "C32");

When zone is not present in a shipment string, you should assign it to general.

const rawData = ["B21|Bananas|10|2027-01-01"];
const result = parseShipment(rawData);
assert.strictEqual(result[0].zone, "general");

The qty value should be converted into a number.

const rawData = ["A10|Tomatoes|5|2027-01-01|fridge"];
const result = parseShipment(rawData);
assert.strictEqual(result[0].qty, 5);
assert.isNumber(result[0].qty);

You should define a function named planRestock that accepts two parameters: pantry and shipment.

assert.isFunction(planRestock);
assert.lengthOf(planRestock, 2);

Your planRestock function should return an array of actions with the properties of type and item.

const pantry = [{ sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" }];
const shipment = [{ sku: "A10", name: "Tomatoes", qty: 6, expires: "2027-01-01", zone: "fridge" }];

const result = planRestock(pantry, shipment);

assert.isArray(result);
assert.isObject(result[0]);
assert.property(result[0], "type");
assert.property(result[0], "item");

Items with a quantity of 0 or less in shipment should create an action with type discard, regardless of whether the item is in the pantry.

const pantry = [{ sku: "D43", name: "Pineapples", qty: 2, expires: "2027-01-01", zone: "general" }];
const shipment = [{ sku: "D43", name: "Pineapples", qty: 0, expires: "2027-01-01", zone: "general" },
                  { sku: "E54", name: "Peppers", qty: -1, expires: "2027-01-01", zone: "fridge" }
                ];

const result = planRestock(pantry, shipment);

assert.strictEqual(result[0].type, "discard");
assert.strictEqual(result[1].type, "discard");

When a shipment item already exists in the pantry (same sku), the action should be restock.

const pantry = [{ sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" }];
const shipment = [{ sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" }];

const result = planRestock(pantry, shipment);

assert.strictEqual(result[0].type, "restock");

If a shipment item does not exist in the pantry, the action should be "donate".

const pantry = [];
const shipment = [{ sku: "C32", name: "Eggs", qty: 3, expires: "2027-01-01", zone: "fridge" }];

const result = planRestock(pantry, shipment);

assert.strictEqual(result[0].type, "donate");

You should define a function named groupByZone that accepts one parameter called actions.

assert.isFunction(groupByZone);
assert.lengthOf(groupByZone, 1);

Your function groupByZone should return the actions grouped by the zone property of each object.


const actions = [
  { type: "restock", item: { sku: "A1", zone: "fridge" } },
  { type: "restock", item: { sku: "B1", zone: "pantry" } }, 
];

const result = groupByZone(actions);

assert.isObject(result);
assert.isArray(result.fridge);
assert.isArray(result.pantry);

You should define a function named clonePantry that accepts one parameter called pantry.

assert.isFunction(clonePantry);
assert.lengthOf(clonePantry, 1);

clonePantry should return a new array instead of the original pantry array.

const pantry = [{ sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" }];
const pantryClone = clonePantry(pantry);

assert.isArray(pantryClone);
assert.notStrictEqual(pantryClone, pantry);

The objects inside the cloned pantry should also be new objects (deep copy).

const pantry = [{ sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" }];
const pantryClone = clonePantry(pantry);

assert.notStrictEqual(pantryClone[0], pantry[0]);
assert.deepEqual(pantryClone[0], pantry[0]);

pantryClone[0].qty = 100;
assert.strictEqual(pantry[0].qty, 4);

The functions should work together to process a shipment and group the resulting actions.

const pantry = [{ sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" }];
const rawData = ["A10|Tomatoes|5|2027-01-01|fridge"];

const shipment = parseShipment(rawData);
const pantryClone = clonePantry(pantry);
const actions = planRestock(pantryClone, shipment);
const grouped = groupByZone(actions);

assert.isObject(grouped);
assert.strictEqual(grouped.fridge[0].item.qty, 5);

You should log the resulting actions grouped by zones.

const groupedLog = getLogs().find(log => {
  if (!log || typeof log !== "object" || Array.isArray(log)) return false;

  return Object.values(log).some(zoneActions => {
    if (!Array.isArray(zoneActions)) return false;

    return zoneActions.some(action =>
      action &&
      ["restock", "discard", "donate"].includes(action.type) &&
      action.item &&
      typeof action.item.sku === "string"
    );
  });
});

assert.isOk(groupedLog, "Log the grouped actions object to the console.");

--seed--

--seed-contents--


--solutions--


function parseShipment(rawData) {

  // Early return if rawData is not an Array
  if (!Array.isArray(rawData)) return [];

  const result = [];
  const seenSKUs = [];

  for (let line of rawData) {

    const parts = line.split("|");

    const sku = parts[0].trim();

    if (seenSKUs.includes(sku)) continue; // Avoid duplicates

    const name = parts[1].trim();
    const qty = Number(parts[2].trim()); // Parse qty into a number
    const expires = parts[3].trim();
    const zone = ( parts[4] ) ? parts[4].trim() : "general";

    result.push({
      sku: sku,
      name: name,
      qty: qty,
      expires: expires,
      zone: zone
    });

    seenSKUs.push(sku);

  }

  return result;
}

function planRestock(pantry, shipment) {
  const actions = [];

  for (let item of shipment) {

    var exists = false;

    for (let stock of pantry)
    {
      if (stock.sku === item.sku)
      {
        exists = true;
      }
    }

    if (item.qty > 0) {
      if (exists) {
        actions.push({ type: "restock", item: item });
      } else{
        actions.push({ type: "donate", item: item });
      }
    } else {
      actions.push({ type: "discard", item: item });
    }
  }

  return actions;
}

function groupByZone(actions) {

  const byZone = {};

  for (const action of actions) {

    const zone = action.item.zone;

    if (!byZone[zone]) {
      byZone[zone] = [];
    }

    byZone[zone].push(action);
  }

  return byZone;

}

function clonePantry(pantry) {
  const pantryClone = [];

  for (let item of pantry) {
    pantryClone.push({
      sku: item.sku,
      name: item.name,
      qty: item.qty,
      expires: item.expires,
      zone: item.zone
    });
  }

  return pantryClone;
}

const pantry = [
  { sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" },
  { sku: "D43", name: "Pineapples", qty: 2, expires: "2020-01-01", zone: "general" }
];

const rawData = [
  "A10|Tomatoes|5|2027-01-01", // Restock existing item
  "B21|Bananas|10|2027-01-01", // Donate new item without zone
  "C32|Eggs|3|2027-01-01|fridge", // Donate to a defined zone
  "C32|Eggs|3|2027-01-01", // Duplicated SKU in shipment
  "D43|Pineapples|0|2027-01-01", // Discard with quantity of 0
  "E54|Peppers|-1|2027-01-01|fridge" // Discard even if it's not in pantry
];

const shipment = parseShipment(rawData);
const pantryCopy = clonePantry(pantry);
const actions = planRestock(pantryCopy, shipment);
const grouped = groupByZone(actions);

// Show items expanded in zones
console.log(grouped);