+ default: ({ label, selected }: { label: string; selected: boolean }) => (
+
{label} {selected ? '(selected)' : ''}
),
@@ -90,9 +64,7 @@ describe('
', () => {
`category-box-${category.label.toLowerCase()}`
);
expect(categoryBox).toBeDefined();
- expect(categoryBox.textContent?.trim()).toBe(
- category.label
- );
+ expect(categoryBox.textContent?.trim()).toBe(category.label);
});
});
@@ -104,11 +76,7 @@ describe('
', () => {
render(
);
- const selectedCategory = screen.getByTestId(
- 'category-box-fruits'
- );
- expect(selectedCategory.textContent).toBe(
- 'Fruits (selected)'
- );
+ const selectedCategory = screen.getByTestId('category-box-fruits');
+ expect(selectedCategory.textContent).toBe('Fruits (selected)');
});
});
diff --git a/__tests__/unit_test/components/navbar/Logo.test.tsx b/__tests__/unit_test/components/navbar/Logo.test.tsx
index 69291cc..5eea3c3 100644
--- a/__tests__/unit_test/components/navbar/Logo.test.tsx
+++ b/__tests__/unit_test/components/navbar/Logo.test.tsx
@@ -1,18 +1,6 @@
import React from 'react';
-import {
- render,
- screen,
- fireEvent,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { render, screen, fireEvent, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import Logo from '@/app/components/navbar/Logo';
import { useRouter } from 'next/navigation';
@@ -58,25 +46,17 @@ describe('
', () => {
it('renders the logo with default light theme', () => {
render(
);
- const logo = screen.getByAltText(
- 'Logo'
- ) as HTMLImageElement;
+ const logo = screen.getByAltText('Logo') as HTMLImageElement;
expect(logo).toBeDefined();
expect(logo.src).toContain('/images/logo-nobg.png');
});
it('renders the logo with dark theme', () => {
- vi.mocked(localStorage.getItem).mockReturnValue(
- 'dark'
- );
+ vi.mocked(localStorage.getItem).mockReturnValue('dark');
render(
);
- const logo = screen.getByAltText(
- 'Logo'
- ) as HTMLImageElement;
+ const logo = screen.getByAltText('Logo') as HTMLImageElement;
expect(logo).toBeDefined();
- expect(logo.src).toContain(
- '/images/no_bg_white.png'
- );
+ expect(logo.src).toContain('/images/no_bg_white.png');
});
it('navigates to home page when clicked', () => {
diff --git a/__tests__/unit_test/components/navbar/MenuItem.test.tsx b/__tests__/unit_test/components/navbar/MenuItem.test.tsx
index 8a1f7ad..5584b85 100644
--- a/__tests__/unit_test/components/navbar/MenuItem.test.tsx
+++ b/__tests__/unit_test/components/navbar/MenuItem.test.tsx
@@ -1,17 +1,6 @@
import React from 'react';
-import {
- render,
- screen,
- fireEvent,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- afterEach,
-} from 'vitest';
+import { render, screen, fireEvent, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, afterEach } from 'vitest';
import MenuItem from '@/app/components/navbar/MenuItem';
describe('
', () => {
@@ -33,9 +22,7 @@ describe('
', () => {
render(
);
const menuItem = screen.getByText('Test Item');
fireEvent.click(menuItem);
- expect(defaultProps.onClick).toHaveBeenCalledTimes(
- 1
- );
+ expect(defaultProps.onClick).toHaveBeenCalledTimes(1);
});
it('has the correct CSS classes', () => {
@@ -45,12 +32,8 @@ describe('
', () => {
expect(menuItem.className).contain('py-3');
expect(menuItem.className).contain('font-semibold');
expect(menuItem.className).contain('transition');
- expect(menuItem.className).contain(
- 'hover:bg-neutral-100'
- );
- expect(menuItem.className).contain(
- 'hover:text-black'
- );
+ expect(menuItem.className).contain('hover:bg-neutral-100');
+ expect(menuItem.className).contain('hover:text-black');
});
it('applies custom label', () => {
diff --git a/__tests__/unit_test/components/navbar/Navbar.test.tsx b/__tests__/unit_test/components/navbar/Navbar.test.tsx
index 5456211..9161a1d 100644
--- a/__tests__/unit_test/components/navbar/Navbar.test.tsx
+++ b/__tests__/unit_test/components/navbar/Navbar.test.tsx
@@ -1,34 +1,18 @@
import React from 'react';
-import {
- render,
- screen,
- fireEvent,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { render, screen, fireEvent, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import Navbar from '@/app/components/navbar/Navbar';
import { SafeUser } from '@/app/types';
// Mock the components used in Navbar
vi.mock('@/app/components/Container', () => ({
- default: ({
- children,
- }: {
- children: React.ReactNode;
- }) =>
{children}
,
+ default: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
}));
vi.mock('@/app/components/navbar/Categories', () => ({
- default: () => (
-
Categories
- ),
+ default: () =>
Categories
,
}));
vi.mock('@/app/components/navbar/Search', () => ({
@@ -45,8 +29,7 @@ vi.mock('@/app/components/navbar/Search', () => ({
vi.mock('@/app/components/navbar/UserMenu', () => ({
default: ({ currentUser }: { currentUser?: any }) => (
- UserMenu:{' '}
- {currentUser ? 'Logged In' : 'Not Logged In'}
+ UserMenu: {currentUser ? 'Logged In' : 'Not Logged In'}
),
}));
@@ -66,24 +49,18 @@ describe('
', () => {
it('renders without crashing', () => {
render(
);
- expect(
- screen.getByTestId('container')
- ).toBeDefined();
+ expect(screen.getByTestId('container')).toBeDefined();
});
it('renders Search and UserMenu components', () => {
render(
);
expect(screen.getByTestId('search')).toBeDefined();
- expect(
- screen.getByTestId('user-menu')
- ).toBeDefined();
+ expect(screen.getByTestId('user-menu')).toBeDefined();
});
it('does not render Categories by default', () => {
render(
);
- expect(
- screen.queryByTestId('categories')
- ).toBeNull();
+ expect(screen.queryByTestId('categories')).toBeNull();
});
it('toggles Categories when Search is clicked', () => {
@@ -91,14 +68,10 @@ describe('
', () => {
const search = screen.getByTestId('search');
fireEvent.click(search);
- expect(
- screen.getByTestId('categories')
- ).toBeDefined();
+ expect(screen.getByTestId('categories')).toBeDefined();
fireEvent.click(search);
- expect(
- screen.queryByTestId('categories')
- ).toBeNull();
+ expect(screen.queryByTestId('categories')).toBeNull();
});
it('passes currentUser to UserMenu', () => {
@@ -117,23 +90,18 @@ describe('
', () => {
verified: false,
};
render(
);
- expect(
- screen.getByText('UserMenu: Logged In')
- ).toBeDefined();
+ expect(screen.getByText('UserMenu: Logged In')).toBeDefined();
});
it('shows UserMenu as not logged in when no user is provided', () => {
render(
);
- expect(
- screen.getByText('UserMenu: Not Logged In')
- ).toBeDefined();
+ expect(screen.getByText('UserMenu: Not Logged In')).toBeDefined();
});
it('applies correct CSS classes', () => {
render(
);
const navbar =
- screen.getByTestId('container').parentElement
- ?.parentElement;
+ screen.getByTestId('container').parentElement?.parentElement;
expect(navbar?.className).include('fixed');
expect(navbar?.className).include('z-10');
expect(navbar?.className).include('w-full');
diff --git a/__tests__/unit_test/components/navbar/Search.test.tsx b/__tests__/unit_test/components/navbar/Search.test.tsx
index 7250f77..fc5c9e6 100644
--- a/__tests__/unit_test/components/navbar/Search.test.tsx
+++ b/__tests__/unit_test/components/navbar/Search.test.tsx
@@ -1,16 +1,5 @@
-import {
- render,
- screen,
- fireEvent,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- afterEach,
-} from 'vitest';
+import { render, screen, fireEvent, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, afterEach } from 'vitest';
import Search from '@/app/components/navbar/Search';
import React from 'react';
@@ -21,11 +10,7 @@ vi.mock('@/app/components/navbar/Logo', () => ({
// Mock the BiSearch icon
vi.mock('react-icons/bi', () => ({
- BiSearch: () => (
-
- Search Icon
-
- ),
+ BiSearch: () =>
Search Icon
,
}));
describe('
', () => {
@@ -35,25 +20,19 @@ describe('
', () => {
it('renders the Logo component', () => {
render(
{}} />);
- expect(
- screen.getByTestId('mock-logo')
- ).toBeDefined();
+ expect(screen.getByTestId('mock-logo')).toBeDefined();
});
it('renders the search icon', () => {
render( {}} />);
- expect(
- screen.getByTestId('mock-search-icon')
- ).toBeDefined();
+ expect(screen.getByTestId('mock-search-icon')).toBeDefined();
});
it('calls onClick handler when search icon is clicked', () => {
const handleClick = vi.fn();
render();
- const searchIcon = screen.getByTestId(
- 'mock-search-icon'
- ).parentElement;
+ const searchIcon = screen.getByTestId('mock-search-icon').parentElement;
fireEvent.click(searchIcon!);
expect(handleClick).toHaveBeenCalledTimes(1);
@@ -62,38 +41,17 @@ describe('', () => {
it('has the correct CSS classes for the search icon container', () => {
render( {}} />);
- const searchIconContainer = screen.getByTestId(
- 'mock-search-icon'
- ).parentElement;
- expect(searchIconContainer?.className).contain(
- 'max-w-[35px]'
- );
- expect(searchIconContainer?.className).contain(
- 'cursor-pointer'
- );
- expect(searchIconContainer?.className).contain(
- 'rounded-full'
- );
- expect(searchIconContainer?.className).contain(
- 'bg-green-450'
- );
- expect(searchIconContainer?.className).contain(
- 'p-2'
- );
- expect(searchIconContainer?.className).contain(
- 'text-white'
- );
- expect(searchIconContainer?.className).contain(
- 'shadow-sm'
- );
- expect(searchIconContainer?.className).contain(
- 'transition'
- );
- expect(searchIconContainer?.className).contain(
- 'hover:shadow-md'
- );
- expect(searchIconContainer?.className).contain(
- 'dark:text-dark'
- );
+ const searchIconContainer =
+ screen.getByTestId('mock-search-icon').parentElement;
+ expect(searchIconContainer?.className).contain('max-w-[35px]');
+ expect(searchIconContainer?.className).contain('cursor-pointer');
+ expect(searchIconContainer?.className).contain('rounded-full');
+ expect(searchIconContainer?.className).contain('bg-green-450');
+ expect(searchIconContainer?.className).contain('p-2');
+ expect(searchIconContainer?.className).contain('text-white');
+ expect(searchIconContainer?.className).contain('shadow-sm');
+ expect(searchIconContainer?.className).contain('transition');
+ expect(searchIconContainer?.className).contain('hover:shadow-md');
+ expect(searchIconContainer?.className).contain('dark:text-dark');
});
});
diff --git a/__tests__/unit_test/components/navbar/UserMenu.test.tsx b/__tests__/unit_test/components/navbar/UserMenu.test.tsx
index fd695fe..5c43e1c 100644
--- a/__tests__/unit_test/components/navbar/UserMenu.test.tsx
+++ b/__tests__/unit_test/components/navbar/UserMenu.test.tsx
@@ -1,17 +1,5 @@
-import {
- render,
- screen,
- fireEvent,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { render, screen, fireEvent, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import UserMenu from '@/app/components/navbar/UserMenu';
import React from 'react';
import { SafeUser } from '@/app/types';
@@ -51,9 +39,7 @@ vi.mock('react-i18next', () => ({
}));
vi.mock('@/app/components/Avatar', () => ({
- default: () => (
- Avatar
- ),
+ default: () => Avatar
,
}));
describe('', () => {
@@ -82,25 +68,15 @@ describe('', () => {
it('renders the post recipe button and avatar when user is logged in', () => {
render();
- expect(
- screen.getByText('post_recipe')
- ).toBeDefined();
- expect(
- screen.getByTestId('mock-avatar')
- ).toBeDefined();
+ expect(screen.getByText('post_recipe')).toBeDefined();
+ expect(screen.getByTestId('mock-avatar')).toBeDefined();
});
it('opens the menu when avatar is clicked', () => {
render();
- fireEvent.click(
- screen.getByTestId('mock-avatar').parentElement!
- );
- expect(
- screen.getByText('my_profile')
- ).toBeDefined();
- expect(
- screen.getByText('my_favorites')
- ).toBeDefined();
+ fireEvent.click(screen.getByTestId('mock-avatar').parentElement!);
+ expect(screen.getByText('my_profile')).toBeDefined();
+ expect(screen.getByText('my_favorites')).toBeDefined();
expect(screen.getByText('settings')).toBeDefined();
expect(screen.getByText('logout')).toBeDefined();
});
@@ -119,18 +95,14 @@ describe('', () => {
it('renders login and signup options when user is not logged in', () => {
render();
- fireEvent.click(
- screen.getByTestId('mock-avatar').parentElement!
- );
+ fireEvent.click(screen.getByTestId('mock-avatar').parentElement!);
expect(screen.getByText('login')).toBeDefined();
expect(screen.getByText('sign_up')).toBeDefined();
});
it('calls signOut when logout is clicked', () => {
render();
- fireEvent.click(
- screen.getByTestId('mock-avatar').parentElement!
- );
+ fireEvent.click(screen.getByTestId('mock-avatar').parentElement!);
fireEvent.click(screen.getByText('logout'));
expect(mockSignOut).toHaveBeenCalled();
});
diff --git a/__tests__/unit_test/components/recipes/DeleteRecipeModal.test.tsx b/__tests__/unit_test/components/recipes/DeleteRecipeModal.test.tsx
index 0af73d9..0c7c02b 100644
--- a/__tests__/unit_test/components/recipes/DeleteRecipeModal.test.tsx
+++ b/__tests__/unit_test/components/recipes/DeleteRecipeModal.test.tsx
@@ -1,18 +1,6 @@
import React from 'react';
-import {
- render,
- screen,
- fireEvent,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { render, screen, fireEvent, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import DeleteRecipeButton from '@/app/components/recipes/DeleteRecipeButton';
// Mock the react-i18next module
@@ -23,38 +11,25 @@ vi.mock('react-i18next', () => ({
}));
// Mock the DeleteRecipeModal component
-vi.mock(
- '@/app/components/modals/DeleteRecipeModal',
- () => ({
- default: ({
- open,
- setIsOpen,
- id,
- }: {
- open: boolean;
- setIsOpen: (open: boolean) => void;
- id: string;
- }) => (
-
- {open && (
-
- Delete Recipe Modal for ID: {id}
-
- )}
-
- ),
- })
-);
-
-// Mock the Button component
-vi.mock('@/app/components/Button', () => ({
+vi.mock('@/app/components/modals/DeleteRecipeModal', () => ({
default: ({
- label,
- onClick,
+ open,
+ setIsOpen,
+ id,
}: {
- label: string;
- onClick: () => void;
+ open: boolean;
+ setIsOpen: (open: boolean) => void;
+ id: string;
}) => (
+
+ {open &&
Delete Recipe Modal for ID: {id}
}
+
+ ),
+}));
+
+// Mock the Button component
+vi.mock('@/app/components/Button', () => ({
+ default: ({ label, onClick }: { label: string; onClick: () => void }) => (
', () => {
const button = screen.getByTestId('delete-button');
fireEvent.click(button);
- const modal = screen.getByTestId(
- 'delete-recipe-modal'
- );
+ const modal = screen.getByTestId('delete-recipe-modal');
expect(modal.textContent).toContain(
`Delete Recipe Modal for ID: ${recipeId}`
);
});
it('initially does not show the DeleteRecipeModal content', () => {
- const modal = screen.getByTestId(
- 'delete-recipe-modal'
- );
+ const modal = screen.getByTestId('delete-recipe-modal');
expect(modal.textContent).not.toContain(
`Delete Recipe Modal for ID: ${recipeId}`
);
diff --git a/__tests__/unit_test/components/recipes/RecipeCard.test.tsx b/__tests__/unit_test/components/recipes/RecipeCard.test.tsx
index ac8412c..3cf1609 100644
--- a/__tests__/unit_test/components/recipes/RecipeCard.test.tsx
+++ b/__tests__/unit_test/components/recipes/RecipeCard.test.tsx
@@ -1,18 +1,6 @@
import React from 'react';
-import {
- render,
- screen,
- fireEvent,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { render, screen, fireEvent, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import RecipeCard from '@/app/components/recipes/RecipeCard';
import { SafeRecipe, SafeUser } from '@/app/types';
import { useRouter } from 'next/navigation';
@@ -85,9 +73,7 @@ describe('', () => {
});
it('renders the recipe title', () => {
- expect(
- screen.getByText('Test Recipe')
- ).toBeDefined();
+ expect(screen.getByText('Test Recipe')).toBeDefined();
});
it('renders the recipe cooking time', () => {
@@ -95,23 +81,17 @@ describe('', () => {
});
it('renders the recipe image', () => {
- const image = screen.getByAltText(
- 'recipe'
- ) as HTMLImageElement;
+ const image = screen.getByAltText('recipe') as HTMLImageElement;
expect(image).toBeDefined();
expect(image.src).toContain('test-image.jpg');
});
it('renders the HeartButton component', () => {
- expect(
- screen.getByTestId('heart-button')
- ).toBeDefined();
+ expect(screen.getByTestId('heart-button')).toBeDefined();
});
it('navigates to the recipe page when clicked', () => {
fireEvent.click(screen.getByText('Test Recipe'));
- expect(mockRouter.push).toHaveBeenCalledWith(
- '/recipes/1'
- );
+ expect(mockRouter.push).toHaveBeenCalledWith('/recipes/1');
});
});
diff --git a/__tests__/unit_test/components/recipes/RecipeCategory.test.tsx b/__tests__/unit_test/components/recipes/RecipeCategory.test.tsx
index 6f55e8a..167ee11 100644
--- a/__tests__/unit_test/components/recipes/RecipeCategory.test.tsx
+++ b/__tests__/unit_test/components/recipes/RecipeCategory.test.tsx
@@ -1,15 +1,5 @@
-import {
- render,
- screen,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- afterEach,
-} from 'vitest';
+import { render, screen, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, afterEach } from 'vitest';
import RecipeCategoryView from '@/app/components/recipes/RecipeCategory';
import { FaUtensils } from 'react-icons/fa';
import React from 'react';
@@ -37,25 +27,17 @@ describe('', () => {
render();
// Check if the icon is rendered
- const iconElement =
- screen.getByTestId('fautensils');
+ const iconElement = screen.getByTestId('fautensils');
expect(iconElement).toBeDefined();
- expect(iconElement.getAttribute('class')).contain(
- 'text-neutral-600'
- );
+ expect(iconElement.getAttribute('class')).contain('text-neutral-600');
// Check if the label is rendered and translated
- const labelElement =
- screen.getByText('MAIN COURSE');
+ const labelElement = screen.getByText('MAIN COURSE');
expect(labelElement).toBeDefined();
- expect(labelElement.className).contain(
- 'text-lg font-semibold'
- );
+ expect(labelElement.className).contain('text-lg font-semibold');
// Check if the description is rendered
- const descriptionElement = screen.getByText(
- defaultProps.description
- );
+ const descriptionElement = screen.getByText(defaultProps.description);
expect(descriptionElement).toBeDefined();
expect(descriptionElement.className).contain(
'font-light text-neutral-500'
@@ -65,12 +47,8 @@ describe('', () => {
it('applies correct styles to the component', () => {
render();
- const containerElement = screen.getByTestId(
- 'recipe-category-view'
- );
- expect(containerElement.className).contain(
- 'flex flex-col gap-6'
- );
+ const containerElement = screen.getByTestId('recipe-category-view');
+ expect(containerElement.className).contain('flex flex-col gap-6');
const innerContainerElement = screen.getByTestId(
'recipe-category-inner'
diff --git a/__tests__/unit_test/components/recipes/RecipeCategoryAndMethod.test.tsx b/__tests__/unit_test/components/recipes/RecipeCategoryAndMethod.test.tsx
index 4daabf6..caf66f0 100644
--- a/__tests__/unit_test/components/recipes/RecipeCategoryAndMethod.test.tsx
+++ b/__tests__/unit_test/components/recipes/RecipeCategoryAndMethod.test.tsx
@@ -1,15 +1,5 @@
-import {
- render,
- screen,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- afterEach,
-} from 'vitest';
+import { render, screen, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, afterEach } from 'vitest';
import RecipeCategoryAndMethod from '@/app/components/recipes/RecipeCategoryAndMethod';
import { FaUtensils, FaFire } from 'react-icons/fa';
@@ -43,12 +33,8 @@ describe('', () => {
render();
- expect(
- screen.getByTestId('recipe-category-and-method')
- ).toBeDefined();
- expect(
- screen.getAllByTestId('mocked-recipe-category')
- ).toHaveLength(2);
+ expect(screen.getByTestId('recipe-category-and-method')).toBeDefined();
+ expect(screen.getAllByTestId('mocked-recipe-category')).toHaveLength(2);
expect(screen.getByText('Italian')).toBeDefined();
expect(screen.getByText('Grilling')).toBeDefined();
});
@@ -65,12 +51,8 @@ describe('', () => {
render();
- expect(
- screen.getByTestId('recipe-category-and-method')
- ).toBeDefined();
- expect(
- screen.getAllByTestId('mocked-recipe-category')
- ).toHaveLength(1);
+ expect(screen.getByTestId('recipe-category-and-method')).toBeDefined();
+ expect(screen.getAllByTestId('mocked-recipe-category')).toHaveLength(1);
expect(screen.getByText('Italian')).toBeDefined();
});
@@ -85,12 +67,8 @@ describe('', () => {
render();
- expect(
- screen.getByTestId('recipe-category-and-method')
- ).toBeDefined();
- expect(
- screen.getAllByTestId('mocked-recipe-category')
- ).toHaveLength(1);
+ expect(screen.getByTestId('recipe-category-and-method')).toBeDefined();
+ expect(screen.getAllByTestId('mocked-recipe-category')).toHaveLength(1);
expect(screen.getByText('Grilling')).toBeDefined();
});
@@ -102,14 +80,10 @@ describe('', () => {
render();
- expect(
- screen.getByTestId('recipe-category-and-method')
- ).toBeDefined();
- expect(
- screen.queryAllByTestId(
- 'mocked-recipe-category'
- )
- ).toHaveLength(0);
+ expect(screen.getByTestId('recipe-category-and-method')).toBeDefined();
+ expect(screen.queryAllByTestId('mocked-recipe-category')).toHaveLength(
+ 0
+ );
});
it('passes correct props to RecipeCategoryView', () => {
@@ -127,14 +101,8 @@ describe('', () => {
render();
- const categoryViews = screen.getAllByTestId(
- 'mocked-recipe-category'
- );
- expect(categoryViews[0].textContent).toBe(
- 'Italian'
- );
- expect(categoryViews[1].textContent).toBe(
- 'Grilling'
- );
+ const categoryViews = screen.getAllByTestId('mocked-recipe-category');
+ expect(categoryViews[0].textContent).toBe('Italian');
+ expect(categoryViews[1].textContent).toBe('Grilling');
});
});
diff --git a/__tests__/unit_test/components/recipes/RecipeHead.test.tsx b/__tests__/unit_test/components/recipes/RecipeHead.test.tsx
index 556e1e0..39e0ba3 100644
--- a/__tests__/unit_test/components/recipes/RecipeHead.test.tsx
+++ b/__tests__/unit_test/components/recipes/RecipeHead.test.tsx
@@ -1,17 +1,5 @@
-import {
- render,
- screen,
- fireEvent,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { render, screen, fireEvent, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import RecipeHead from '@/app/components/recipes/RecipeHead';
import { useRouter } from 'next/navigation';
@@ -49,9 +37,7 @@ describe('', () => {
it('renders the component with correct title and minutes', () => {
render();
- expect(
- screen.getByText('Test Recipe')
- ).toBeDefined();
+ expect(screen.getByText('Test Recipe')).toBeDefined();
expect(screen.getByText('30 min')).toBeDefined();
});
@@ -64,8 +50,7 @@ describe('', () => {
it('changes image when next button is clicked', () => {
render();
- const nextButton =
- screen.getByTestId('next-button');
+ const nextButton = screen.getByTestId('next-button');
const image = screen.getByAltText('Image');
expect(image).toHaveProperty(
'src',
@@ -80,8 +65,7 @@ describe('', () => {
it('changes image when previous button is clicked', () => {
render();
- const prevButton =
- screen.getByTestId('prev-button');
+ const prevButton = screen.getByTestId('prev-button');
const image = screen.getByAltText('Image');
fireEvent.click(prevButton);
expect(image).toHaveProperty(
@@ -100,8 +84,8 @@ describe('', () => {
render();
const shareButton = screen.getByLabelText('Share');
fireEvent.click(shareButton);
- expect(
- mockClipboard.writeText
- ).toHaveBeenCalledWith(window.location.href);
+ expect(mockClipboard.writeText).toHaveBeenCalledWith(
+ window.location.href
+ );
});
});
diff --git a/__tests__/unit_test/components/recipes/RecipeInfo.test.tsx b/__tests__/unit_test/components/recipes/RecipeInfo.test.tsx
index 4ea6ee4..04a0351 100644
--- a/__tests__/unit_test/components/recipes/RecipeInfo.test.tsx
+++ b/__tests__/unit_test/components/recipes/RecipeInfo.test.tsx
@@ -1,17 +1,6 @@
import React from 'react';
-import {
- render,
- screen,
- fireEvent,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- afterEach,
-} from 'vitest';
+import { render, screen, fireEvent, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, afterEach } from 'vitest';
import RecipeInfo from '@/app/components/recipes/RecipeInfo';
import { SafeUser } from '@/app/types';
import * as nextNavigation from 'next/navigation';
@@ -45,17 +34,13 @@ vi.mock('../HeartButton', () => ({
),
}));
-vi.mock(
- '@/app/components/recipes/RecipeCategoryAndMethod',
- () => ({
- default: ({ category, method }: any) => (
-
- Category: {category?.label}, Method:{' '}
- {method?.label}
-
- ),
- })
-);
+vi.mock('@/app/components/recipes/RecipeCategoryAndMethod', () => ({
+ default: ({ category, method }: any) => (
+
+ Category: {category?.label}, Method: {method?.label}
+
+ ),
+}));
describe('RecipeInfo', () => {
const mockUser: SafeUser = {
@@ -96,38 +81,24 @@ describe('RecipeInfo', () => {
it('renders user information correctly', () => {
render();
- expect(
- screen.queryByText(mockUser.name!)
- ).toBeDefined();
+ expect(screen.queryByText(mockUser.name!)).toBeDefined();
expect(screen.getByText('level 5')).toBeDefined();
- expect(
- screen.getByTestId('heart-button')
- ).toBeDefined();
+ expect(screen.getByTestId('heart-button')).toBeDefined();
expect(screen.getByText('10')).toBeDefined();
});
it('displays recipe details correctly', () => {
render();
expect(screen.getByText('2 steps')).toBeDefined();
- expect(
- screen.getByText('2 ingredients')
- ).toBeDefined();
- expect(
- screen.getByText('A delicious recipe')
- ).toBeDefined();
+ expect(screen.getByText('2 ingredients')).toBeDefined();
+ expect(screen.getByText('A delicious recipe')).toBeDefined();
});
it('renders ingredients and steps', () => {
render();
- expect(
- screen.getByText('ingredients')
- ).toBeDefined();
- expect(
- screen.getByText('Ingredient 1')
- ).toBeDefined();
- expect(
- screen.getByText('Ingredient 2')
- ).toBeDefined();
+ expect(screen.getByText('ingredients')).toBeDefined();
+ expect(screen.getByText('Ingredient 1')).toBeDefined();
+ expect(screen.getByText('Ingredient 2')).toBeDefined();
expect(screen.getByText('steps')).toBeDefined();
expect(screen.getByText('Step 1')).toBeDefined();
expect(screen.getByText('Step 2')).toBeDefined();
@@ -135,10 +106,7 @@ describe('RecipeInfo', () => {
it('navigates to user profile when avatar is clicked', () => {
const pushMock = vi.fn();
- vi.spyOn(
- nextNavigation,
- 'useRouter'
- ).mockReturnValue({
+ vi.spyOn(nextNavigation, 'useRouter').mockReturnValue({
push: pushMock,
} as any);
@@ -149,9 +117,7 @@ describe('RecipeInfo', () => {
it('displays verified icon for verified users', () => {
render();
- expect(
- screen.getByTestId('verified-icon')
- ).toBeDefined();
+ expect(screen.getByTestId('verified-icon')).toBeDefined();
});
it('does not display verified icon for unverified users', () => {
@@ -160,15 +126,11 @@ describe('RecipeInfo', () => {
user: { ...mockUser, verified: false },
};
render();
- expect(
- screen.queryByTestId('verified-icon')
- ).toBeNull();
+ expect(screen.queryByTestId('verified-icon')).toBeNull();
});
it('renders RecipeCategoryAndMethod component', () => {
render();
- expect(
- screen.getByTestId('recipe-category-and-method')
- ).toBeDefined();
+ expect(screen.getByTestId('recipe-category-and-method')).toBeDefined();
});
});
diff --git a/__tests__/unit_test/components/settings/ChangeUserImage.test.tsx b/__tests__/unit_test/components/settings/ChangeUserImage.test.tsx
index a1ce7b1..f4c31ee 100644
--- a/__tests__/unit_test/components/settings/ChangeUserImage.test.tsx
+++ b/__tests__/unit_test/components/settings/ChangeUserImage.test.tsx
@@ -5,14 +5,7 @@ import {
waitFor,
cleanup,
} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import ChangeUserImageSelector from '@/app/components/settings/ChangeUserImage';
import axios from 'axios';
import { toast } from 'react-hot-toast';
@@ -43,8 +36,7 @@ vi.mock('next-cloudinary', () => ({
const handleClick = () => {
onUpload({
info: {
- secure_url:
- 'https://example.com/new-image.jpg',
+ secure_url: 'https://example.com/new-image.jpg',
},
});
};
@@ -101,11 +93,7 @@ describe('', () => {
image: null,
},
};
- render(
-
- );
+ render();
const image = screen.getByAltText('Upload');
expect(image).toHaveProperty(
'src',
@@ -120,8 +108,7 @@ describe('', () => {
fireEvent.click(uploadWidget);
await waitFor(() => {
- const saveIcon =
- screen.getByTestId('save-icon');
+ const saveIcon = screen.getByTestId('save-icon');
expect(saveIcon).toBeDefined();
});
});
@@ -135,8 +122,7 @@ describe('', () => {
fireEvent.click(uploadWidget);
await waitFor(() => {
- const saveIcon =
- screen.getByTestId('save-icon');
+ const saveIcon = screen.getByTestId('save-icon');
fireEvent.click(saveIcon);
});
@@ -144,20 +130,15 @@ describe('', () => {
expect(axios.put).toHaveBeenCalledWith(
`/api/userImage/${mockCurrentUser.id}`,
{
- userImage:
- 'https://example.com/new-image.jpg',
+ userImage: 'https://example.com/new-image.jpg',
}
);
- expect(toast.success).toHaveBeenCalledWith(
- 'Image updated!'
- );
+ expect(toast.success).toHaveBeenCalledWith('Image updated!');
});
});
it('handles API error when updating profile', async () => {
- (axios.put as any).mockRejectedValue(
- new Error('API Error')
- );
+ (axios.put as any).mockRejectedValue(new Error('API Error'));
render();
const uploadWidget = screen.getByAltText('Upload');
@@ -165,15 +146,12 @@ describe('', () => {
fireEvent.click(uploadWidget);
await waitFor(() => {
- const saveIcon =
- screen.getByTestId('save-icon');
+ const saveIcon = screen.getByTestId('save-icon');
fireEvent.click(saveIcon);
});
await waitFor(() => {
- expect(toast.error).toHaveBeenCalledWith(
- 'Something went wrong.'
- );
+ expect(toast.error).toHaveBeenCalledWith('Something went wrong.');
});
});
});
diff --git a/__tests__/unit_test/components/settings/EmailNotificationsSelector.test.tsx b/__tests__/unit_test/components/settings/EmailNotificationsSelector.test.tsx
index 4224bf7..ce59512 100644
--- a/__tests__/unit_test/components/settings/EmailNotificationsSelector.test.tsx
+++ b/__tests__/unit_test/components/settings/EmailNotificationsSelector.test.tsx
@@ -5,14 +5,7 @@ import {
waitFor,
cleanup,
} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import EmailNotificationsSelector from '@/app/components/settings/EmailNotificationsSelector';
import axios from 'axios';
import { act } from 'react';
@@ -60,34 +53,22 @@ describe('', () => {
});
it('renders correctly with user preferences', () => {
- render(
-
- );
+ render();
- expect(
- screen.getByText('enable_email_notifications')
- ).toBeDefined();
+ expect(screen.getByText('enable_email_notifications')).toBeDefined();
expect(screen.getByRole('button')).toBeDefined();
});
it('toggles email notifications when button is clicked', async () => {
(axios.put as any).mockResolvedValue({});
- render(
-
- );
+ render();
const button = screen.getByRole('button');
fireEvent.click(button);
await waitFor(() => {
- expect(axios.put).toHaveBeenCalledWith(
- '/api/emailNotifications/1'
- );
+ expect(axios.put).toHaveBeenCalledWith('/api/emailNotifications/1');
expect(toast.success).toHaveBeenCalledWith(
'Email notifications updated!'
);
@@ -95,26 +76,16 @@ describe('', () => {
});
it('shows error toast when API call fails', async () => {
- (axios.put as any).mockRejectedValue(
- new Error('API Error')
- );
+ (axios.put as any).mockRejectedValue(new Error('API Error'));
- render(
-
- );
+ render();
const button = screen.getByRole('button');
fireEvent.click(button);
await waitFor(() => {
- expect(axios.put).toHaveBeenCalledWith(
- '/api/emailNotifications/1'
- );
- expect(toast.error).toHaveBeenCalledWith(
- 'Something went wrong.'
- );
+ expect(axios.put).toHaveBeenCalledWith('/api/emailNotifications/1');
+ expect(toast.error).toHaveBeenCalledWith('Something went wrong.');
});
});
@@ -123,11 +94,7 @@ describe('', () => {
(axios.put as any).mockResolvedValue({});
await act(async () => {
- render(
-
- );
+ render();
});
const button = screen.getByRole('button');
@@ -162,14 +129,10 @@ describe('', () => {
verified: false,
};
const { rerender } = render(
-
+
);
- expect(
- screen.getByTestId('thumb-up-icon')
- ).toBeDefined();
+ expect(screen.getByTestId('thumb-up-icon')).toBeDefined();
const userWithoutNotifications = {
...mockUser,
@@ -191,8 +154,6 @@ describe('', () => {
/>
);
- expect(
- screen.getByTestId('thumb-down-icon')
- ).toBeDefined();
+ expect(screen.getByTestId('thumb-down-icon')).toBeDefined();
});
});
diff --git a/__tests__/unit_test/components/settings/LanguageSelector.test.tsx b/__tests__/unit_test/components/settings/LanguageSelector.test.tsx
index 0eb99d0..9e0b1be 100644
--- a/__tests__/unit_test/components/settings/LanguageSelector.test.tsx
+++ b/__tests__/unit_test/components/settings/LanguageSelector.test.tsx
@@ -1,17 +1,5 @@
-import {
- render,
- screen,
- fireEvent,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { render, screen, fireEvent, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import LanguageSelector from '@/app/components/settings/LanguageSelector';
import i18n from '@/app/i18n';
@@ -45,29 +33,21 @@ describe('', () => {
it('renders correctly with initial language', () => {
render();
- expect(
- screen.getByText('select_your_language')
- ).toBeDefined();
+ expect(screen.getByText('select_your_language')).toBeDefined();
expect(screen.getByRole('combobox')).toBeDefined();
expect(
screen.getByRole('option', {
name: 'Castellano',
})
).toBeDefined();
- expect(
- screen.getByRole('option', { name: 'English' })
- ).toBeDefined();
- expect(
- screen.getByRole('option', { name: 'Català ' })
- ).toBeDefined();
+ expect(screen.getByRole('option', { name: 'English' })).toBeDefined();
+ expect(screen.getByRole('option', { name: 'Català ' })).toBeDefined();
});
it('sets the correct initial value for the select element', () => {
render();
- const select = screen.getByRole(
- 'combobox'
- ) as HTMLSelectElement;
+ const select = screen.getByRole('combobox') as HTMLSelectElement;
expect(select.value).toBe('en');
});
@@ -79,17 +59,13 @@ describe('', () => {
target: { value: 'es' },
});
- expect(i18n.changeLanguage).toHaveBeenCalledWith(
- 'es'
- );
+ expect(i18n.changeLanguage).toHaveBeenCalledWith('es');
});
it('changes select value when language is changed', () => {
render();
- const select = screen.getByRole(
- 'combobox'
- ) as HTMLSelectElement;
+ const select = screen.getByRole('combobox') as HTMLSelectElement;
fireEvent.change(select, {
target: { value: 'ca' },
});
diff --git a/__tests__/unit_test/components/settings/ThemeSelector.test.tsx b/__tests__/unit_test/components/settings/ThemeSelector.test.tsx
index c6c9008..2e56a33 100644
--- a/__tests__/unit_test/components/settings/ThemeSelector.test.tsx
+++ b/__tests__/unit_test/components/settings/ThemeSelector.test.tsx
@@ -1,17 +1,5 @@
-import {
- render,
- screen,
- fireEvent,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { render, screen, fireEvent, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { act } from 'react';
import ThemeSelector from '@/app/components/settings/ThemeSelector';
import { useRouter } from 'next/navigation';
@@ -45,9 +33,7 @@ describe('', () => {
localStorageMock = {};
Object.defineProperty(window, 'localStorage', {
value: {
- getItem: vi.fn(
- (key) => localStorageMock[key]
- ),
+ getItem: vi.fn((key) => localStorageMock[key]),
setItem: vi.fn((key, value) => {
localStorageMock[key] = value;
}),
@@ -67,9 +53,7 @@ describe('', () => {
it('renders correctly with initial light theme', () => {
render();
- expect(
- screen.getByText('dark_theme')
- ).toBeDefined();
+ expect(screen.getByText('dark_theme')).toBeDefined();
expect(screen.getByRole('button')).toBeDefined();
});
@@ -82,13 +66,12 @@ describe('', () => {
fireEvent.click(button);
});
- expect(
- screen.getByText('light_theme')
- ).toBeDefined();
+ expect(screen.getByText('light_theme')).toBeDefined();
expect(localStorageMock['theme']).toBe('dark');
- expect(
- document.documentElement.classList.toggle
- ).toHaveBeenCalledWith('dark', true);
+ expect(document.documentElement.classList.toggle).toHaveBeenCalledWith(
+ 'dark',
+ true
+ );
expect(mockRefresh).toHaveBeenCalled();
});
@@ -99,20 +82,17 @@ describe('', () => {
render();
});
- expect(
- screen.getByText('light_theme')
- ).toBeDefined();
- expect(
- document.documentElement.classList.toggle
- ).toHaveBeenCalledWith('dark', true);
+ expect(screen.getByText('light_theme')).toBeDefined();
+ expect(document.documentElement.classList.toggle).toHaveBeenCalledWith(
+ 'dark',
+ true
+ );
});
it('uses translation for theme text', () => {
render();
- expect(mockTranslate).toHaveBeenCalledWith(
- 'dark_theme'
- );
+ expect(mockTranslate).toHaveBeenCalledWith('dark_theme');
});
it('toggles theme multiple times', async () => {
@@ -124,18 +104,14 @@ describe('', () => {
fireEvent.click(button);
});
- expect(
- screen.getByText('light_theme')
- ).toBeDefined();
+ expect(screen.getByText('light_theme')).toBeDefined();
expect(localStorageMock['theme']).toBe('dark');
await act(async () => {
fireEvent.click(button);
});
- expect(
- screen.getByText('dark_theme')
- ).toBeDefined();
+ expect(screen.getByText('dark_theme')).toBeDefined();
expect(localStorageMock['theme']).toBe('light');
});
});
diff --git a/__tests__/unit_test/pages/favorites/FavoritesClient.test.tsx b/__tests__/unit_test/pages/favorites/FavoritesClient.test.tsx
index 352468c..575f649 100644
--- a/__tests__/unit_test/pages/favorites/FavoritesClient.test.tsx
+++ b/__tests__/unit_test/pages/favorites/FavoritesClient.test.tsx
@@ -2,13 +2,7 @@ import React from 'react';
import { render, cleanup } from '@testing-library/react';
import FavoritesClient from '@/app/favorites/FavoritesClient';
import { SafeRecipe, SafeUser } from '@/app/types';
-import {
- vi,
- it,
- describe,
- expect,
- afterEach,
-} from 'vitest';
+import { vi, it, describe, expect, afterEach } from 'vitest';
// Mock the useRouter hook
vi.mock('next/navigation', () => ({
@@ -105,8 +99,6 @@ describe('FavoritesClient', () => {
);
// Assert that no recipes are displayed
- expect(
- container.querySelector('.grid')?.nodeValue
- ).toBeNull();
+ expect(container.querySelector('.grid')?.nodeValue).toBeNull();
});
});
diff --git a/__tests__/unit_test/pages/favorites/page.test.tsx b/__tests__/unit_test/pages/favorites/page.test.tsx
index 02bb2f5..719f23c 100644
--- a/__tests__/unit_test/pages/favorites/page.test.tsx
+++ b/__tests__/unit_test/pages/favorites/page.test.tsx
@@ -24,13 +24,9 @@ describe('FavoritesPage', () => {
const { getByText } = render(page);
await waitFor(() => {
+ expect(getByText('No favorites found')).toBeDefined();
expect(
- getByText('No favorites found')
- ).toBeDefined();
- expect(
- getByText(
- 'Looks like you have no favorite recipes.'
- )
+ getByText('Looks like you have no favorite recipes.')
).toBeDefined();
});
});
@@ -60,12 +56,8 @@ describe('FavoritesPage', () => {
createdAt: new Date().toISOString(),
};
- (getFavoriteRecipes as any).mockResolvedValue(
- mockFavoriteRecipes
- );
- (getCurrentUser as any).mockResolvedValue(
- mockCurrentUser
- );
+ (getFavoriteRecipes as any).mockResolvedValue(mockFavoriteRecipes);
+ (getCurrentUser as any).mockResolvedValue(mockCurrentUser);
const page = await FavoritesPage();
const { getByText } = render(page);
diff --git a/__tests__/unit_test/pages/home/layout.test.tsx b/__tests__/unit_test/pages/home/layout.test.tsx
index 27924cf..d899011 100644
--- a/__tests__/unit_test/pages/home/layout.test.tsx
+++ b/__tests__/unit_test/pages/home/layout.test.tsx
@@ -1,13 +1,6 @@
import React from 'react';
import { render, cleanup } from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import RootLayout from '@/app/layout';
// Mocks
@@ -20,33 +13,21 @@ vi.mock('@/app/components/navbar/Navbar', () => ({
}));
vi.mock('@/app/components/ClientOnly', () => ({
- default: ({
- children,
- }: {
- children: React.ReactNode;
- }) => {children}
,
+ default: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
}));
vi.mock('@/app/components/modals/RegisterModal', () => ({
- default: () => (
-
- RegisterModal
-
- ),
+ default: () => RegisterModal
,
}));
vi.mock('@/app/providers/ToasterProvider', () => ({
- default: () => (
-
- ToasterProvider
-
- ),
+ default: () => ToasterProvider
,
}));
vi.mock('@/app/components/modals/LoginModal', () => ({
- default: () => (
- LoginModal
- ),
+ default: () => LoginModal
,
}));
vi.mock('@/app/actions/getCurrentUser', () => ({
@@ -54,17 +35,11 @@ vi.mock('@/app/actions/getCurrentUser', () => ({
}));
vi.mock('@/app/components/modals/RecipeModal', () => ({
- default: () => (
- RecipeModal
- ),
+ default: () => RecipeModal
,
}));
vi.mock('@/app/components/modals/SettingsModal', () => ({
- default: () => (
-
- SettingsModal
-
- ),
+ default: () => SettingsModal
,
}));
vi.mock('@/app/components/Footer', () => ({
@@ -81,12 +56,8 @@ describe('RootLayout', () => {
});
it('renders the layout correctly', async () => {
- const getCurrentUserMock = await import(
- '@/app/actions/getCurrentUser'
- );
- vi.mocked(
- getCurrentUserMock.default
- ).mockResolvedValue({
+ const getCurrentUserMock = await import('@/app/actions/getCurrentUser');
+ vi.mocked(getCurrentUserMock.default).mockResolvedValue({
id: '1',
name: 'Test User',
} as any);
@@ -94,49 +65,26 @@ describe('RootLayout', () => {
const layout = await RootLayout({
children: Test Content
,
});
- const {
- findByTestId,
- findAllByTestId,
- findByText,
- } = render(layout);
+ const { findByTestId, findAllByTestId, findByText } = render(layout);
// Check if all components are rendered
expect(await findByTestId('navbar')).toBeDefined();
- const clientOnlyComponents =
- await findAllByTestId('client-only');
- expect(clientOnlyComponents.length).toBeGreaterThan(
- 0
- );
- expect(
- await findByTestId('register-modal')
- ).toBeDefined();
- expect(
- await findByTestId('toaster-provider')
- ).toBeDefined();
- expect(
- await findByTestId('login-modal')
- ).toBeDefined();
- expect(
- await findByTestId('recipe-modal')
- ).toBeDefined();
- expect(
- await findByTestId('settings-modal')
- ).toBeDefined();
+ const clientOnlyComponents = await findAllByTestId('client-only');
+ expect(clientOnlyComponents.length).toBeGreaterThan(0);
+ expect(await findByTestId('register-modal')).toBeDefined();
+ expect(await findByTestId('toaster-provider')).toBeDefined();
+ expect(await findByTestId('login-modal')).toBeDefined();
+ expect(await findByTestId('recipe-modal')).toBeDefined();
+ expect(await findByTestId('settings-modal')).toBeDefined();
expect(await findByTestId('footer')).toBeDefined();
// Check if the children are rendered
- expect(
- await findByText('Test Content')
- ).toBeDefined();
+ expect(await findByText('Test Content')).toBeDefined();
});
it('applies the correct CSS classes', async () => {
- const getCurrentUserMock = await import(
- '@/app/actions/getCurrentUser'
- );
- vi.mocked(
- getCurrentUserMock.default
- ).mockResolvedValue(null);
+ const getCurrentUserMock = await import('@/app/actions/getCurrentUser');
+ vi.mocked(getCurrentUserMock.default).mockResolvedValue(null);
const layout = await RootLayout({
children: Test Content
,
@@ -144,30 +92,21 @@ describe('RootLayout', () => {
const { container } = render(layout);
const body = container.querySelector('body');
- expect(body?.className).include(
- 'mocked-font-class'
- );
+ expect(body?.className).include('mocked-font-class');
expect(body?.className).include('dark:bg-dark');
});
it('wraps children in a div with correct classes', async () => {
- const getCurrentUserMock = await import(
- '@/app/actions/getCurrentUser'
- );
- vi.mocked(
- getCurrentUserMock.default
- ).mockResolvedValue(null);
+ const getCurrentUserMock = await import('@/app/actions/getCurrentUser');
+ vi.mocked(getCurrentUserMock.default).mockResolvedValue(null);
const layout = await RootLayout({
children: Test Content
,
});
const { container } = render(layout);
- const contentWrapper =
- container.querySelector('.pb-20.pt-28');
+ const contentWrapper = container.querySelector('.pb-20.pt-28');
expect(contentWrapper).toBeDefined();
- expect(contentWrapper?.textContent).toBe(
- 'Test Content'
- );
+ expect(contentWrapper?.textContent).toBe('Test Content');
});
});
diff --git a/__tests__/unit_test/pages/home/page.test.tsx b/__tests__/unit_test/pages/home/page.test.tsx
index 7f13b88..ff225e4 100644
--- a/__tests__/unit_test/pages/home/page.test.tsx
+++ b/__tests__/unit_test/pages/home/page.test.tsx
@@ -1,49 +1,31 @@
import React from 'react';
import { render } from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
-} from 'vitest';
+import { describe, it, expect, vi, beforeEach } from 'vitest';
import Home from '@/app/page';
// Mocks
vi.mock('@/app/components/Container', () => ({
- default: ({
- children,
- }: {
- children: React.ReactNode;
- }) => {children}
,
+ default: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
}));
vi.mock('@/app/components/recipes/RecipeCard', () => ({
default: ({ data }: { data: any }) => (
-
- {data.title}
-
+ {data.title}
),
}));
vi.mock('@/app/components/EmptyState', () => ({
- default: () => (
- Empty State
- ),
+ default: () => Empty State
,
}));
vi.mock('@/app/components/Pagination', () => ({
- default: () => (
- Pagination
- ),
+ default: () => Pagination
,
}));
vi.mock('@/app/components/ClientOnly', () => ({
- default: ({
- children,
- }: {
- children: React.ReactNode;
- }) => <>{children}>,
+ default: ({ children }: { children: React.ReactNode }) => <>{children}>,
}));
vi.mock('@/app/utils/deviceDetector', () => ({
@@ -98,91 +80,53 @@ describe('Home', () => {
});
it('renders recipes when available', async () => {
- const getRecipesMock = await import(
- '@/app/actions/getRecipes'
- );
- vi.mocked(getRecipesMock.default).mockResolvedValue(
- {
- recipes: mockRecipes,
- totalPages: 1,
- currentPage: 1,
- totalRecipes: 2,
- }
- );
-
- const getCurrentUserMock = await import(
- '@/app/actions/getCurrentUser'
- );
- vi.mocked(
- getCurrentUserMock.default
- ).mockResolvedValue({} as any);
-
- const { findByTestId } = render(
- await Home({ searchParams: {} })
- );
-
- expect(
- await findByTestId('container')
- ).toBeDefined();
- expect(
- await findByTestId('recipe-card-1')
- ).toBeDefined();
- expect(
- await findByTestId('recipe-card-2')
- ).toBeDefined();
- expect(
- await findByTestId('pagination')
- ).toBeDefined();
+ const getRecipesMock = await import('@/app/actions/getRecipes');
+ vi.mocked(getRecipesMock.default).mockResolvedValue({
+ recipes: mockRecipes,
+ totalPages: 1,
+ currentPage: 1,
+ totalRecipes: 2,
+ });
+
+ const getCurrentUserMock = await import('@/app/actions/getCurrentUser');
+ vi.mocked(getCurrentUserMock.default).mockResolvedValue({} as any);
+
+ const { findByTestId } = render(await Home({ searchParams: {} }));
+
+ expect(await findByTestId('container')).toBeDefined();
+ expect(await findByTestId('recipe-card-1')).toBeDefined();
+ expect(await findByTestId('recipe-card-2')).toBeDefined();
+ expect(await findByTestId('pagination')).toBeDefined();
});
it('renders empty state when no recipes', async () => {
- const getRecipesMock = await import(
- '@/app/actions/getRecipes'
- );
- vi.mocked(getRecipesMock.default).mockResolvedValue(
- {
- recipes: [],
- totalPages: 0,
- currentPage: 1,
- totalRecipes: 0,
- }
- );
+ const getRecipesMock = await import('@/app/actions/getRecipes');
+ vi.mocked(getRecipesMock.default).mockResolvedValue({
+ recipes: [],
+ totalPages: 0,
+ currentPage: 1,
+ totalRecipes: 0,
+ });
- const getCurrentUserMock = await import(
- '@/app/actions/getCurrentUser'
- );
- vi.mocked(
- getCurrentUserMock.default
- ).mockResolvedValue(null);
+ const getCurrentUserMock = await import('@/app/actions/getCurrentUser');
+ vi.mocked(getCurrentUserMock.default).mockResolvedValue(null);
- const { findByTestId } = render(
- await Home({ searchParams: {} })
- );
+ const { findByTestId } = render(await Home({ searchParams: {} }));
- expect(
- await findByTestId('empty-state')
- ).toBeDefined();
+ expect(await findByTestId('empty-state')).toBeDefined();
});
it('uses mobile limit when on mobile device', async () => {
- const deviceDetectorMock = await import(
- '@/app/utils/deviceDetector'
- );
- vi.mocked(
- deviceDetectorMock.isMobile
- ).mockReturnValue(true);
+ const deviceDetectorMock = await import('@/app/utils/deviceDetector');
+ vi.mocked(deviceDetectorMock.isMobile).mockReturnValue(true);
- const getRecipesMock = await import(
- '@/app/actions/getRecipes'
- );
- vi.mocked(getRecipesMock.default).mockResolvedValue(
- {
- recipes: mockRecipes,
- totalPages: 1,
- currentPage: 1,
- totalRecipes: 2,
- }
- );
+ const getRecipesMock = await import('@/app/actions/getRecipes');
+ vi.mocked(getRecipesMock.default).mockResolvedValue({
+ recipes: mockRecipes,
+ totalPages: 1,
+ currentPage: 1,
+ totalRecipes: 2,
+ });
await Home({ searchParams: {} });
@@ -192,24 +136,16 @@ describe('Home', () => {
});
it('uses desktop limit when not on mobile device', async () => {
- const deviceDetectorMock = await import(
- '@/app/utils/deviceDetector'
- );
- vi.mocked(
- deviceDetectorMock.isMobile
- ).mockReturnValue(false);
-
- const getRecipesMock = await import(
- '@/app/actions/getRecipes'
- );
- vi.mocked(getRecipesMock.default).mockResolvedValue(
- {
- recipes: mockRecipes,
- totalPages: 1,
- currentPage: 1,
- totalRecipes: 2,
- }
- );
+ const deviceDetectorMock = await import('@/app/utils/deviceDetector');
+ vi.mocked(deviceDetectorMock.isMobile).mockReturnValue(false);
+
+ const getRecipesMock = await import('@/app/actions/getRecipes');
+ vi.mocked(getRecipesMock.default).mockResolvedValue({
+ recipes: mockRecipes,
+ totalPages: 1,
+ currentPage: 1,
+ totalRecipes: 2,
+ });
await Home({ searchParams: {} });
diff --git a/__tests__/unit_test/pages/profile/ProfileClient.test.tsx b/__tests__/unit_test/pages/profile/ProfileClient.test.tsx
index 579bbf3..b16a5fd 100644
--- a/__tests__/unit_test/pages/profile/ProfileClient.test.tsx
+++ b/__tests__/unit_test/pages/profile/ProfileClient.test.tsx
@@ -1,13 +1,7 @@
import React from 'react';
import { render, cleanup } from '@testing-library/react';
import ProfileClient from '@/app/profile/[userId]/ProfileClient';
-import {
- describe,
- it,
- expect,
- vi,
- afterEach,
-} from 'vitest';
+import { describe, it, expect, vi, afterEach } from 'vitest';
import { SafeRecipe, SafeUser } from '@/app/types';
vi.mock('next/navigation', () => ({
@@ -104,8 +98,6 @@ describe('ProfileClient', () => {
);
// Assert that no recipes are displayed
- expect(
- container.querySelector('.grid')?.nodeValue
- ).toBeNull();
+ expect(container.querySelector('.grid')?.nodeValue).toBeNull();
});
});
diff --git a/__tests__/unit_test/pages/profile/ProfileHeader.test.tsx b/__tests__/unit_test/pages/profile/ProfileHeader.test.tsx
index 817b2d6..6cbdc76 100644
--- a/__tests__/unit_test/pages/profile/ProfileHeader.test.tsx
+++ b/__tests__/unit_test/pages/profile/ProfileHeader.test.tsx
@@ -1,16 +1,6 @@
import React from 'react';
-import {
- render,
- fireEvent,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- afterEach,
-} from 'vitest';
+import { render, fireEvent, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, afterEach } from 'vitest';
import ProfileHeader from '@/app/profile/[userId]/ProfileHeader';
import { SafeUser } from '@/app/types';
import { useRouter } from 'next/navigation';
@@ -60,26 +50,20 @@ describe('ProfileHeader', () => {
expect(getByText('Test User')).toBeDefined();
expect(getByText('level 5')).toBeDefined();
expect(getByAltText('Avatar')).toBeDefined();
- expect(
- getByText('Test User').closest('svg')
- ).toBeDefined();
+ expect(getByText('Test User').closest('svg')).toBeDefined();
});
it('calls router.push on user name click', () => {
const router = { push: vi.fn() };
(useRouter as any).mockReturnValue(router);
- const { getByText } = render(
-
- );
+ const { getByText } = render();
// Simulate click on user name
fireEvent.click(getByText('Test User'));
// Assert router.push is called with correct URL
- expect(router.push).toHaveBeenCalledWith(
- '/profile/user1'
- );
+ expect(router.push).toHaveBeenCalledWith('/profile/user1');
});
it('renders correctly with unverified user', () => {
diff --git a/__tests__/unit_test/pages/profile/page.test.tsx b/__tests__/unit_test/pages/profile/page.test.tsx
index 06a85ce..b45bfda 100644
--- a/__tests__/unit_test/pages/profile/page.test.tsx
+++ b/__tests__/unit_test/pages/profile/page.test.tsx
@@ -1,17 +1,6 @@
import React from 'react';
-import {
- cleanup,
- render,
- waitFor,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { cleanup, render, waitFor } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import ProfilePage from '@/app/profile/[userId]/page';
import getRecipesByUserId from '@/app/actions/getRecipesByUserId';
import getUserById from '@/app/actions/getUserById';
@@ -82,13 +71,9 @@ describe('ProfilePage', () => {
const { getByText } = render(profilePage);
await waitFor(() => {
+ expect(getByText('No recipes found')).toBeDefined();
expect(
- getByText('No recipes found')
- ).toBeDefined();
- expect(
- getByText(
- 'Looks like this user has not created recipes.'
- )
+ getByText('Looks like this user has not created recipes.')
).toBeDefined();
});
});
@@ -147,12 +132,8 @@ describe('ProfilePage', () => {
await waitFor(() => {
expect(getByText('Test User')).toBeDefined();
- expect(
- getByText('Test Recipe 1')
- ).toBeDefined();
- expect(
- getByText('Test Recipe 2')
- ).toBeDefined();
+ expect(getByText('Test Recipe 1')).toBeDefined();
+ expect(getByText('Test Recipe 2')).toBeDefined();
});
});
@@ -194,13 +175,9 @@ describe('ProfilePage', () => {
await waitFor(() => {
expect(getByText('Test User')).toBeDefined();
+ expect(getByText('No recipes found')).toBeDefined();
expect(
- getByText('No recipes found')
- ).toBeDefined();
- expect(
- getByText(
- 'Looks like this user has not created recipes.'
- )
+ getByText('Looks like this user has not created recipes.')
).toBeDefined();
});
});
diff --git a/__tests__/unit_test/pages/recipes/RecipeClient.test.tsx b/__tests__/unit_test/pages/recipes/RecipeClient.test.tsx
index a9b2712..c814f2f 100644
--- a/__tests__/unit_test/pages/recipes/RecipeClient.test.tsx
+++ b/__tests__/unit_test/pages/recipes/RecipeClient.test.tsx
@@ -1,25 +1,9 @@
// RecipeClient.test.tsx
import React from 'react';
-import {
- render,
- fireEvent,
- waitFor,
- cleanup,
-} from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
-} from 'vitest';
+import { render, fireEvent, waitFor, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import RecipeClient from '@/app/recipes/[recipeId]/RecipeClient';
-import {
- SafeUser,
- SafeRecipe,
- SafeComment,
-} from '@/app/types';
+import { SafeUser, SafeRecipe, SafeComment } from '@/app/types';
// Mocks
vi.mock('axios');
@@ -139,29 +123,24 @@ describe('RecipeClient', () => {
const axios = await import('axios');
vi.mocked(axios.default.post).mockResolvedValue({});
- const { getByPlaceholderText, getByTestId } =
- render(
-
- );
-
- fireEvent.change(
- getByPlaceholderText('write_comment'),
- { target: { value: 'New comment' } }
+ const { getByPlaceholderText, getByTestId } = render(
+
);
+
+ fireEvent.change(getByPlaceholderText('write_comment'), {
+ target: { value: 'New comment' },
+ });
fireEvent.click(getByTestId('submit-comment'));
await waitFor(() => {
- expect(axios.default.post).toHaveBeenCalledWith(
- '/api/comments',
- {
- comment: 'New comment',
- recipeId: '1',
- }
- );
+ expect(axios.default.post).toHaveBeenCalledWith('/api/comments', {
+ comment: 'New comment',
+ recipeId: '1',
+ });
});
});
});
diff --git a/__tests__/unit_test/pages/recipes/page.test.tsx b/__tests__/unit_test/pages/recipes/page.test.tsx
index f734bc2..906f77c 100644
--- a/__tests__/unit_test/pages/recipes/page.test.tsx
+++ b/__tests__/unit_test/pages/recipes/page.test.tsx
@@ -1,12 +1,6 @@
import React from 'react';
import { render } from '@testing-library/react';
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
-} from 'vitest';
+import { describe, it, expect, vi, beforeEach } from 'vitest';
import RecipePage from '@/app/recipes/[recipeId]/page';
// Mocks
@@ -30,23 +24,17 @@ vi.mock('next/navigation', () => ({
}));
vi.mock('@/app/components/ClientOnly', () => ({
- default: ({
- children,
- }: {
- children: React.ReactNode;
- }) => {children}
,
+ default: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
}));
vi.mock('@/app/components/EmptyState', () => ({
- default: () => (
- Empty State
- ),
+ default: () => Empty State
,
}));
vi.mock('@/app/recipes/[recipeId]/RecipeClient', () => ({
- default: () => (
- Recipe Client
- ),
+ default: () => Recipe Client
,
}));
describe('RecipePage', () => {
@@ -55,53 +43,39 @@ describe('RecipePage', () => {
});
it('renders EmptyState when recipe is not found', async () => {
- const getRecipeByIdMock = await import(
- '@/app/actions/getRecipeById'
- );
- vi.mocked(
- getRecipeByIdMock.default
- ).mockResolvedValue(null);
+ const getRecipeByIdMock = await import('@/app/actions/getRecipeById');
+ vi.mocked(getRecipeByIdMock.default).mockResolvedValue(null);
const { findByTestId } = render(
await RecipePage({ params: { recipeId: '1' } })
);
- expect(
- await findByTestId('empty-state')
- ).toBeDefined();
+ expect(await findByTestId('empty-state')).toBeDefined();
});
it('renders RecipeClient when recipe is found', async () => {
- const getRecipeByIdMock = await import(
- '@/app/actions/getRecipeById'
- );
- vi.mocked(
- getRecipeByIdMock.default
- ).mockResolvedValue({
+ const getRecipeByIdMock = await import('@/app/actions/getRecipeById');
+ vi.mocked(getRecipeByIdMock.default).mockResolvedValue({
id: '1',
title: 'Test Recipe',
} as any);
- const getCurrentUserMock = await import(
- '@/app/actions/getCurrentUser'
- );
- vi.mocked(
- getCurrentUserMock.default
- ).mockResolvedValue({ id: 'user1' } as any);
+ const getCurrentUserMock = await import('@/app/actions/getCurrentUser');
+ vi.mocked(getCurrentUserMock.default).mockResolvedValue({
+ id: 'user1',
+ } as any);
const getCommentsByRecipeIdMock = await import(
'@/app/actions/getCommentsByRecipeId'
);
- vi.mocked(
- getCommentsByRecipeIdMock.default
- ).mockResolvedValue([{ id: 'comment1' }] as any);
+ vi.mocked(getCommentsByRecipeIdMock.default).mockResolvedValue([
+ { id: 'comment1' },
+ ] as any);
const { findByTestId } = render(
await RecipePage({ params: { recipeId: '1' } })
);
- expect(
- await findByTestId('recipe-client')
- ).toBeDefined();
+ expect(await findByTestId('recipe-client')).toBeDefined();
});
});
diff --git a/__tests__/unit_test/routes/recipes.test.ts b/__tests__/unit_test/routes/recipes.test.ts
new file mode 100644
index 0000000..6636a92
--- /dev/null
+++ b/__tests__/unit_test/routes/recipes.test.ts
@@ -0,0 +1,309 @@
+import getRecipes from '@/app/actions/getRecipes';
+import { Session } from 'next-auth';
+import { POST as RecipePOST } from '@/app/api/recipes/route';
+import { DELETE as RecipeDELETE } from '@/app/api/recipe/[recipeId]/route';
+import getRecipeById from '@/app/actions/getRecipeById';
+import getCurrentUser from '@/app/actions/getCurrentUser';
+import getRecipesByUserId from '@/app/actions/getRecipesByUserId';
+import { POST as FavoritePOST } from '@/app/api/favorites/[recipeId]/route';
+import { DELETE as FavoriteDELETE } from '@/app/api/favorites/[recipeId]/route';
+import getFavoriteRecipes from '@/app/actions/getFavoriteRecipes';
+import { POST as CommentPOST } from '@/app/api/comments/route';
+import { DELETE as CommentDELETE } from '@/app/api/comments/[commentId]/route';
+import getCommentsByRecipeId from '@/app/actions/getCommentsByRecipeId';
+import getCommentById from '@/app/actions/getCommentById';
+
+let mockedSession: Session | null = null;
+
+// This mocks our custom helper function to avoid passing authOptions around
+jest.mock('@/pages/api/auth/[...nextauth].ts', () => ({
+ authOptions: {
+ adapter: {},
+ providers: [],
+ callbacks: {},
+ },
+}));
+
+// This mocks calls to getServerSession
+jest.mock('next-auth/next', () => ({
+ getServerSession: jest.fn(() => {
+ return Promise.resolve(mockedSession);
+ }),
+}));
+
+describe('Recipes API Routes and Server Actions', () => {
+ let initialRecipes: {
+ createdAt: string;
+ id: string;
+ title: string;
+ description: string;
+ imageSrc: string;
+ category: string;
+ method: string;
+ minutes: number;
+ numLikes: number;
+ ingredients: string[];
+ steps: string[];
+ extraImages: string[];
+ userId: string;
+ }[] = [];
+ let publishedRecipe: {
+ id: string;
+ title: string;
+ description: string;
+ imageSrc: string;
+ createdAt: Date;
+ category: string;
+ method: string;
+ minutes: number;
+ numLikes: number;
+ ingredients: string[];
+ steps: string[];
+ extraImages: string[];
+ userId: string;
+ } | null = null;
+
+ let initialUser: {
+ createdAt: string;
+ updatedAt: string;
+ emailVerified: string | null;
+ id: string;
+ name: string | null;
+ email: string | null;
+ image: string | null;
+ hashedPassword: string | null;
+ favoriteIds: string[];
+ emailNotifications: boolean;
+ level: number;
+ verified: boolean;
+ } | null = null;
+
+ let comment: {
+ id: string;
+ userId: string;
+ comment: string;
+ recipeId: string;
+ createdAt: string;
+ } | null = null;
+
+ beforeEach(() => {
+ mockedSession = {
+ expires: 'expires',
+ user: {
+ name: 'test',
+ email: 'test@a.com',
+ },
+ };
+ });
+
+ it('should return the current recipes and the current user', async () => {
+ const response = await getRecipes({});
+ initialRecipes = response.recipes;
+ const currentUser = await getCurrentUser();
+ initialUser = currentUser;
+ });
+
+ it('should create a new recipe', async () => {
+ const mockRecipe = {
+ title: 'Test Recipe',
+ description: 'Delicious test recipe',
+ imageSrc: `https://res.cloudinary.com/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/image/upload/v1721469287/IMG_7717_xedos6.webp`,
+ category: 'Desserts',
+ method: 'Oven',
+ ingredients: ['sugar', 'flour'],
+ steps: ['Mix ingredients', 'Bake at 350F'],
+ minutes: 30,
+ };
+ const mockRequest = {
+ json: jest.fn().mockResolvedValue(mockRecipe),
+ } as unknown as Request;
+
+ const response = await RecipePOST(mockRequest);
+ const result = await response.json();
+ expect(result).toMatchObject(mockRecipe);
+ publishedRecipe = result as {
+ id: string;
+ title: string;
+ description: string;
+ imageSrc: string;
+ createdAt: Date;
+ category: string;
+ method: string;
+ minutes: number;
+ numLikes: number;
+ ingredients: string[];
+ steps: string[];
+ extraImages: string[];
+ userId: string;
+ };
+ });
+
+ it('should return the updated recipes', async () => {
+ const response = await getRecipes({});
+ expect(response.recipes.length).toBeGreaterThan(initialRecipes.length);
+ });
+
+ it('should level up the recipe user level', async () => {
+ const currentUser = await getCurrentUser();
+ if (!currentUser || !initialUser) {
+ console.log({
+ currentUser,
+ initialUser,
+ });
+ throw new Error('currentUser or initialUser is not defined');
+ }
+ expect(currentUser.level).toBeGreaterThan(initialUser.level);
+ });
+
+ it('should return the user recipes', async () => {
+ if (!initialUser) {
+ throw new Error('initialUser is not defined');
+ }
+ const mockParams = { userId: initialUser.id };
+ const response = await getRecipesByUserId(mockParams);
+ expect(response.length).toBeGreaterThan(0);
+ });
+
+ it('should return the recipes filtered by category', async () => {
+ const response = await getRecipes({ category: 'Desserts' });
+ expect(response.recipes.length).toBeGreaterThan(0);
+ });
+
+ it('should return the recipe by id', async () => {
+ const response = await getRecipeById({ recipeId: publishedRecipe?.id });
+ expect(response).toMatchObject(publishedRecipe || {});
+ });
+
+ it('should like the recipe', async () => {
+ const mockParams = { params: { recipeId: publishedRecipe?.id } };
+ const responseFav = await FavoritePOST(
+ {} as unknown as Request,
+ mockParams
+ );
+ const resultFav = await responseFav.json();
+ const currentUser = await getCurrentUser();
+ expect(resultFav).toMatchObject(currentUser || {});
+ });
+
+ it('should have added the recipe id to the current user favoriteIds', async () => {
+ const currentUser = await getCurrentUser();
+ expect(currentUser?.favoriteIds).toContain(publishedRecipe?.id);
+ });
+
+ it('should return favorites recipes from the current user with the return', async () => {
+ const response = await getFavoriteRecipes();
+ expect(
+ response.filter((recipe) => recipe.id == publishedRecipe?.id).length
+ ).toBe(1);
+ });
+
+ it('should undo the like of the recipe', async () => {
+ const mockParams = { params: { recipeId: publishedRecipe?.id } };
+ const responseFav = await FavoriteDELETE(
+ {} as unknown as Request,
+ mockParams
+ );
+ const resultFav = await responseFav.json();
+ const currentUser = await getCurrentUser();
+ expect(resultFav).toMatchObject(currentUser || {});
+ });
+
+ it('should have removed the recipe id to the current user favoriteIds', async () => {
+ const currentUser = await getCurrentUser();
+ expect(currentUser?.favoriteIds).not.toContain(publishedRecipe?.id);
+ });
+
+ it('should return favorites recipes from the current user without the recipe id', async () => {
+ const response = await getFavoriteRecipes();
+ expect(
+ response.filter((recipe) => recipe.id == publishedRecipe?.id).length
+ ).toBe(0);
+ });
+
+ it('should comment the recipe', async () => {
+ const mockBody = {
+ recipeId: publishedRecipe?.id,
+ comment: 'Great recipe!',
+ };
+ const mockRequest = {
+ json: jest.fn().mockResolvedValue(mockBody),
+ } as unknown as Request;
+ const response = await CommentPOST(mockRequest);
+ const result = await response.json();
+ expect(result).toMatchObject(publishedRecipe || {});
+ expect(result.comments.length).toBe(1);
+ expect(result.comments[0].recipeId).toBe(publishedRecipe?.id);
+ const currentUser = await getCurrentUser();
+ expect(result.comments[0].userId).toBe(currentUser?.id);
+ expect(result.comments[0].comment).toBe(mockBody.comment);
+ comment = result.comments[0];
+ });
+
+ it('should return comment by comment id', async () => {
+ if (!comment) {
+ throw new Error('comment is not defined');
+ }
+ const response = await getCommentById({ commentId: comment?.id });
+ expect(response).toMatchObject(comment);
+ });
+
+ it('should return comments by recipe id', async () => {
+ if (!comment) {
+ throw new Error('comment is not defined');
+ }
+ const response = await getCommentsByRecipeId({
+ recipeId: publishedRecipe?.id,
+ });
+ expect(response[0]).toMatchObject(comment);
+ });
+
+ it('should delete the comment', async () => {
+ if (!comment) {
+ throw new Error('comment is not defined');
+ }
+ const mockParams = { params: { commentId: comment.id } };
+ const response = await CommentDELETE(
+ {} as unknown as Request,
+ mockParams
+ );
+ const result = await response.json();
+ expect(result).toMatchObject(comment || {});
+ });
+
+ it('should not return any comments by recipe id', async () => {
+ if (!comment) {
+ throw new Error('comment is not defined');
+ }
+ const response = await getCommentsByRecipeId({
+ recipeId: publishedRecipe?.id,
+ });
+ expect(response.length).toBe(0);
+ });
+
+ it('should delete the recipe', async () => {
+ if (!publishedRecipe?.id) {
+ throw new Error('publishedRecipe id is not defined');
+ }
+ const mockParams = { params: { recipeId: publishedRecipe.id } };
+ const response = await RecipeDELETE(
+ {} as unknown as Request,
+ mockParams
+ );
+ const result = await response.json();
+ expect(result).toMatchObject(publishedRecipe || {});
+ publishedRecipe = null;
+ });
+
+ it('should return the updated recipes', async () => {
+ const response = await getRecipes({});
+ expect(response.recipes.length).toBe(initialRecipes.length);
+ });
+
+ it('should level down the recipe user level', async () => {
+ const currentUser = await getCurrentUser();
+ if (!currentUser || !initialUser) {
+ throw new Error('currentUser or initialUser is not defined');
+ }
+ expect(currentUser.level).toBe(initialUser.level);
+ });
+});
diff --git a/__tests__/unit_test/routes/user.test.ts b/__tests__/unit_test/routes/user.test.ts
new file mode 100644
index 0000000..1ebfc2e
--- /dev/null
+++ b/__tests__/unit_test/routes/user.test.ts
@@ -0,0 +1,51 @@
+import getCurrentUser from '@/app/actions/getCurrentUser';
+import getUserById from '@/app/actions/getUserById';
+import { Session } from 'next-auth';
+
+let mockedSession: Session | null = null;
+
+// This mocks our custom helper function to avoid passing authOptions around
+jest.mock('@/pages/api/auth/[...nextauth].ts', () => ({
+ authOptions: {
+ adapter: {},
+ providers: [],
+ callbacks: {},
+ },
+}));
+
+// This mocks calls to getServerSession
+jest.mock('next-auth/next', () => ({
+ getServerSession: jest.fn(() => {
+ return Promise.resolve(mockedSession);
+ }),
+}));
+
+describe('User API Routes and Server Actions', () => {
+ let currentUser: any = null;
+ afterEach(async () => {
+ mockedSession = null;
+ });
+
+ it('should return empty user', async () => {
+ const response = await getCurrentUser();
+ expect(response).toStrictEqual(mockedSession?.user || null);
+ });
+
+ it('should return current user', async () => {
+ mockedSession = {
+ expires: 'expires',
+ user: {
+ name: 'test',
+ email: 'test@a.com',
+ },
+ };
+ const response = await getCurrentUser();
+ expect(response).toMatchObject(mockedSession?.user || {});
+ currentUser = response;
+ });
+
+ it('should return the user with the id', async () => {
+ const response = await getUserById({ userId: currentUser.id });
+ expect(response).toMatchObject(mockedSession?.user || {});
+ });
+});
diff --git a/app/actions/getCommentById.ts b/app/actions/getCommentById.ts
index 7483ce1..711e2b3 100644
--- a/app/actions/getCommentById.ts
+++ b/app/actions/getCommentById.ts
@@ -4,9 +4,7 @@ interface IParams {
commentId?: string;
}
-export default async function getCommentById(
- params: IParams
-) {
+export default async function getCommentById(params: IParams) {
try {
const { commentId } = params;
@@ -28,13 +26,9 @@ export default async function getCommentById(
createdAt: comment.createdAt.toISOString(),
user: {
...comment.user,
- createdAt:
- comment.user.createdAt.toISOString(),
- updatedAt:
- comment.user.updatedAt.toISOString(),
- emailVerified:
- comment.user.emailVerified?.toString() ||
- null,
+ createdAt: comment.user.createdAt.toISOString(),
+ updatedAt: comment.user.updatedAt.toISOString(),
+ emailVerified: comment.user.emailVerified?.toString() || null,
},
};
} catch (error: any) {
diff --git a/app/actions/getCommentsByRecipeId.ts b/app/actions/getCommentsByRecipeId.ts
index fe0599a..2f9775d 100644
--- a/app/actions/getCommentsByRecipeId.ts
+++ b/app/actions/getCommentsByRecipeId.ts
@@ -4,9 +4,7 @@ interface IParams {
recipeId?: string;
}
-export default async function getCommentsByRecipeId(
- params: IParams
-) {
+export default async function getCommentsByRecipeId(params: IParams) {
try {
const { recipeId } = params;
@@ -31,13 +29,10 @@ export default async function getCommentsByRecipeId(
createdAt: comment.createdAt.toISOString(),
user: {
...comment.user,
- createdAt:
- comment.user.createdAt.toISOString(),
- updatedAt:
- comment.user.updatedAt.toISOString(),
+ createdAt: comment.user.createdAt.toISOString(),
+ updatedAt: comment.user.updatedAt.toISOString(),
emailVerified:
- comment.user.emailVerified?.toISOString() ||
- null,
+ comment.user.emailVerified?.toISOString() || null,
},
}));
diff --git a/app/actions/getCurrentUser.ts b/app/actions/getCurrentUser.ts
index bec9471..13bb3e9 100644
--- a/app/actions/getCurrentUser.ts
+++ b/app/actions/getCurrentUser.ts
@@ -1,15 +1,10 @@
import { getServerSession } from 'next-auth/next';
-
import { authOptions } from '@/pages/api/auth/[...nextauth]';
import prisma from '@/app/libs/prismadb';
-export async function getSession() {
- return await getServerSession(authOptions);
-}
-
export default async function getCurrentUser() {
try {
- const session = await getSession();
+ const session = await getServerSession(authOptions);
if (!session?.user?.email) {
return null;
@@ -29,9 +24,7 @@ export default async function getCurrentUser() {
...currentUser,
createdAt: currentUser.createdAt.toISOString(),
updatedAt: currentUser.updatedAt.toISOString(),
- emailVerified:
- currentUser.emailVerified?.toISOString() ||
- null,
+ emailVerified: currentUser.emailVerified?.toISOString() || null,
};
} catch (error: any) {
return null;
diff --git a/app/actions/getFavoriteRecipes.ts b/app/actions/getFavoriteRecipes.ts
index 92a4620..e67b979 100644
--- a/app/actions/getFavoriteRecipes.ts
+++ b/app/actions/getFavoriteRecipes.ts
@@ -13,9 +13,7 @@ export default async function getFavoriteRecipes() {
const favorites = await prisma.recipe.findMany({
where: {
id: {
- in: [
- ...(currentUser.favoriteIds || []),
- ],
+ in: [...(currentUser.favoriteIds || [])],
},
},
});
diff --git a/app/actions/getRecipeById.ts b/app/actions/getRecipeById.ts
index dacdb48..c81ca7a 100644
--- a/app/actions/getRecipeById.ts
+++ b/app/actions/getRecipeById.ts
@@ -4,9 +4,7 @@ interface IParams {
recipeId?: string;
}
-export default async function getRecipeById(
- params: IParams
-) {
+export default async function getRecipeById(params: IParams) {
try {
const { recipeId } = params;
@@ -28,13 +26,9 @@ export default async function getRecipeById(
createdAt: recipe.createdAt.toISOString(),
user: {
...recipe.user,
- createdAt:
- recipe.user.createdAt.toISOString(),
- updatedAt:
- recipe.user.updatedAt.toISOString(),
- emailVerified:
- recipe.user.emailVerified?.toString() ||
- null,
+ createdAt: recipe.user.createdAt.toISOString(),
+ updatedAt: recipe.user.updatedAt.toISOString(),
+ emailVerified: recipe.user.emailVerified?.toString() || null,
},
};
} catch (error: any) {
diff --git a/app/actions/getRecipes.ts b/app/actions/getRecipes.ts
index 644c36d..16b7014 100644
--- a/app/actions/getRecipes.ts
+++ b/app/actions/getRecipes.ts
@@ -6,9 +6,7 @@ export interface IRecipesParams {
limit?: number;
}
-export default async function getRecipes(
- params: IRecipesParams
-) {
+export default async function getRecipes(params: IRecipesParams) {
try {
const { category, page = 1, limit = 10 } = params;
diff --git a/app/actions/getRecipesByUserId.ts b/app/actions/getRecipesByUserId.ts
index 14b20bd..8bf13b5 100644
--- a/app/actions/getRecipesByUserId.ts
+++ b/app/actions/getRecipesByUserId.ts
@@ -4,9 +4,7 @@ interface IParams {
userId?: string;
}
-export default async function getRecipesByUserId(
- params: IParams
-) {
+export default async function getRecipesByUserId(params: IParams) {
try {
const { userId } = params;
diff --git a/app/actions/getUserById.ts b/app/actions/getUserById.ts
index 67326bc..5d5866b 100644
--- a/app/actions/getUserById.ts
+++ b/app/actions/getUserById.ts
@@ -22,8 +22,7 @@ export default async function getUserById(params: IParams) {
...user,
createdAt: user.createdAt.toISOString(),
updatedAt: user.updatedAt.toISOString(),
- emailVerified:
- user.emailVerified?.toISOString() || null,
+ emailVerified: user.emailVerified?.toISOString() || null,
};
} catch (error: any) {
return null;
diff --git a/app/actions/setLevelByUserId.ts b/app/actions/updateUserLevel.ts
similarity index 66%
rename from app/actions/setLevelByUserId.ts
rename to app/actions/updateUserLevel.ts
index dc5542c..aadc13d 100644
--- a/app/actions/setLevelByUserId.ts
+++ b/app/actions/updateUserLevel.ts
@@ -1,12 +1,11 @@
import prisma from '@/app/libs/prismadb';
+import { calculateLevel } from '@/app/utils/calculateLevel';
interface IParams {
userId?: string;
}
-export default async function setLevelByUserId(
- params: IParams
-) {
+export default async function updateUserLevel(params: IParams) {
try {
const { userId } = params;
@@ -27,15 +26,11 @@ export default async function setLevelByUserId(
});
const totalLikes = userRecipes.reduce(
- (total, recipe) =>
- total + (recipe.numLikes || 0),
+ (total, recipe) => total + (recipe.numLikes || 0),
0
);
- const newLevel = calculateLevel(
- userRecipes.length,
- totalLikes
- );
+ const newLevel = calculateLevel(userRecipes.length, totalLikes);
const updatedUser = await prisma.user.update({
where: {
@@ -51,12 +46,3 @@ export default async function setLevelByUserId(
throw new Error(error);
}
}
-
-function calculateLevel(
- numRecipes: number,
- numTotalLikes: number
-): number {
- const level = numRecipes + numTotalLikes;
-
- return Math.floor(level);
-}
diff --git a/app/api/comments/[commentId]/route.ts b/app/api/comments/[commentId]/route.ts
index 875dfe5..44c6b30 100644
--- a/app/api/comments/[commentId]/route.ts
+++ b/app/api/comments/[commentId]/route.ts
@@ -30,11 +30,11 @@ export async function DELETE(
return NextResponse.error();
}
- const user = await prisma.comment.delete({
+ const deletedComment = await prisma.comment.delete({
where: {
id: commentId,
},
});
- return NextResponse.json(user);
+ return NextResponse.json(deletedComment);
}
diff --git a/app/api/comments/route.ts b/app/api/comments/route.ts
index 9fbc0a9..dfd1cc9 100644
--- a/app/api/comments/route.ts
+++ b/app/api/comments/route.ts
@@ -49,6 +49,9 @@ export async function POST(request: Request) {
},
},
},
+ include: {
+ comments: true,
+ },
});
return NextResponse.json(recipeAndComment);
diff --git a/app/api/emailNotifications/[userId]/route.ts b/app/api/emailNotifications/[userId]/route.ts
index 302f1bb..6a77a8c 100644
--- a/app/api/emailNotifications/[userId]/route.ts
+++ b/app/api/emailNotifications/[userId]/route.ts
@@ -16,16 +16,12 @@ export async function PUT(_request: Request) {
id: currentUser.id,
},
data: {
- emailNotifications:
- !currentUser.emailNotifications,
+ emailNotifications: !currentUser.emailNotifications,
},
});
if (user.emailNotifications) {
- await sendEmail(
- 'Email notifications have been activated.',
- user.email
- );
+ await sendEmail('Email notifications have been activated.', user.email);
}
return NextResponse.json(user);
diff --git a/app/api/favorites/[recipeId]/route.ts b/app/api/favorites/[recipeId]/route.ts
index 6e6d268..7adebf4 100644
--- a/app/api/favorites/[recipeId]/route.ts
+++ b/app/api/favorites/[recipeId]/route.ts
@@ -7,10 +7,7 @@ interface IParams {
recipeId?: string;
}
-export async function POST(
- request: Request,
- { params }: { params: IParams }
-) {
+export async function POST(request: Request, { params }: { params: IParams }) {
const currentUser = await getCurrentUser();
if (!currentUser) {
@@ -57,9 +54,7 @@ export async function DELETE(
let favoriteIds = [...(currentUser.favoriteIds || [])];
- favoriteIds = favoriteIds.filter(
- (id) => id !== recipeId
- );
+ favoriteIds = favoriteIds.filter((id) => id !== recipeId);
const user = await prisma.user.update({
where: {
diff --git a/app/api/recipe/[recipeId]/route.ts b/app/api/recipe/[recipeId]/route.ts
index de697a5..6a029d4 100644
--- a/app/api/recipe/[recipeId]/route.ts
+++ b/app/api/recipe/[recipeId]/route.ts
@@ -4,16 +4,13 @@ import prisma from '@/app/libs/prismadb';
import getCurrentUser from '@/app/actions/getCurrentUser';
import sendEmail from '@/app/actions/sendEmail';
import getRecipeById from '@/app/actions/getRecipeById';
-import setLevelByUserId from '@/app/actions/setLevelByUserId';
+import updateUserLevel from '@/app/actions/updateUserLevel';
interface IParams {
recipeId?: string;
}
-export async function POST(
- request: Request,
- { params }: { params: IParams }
-) {
+export async function POST(request: Request, { params }: { params: IParams }) {
const body = await request.json();
const { operation } = body;
@@ -71,7 +68,7 @@ export async function POST(
},
});
- await setLevelByUserId({
+ await updateUserLevel({
userId: currentRecipe?.user.id,
});
@@ -106,7 +103,7 @@ export async function DELETE(
},
});
- await setLevelByUserId({
+ await updateUserLevel({
userId: recipe.userId,
});
diff --git a/app/api/recipes/route.ts b/app/api/recipes/route.ts
index 433a615..3ed67e0 100644
--- a/app/api/recipes/route.ts
+++ b/app/api/recipes/route.ts
@@ -3,7 +3,7 @@ import { NextResponse } from 'next/server';
import prisma from '@/app/libs/prismadb';
import getCurrentUser from '@/app/actions/getCurrentUser';
import sendEmail from '@/app/actions/sendEmail';
-import setLevelByUserId from '@/app/actions/setLevelByUserId';
+import updateUserLevel from '@/app/actions/updateUserLevel';
export async function POST(request: Request) {
const currentUser = await getCurrentUser();
@@ -92,7 +92,7 @@ export async function POST(request: Request) {
})
);
- await setLevelByUserId({
+ await updateUserLevel({
userId: currentUser.id,
});
diff --git a/app/components/Avatar.tsx b/app/components/Avatar.tsx
index 2c747b6..4689dd6 100644
--- a/app/components/Avatar.tsx
+++ b/app/components/Avatar.tsx
@@ -8,11 +8,7 @@ interface AvatarProps {
onClick?: () => void;
}
-const Avatar: React.FC = ({
- src,
- size = 30,
- onClick,
-}) => {
+const Avatar: React.FC = ({ src, size = 30, onClick }) => {
return (
- ) => void;
+ onClick: (e: React.MouseEvent) => void;
disabled?: boolean;
outline?: boolean;
small?: boolean;
@@ -26,9 +24,7 @@ const Button: React.FC = ({
deleteButton,
}) => {
const [isDisabled, setIsDisabled] = useState(false);
- const handleButtonClick = (
- e: React.MouseEvent
- ) => {
+ const handleButtonClick = (e: React.MouseEvent) => {
if (!disabled && !isDisabled) {
onClick(e);
if (withDelay) {
diff --git a/app/components/CategoryBox.tsx b/app/components/CategoryBox.tsx
index bddf168..f772b6e 100644
--- a/app/components/CategoryBox.tsx
+++ b/app/components/CategoryBox.tsx
@@ -1,10 +1,7 @@
'use client';
import qs from 'query-string';
-import {
- useRouter,
- useSearchParams,
-} from 'next/navigation';
+import { useRouter, useSearchParams } from 'next/navigation';
import { useCallback } from 'react';
import { IconType } from 'react-icons';
import { useTranslation } from 'react-i18next';
diff --git a/app/components/ClientOnly.tsx b/app/components/ClientOnly.tsx
index e8e59b4..80ae6a7 100644
--- a/app/components/ClientOnly.tsx
+++ b/app/components/ClientOnly.tsx
@@ -8,9 +8,7 @@ interface ClientOnlyProps {
children: React.ReactNode;
}
-const ClientOnly: React.FC = ({
- children,
-}) => {
+const ClientOnly: React.FC = ({ children }) => {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
@@ -19,11 +17,7 @@ const ClientOnly: React.FC = ({
if (!hasMounted) return null;
- return (
-
- {children}
-
- );
+ return {children};
};
export default ClientOnly;
diff --git a/app/components/Container.tsx b/app/components/Container.tsx
index 6013341..c3df7ac 100644
--- a/app/components/Container.tsx
+++ b/app/components/Container.tsx
@@ -4,9 +4,7 @@ interface ContainerProps {
children: React.ReactNode;
}
-const Container: React.FC = ({
- children,
-}) => {
+const Container: React.FC = ({ children }) => {
return (
{children}
diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx
index 03cbaf4..f5ca6df 100644
--- a/app/components/Footer.tsx
+++ b/app/components/Footer.tsx
@@ -11,9 +11,7 @@ const Footer = () => {
return (
{`${t('version')} 0.5`}
-
- {`${t('contact')} jbonetv5@gmail.com`}
-
+
{`${t('contact')} jbonetv5@gmail.com`}
);
};
diff --git a/app/components/Heading.tsx b/app/components/Heading.tsx
index 9262bca..c614163 100644
--- a/app/components/Heading.tsx
+++ b/app/components/Heading.tsx
@@ -6,21 +6,11 @@ interface HeadingProps {
center?: boolean;
}
-const Heading: React.FC
= ({
- title,
- subtitle,
- center,
-}) => {
+const Heading: React.FC = ({ title, subtitle, center }) => {
const words = title.split(' ');
- const isLongWord = words.some(
- (word) => word.length > 20
- );
+ const isLongWord = words.some((word) => word.length > 20);
return (
-
+
= ({
>
{title}
-
- {subtitle}
-
+
{subtitle}
);
};
diff --git a/app/components/HeartButton.tsx b/app/components/HeartButton.tsx
index ba9e647..f450e34 100644
--- a/app/components/HeartButton.tsx
+++ b/app/components/HeartButton.tsx
@@ -1,7 +1,4 @@
-import {
- AiFillHeart,
- AiOutlineHeart,
-} from 'react-icons/ai';
+import { AiFillHeart, AiOutlineHeart } from 'react-icons/ai';
import { SafeUser } from '@/app/types';
import { useState, useEffect } from 'react';
import useFavorite from '@/app/hooks/useFavorite';
@@ -11,10 +8,7 @@ interface HeartButtonProps {
currentUser?: SafeUser | null;
}
-const HeartButton: React.FC
= ({
- recipeId,
- currentUser,
-}) => {
+const HeartButton: React.FC = ({ recipeId, currentUser }) => {
const { hasFavorited, toggleFavorite } = useFavorite({
recipeId,
currentUser,
@@ -54,14 +48,10 @@ const HeartButton: React.FC = ({
data-testid="heart-button"
size={24}
className={
- hasFavorited
- ? 'fill-green-450'
- : 'fill-neutral-500/70'
+ hasFavorited ? 'fill-green-450' : 'fill-neutral-500/70'
}
style={{
- pointerEvents: isDisabled
- ? 'none'
- : 'auto',
+ pointerEvents: isDisabled ? 'none' : 'auto',
}}
/>
diff --git a/app/components/Pagination.tsx b/app/components/Pagination.tsx
index f47c682..d903b50 100644
--- a/app/components/Pagination.tsx
+++ b/app/components/Pagination.tsx
@@ -1,10 +1,7 @@
'use client';
import { useRouter } from 'next/navigation';
-import {
- FiChevronLeft,
- FiChevronRight,
-} from 'react-icons/fi';
+import { FiChevronLeft, FiChevronRight } from 'react-icons/fi';
import { useTranslation } from 'react-i18next';
interface PaginationProps {
@@ -23,9 +20,7 @@ const Pagination = ({
const handlePageChange = (page: number) => {
const newSearchParams = { ...searchParams, page };
- const queryString = new URLSearchParams(
- newSearchParams
- ).toString();
+ const queryString = new URLSearchParams(newSearchParams).toString();
const newUrl = `?${queryString}`;
router.push(newUrl);
@@ -35,9 +30,7 @@ const Pagination = ({