MeshiTrack/packages/web/tests/app/(dashboard)/stores/page.test.tsx

749 lines
25 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
const { mockUseApi } = vi.hoisted(() => ({
mockUseApi: vi.fn(),
}));
const { mockListStores, mockCreateStore, mockUpdateStore, mockDeactivateStore } = vi.hoisted(
() => ({
mockListStores: vi.fn(),
mockCreateStore: vi.fn(),
mockUpdateStore: vi.fn(),
mockDeactivateStore: vi.fn(),
}),
);
vi.mock('@/lib/useApi', () => ({ useApi: mockUseApi }));
vi.mock('@/services/stores', () => ({
listStores: mockListStores,
createStore: mockCreateStore,
updateStore: mockUpdateStore,
deactivateStore: mockDeactivateStore,
}));
vi.mock('next/link', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return { default: (props: any) => props.children };
});
import StoresPage from '../../../../src/app/(dashboard)/stores/page';
beforeEach(() => vi.clearAllMocks());
describe('StoresPage', () => {
it('shows loading skeleton when session is loading', () => {
mockUseApi.mockReturnValue({ householdId: null, isLoading: true });
render(<StoresPage />);
expect(screen.getByText('Stores')).toBeInTheDocument();
expect(screen.queryByText('Add Store')).not.toBeInTheDocument();
});
it('shows household prompt when no householdId', () => {
mockUseApi.mockReturnValue({ householdId: null, isLoading: false });
render(<StoresPage />);
expect(screen.getByText(/create or join a household/)).toBeInTheDocument();
});
it('renders store list when household exists', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{
_id: 'st-1',
name: 'Walgreens',
tags: ['pharmacy'],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
updatedAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
],
pagination: { cursor: null, hasMore: false },
});
render(<StoresPage />);
await waitFor(() => {
expect(screen.getByText('Walgreens')).toBeInTheDocument();
});
});
it('shows empty state when no stores', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [],
pagination: { cursor: null, hasMore: false },
});
render(<StoresPage />);
await waitFor(() => {
expect(screen.getByText(/No stores yet/)).toBeInTheDocument();
});
});
it('shows Add Store form when button clicked', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [],
pagination: { cursor: null, hasMore: false },
});
render(<StoresPage />);
await waitFor(() => {
expect(screen.getByText('Add Store')).toBeInTheDocument();
});
await userEvent.click(screen.getByText('Add Store'));
expect(screen.getByPlaceholderText('e.g. Walgreens')).toBeInTheDocument();
});
it('shows error when store list fails to load', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockRejectedValue(new Error('Network error'));
render(<StoresPage />);
await waitFor(() => {
expect(screen.getByText('Network error')).toBeInTheDocument();
});
});
it('deactivates store after confirmation', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{
_id: 'st-1',
name: 'CVS',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
updatedAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
],
pagination: { cursor: null, hasMore: false },
});
mockDeactivateStore.mockResolvedValue({});
vi.spyOn(window, 'confirm').mockReturnValue(true);
render(<StoresPage />);
await waitFor(() => {
expect(screen.getByText('CVS')).toBeInTheDocument();
});
await userEvent.click(screen.getByTitle('Deactivate'));
expect(mockDeactivateStore).toHaveBeenCalledWith('hh1', 'st-1');
});
it('creates a store on form submit', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
mockCreateStore.mockResolvedValue({
_id: 'st-new',
name: 'Walmart',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
});
render(<StoresPage />);
await waitFor(() => screen.getByText('Add Store'));
await userEvent.click(screen.getByText('Add Store'));
await waitFor(() => screen.getByPlaceholderText('e.g. Walgreens'));
fireEvent.change(screen.getByPlaceholderText('e.g. Walgreens'), {
target: { value: 'Walmart' },
});
fireEvent.submit(screen.getByPlaceholderText('e.g. Walgreens').closest('form')!);
await waitFor(() =>
expect(mockCreateStore).toHaveBeenCalledWith(
'hh1',
expect.objectContaining({ name: 'Walmart' }),
),
);
});
it('shows error when create store fails', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
mockCreateStore.mockRejectedValue(new Error('Store already exists'));
render(<StoresPage />);
await waitFor(() => screen.getByText('Add Store'));
await userEvent.click(screen.getByText('Add Store'));
await waitFor(() => screen.getByPlaceholderText('e.g. Walgreens'));
fireEvent.change(screen.getByPlaceholderText('e.g. Walgreens'), {
target: { value: 'Walmart' },
});
fireEvent.submit(screen.getByPlaceholderText('e.g. Walgreens').closest('form')!);
await waitFor(() => expect(screen.getByText('Store already exists')).toBeInTheDocument());
});
it('opens edit form for a store', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{
_id: 'st-1',
name: 'CVS',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
],
pagination: { cursor: null, hasMore: false },
});
render(<StoresPage />);
await waitFor(() => screen.getByText('CVS'));
await userEvent.click(screen.getByTitle('Edit'));
expect(screen.getByDisplayValue('CVS')).toBeInTheDocument();
expect(screen.getByText('Edit Store')).toBeInTheDocument();
});
it('saves edited store', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{
_id: 'st-1',
name: 'CVS',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
],
pagination: { cursor: null, hasMore: false },
});
mockUpdateStore.mockResolvedValue({});
render(<StoresPage />);
await waitFor(() => screen.getByText('CVS'));
await userEvent.click(screen.getByTitle('Edit'));
await waitFor(() => screen.getByDisplayValue('CVS'));
fireEvent.change(screen.getByDisplayValue('CVS'), { target: { value: 'CVS Pharmacy' } });
fireEvent.submit(screen.getByDisplayValue('CVS Pharmacy').closest('form')!);
await waitFor(() =>
expect(mockUpdateStore).toHaveBeenCalledWith(
'hh1',
'st-1',
expect.objectContaining({ name: 'CVS Pharmacy' }),
),
);
});
it('dismisses error', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockRejectedValue(new Error('Network error'));
render(<StoresPage />);
await waitFor(() => screen.getByText('Network error'));
await userEvent.click(screen.getByText('Dismiss'));
expect(screen.queryByText('Network error')).not.toBeInTheDocument();
});
it('shows error when deactivate fails', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{
_id: 'st-1',
name: 'CVS',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
],
pagination: { cursor: null, hasMore: false },
});
mockDeactivateStore.mockRejectedValue(new Error('Deactivate failed'));
vi.spyOn(window, 'confirm').mockReturnValue(true);
render(<StoresPage />);
await waitFor(() => screen.getByTitle('Deactivate'));
await userEvent.click(screen.getByTitle('Deactivate'));
await waitFor(() => expect(screen.getByText('Deactivate failed')).toBeInTheDocument());
});
it('cancels the create store form', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
render(<StoresPage />);
await waitFor(() => screen.getByText('Add Store'));
await userEvent.click(screen.getByText('Add Store'));
await waitFor(() => screen.getByPlaceholderText('e.g. Walgreens'));
await userEvent.click(
screen.getAllByRole('button', { name: 'Cancel' })[
screen.getAllByRole('button', { name: 'Cancel' }).length - 1
]!,
);
expect(screen.queryByPlaceholderText('e.g. Walgreens')).not.toBeInTheDocument();
});
it('cancels the edit store form', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{
_id: 'st-1',
name: 'CVS',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
],
pagination: { cursor: null, hasMore: false },
});
render(<StoresPage />);
await waitFor(() => screen.getByText('CVS'));
await userEvent.click(screen.getByTitle('Edit'));
await waitFor(() => screen.getByDisplayValue('CVS'));
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
expect(screen.queryByDisplayValue('CVS')).not.toBeInTheDocument();
});
it('filters stores by tag', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{
_id: 'st-1',
name: 'Walgreens',
tags: ['pharmacy'],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
{
_id: 'st-2',
name: 'Costco',
tags: ['supermarket'],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
],
pagination: { cursor: null, hasMore: false },
});
render(<StoresPage />);
await waitFor(() => screen.getByText('Walgreens'));
expect(screen.getByText('Costco')).toBeInTheDocument();
fireEvent.change(screen.getByDisplayValue('All tags'), { target: { value: 'pharmacy' } });
await waitFor(() => expect(screen.queryByText('Costco')).not.toBeInTheDocument());
expect(screen.getByText('Walgreens')).toBeInTheDocument();
});
it('changes store form fields (notes, address, url)', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
render(<StoresPage />);
await waitFor(() => screen.getByText('Add Store'));
await userEvent.click(screen.getByText('Add Store'));
await waitFor(() => screen.getByPlaceholderText('e.g. Walgreens'));
fireEvent.change(screen.getByPlaceholderText('123 Main St'), {
target: { value: '456 Oak Ave' },
});
fireEvent.change(screen.getByPlaceholderText('https://walgreens.com'), {
target: { value: 'https://newurl.com' },
});
fireEvent.change(screen.getByPlaceholderText('Any notes'), {
target: { value: 'Good prices' },
});
expect(screen.getByPlaceholderText('e.g. Walgreens')).toBeInTheDocument();
});
it('toggles a preset tag and adds a custom tag', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
render(<StoresPage />);
await waitFor(() => screen.getByText('Add Store'));
await userEvent.click(screen.getByText('Add Store'));
await waitFor(() => screen.getByPlaceholderText('Custom tag...'));
// Toggle a preset tag (e.g. 'pharmacy')
await userEvent.click(screen.getByRole('button', { name: 'pharmacy' }));
// Toggle it off again
await userEvent.click(screen.getByRole('button', { name: 'pharmacy' }));
// Add a custom tag via button
fireEvent.change(screen.getByPlaceholderText('Custom tag...'), { target: { value: 'mytag' } });
await userEvent.click(screen.getByRole('button', { name: 'Add' }));
await waitFor(() => expect(screen.getByText('mytag')).toBeInTheDocument());
// Remove it via the × button
await userEvent.click(screen.getByText('×'));
expect(screen.queryByText('mytag')).not.toBeInTheDocument();
});
it('adds custom tag via Enter key', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
render(<StoresPage />);
await waitFor(() => screen.getByText('Add Store'));
await userEvent.click(screen.getByText('Add Store'));
await waitFor(() => screen.getByPlaceholderText('Custom tag...'));
fireEvent.change(screen.getByPlaceholderText('Custom tag...'), { target: { value: 'keytag' } });
fireEvent.keyDown(screen.getByPlaceholderText('Custom tag...'), { key: 'Enter' });
await waitFor(() => expect(screen.getByText('keytag')).toBeInTheDocument());
});
it('ignores custom tag via non-Enter keydown', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
render(<StoresPage />);
await waitFor(() => screen.getByText('Add Store'));
await userEvent.click(screen.getByText('Add Store'));
await waitFor(() => screen.getByPlaceholderText('Custom tag...'));
fireEvent.change(screen.getByPlaceholderText('Custom tag...'), { target: { value: 'escapetag' } });
fireEvent.keyDown(screen.getByPlaceholderText('Custom tag...'), { key: 'Escape' });
expect(screen.queryByText('escapetag')).not.toBeInTheDocument();
});
it('renders store card with missing optional parameters correctly', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{
_id: 'st-addr',
name: 'Address Only Store',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
address: '123 Unique Way',
url: undefined,
notes: undefined
},
{
_id: 'st-url',
name: 'URL Only Store',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
address: undefined,
url: 'https://onlyurl.com',
notes: undefined
},
{
_id: 'st-notes',
name: 'Notes Only Store',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
address: undefined,
url: undefined,
notes: 'Special instruction notes'
}
],
pagination: { cursor: null, hasMore: false }
});
render(<StoresPage />);
await waitFor(() => expect(screen.getByText('Address Only Store')).toBeInTheDocument());
expect(screen.getByText('123 Unique Way')).toBeInTheDocument();
expect(screen.getByText('https://onlyurl.com')).toBeInTheDocument();
expect(screen.getByText('Special instruction notes')).toBeInTheDocument();
});
it('changes search filter', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{
_id: 'st-1',
name: 'Walgreens',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
{
_id: 'st-2',
name: 'CVS',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
],
pagination: { cursor: null, hasMore: false },
});
render(<StoresPage />);
await waitFor(() => screen.getByText('Walgreens'));
fireEvent.change(screen.getByPlaceholderText('Search stores...'), { target: { value: 'wal' } });
await waitFor(() => expect(screen.queryByText('CVS')).not.toBeInTheDocument());
expect(screen.getByText('Walgreens')).toBeInTheDocument();
});
it('toggles isActive in edit form', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{
_id: 'st-1',
name: 'CVS',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
],
pagination: { cursor: null, hasMore: false },
});
mockUpdateStore.mockResolvedValue({});
render(<StoresPage />);
await waitFor(() => screen.getByText('CVS'));
await userEvent.click(screen.getByTitle('Edit'));
await waitFor(() => screen.getByLabelText('Active'));
fireEvent.click(screen.getByLabelText('Active'));
expect(screen.getByLabelText('Active')).not.toBeChecked();
});
it('shows inactive stores when toggle checked', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{
_id: 'st-1',
name: 'Active Store',
tags: [],
isActive: true,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
{
_id: 'st-2',
name: 'Old Store',
tags: [],
isActive: false,
createdAt: '2026-01-01T00:00:00.000Z',
householdId: 'hh1',
createdBy: 'u-1',
},
],
pagination: { cursor: null, hasMore: false },
});
render(<StoresPage />);
await waitFor(() => screen.getByText('Active Store'));
expect(screen.queryByText('Old Store')).not.toBeInTheDocument();
fireEvent.click(screen.getByLabelText('Show inactive'));
expect(screen.getByText('Old Store')).toBeInTheDocument();
});
it('handles create store generic error fallback', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
mockCreateStore.mockRejectedValue('Generic store save error');
render(<StoresPage />);
await waitFor(() => screen.getByText('Add Store'));
await userEvent.click(screen.getByText('Add Store'));
await waitFor(() => screen.getByPlaceholderText('e.g. Walgreens'));
fireEvent.change(screen.getByPlaceholderText('e.g. Walgreens'), { target: { value: 'Walmart' } });
fireEvent.submit(screen.getByPlaceholderText('e.g. Walgreens').closest('form')!);
await waitFor(() => expect(screen.getByText('Failed to save store')).toBeInTheDocument());
});
it('handles list stores generic error fallback', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockRejectedValue('Generic list error');
render(<StoresPage />);
await waitFor(() => expect(screen.getByText('Failed to load stores')).toBeInTheDocument());
});
it('handles deactivate store generic error fallback', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{ _id: 'st-1', name: 'CVS', tags: [], isActive: true, createdAt: '2026-01-01T00:00:00.000Z', householdId: 'hh1', createdBy: 'u-1' }
],
pagination: { cursor: null, hasMore: false },
});
mockDeactivateStore.mockRejectedValue('Generic deactivate error');
vi.spyOn(window, 'confirm').mockReturnValue(true);
render(<StoresPage />);
await waitFor(() => screen.getByTitle('Deactivate'));
await userEvent.click(screen.getByTitle('Deactivate'));
await waitFor(() => expect(screen.getByText('Failed to deactivate store')).toBeInTheDocument());
});
it('skips deactivation if confirmation is rejected', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({
data: [
{ _id: 'st-1', name: 'CVS', tags: [], isActive: true, createdAt: '2026-01-01T00:00:00.000Z', householdId: 'hh1', createdBy: 'u-1' }
],
pagination: { cursor: null, hasMore: false },
});
vi.spyOn(window, 'confirm').mockReturnValue(false);
render(<StoresPage />);
await waitFor(() => screen.getByTitle('Deactivate'));
await userEvent.click(screen.getByTitle('Deactivate'));
expect(mockDeactivateStore).not.toHaveBeenCalled();
});
it('handles empty optional fields creation gracefully', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
mockCreateStore.mockResolvedValue({} as any);
render(<StoresPage />);
await waitFor(() => screen.getByText('Add Store'));
await userEvent.click(screen.getByText('Add Store'));
await waitFor(() => screen.getByPlaceholderText('e.g. Walgreens'));
fireEvent.change(screen.getByPlaceholderText('e.g. Walgreens'), { target: { value: 'Walmart' } });
fireEvent.change(screen.getByPlaceholderText('123 Main St'), { target: { value: ' ' } });
fireEvent.change(screen.getByPlaceholderText('https://walgreens.com'), { target: { value: ' ' } });
fireEvent.change(screen.getByPlaceholderText('Any notes'), { target: { value: ' ' } });
fireEvent.submit(screen.getByPlaceholderText('e.g. Walgreens').closest('form')!);
await waitFor(() => {
expect(mockCreateStore).toHaveBeenCalledWith('hh1', {
name: 'Walmart',
address: undefined,
url: undefined,
notes: undefined,
tags: [],
isActive: true
});
});
});
it('renders empty search message when filters are applied with no match', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
render(<StoresPage />);
await waitFor(() => expect(screen.getByText(/No stores yet/)).toBeInTheDocument());
fireEvent.change(screen.getByPlaceholderText('Search stores...'), { target: { value: 'MissingName' } });
await waitFor(() => {
expect(screen.getByText('No stores match your filters.')).toBeInTheDocument();
});
});
it('prevents adding duplicate custom tags', async () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false });
mockListStores.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false } });
render(<StoresPage />);
await waitFor(() => screen.getByText('Add Store'));
await userEvent.click(screen.getByText('Add Store'));
await waitFor(() => screen.getByPlaceholderText('Custom tag...'));
const input = screen.getByPlaceholderText('Custom tag...');
// Add tag once
fireEvent.change(input, { target: { value: 'duplicated' } });
fireEvent.keyDown(input, { key: 'Enter' });
await waitFor(() => expect(screen.getByText('duplicated')).toBeInTheDocument());
// Attempt to add it again
fireEvent.change(input, { target: { value: 'duplicated' } });
fireEvent.keyDown(input, { key: 'Enter' });
// Should only exist once in the document
expect(screen.getAllByText('duplicated')).toHaveLength(1);
});
});