diff --git a/src/components/DynamicPropertiesPanel.tsx b/src/components/DynamicPropertiesPanel.tsx index a6306f04..97d1aee3 100644 --- a/src/components/DynamicPropertiesPanel.tsx +++ b/src/components/DynamicPropertiesPanel.tsx @@ -5,6 +5,7 @@ import type { ParsedFrontmatter } from '../utils/frontmatter' import { EditableValue, TagPillList } from './EditableValue' import { Button } from '@/components/ui/button' import { getTypeColor, getTypeLightColor } from '../utils/typeColors' +import { countWords } from '../utils/wikilinks' const STATUS_STYLES: Record = { Active: { bg: 'var(--accent-green-light)', color: 'var(--accent-green)' }, @@ -37,12 +38,6 @@ export function containsWikilinks(value: FrontmatterValue): boolean { return false } -function countWords(content: string | null): number { - if (!content) return 0 - const stripped = content.replace(/^---[\s\S]*?---\n?/, '') - const words = stripped.trim().split(/\s+/).filter((w) => w.length > 0) - return words.length -} function formatDate(timestamp: number | null): string { if (!timestamp) return '\u2014' @@ -238,7 +233,7 @@ export function DynamicPropertiesPanel({ const [editingKey, setEditingKey] = useState(null) const [showAddDialog, setShowAddDialog] = useState(false) - const wordCount = countWords(content) + const wordCount = countWords(content ?? '') const propertyEntries = useMemo(() => { return Object.entries(frontmatter) diff --git a/src/components/Inspector.test.tsx b/src/components/Inspector.test.tsx index 6ffc6abb..515b6674 100644 --- a/src/components/Inspector.test.tsx +++ b/src/components/Inspector.test.tsx @@ -131,8 +131,8 @@ describe('Inspector', () => { it('computes word count from content minus frontmatter', () => { render() - // "# Test Project" + "This is a test note with some words to count." = 13 words - expect(screen.getByText('13')).toBeInTheDocument() + // "Test Project" (# stripped) + "This is a test note with some words to count." = 12 words + expect(screen.getByText('12')).toBeInTheDocument() }) it('shows "Add property" button as disabled placeholder', () => { @@ -470,8 +470,11 @@ Status: Active allContent={{}} /> ) - // 2 entries reference via Belongs to - expect(screen.getByText('2')).toBeInTheDocument() + // 2 entries reference via Belongs to — badge appears in the Referenced by header + const allTwos = screen.getAllByText('2') + expect(allTwos.length).toBeGreaterThanOrEqual(1) + // At least one "2" is inside a badge (span with ml-1 class) + expect(allTwos.some(el => el.classList.contains('ml-1'))).toBe(true) }) it('shows "No references" when no entries reference the current note', () => { diff --git a/src/utils/wikilinks.test.ts b/src/utils/wikilinks.test.ts index b2cc16b8..33161dcf 100644 --- a/src/utils/wikilinks.test.ts +++ b/src/utils/wikilinks.test.ts @@ -163,6 +163,13 @@ describe('splitFrontmatter', () => { expect(fm).toBe('---\ntitle: Hello\n---\n') expect(body).toBe('Content') }) + + it('ignores dashes inside frontmatter values', () => { + const content = '---\ntitle: "A --- B"\ntype: Note\n---\n\nBody text' + const [fm, body] = splitFrontmatter(content) + expect(fm).toBe('---\ntitle: "A --- B"\ntype: Note\n---\n') + expect(body).toBe('\nBody text') + }) }) describe('countWords', () => { @@ -195,6 +202,67 @@ describe('countWords', () => { const content = 'Hello world this is four words plus three' expect(countWords(content)).toBe(8) }) + + it('excludes long frontmatter with many keys from count', () => { + const content = [ + '---', + 'type: Note', + 'workspace: personal', + 'notion_id: 63aeb735-e6f4-4a32-b7b6-d34276a26dee', + 'status: Active', + 'owner: Luca Rossi', + 'tags: [Tauri, React, TypeScript]', + 'belongs_to:', + ' - "[[quarter/q1-2026]]"', + 'related_to:', + ' - "[[topic/software-development]]"', + '---', + '', + '# My Note', + '', + 'Only these five words count.', + ].join('\n') + // Body: "# My Note\n\nOnly these five words count." + // After stripping '#': " My Note\n\nOnly these five words count." + // Words: My, Note, Only, these, five, words, count. = 7 + expect(countWords(content)).toBe(7) + }) + + it('ignores frontmatter values containing dashes', () => { + const content = [ + '---', + 'title: "Something --- More"', + 'type: Note', + '---', + '', + 'Body words here.', + ].join('\n') + expect(countWords(content)).toBe(3) + }) + + it('handles frontmatter with horizontal rule in body', () => { + const content = [ + '---', + 'type: Note', + '---', + '', + '# Title', + '', + 'Before the rule.', + '', + '---', + '', + 'After the rule.', + ].join('\n') + // Body includes: Title, Before, the, rule., After, the, rule. + // The --- horizontal rule is stripped by the markdown char filter + expect(countWords(content)).toBe(7) + }) + + it('returns 0 when content is only frontmatter with no trailing body', () => { + const content = '---\ntitle: Hello\nstatus: Active\ntags: [a, b, c]\n---' + expect(countWords(content)).toBe(0) + }) }) describe('restoreWikilinksInBlocks', () => {