diff --git a/src/components/DynamicPropertiesPanel.test.tsx b/src/components/DynamicPropertiesPanel.test.tsx index d9bfec1d..20e9a701 100644 --- a/src/components/DynamicPropertiesPanel.test.tsx +++ b/src/components/DynamicPropertiesPanel.test.tsx @@ -356,4 +356,133 @@ describe('DynamicPropertiesPanel', () => { fireEvent.click(screen.getByText('Cancel')) expect(screen.getByText('+ Add property')).toBeInTheDocument() }) + + describe('editable vs read-only distinction', () => { + it('renders Info section header', () => { + render( + + ) + expect(screen.getByText('Info')).toBeInTheDocument() + }) + + it('renders Modified and Words in read-only Info section', () => { + render( + + ) + const readOnlyRows = screen.getAllByTestId('readonly-property') + const labels = readOnlyRows.map(row => row.querySelector('span')?.textContent) + expect(labels).toContain('Modified') + expect(labels).toContain('Words') + }) + + it('renders Created date in Info section', () => { + render( + + ) + const readOnlyRows = screen.getAllByTestId('readonly-property') + const labels = readOnlyRows.map(row => row.querySelector('span')?.textContent) + expect(labels).toContain('Created') + }) + + it('renders file size in Info section', () => { + render( + + ) + expect(screen.getByText('Size')).toBeInTheDocument() + expect(screen.getByText('4.2 KB')).toBeInTheDocument() + }) + + it('shows em dash for null timestamps in Info section', () => { + render( + + ) + // Two em dashes for null Modified and Created + const dashes = screen.getAllByText('\u2014') + expect(dashes.length).toBeGreaterThanOrEqual(2) + }) + + it('editable properties have hover styling via data-testid', () => { + render( + + ) + const editableRows = screen.getAllByTestId('editable-property') + expect(editableRows.length).toBe(2) + // Editable rows have hover:bg-muted class for interactivity + editableRows.forEach(row => { + expect(row.className).toContain('hover:bg-muted') + }) + }) + + it('read-only rows do not have hover styling', () => { + render( + + ) + const readOnlyRows = screen.getAllByTestId('readonly-property') + readOnlyRows.forEach(row => { + expect(row.className).not.toContain('hover:bg-muted') + }) + }) + + it('formats file sizes correctly', () => { + // Small file — bytes + const { rerender } = render( + + ) + expect(screen.getByText('500 B')).toBeInTheDocument() + + // KB range + rerender( + + ) + expect(screen.getByText('2.0 KB')).toBeInTheDocument() + + // MB range + rerender( + + ) + expect(screen.getByText('1.0 MB')).toBeInTheDocument() + }) + }) }) diff --git a/src/components/DynamicPropertiesPanel.tsx b/src/components/DynamicPropertiesPanel.tsx index b36ac11f..c7028fd0 100644 --- a/src/components/DynamicPropertiesPanel.tsx +++ b/src/components/DynamicPropertiesPanel.tsx @@ -63,6 +63,14 @@ function parseNewValue(rawValue: string): FrontmatterValue { return items.length === 1 ? items[0] : items } +function formatFileSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B` + const kb = bytes / 1024 + if (kb < 1024) return `${kb.toFixed(1)} KB` + const mb = kb / 1024 + return `${mb.toFixed(1)} MB` +} + function isStatusKey(key: string): boolean { return key === 'Status' || key.includes('Status') } @@ -165,7 +173,7 @@ function PropertyRow({ propKey, value, editingKey, onStartEdit, onSave, onSaveLi onUpdate?: (key: string, value: FrontmatterValue) => void; onDelete?: (key: string) => void }) { return ( -
+
{propKey} {onDelete && ( @@ -191,11 +199,11 @@ function PropertyValueCell({ propKey, value, isEditing, onStartEdit, onSave, onS return } -function StaticRow({ label, value }: { label: string; value: string }) { +function InfoRow({ label, value }: { label: string; value: string }) { return ( -
- {label} - {value} +
+ {label} + {value}
) } @@ -256,19 +264,28 @@ export function DynamicPropertiesPanel({ }, [onAddProperty]) return ( -
+
+ {/* Editable properties section */}
{propertyEntries.map(([key, value]) => ( ))} - -
{showAddDialog ? setShowAddDialog(false)} /> : setShowAddDialog(true)} disabled={!onAddProperty} /> } + {/* Read-only Info section */} +
+

Info

+
+ + + + +
+
) } diff --git a/src/components/Inspector.test.tsx b/src/components/Inspector.test.tsx index d7e31820..0d771cfa 100644 --- a/src/components/Inspector.test.tsx +++ b/src/components/Inspector.test.tsx @@ -279,4 +279,49 @@ This is a test note with some words to count. ) expect(screen.getByText('No revision history')).toBeInTheDocument() }) + + it('shows separate Info section with read-only metadata', () => { + render( + + ) + expect(screen.getByText('Info')).toBeInTheDocument() + expect(screen.getByText('Modified')).toBeInTheDocument() + expect(screen.getByText('Created')).toBeInTheDocument() + expect(screen.getByText('Size')).toBeInTheDocument() + }) + + it('renders editable properties with interactive styling', () => { + render( + + ) + const editableRows = screen.getAllByTestId('editable-property') + expect(editableRows.length).toBeGreaterThan(0) + editableRows.forEach(row => { + expect(row.className).toContain('hover:bg-muted') + }) + }) + + it('renders read-only properties with muted non-interactive styling', () => { + render( + + ) + const readOnlyRows = screen.getAllByTestId('readonly-property') + expect(readOnlyRows.length).toBe(4) // Modified, Created, Words, Size + readOnlyRows.forEach(row => { + expect(row.className).not.toContain('hover:bg-muted') + expect(row.className).not.toContain('cursor-pointer') + }) + }) })