fix(ui): profile page directive + avatar dropdown links/fallbacks
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import { cleanup, render, screen } from '@testing-library/react';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { api } from '@spoon/backend/convex/_generated/api.js';
|
||||
|
||||
import { AvatarDropdown } from '../../src/components/layout/header/controls/AvatarDropdown';
|
||||
|
||||
// Stable sentinels: the real Convex `api` proxy hands back a fresh reference on
|
||||
// every access, so `===` comparisons in the mocks below would never match.
|
||||
vi.mock('@spoon/backend/convex/_generated/api.js', () => ({
|
||||
api: {
|
||||
auth: {
|
||||
getUser: 'auth:getUser',
|
||||
},
|
||||
files: {
|
||||
getImageUrl: 'files:getImageUrl',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const { mockUseConvexAuth, mockUseQuery } = vi.hoisted(() => ({
|
||||
mockUseConvexAuth: vi.fn(),
|
||||
mockUseQuery: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('convex/react', () => ({
|
||||
useConvexAuth: mockUseConvexAuth,
|
||||
useQuery: mockUseQuery,
|
||||
}));
|
||||
|
||||
vi.mock('@convex-dev/auth/react', () => ({
|
||||
useAuthActions: () => ({ signOut: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/avatar', () => ({
|
||||
isRemoteImageUrl: () => false,
|
||||
}));
|
||||
|
||||
// Render the dropdown primitives as pass-through wrappers so the menu content
|
||||
// is deterministically present under jsdom (Radix pointer behavior is flaky
|
||||
// here). We are exercising AvatarDropdown's logic, not Radix.
|
||||
vi.mock('@spoon/ui', () => ({
|
||||
BasedAvatar: () => <div>avatar</div>,
|
||||
Button: (props: ComponentProps<'button'>) => <button {...props} />,
|
||||
DropdownMenu: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
|
||||
DropdownMenuTrigger: ({ children }: { children?: ReactNode }) => (
|
||||
<>{children}</>
|
||||
),
|
||||
DropdownMenuContent: ({ children }: { children?: ReactNode }) => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
DropdownMenuItem: ({ children }: { children?: ReactNode }) => <>{children}</>,
|
||||
DropdownMenuLabel: ({ children }: { children?: ReactNode }) => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
DropdownMenuSeparator: () => <hr />,
|
||||
}));
|
||||
|
||||
const seedUser = (user: { name: string; email: string }) => {
|
||||
mockUseConvexAuth.mockReturnValue({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
});
|
||||
mockUseQuery.mockImplementation((reference: unknown) => {
|
||||
if (reference === api.auth.getUser) return user;
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('AvatarDropdown', () => {
|
||||
it('falls back to the email when the name is an empty string', () => {
|
||||
seedUser({ name: '', email: 'me@x.com' });
|
||||
render(<AvatarDropdown />);
|
||||
expect(screen.getByText('me@x.com')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the trimmed name when it is present', () => {
|
||||
seedUser({ name: 'Gib', email: 'me@x.com' });
|
||||
render(<AvatarDropdown />);
|
||||
expect(screen.getByText('Gib')).toBeInTheDocument();
|
||||
expect(screen.queryByText('me@x.com')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('links Edit Profile directly to the settings profile page', () => {
|
||||
seedUser({ name: 'Gib', email: 'me@x.com' });
|
||||
render(<AvatarDropdown />);
|
||||
const editLink = screen.getByRole('link', { name: /edit profile/i });
|
||||
expect(editLink).toHaveAttribute('href', '/settings/profile');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user