Co-authored-by: sembauke <semboot699@gmail.com>
9.8 KiB
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:
-
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. -
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 }. -
You should implement a
groupByZone(actions)function that groups the actions into storage zones based on each item’szoneproperty. -
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. -
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);