Files
mealprep/frontend/src/routes/(app)/members/MemberCard.test.ts
Marcel Raddatz 1b5704c8b5 feat(members): implement /members page — Kachel-Ansicht (E2, issue #48)
Backend:
- Rename V006 migration to V026 (avoid conflict with existing V006)
- Migration adds invalidated_at + partial unique index on household_invite

Frontend:
- Toast.svelte — new system component (message + dismiss)
- SegmentedControl.svelte — new system component (options, value, onchange)
- members/+page.server.ts — loads members + active invite
- members/[userId]/+server.ts — DELETE/PATCH proxy
- members/invites/+server.ts — POST (regenerate) proxy
- MemberCard.svelte — tile with avatar, kebab, inline role edit
- RemoveDialog.svelte — confirmation dialog (desktop modal + BottomSheet mobile)
- InviteCard.svelte + InvitePanel.svelte — invite management UI
- MemberGrid.svelte — responsive 4/2-col grid with sorted members
- members/+page.svelte — page composing all components with optimistic updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 19:01:08 +02:00

148 lines
3.6 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import MemberCard from './MemberCard.svelte';
const plannerMember = {
userId: 'u1',
displayName: 'Sarah',
role: 'planner',
joinedAt: '2024-01-01T00:00:00Z'
};
const regularMember = {
userId: 'u2',
displayName: 'Tom',
role: 'member',
joinedAt: '2024-02-01T00:00:00Z'
};
describe('MemberCard', () => {
it('shows the member display name', () => {
render(MemberCard, {
props: {
member: plannerMember,
isCurrentUser: false,
isPlanner: false,
onremove: vi.fn(),
onrolechange: vi.fn()
}
});
expect(screen.getByText('Sarah')).toBeInTheDocument();
});
it('shows "Du"-badge when isCurrentUser is true', () => {
render(MemberCard, {
props: {
member: plannerMember,
isCurrentUser: true,
isPlanner: false,
onremove: vi.fn(),
onrolechange: vi.fn()
}
});
expect(screen.getByText('Du')).toBeInTheDocument();
});
it('does not show kebab button when isCurrentUser is true', () => {
render(MemberCard, {
props: {
member: plannerMember,
isCurrentUser: true,
isPlanner: true,
onremove: vi.fn(),
onrolechange: vi.fn()
}
});
expect(screen.queryByTestId('kebab-btn')).toBeNull();
});
it('does not show kebab button when viewer is not a planner', () => {
render(MemberCard, {
props: {
member: regularMember,
isCurrentUser: false,
isPlanner: false,
onremove: vi.fn(),
onrolechange: vi.fn()
}
});
expect(screen.queryByTestId('kebab-btn')).toBeNull();
});
it('shows kebab button for other members when viewer is planner', () => {
render(MemberCard, {
props: {
member: regularMember,
isCurrentUser: false,
isPlanner: true,
onremove: vi.fn(),
onrolechange: vi.fn()
}
});
expect(screen.getByTestId('kebab-btn')).toBeInTheDocument();
});
it('opens dropdown when kebab is clicked', async () => {
render(MemberCard, {
props: {
member: regularMember,
isCurrentUser: false,
isPlanner: true,
onremove: vi.fn(),
onrolechange: vi.fn()
}
});
await userEvent.click(screen.getByTestId('kebab-btn'));
expect(screen.getByText('Rolle ändern')).toBeInTheDocument();
expect(screen.getByText('Entfernen')).toBeInTheDocument();
});
it('calls onremove when "Entfernen" is clicked in dropdown', async () => {
const onremove = vi.fn();
render(MemberCard, {
props: {
member: regularMember,
isCurrentUser: false,
isPlanner: true,
onremove,
onrolechange: vi.fn()
}
});
await userEvent.click(screen.getByTestId('kebab-btn'));
await userEvent.click(screen.getByText('Entfernen'));
expect(onremove).toHaveBeenCalledWith(regularMember);
});
it('shows SegmentedControl when "Rolle ändern" is clicked', async () => {
render(MemberCard, {
props: {
member: regularMember,
isCurrentUser: false,
isPlanner: true,
onremove: vi.fn(),
onrolechange: vi.fn()
}
});
await userEvent.click(screen.getByTestId('kebab-btn'));
await userEvent.click(screen.getByText('Rolle ändern'));
expect(screen.getByRole('group')).toBeInTheDocument();
});
it('closes dropdown on Escape key', async () => {
render(MemberCard, {
props: {
member: regularMember,
isCurrentUser: false,
isPlanner: true,
onremove: vi.fn(),
onrolechange: vi.fn()
}
});
await userEvent.click(screen.getByTestId('kebab-btn'));
expect(screen.getByText('Entfernen')).toBeInTheDocument();
await userEvent.keyboard('{Escape}');
expect(screen.queryByText('Entfernen')).toBeNull();
});
});