Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions quotevote-frontend/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ const nextConfig: NextConfig = {
formats: ["image/avif", "image/webp"],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
remotePatterns: [
{
protocol: "https",
hostname: "avataaars.io",
pathname: "/**",
},
],
},

// Compress output
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,15 @@ describe('Settings Page', () => {

it('renders settings page heading', () => {
render(<SettingsPageClient />)
expect(screen.getByRole('heading', { name: 'Settings' })).toBeInTheDocument()
expect(screen.getByRole('heading', { name: /settings/i })).toBeInTheDocument()
})

it('renders navigation buttons for Profile, Account, Password', () => {
it('renders unified form with all fields', () => {
render(<SettingsPageClient />)
// Navigation buttons
expect(screen.getByRole('button', { name: 'Profile' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Account' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Password' })).toBeInTheDocument()
expect(screen.getByLabelText('Display Name')).toBeInTheDocument()
expect(screen.getByLabelText('Username')).toBeInTheDocument()
expect(screen.getByLabelText('Email')).toBeInTheDocument()
expect(screen.getByLabelText('Password')).toBeInTheDocument()
})

it('renders profile form with user data by default', () => {
Expand All @@ -96,18 +96,15 @@ describe('Settings Page', () => {
expect(screen.getByDisplayValue('test@example.com')).toBeInTheDocument()
})

it('switches to account section when clicked', () => {
it('renders dark mode toggle', () => {
render(<SettingsPageClient />)
fireEvent.click(screen.getByRole('button', { name: 'Account' }))
expect(screen.getByText('Account Status')).toBeInTheDocument()
expect(screen.getByText('Dark Mode')).toBeInTheDocument()
expect(screen.getByRole('switch', { name: /toggle dark mode/i })).toBeInTheDocument()
})

it('switches to password section when clicked', () => {
it('renders optional password field', () => {
render(<SettingsPageClient />)
fireEvent.click(screen.getByRole('button', { name: 'Password' }))
expect(screen.getByText('Change Password')).toBeInTheDocument()
expect(screen.getByText('Current Password')).toBeInTheDocument()
expect(screen.getByText('New Password')).toBeInTheDocument()
expect(screen.getByPlaceholderText('Leave blank to keep current password')).toBeInTheDocument()
})

it('shows save button as disabled when form is pristine', () => {
Expand Down Expand Up @@ -137,27 +134,19 @@ describe('Settings Page', () => {
expect(mockPush).toHaveBeenCalledWith('/dashboard/profile/testuser/avatar')
})

it('shows account status as Active', () => {
it('renders sign out button', () => {
render(<SettingsPageClient />)
fireEvent.click(screen.getByRole('button', { name: 'Account' }))
expect(screen.getByText('Active')).toBeInTheDocument()
expect(screen.getByRole('button', { name: /sign out/i })).toBeInTheDocument()
})

it('validates password requirements', async () => {
render(<SettingsPageClient />)
fireEvent.click(screen.getByRole('button', { name: 'Password' }))

const currentPw = screen.getByPlaceholderText('Enter current password')
fireEvent.change(currentPw, { target: { value: 'oldpass123' } })
const pwInput = screen.getByPlaceholderText('Leave blank to keep current password')
fireEvent.change(pwInput, { target: { value: 'short' } })

const newPw = screen.getByPlaceholderText('Enter new password')
fireEvent.change(newPw, { target: { value: 'short' } })

const confirmPw = screen.getByPlaceholderText('Confirm new password')
fireEvent.change(confirmPw, { target: { value: 'short' } })

const updateButton = screen.getByRole('button', { name: /update password/i })
fireEvent.click(updateButton)
const saveButton = screen.getByRole('button', { name: /save changes/i })
fireEvent.click(saveButton)

await waitFor(() => {
expect(screen.getByText(/at least 8 characters/i)).toBeInTheDocument()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ describe('CustomButtons Components', () => {
user: {
loading: false,
loginError: null,
data: { _followingId: '' },
data: { _followingId: [] },
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ jest.mock('@apollo/client/react', () => {
})

// Mock child components
jest.mock('@/components/Avatar', () => ({
__esModule: true,
default: ({ alt }: { alt: string }) => <div data-testid="avatar">{alt}</div>,
jest.mock('@/components/DisplayAvatar', () => ({
DisplayAvatar: ({ username }: { username?: string }) => (
<div data-testid="avatar">{username}</div>
),
}))

jest.mock('@/components/Comment/CommentReactions', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,14 @@ jest.mock('next/navigation', () => ({
}),
}))

// Mock Avatar component
jest.mock('@/components/Avatar', () => ({
__esModule: true,
default: ({ src, alt, size, onClick, className }: {
src?: string;
alt: string;
size: number;
onClick?: () => void;
// Mock DisplayAvatar component
jest.mock('@/components/DisplayAvatar', () => ({
DisplayAvatar: ({ username, className }: {
username?: string;
className?: string;
}) => (
<div
data-testid="avatar"
data-src={src}
data-alt={alt}
data-size={size}
onClick={onClick}
className={className}
>
Avatar
<div data-testid="avatar" className={className}>
{username}
</div>
),
}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('FollowButton', () => {
fireEvent.click(screen.getByText('Follow'));

await waitFor(() => {
expect(mockUpdateFollowing).toHaveBeenCalledWith('profile-user-id');
expect(mockUpdateFollowing).toHaveBeenCalledWith(['profile-user-id']);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('FollowInfo', () => {
loginError: null,
data: {
_id: 'currentuser',
_followingId: 'user1',
_followingId: ['user1'],
},
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,28 @@
/**
* ProfileAvatar Component Tests
*
* Tests for the ProfileAvatar component including:
* - Rendering with different sizes
* - Avatar source handling
* - Store integration
*/

import { render } from '../../utils/test-utils';
import { ProfileAvatar } from '../../../components/Profile/ProfileAvatar';
import { useAppStore } from '@/store';

// Mock the Avatar component (default export)
jest.mock('../../../components/Avatar', () => ({
__esModule: true,
default: ({ src, alt, size }: { src?: string; alt?: string; size?: string | number }) => (
<div data-testid="avatar" data-src={src} data-alt={alt} data-size={String(size)}>
Avatar
jest.mock('../../../components/DisplayAvatar', () => ({
DisplayAvatar: ({ avatar, username, size }: { avatar?: unknown; username?: string; size?: number }) => (
<div
data-testid="display-avatar"
data-avatar={JSON.stringify(avatar)}
data-username={username}
data-size={String(size)}
>
DisplayAvatar
</div>
),
}));

describe('ProfileAvatar', () => {
beforeEach(() => {
// Reset store before each test
useAppStore.setState({
user: {
loading: false,
loginError: null,
data: {},
},
user: { loading: false, loginError: null, data: {} },
});
});

Expand All @@ -38,67 +31,68 @@ describe('ProfileAvatar', () => {
expect(container).toBeInTheDocument();
});

it('renders with default size', () => {
it('renders with default size (md → 40px)', () => {
const { getByTestId } = render(<ProfileAvatar />);
const avatar = getByTestId('avatar');
expect(avatar).toHaveAttribute('data-size', 'md');
expect(getByTestId('display-avatar')).toHaveAttribute('data-size', '40');
});

it('renders with custom size', () => {
it('renders with lg size (64px)', () => {
const { getByTestId } = render(<ProfileAvatar size="lg" />);
const avatar = getByTestId('avatar');
expect(avatar).toHaveAttribute('data-size', 'lg');
expect(getByTestId('display-avatar')).toHaveAttribute('data-size', '64');
});

it('handles string avatar from store', () => {
it('renders with numeric size', () => {
const { getByTestId } = render(<ProfileAvatar size={80} />);
expect(getByTestId('display-avatar')).toHaveAttribute('data-size', '80');
});

it('passes username from store to DisplayAvatar', () => {
useAppStore.setState({
user: {
loading: false,
loginError: null,
data: {
avatar: 'https://example.com/avatar.jpg',
},
data: { username: 'testuser' },
},
});

const { getByTestId } = render(<ProfileAvatar />);
const avatar = getByTestId('avatar');
expect(avatar).toHaveAttribute('data-src', 'https://example.com/avatar.jpg');
expect(getByTestId('display-avatar')).toHaveAttribute('data-username', 'testuser');
});

it('handles object avatar with url from store', () => {
useAppStore.setState({
user: {
loading: false,
loginError: null,
data: {
// @ts-expect-error - Testing avatar as object even though store type is string
avatar: {
url: 'https://example.com/avatar.jpg',
},
},
},
});

it('passes avatar qualities object to DisplayAvatar', () => {
const qualities = { topType: 'ShortHairShortFlat', skinColor: 'Light' };
useAppStore.setState({
user: {
loading: false,
loginError: null,
// @ts-expect-error - avatar as object in test
data: { avatar: qualities },
},
});
const { getByTestId } = render(<ProfileAvatar />);
const avatar = getByTestId('avatar');
expect(avatar).toHaveAttribute('data-src', 'https://example.com/avatar.jpg');
expect(getByTestId('display-avatar')).toHaveAttribute(
'data-avatar',
JSON.stringify(qualities)
);
});

it('handles missing avatar gracefully', () => {
useAppStore.setState({
user: {
loading: false,
loginError: null,
data: {},
},
});

const { getByTestId } = render(<ProfileAvatar />);
const avatar = getByTestId('avatar');
// Avatar src can be undefined or empty string
const src = avatar.getAttribute('data-src');
expect(src === '' || src === null || src === undefined).toBe(true);
it('passes string avatar URL to DisplayAvatar', () => {
useAppStore.setState({
user: {
loading: false,
loginError: null,
data: { avatar: 'https://example.com/avatar.jpg' },
},
});
});
const { getByTestId } = render(<ProfileAvatar />);
expect(getByTestId('display-avatar')).toHaveAttribute(
'data-avatar',
'"https://example.com/avatar.jpg"'
);
});

it('renders without avatar (shows default cartoon)', () => {
const { getByTestId } = render(<ProfileAvatar />);
// Should still render — DisplayAvatar will generate a default
expect(getByTestId('display-avatar')).toBeInTheDocument();
});
});
Loading
Loading