test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
|
|
|
import { renderHook, act } from '@testing-library/react'
|
|
|
|
|
import type { VaultEntry } from '../types'
|
|
|
|
|
import {
|
|
|
|
|
slugify,
|
|
|
|
|
buildNewEntry,
|
|
|
|
|
generateUntitledName,
|
|
|
|
|
entryMatchesTarget,
|
|
|
|
|
buildNoteContent,
|
|
|
|
|
resolveNewNote,
|
|
|
|
|
resolveNewType,
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
frontmatterToEntryPatch,
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
useNoteActions,
|
|
|
|
|
} from './useNoteActions'
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
import type { NoteActionsConfig } from './useNoteActions'
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
|
|
|
|
|
// Mock dependencies
|
|
|
|
|
vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn() }))
|
|
|
|
|
vi.mock('../mock-tauri', () => ({
|
|
|
|
|
isTauri: () => false,
|
|
|
|
|
addMockEntry: vi.fn(),
|
|
|
|
|
updateMockContent: vi.fn(),
|
|
|
|
|
mockInvoke: vi.fn().mockResolvedValue(''),
|
|
|
|
|
}))
|
|
|
|
|
vi.mock('./mockFrontmatterHelpers', () => ({
|
|
|
|
|
updateMockFrontmatter: vi.fn().mockReturnValue('---\nupdated: true\n---\n'),
|
|
|
|
|
deleteMockFrontmatterProperty: vi.fn().mockReturnValue('---\n---\n'),
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
const makeEntry = (overrides: Partial<VaultEntry> = {}): VaultEntry => ({
|
|
|
|
|
path: '/Users/luca/Laputa/note/test.md',
|
|
|
|
|
filename: 'test.md',
|
|
|
|
|
title: 'Test Note',
|
|
|
|
|
isA: 'Note',
|
|
|
|
|
aliases: [],
|
|
|
|
|
belongsTo: [],
|
|
|
|
|
relatedTo: [],
|
|
|
|
|
status: 'Active',
|
|
|
|
|
owner: null,
|
|
|
|
|
cadence: null,
|
|
|
|
|
archived: false,
|
|
|
|
|
trashed: false,
|
|
|
|
|
trashedAt: null,
|
|
|
|
|
modifiedAt: 1700000000,
|
|
|
|
|
createdAt: 1700000000,
|
|
|
|
|
fileSize: 100,
|
|
|
|
|
snippet: '',
|
|
|
|
|
relationships: {},
|
|
|
|
|
icon: null,
|
|
|
|
|
color: null,
|
|
|
|
|
order: null,
|
|
|
|
|
...overrides,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('slugify', () => {
|
|
|
|
|
it('converts text to lowercase kebab-case', () => {
|
|
|
|
|
expect(slugify('Hello World')).toBe('hello-world')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('removes special characters', () => {
|
|
|
|
|
expect(slugify('My Project! @#$%')).toBe('my-project')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('strips leading and trailing hyphens', () => {
|
|
|
|
|
expect(slugify('--hello--')).toBe('hello')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handles empty string', () => {
|
|
|
|
|
expect(slugify('')).toBe('')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('collapses multiple separators into one hyphen', () => {
|
|
|
|
|
expect(slugify('hello world---foo')).toBe('hello-world-foo')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('buildNewEntry', () => {
|
|
|
|
|
it('creates a VaultEntry with correct fields', () => {
|
|
|
|
|
const entry = buildNewEntry({
|
|
|
|
|
path: '/vault/note/my-note.md',
|
|
|
|
|
slug: 'my-note',
|
|
|
|
|
title: 'My Note',
|
|
|
|
|
type: 'Note',
|
|
|
|
|
status: 'Active',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(entry.path).toBe('/vault/note/my-note.md')
|
|
|
|
|
expect(entry.filename).toBe('my-note.md')
|
|
|
|
|
expect(entry.title).toBe('My Note')
|
|
|
|
|
expect(entry.isA).toBe('Note')
|
|
|
|
|
expect(entry.status).toBe('Active')
|
|
|
|
|
expect(entry.archived).toBe(false)
|
|
|
|
|
expect(entry.trashed).toBe(false)
|
|
|
|
|
expect(entry.modifiedAt).toBeGreaterThan(0)
|
|
|
|
|
expect(entry.createdAt).toBe(entry.modifiedAt)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('sets null status when provided', () => {
|
|
|
|
|
const entry = buildNewEntry({
|
|
|
|
|
path: '/vault/topic/ai.md',
|
|
|
|
|
slug: 'ai',
|
|
|
|
|
title: 'AI',
|
|
|
|
|
type: 'Topic',
|
|
|
|
|
status: null,
|
|
|
|
|
})
|
|
|
|
|
expect(entry.status).toBeNull()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('generateUntitledName', () => {
|
|
|
|
|
it('returns base name when no conflicts', () => {
|
|
|
|
|
expect(generateUntitledName([], 'Note')).toBe('Untitled note')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('appends counter when base name exists', () => {
|
|
|
|
|
const entries = [makeEntry({ title: 'Untitled note' })]
|
|
|
|
|
expect(generateUntitledName(entries, 'Note')).toBe('Untitled note 2')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('increments counter past existing numbered entries', () => {
|
|
|
|
|
const entries = [
|
|
|
|
|
makeEntry({ title: 'Untitled note' }),
|
|
|
|
|
makeEntry({ title: 'Untitled note 2' }),
|
|
|
|
|
makeEntry({ title: 'Untitled note 3' }),
|
|
|
|
|
]
|
|
|
|
|
expect(generateUntitledName(entries, 'Note')).toBe('Untitled note 4')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('uses type name in lowercase', () => {
|
|
|
|
|
expect(generateUntitledName([], 'Project')).toBe('Untitled project')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('entryMatchesTarget', () => {
|
|
|
|
|
it('matches by exact title (case-insensitive)', () => {
|
|
|
|
|
const entry = makeEntry({ title: 'My Project' })
|
|
|
|
|
expect(entryMatchesTarget(entry, 'my project', 'my project')).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('matches by alias', () => {
|
|
|
|
|
const entry = makeEntry({ aliases: ['MP', 'TheProject'] })
|
|
|
|
|
expect(entryMatchesTarget(entry, 'mp', 'mp')).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('matches by path stem (relative to Laputa)', () => {
|
|
|
|
|
const entry = makeEntry({ path: '/Users/luca/Laputa/project/my-project.md' })
|
|
|
|
|
expect(entryMatchesTarget(entry, 'project/my-project', 'project/my-project')).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('matches by filename stem', () => {
|
|
|
|
|
const entry = makeEntry({ filename: 'my-project.md' })
|
|
|
|
|
expect(entryMatchesTarget(entry, 'my-project', 'my-project')).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('matches when target as words matches title', () => {
|
|
|
|
|
const entry = makeEntry({ title: 'my project' })
|
|
|
|
|
expect(entryMatchesTarget(entry, 'project/my-project', 'my project')).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns false when nothing matches', () => {
|
|
|
|
|
const entry = makeEntry({ title: 'Something Else', aliases: [], filename: 'else.md' })
|
|
|
|
|
expect(entryMatchesTarget(entry, 'nonexistent', 'nonexistent')).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('buildNoteContent', () => {
|
|
|
|
|
it('generates frontmatter with status for regular types', () => {
|
|
|
|
|
const content = buildNoteContent('My Note', 'Note', 'Active')
|
|
|
|
|
expect(content).toBe('---\ntitle: My Note\nis_a: Note\nstatus: Active\n---\n\n# My Note\n\n')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('omits status when null', () => {
|
|
|
|
|
const content = buildNoteContent('AI', 'Topic', null)
|
|
|
|
|
expect(content).toBe('---\ntitle: AI\nis_a: Topic\n---\n\n# AI\n\n')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('resolveNewNote', () => {
|
|
|
|
|
it('uses TYPE_FOLDER_MAP for known types', () => {
|
|
|
|
|
const { entry, content } = resolveNewNote('My Project', 'Project')
|
|
|
|
|
expect(entry.path).toBe('/Users/luca/Laputa/project/my-project.md')
|
|
|
|
|
expect(entry.isA).toBe('Project')
|
|
|
|
|
expect(entry.status).toBe('Active')
|
|
|
|
|
expect(content).toContain('is_a: Project')
|
|
|
|
|
expect(content).toContain('status: Active')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('falls back to slugified type for custom types', () => {
|
|
|
|
|
const { entry } = resolveNewNote('First Recipe', 'Recipe')
|
|
|
|
|
expect(entry.path).toBe('/Users/luca/Laputa/recipe/first-recipe.md')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('omits status for Topic type', () => {
|
|
|
|
|
const { entry, content } = resolveNewNote('Machine Learning', 'Topic')
|
|
|
|
|
expect(entry.status).toBeNull()
|
|
|
|
|
expect(content).not.toContain('status:')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('omits status for Person type', () => {
|
|
|
|
|
const { entry } = resolveNewNote('John Doe', 'Person')
|
|
|
|
|
expect(entry.status).toBeNull()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('resolveNewType', () => {
|
|
|
|
|
it('creates a type entry in the type folder', () => {
|
|
|
|
|
const { entry, content } = resolveNewType('Recipe')
|
|
|
|
|
expect(entry.path).toBe('/Users/luca/Laputa/type/recipe.md')
|
|
|
|
|
expect(entry.isA).toBe('Type')
|
|
|
|
|
expect(entry.status).toBeNull()
|
|
|
|
|
expect(content).toContain('Is A: Type')
|
|
|
|
|
expect(content).toContain('# Recipe')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
describe('frontmatterToEntryPatch', () => {
|
|
|
|
|
it.each([
|
|
|
|
|
['is_a', 'Project', { isA: 'Project' }],
|
|
|
|
|
['status', 'Done', { status: 'Done' }],
|
|
|
|
|
['color', 'red', { color: 'red' }],
|
|
|
|
|
['icon', 'star', { icon: 'star' }],
|
|
|
|
|
['owner', 'Luca', { owner: 'Luca' }],
|
|
|
|
|
['cadence', 'Weekly', { cadence: 'Weekly' }],
|
|
|
|
|
['archived', true, { archived: true }],
|
|
|
|
|
['trashed', true, { trashed: true }],
|
|
|
|
|
['order', 5, { order: 5 }],
|
|
|
|
|
] as [string, unknown, Partial<VaultEntry>][])(
|
|
|
|
|
'maps %s update to correct entry field',
|
|
|
|
|
(key, value, expected) => {
|
|
|
|
|
expect(frontmatterToEntryPatch('update', key, value as never)).toEqual(expected)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
it('maps aliases update with array value', () => {
|
|
|
|
|
expect(frontmatterToEntryPatch('update', 'aliases', ['A', 'B'])).toEqual({ aliases: ['A', 'B'] })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('maps belongs_to update with array value', () => {
|
|
|
|
|
expect(frontmatterToEntryPatch('update', 'belongs_to', ['[[parent]]'])).toEqual({ belongsTo: ['[[parent]]'] })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handles case-insensitive keys with spaces (e.g. "Is A", "Belongs to")', () => {
|
|
|
|
|
expect(frontmatterToEntryPatch('update', 'Is A', 'Experiment')).toEqual({ isA: 'Experiment' })
|
|
|
|
|
expect(frontmatterToEntryPatch('update', 'Belongs to', ['[[x]]'])).toEqual({ belongsTo: ['[[x]]'] })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns empty object for unknown keys', () => {
|
|
|
|
|
expect(frontmatterToEntryPatch('update', 'custom_field', 'value')).toEqual({})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it.each([
|
|
|
|
|
['status', { status: null }],
|
|
|
|
|
['color', { color: null }],
|
|
|
|
|
['aliases', { aliases: [] }],
|
|
|
|
|
['archived', { archived: false }],
|
|
|
|
|
['order', { order: null }],
|
|
|
|
|
] as [string, Partial<VaultEntry>][])(
|
|
|
|
|
'maps delete of %s to null/default',
|
|
|
|
|
(key, expected) => {
|
|
|
|
|
expect(frontmatterToEntryPatch('delete', key)).toEqual(expected)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
it('returns empty object for unknown key on delete', () => {
|
|
|
|
|
expect(frontmatterToEntryPatch('delete', 'unknown_key')).toEqual({})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
describe('useNoteActions hook', () => {
|
|
|
|
|
const addEntry = vi.fn()
|
|
|
|
|
const updateContent = vi.fn()
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
const updateEntry = vi.fn()
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
const setToastMessage = vi.fn()
|
|
|
|
|
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
const makeConfig = (entries: VaultEntry[] = []): NoteActionsConfig => ({
|
|
|
|
|
addEntry, updateContent, entries, setToastMessage, updateEntry,
|
|
|
|
|
})
|
|
|
|
|
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.clearAllMocks()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleCreateNote calls addEntry and creates correct entry', () => {
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleCreateNote('Test Note', 'Note')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(addEntry).toHaveBeenCalledTimes(1)
|
|
|
|
|
const [createdEntry, createdContent] = addEntry.mock.calls[0]
|
|
|
|
|
expect(createdEntry.title).toBe('Test Note')
|
|
|
|
|
expect(createdEntry.isA).toBe('Note')
|
|
|
|
|
expect(createdEntry.path).toContain('note/test-note.md')
|
|
|
|
|
expect(createdContent).toContain('title: Test Note')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleCreateType creates type entry', () => {
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleCreateType('Recipe')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(addEntry).toHaveBeenCalledTimes(1)
|
|
|
|
|
const [createdEntry] = addEntry.mock.calls[0]
|
|
|
|
|
expect(createdEntry.isA).toBe('Type')
|
|
|
|
|
expect(createdEntry.title).toBe('Recipe')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleNavigateWikilink finds entry by title', async () => {
|
|
|
|
|
const target = makeEntry({ title: 'Target Note', path: '/vault/note/target.md' })
|
|
|
|
|
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig([target])))
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
result.current.handleNavigateWikilink('Target Note')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(result.current.activeTabPath).toBe('/vault/note/target.md')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleNavigateWikilink warns when target not found', () => {
|
|
|
|
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
|
|
|
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleNavigateWikilink('Nonexistent')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(warnSpy).toHaveBeenCalledWith('Navigation target not found: Nonexistent')
|
|
|
|
|
warnSpy.mockRestore()
|
|
|
|
|
})
|
|
|
|
|
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
it('handleUpdateFrontmatter calls updateEntry with mapped patch', async () => {
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
await result.current.handleUpdateFrontmatter('/vault/note.md', 'status', 'Done')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(updateContent).toHaveBeenCalled()
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
expect(updateEntry).toHaveBeenCalledWith('/vault/note.md', { status: 'Done' })
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
expect(setToastMessage).toHaveBeenCalledWith('Property updated')
|
|
|
|
|
})
|
|
|
|
|
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
it('handleUpdateFrontmatter syncs is_a and color changes to entries', async () => {
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
await result.current.handleUpdateFrontmatter('/vault/note.md', 'is_a', 'Project')
|
|
|
|
|
})
|
|
|
|
|
expect(updateEntry).toHaveBeenCalledWith('/vault/note.md', { isA: 'Project' })
|
|
|
|
|
|
|
|
|
|
vi.clearAllMocks()
|
|
|
|
|
await act(async () => {
|
|
|
|
|
await result.current.handleUpdateFrontmatter('/vault/note.md', 'color', 'blue')
|
|
|
|
|
})
|
|
|
|
|
expect(updateEntry).toHaveBeenCalledWith('/vault/note.md', { color: 'blue' })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleDeleteProperty calls updateEntry with null/default values', async () => {
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
await result.current.handleDeleteProperty('/vault/note.md', 'status')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(updateContent).toHaveBeenCalled()
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
expect(updateEntry).toHaveBeenCalledWith('/vault/note.md', { status: null })
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
expect(setToastMessage).toHaveBeenCalledWith('Property deleted')
|
|
|
|
|
})
|
fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:
1. Property changes (type, status, color, etc.) now immediately sync to
the entries state via frontmatterToEntryPatch(), so sidebar, note list,
and relations panel reflect updates without restart.
2. After renaming a note, all other open tabs reload their content from
disk, picking up updated wikilinks immediately.
Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
parse_md_file Tauri command — simpler, works in both Tauri and mock
modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
as standalone functions to reduce hook complexity (cc 10→9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:48:39 +01:00
|
|
|
|
|
|
|
|
it('handleUpdateFrontmatter does not call updateEntry for unknown keys', async () => {
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
await result.current.handleUpdateFrontmatter('/vault/note.md', 'custom_field', 'value')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(updateEntry).not.toHaveBeenCalled()
|
|
|
|
|
expect(setToastMessage).toHaveBeenCalledWith('Property updated')
|
|
|
|
|
})
|
test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
|
|
|
})
|