Skip to content

Commit 6de988b

Browse files
committed
feat(ShoppingList): addManualItem and removeManualItem
1 parent b6c4184 commit 6de988b

2 files changed

Lines changed: 123 additions & 0 deletions

File tree

src/classes/shopping_list.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,31 @@ export class ShoppingList {
631631
this.categorize();
632632
}
633633

634+
/**
635+
* Adds a free-hand ingredient item to the shopping list, then automatically
636+
* recalculates the quantities and recategorize the ingredients.
637+
* @param item - The ingredient item to add.
638+
*/
639+
addManualItem(item: AddedIngredient): void {
640+
this.manualItems.push(item);
641+
this.calculateIngredients();
642+
this.categorize();
643+
}
644+
645+
/**
646+
* Removes a free-hand ingredient item from the shopping list, then automatically
647+
* recalculates the quantities and recategorize the ingredients.
648+
* @param index - The index of the item to remove within {@link ShoppingList.manualItems}.
649+
*/
650+
removeManualItem(index: number): void {
651+
if (index < 0 || index >= this.manualItems.length) {
652+
throw new Error("Index out of bounds");
653+
}
654+
this.manualItems.splice(index, 1);
655+
this.calculateIngredients();
656+
this.categorize();
657+
}
658+
634659
/**
635660
* Adds a pantry to the shopping list. On-hand pantry quantities will be
636661
* subtracted from recipe ingredient needs on each recalculation.

test/shopping_list.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,104 @@ sugar
11601160
});
11611161
});
11621162

1163+
describe("Manual items", () => {
1164+
it("should add a manual item and recalculate ingredients", () => {
1165+
const shoppingList = new ShoppingList();
1166+
shoppingList.addManualItem({
1167+
name: "bread",
1168+
quantities: [
1169+
{
1170+
quantity: { type: "fixed", value: { type: "decimal", decimal: 1 } },
1171+
unit: "loaf",
1172+
},
1173+
],
1174+
});
1175+
expect(shoppingList.manualItems).toHaveLength(1);
1176+
expect(shoppingList.ingredients).toEqual([
1177+
{
1178+
name: "bread",
1179+
quantities: [
1180+
{
1181+
quantity: {
1182+
type: "fixed",
1183+
value: { type: "decimal", decimal: 1 },
1184+
},
1185+
unit: "loaf",
1186+
},
1187+
],
1188+
},
1189+
]);
1190+
});
1191+
1192+
it("should add a manual item without quantities", () => {
1193+
const shoppingList = new ShoppingList();
1194+
shoppingList.addManualItem({ name: "olive oil" });
1195+
expect(shoppingList.manualItems).toHaveLength(1);
1196+
expect(shoppingList.ingredients).toEqual([{ name: "olive oil" }]);
1197+
});
1198+
1199+
it("should merge a manual item with an existing recipe ingredient", () => {
1200+
const shoppingList = new ShoppingList();
1201+
shoppingList.addRecipe(recipe1);
1202+
shoppingList.addManualItem({
1203+
name: "flour",
1204+
quantities: [
1205+
{
1206+
quantity: {
1207+
type: "fixed",
1208+
value: { type: "decimal", decimal: 50 },
1209+
},
1210+
unit: "g",
1211+
},
1212+
],
1213+
});
1214+
const flour = shoppingList.ingredients.find((i) => i.name === "flour");
1215+
expect(flour?.quantities?.[0]).toMatchObject<{
1216+
quantity: object;
1217+
unit: string;
1218+
}>({
1219+
quantity: { type: "fixed", value: { type: "decimal", decimal: 150 } },
1220+
unit: "g",
1221+
});
1222+
});
1223+
1224+
it("should remove a manual item and recalculate ingredients", () => {
1225+
const shoppingList = new ShoppingList();
1226+
shoppingList.addManualItem({ name: "bread" });
1227+
shoppingList.addManualItem({ name: "olive oil" });
1228+
shoppingList.removeManualItem(0);
1229+
expect(shoppingList.manualItems).toHaveLength(1);
1230+
expect(shoppingList.manualItems[0]!.name).toBe("olive oil");
1231+
expect(shoppingList.ingredients).toEqual([{ name: "olive oil" }]);
1232+
});
1233+
1234+
it("should throw an error when removing a manual item with an invalid index", () => {
1235+
const shoppingList = new ShoppingList();
1236+
shoppingList.addManualItem({ name: "bread" });
1237+
expect(() => shoppingList.removeManualItem(1)).toThrow(
1238+
"Index out of bounds",
1239+
);
1240+
expect(() => shoppingList.removeManualItem(-1)).toThrow(
1241+
"Index out of bounds",
1242+
);
1243+
});
1244+
1245+
it("should re-categorize after adding a manual item", () => {
1246+
const shoppingList = new ShoppingList(`[Bakery]\nbread`);
1247+
shoppingList.addManualItem({ name: "bread" });
1248+
expect(shoppingList.categories?.Bakery).toBeDefined();
1249+
expect(shoppingList.categories!.Bakery![0]!.name).toBe("bread");
1250+
});
1251+
1252+
it("should re-categorize after removing a manual item", () => {
1253+
const shoppingList = new ShoppingList(`[Bakery]\nbread`);
1254+
shoppingList.addManualItem({ name: "bread" });
1255+
shoppingList.addManualItem({ name: "butter" });
1256+
shoppingList.removeManualItem(0);
1257+
expect(shoppingList.categories?.Bakery).toEqual([]);
1258+
});
1259+
});
1260+
11631261
describe("Pantry integration", () => {
11641262
const pantryRecipe = new Recipe(
11651263
`Add @flour{200%g} and @sugar{100%g} and @eggs{3}.`,

0 commit comments

Comments
 (0)