large mock refector hopefully done + no errors?
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 1h17m3s
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 1h17m3s
This commit is contained in:
@@ -4,15 +4,22 @@ import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Header } from './Header';
|
||||
import type { Profile } from '../types';
|
||||
import { createMockProfile, createMockUser } from '../tests/utils/mockFactories';
|
||||
import type { UserProfile } from '../types';
|
||||
import { createMockUserProfile } from '../tests/utils/mockFactories';
|
||||
|
||||
// Unmock the component to test the real implementation
|
||||
vi.unmock('./Header');
|
||||
|
||||
const mockUser = createMockUser({ user_id: 'user-123', email: 'test@example.com' });
|
||||
const mockProfile: Profile = createMockProfile({ user_id: 'user-123', role: 'user', points: 0 });
|
||||
const mockAdminProfile: Profile = createMockProfile({ user_id: 'user-123', role: 'admin', points: 0 });
|
||||
const mockUserProfile: UserProfile = createMockUserProfile({
|
||||
user_id: 'user-123',
|
||||
role: 'user',
|
||||
user: { user_id: 'user-123', email: 'test@example.com' },
|
||||
});
|
||||
const mockAdminProfile: UserProfile = createMockUserProfile({
|
||||
user_id: 'admin-123',
|
||||
role: 'admin',
|
||||
user: { user_id: 'admin-123', email: 'admin@example.com' },
|
||||
});
|
||||
|
||||
const mockOnOpenProfile = vi.fn();
|
||||
const mockOnOpenVoiceAssistant = vi.fn();
|
||||
@@ -21,9 +28,8 @@ const mockOnSignOut = vi.fn();
|
||||
const defaultProps = {
|
||||
isDarkMode: false,
|
||||
unitSystem: 'imperial' as const,
|
||||
user: null,
|
||||
userProfile: null,
|
||||
authStatus: 'SIGNED_OUT' as const,
|
||||
profile: null,
|
||||
onOpenProfile: mockOnOpenProfile,
|
||||
onOpenVoiceAssistant: mockOnOpenVoiceAssistant,
|
||||
onSignOut: mockOnSignOut,
|
||||
@@ -56,18 +62,18 @@ describe('Header', () => {
|
||||
|
||||
describe('When user is logged out', () => {
|
||||
it('should show a Login button', () => {
|
||||
renderWithRouter({ user: null, authStatus: 'SIGNED_OUT' });
|
||||
renderWithRouter({ userProfile: null, authStatus: 'SIGNED_OUT' });
|
||||
expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onOpenProfile when Login button is clicked', () => {
|
||||
renderWithRouter({ user: null, authStatus: 'SIGNED_OUT' });
|
||||
renderWithRouter({ userProfile: null, authStatus: 'SIGNED_OUT' });
|
||||
fireEvent.click(screen.getByRole('button', { name: /login/i }));
|
||||
expect(mockOnOpenProfile).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not show user-specific buttons', () => {
|
||||
renderWithRouter({ user: null, authStatus: 'SIGNED_OUT' });
|
||||
renderWithRouter({ userProfile: null, authStatus: 'SIGNED_OUT' });
|
||||
expect(screen.queryByLabelText(/open voice assistant/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText(/open my account settings/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /logout/i })).not.toBeInTheDocument();
|
||||
@@ -76,29 +82,29 @@ describe('Header', () => {
|
||||
|
||||
describe('When user is authenticated', () => {
|
||||
it('should display the user email', () => {
|
||||
renderWithRouter({ user: mockUser, authStatus: 'AUTHENTICATED' });
|
||||
expect(screen.getByText(mockUser.email)).toBeInTheDocument();
|
||||
renderWithRouter({ userProfile: mockUserProfile, authStatus: 'AUTHENTICATED' });
|
||||
expect(screen.getByText(mockUserProfile.user.email)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display "Guest" for anonymous users', () => {
|
||||
renderWithRouter({ user: mockUser, authStatus: 'SIGNED_OUT' });
|
||||
renderWithRouter({ userProfile: mockUserProfile, authStatus: 'SIGNED_OUT' });
|
||||
expect(screen.getByText(/guest/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onOpenVoiceAssistant when microphone icon is clicked', () => {
|
||||
renderWithRouter({ user: mockUser, authStatus: 'AUTHENTICATED' });
|
||||
renderWithRouter({ userProfile: mockUserProfile, authStatus: 'AUTHENTICATED' });
|
||||
fireEvent.click(screen.getByLabelText(/open voice assistant/i));
|
||||
expect(mockOnOpenVoiceAssistant).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should call onOpenProfile when cog icon is clicked', () => {
|
||||
renderWithRouter({ user: mockUser, authStatus: 'AUTHENTICATED' });
|
||||
renderWithRouter({ userProfile: mockUserProfile, authStatus: 'AUTHENTICATED' });
|
||||
fireEvent.click(screen.getByLabelText(/open my account settings/i));
|
||||
expect(mockOnOpenProfile).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should call onSignOut when Logout button is clicked', () => {
|
||||
renderWithRouter({ user: mockUser, authStatus: 'AUTHENTICATED' });
|
||||
renderWithRouter({ userProfile: mockUserProfile, authStatus: 'AUTHENTICATED' });
|
||||
fireEvent.click(screen.getByRole('button', { name: /logout/i }));
|
||||
expect(mockOnSignOut).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -106,14 +112,14 @@ describe('Header', () => {
|
||||
|
||||
describe('Admin user', () => {
|
||||
it('should show the Admin Area link for admin users', () => {
|
||||
renderWithRouter({ user: mockUser, authStatus: 'AUTHENTICATED', profile: mockAdminProfile });
|
||||
renderWithRouter({ userProfile: mockAdminProfile, authStatus: 'AUTHENTICATED' });
|
||||
const adminLink = screen.getByTitle(/admin area/i);
|
||||
expect(adminLink).toBeInTheDocument();
|
||||
expect(adminLink.closest('a')).toHaveAttribute('href', '/admin');
|
||||
});
|
||||
|
||||
it('should not show the Admin Area link for non-admin users', () => {
|
||||
renderWithRouter({ user: mockUser, authStatus: 'AUTHENTICATED', profile: mockProfile });
|
||||
renderWithRouter({ userProfile: mockUserProfile, authStatus: 'AUTHENTICATED' });
|
||||
expect(screen.queryByTitle(/admin area/i)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,21 +6,20 @@ import { Cog8ToothIcon } from './icons/Cog8ToothIcon';
|
||||
import { MicrophoneIcon } from './icons/MicrophoneIcon';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ShieldCheckIcon } from './icons/ShieldCheckIcon';
|
||||
import type { Profile, User } from '../types';
|
||||
import type { UserProfile } from '../types';
|
||||
import type { AuthStatus } from '../hooks/useAuth';
|
||||
|
||||
export interface HeaderProps {
|
||||
isDarkMode: boolean;
|
||||
unitSystem: 'metric' | 'imperial';
|
||||
user: User | null;
|
||||
authStatus: AuthStatus;
|
||||
profile: Profile | null;
|
||||
userProfile: UserProfile | null;
|
||||
onOpenProfile: () => void;
|
||||
onOpenVoiceAssistant: () => void;
|
||||
onSignOut: () => void;
|
||||
}
|
||||
|
||||
export const Header: React.FC<HeaderProps> = ({ isDarkMode, unitSystem, user, authStatus, profile, onOpenProfile, onOpenVoiceAssistant, onSignOut }) => {
|
||||
export const Header: React.FC<HeaderProps> = ({ isDarkMode, unitSystem, authStatus, userProfile, onOpenProfile, onOpenVoiceAssistant, onSignOut }) => {
|
||||
// The state and handlers for the old AuthModal and SignUpModal have been removed.
|
||||
return (
|
||||
<>
|
||||
@@ -34,7 +33,7 @@ export const Header: React.FC<HeaderProps> = ({ isDarkMode, unitSystem, user, au
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 md:space-x-6">
|
||||
{user && (
|
||||
{userProfile && (
|
||||
<button
|
||||
onClick={onOpenVoiceAssistant}
|
||||
className="p-1.5 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700/50 text-gray-500 dark:text-gray-400 transition-colors"
|
||||
@@ -52,13 +51,13 @@ export const Header: React.FC<HeaderProps> = ({ isDarkMode, unitSystem, user, au
|
||||
</div>
|
||||
|
||||
<div className="w-px h-6 bg-gray-200 dark:bg-gray-700 hidden sm:block"></div>
|
||||
{user ? ( // This ternary was missing a 'null' or alternative rendering path for when 'user' is not present.
|
||||
{userProfile ? ( // This ternary was missing a 'null' or alternative rendering path for when 'user' is not present.
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="hidden md:flex items-center space-x-2 text-sm">
|
||||
<UserIcon className="w-5 h-5 text-gray-500 dark:text-gray-400" />
|
||||
{authStatus === 'AUTHENTICATED' ? (
|
||||
// Use the user object from the new auth system
|
||||
<span className="font-medium text-gray-700 dark:text-gray-300">{user.email}</span>
|
||||
<span className="font-medium text-gray-700 dark:text-gray-300">{userProfile.user.email}</span>
|
||||
) : (
|
||||
<span className="font-medium text-gray-500 dark:text-gray-400 italic">Guest</span>
|
||||
)}
|
||||
@@ -71,7 +70,7 @@ export const Header: React.FC<HeaderProps> = ({ isDarkMode, unitSystem, user, au
|
||||
>
|
||||
<Cog8ToothIcon className="w-5 h-5" />
|
||||
</button>
|
||||
{profile?.role === 'admin' && (
|
||||
{userProfile?.role === 'admin' && (
|
||||
<Link to="/admin" className="p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700/50 text-gray-500 dark:text-gray-400 transition-colors" title="Admin Area">
|
||||
<ShieldCheckIcon className="w-5 h-5" />
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user