diff --git a/src/components/DynamicPropertiesPanel.test.tsx b/src/components/DynamicPropertiesPanel.test.tsx index b88e9017..1351266c 100644 --- a/src/components/DynamicPropertiesPanel.test.tsx +++ b/src/components/DynamicPropertiesPanel.test.tsx @@ -113,7 +113,7 @@ describe('DynamicPropertiesPanel', () => { /> ) // Status rendered with CSS text-transform: uppercase, DOM text is still "Active" - expect(screen.getByTitle('Active')).toBeInTheDocument() + expect(screen.getByTestId('status-badge')).toBeInTheDocument() }) it('renders properties from frontmatter', () => { @@ -124,9 +124,9 @@ describe('DynamicPropertiesPanel', () => { frontmatter={{ cadence: 'Weekly', owner: 'Luca' }} /> ) - expect(screen.getByText('cadence')).toBeInTheDocument() + expect(screen.getByText('Cadence')).toBeInTheDocument() expect(screen.getByText('Weekly')).toBeInTheDocument() - expect(screen.getByText('owner')).toBeInTheDocument() + expect(screen.getByText('Owner')).toBeInTheDocument() expect(screen.getByText('Luca')).toBeInTheDocument() }) @@ -162,7 +162,7 @@ describe('DynamicPropertiesPanel', () => { frontmatter={{ notion_id: 'abc-123-def' }} /> ) - expect(screen.getByText('notion_id')).toBeInTheDocument() + expect(screen.getByText('Notion id')).toBeInTheDocument() expect(screen.getByText('abc-123-def')).toBeInTheDocument() }) @@ -177,7 +177,7 @@ describe('DynamicPropertiesPanel', () => { // aliases skipped (in SKIP_KEYS); 'Belongs to' skipped (has wikilinks) expect(screen.queryByText('aliases')).not.toBeInTheDocument() expect(screen.queryByText('Belongs to')).not.toBeInTheDocument() - expect(screen.getByText('cadence')).toBeInTheDocument() + expect(screen.getByText('Cadence')).toBeInTheDocument() }) it('shows former relationship key with plain text value in Properties', () => { @@ -219,7 +219,7 @@ describe('DynamicPropertiesPanel', () => { const typeLabels = screen.getAllByText('Type') // Only the TypeRow label should exist, not a property row expect(typeLabels).toHaveLength(1) - expect(screen.getByTitle('Active')).toBeInTheDocument() + expect(screen.getByTestId('status-badge')).toBeInTheDocument() }) it('renders boolean property as toggle', () => { @@ -232,7 +232,7 @@ describe('DynamicPropertiesPanel', () => { /> ) // Boolean should show as Yes/No toggle - const toggleBtn = screen.getByText('\u2717 No') + const toggleBtn = screen.getByText('No') fireEvent.click(toggleBtn) expect(onUpdateProperty).toHaveBeenCalledWith('published', true) }) @@ -260,7 +260,7 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - expect(screen.getByText('+ Add property')).toBeInTheDocument() + expect(screen.getByText('Add property')).toBeInTheDocument() }) it('opens add property form when button clicked', () => { @@ -272,7 +272,7 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - fireEvent.click(screen.getByText('+ Add property')) + fireEvent.click(screen.getByText('Add property')) expect(screen.getByPlaceholderText('Property name')).toBeInTheDocument() expect(screen.getByPlaceholderText('Value')).toBeInTheDocument() expect(screen.getByTestId('add-property-type-trigger')).toBeInTheDocument() @@ -287,7 +287,7 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - fireEvent.click(screen.getByText('+ Add property')) + fireEvent.click(screen.getByText('Add property')) const keyInput = screen.getByPlaceholderText('Property name') const valueInput = screen.getByPlaceholderText('Value') fireEvent.change(keyInput, { target: { value: 'priority' } }) @@ -412,7 +412,7 @@ describe('DynamicPropertiesPanel', () => { /> ) // Click status pill to open dropdown - fireEvent.click(screen.getByTitle('Active')) + fireEvent.click(screen.getByTestId('status-badge')) // Should show dropdown with search input expect(screen.getByTestId('status-dropdown')).toBeInTheDocument() expect(screen.getByTestId('status-search-input')).toBeInTheDocument() @@ -478,11 +478,11 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - fireEvent.click(screen.getByText('+ Add property')) + fireEvent.click(screen.getByText('Add property')) const keyInput = screen.getByPlaceholderText('Property name') fireEvent.keyDown(keyInput, { key: 'Escape' }) // Form should be hidden, button should reappear - expect(screen.getByText('+ Add property')).toBeInTheDocument() + expect(screen.getByText('Add property')).toBeInTheDocument() }) it('adds property on Enter in form', () => { @@ -494,7 +494,7 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - fireEvent.click(screen.getByText('+ Add property')) + fireEvent.click(screen.getByText('Add property')) const keyInput = screen.getByPlaceholderText('Property name') const valueInput = screen.getByPlaceholderText('Value') fireEvent.change(keyInput, { target: { value: 'key' } }) @@ -512,7 +512,7 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - fireEvent.click(screen.getByText('+ Add property')) + fireEvent.click(screen.getByText('Add property')) const keyInput = screen.getByPlaceholderText('Property name') const valueInput = screen.getByPlaceholderText('Value') fireEvent.change(keyInput, { target: { value: 'tags' } }) @@ -530,9 +530,9 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - fireEvent.click(screen.getByText('+ Add property')) + fireEvent.click(screen.getByText('Add property')) fireEvent.click(screen.getByTestId('add-property-cancel')) - expect(screen.getByText('+ Add property')).toBeInTheDocument() + expect(screen.getByText('Add property')).toBeInTheDocument() }) describe('editable vs read-only distinction', () => { @@ -775,7 +775,7 @@ describe('DynamicPropertiesPanel', () => { expect(screen.getByText('Mar 31, 2026')).toBeInTheDocument() }) - it('renders calendar icon in date trigger button', () => { + it('renders date trigger button', () => { render( { ) const trigger = screen.getByTestId('date-display') expect(trigger.tagName).toBe('BUTTON') - expect(trigger.querySelector('svg')).toBeInTheDocument() }) it('opens calendar popover when date button clicked', () => { @@ -841,7 +840,7 @@ describe('DynamicPropertiesPanel', () => { onUpdateProperty={onUpdateProperty} /> ) - fireEvent.click(screen.getByTitle('Active')) + fireEvent.click(screen.getByTestId('status-badge')) expect(screen.getByTestId('status-dropdown')).toBeInTheDocument() fireEvent.keyDown(screen.getByTestId('status-search-input'), { key: 'Escape' }) expect(screen.queryByTestId('status-dropdown')).not.toBeInTheDocument() @@ -857,7 +856,7 @@ describe('DynamicPropertiesPanel', () => { onUpdateProperty={onUpdateProperty} /> ) - fireEvent.click(screen.getByTitle('Active')) + fireEvent.click(screen.getByTestId('status-badge')) fireEvent.click(screen.getByTestId('status-dropdown-backdrop')) expect(screen.queryByTestId('status-dropdown')).not.toBeInTheDocument() expect(onUpdateProperty).not.toHaveBeenCalled() @@ -872,7 +871,7 @@ describe('DynamicPropertiesPanel', () => { onUpdateProperty={onUpdateProperty} /> ) - fireEvent.click(screen.getByTitle('Active')) + fireEvent.click(screen.getByTestId('status-badge')) const input = screen.getByTestId('status-search-input') fireEvent.change(input, { target: { value: 'Needs Review' } }) fireEvent.keyDown(input, { key: 'Enter' }) @@ -893,7 +892,7 @@ describe('DynamicPropertiesPanel', () => { onUpdateProperty={onUpdateProperty} /> ) - fireEvent.click(screen.getByTitle('Active')) + fireEvent.click(screen.getByTestId('status-badge')) expect(screen.getByTestId('status-option-Reviewing')).toBeInTheDocument() expect(screen.getByTestId('status-option-Shipped')).toBeInTheDocument() }) @@ -910,7 +909,7 @@ describe('DynamicPropertiesPanel', () => { /> ) expect(screen.getByTestId('boolean-toggle')).toBeInTheDocument() - expect(screen.getByText('\u2713 Yes')).toBeInTheDocument() + expect(screen.getByText('Yes')).toBeInTheDocument() }) it('renders boolean toggle for false values', () => { @@ -923,7 +922,7 @@ describe('DynamicPropertiesPanel', () => { /> ) expect(screen.getByTestId('boolean-toggle')).toBeInTheDocument() - expect(screen.getByText('\u2717 No')).toBeInTheDocument() + expect(screen.getByText('No')).toBeInTheDocument() }) }) @@ -937,13 +936,13 @@ describe('DynamicPropertiesPanel', () => { onUpdateProperty={onUpdateProperty} /> ) - expect(screen.queryByText('trashed')).not.toBeInTheDocument() - expect(screen.queryByText('trashed_at')).not.toBeInTheDocument() - expect(screen.queryByText('archived')).not.toBeInTheDocument() - expect(screen.queryByText('archived_at')).not.toBeInTheDocument() - expect(screen.queryByText('icon')).not.toBeInTheDocument() + expect(screen.queryByText('Trashed')).not.toBeInTheDocument() + expect(screen.queryByText('Trashed at')).not.toBeInTheDocument() + expect(screen.queryByText('Archived')).not.toBeInTheDocument() + expect(screen.queryByText('Archived at')).not.toBeInTheDocument() + expect(screen.queryByText('Icon')).not.toBeInTheDocument() // Custom property still visible - expect(screen.getByText('cadence')).toBeInTheDocument() + expect(screen.getByText('Cadence')).toBeInTheDocument() }) it('filters system properties case-insensitively', () => { @@ -958,7 +957,7 @@ describe('DynamicPropertiesPanel', () => { expect(screen.queryByText('Trashed')).not.toBeInTheDocument() expect(screen.queryByText('Archived')).not.toBeInTheDocument() expect(screen.queryByText('Icon')).not.toBeInTheDocument() - expect(screen.getByText('cadence')).toBeInTheDocument() + expect(screen.getByText('Cadence')).toBeInTheDocument() }) it('does not filter similar but non-matching property names', () => { @@ -971,7 +970,7 @@ describe('DynamicPropertiesPanel', () => { /> ) expect(screen.getByText('Is Trashed')).toBeInTheDocument() - expect(screen.getByText('archive_date')).toBeInTheDocument() + expect(screen.getByText('Archive date')).toBeInTheDocument() }) }) @@ -1055,7 +1054,7 @@ describe('DynamicPropertiesPanel', () => { /> ) expect(screen.getByTestId('boolean-toggle')).toBeInTheDocument() - expect(screen.getByText('\u2713 Yes')).toBeInTheDocument() + expect(screen.getByText('Yes')).toBeInTheDocument() }) it('renders boolean toggle for string "false" when boolean mode overridden', () => { @@ -1069,7 +1068,7 @@ describe('DynamicPropertiesPanel', () => { /> ) expect(screen.getByTestId('boolean-toggle')).toBeInTheDocument() - expect(screen.getByText('\u2717 No')).toBeInTheDocument() + expect(screen.getByText('No')).toBeInTheDocument() }) it('toggles string boolean from false to true', () => { @@ -1124,7 +1123,7 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - fireEvent.click(screen.getByText('+ Add property')) + fireEvent.click(screen.getByText('Add property')) // Switch type to boolean fireEvent.pointerDown(screen.getByTestId('add-property-type-trigger'), { button: 0, pointerType: 'mouse' }) fireEvent.click(screen.getByRole('option', { name: /Boolean/ })) @@ -1141,7 +1140,7 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - fireEvent.click(screen.getByText('+ Add property')) + fireEvent.click(screen.getByText('Add property')) fireEvent.pointerDown(screen.getByTestId('add-property-type-trigger'), { button: 0, pointerType: 'mouse' }) fireEvent.click(screen.getByRole('option', { name: /Boolean/ })) // Toggle from No to Yes @@ -1158,7 +1157,7 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - fireEvent.click(screen.getByText('+ Add property')) + fireEvent.click(screen.getByText('Add property')) const keyInput = screen.getByPlaceholderText('Property name') fireEvent.change(keyInput, { target: { value: 'published' } }) // Switch to boolean type @@ -1180,7 +1179,7 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - fireEvent.click(screen.getByText('+ Add property')) + fireEvent.click(screen.getByText('Add property')) fireEvent.pointerDown(screen.getByTestId('add-property-type-trigger'), { button: 0, pointerType: 'mouse' }) fireEvent.click(screen.getByRole('option', { name: /Date/ })) expect(screen.getByTestId('add-property-date-trigger')).toBeInTheDocument() @@ -1196,7 +1195,7 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - fireEvent.click(screen.getByText('+ Add property')) + fireEvent.click(screen.getByText('Add property')) fireEvent.pointerDown(screen.getByTestId('add-property-type-trigger'), { button: 0, pointerType: 'mouse' }) fireEvent.click(screen.getByRole('option', { name: /Status/ })) expect(screen.getByTestId('add-property-status-trigger')).toBeInTheDocument() @@ -1211,7 +1210,7 @@ describe('DynamicPropertiesPanel', () => { onAddProperty={onAddProperty} /> ) - fireEvent.click(screen.getByText('+ Add property')) + fireEvent.click(screen.getByText('Add property')) // Default mode is text expect(screen.getByPlaceholderText('Value')).toBeInTheDocument() }) diff --git a/src/components/DynamicPropertiesPanel.tsx b/src/components/DynamicPropertiesPanel.tsx index 67e12da8..b3abebde 100644 --- a/src/components/DynamicPropertiesPanel.tsx +++ b/src/components/DynamicPropertiesPanel.tsx @@ -9,6 +9,12 @@ import { AddPropertyForm } from './AddPropertyForm' import { countWords } from '../utils/wikilinks' import type { PropertyDisplayMode } from '../utils/propertyTypes' +function toSentenceCase(key: string): string { + const spaced = key.replace(/[_-]/g, ' ') + if (!spaced) return spaced + return spaced.charAt(0).toUpperCase() + spaced.slice(1) +} + // eslint-disable-next-line react-refresh/only-export-components -- utility co-located with component export function containsWikilinks(value: FrontmatterValue): boolean { if (typeof value === 'string') return /^\[\[.*\]\]$/.test(value) @@ -48,8 +54,8 @@ function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultS return (
- - {propKey} + + {toSentenceCase(propKey)} {onDelete && ( )} @@ -65,7 +71,7 @@ function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultS function InfoRow({ label, value }: { label: string; value: string }) { return (
- {label} + {label} {value}
) @@ -74,10 +80,12 @@ function InfoRow({ label, value }: { label: string; value: string }) { function AddPropertyButton({ onClick, disabled }: { onClick: () => void; disabled: boolean }) { return ( + > + + + Add property + ) } diff --git a/src/components/EditableValue.tsx b/src/components/EditableValue.tsx index 73fa362a..28d3cdc7 100644 --- a/src/components/EditableValue.tsx +++ b/src/components/EditableValue.tsx @@ -65,7 +65,7 @@ export function UrlValue({ return ( { expect(screen.getByText('Words')).toBeInTheDocument() }) - it('renders status as a colored pill', () => { + it('renders status as a colored badge with dot indicator', () => { render() - const pill = screen.getByText('Active') - // Status is rendered as an inline pill with CSS variable-based styles - expect(pill).toHaveStyle({ borderRadius: '16px' }) + const badge = screen.getByTestId('status-badge') + expect(badge).toHaveTextContent('Active') + expect(badge.className).toContain('rounded') }) it('computes word count from content minus frontmatter and title', () => { @@ -137,7 +137,7 @@ describe('Inspector', () => { it('shows "Add property" button as disabled placeholder', () => { render() - const btn = screen.getByText('+ Add property') + const btn = screen.getByText('Add property') expect(btn).toBeDisabled() }) diff --git a/src/components/PropertyValueCells.tsx b/src/components/PropertyValueCells.tsx index eb0084e4..3d825957 100644 --- a/src/components/PropertyValueCells.tsx +++ b/src/components/PropertyValueCells.tsx @@ -5,7 +5,7 @@ import { EditableValue, TagPillList, UrlValue } from './EditableValue' import { isUrlValue } from '../utils/url' import { Calendar } from '@/components/ui/calendar' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' -import { CalendarIcon, XIcon } from 'lucide-react' +import { XIcon } from 'lucide-react' import { isValidCssColor } from '../utils/colorUtils' import { type PropertyDisplayMode, @@ -14,7 +14,8 @@ import { DISPLAY_MODE_OPTIONS, DISPLAY_MODE_ICONS, } from '../utils/propertyTypes' -import { StatusPill, StatusDropdown } from './StatusDropdown' +import { StatusDropdown } from './StatusDropdown' +import { getStatusStyle } from '../utils/statusStyles' import { TagsDropdown } from './TagsDropdown' import { getTagStyle } from '../utils/tagStyles' import { ColorEditableValue } from './ColorInput' @@ -37,14 +38,17 @@ function StatusValue({ propKey, value, isEditing, vaultStatuses, onSave, onStart onSave: (key: string, value: string) => void; onStartEdit: (key: string | null) => void }) { const statusStr = String(value) + const style = getStatusStyle(statusStr) return ( onStartEdit(propKey)} data-testid="status-badge" > - + + {statusStr} {isEditing && ( + ) } @@ -160,11 +163,10 @@ function DateValue({ value, onSave }: {