test: add folder tree and filtering tests; docs: ADR-0033

Add FolderTree component tests (render, expand, collapse, select,
highlight) and folder filtering tests in noteListHelpers (path
matching, sibling exclusion, archive/trash filtering).

ADR-0033 documents the decision to scan all subdirectories and
expose folder tree navigation, superseding ADR-0006's scanning
constraint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Test
2026-03-31 11:19:31 +02:00
parent 7dc7897367
commit 093f1bc9dc
4 changed files with 144 additions and 1 deletions

View File

@@ -0,0 +1,70 @@
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { FolderTree } from './FolderTree'
import type { FolderNode, SidebarSelection } from '../types'
const mockFolders: FolderNode[] = [
{
name: 'projects',
path: 'projects',
children: [
{ name: 'laputa', path: 'projects/laputa', children: [] },
{ name: 'portfolio', path: 'projects/portfolio', children: [] },
],
},
{ name: 'areas', path: 'areas', children: [] },
{ name: 'journal', path: 'journal', children: [] },
]
const defaultSelection: SidebarSelection = { kind: 'filter', filter: 'all' }
describe('FolderTree', () => {
it('renders nothing when folders is empty', () => {
const { container } = render(
<FolderTree folders={[]} selection={defaultSelection} onSelect={vi.fn()} />,
)
expect(container.firstChild).toBeNull()
})
it('renders FOLDERS header and top-level folders', () => {
render(<FolderTree folders={mockFolders} selection={defaultSelection} onSelect={vi.fn()} />)
expect(screen.getByText('FOLDERS')).toBeInTheDocument()
expect(screen.getByText('projects')).toBeInTheDocument()
expect(screen.getByText('areas')).toBeInTheDocument()
expect(screen.getByText('journal')).toBeInTheDocument()
})
it('does not show children initially', () => {
render(<FolderTree folders={mockFolders} selection={defaultSelection} onSelect={vi.fn()} />)
expect(screen.queryByText('laputa')).not.toBeInTheDocument()
})
it('calls onSelect with folder kind when clicking a folder', () => {
const onSelect = vi.fn()
render(<FolderTree folders={mockFolders} selection={defaultSelection} onSelect={onSelect} />)
fireEvent.click(screen.getByText('projects'))
expect(onSelect).toHaveBeenCalledWith({ kind: 'folder', path: 'projects' })
})
it('expands children when clicking a folder with children', () => {
const onSelect = vi.fn()
render(<FolderTree folders={mockFolders} selection={defaultSelection} onSelect={onSelect} />)
fireEvent.click(screen.getByText('projects'))
expect(screen.getByText('laputa')).toBeInTheDocument()
expect(screen.getByText('portfolio')).toBeInTheDocument()
})
it('collapses section when clicking FOLDERS header', () => {
render(<FolderTree folders={mockFolders} selection={defaultSelection} onSelect={vi.fn()} />)
expect(screen.getByText('projects')).toBeInTheDocument()
fireEvent.click(screen.getByText('FOLDERS'))
expect(screen.queryByText('projects')).not.toBeInTheDocument()
})
it('highlights selected folder', () => {
const sel: SidebarSelection = { kind: 'folder', path: 'areas' }
render(<FolderTree folders={mockFolders} selection={sel} onSelect={vi.fn()} />)
const btn = screen.getByText('areas').closest('button')!
expect(btn.className).toContain('text-primary')
})
})

View File

@@ -1,5 +1,5 @@
import { describe, it, expect, vi, afterEach } from 'vitest'
import { formatSubtitle, formatSearchSubtitle, relativeDate, buildRelationshipGroups, getSortComparator, extractSortableProperties, getSortOptionLabel, getDefaultDirection, parseSortConfig, serializeSortConfig, buildValidLinkTargets, isInboxEntry, filterInboxEntries } from './noteListHelpers'
import { formatSubtitle, formatSearchSubtitle, relativeDate, buildRelationshipGroups, getSortComparator, extractSortableProperties, getSortOptionLabel, getDefaultDirection, parseSortConfig, serializeSortConfig, buildValidLinkTargets, isInboxEntry, filterInboxEntries, filterEntries } from './noteListHelpers'
import type { VaultEntry } from '../types'
function makeEntry(overrides: Partial<VaultEntry> = {}): VaultEntry {
@@ -695,3 +695,42 @@ describe('filterInboxEntries', () => {
expect(result).toEqual([])
})
})
describe('filterEntries — folder selection', () => {
const entries = [
makeEntry({ path: '/vault/projects/laputa/note1.md', title: 'Note 1' }),
makeEntry({ path: '/vault/projects/laputa/note2.md', title: 'Note 2' }),
makeEntry({ path: '/vault/projects/portfolio/site.md', title: 'Site' }),
makeEntry({ path: '/vault/areas/health.md', title: 'Health' }),
makeEntry({ path: '/vault/root-note.md', title: 'Root' }),
]
it('filters entries by folder path', () => {
const result = filterEntries(entries, { kind: 'folder', path: 'projects/laputa' })
expect(result.map(e => e.title)).toEqual(['Note 1', 'Note 2'])
})
it('does not include notes from sibling folders', () => {
const result = filterEntries(entries, { kind: 'folder', path: 'projects/laputa' })
expect(result.find(e => e.title === 'Site')).toBeUndefined()
})
it('filters by parent folder (non-recursive — direct children only)', () => {
const result = filterEntries(entries, { kind: 'folder', path: 'areas' })
expect(result.map(e => e.title)).toEqual(['Health'])
})
it('returns empty for root when entries are in subfolders', () => {
const result = filterEntries(entries, { kind: 'folder', path: 'nonexistent' })
expect(result).toEqual([])
})
it('excludes archived and trashed entries by default', () => {
const withArchived = [
...entries,
makeEntry({ path: '/vault/projects/laputa/archived.md', title: 'Archived', archived: true }),
]
const result = filterEntries(withArchived, { kind: 'folder', path: 'projects/laputa' })
expect(result.find(e => e.title === 'Archived')).toBeUndefined()
})
})