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'
|
feat: add optimistic error recovery for note creation (#69)
Note creation was already optimistic (UI updates before disk write) but
errors were silently swallowed. Now if the disk write fails, the
optimistic entry is reverted (tab closed, entry removed) and the user
sees an error toast. Each note creation is independent, so one failure
in rapid Cmd+N presses doesn't affect the others.
- Add removeEntry to useVaultLoader for reverting optimistic adds
- Refactor persistNewNote to return a Promise for error handling
- Extract navigateWikilink, persistOptimistic, createAndPersist,
and runFrontmatterAndApply helpers to reduce hook complexity
- Add tests for error recovery: single note, type, success path,
and rapid creation with partial failure
- Code health: 8.93→9.01 (CC fixed: 10→7, primitive obsession improved)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:11:59 +01:00
|
|
|
import { invoke } from '@tauri-apps/api/core'
|
2026-03-08 21:37:27 +01:00
|
|
|
import { isTauri, mockInvoke } from '../mock-tauri'
|
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 type { VaultEntry } from '../types'
|
|
|
|
|
import {
|
|
|
|
|
slugify,
|
|
|
|
|
buildNewEntry,
|
|
|
|
|
generateUntitledName,
|
|
|
|
|
entryMatchesTarget,
|
|
|
|
|
buildNoteContent,
|
|
|
|
|
resolveNewNote,
|
|
|
|
|
resolveNewType,
|
2026-03-02 03:08:15 +01:00
|
|
|
todayDateString,
|
|
|
|
|
buildDailyNoteContent,
|
|
|
|
|
resolveDailyNote,
|
|
|
|
|
findDailyNote,
|
2026-03-02 08:37:06 +01:00
|
|
|
DEFAULT_TEMPLATES,
|
|
|
|
|
resolveTemplate,
|
2026-03-16 23:34:45 +01:00
|
|
|
} from './useNoteCreation'
|
|
|
|
|
import { needsRenameOnSave } from './useNoteRename'
|
2026-03-20 03:26:52 +01:00
|
|
|
import { frontmatterToEntryPatch, applyRelationshipPatch } from './frontmatterOps'
|
2026-03-16 23:34:45 +01:00
|
|
|
import { 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', () => ({
|
feat: add optimistic error recovery for note creation (#69)
Note creation was already optimistic (UI updates before disk write) but
errors were silently swallowed. Now if the disk write fails, the
optimistic entry is reverted (tab closed, entry removed) and the user
sees an error toast. Each note creation is independent, so one failure
in rapid Cmd+N presses doesn't affect the others.
- Add removeEntry to useVaultLoader for reverting optimistic adds
- Refactor persistNewNote to return a Promise for error handling
- Extract navigateWikilink, persistOptimistic, createAndPersist,
and runFrontmatterAndApply helpers to reduce hook complexity
- Add tests for error recovery: single note, type, success path,
and rapid creation with partial failure
- Code health: 8.93→9.01 (CC fixed: 10→7, primitive obsession improved)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:11:59 +01:00
|
|
|
isTauri: vi.fn(() => false),
|
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
|
|
|
addMockEntry: vi.fn(),
|
|
|
|
|
updateMockContent: vi.fn(),
|
2026-03-06 23:08:18 +01:00
|
|
|
trackMockChange: 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
|
|
|
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 => ({
|
2026-03-15 23:40:47 +01:00
|
|
|
path: '/Users/luca/Laputa/test.md',
|
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
|
|
|
filename: 'test.md',
|
|
|
|
|
title: 'Test Note',
|
|
|
|
|
isA: 'Note',
|
|
|
|
|
aliases: [],
|
|
|
|
|
belongsTo: [],
|
|
|
|
|
relatedTo: [],
|
|
|
|
|
status: 'Active',
|
|
|
|
|
archived: false,
|
|
|
|
|
trashed: false,
|
|
|
|
|
trashedAt: null,
|
|
|
|
|
modifiedAt: 1700000000,
|
|
|
|
|
createdAt: 1700000000,
|
|
|
|
|
fileSize: 100,
|
|
|
|
|
snippet: '',
|
2026-02-26 20:50:29 +01:00
|
|
|
wordCount: 0,
|
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
|
|
|
relationships: {},
|
|
|
|
|
icon: null,
|
|
|
|
|
color: null,
|
|
|
|
|
order: null,
|
2026-02-25 15:04:49 +01:00
|
|
|
outgoingLinks: [],
|
2026-03-03 11:22:04 +01:00
|
|
|
template: null, sort: 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
|
|
|
...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')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-11 16:42:32 +01:00
|
|
|
it('handles empty string with fallback', () => {
|
|
|
|
|
expect(slugify('')).toBe('untitled')
|
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
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('collapses multiple separators into one hyphen', () => {
|
|
|
|
|
expect(slugify('hello world---foo')).toBe('hello-world-foo')
|
|
|
|
|
})
|
2026-03-11 16:42:32 +01:00
|
|
|
|
|
|
|
|
it('returns fallback for strings with only special characters', () => {
|
|
|
|
|
// slugify('+++') should not return empty string — it causes invalid paths
|
|
|
|
|
expect(slugify('+++')).not.toBe('')
|
|
|
|
|
expect(slugify('!!!')).not.toBe('')
|
|
|
|
|
expect(slugify('---')).not.toBe('')
|
|
|
|
|
expect(slugify('@#$')).not.toBe('')
|
|
|
|
|
})
|
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
|
|
|
})
|
|
|
|
|
|
2026-03-11 17:18:37 +01:00
|
|
|
describe('needsRenameOnSave', () => {
|
|
|
|
|
it('returns true when filename does not match title slug', () => {
|
|
|
|
|
expect(needsRenameOnSave('My New Note', 'untitled-note.md')).toBe(true)
|
|
|
|
|
expect(needsRenameOnSave('Run good ads for newsletter', 'untitled-note-9.md')).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns false when filename matches title slug', () => {
|
|
|
|
|
expect(needsRenameOnSave('My Note', 'my-note.md')).toBe(false)
|
|
|
|
|
expect(needsRenameOnSave('Hello World', 'hello-world.md')).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns false for untitled note with matching slug', () => {
|
|
|
|
|
expect(needsRenameOnSave('Untitled note', 'untitled-note.md')).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
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('buildNewEntry', () => {
|
|
|
|
|
it('creates a VaultEntry with correct fields', () => {
|
|
|
|
|
const entry = buildNewEntry({
|
2026-03-15 23:40:47 +01:00
|
|
|
path: '/vault/my-note.md',
|
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
|
|
|
slug: 'my-note',
|
|
|
|
|
title: 'My Note',
|
|
|
|
|
type: 'Note',
|
|
|
|
|
status: 'Active',
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-15 23:40:47 +01:00
|
|
|
expect(entry.path).toBe('/vault/my-note.md')
|
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(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')
|
|
|
|
|
})
|
2026-02-24 23:03:01 +01:00
|
|
|
|
|
|
|
|
it('avoids names in the pending set', () => {
|
|
|
|
|
const pending = new Set(['Untitled note'])
|
|
|
|
|
expect(generateUntitledName([], 'Note', pending)).toBe('Untitled note 2')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('avoids both existing and pending names', () => {
|
|
|
|
|
const entries = [makeEntry({ title: 'Untitled note' })]
|
|
|
|
|
const pending = new Set(['Untitled note 2'])
|
|
|
|
|
expect(generateUntitledName(entries, 'Note', pending)).toBe('Untitled note 3')
|
|
|
|
|
})
|
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('entryMatchesTarget', () => {
|
|
|
|
|
it('matches by exact title (case-insensitive)', () => {
|
|
|
|
|
const entry = makeEntry({ title: 'My Project' })
|
2026-03-11 19:12:05 +01:00
|
|
|
expect(entryMatchesTarget(entry, 'my project')).toBe(true)
|
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
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('matches by alias', () => {
|
|
|
|
|
const entry = makeEntry({ aliases: ['MP', 'TheProject'] })
|
2026-03-11 19:12:05 +01:00
|
|
|
expect(entryMatchesTarget(entry, 'mp')).toBe(true)
|
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
|
|
|
})
|
|
|
|
|
|
2026-03-16 04:40:46 +01:00
|
|
|
it('matches legacy path-style target via filename stem', () => {
|
|
|
|
|
const entry = makeEntry({ filename: 'my-project.md' })
|
2026-03-11 19:12:05 +01:00
|
|
|
expect(entryMatchesTarget(entry, 'project/my-project')).toBe(true)
|
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
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('matches by filename stem', () => {
|
|
|
|
|
const entry = makeEntry({ filename: 'my-project.md' })
|
2026-03-11 19:12:05 +01:00
|
|
|
expect(entryMatchesTarget(entry, 'my-project')).toBe(true)
|
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
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('matches when target as words matches title', () => {
|
|
|
|
|
const entry = makeEntry({ title: 'my project' })
|
2026-03-11 19:12:05 +01:00
|
|
|
expect(entryMatchesTarget(entry, 'my-project')).toBe(true)
|
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
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns false when nothing matches', () => {
|
|
|
|
|
const entry = makeEntry({ title: 'Something Else', aliases: [], filename: 'else.md' })
|
2026-03-11 19:12:05 +01:00
|
|
|
expect(entryMatchesTarget(entry, 'nonexistent')).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handles pipe syntax targets', () => {
|
|
|
|
|
const entry = makeEntry({ path: '/vault/project/alpha.md', filename: 'alpha.md', title: 'Alpha' })
|
|
|
|
|
expect(entryMatchesTarget(entry, 'project/alpha|Alpha Project')).toBe(true)
|
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('buildNoteContent', () => {
|
|
|
|
|
it('generates frontmatter with status for regular types', () => {
|
|
|
|
|
const content = buildNoteContent('My Note', 'Note', 'Active')
|
2026-02-23 15:24:57 +01:00
|
|
|
expect(content).toBe('---\ntitle: My Note\ntype: Note\nstatus: Active\n---\n\n# My Note\n\n')
|
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
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('omits status when null', () => {
|
|
|
|
|
const content = buildNoteContent('AI', 'Topic', null)
|
2026-02-23 15:24:57 +01:00
|
|
|
expect(content).toBe('---\ntitle: AI\ntype: Topic\n---\n\n# AI\n\n')
|
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
|
|
|
})
|
2026-03-02 08:37:06 +01:00
|
|
|
|
|
|
|
|
it('includes template body when provided', () => {
|
|
|
|
|
const content = buildNoteContent('My Project', 'Project', 'Active', '## Objective\n\n## Notes\n\n')
|
|
|
|
|
expect(content).toContain('# My Project')
|
|
|
|
|
expect(content).toContain('## Objective')
|
|
|
|
|
expect(content).toContain('## Notes')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('ignores null template', () => {
|
|
|
|
|
const content = buildNoteContent('My Note', 'Note', 'Active', null)
|
|
|
|
|
expect(content).toBe('---\ntitle: My Note\ntype: Note\nstatus: Active\n---\n\n# My Note\n\n')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('resolveTemplate', () => {
|
|
|
|
|
it('returns template from type entry when set', () => {
|
|
|
|
|
const typeEntry = makeEntry({ isA: 'Type', title: 'Recipe', template: '## Ingredients\n\n## Steps\n\n' })
|
|
|
|
|
expect(resolveTemplate([typeEntry], 'Recipe')).toBe('## Ingredients\n\n## Steps\n\n')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('falls back to DEFAULT_TEMPLATES for built-in types', () => {
|
|
|
|
|
expect(resolveTemplate([], 'Project')).toBe(DEFAULT_TEMPLATES.Project)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns null when no template and no default', () => {
|
|
|
|
|
expect(resolveTemplate([], 'CustomType')).toBeNull()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('type entry template overrides default', () => {
|
|
|
|
|
const typeEntry = makeEntry({ isA: 'Type', title: 'Project', template: '## Custom\n\n' })
|
|
|
|
|
expect(resolveTemplate([typeEntry], 'Project')).toBe('## Custom\n\n')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('DEFAULT_TEMPLATES', () => {
|
|
|
|
|
it('has templates for Project, Person, Responsibility, Experiment', () => {
|
|
|
|
|
expect(DEFAULT_TEMPLATES.Project).toBeDefined()
|
|
|
|
|
expect(DEFAULT_TEMPLATES.Person).toBeDefined()
|
|
|
|
|
expect(DEFAULT_TEMPLATES.Responsibility).toBeDefined()
|
|
|
|
|
expect(DEFAULT_TEMPLATES.Experiment).toBeDefined()
|
|
|
|
|
})
|
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('resolveNewNote', () => {
|
2026-03-15 23:40:47 +01:00
|
|
|
it('creates note at vault root', () => {
|
2026-03-08 20:00:20 +01:00
|
|
|
const { entry, content } = resolveNewNote('My Project', 'Project', '/my/vault')
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(entry.path).toBe('/my/vault/my-project.md')
|
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(entry.isA).toBe('Project')
|
|
|
|
|
expect(entry.status).toBe('Active')
|
2026-02-23 15:24:57 +01:00
|
|
|
expect(content).toContain('type: Project')
|
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(content).toContain('status: Active')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-15 23:40:47 +01:00
|
|
|
it('creates custom type note at vault root', () => {
|
2026-03-08 20:00:20 +01:00
|
|
|
const { entry } = resolveNewNote('First Recipe', 'Recipe', '/my/vault')
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(entry.path).toBe('/my/vault/first-recipe.md')
|
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
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('omits status for Topic type', () => {
|
2026-03-08 20:00:20 +01:00
|
|
|
const { entry, content } = resolveNewNote('Machine Learning', 'Topic', '/my/vault')
|
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(entry.status).toBeNull()
|
|
|
|
|
expect(content).not.toContain('status:')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('omits status for Person type', () => {
|
2026-03-08 20:00:20 +01:00
|
|
|
const { entry } = resolveNewNote('John Doe', 'Person', '/my/vault')
|
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(entry.status).toBeNull()
|
|
|
|
|
})
|
2026-03-08 20:00:20 +01:00
|
|
|
|
2026-03-15 23:40:47 +01:00
|
|
|
it('uses provided vault path', () => {
|
2026-03-08 20:00:20 +01:00
|
|
|
const { entry } = resolveNewNote('Test', 'Note', '/other/vault')
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(entry.path).toBe('/other/vault/test.md')
|
2026-03-08 20:00:20 +01:00
|
|
|
})
|
2026-03-11 16:42:32 +01:00
|
|
|
|
|
|
|
|
it('produces a valid path for custom types with special characters', () => {
|
|
|
|
|
const { entry } = resolveNewNote('My Note', 'Q&A', '/vault')
|
|
|
|
|
expect(entry.path).not.toContain('//')
|
|
|
|
|
expect(entry.path).toMatch(/\.md$/)
|
|
|
|
|
expect(entry.filename).not.toBe('.md')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('produces a valid path when type is all special characters', () => {
|
|
|
|
|
const { entry } = resolveNewNote('My Note', '+++', '/vault')
|
|
|
|
|
expect(entry.path).not.toContain('//')
|
|
|
|
|
expect(entry.path).toMatch(/\.md$/)
|
|
|
|
|
})
|
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('resolveNewType', () => {
|
2026-03-16 16:17:58 +01:00
|
|
|
it('creates a type entry at vault root', () => {
|
2026-03-08 20:00:20 +01:00
|
|
|
const { entry, content } = resolveNewType('Recipe', '/my/vault')
|
2026-03-16 16:17:58 +01:00
|
|
|
expect(entry.path).toBe('/my/vault/recipe.md')
|
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(entry.isA).toBe('Type')
|
|
|
|
|
expect(entry.status).toBeNull()
|
2026-02-23 15:24:57 +01:00
|
|
|
expect(content).toContain('type: Type')
|
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(content).toContain('# Recipe')
|
|
|
|
|
})
|
2026-03-08 20:00:20 +01:00
|
|
|
|
|
|
|
|
it('uses provided vault path instead of hardcoded path', () => {
|
|
|
|
|
const { entry } = resolveNewType('Responsibility', '/other/vault')
|
2026-03-16 16:17:58 +01:00
|
|
|
expect(entry.path).toBe('/other/vault/responsibility.md')
|
2026-03-08 20:00:20 +01:00
|
|
|
expect(entry.path).not.toContain('/Users/luca/Laputa')
|
|
|
|
|
})
|
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
|
|
|
})
|
|
|
|
|
|
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([
|
2026-02-23 15:24:57 +01:00
|
|
|
['type', 'Project', { isA: 'Project' }],
|
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
|
|
|
['is_a', 'Project', { isA: 'Project' }],
|
|
|
|
|
['status', 'Done', { status: 'Done' }],
|
|
|
|
|
['color', 'red', { color: 'red' }],
|
|
|
|
|
['icon', 'star', { icon: 'star' }],
|
|
|
|
|
['archived', true, { archived: true }],
|
|
|
|
|
['trashed', true, { trashed: true }],
|
|
|
|
|
['order', 5, { order: 5 }],
|
2026-03-02 08:37:06 +01:00
|
|
|
['template', '## Heading\n\n', { template: '## Heading\n\n' }],
|
2026-03-07 00:20:51 +01:00
|
|
|
['visible', false, { visible: false }],
|
|
|
|
|
['visible', true, { visible: null }],
|
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
|
|
|
] as [string, unknown, Partial<VaultEntry>][])(
|
|
|
|
|
'maps %s update to correct entry field',
|
|
|
|
|
(key, value, expected) => {
|
2026-03-20 03:26:52 +01:00
|
|
|
expect(frontmatterToEntryPatch('update', key, value as never).patch).toEqual(expected)
|
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('maps aliases update with array value', () => {
|
2026-03-20 03:26:52 +01:00
|
|
|
expect(frontmatterToEntryPatch('update', 'aliases', ['A', 'B']).patch).toEqual({ aliases: ['A', 'B'] })
|
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('maps belongs_to update with array value', () => {
|
2026-03-20 03:26:52 +01:00
|
|
|
const result = frontmatterToEntryPatch('update', 'belongs_to', ['[[parent]]'])
|
|
|
|
|
expect(result.patch).toEqual({ belongsTo: ['[[parent]]'] })
|
|
|
|
|
// Also produces a relationship patch for the wikilink
|
|
|
|
|
expect(result.relationshipPatch).toEqual({ 'belongs_to': ['[[parent]]'] })
|
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('handles case-insensitive keys with spaces (e.g. "Is A", "Belongs to")', () => {
|
2026-03-20 03:26:52 +01:00
|
|
|
expect(frontmatterToEntryPatch('update', 'Is A', 'Experiment').patch).toEqual({ isA: 'Experiment' })
|
|
|
|
|
const result = frontmatterToEntryPatch('update', 'Belongs to', ['[[x]]'])
|
|
|
|
|
expect(result.patch).toEqual({ belongsTo: ['[[x]]'] })
|
|
|
|
|
expect(result.relationshipPatch).toEqual({ 'Belongs to': ['[[x]]'] })
|
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
|
|
|
})
|
|
|
|
|
|
2026-03-20 03:26:52 +01:00
|
|
|
it('returns empty patch for unknown non-wikilink keys', () => {
|
|
|
|
|
const result = frontmatterToEntryPatch('update', 'custom_field', 'value')
|
|
|
|
|
expect(result.patch).toEqual({})
|
|
|
|
|
// Non-wikilink value → no relationship change
|
|
|
|
|
expect(result.relationshipPatch).toBeNull()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('produces relationship patch for wikilink values on unknown keys', () => {
|
|
|
|
|
const result = frontmatterToEntryPatch('update', 'Notes', ['[[note-a]]', '[[note-b|Note B]]'])
|
|
|
|
|
expect(result.patch).toEqual({})
|
|
|
|
|
expect(result.relationshipPatch).toEqual({ Notes: ['[[note-a]]', '[[note-b|Note B]]'] })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('produces relationship patch for single wikilink string', () => {
|
|
|
|
|
const result = frontmatterToEntryPatch('update', 'Owner', '[[person/alice]]')
|
|
|
|
|
expect(result.patch).toEqual({})
|
|
|
|
|
expect(result.relationshipPatch).toEqual({ Owner: ['[[person/alice]]'] })
|
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.each([
|
|
|
|
|
['status', { status: null }],
|
|
|
|
|
['color', { color: null }],
|
|
|
|
|
['aliases', { aliases: [] }],
|
|
|
|
|
['archived', { archived: false }],
|
|
|
|
|
['order', { order: null }],
|
2026-03-02 08:37:06 +01:00
|
|
|
['template', { template: null }],
|
2026-03-07 00:20:51 +01:00
|
|
|
['visible', { visible: null }],
|
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
|
|
|
] as [string, Partial<VaultEntry>][])(
|
|
|
|
|
'maps delete of %s to null/default',
|
|
|
|
|
(key, expected) => {
|
2026-03-20 03:26:52 +01:00
|
|
|
expect(frontmatterToEntryPatch('delete', key).patch).toEqual(expected)
|
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
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-20 03:26:52 +01:00
|
|
|
it('returns empty patch for unknown key on delete, with relationship removal', () => {
|
|
|
|
|
const result = frontmatterToEntryPatch('delete', 'unknown_key')
|
|
|
|
|
expect(result.patch).toEqual({})
|
|
|
|
|
expect(result.relationshipPatch).toEqual({ unknown_key: null })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('delete of known key also produces relationship removal', () => {
|
|
|
|
|
const result = frontmatterToEntryPatch('delete', 'status')
|
|
|
|
|
expect(result.patch).toEqual({ status: null })
|
|
|
|
|
expect(result.relationshipPatch).toEqual({ status: null })
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('applyRelationshipPatch', () => {
|
|
|
|
|
it('adds new relationship key', () => {
|
|
|
|
|
const existing = { 'Belongs to': ['[[eng]]'] }
|
|
|
|
|
const result = applyRelationshipPatch(existing, { Notes: ['[[note-a]]', '[[note-b]]'] })
|
|
|
|
|
expect(result).toEqual({ 'Belongs to': ['[[eng]]'], Notes: ['[[note-a]]', '[[note-b]]'] })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('overwrites existing relationship key', () => {
|
|
|
|
|
const existing = { Notes: ['[[old]]'] }
|
|
|
|
|
const result = applyRelationshipPatch(existing, { Notes: ['[[new-a]]', '[[new-b]]'] })
|
|
|
|
|
expect(result).toEqual({ Notes: ['[[new-a]]', '[[new-b]]'] })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('removes relationship key when value is null', () => {
|
|
|
|
|
const existing = { Notes: ['[[a]]'], Owner: ['[[alice]]'] }
|
|
|
|
|
const result = applyRelationshipPatch(existing, { Notes: null })
|
|
|
|
|
expect(result).toEqual({ Owner: ['[[alice]]'] })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('does not mutate the original map', () => {
|
|
|
|
|
const existing = { Notes: ['[[a]]'] }
|
|
|
|
|
applyRelationshipPatch(existing, { Notes: ['[[b]]'] })
|
|
|
|
|
expect(existing).toEqual({ Notes: ['[[a]]'] })
|
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
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-02 03:08:15 +01:00
|
|
|
describe('todayDateString', () => {
|
|
|
|
|
it('returns date in YYYY-MM-DD format', () => {
|
|
|
|
|
const result = todayDateString()
|
|
|
|
|
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('buildDailyNoteContent', () => {
|
|
|
|
|
it('generates frontmatter with Journal type and date', () => {
|
|
|
|
|
const content = buildDailyNoteContent('2026-03-02')
|
|
|
|
|
expect(content).toContain('type: Journal')
|
|
|
|
|
expect(content).toContain('date: 2026-03-02')
|
|
|
|
|
expect(content).toContain('title: 2026-03-02')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('includes Intentions and Reflections sections', () => {
|
|
|
|
|
const content = buildDailyNoteContent('2026-03-02')
|
|
|
|
|
expect(content).toContain('## Intentions')
|
|
|
|
|
expect(content).toContain('## Reflections')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('includes H1 heading with the date', () => {
|
|
|
|
|
const content = buildDailyNoteContent('2026-03-02')
|
|
|
|
|
expect(content).toContain('# 2026-03-02')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('resolveDailyNote', () => {
|
2026-03-15 19:43:00 +01:00
|
|
|
it('creates entry at vault root with date as filename', () => {
|
2026-03-08 20:00:20 +01:00
|
|
|
const { entry } = resolveDailyNote('2026-03-02', '/my/vault')
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(entry.path).toBe('/my/vault/2026-03-02.md')
|
2026-03-02 03:08:15 +01:00
|
|
|
expect(entry.filename).toBe('2026-03-02.md')
|
|
|
|
|
expect(entry.title).toBe('2026-03-02')
|
|
|
|
|
expect(entry.isA).toBe('Journal')
|
|
|
|
|
expect(entry.status).toBeNull()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns content with daily note template', () => {
|
2026-03-08 20:00:20 +01:00
|
|
|
const { content } = resolveDailyNote('2026-03-02', '/my/vault')
|
2026-03-02 03:08:15 +01:00
|
|
|
expect(content).toContain('type: Journal')
|
|
|
|
|
expect(content).toContain('## Intentions')
|
|
|
|
|
})
|
2026-03-08 20:00:20 +01:00
|
|
|
|
2026-03-15 23:40:47 +01:00
|
|
|
it('uses provided vault path', () => {
|
2026-03-08 20:00:20 +01:00
|
|
|
const { entry } = resolveDailyNote('2026-03-02', '/other/vault')
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(entry.path).toBe('/other/vault/2026-03-02.md')
|
2026-03-08 20:00:20 +01:00
|
|
|
})
|
2026-03-02 03:08:15 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('findDailyNote', () => {
|
2026-03-15 19:43:00 +01:00
|
|
|
it('finds entry by filename and Journal type', () => {
|
2026-03-02 03:08:15 +01:00
|
|
|
const entries = [
|
2026-03-15 19:43:00 +01:00
|
|
|
makeEntry({ path: '/Users/luca/Laputa/2026-03-02.md', filename: '2026-03-02.md', isA: 'Journal' }),
|
2026-03-15 23:40:47 +01:00
|
|
|
makeEntry({ path: '/Users/luca/Laputa/other.md' }),
|
2026-03-02 03:08:15 +01:00
|
|
|
]
|
|
|
|
|
const found = findDailyNote(entries, '2026-03-02')
|
|
|
|
|
expect(found).toBeDefined()
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(found!.path).toBe('/Users/luca/Laputa/2026-03-02.md')
|
2026-03-02 03:08:15 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns undefined when no matching entry exists', () => {
|
2026-03-15 23:40:47 +01:00
|
|
|
const entries = [makeEntry({ path: '/Users/luca/Laputa/other.md' })]
|
2026-03-02 03:08:15 +01:00
|
|
|
expect(findDailyNote(entries, '2026-03-02')).toBeUndefined()
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-15 23:40:47 +01:00
|
|
|
it('does not match non-Journal notes with date filename', () => {
|
2026-03-02 03:08:15 +01:00
|
|
|
const entries = [
|
2026-03-15 19:43:00 +01:00
|
|
|
makeEntry({ path: '/vault/2026-03-02.md', filename: '2026-03-02.md', isA: 'Note' }),
|
2026-03-02 03:08:15 +01:00
|
|
|
]
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(findDailyNote(entries, '2026-03-02')).toBeUndefined()
|
2026-03-02 03:08:15 +01:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
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()
|
feat: add optimistic error recovery for note creation (#69)
Note creation was already optimistic (UI updates before disk write) but
errors were silently swallowed. Now if the disk write fails, the
optimistic entry is reverted (tab closed, entry removed) and the user
sees an error toast. Each note creation is independent, so one failure
in rapid Cmd+N presses doesn't affect the others.
- Add removeEntry to useVaultLoader for reverting optimistic adds
- Refactor persistNewNote to return a Promise for error handling
- Extract navigateWikilink, persistOptimistic, createAndPersist,
and runFrontmatterAndApply helpers to reduce hook complexity
- Add tests for error recovery: single note, type, success path,
and rapid creation with partial failure
- Code health: 8.93→9.01 (CC fixed: 10→7, primitive obsession improved)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:11:59 +01:00
|
|
|
const removeEntry = 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 => ({
|
2026-03-08 22:15:08 +01:00
|
|
|
addEntry, removeEntry, entries, setToastMessage, updateEntry, vaultPath: '/test/vault',
|
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
|
|
|
})
|
|
|
|
|
|
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()
|
feat: add optimistic error recovery for note creation (#69)
Note creation was already optimistic (UI updates before disk write) but
errors were silently swallowed. Now if the disk write fails, the
optimistic entry is reverted (tab closed, entry removed) and the user
sees an error toast. Each note creation is independent, so one failure
in rapid Cmd+N presses doesn't affect the others.
- Add removeEntry to useVaultLoader for reverting optimistic adds
- Refactor persistNewNote to return a Promise for error handling
- Extract navigateWikilink, persistOptimistic, createAndPersist,
and runFrontmatterAndApply helpers to reduce hook complexity
- Add tests for error recovery: single note, type, success path,
and rapid creation with partial failure
- Code health: 8.93→9.01 (CC fixed: 10→7, primitive obsession improved)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:11:59 +01:00
|
|
|
vi.mocked(isTauri).mockReturnValue(false)
|
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
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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)
|
2026-03-08 22:15:08 +01:00
|
|
|
const [createdEntry] = addEntry.mock.calls[0]
|
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(createdEntry.title).toBe('Test Note')
|
|
|
|
|
expect(createdEntry.isA).toBe('Note')
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(createdEntry.path).toContain('test-note.md')
|
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
|
|
|
})
|
|
|
|
|
|
2026-02-26 11:41:46 +01:00
|
|
|
it('handleCreateNote opens tab immediately (before addEntry resolves)', () => {
|
|
|
|
|
const callOrder: string[] = []
|
|
|
|
|
const trackedAddEntry = vi.fn(() => { callOrder.push('addEntry') })
|
|
|
|
|
const config = makeConfig()
|
|
|
|
|
config.addEntry = trackedAddEntry
|
|
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(config))
|
|
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleCreateNote('Fast Note', 'Note')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Tab should be open with the new note
|
|
|
|
|
expect(result.current.tabs).toHaveLength(1)
|
|
|
|
|
expect(result.current.tabs[0].entry.title).toBe('Fast Note')
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(result.current.activeTabPath).toContain('fast-note.md')
|
2026-02-26 11:41:46 +01:00
|
|
|
})
|
|
|
|
|
|
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
|
|
|
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 () => {
|
2026-03-15 23:40:47 +01:00
|
|
|
const target = makeEntry({ title: 'Target Note', path: '/vault/target.md' })
|
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
|
|
|
|
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')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-15 23:40:47 +01:00
|
|
|
expect(result.current.activeTabPath).toBe('/vault/target.md')
|
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
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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')
|
|
|
|
|
})
|
|
|
|
|
|
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')
|
|
|
|
|
})
|
|
|
|
|
|
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
|
|
|
|
2026-02-24 23:03:01 +01:00
|
|
|
it('handleCreateNoteImmediate creates note with auto-generated title', () => {
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
|
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleCreateNoteImmediate()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(addEntry).toHaveBeenCalledTimes(1)
|
|
|
|
|
const [createdEntry] = addEntry.mock.calls[0]
|
|
|
|
|
expect(createdEntry.title).toBe('Untitled note')
|
|
|
|
|
expect(createdEntry.isA).toBe('Note')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleCreateNoteImmediate generates unique names on rapid calls', () => {
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
|
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleCreateNoteImmediate()
|
|
|
|
|
result.current.handleCreateNoteImmediate()
|
|
|
|
|
result.current.handleCreateNoteImmediate()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(addEntry).toHaveBeenCalledTimes(3)
|
|
|
|
|
const titles = addEntry.mock.calls.map(([e]: [VaultEntry]) => e.title)
|
|
|
|
|
expect(titles[0]).toBe('Untitled note')
|
|
|
|
|
expect(titles[1]).toBe('Untitled note 2')
|
|
|
|
|
expect(titles[2]).toBe('Untitled note 3')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleCreateNoteImmediate accepts custom type', () => {
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
|
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleCreateNoteImmediate('Project')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(addEntry).toHaveBeenCalledTimes(1)
|
|
|
|
|
const [createdEntry] = addEntry.mock.calls[0]
|
|
|
|
|
expect(createdEntry.title).toBe('Untitled project')
|
|
|
|
|
expect(createdEntry.isA).toBe('Project')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-02 08:37:06 +01:00
|
|
|
it('handleCreateNote uses default template for Project type', () => {
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
|
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleCreateNote('My Project', 'Project')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-08 22:15:08 +01:00
|
|
|
const tabContent = result.current.tabs[0].content
|
|
|
|
|
expect(tabContent).toContain('## Objective')
|
|
|
|
|
expect(tabContent).toContain('## Key Results')
|
2026-03-02 08:37:06 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleCreateNote uses custom template from type entry', () => {
|
|
|
|
|
const typeEntry = makeEntry({ isA: 'Type', title: 'Recipe', template: '## Ingredients\n\n## Steps\n\n' })
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig([typeEntry])))
|
|
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleCreateNote('Pasta', 'Recipe')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-08 22:15:08 +01:00
|
|
|
const tabContent = result.current.tabs[0].content
|
|
|
|
|
expect(tabContent).toContain('## Ingredients')
|
|
|
|
|
expect(tabContent).toContain('## Steps')
|
2026-03-02 08:37:06 +01:00
|
|
|
})
|
|
|
|
|
|
2026-03-11 16:42:32 +01:00
|
|
|
it('handleCreateNoteImmediate does not throw for custom types with special characters', () => {
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
|
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleCreateNoteImmediate('Q&A')
|
|
|
|
|
})
|
|
|
|
|
}).not.toThrow()
|
|
|
|
|
|
|
|
|
|
const [entry] = addEntry.mock.calls[0]
|
|
|
|
|
expect(entry.isA).toBe('Q&A')
|
|
|
|
|
expect(entry.path).not.toContain('//')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleCreateNoteImmediate does not throw for types that slugify to empty string', () => {
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
|
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleCreateNoteImmediate('+++')
|
|
|
|
|
})
|
|
|
|
|
}).not.toThrow()
|
|
|
|
|
|
|
|
|
|
const [entry] = addEntry.mock.calls[0]
|
|
|
|
|
expect(entry.path).not.toContain('//')
|
|
|
|
|
expect(entry.filename).not.toBe('.md')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-02 08:37:06 +01:00
|
|
|
it('handleCreateNoteImmediate uses template for typed notes', () => {
|
|
|
|
|
const typeEntry = makeEntry({ isA: 'Type', title: 'Project', template: '## Custom Template\n\n' })
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig([typeEntry])))
|
|
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleCreateNoteImmediate('Project')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-08 22:15:08 +01:00
|
|
|
const tabContent = result.current.tabs[0].content
|
|
|
|
|
expect(tabContent).toContain('## Custom Template')
|
2026-03-02 08:37:06 +01:00
|
|
|
})
|
|
|
|
|
|
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')
|
|
|
|
|
})
|
feat: add optimistic error recovery for note creation (#69)
Note creation was already optimistic (UI updates before disk write) but
errors were silently swallowed. Now if the disk write fails, the
optimistic entry is reverted (tab closed, entry removed) and the user
sees an error toast. Each note creation is independent, so one failure
in rapid Cmd+N presses doesn't affect the others.
- Add removeEntry to useVaultLoader for reverting optimistic adds
- Refactor persistNewNote to return a Promise for error handling
- Extract navigateWikilink, persistOptimistic, createAndPersist,
and runFrontmatterAndApply helpers to reduce hook complexity
- Add tests for error recovery: single note, type, success path,
and rapid creation with partial failure
- Code health: 8.93→9.01 (CC fixed: 10→7, primitive obsession improved)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:11:59 +01:00
|
|
|
|
2026-03-02 03:08:15 +01:00
|
|
|
it('handleOpenDailyNote creates a new daily note when none exists', () => {
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
|
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
|
result.current.handleOpenDailyNote()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(addEntry).toHaveBeenCalledTimes(1)
|
|
|
|
|
const [createdEntry] = addEntry.mock.calls[0]
|
|
|
|
|
expect(createdEntry.isA).toBe('Journal')
|
2026-03-15 23:40:47 +01:00
|
|
|
expect(createdEntry.path).toMatch(/\/\d{4}-\d{2}-\d{2}\.md$/)
|
2026-03-02 03:08:15 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleOpenDailyNote opens existing daily note instead of creating', async () => {
|
|
|
|
|
const today = todayDateString()
|
2026-03-15 19:43:00 +01:00
|
|
|
const existing = makeEntry({ path: `/Users/luca/Laputa/${today}.md`, filename: `${today}.md`, title: today, isA: 'Journal' })
|
2026-03-02 03:08:15 +01:00
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig([existing])))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
result.current.handleOpenDailyNote()
|
|
|
|
|
await new Promise((r) => setTimeout(r, 0))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Should open existing note, not create a new one
|
|
|
|
|
expect(addEntry).not.toHaveBeenCalled()
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(result.current.activeTabPath).toBe(`/Users/luca/Laputa/${today}.md`)
|
2026-03-02 03:08:15 +01:00
|
|
|
})
|
|
|
|
|
|
2026-02-27 14:11:31 +01:00
|
|
|
describe('pending save lifecycle', () => {
|
|
|
|
|
it('createAndPersist calls addPendingSave on start (non-Tauri)', async () => {
|
|
|
|
|
const addPendingSave = vi.fn()
|
|
|
|
|
const removePendingSave = vi.fn()
|
|
|
|
|
const config = makeConfig()
|
|
|
|
|
config.addPendingSave = addPendingSave
|
|
|
|
|
config.removePendingSave = removePendingSave
|
|
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(config))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
result.current.handleCreateNote('Pending Test', 'Note')
|
|
|
|
|
await new Promise((r) => setTimeout(r, 0))
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(addPendingSave).toHaveBeenCalledWith(expect.stringContaining('pending-test.md'))
|
2026-02-27 14:11:31 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('createAndPersist calls removePendingSave when persist completes (non-Tauri)', async () => {
|
|
|
|
|
const addPendingSave = vi.fn()
|
|
|
|
|
const removePendingSave = vi.fn()
|
|
|
|
|
const config = makeConfig()
|
|
|
|
|
config.addPendingSave = addPendingSave
|
|
|
|
|
config.removePendingSave = removePendingSave
|
|
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(config))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
result.current.handleCreateNote('Persist OK', 'Note')
|
|
|
|
|
await new Promise((r) => setTimeout(r, 0))
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(removePendingSave).toHaveBeenCalledWith(expect.stringContaining('persist-ok.md'))
|
2026-02-27 14:11:31 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('createAndPersist calls removePendingSave AND reverts when persist fails (Tauri)', async () => {
|
|
|
|
|
vi.mocked(isTauri).mockReturnValue(true)
|
|
|
|
|
vi.mocked(invoke).mockRejectedValueOnce(new Error('disk full'))
|
|
|
|
|
const addPendingSave = vi.fn()
|
|
|
|
|
const removePendingSave = vi.fn()
|
|
|
|
|
const config = makeConfig()
|
|
|
|
|
config.addPendingSave = addPendingSave
|
|
|
|
|
config.removePendingSave = removePendingSave
|
|
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(config))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
result.current.handleCreateNote('Fail Save', 'Note')
|
|
|
|
|
await new Promise((r) => setTimeout(r, 0))
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(addPendingSave).toHaveBeenCalledWith(expect.stringContaining('fail-save.md'))
|
|
|
|
|
expect(removePendingSave).toHaveBeenCalledWith(expect.stringContaining('fail-save.md'))
|
|
|
|
|
expect(removeEntry).toHaveBeenCalledWith(expect.stringContaining('fail-save.md'))
|
2026-02-27 14:11:31 +01:00
|
|
|
expect(setToastMessage).toHaveBeenCalledWith('Failed to create note — disk write error')
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-27 18:17:47 +01:00
|
|
|
it('handleCreateNoteImmediate calls trackUnsaved and markContentPending (no disk write)', async () => {
|
|
|
|
|
const trackUnsaved = vi.fn()
|
|
|
|
|
const markContentPending = vi.fn()
|
2026-02-27 14:11:31 +01:00
|
|
|
const config = makeConfig()
|
2026-02-27 18:17:47 +01:00
|
|
|
config.trackUnsaved = trackUnsaved
|
|
|
|
|
config.markContentPending = markContentPending
|
2026-02-27 14:11:31 +01:00
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(config))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
result.current.handleCreateNoteImmediate()
|
|
|
|
|
await new Promise((r) => setTimeout(r, 0))
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-15 19:43:00 +01:00
|
|
|
expect(trackUnsaved).toHaveBeenCalledWith(expect.stringContaining('untitled-note.md'))
|
|
|
|
|
expect(markContentPending).toHaveBeenCalledWith(expect.stringContaining('untitled-note.md'), expect.stringContaining('Untitled note'))
|
2026-02-27 14:11:31 +01:00
|
|
|
})
|
2026-03-02 03:07:02 +01:00
|
|
|
|
|
|
|
|
it('calls onNewNotePersisted after successful disk write (non-Tauri)', async () => {
|
|
|
|
|
const onNewNotePersisted = vi.fn()
|
|
|
|
|
const config = makeConfig()
|
|
|
|
|
config.onNewNotePersisted = onNewNotePersisted
|
|
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(config))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
result.current.handleCreateNote('Persist Callback', 'Note')
|
|
|
|
|
await new Promise((r) => setTimeout(r, 0))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(onNewNotePersisted).toHaveBeenCalledTimes(1)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('does not call onNewNotePersisted when disk write fails (Tauri)', async () => {
|
|
|
|
|
vi.mocked(isTauri).mockReturnValue(true)
|
|
|
|
|
vi.mocked(invoke).mockRejectedValueOnce(new Error('disk full'))
|
|
|
|
|
const onNewNotePersisted = vi.fn()
|
|
|
|
|
const config = makeConfig()
|
|
|
|
|
config.onNewNotePersisted = onNewNotePersisted
|
|
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(config))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
result.current.handleCreateNote('Fail Persist', 'Note')
|
|
|
|
|
await new Promise((r) => setTimeout(r, 0))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(onNewNotePersisted).not.toHaveBeenCalled()
|
|
|
|
|
})
|
2026-02-27 14:11:31 +01:00
|
|
|
})
|
|
|
|
|
|
feat: add optimistic error recovery for note creation (#69)
Note creation was already optimistic (UI updates before disk write) but
errors were silently swallowed. Now if the disk write fails, the
optimistic entry is reverted (tab closed, entry removed) and the user
sees an error toast. Each note creation is independent, so one failure
in rapid Cmd+N presses doesn't affect the others.
- Add removeEntry to useVaultLoader for reverting optimistic adds
- Refactor persistNewNote to return a Promise for error handling
- Extract navigateWikilink, persistOptimistic, createAndPersist,
and runFrontmatterAndApply helpers to reduce hook complexity
- Add tests for error recovery: single note, type, success path,
and rapid creation with partial failure
- Code health: 8.93→9.01 (CC fixed: 10→7, primitive obsession improved)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:11:59 +01:00
|
|
|
describe('optimistic error recovery (Tauri mode)', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.mocked(isTauri).mockReturnValue(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it.each([
|
2026-03-15 19:43:00 +01:00
|
|
|
['handleCreateNote', 'Failing Note', 'Note', 'failing-note.md'],
|
2026-03-16 16:17:58 +01:00
|
|
|
['handleCreateType', 'Recipe', 'Type', 'recipe.md'],
|
feat: add optimistic error recovery for note creation (#69)
Note creation was already optimistic (UI updates before disk write) but
errors were silently swallowed. Now if the disk write fails, the
optimistic entry is reverted (tab closed, entry removed) and the user
sees an error toast. Each note creation is independent, so one failure
in rapid Cmd+N presses doesn't affect the others.
- Add removeEntry to useVaultLoader for reverting optimistic adds
- Refactor persistNewNote to return a Promise for error handling
- Extract navigateWikilink, persistOptimistic, createAndPersist,
and runFrontmatterAndApply helpers to reduce hook complexity
- Add tests for error recovery: single note, type, success path,
and rapid creation with partial failure
- Code health: 8.93→9.01 (CC fixed: 10→7, primitive obsession improved)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:11:59 +01:00
|
|
|
])('reverts optimistic creation via %s when disk write fails', async (method, title, type, pathFragment) => {
|
|
|
|
|
vi.mocked(invoke).mockRejectedValueOnce(new Error('disk full'))
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
if (method === 'handleCreateNote') result.current.handleCreateNote(title, type)
|
|
|
|
|
else result.current.handleCreateType(title)
|
|
|
|
|
await new Promise((r) => setTimeout(r, 0))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(addEntry).toHaveBeenCalledTimes(1)
|
|
|
|
|
expect(removeEntry).toHaveBeenCalledWith(expect.stringContaining(pathFragment))
|
|
|
|
|
expect(setToastMessage).toHaveBeenCalledWith('Failed to create note — disk write error')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('does not revert when disk write succeeds', async () => {
|
|
|
|
|
vi.mocked(invoke).mockResolvedValueOnce(undefined)
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
result.current.handleCreateNote('Good Note', 'Note')
|
|
|
|
|
await new Promise((r) => setTimeout(r, 0))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(removeEntry).not.toHaveBeenCalled()
|
|
|
|
|
expect(setToastMessage).not.toHaveBeenCalled()
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-27 18:17:47 +01:00
|
|
|
it('handleCreateNoteImmediate does not call invoke (no disk write)', async () => {
|
feat: add optimistic error recovery for note creation (#69)
Note creation was already optimistic (UI updates before disk write) but
errors were silently swallowed. Now if the disk write fails, the
optimistic entry is reverted (tab closed, entry removed) and the user
sees an error toast. Each note creation is independent, so one failure
in rapid Cmd+N presses doesn't affect the others.
- Add removeEntry to useVaultLoader for reverting optimistic adds
- Refactor persistNewNote to return a Promise for error handling
- Extract navigateWikilink, persistOptimistic, createAndPersist,
and runFrontmatterAndApply helpers to reduce hook complexity
- Add tests for error recovery: single note, type, success path,
and rapid creation with partial failure
- Code health: 8.93→9.01 (CC fixed: 10→7, primitive obsession improved)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:11:59 +01:00
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig()))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
result.current.handleCreateNoteImmediate()
|
|
|
|
|
result.current.handleCreateNoteImmediate()
|
|
|
|
|
result.current.handleCreateNoteImmediate()
|
|
|
|
|
await new Promise((r) => setTimeout(r, 0))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(addEntry).toHaveBeenCalledTimes(3)
|
2026-02-27 18:17:47 +01:00
|
|
|
// No disk writes for immediate creation — notes are unsaved/in-memory
|
|
|
|
|
expect(invoke).not.toHaveBeenCalled()
|
|
|
|
|
expect(removeEntry).not.toHaveBeenCalled()
|
|
|
|
|
})
|
|
|
|
|
|
feat: add optimistic error recovery for note creation (#69)
Note creation was already optimistic (UI updates before disk write) but
errors were silently swallowed. Now if the disk write fails, the
optimistic entry is reverted (tab closed, entry removed) and the user
sees an error toast. Each note creation is independent, so one failure
in rapid Cmd+N presses doesn't affect the others.
- Add removeEntry to useVaultLoader for reverting optimistic adds
- Refactor persistNewNote to return a Promise for error handling
- Extract navigateWikilink, persistOptimistic, createAndPersist,
and runFrontmatterAndApply helpers to reduce hook complexity
- Add tests for error recovery: single note, type, success path,
and rapid creation with partial failure
- Code health: 8.93→9.01 (CC fixed: 10→7, primitive obsession improved)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:11:59 +01:00
|
|
|
})
|
2026-03-08 21:37:27 +01:00
|
|
|
|
2026-03-15 23:40:47 +01:00
|
|
|
describe('type change does not move file', () => {
|
|
|
|
|
it('changing type only updates frontmatter, does not move file', async () => {
|
|
|
|
|
const entry = makeEntry({ path: '/test/vault/my-note.md', filename: 'my-note.md', title: 'My Note', isA: 'Note' })
|
|
|
|
|
const config = makeConfig([entry])
|
2026-03-08 21:37:27 +01:00
|
|
|
vi.mocked(mockInvoke).mockResolvedValue('')
|
|
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(config))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
2026-03-15 23:40:47 +01:00
|
|
|
await result.current.handleUpdateFrontmatter('/test/vault/my-note.md', 'type', 'Quarter')
|
2026-03-08 21:37:27 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(setToastMessage).toHaveBeenCalledWith('Property updated')
|
|
|
|
|
})
|
|
|
|
|
})
|
2026-03-11 18:47:58 +01:00
|
|
|
|
2026-03-18 02:37:29 +01:00
|
|
|
describe('title sync on reopen', () => {
|
|
|
|
|
it('calls sync_note_title when reopening an already-open tab', async () => {
|
|
|
|
|
vi.mocked(isTauri).mockReturnValue(true)
|
|
|
|
|
const entry = makeEntry({ path: '/test/vault/qa-test.md', filename: 'qa-test.md', title: 'Qa Test' })
|
|
|
|
|
|
|
|
|
|
// First open: handleSelectNoteWithSync calls sync, then handleSelectNote calls sync again + loads
|
|
|
|
|
vi.mocked(invoke)
|
|
|
|
|
.mockResolvedValueOnce(false) // sync_note_title (from handleSelectNoteWithSync)
|
|
|
|
|
.mockResolvedValueOnce(false) // sync_note_title (from handleSelectNote)
|
|
|
|
|
.mockResolvedValueOnce('# Qa Test\n') // get_note_content
|
|
|
|
|
.mockResolvedValueOnce({ ...entry }) // reload_vault_entry (first open)
|
|
|
|
|
|
|
|
|
|
// Second open (reopen, tab already exists): handleSelectNoteWithSync calls sync + reload
|
|
|
|
|
vi.mocked(invoke)
|
|
|
|
|
.mockResolvedValueOnce(true) // sync_note_title (reopen — file was modified)
|
|
|
|
|
.mockResolvedValueOnce('---\ntitle: Qa Test\n---\n# Qa Test\n') // get_note_content (reload)
|
|
|
|
|
.mockResolvedValueOnce({ ...entry, title: 'Qa Test' }) // reload_vault_entry (reopen)
|
|
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(makeConfig([entry])))
|
|
|
|
|
|
|
|
|
|
// Open the note (creates tab)
|
|
|
|
|
await act(async () => { await result.current.handleSelectNote(entry) })
|
|
|
|
|
|
|
|
|
|
// Reopen the same note (tab already open — this is the Cmd+P reopen scenario)
|
|
|
|
|
const desyncedEntry = { ...entry, title: 'Wrong Title Desynced' }
|
|
|
|
|
await act(async () => { await result.current.handleSelectNote(desyncedEntry) })
|
|
|
|
|
|
|
|
|
|
// sync_note_title should have been called for both opens (3 total: 2 for first open, 1 for reopen)
|
|
|
|
|
const syncCalls = vi.mocked(invoke).mock.calls.filter(c => c[0] === 'sync_note_title')
|
|
|
|
|
expect(syncCalls.length).toBeGreaterThanOrEqual(2)
|
|
|
|
|
// Critical: sync was called on reopen (the bug fix)
|
|
|
|
|
expect(syncCalls.at(-1)).toEqual(['sync_note_title', { path: '/test/vault/qa-test.md' }])
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-11 18:47:58 +01:00
|
|
|
describe('rename note updates wikilinks', () => {
|
|
|
|
|
it('handleRenameNote passes entry title as old_title to rename_note', async () => {
|
|
|
|
|
const entry = makeEntry({
|
2026-03-15 23:40:47 +01:00
|
|
|
path: '/test/vault/weekly-review.md',
|
2026-03-11 18:47:58 +01:00
|
|
|
filename: 'weekly-review.md',
|
|
|
|
|
title: 'Weekly Review',
|
|
|
|
|
})
|
|
|
|
|
const replaceEntry = vi.fn()
|
|
|
|
|
const config = makeConfig([entry])
|
|
|
|
|
config.replaceEntry = replaceEntry
|
|
|
|
|
|
|
|
|
|
vi.mocked(mockInvoke).mockImplementation(async (cmd: string) => {
|
2026-03-15 23:40:47 +01:00
|
|
|
if (cmd === 'rename_note') return { new_path: '/test/vault/sprint-retro.md', updated_files: 2 }
|
2026-03-11 18:47:58 +01:00
|
|
|
if (cmd === 'get_note_content') return '---\nIs A: Note\n---\n# Sprint Retro\n'
|
|
|
|
|
return ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(config))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
await result.current.handleRenameNote(
|
2026-03-15 23:40:47 +01:00
|
|
|
'/test/vault/weekly-review.md',
|
2026-03-11 18:47:58 +01:00
|
|
|
'Sprint Retro',
|
|
|
|
|
'/test/vault',
|
|
|
|
|
replaceEntry,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(mockInvoke).toHaveBeenCalledWith('rename_note', expect.objectContaining({
|
|
|
|
|
vault_path: '/test/vault',
|
2026-03-15 23:40:47 +01:00
|
|
|
old_path: '/test/vault/weekly-review.md',
|
2026-03-11 18:47:58 +01:00
|
|
|
new_title: 'Sprint Retro',
|
|
|
|
|
old_title: 'Weekly Review',
|
|
|
|
|
}))
|
|
|
|
|
expect(setToastMessage).toHaveBeenCalledWith('Renamed — updated 2 wiki links')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleRenameNote passes null old_title when entry not found', async () => {
|
|
|
|
|
const config = makeConfig([])
|
|
|
|
|
|
|
|
|
|
vi.mocked(mockInvoke).mockImplementation(async (cmd: string) => {
|
2026-03-15 23:40:47 +01:00
|
|
|
if (cmd === 'rename_note') return { new_path: '/test/vault/new.md', updated_files: 0 }
|
2026-03-11 18:47:58 +01:00
|
|
|
if (cmd === 'get_note_content') return '# New\n'
|
|
|
|
|
return ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(config))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
await result.current.handleRenameNote(
|
2026-03-15 23:40:47 +01:00
|
|
|
'/test/vault/old.md', 'New', '/test/vault', vi.fn(),
|
2026-03-11 18:47:58 +01:00
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(mockInvoke).toHaveBeenCalledWith('rename_note', expect.objectContaining({
|
|
|
|
|
old_title: null,
|
|
|
|
|
}))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleUpdateFrontmatter triggers rename when title key is changed', async () => {
|
|
|
|
|
const entry = makeEntry({
|
2026-03-15 23:40:47 +01:00
|
|
|
path: '/test/vault/old-name.md',
|
2026-03-11 18:47:58 +01:00
|
|
|
filename: 'old-name.md',
|
|
|
|
|
title: 'Old Name',
|
|
|
|
|
})
|
|
|
|
|
const replaceEntry = vi.fn()
|
|
|
|
|
const config = makeConfig([entry])
|
|
|
|
|
config.replaceEntry = replaceEntry
|
|
|
|
|
|
|
|
|
|
vi.mocked(mockInvoke).mockImplementation(async (cmd: string) => {
|
2026-03-15 23:40:47 +01:00
|
|
|
if (cmd === 'rename_note') return { new_path: '/test/vault/new-name.md', updated_files: 1 }
|
2026-03-11 18:47:58 +01:00
|
|
|
if (cmd === 'get_note_content') return '---\ntitle: New Name\n---\n# New Name\n'
|
|
|
|
|
return ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(config))
|
|
|
|
|
|
|
|
|
|
// Open a tab for the entry so the rename can find it via tabsRef
|
|
|
|
|
await act(async () => { result.current.handleSelectNote(entry) })
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
2026-03-15 23:40:47 +01:00
|
|
|
await result.current.handleUpdateFrontmatter('/test/vault/old-name.md', 'title', 'New Name')
|
2026-03-11 18:47:58 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(mockInvoke).toHaveBeenCalledWith('rename_note', expect.objectContaining({
|
2026-03-15 23:40:47 +01:00
|
|
|
old_path: '/test/vault/old-name.md',
|
2026-03-11 18:47:58 +01:00
|
|
|
new_title: 'New Name',
|
|
|
|
|
old_title: 'Old Name',
|
|
|
|
|
}))
|
|
|
|
|
expect(replaceEntry).toHaveBeenCalledWith(
|
2026-03-15 23:40:47 +01:00
|
|
|
'/test/vault/old-name.md',
|
|
|
|
|
expect.objectContaining({ path: '/test/vault/new-name.md', title: 'New Name' }),
|
2026-03-11 18:47:58 +01:00
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('handleUpdateFrontmatter does not trigger rename for non-title keys', async () => {
|
|
|
|
|
const config = makeConfig()
|
|
|
|
|
vi.mocked(mockInvoke).mockResolvedValue('')
|
|
|
|
|
|
|
|
|
|
const { result } = renderHook(() => useNoteActions(config))
|
|
|
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
|
await result.current.handleUpdateFrontmatter('/vault/note.md', 'status', 'Done')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(mockInvoke).not.toHaveBeenCalledWith('rename_note', expect.anything())
|
|
|
|
|
})
|
|
|
|
|
})
|
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
|
|
|
})
|