diff --git a/__tests__/helpers/cocktailClass.test.tsx b/__tests__/helpers/cocktailClass.test.tsx index 878ea50..7cf1204 100644 --- a/__tests__/helpers/cocktailClass.test.tsx +++ b/__tests__/helpers/cocktailClass.test.tsx @@ -1,7 +1,8 @@ import { Cocktail } from "@/interfaces/cocktails"; -import { CocktailDbClient } from "@/helpers/cocktailClass"; +import { CocktailDbClient, profanityWords } from "@/helpers/cocktailClass"; import fetchMock from "jest-fetch-mock"; import { Ingredient } from "@/interfaces/ingredient"; +import { User } from "@/app/page"; beforeEach(() => { fetchMock.resetMocks(); @@ -18,6 +19,11 @@ const mockIngredients = ["Egg", "Gin"]; const mockAllergies = ["Vodka", "Lemon"]; const cocktailDBClient = new CocktailDbClient(); const mockCocktailID = "123"; +const mockUserData = { + sub: "user-sub", + allergies: ["Vodka", "Lemon"], + favoriteCocktails: [], +} as User; // Mock cocktails with duplicate entry for testing purposes const mockCocktails = { @@ -403,6 +409,166 @@ test("fetchIngredientDataById response error", async () => { ); }); +test("fetchPopularCocktails", async () => { + fetchMock.mockResponseOnce(JSON.stringify(mockCocktails)); + + const resultPromise = cocktailDBClient.fetchPopularCocktails(); + + await expect(resultPromise).resolves.toEqual(expectedOutput); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/popular.php` + ); +}); + +test("fetchPopularCocktails with no results found", async () => { + fetchMock.mockResponseOnce(JSON.stringify({ drinks: [] as Cocktail[] })); + + const resultPromise = cocktailDBClient.fetchPopularCocktails(); + + await expect(resultPromise).resolves.toEqual([] as Cocktail[]); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/popular.php` + ); +}); + +test("fetchPopularCocktails response error", async () => { + fetchMock.mockReject(new Error("API Error")); + + const resultPromise = cocktailDBClient.fetchPopularCocktails(); + + await expect(resultPromise).resolves.toEqual([] as Cocktail[]); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/popular.php` + ); +}); + +test("fetchNewCocktails", async () => { + fetchMock.mockResponseOnce(JSON.stringify(mockCocktails)); + + const resultPromise = cocktailDBClient.fetchNewCocktails(); + + await expect(resultPromise).resolves.toEqual(expectedOutput); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/latest.php` + ); +}); + +test("fetchNewCocktails with no results found", async () => { + fetchMock.mockResponseOnce(JSON.stringify({ drinks: [] as Cocktail[] })); + + const resultPromise = cocktailDBClient.fetchNewCocktails(); + + await expect(resultPromise).resolves.toEqual([] as Cocktail[]); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/latest.php` + ); +}); + +test("fetchNewCocktails response error", async () => { + fetchMock.mockReject(new Error("API Error")); + + const resultPromise = cocktailDBClient.fetchNewCocktails(); + + await expect(resultPromise).resolves.toEqual([] as Cocktail[]); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/latest.php` + ); +}); + +test("fetchRandomCocktails", async () => { + fetchMock.mockResponseOnce(JSON.stringify(mockCocktails)); + + const resultPromise = cocktailDBClient.fetchRandomCocktails(); + + await expect(resultPromise).resolves.toEqual(expectedOutput); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/randomselection.php` + ); +}); + +test("fetchRandomCocktails with no results found", async () => { + fetchMock.mockResponseOnce(JSON.stringify({ drinks: [] as Cocktail[] })); + + const resultPromise = cocktailDBClient.fetchRandomCocktails(); + + await expect(resultPromise).resolves.toEqual([] as Cocktail[]); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/randomselection.php` + ); +}); + +test("fetchRandomCocktails response error", async () => { + fetchMock.mockReject(new Error("API Error")); + + const resultPromise = cocktailDBClient.fetchRandomCocktails(); + + await expect(resultPromise).resolves.toEqual([] as Cocktail[]); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/randomselection.php` + ); +}); + +test("fetchSingleRandomCocktail", async () => { + fetchMock.mockResponseOnce(JSON.stringify(mockCocktails)); + + const resultPromise = cocktailDBClient.fetchSingleRandomCocktail(); + + await expect(resultPromise).resolves.toEqual(expectedOutput[0]); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/random.php` + ); +}); + +test("fetchSingleRandomCocktail with no results found", async () => { + fetchMock.mockResponseOnce(JSON.stringify({ drinks: [] as Cocktail[] })); + + const resultPromise = cocktailDBClient.fetchSingleRandomCocktail(); + + await expect(resultPromise).resolves.toEqual({} as Cocktail); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/random.php` + ); +}); + +test("fetchSingleRandomCocktail response error", async () => { + fetchMock.mockReject(new Error("API Error")); + + const resultPromise = cocktailDBClient.fetchSingleRandomCocktail(); + + await expect(resultPromise).resolves.toEqual({} as Cocktail); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/random.php` + ); +}); + +test("fetchSingleRandomCocktail with profanity result", async () => { + fetchMock.mockResponseOnce( + JSON.stringify({ + drinks: [{ strDrink: profanityWords.values().next() }], + }) + ); + + const resultPromise = cocktailDBClient.fetchSingleRandomCocktail(); + + await expect(resultPromise).resolves.toEqual({} as Cocktail); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toEqual( + `https://www.thecocktaildb.com/api/json/v2/${API_KEY}/random.php` + ); +}); + test("filterCocktails by allergies", () => { const result = cocktailDBClient.filterCocktails( mockAllergies, @@ -442,3 +608,41 @@ test("filterCocktails by ingredients with no cocktails", () => { expect(result).toEqual([]); }); + +test("handleFavoriteCocktail", () => { + const mockCocktail = expectedOutput[0]; + + const result = cocktailDBClient.handleFavoriteCocktail( + mockCocktail.cocktailId, + mockUserData + ); + + const expectedUserData = { + ...mockUserData, + favoriteCocktails: [ + ...mockUserData.favoriteCocktails, + mockCocktail.cocktailId, + ], + }; + + expect(result).toEqual(expectedUserData); +}); + +test("handleFavoriteCocktail to remove favorite", () => { + const mockCocktail = expectedOutput[0]; + + const result = cocktailDBClient.handleFavoriteCocktail( + mockCocktail.cocktailId, + { + ...mockUserData, + favoriteCocktails: [mockCocktail.cocktailId], + } + ); + + const expectedUserData = { + ...mockUserData, + favoriteCocktails: [], + }; + + expect(result).toEqual(expectedUserData); +}); diff --git a/__tests__/helpers/textFuncs.test.tsx b/__tests__/helpers/textFuncs.test.tsx index a04b204..dedbb47 100644 --- a/__tests__/helpers/textFuncs.test.tsx +++ b/__tests__/helpers/textFuncs.test.tsx @@ -1,5 +1,8 @@ import "@testing-library/jest-dom"; -import { toSentenceCase } from "@/helpers/textFuncs"; +import { + toSentenceCase, + breakTextIntoTwoSentenceChunks, +} from "@/helpers/textFuncs"; describe("toSentenceCase", () => { it("should convert the first character of a string to uppercase and the rest to lowercase", () => { @@ -18,3 +21,33 @@ describe("toSentenceCase", () => { expect(toSentenceCase("A")).toBe("A"); }); }); + +describe("breakTextIntoTwoSentenceChunks", () => { + it("should handle a string with only one sentence", () => { + const testString = "This is a test."; + const expectedResult = ["This is a test."]; + expect(breakTextIntoTwoSentenceChunks(testString)).toEqual(expectedResult); + }); + + it("should not break text that is less than 3 sentences in length", () => { + const testString = "This is a test. This is another test."; + const expectedResult = ["This is a test. This is another test."]; + expect(breakTextIntoTwoSentenceChunks(testString)).toEqual(expectedResult); + }); + + it("should handle an empty string", () => { + const testString = ""; + const expectedResult = [""]; + expect(breakTextIntoTwoSentenceChunks(testString)).toEqual(expectedResult); + }); + + it("should handle a string with more than 3 sentences", () => { + const testString = + "This is a test. This is another test. This is yet another test. This is the final test."; + const expectedResult = [ + "This is a test. This is another test.", + "This is yet another test. This is the final test.", + ]; + expect(breakTextIntoTwoSentenceChunks(testString)).toEqual(expectedResult); + }); +}); diff --git a/components/carousel.tsx b/components/carousel.tsx deleted file mode 100644 index 2faee0b..0000000 --- a/components/carousel.tsx +++ /dev/null @@ -1,83 +0,0 @@ -"use client"; -import Image from "next/image"; -import { useEffect, useState } from "react"; -import { motion } from "framer-motion"; -import Link from "next/link"; -import { Cocktail } from "@/interfaces/cocktails"; - -export default function Carousel({ - cocktails, - numItems = 3, - scrollInterval = 3000, -}: { - cocktails: Cocktail[]; - numItems?: number; - scrollInterval?: number; -}) { - const [lowIndex, setLowIndex] = useState(0); - - const getNextIndex = (index: number): number => { - return (index + 1) % cocktails.length; - }; - - const getPreviousIndex = (index: number): number => { - return (index - 1 + cocktails.length) % cocktails.length; - }; - - useEffect(() => { - const interval = setInterval(() => { - setLowIndex(getNextIndex(lowIndex)); - }, scrollInterval); - return () => clearInterval(interval); - }, [lowIndex]); - - const getVisibleCocktails = (): Cocktail[] => { - const visibleCocktails = [] as Cocktail[]; - for (let i = 0; i < numItems; i++) { - // Calculate the current index in a circular manner (go back to the start if we reach the end of the array) - const index = (lowIndex + i) % cocktails.length; - visibleCocktails.push(cocktails[index]); - } - return visibleCocktails; - }; - - return ( -
- - {getVisibleCocktails().map((cocktail) => ( - - - {cocktail.name} -

- {cocktail.name} -

- -
- ))} - -
- ); -} diff --git a/helpers/cocktailClass.ts b/helpers/cocktailClass.ts index ebd2f69..7002672 100644 --- a/helpers/cocktailClass.ts +++ b/helpers/cocktailClass.ts @@ -3,7 +3,7 @@ import { Cocktail, OriginalCocktail } from "@/interfaces/cocktails"; import { Ingredient, OriginalIngredient } from "@/interfaces/ingredient"; // Words that should not be included in any cocktail names -const profanityWords = new Set([ +export const profanityWords = new Set([ "fuck", "f**k", "ass", @@ -316,6 +316,7 @@ export class CocktailDbClient { if (data.drinks.length === 0) { return {} as Cocktail; } + cocktailName = data.drinks[0].strDrink; return this.formatCocktails(data.drinks)[0]; } catch (error) { console.error(error); @@ -323,6 +324,7 @@ export class CocktailDbClient { } } + // This should only be reached if the maximum number of retries is reached return {} as Cocktail; } diff --git a/helpers/textFuncs.ts b/helpers/textFuncs.ts index b56bb4e..760ecc8 100644 --- a/helpers/textFuncs.ts +++ b/helpers/textFuncs.ts @@ -8,13 +8,13 @@ export const breakTextIntoTwoSentenceChunks = function ( // The ingredient descriptions are returned as a single large block of text. // This function will break the text into chunks of two sentences each, so that // the description can be displayed in a more readable format. - const sentences = text.match(/[^.!?]+[.!?]+\s*(?=[A-Z])/g) || [text]; + const sentences = text.match(/[^.!?]+[.!?]+(\s|$)/g) || [text]; - // Group the sentences into chunks of two - const chunks = []; + // Group the sentences into chunks of two. If there are less than 3 sentences, + // the text will not be broken up. + const chunks: string[] = []; for (let i = 0; i < sentences.length; i += 2) { - // Concatenate two sentences at a time, or just one if it's the last sentence without a pair. - chunks.push(sentences.slice(i, i + 2).join(" ")); + chunks.push(sentences.slice(i, i + 2).join("")); } return chunks.map((s) => s.trim());