Files
TREK/client/src/components/Collections/BulkAssignLabelModal.test.tsx
T
Maurice acd003ea67 feat(dialogs): Vacay, Collections, Atlas and Journey in the new dialog look
Every popup of the four pages moves to the shared dialog frame the trip
uses: head band with its icon, the scrolling body, the footer with its
actions, and ConfirmDialog for the questions.

- Vacay: settings, removing a year (now says so when it fails), the
  fusion request, inviting a person, a person's colour and sharing the
  calendar.
- Collections: new and edited lists, adding a place, importing a file and
  a trip's places, sharing, labels and assigning them, copying to a trip,
  moving or duplicating, saving a trip's places and deleting a list. The
  stylesheet of the old import view goes.
- Atlas: the country popup with the flag in its head, its choices as rows
  and the wishlist step; Dawarich's Atlas dialog and its accept dialog on
  the desktop.
- Journey: creating a journey, its settings, linking a trip, inviting a
  contributor, the entry editor (discarding asks in a dialog) and the
  photo provider picker; the entry card's menu is the shared one.

The phone keeps its own sheets: where a dialog is shared with it
(Dawarich, the contributor invite, the provider picker) only the desktop
frame changes.
2026-09-28 16:52:32 +02:00

128 lines
5.1 KiB
TypeScript

// FE-COMP-BULKLABEL-001 to FE-COMP-BULKLABEL-009
import React from 'react';
import { render, screen, waitFor } from '../../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import type { CollectionLabel } from '@trek/shared';
import { useTranslation } from '../../i18n/TranslationContext';
import BulkAssignLabelModal from './BulkAssignLabelModal';
type ModalProps = Omit<React.ComponentProps<typeof BulkAssignLabelModal>, 't'>;
function Harness(props: ModalProps): React.ReactElement {
const { t } = useTranslation();
return <BulkAssignLabelModal {...props} t={t} />;
}
const berlin: CollectionLabel = { id: 1, collection_id: 10, name: 'Berlin', color: '#0ea5e9' };
const food: CollectionLabel = { id: 2, collection_id: 10, name: 'Food', color: null };
function renderModal(overrides: Partial<ModalProps> = {}) {
const props: ModalProps = {
isOpen: true,
labels: [berlin, food],
count: 4,
onAssign: vi.fn(async () => {}),
onManage: vi.fn(),
onClose: vi.fn(),
...overrides,
};
render(<Harness {...props} />);
return props;
}
describe('BulkAssignLabelModal', () => {
it('FE-COMP-BULKLABEL-001: titles the modal with the selection count and lists every label', () => {
renderModal({ count: 4 });
expect(screen.getByRole('heading', { name: 'Add labels to 4 places' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Berlin' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Food' })).toBeInTheDocument();
});
it('FE-COMP-BULKLABEL-002: with no labels it explains why and offers the manager instead', async () => {
const user = userEvent.setup();
const props = renderModal({ labels: [] });
expect(screen.getByText('Create a label first to group places in this list.')).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Assign label' })).not.toBeInTheDocument();
await user.click(screen.getByRole('button', { name: /Manage labels/ }));
expect(props.onManage).toHaveBeenCalledTimes(1);
});
it('FE-COMP-BULKLABEL-003: the assign button is disabled until at least one label is picked', async () => {
const user = userEvent.setup();
renderModal();
const assign = screen.getByRole('button', { name: /^Assign label$/ });
expect(assign).toBeDisabled();
await user.click(screen.getByRole('button', { name: 'Berlin' }));
expect(assign).toBeEnabled();
});
it('FE-COMP-BULKLABEL-004: picking a label marks the row selected, clicking again unpicks it', async () => {
const user = userEvent.setup();
renderModal();
const berlinRow = screen.getByRole('button', { name: 'Berlin' });
await user.click(berlinRow);
expect(berlinRow).toHaveAttribute('aria-pressed', 'true');
await user.click(berlinRow);
expect(berlinRow).toHaveAttribute('aria-pressed', 'false');
expect(screen.getByRole('button', { name: /^Assign label$/ })).toBeDisabled();
});
it('FE-COMP-BULKLABEL-005: assigning sends every picked id in click order and clears the selection', async () => {
const user = userEvent.setup();
const props = renderModal();
await user.click(screen.getByRole('button', { name: 'Food' }));
await user.click(screen.getByRole('button', { name: 'Berlin' }));
await user.click(screen.getByRole('button', { name: /^Assign label$/ }));
await waitFor(() => expect(props.onAssign).toHaveBeenCalledWith([2, 1]));
// Selection resets so the modal is ready for the next batch.
await waitFor(() => expect(screen.getByRole('button', { name: /^Assign label$/ })).toBeDisabled());
expect(screen.getByRole('button', { name: 'Berlin' })).toHaveAttribute('aria-pressed', 'false');
});
it('FE-COMP-BULKLABEL-006: a second click while the assign is pending is ignored', async () => {
const user = userEvent.setup();
const onAssign = vi.fn(() => new Promise<void>(() => {}));
renderModal({ onAssign });
await user.click(screen.getByRole('button', { name: 'Berlin' }));
const assign = screen.getByRole('button', { name: /^Assign label$/ });
await user.click(assign);
await user.click(assign);
expect(onAssign).toHaveBeenCalledTimes(1);
expect(assign.querySelector('.animate-spin')).not.toBeNull();
expect(assign).toBeDisabled();
});
it('FE-COMP-BULKLABEL-007: Cancel closes without assigning anything', async () => {
const user = userEvent.setup();
const props = renderModal();
await user.click(screen.getByRole('button', { name: 'Berlin' }));
await user.click(screen.getByRole('button', { name: 'Cancel' }));
expect(props.onClose).toHaveBeenCalledTimes(1);
expect(props.onAssign).not.toHaveBeenCalled();
});
it('FE-COMP-BULKLABEL-008: the footer manage shortcut opens the label manager', async () => {
const user = userEvent.setup();
const props = renderModal();
await user.click(screen.getByRole('button', { name: /Manage labels/ }));
expect(props.onManage).toHaveBeenCalledTimes(1);
});
it('FE-COMP-BULKLABEL-009: renders nothing while closed', () => {
renderModal({ isOpen: false });
expect(screen.queryByRole('heading', { name: /Add labels to/ })).not.toBeInTheDocument();
});
});