Files
tolaria/src/components/Sidebar.test.ts
Test 1930e7132b fix: show types with 0 notes in sidebar by iterating typeEntryMap
Types created via "Create Type" now appear in the sidebar immediately,
even before any notes of that type exist. The buildDynamicSections
function now adds all active (non-trashed/archived) type definitions
from typeEntryMap to the active types set, using name === entry.title
guard to avoid duplicate entries from lowercase aliases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 10:31:47 +02:00

142 lines
5.9 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { buildSectionGroup, buildDynamicSections, collectActiveTypes } from '../utils/sidebarSections'
import { resolveIcon } from '../utils/iconRegistry'
import type { VaultEntry } from '../types'
import { GearSix, CookingPot, FileText } from '@phosphor-icons/react'
const baseEntry: VaultEntry = {
path: '', filename: '', title: '', isA: null, aliases: [], belongsTo: [], relatedTo: [],
status: null, archived: false, trashed: false, trashedAt: null,
modifiedAt: null, createdAt: null, fileSize: 0, snippet: '', relationships: {},
wordCount: 0,
icon: null, color: null, order: null, sidebarLabel: null, template: null, sort: null,
view: null, visible: null, outgoingLinks: [], properties: {},
}
describe('buildSectionGroup', () => {
it('uses type entry icon/color/sidebarLabel for custom type', () => {
const typeEntryMap: Record<string, VaultEntry> = {
Config: { ...baseEntry, title: 'Config', isA: 'Type', icon: 'gear-six', color: 'blue', sidebarLabel: 'Config' },
}
const group = buildSectionGroup('Config', typeEntryMap)
expect(group.label).toBe('Config')
expect(group.customColor).toBe('blue')
expect(group.Icon).toBe(GearSix)
})
it('uses type entry icon/color for custom type with custom icon', () => {
const typeEntryMap: Record<string, VaultEntry> = {
Recipe: { ...baseEntry, title: 'Recipe', isA: 'Type', icon: 'cooking-pot', color: 'orange' },
}
const group = buildSectionGroup('Recipe', typeEntryMap)
expect(group.label).toBe('Recipes')
expect(group.customColor).toBe('orange')
expect(group.Icon).toBe(CookingPot)
})
it('falls back to pluralized name and FileText when no type entry', () => {
const group = buildSectionGroup('Widget', {})
expect(group.label).toBe('Widgets')
expect(group.customColor).toBeNull()
expect(group.Icon).toBe(FileText)
})
it('overrides built-in type icon/color when type entry has custom values', () => {
const typeEntryMap: Record<string, VaultEntry> = {
Project: { ...baseEntry, title: 'Project', isA: 'Type', icon: 'rocket', color: 'green', sidebarLabel: 'My Projects' },
}
const group = buildSectionGroup('Project', typeEntryMap)
expect(group.label).toBe('My Projects')
expect(group.customColor).toBe('green')
expect(group.Icon).toBe(resolveIcon('rocket'))
})
it('uses gray color for Config type', () => {
const typeEntryMap: Record<string, VaultEntry> = {
Config: { ...baseEntry, title: 'Config', isA: 'Type', icon: 'gear-six', color: 'gray', sidebarLabel: 'Config' },
}
const group = buildSectionGroup('Config', typeEntryMap)
expect(group.customColor).toBe('gray')
})
it('resolves type entry via lowercase key (case-insensitive isA)', () => {
// When instances have isA: 'config' (lowercase) but type entry title is 'Config'
const typeEntryMap: Record<string, VaultEntry> = {
Config: { ...baseEntry, title: 'Config', isA: 'Type', icon: 'gear-six', color: 'gray', sidebarLabel: 'Config' },
config: { ...baseEntry, title: 'Config', isA: 'Type', icon: 'gear-six', color: 'gray', sidebarLabel: 'Config' },
}
const group = buildSectionGroup('config', typeEntryMap)
expect(group.label).toBe('Config')
expect(group.customColor).toBe('gray')
expect(group.Icon).toBe(GearSix)
})
})
describe('buildDynamicSections', () => {
it('includes types with 0 notes from typeEntryMap', () => {
const entries: VaultEntry[] = [
{ ...baseEntry, title: 'My Note', isA: 'Note' },
]
const typeEntryMap: Record<string, VaultEntry> = {
Recipe: { ...baseEntry, title: 'Recipe', isA: 'Type', icon: 'cooking-pot' },
recipe: { ...baseEntry, title: 'Recipe', isA: 'Type', icon: 'cooking-pot' },
}
const sections = buildDynamicSections(entries, typeEntryMap)
const types = sections.map((s) => s.type)
expect(types).toContain('Note')
expect(types).toContain('Recipe')
})
it('does not duplicate types that have both entries and type definitions', () => {
const entries: VaultEntry[] = [
{ ...baseEntry, title: 'My Project', isA: 'Project' },
]
const typeEntryMap: Record<string, VaultEntry> = {
Project: { ...baseEntry, title: 'Project', isA: 'Type' },
project: { ...baseEntry, title: 'Project', isA: 'Type' },
}
const sections = buildDynamicSections(entries, typeEntryMap)
const projectSections = sections.filter((s) => s.type === 'Project')
expect(projectSections).toHaveLength(1)
})
it('excludes trashed type definitions', () => {
const entries: VaultEntry[] = []
const typeEntryMap: Record<string, VaultEntry> = {
Deleted: { ...baseEntry, title: 'Deleted', isA: 'Type', trashed: true },
}
const sections = buildDynamicSections(entries, typeEntryMap)
expect(sections.map((s) => s.type)).not.toContain('Deleted')
})
it('excludes archived type definitions', () => {
const entries: VaultEntry[] = []
const typeEntryMap: Record<string, VaultEntry> = {
Old: { ...baseEntry, title: 'Old', isA: 'Type', archived: true },
}
const sections = buildDynamicSections(entries, typeEntryMap)
expect(sections.map((s) => s.type)).not.toContain('Old')
})
})
describe('collectActiveTypes', () => {
it('excludes non-markdown entries from type collection', () => {
const entries: VaultEntry[] = [
{ ...baseEntry, title: 'My Note', isA: 'Note', fileKind: 'markdown' },
{ ...baseEntry, title: 'config.yml', isA: null, fileKind: 'text' },
{ ...baseEntry, title: 'photo.png', isA: null, fileKind: 'binary' },
]
const types = collectActiveTypes(entries)
expect(types).toContain('Note')
expect(types.size).toBe(1)
})
it('treats entries without fileKind as markdown', () => {
const entries: VaultEntry[] = [
{ ...baseEntry, title: 'Legacy Note', isA: 'Note' },
]
const types = collectActiveTypes(entries)
expect(types).toContain('Note')
})
})