import { fireEvent, render, screen } from '@testing-library/react' import { afterEach, describe, expect, it, vi } from 'vitest' import type { ReactNode } from 'react' import { DisplayModeSelector, SmartPropertyValueCell } from './PropertyValueCells' const { createValueButtonMock } = vi.hoisted(() => ({ createValueButtonMock: (testId: string, nextValue: (value: string) => string) => ({ value, onSave }: { value: string; onSave: (value: string) => void }) => ( ), })) vi.mock('./EditableValue', () => ({ EditableValue: createValueButtonMock('editable-value', (value) => `${value}-saved`), TagPillList: ({ items, label, onSave, }: { items: string[] label: string onSave: (items: string[]) => void }) => ( ), UrlValue: createValueButtonMock('url-value', (value) => `${value}/updated`), })) vi.mock('./StatusDropdown', () => ({ StatusDropdown: ({ onSave, onCancel, }: { onSave: (value: string) => void onCancel: () => void }) => (
), })) vi.mock('./TagsDropdown', () => ({ TagsDropdown: ({ onToggle, onClose, }: { onToggle: (tag: string) => void onClose: () => void }) => (
), })) vi.mock('./ColorInput', () => ({ ColorEditableValue: createValueButtonMock('color-value', (value) => value.toUpperCase()), })) vi.mock('./IconEditableValue', () => ({ IconEditableValue: createValueButtonMock('icon-value', (value) => `${value}-icon`), })) vi.mock('@/components/ui/calendar', () => ({ Calendar: ({ onSelect }: { onSelect: (value?: Date) => void }) => (
), })) vi.mock('@/components/ui/popover', () => ({ Popover: ({ children, onOpenChange, }: { children: ReactNode onOpenChange?: (open: boolean) => void }) => (
{children}
), PopoverTrigger: ({ children }: { children: ReactNode }) => <>{children}, PopoverContent: ({ children, ...props }: React.HTMLAttributes) => (
{children}
), })) function makeRect(right: number, bottom: number): DOMRect { return { x: 0, y: 0, width: 24, height: 24, top: 0, left: 0, right, bottom, toJSON: () => ({}), } as DOMRect } describe('PropertyValueCells extra', () => { afterEach(() => { vi.restoreAllMocks() }) it('normalizes relationship property keys and positions the display-mode menu within the viewport', () => { const rectSpy = vi .spyOn(HTMLElement.prototype, 'getBoundingClientRect') .mockReturnValue(makeRect(100, 20)) const onSelect = vi.fn() render( , ) fireEvent.click(screen.getByTestId('display-mode-trigger')) expect(screen.getByTestId('display-mode-icon-relationship')).toBeInTheDocument() expect(screen.getByTestId('display-mode-menu').style.left).toBe('8px') expect(screen.getByTestId('display-mode-menu').style.top).toBe('24px') const backdrop = Array.from(document.body.querySelectorAll('div')).find( (node) => node.className === 'fixed inset-0 z-[12000]', ) fireEvent.click(backdrop as HTMLDivElement) expect(screen.queryByTestId('display-mode-menu')).not.toBeInTheDocument() fireEvent.click(screen.getByTestId('display-mode-trigger')) fireEvent.click(screen.getByTestId('display-mode-option-date')) expect(onSelect).toHaveBeenCalledWith(' Belongs-To ', 'date') rectSpy.mockRestore() }) it('closes empty date pickers without saving and ignores undefined selections', () => { const onSave = vi.fn() const onStartEdit = vi.fn() render( , ) fireEvent.click(screen.getByTestId('date-picker-empty')) fireEvent.click(screen.getByTestId('popover-close')) expect(onSave).not.toHaveBeenCalled() expect(onStartEdit).toHaveBeenCalledWith(null) }) it('renders number displays, restores invalid input on escape, and falls back to editable text', () => { const onSave = vi.fn() const onStartEdit = vi.fn() const { rerender } = render( , ) fireEvent.click(screen.getByTestId('number-display')) expect(onStartEdit).toHaveBeenCalledWith('Count') rerender( , ) fireEvent.change(screen.getByTestId('number-input'), { target: { value: 'oops' } }) fireEvent.keyDown(screen.getByTestId('number-input'), { key: 'Escape' }) expect(onStartEdit).toHaveBeenCalledWith(null) rerender( , ) fireEvent.click(screen.getByTestId('editable-value')) expect(onSave).toHaveBeenCalledWith('Title', 'Plain text-saved') }) it('handles string booleans, tag toggles, and tag removals', () => { const onSaveList = vi.fn() const onStartEdit = vi.fn() const onUpdate = vi.fn() const { rerender } = render( , ) const checkbox = screen.getByRole('checkbox') as HTMLInputElement expect(checkbox.checked).toBe(false) fireEvent.click(checkbox) expect(onUpdate).toHaveBeenCalledWith('Published', true) rerender( , ) expect((screen.getByRole('checkbox') as HTMLInputElement).checked).toBe(false) rerender( , ) fireEvent.click(screen.getByTestId('tags-toggle-alpha')) fireEvent.click(screen.getByTitle('Remove alpha')) fireEvent.click(screen.getByTestId('tags-add-button')) fireEvent.click(screen.getByTestId('tags-toggle-beta')) expect(onSaveList).toHaveBeenCalledWith('Tags', []) expect(onStartEdit).toHaveBeenCalledWith('Tags') expect(onSaveList).toHaveBeenCalledWith('Tags', ['alpha', 'beta']) rerender( , ) fireEvent.click(screen.getByTestId('tags-toggle-beta')) expect(onSaveList).toHaveBeenCalledWith('Category', ['solo', 'beta']) }) })