fix: align Notes counts with explicit type

This commit is contained in:
lucaronin
2026-04-18 13:07:17 +02:00
parent e18491bcd3
commit ed9ceff1d2
4 changed files with 61 additions and 13 deletions

View File

@@ -262,6 +262,23 @@ describe('NoteList filter pills', () => {
expect(screen.queryByText('Open Project 1')).not.toBeInTheDocument()
})
it('shows only explicit Note entries for the Notes type filter', () => {
const noteEntries = [
makeEntry({ title: 'Note Type', isA: 'Type', path: '/types/note.md', filename: 'note.md' }),
makeEntry({ title: 'Explicit Note', isA: 'Note', path: '/explicit-note.md', filename: 'explicit-note.md' }),
makeEntry({ title: 'Untyped Note', isA: null, path: '/untyped-note.md', filename: 'untyped-note.md' }),
makeEntry({ title: 'Archived Explicit Note', isA: 'Note', archived: true, path: '/archived-note.md', filename: 'archived-note.md' }),
]
renderNoteList({ entries: noteEntries, selection: { kind: 'sectionGroup', type: 'Note' } })
expect(screen.getByText('Explicit Note')).toBeInTheDocument()
expect(screen.queryByText('Untyped Note')).not.toBeInTheDocument()
expect(screen.queryByText('Archived Explicit Note')).not.toBeInTheDocument()
expect(screen.getByTestId('filter-pill-open')).toHaveTextContent('1')
expect(screen.getByTestId('filter-pill-archived')).toHaveTextContent('1')
})
it('shows the archived empty state when a section has no archived notes', () => {
renderNoteList({
entries: projectEntries.filter((entry) => !entry.archived),

View File

@@ -780,6 +780,14 @@ describe('Sidebar', () => {
icon: null, color: null, order: null, sidebarLabel: null, template: null, sort: null,
outgoingLinks: [], properties: {},
},
{
path: '/vault/binary-note.pdf', filename: 'binary-note.pdf', title: 'Binary Note',
isA: 'Note', aliases: [], belongsTo: [], relatedTo: [], status: null, owner: null,
cadence: null, archived: false, modifiedAt: 1700000000,
createdAt: null, fileSize: 150, snippet: '', wordCount: 0, relationships: {},
icon: null, color: null, order: null, sidebarLabel: null, template: null, sort: null,
outgoingLinks: [], properties: {}, fileKind: 'binary',
},
]
it('shows Notes section when Note entries exist', () => {
@@ -787,13 +795,40 @@ describe('Sidebar', () => {
expect(screen.getByText('Notes')).toBeInTheDocument()
})
it('counts both explicit and untyped notes in Notes section chip', () => {
it('counts only explicit Note entries in the Notes section chip', () => {
render(<Sidebar entries={noteEntries} selection={defaultSelection} onSelect={() => {}} />)
const notesHeader = screen.getByText('Notes').closest('[class*="group/section"]')!
expect(notesHeader.textContent).toContain('1')
})
it('ignores non-markdown Note entries in the Notes section chip', () => {
render(<Sidebar entries={noteEntries} selection={defaultSelection} onSelect={() => {}} />)
const notesHeader = screen.getByText('Notes').closest('[class*="group/section"]')!
expect(notesHeader.textContent).toContain('1')
expect(notesHeader.textContent).not.toContain('2')
})
it('keeps the Notes section count aligned when an entry changes to or from Note', () => {
const { rerender } = render(<Sidebar entries={noteEntries} selection={defaultSelection} onSelect={() => {}} />)
let notesHeader = screen.getByText('Notes').closest('[class*="group/section"]')!
expect(notesHeader.textContent).toContain('1')
const withoutExplicitNote = noteEntries.map((entry) =>
entry.path === '/vault/explicit-note.md' ? { ...entry, isA: null } : entry,
)
rerender(<Sidebar entries={withoutExplicitNote} selection={defaultSelection} onSelect={() => {}} />)
notesHeader = screen.getByText('Notes').closest('[class*="group/section"]')!
expect(notesHeader.textContent).toBe('Notes')
const withNewExplicitNote = noteEntries.map((entry) =>
entry.path === '/vault/untyped-note.md' ? { ...entry, isA: 'Note' } : entry,
)
rerender(<Sidebar entries={withNewExplicitNote} selection={defaultSelection} onSelect={() => {}} />)
notesHeader = screen.getByText('Notes').closest('[class*="group/section"]')!
expect(notesHeader.textContent).toContain('2')
})
it('shows Notes section for untyped entries even without explicit Note entries', () => {
it('does not show Notes section for untyped entries without explicit Note entries', () => {
const untypedOnly: VaultEntry[] = [
{
path: '/vault/plain.md', filename: 'plain.md', title: 'Plain Note',
@@ -805,7 +840,7 @@ describe('Sidebar', () => {
},
]
render(<Sidebar entries={untypedOnly} selection={defaultSelection} onSelect={() => {}} />)
expect(screen.getByText('Notes')).toBeInTheDocument()
expect(screen.queryByText('Notes')).not.toBeInTheDocument()
})
})

View File

@@ -20,6 +20,7 @@ import { TypeCustomizePopover } from '../TypeCustomizePopover'
import { useDragRegion } from '../../hooks/useDragRegion'
import { SidebarGroupHeader } from './SidebarGroupHeader'
import { SidebarViewItem } from './SidebarViewItem'
import { countByFilter } from '../../utils/noteListHelpers'
export { SidebarTopNav } from './SidebarTopNav'
export { FavoritesSection } from './FavoritesSection'
@@ -94,9 +95,7 @@ function SortableSection({
sectionProps: SidebarSectionProps
}) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: group.type })
const itemCount = sectionProps.entries.filter((entry) =>
!entry.archived && (group.type === 'Note' ? (entry.isA === 'Note' || !entry.isA) : entry.isA === group.type),
).length
const itemCount = countByFilter(sectionProps.entries, group.type).open
const isRenaming = sectionProps.renamingType === group.type
return (

View File

@@ -31,13 +31,10 @@ const isMarkdown = (e: VaultEntry) => e.fileKind === 'markdown' || !e.fileKind
const isActive = (e: VaultEntry) => !e.archived
const isSupportedSectionType = (type: string) => !isLegacyJournalingType(type)
function resolveEntrySectionType(entry: VaultEntry): string {
return entry.isA || 'Note'
}
function shouldCollectActiveType(entry: VaultEntry): boolean {
if (!isActive(entry) || !isMarkdown(entry)) return false
return isSupportedSectionType(resolveEntrySectionType(entry))
if (!entry.isA) return false
return isSupportedSectionType(entry.isA)
}
function shouldIncludeTypeDefinition(name: string, entry: VaultEntry): boolean {
@@ -45,12 +42,12 @@ function shouldIncludeTypeDefinition(name: string, entry: VaultEntry): boolean {
return isSupportedSectionType(name)
}
/** Collect unique isA values from active (non-archived) markdown entries. Untyped entries count as 'Note'. */
/** Collect unique explicit isA values from active (non-archived) markdown entries. */
export function collectActiveTypes(entries: VaultEntry[]): Set<string> {
const types = new Set<string>()
for (const e of entries) {
if (!shouldCollectActiveType(e)) continue
types.add(resolveEntrySectionType(e))
types.add(e.isA!)
}
return types
}