diff --git a/components/contacts/__tests__/contact-list-item.test.tsx b/components/contacts/__tests__/contact-list-item.test.tsx index d45560265..5f658ac52 100644 --- a/components/contacts/__tests__/contact-list-item.test.tsx +++ b/components/contacts/__tests__/contact-list-item.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, act } from '@testing-library/react'; import { describe, it, expect, vi } from 'vitest'; import { ContactListItem } from '../contact-list-item'; import type { ContactCard } from '@/lib/jmap/types'; @@ -82,4 +82,24 @@ describe('ContactListItem', () => { const { container } = render(); expect(container.querySelector('[data-testid="avatar"]') || container.querySelector('.rounded-full')).toBeNull(); }); + + it('opens the row menu on long-press without leaking the press state onto the row', () => { + const errors = vi.spyOn(console, 'error').mockImplementation(() => {}); + vi.useFakeTimers(); + try { + const onLongPress = vi.fn(); + const { container } = render(); + const row = container.firstElementChild!; + const touchStart = new Event('touchstart', { bubbles: true }); + Object.defineProperty(touchStart, 'touches', { value: [{ clientX: 5, clientY: 5 }] }); + act(() => { row.dispatchEvent(touchStart); }); + act(() => { vi.advanceTimersByTime(600); }); + expect(onLongPress).toHaveBeenCalledWith(row); + const unknownProp = errors.mock.calls.some((args) => args.some((a) => String(a).includes('isPressed'))); + expect(unknownProp).toBe(false); + } finally { + vi.useRealTimers(); + errors.mockRestore(); + } + }); }); diff --git a/components/contacts/__tests__/contacts-sidebar.test.tsx b/components/contacts/__tests__/contacts-sidebar.test.tsx index 30b3f73a2..c9939eb04 100644 --- a/components/contacts/__tests__/contacts-sidebar.test.tsx +++ b/components/contacts/__tests__/contacts-sidebar.test.tsx @@ -38,6 +38,20 @@ function renderSidebar(onComposeGroup = vi.fn()) { return onComposeGroup; } +// First in the file: React warns about an unknown DOM prop only once per run. +describe('ContactsSidebar — row props', () => { + it('keeps the long-press state off the row element', () => { + const errors = vi.spyOn(console, 'error').mockImplementation(() => {}); + try { + renderSidebar(); + const unknownProp = errors.mock.calls.some((args) => args.some((a) => String(a).includes('isPressed'))); + expect(unknownProp).toBe(false); + } finally { + errors.mockRestore(); + } + }); +}); + describe('ContactsSidebar — compose to group', () => { beforeEach(() => vi.clearAllMocks()); diff --git a/components/contacts/contact-list-item.tsx b/components/contacts/contact-list-item.tsx index 28e90ad8e..08a95ab67 100644 --- a/components/contacts/contact-list-item.tsx +++ b/components/contacts/contact-list-item.tsx @@ -67,7 +67,7 @@ export function ContactListItem({ contact, isSelected, isChecked, hasSelection, return (
({ onTouchStart, onTouchEnd, onTouchMove, onTouchCancel }), + [onTouchStart, onTouchEnd, onTouchMove, onTouchCancel] + ); + + return { onTouchStart, onTouchEnd, onTouchMove, onTouchCancel, isPressed, handlers }; }