2026-02-12 19:07:23 -06:00
|
|
|
import { describe, test, expect, vi, beforeEach } from "vitest";
|
2023-08-21 17:32:09 +02:00
|
|
|
import { parseIngredientText } from "./use-recipe-ingredients";
|
2025-06-20 00:09:12 +07:00
|
|
|
import type { RecipeIngredient } from "~/lib/api/types/recipe";
|
2026-02-12 19:07:23 -06:00
|
|
|
import { useLocales } from "../use-locales";
|
|
|
|
|
|
|
|
|
|
vi.mock("../use-locales");
|
2023-08-21 17:32:09 +02:00
|
|
|
|
|
|
|
|
describe(parseIngredientText.name, () => {
|
2026-02-12 19:07:23 -06:00
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.clearAllMocks();
|
|
|
|
|
vi.mocked(useLocales).mockReturnValue({
|
|
|
|
|
locales: [{ value: "en-US", pluralFoodHandling: "always" }],
|
|
|
|
|
locale: { value: "en-US", pluralFoodHandling: "always" },
|
|
|
|
|
} as any);
|
|
|
|
|
});
|
|
|
|
|
|
2023-08-21 17:32:09 +02:00
|
|
|
const createRecipeIngredient = (overrides: Partial<RecipeIngredient>): RecipeIngredient => ({
|
|
|
|
|
quantity: 1,
|
|
|
|
|
food: {
|
|
|
|
|
id: "1",
|
|
|
|
|
name: "Item 1",
|
|
|
|
|
},
|
|
|
|
|
unit: {
|
|
|
|
|
id: "1",
|
|
|
|
|
name: "cup",
|
|
|
|
|
},
|
|
|
|
|
...overrides,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("adds note section if note present", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({ note: "custom note" });
|
|
|
|
|
|
2025-07-31 10:36:24 -05:00
|
|
|
expect(parseIngredientText(ingredient)).toContain("custom note");
|
2023-08-21 17:32:09 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("ingredient text with fraction", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({ quantity: 1.5, unit: { fraction: true, id: "1", name: "cup" } });
|
|
|
|
|
|
2025-07-31 10:36:24 -05:00
|
|
|
expect(parseIngredientText(ingredient, 1, true)).contain("1<sup>1</sup>").and.to.contain("<sub>2</sub>");
|
2023-08-31 12:08:44 -05:00
|
|
|
});
|
|
|
|
|
|
2024-05-12 14:15:26 -05:00
|
|
|
test("ingredient text with fraction when unit is null", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({ quantity: 1.5, unit: undefined });
|
|
|
|
|
|
2025-07-31 10:36:24 -05:00
|
|
|
expect(parseIngredientText(ingredient, 1, true)).contain("1<sup>1</sup>").and.to.contain("<sub>2</sub>");
|
2024-05-12 14:15:26 -05:00
|
|
|
});
|
|
|
|
|
|
2023-08-31 12:08:44 -05:00
|
|
|
test("ingredient text with fraction no formatting", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({ quantity: 1.5, unit: { fraction: true, id: "1", name: "cup" } });
|
2025-07-31 10:36:24 -05:00
|
|
|
const result = parseIngredientText(ingredient, 1, false);
|
2023-08-31 12:08:44 -05:00
|
|
|
|
|
|
|
|
expect(result).not.contain("<");
|
|
|
|
|
expect(result).not.contain(">");
|
|
|
|
|
expect(result).contain("1 1/2");
|
2023-08-21 17:32:09 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("sanitizes html", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({ note: "<script>alert('foo')</script>" });
|
|
|
|
|
|
2025-07-31 10:36:24 -05:00
|
|
|
expect(parseIngredientText(ingredient)).not.toContain("<script>");
|
2023-08-21 17:32:09 +02:00
|
|
|
});
|
2023-11-14 09:39:07 -06:00
|
|
|
|
|
|
|
|
test("plural test : plural qty : use abbreviation", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({
|
|
|
|
|
quantity: 2,
|
|
|
|
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: true },
|
2025-06-20 00:09:12 +07:00
|
|
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
2025-07-31 10:36:24 -05:00
|
|
|
expect(parseIngredientText(ingredient)).toEqual("2 tbsps diced onions");
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("plural test : plural qty : not abbreviation", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({
|
|
|
|
|
quantity: 2,
|
|
|
|
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
|
2025-06-20 00:09:12 +07:00
|
|
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
2025-07-31 10:36:24 -05:00
|
|
|
expect(parseIngredientText(ingredient)).toEqual("2 tablespoons diced onions");
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("plural test : single qty : use abbreviation", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({
|
|
|
|
|
quantity: 1,
|
|
|
|
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: true },
|
2025-06-20 00:09:12 +07:00
|
|
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
2025-07-31 10:36:24 -05:00
|
|
|
expect(parseIngredientText(ingredient)).toEqual("1 tbsp diced onion");
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("plural test : single qty : not abbreviation", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({
|
|
|
|
|
quantity: 1,
|
|
|
|
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
|
2025-06-20 00:09:12 +07:00
|
|
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
2025-07-31 10:36:24 -05:00
|
|
|
expect(parseIngredientText(ingredient)).toEqual("1 tablespoon diced onion");
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("plural test : small qty : use abbreviation", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({
|
|
|
|
|
quantity: 0.5,
|
|
|
|
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: true },
|
2025-06-20 00:09:12 +07:00
|
|
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
2025-07-31 10:36:24 -05:00
|
|
|
expect(parseIngredientText(ingredient)).toEqual("0.5 tbsp diced onion");
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("plural test : small qty : not abbreviation", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({
|
|
|
|
|
quantity: 0.5,
|
|
|
|
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
|
2025-06-20 00:09:12 +07:00
|
|
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
2025-07-31 10:36:24 -05:00
|
|
|
expect(parseIngredientText(ingredient)).toEqual("0.5 tablespoon diced onion");
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("plural test : zero qty", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({
|
|
|
|
|
quantity: 0,
|
|
|
|
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
|
2025-06-20 00:09:12 +07:00
|
|
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
|
|
|
|
|
2025-07-31 10:36:24 -05:00
|
|
|
expect(parseIngredientText(ingredient)).toEqual("diced onions");
|
2023-11-14 09:39:07 -06:00
|
|
|
});
|
2023-11-27 10:58:18 -06:00
|
|
|
|
|
|
|
|
test("plural test : single qty, scaled", () => {
|
|
|
|
|
const ingredient = createRecipeIngredient({
|
|
|
|
|
quantity: 1,
|
|
|
|
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
|
2025-06-20 00:09:12 +07:00
|
|
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
|
2023-11-27 10:58:18 -06:00
|
|
|
});
|
|
|
|
|
|
2025-07-31 10:36:24 -05:00
|
|
|
expect(parseIngredientText(ingredient, 2)).toEqual("2 tablespoons diced onions");
|
2023-11-27 10:58:18 -06:00
|
|
|
});
|
2026-02-12 19:07:23 -06:00
|
|
|
|
|
|
|
|
test("plural handling: 'always' strategy uses plural food with unit", () => {
|
|
|
|
|
vi.mocked(useLocales).mockReturnValue({
|
|
|
|
|
locales: [{ value: "en-US", pluralFoodHandling: "always" }],
|
|
|
|
|
locale: { value: "en-US", pluralFoodHandling: "always" },
|
|
|
|
|
} as any);
|
|
|
|
|
|
|
|
|
|
const ingredient = createRecipeIngredient({
|
|
|
|
|
quantity: 2,
|
|
|
|
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", useAbbreviation: false },
|
|
|
|
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(parseIngredientText(ingredient)).toEqual("2 tablespoons diced onions");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("plural handling: 'never' strategy never uses plural food", () => {
|
|
|
|
|
vi.mocked(useLocales).mockReturnValue({
|
|
|
|
|
locales: [{ value: "en-US", pluralFoodHandling: "never" }],
|
|
|
|
|
locale: { value: "en-US", pluralFoodHandling: "never" },
|
|
|
|
|
} as any);
|
|
|
|
|
|
|
|
|
|
const ingredient = createRecipeIngredient({
|
|
|
|
|
quantity: 2,
|
|
|
|
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", useAbbreviation: false },
|
|
|
|
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(parseIngredientText(ingredient)).toEqual("2 tablespoons diced onion");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("plural handling: 'without-unit' strategy uses plural food without unit", () => {
|
|
|
|
|
vi.mocked(useLocales).mockReturnValue({
|
|
|
|
|
locales: [{ value: "en-US", pluralFoodHandling: "without-unit" }],
|
|
|
|
|
locale: { value: "en-US", pluralFoodHandling: "without-unit" },
|
|
|
|
|
} as any);
|
|
|
|
|
|
|
|
|
|
const ingredient = createRecipeIngredient({
|
|
|
|
|
quantity: 2,
|
|
|
|
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
|
|
|
|
|
unit: undefined,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(parseIngredientText(ingredient)).toEqual("2 diced onions");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("plural handling: 'without-unit' strategy uses singular food with unit", () => {
|
|
|
|
|
vi.mocked(useLocales).mockReturnValue({
|
|
|
|
|
locales: [{ value: "en-US", pluralFoodHandling: "without-unit" }],
|
|
|
|
|
locale: { value: "en-US", pluralFoodHandling: "without-unit" },
|
|
|
|
|
} as any);
|
|
|
|
|
|
|
|
|
|
const ingredient = createRecipeIngredient({
|
|
|
|
|
quantity: 2,
|
|
|
|
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", useAbbreviation: false },
|
|
|
|
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(parseIngredientText(ingredient)).toEqual("2 tablespoons diced onion");
|
|
|
|
|
});
|
2023-08-21 17:32:09 +02:00
|
|
|
});
|