diff --git a/__tests__/components/Navbar.test.tsx b/__tests__/components/Navbar.test.tsx index 0dc9e0b..ac626e3 100644 --- a/__tests__/components/Navbar.test.tsx +++ b/__tests__/components/Navbar.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import Navbar from '@/components/Navbar/'; -import { render } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; describe('Navbar', () => { @@ -12,16 +12,40 @@ describe('Navbar', () => { it('should have dropdown menu', () => { const { container } = render(); - expect(container.querySelector('ul')).toHaveTextContent('Profile'); - expect(container.querySelector('ul')).toHaveTextContent('Dashboard'); - expect(container.querySelector('ul')).toHaveTextContent('Settings'); + expect(container.querySelector('ul')).toBeInTheDocument(); + expect(container.querySelector('ul')).toContainHTML('Profile'); + expect(container.querySelector('ul')).toContainHTML('Dashboard'); + expect(container.querySelector('ul')).toContainHTML('Settings'); + expect(container.querySelector('ul')).toContainHTML('Sign Out'); }); - it('should toggle menu', () => { - const { container } = render(); - const button = container.querySelector('button'); - expect(button).toHaveTextContent('Sunny'); - button?.click(); - expect(container.querySelector('ul')).toHaveClass('lg:flex space-x-4'); + it('should have google login button', () => { + render(); + const googleLoginButton = screen.getByTestId('google-login'); + expect(googleLoginButton).toBeInTheDocument(); + expect(googleLoginButton).toHaveTextContent('Sign In'); + expect(googleLoginButton).toHaveAttribute('href', 'https://api-tinysite.onrender.com/v1/auth/google/login'); + }); + + it('should display "Sign In" when not logged in', () => { + render(); + const signInButton = screen.getByText('Sign In'); + expect(signInButton).toBeInTheDocument(); + }); + + it('should handle "Sign Out" button click', () => { + render(); + const signOutButton = screen.getByText('Sign Out'); + expect(signOutButton).toBeInTheDocument(); + + const originalIsLoggedIn = screen.getByText('Sign In'); + fireEvent.click(originalIsLoggedIn); + + expect(signOutButton).toBeInTheDocument(); + + fireEvent.click(signOutButton); + + const signInButton = screen.getByText('Sign In'); + expect(signInButton).toBeInTheDocument(); }); }); diff --git a/__tests__/hooks/isAuthenticated.test.tsx b/__tests__/hooks/isAuthenticated.test.tsx new file mode 100644 index 0000000..c2e7a6c --- /dev/null +++ b/__tests__/hooks/isAuthenticated.test.tsx @@ -0,0 +1,28 @@ +import { renderHook } from '@testing-library/react-hooks'; +import fetchMock from 'jest-fetch-mock'; +import IsAuthenticated from '@/hooks/isAuthenticated'; +import { userData } from '../../fixtures/users'; + +beforeAll(() => { + fetchMock.enableMocks(); +}); + +afterEach(() => { + fetchMock.resetMocks(); +}); + +it('should return isLoggedIn as true and userData if the request is successful', async () => { + const userDataMock = userData; + fetchMock.mockResponseOnce(JSON.stringify(userDataMock), { status: 200 }); + const { result, waitForNextUpdate } = renderHook(() => IsAuthenticated()); + await waitForNextUpdate(); + expect(result.current.isLoggedIn).toBe(true); + expect(result.current.userData).toEqual(userDataMock.data); +}); + +it('should return isLoggedIn as false if the request is unsuccessful', async () => { + fetchMock.mockResponseOnce(JSON.stringify({}), { status: 401 }); + const { result } = renderHook(() => IsAuthenticated()); + expect(result.current.isLoggedIn).toBe(false); + expect(result.current.userData).toBeNull(); +}); diff --git a/fixtures/users.ts b/fixtures/users.ts new file mode 100644 index 0000000..cb4f072 --- /dev/null +++ b/fixtures/users.ts @@ -0,0 +1,13 @@ +export const userData = { + data: { + Id: 1, + Username: 'Sunny Sahsi', + Email: 'sunnysahsi@gmail.com', + Password: '', + IsVerified: false, + IsOnboarding: true, + CreatedAt: '2023-11-04T10:02:26.582699Z', + UpdatedAt: '2023-11-04T10:02:26.582699Z', + }, + message: 'user fetched successfully', +}; diff --git a/package.json b/package.json index 611f0f8..708fd43 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "check:all": "yarn format:check && yarn lint:check", "fix:all": "yarn format:fix && yarn lint:fix", "prepare": "husky install", - "test": "jest --coverage", + "test": "jest --coverage -u", "lint": "eslint .", "lint-fix": "eslint . --fix" }, @@ -30,6 +30,7 @@ "devDependencies": { "@testing-library/jest-dom": "5.17.0", "@testing-library/react": "^14.0.0", + "@testing-library/react-hooks": "^8.0.1", "@types/eslint": "^8", "@types/jest": "^29.5.3", "@types/node": "20.2.5", @@ -48,6 +49,7 @@ "husky": "^8.0.3", "jest": "^29.6.2", "jest-environment-jsdom": "^29.6.2", + "jest-fetch-mock": "^3.0.3", "prettier": "^2.8.8", "ts-jest": "^29.1.1" }, diff --git a/public/assets/icons/downArrow.tsx b/public/assets/icons/downArrow.tsx new file mode 100644 index 0000000..5ef5f0a --- /dev/null +++ b/public/assets/icons/downArrow.tsx @@ -0,0 +1,13 @@ +const DownArrow = () => ( + + + +); + +export default DownArrow; diff --git a/public/assets/icons/google.tsx b/public/assets/icons/google.tsx new file mode 100644 index 0000000..cc504ce --- /dev/null +++ b/public/assets/icons/google.tsx @@ -0,0 +1,23 @@ +const GoogleIcon = () => ( + + Google + + + + + +); + +export default GoogleIcon; diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index 677fcd1..5c1dfae 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -1,17 +1,32 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import Button from '@/components/Button'; import ProfileIcon from '../ProfileIcon/ProfileIcon'; +import GoogleIcon from '../../../public/assets/icons/google'; +import DownArrowIcon from '../../../public/assets/icons/downArrow'; +import IsAuthenticated from '@/hooks/isAuthenticated'; +import { TINY_API_GOOGLE_LOGIN, TINY_API_LOGOUT } from '@/constants/url'; const Navbar: React.FC = () => { const [menuOpen, setMenuOpen] = useState(false); + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const { isLoggedIn: isAuth, userData } = IsAuthenticated(); + + useEffect(() => { + setIsLoggedIn(isAuth); + if (userData) { + const username = userData.Username; + const [first, last] = username.split(' '); + setFirstName(first); + setLastName(last); + } + }, [isAuth, userData]); const toggleDropdown = () => { setMenuOpen(!menuOpen); }; - const firstName = 'Sunny'; - const lastName = 'Sahsi'; - return (