From 85efac018c467ec8df1ea80a8a90c7cbcf30f6ad Mon Sep 17 00:00:00 2001 From: Anuj Chhikara <107175639+AnujChhikara@users.noreply.github.com> Date: Thu, 10 Oct 2024 23:20:34 +0530 Subject: [PATCH] add loading skeleton for user profile in the navbar (#1270) * add loading skeleton for user profile in the navbar * Add tests for NavBar skeleton loader, user info, and GitHub login button --- .../Unit/Components/Navbar/Navbar.test.tsx | 36 ++++++++++++++++--- src/components/navBar/index.tsx | 19 ++++++++-- src/components/navBar/navBar.module.scss | 34 ++++++++++++++++++ src/hooks/useUserData.ts | 4 +-- 4 files changed, 83 insertions(+), 10 deletions(-) diff --git a/__tests__/Unit/Components/Navbar/Navbar.test.tsx b/__tests__/Unit/Components/Navbar/Navbar.test.tsx index 3d1d086cd..e688543e4 100644 --- a/__tests__/Unit/Components/Navbar/Navbar.test.tsx +++ b/__tests__/Unit/Components/Navbar/Navbar.test.tsx @@ -1,9 +1,10 @@ import React from 'react'; -import { screen, render, fireEvent } from '@testing-library/react'; +import { screen, render, fireEvent, waitFor } from '@testing-library/react'; import NavBar from '../../../../src/components/navBar/index'; import * as authHooks from '@/hooks/useAuthenticated'; import { renderWithProviders } from '@/test-utils/renderWithProvider'; import { setupServer } from 'msw/node'; +import { rest } from 'msw'; import handlers from '../../../../__mocks__/handlers'; const server = setupServer(...handlers); @@ -15,11 +16,36 @@ afterEach(() => server.resetHandlers()); afterAll(() => server.close()); describe('Navbar', () => { - test('user whether loggedIn or not', async () => { + test('shows skeleton loader first, then displays user info if logged in', async () => { renderWithProviders(); - const navbar = await screen.findByTestId('navbar'); - expect(navbar).toBeInTheDocument(); - expect(screen.getByText('Sign In With Github')); + const loadingSkeleton = screen.getByTestId('loading-skeleton'); + expect(loadingSkeleton).toBeInTheDocument(); + + await waitFor(() => { + const userGreet = screen.getByText(/Hello, Mahima/i); + expect(userGreet).toBeInTheDocument(); + }); + }); + + test('shows "Sign In With Github" button when not logged in', async () => { + server.use( + rest.get( + `${process.env.NEXT_PUBLIC_BASE_URL}/users/self`, + (_, res, ctx) => { + return res( + ctx.status(401), + ctx.json({ error: 'Not Authenticated' }) + ); + } + ) + ); + renderWithProviders(); + await waitFor(() => { + const signInButton = screen.getByRole('button', { + name: /Sign In With Github/i, + }); + expect(signInButton).toBeInTheDocument(); + }); }); test('renders the hamburger icon', async () => { diff --git a/src/components/navBar/index.tsx b/src/components/navBar/index.tsx index fb938ad45..c463edf5c 100644 --- a/src/components/navBar/index.tsx +++ b/src/components/navBar/index.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import Link from 'next/link'; import Image from 'next/image'; @@ -20,11 +20,16 @@ import useAuthenticated from '@/hooks/useAuthenticated'; const NavBar = () => { const { isLoggedIn } = useAuthenticated(); + const [isProfileLoading, setIsProfileLoading] = useState(true); const [toggleDropdown, setToggleDropdown] = useState(false); const [showMenu, setShowMenu] = useState(false); - const { data: userData } = useUserData(); + const { data: userData, isLoading } = useUserData(); + + useEffect(() => { + setIsProfileLoading(isLoading); + }, [isLoading]); return ( @@ -77,7 +82,15 @@ const NavBar = () => { - {!isLoggedIn ? ( + {isProfileLoading ? ( + + + + + ) : !isLoggedIn ? ( Sign In With Github diff --git a/src/components/navBar/navBar.module.scss b/src/components/navBar/navBar.module.scss index 2e15c857c..304da5ee6 100644 --- a/src/components/navBar/navBar.module.scss +++ b/src/components/navBar/navBar.module.scss @@ -78,6 +78,40 @@ $thickness: 3px; color: $white; font-weight: 600; } + .skeletonContainer { + display: flex; + align-items: center; + gap: 0.625rem; + padding: ($offset + $thickness) + 7; + } + + .skeletonGreetMsg { + width: 100px; + height: 20px; + background-color: lighten($theme-primary, 20%); + border-radius: 4px; + animation: skeleton-loading 1.2s infinite ease-in-out; + } + + .skeletonProfilePic { + width: 32px; + height: 32px; + background-color: lighten($theme-primary, 20%); + border-radius: 50%; + animation: skeleton-loading 1.2s infinite ease-in-out; + } + + @keyframes skeleton-loading { + 0% { + background-color: lighten($theme-primary, 20%); + } + 50% { + background-color: lighten($theme-primary, 30%); + } + 100% { + background-color: lighten($theme-primary, 20%); + } + } .hamburgerIcon { display: none; diff --git a/src/hooks/useUserData.ts b/src/hooks/useUserData.ts index 3e4d307a4..e01cc51d3 100644 --- a/src/hooks/useUserData.ts +++ b/src/hooks/useUserData.ts @@ -1,11 +1,11 @@ import { useGetUserQuery } from '@/app/services/userApi'; const useUserData = () => { - const { data, isSuccess } = useGetUserQuery(); + const { data, isSuccess, isLoading } = useGetUserQuery(); const adminData = data?.roles.admin; const superUserData = data?.roles.super_user; const isUserAuthorized = !!adminData || !!superUserData; - return { data, isUserAuthorized, isSuccess }; + return { data, isUserAuthorized, isSuccess, isLoading }; }; export default useUserData;