diff --git a/app/(main)/account/recovery/page.tsx b/app/(main)/account/recovery/page.tsx index aba1469d..05192427 100644 --- a/app/(main)/account/recovery/page.tsx +++ b/app/(main)/account/recovery/page.tsx @@ -106,7 +106,6 @@ const ResetPassword = (): React.JSX.Element => { const password: string = useWatch({ control: form.control, name: 'password', - defaultValue: '', }); /** @@ -116,7 +115,6 @@ const ResetPassword = (): React.JSX.Element => { const confirmPassword: string = useWatch({ control: form.control, name: 'confirmPassword', - defaultValue: '', }); /** diff --git a/app/(main)/league/[leagueId]/entry/all/page.test.tsx b/app/(main)/league/[leagueId]/entry/all/page.test.tsx index 9764ce08..7a241f78 100644 --- a/app/(main)/league/[leagueId]/entry/all/page.test.tsx +++ b/app/(main)/league/[leagueId]/entry/all/page.test.tsx @@ -344,148 +344,4 @@ describe('League entries page (Entry Component)', () => { ); }); }); - it('should not display the user pick history if the current week is 1', async () => { - mockUseDataStore.mockReturnValue({ - ...mockUseDataStore(), - currentWeek: 1, - NFLTeams: [ - { - teamId: 'packers', - teamLogo: '/packers-logo.png', - teamName: 'Packers', - }, - ], - }); - mockGetCurrentUserEntries.mockResolvedValueOnce([ - { - $id: '123', - name: 'Test Entry', - week: 1, - selectedTeams: ['Packers', 'Bears'], - }, - ]); - mockGetGameWeek.mockResolvedValueOnce({ week: 1 }); - mockGetCurrentLeague.mockResolvedValue({ - leagueName: 'Test League', - participants: 10, - survivors: 8, - }); - mockGetNFLTeams.mockResolvedValue([ - { teamId: 'packers', teamLogo: '/packers-logo.png', teamName: 'Packers' }, - { teamId: 'bears', teamLogo: '/bears-logo.png', teamName: 'Bears' }, - ]); - - render(); - - await waitFor(() => { - expect(mockGetGameWeek).toHaveBeenCalled(); - expect(mockGetCurrentUserEntries).toHaveBeenCalled(); - expect(mockGetCurrentLeague).toHaveBeenCalled(); - expect(mockGetNFLTeams).toHaveBeenCalled(); - }); - - expect(screen.queryByTestId('user-pick-history')).not.toBeInTheDocument(); - }); - - it('should display the user pick history if the current week is greater than 1', async () => { - mockUseDataStore.mockReturnValue({ - ...mockUseDataStore(), - currentWeek: 2, - NFLTeams: [ - { - teamId: 'packers', - teamLogo: '/packers-logo.png', - teamName: 'Packers', - }, - { - teamId: 'bears', - teamLogo: '/bears-logo.png', - teamName: 'Bears', - }, - ], - }); - mockGetCurrentUserEntries.mockResolvedValueOnce([ - { - $id: '123', - name: 'Test Entry', - week: 2, - selectedTeams: ['Packers', 'Bears'], - }, - ]); - mockGetGameWeek.mockResolvedValueOnce({ week: 2 }); - mockGetCurrentLeague.mockResolvedValue({ - leagueName: 'Test League', - participants: ['123', '456'], - survivors: ['123', '456'], - }); - mockGetNFLTeams.mockResolvedValue([ - { teamId: 'packers', teamLogo: '/packers-logo.png', teamName: 'Packers' }, - { teamId: 'bears', teamLogo: '/bears-logo.png', teamName: 'Bears' }, - ]); - - render(); - - await waitFor(() => { - expect(mockGetGameWeek).toHaveBeenCalled(); - expect(mockGetCurrentUserEntries).toHaveBeenCalled(); - expect(mockGetCurrentLeague).toHaveBeenCalled(); - expect(mockGetNFLTeams).toHaveBeenCalled(); - }); - // Add a delay to allow for any asynchronous rendering - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(screen.queryByTestId('user-pick-history')).toBeInTheDocument(); - expect(screen.getByTestId('league-history-logo')).toHaveAttribute( - 'src', - '/_next/image?url=%2Fpackers-logo.png&w=96&q=75', - ); - expect(screen.getByTestId('league-entry-logo')).toHaveAttribute( - 'src', - '/_next/image?url=%2Fbears-logo.png&w=96&q=75', - ); - }); - - it('should not display the user pick history if the entries selected teams are empty', async () => { - mockUseDataStore.mockReturnValue({ - ...mockUseDataStore(), - currentWeek: 2, - NFLTeams: [ - { - teamId: 'packers', - teamLogo: '/packers-logo.png', - teamName: 'Packers', - }, - { teamId: '2', teamLogo: '/bears-logo.png', teamName: 'Bears' }, - ], - }); - mockGetCurrentUserEntries.mockResolvedValueOnce([ - { - $id: '123', - name: 'Test Entry', - week: 2, - selectedTeams: [], - }, - ]); - mockGetGameWeek.mockResolvedValueOnce({ week: 2 }); - mockGetCurrentLeague.mockResolvedValue({ - leagueName: 'Test League', - participants: ['123', '456'], - survivors: ['123', '456'], - }); - mockGetNFLTeams.mockResolvedValue([ - { teamId: 'packers', teamLogo: '/packers-logo.png', teamName: 'Packers' }, - { teamId: 'bears', teamLogo: '/bears-logo.png', teamName: 'Bears' }, - ]); - - render(); - - await waitFor(() => { - expect(mockGetGameWeek).toHaveBeenCalled(); - expect(mockGetCurrentUserEntries).toHaveBeenCalled(); - expect(mockGetCurrentLeague).toHaveBeenCalled(); - expect(mockGetNFLTeams).toHaveBeenCalled(); - }); - - expect(screen.queryByTestId('user-pick-history')).not.toBeInTheDocument(); - }); }); diff --git a/app/(main)/league/[leagueId]/entry/all/page.tsx b/app/(main)/league/[leagueId]/entry/all/page.tsx index 5ef1214c..52c7f776 100644 --- a/app/(main)/league/[leagueId]/entry/all/page.tsx +++ b/app/(main)/league/[leagueId]/entry/all/page.tsx @@ -172,14 +172,6 @@ const Entry = ({ const selectedTeamLogo = getNFLTeamLogo(NFLTeams, selectedTeam); - let userPickHistory: string[] = []; - - if (currentWeek > 1 && entry.selectedTeams.length > 0) { - userPickHistory = entry.selectedTeams - .slice(0, currentWeek - 1) - .map((teamName) => getNFLTeamLogo(NFLTeams, teamName)); - } - return (
diff --git a/app/(main)/login/page.test.tsx b/app/(main)/login/page.test.tsx index 7ea58670..8c3dabc9 100644 --- a/app/(main)/login/page.test.tsx +++ b/app/(main)/login/page.test.tsx @@ -17,7 +17,13 @@ let continueButton: HTMLElement, emailInput: HTMLInputElement, passwordInput: HTMLInputElement; -const mockUseAuthContext = { +interface MockUseAuthContext { + getUser: jest.Mock; + isSignedIn: boolean | null; + login: jest.Mock; +} + +const mockUseAuthContext: MockUseAuthContext = { getUser, isSignedIn: false, login: mockLogin, @@ -46,20 +52,45 @@ describe('Login', () => { jest .spyOn(React, 'useState') .mockImplementation(() => [false, setIsLoading]); + }); + + it('should display GlobalSpinner while authenticating the user', async () => { + mockUseAuthContext.isSignedIn = null; + + render(); + + expect(screen.getByTestId('global-spinner')).toBeInTheDocument(); + }); + + it('should not display GlobalSpinner once authentication is complete', async () => { + mockUseAuthContext.isSignedIn = false; + + render(); + + expect(screen.queryByTestId('global-spinner')).not.toBeInTheDocument(); + }); + + it('should render the login page if the user is not logged in', () => { + mockUseAuthContext.isSignedIn = false; render(); continueButton = screen.getByTestId('continue-button'); emailInput = screen.getByTestId('email'); passwordInput = screen.getByTestId('password'); - }); - it('should render the login page', () => { + expect(continueButton).toBeInTheDocument(); expect(emailInput).toBeInTheDocument(); expect(passwordInput).toBeInTheDocument(); }); it('should update email and password fields and submit form', async () => { + mockUseAuthContext.isSignedIn = false; + + render(); + + const emailInput = screen.getByTestId('email'); + const passwordInput = screen.getByTestId('password'); const form = screen.getByTestId('login-form'); await act(async () => { @@ -80,16 +111,7 @@ describe('Login', () => { }); }); - it('redirects to /weeklyPicks when the button is clicked', () => { - mockUseAuthContext.isSignedIn = true; - - render(); - expect(mockUseAuthContext.getUser).toHaveBeenCalled(); - - mockUseAuthContext.isSignedIn = false; - }); - - it('redirects to /league/all when user navigates to /login', async () => { + it('redirects to /league/all when user navigates to /login and is logged in', async () => { mockUseAuthContext.isSignedIn = true; act(() => { @@ -106,6 +128,8 @@ describe('Login', () => { describe('Login loading spinner', () => { it('should show the loading spinner', async () => { + mockUseAuthContext.isSignedIn = false; + (useStateMock as jest.Mock).mockImplementation((init: boolean) => [ true, setIsLoading, @@ -117,6 +141,7 @@ describe('Login loading spinner', () => { expect(screen.queryByTestId('loading-spinner')).toBeInTheDocument(); }); }); + it('should not show the loading spinner', async () => { (useStateMock as jest.Mock).mockImplementation((init: boolean) => [ false, diff --git a/app/(main)/login/page.tsx b/app/(main)/login/page.tsx index 7aa5d925..66b6fd9a 100644 --- a/app/(main)/login/page.tsx +++ b/app/(main)/login/page.tsx @@ -11,6 +11,7 @@ import { FormItem, FormMessage, } from '../../../components/Form/Form'; +import GlobalSpinner from '@/components/GlobalSpinner/GlobalSpinner'; import { Input } from '@/components/Input/Input'; import { useAuthContext } from '@/context/AuthContextProvider'; import { useRouter } from 'next/navigation'; @@ -51,31 +52,32 @@ type LoginUserSchemaType = z.infer; */ const Login = (): React.JSX.Element => { const router = useRouter(); - const { login, isSignedIn, getUser } = useAuthContext(); - const [isLoading, setIsLoading] = useState(false); + const { login, isSignedIn } = useAuthContext(); + const [isLoading, setIsLoading] = useState(false); useEffect(() => { - if (isSignedIn) { - getUser(); + if (isSignedIn === true) { router.push('/league/all'); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isSignedIn, getUser]); + }, [isSignedIn]); const form = useForm({ resolver: zodResolver(LoginUserSchema), + defaultValues: { + email: '', + password: '', + }, }); const email = useWatch({ control: form.control, name: 'email', - defaultValue: '', }); const password = useWatch({ control: form.control, name: 'password', - defaultValue: '', }); /** @@ -97,98 +99,105 @@ const Login = (): React.JSX.Element => { }; return ( -
-
- -
-

- Thank you... fantasy football draft, for letting me know that even - in my fantasies, I am bad at sports. -

-

Jimmy Fallon

+
+ {(isSignedIn === null || isSignedIn === true) && + + } + {isSignedIn === false && + <> +
+ +
+

+ Thank you... fantasy football draft, for letting me know that even + in my fantasies, I am bad at sports. +

+

Jimmy Fallon

+
-
-
-
-

- Join Gridiron Survivor -

-

- Log in to your existing account or{' '} - sign up to get started - with a league -

+
+
+

+ Join Gridiron Survivor +

+

+ Log in to your existing account or{' '} + sign up to get started + with a league +

+
+
+ + } + name="email" + render={({ field }) => ( + + + + + {form.formState.errors?.email && ( + + {form.formState.errors.email.message} + + )} + + )} + /> + } + name="password" + render={({ field }) => ( + + + + + {form.formState.errors?.password && ( + + {form.formState.errors?.password.message} + + )} + + )} + /> +
-
- - } - name="email" - render={({ field }) => ( - - - - - {form.formState.errors?.email && ( - - {form.formState.errors.email.message} - - )} - - )} - /> - } - name="password" - render={({ field }) => ( - - - - - {form.formState.errors?.password && ( - - {form.formState.errors?.password.message} - - )} - - )} - /> -
+ + }
); }; diff --git a/app/(main)/recover-password/page.tsx b/app/(main)/recover-password/page.tsx index 6eca9c91..81937e63 100644 --- a/app/(main)/recover-password/page.tsx +++ b/app/(main)/recover-password/page.tsx @@ -67,7 +67,6 @@ const RecoverPassword = (): React.JSX.Element => { const email = useWatch({ control: form.control, name: 'email', - defaultValue: '', }); /** diff --git a/app/(main)/register/Register.tsx b/app/(main)/register/Register.tsx index 6c8d02b9..9338dc2d 100644 --- a/app/(main)/register/Register.tsx +++ b/app/(main)/register/Register.tsx @@ -67,6 +67,11 @@ const Register = (): JSX.Element => { const form = useForm({ resolver: zodResolver(RegisterUserSchema), + defaultValues: { + email: '', + password: '', + confirmPassword: '' + }, }); /** @@ -76,7 +81,6 @@ const Register = (): JSX.Element => { const email: string = useWatch({ control: form.control, name: 'email', - defaultValue: '', }); /** @@ -86,7 +90,6 @@ const Register = (): JSX.Element => { const password: string = useWatch({ control: form.control, name: 'password', - defaultValue: '', }); /** @@ -96,7 +99,6 @@ const Register = (): JSX.Element => { const confirmPassword: string = useWatch({ control: form.control, name: 'confirmPassword', - defaultValue: '', }); /** diff --git a/components/AdminNav/AdminNav.test.tsx b/components/AdminNav/AdminNav.test.tsx index b0f3c7ad..c39a39e2 100644 --- a/components/AdminNav/AdminNav.test.tsx +++ b/components/AdminNav/AdminNav.test.tsx @@ -1,10 +1,31 @@ import { AdminNav } from './AdminNav'; +import { AuthContextProvider } from '@/context/AuthContextProvider'; import { render, screen } from '@testing-library/react'; import React from 'react'; +const mockPush = jest.fn(); +const mockUsePathname = jest.fn(); + +jest.mock('next/navigation', () => ({ + useRouter() { + return { + push: mockPush, + }; + }, + usePathname() { + return mockUsePathname(); + }, +})); + describe('AdminNav Component', () => { beforeEach(() => { - render(); + jest.clearAllMocks(); + + render( + + + , + ); }); it('should render the navigation links with correct href attributes', () => { diff --git a/components/AdminUserSettings/AdminUserSettings.test.tsx b/components/AdminUserSettings/AdminUserSettings.test.tsx index e2048e49..6cf07921 100644 --- a/components/AdminUserSettings/AdminUserSettings.test.tsx +++ b/components/AdminUserSettings/AdminUserSettings.test.tsx @@ -1,12 +1,98 @@ import { AdminUserSettings } from './AdminUserSettings'; -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import React from 'react'; +const mockPush = jest.fn(); +const mockUsePathname = jest.fn(); +const mockLogoutAccount = jest.fn(); + +const mockUseAuthContext = { + logoutAccount: mockLogoutAccount, +}; + +jest.mock('../../context/AuthContextProvider', () => ({ + useAuthContext() { + return { + ...mockUseAuthContext, + }; + }, +})); + +jest.mock('next/navigation', () => ({ + useRouter() { + return { + push: mockPush, + }; + }, + usePathname() { + return mockUsePathname(); + }, +})); + describe('AdminUserSettings Component', () => { - it('should render the component', () => { + beforeEach(() => { + jest.clearAllMocks(); + render(); + }); + + it('should render the component', async () => { const adminUserSettings = screen.getByTestId('admin-user-settings'); expect(adminUserSettings).toBeInTheDocument(); }); + + it('should show user options when clicked', () => { + const adminUserSettings = screen.getByTestId('admin-user-settings'); + + fireEvent.click(adminUserSettings); + + waitFor(() => { + expect(screen.getByTestId('edit-profile-link')).toBeInTheDocument(); + expect(screen.getByTestId('sign-out-button')).toBeInTheDocument(); + }); + }); + + it('should not show user options when closed', () => { + const adminUserSettings = screen.getByTestId('admin-user-settings'); + + fireEvent.click(adminUserSettings); + + fireEvent.click(adminUserSettings); + + waitFor(() => { + expect(screen.getByTestId('edit-profile-link')).not.toBeInTheDocument(); + expect(screen.getByTestId('sign-out-button')).not.toBeInTheDocument(); + }); + }); + + it('should direct to /account/settings route when the edit profile button is clicked', () => { + const adminUserSettings = screen.getByTestId('admin-user-settings'); + + fireEvent.click(adminUserSettings); + + waitFor(() => { + const editProfileButton = screen.getByTestId('edit-profile-link'); + fireEvent.click(editProfileButton); + }); + + waitFor(() => { + expect(mockPush).toHaveBeenCalledWith('/account/settings'); + }); + }); + + it('should direct to /login route when the sign out button is clicked', () => { + const adminUserSettings = screen.getByTestId('admin-user-settings'); + + fireEvent.click(adminUserSettings); + + waitFor(() => { + const signOutButton = screen.getByTestId('sign-out-button'); + fireEvent.click(signOutButton); + }); + + waitFor(() => { + expect(mockPush).toHaveBeenCalledWith('/login'); + }); + }); }); diff --git a/components/AdminUserSettings/AdminUserSettings.tsx b/components/AdminUserSettings/AdminUserSettings.tsx index e13e77cb..58bd6302 100644 --- a/components/AdminUserSettings/AdminUserSettings.tsx +++ b/components/AdminUserSettings/AdminUserSettings.tsx @@ -1,25 +1,75 @@ // Copyright (c) Gridiron Survivor. // Licensed under the MIT License. +'use client'; +import React, { JSX } from 'react'; +import { Button } from '../Button/Button'; +import { useDataStore } from '@/store/dataStore'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '../TableDropDownMenu/TableDropDownMenu'; import { LucideChevronsUpDown } from 'lucide-react'; -import React from 'react'; +import { useAuthContext } from '@/context/AuthContextProvider'; +import { useRouter } from 'next/navigation'; +import LinkCustom from '../LinkCustom/LinkCustom'; /** - * The admin user settings component. - * @returns The rendered admin user settings. + * Renders admin user settings. + * @returns {JSX.Element} The rendered admin user settings component. */ -export const AdminUserSettings = (): React.JSX.Element => { +export const AdminUserSettings = (): JSX.Element => { + const router = useRouter(); + const { logoutAccount } = useAuthContext(); + const { user } = useDataStore((state) => state); + + /** + * Handles the logout. + * @returns {Promise} The logout promise. + */ + const handleLogout = async (): Promise => { + try { + await logoutAccount(); + router.push('/login'); + } catch (error) { + throw error; + } + }; + return ( -
- -

Users Name

- -
+ + +
+ +

{user.email}

+ +
+
+ + + + Edit Profile + + + +