Files
tolaria/src/hooks/useSaveNote.test.ts
lucaronin 6c2e1b7607 fix: replace broken auto-save with explicit Cmd+S save
Removes the debounced auto-save (useAutoSave hook) which was causing the
editor to reload previous content. Replaces it with explicit save on
Cmd+S (⌘S), consistent with the git-based UX of the app.

- Removed useAutoSave hook and its debounce mechanism
- Added useSaveNote hook for direct persist-to-disk
- Added useEditorSave hook that manages pending content buffer + save
- Cmd+S now persists the current editor content immediately
- Rename-before-save: saves pending content before rename to prevent
  the "Failed to rename note" error
- Updated E2E test to verify Cmd+S behavior
- 471 tests passing, code health gates passed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 19:53:36 +01:00

65 lines
2.2 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { renderHook, act } from '@testing-library/react'
import { useSaveNote } from './useSaveNote'
const mockInvokeFn = vi.fn<(cmd: string, args?: Record<string, unknown>) => Promise<null>>(() => Promise.resolve(null))
vi.mock('@tauri-apps/api/core', () => ({
invoke: vi.fn(),
}))
vi.mock('../mock-tauri', () => ({
isTauri: () => false,
mockInvoke: (cmd: string, args?: Record<string, unknown>) => mockInvokeFn(cmd, args),
updateMockContent: vi.fn(),
}))
describe('useSaveNote', () => {
let updateContent: (path: string, content: string) => void
beforeEach(() => {
updateContent = vi.fn<(path: string, content: string) => void>()
mockInvokeFn.mockClear()
})
it('saves content immediately via Tauri command', async () => {
const { result } = renderHook(() => useSaveNote(updateContent))
await act(async () => {
await result.current.saveNote('/test/note.md', '---\ntitle: Test\n---\n\n# Test\n\nContent')
})
expect(mockInvokeFn).toHaveBeenCalledWith('save_note_content', {
path: '/test/note.md',
content: '---\ntitle: Test\n---\n\n# Test\n\nContent',
})
expect(updateContent).toHaveBeenCalledWith('/test/note.md', '---\ntitle: Test\n---\n\n# Test\n\nContent')
})
it('updates in-memory state after saving', async () => {
const { result } = renderHook(() => useSaveNote(updateContent))
await act(async () => {
await result.current.saveNote('/test/a.md', 'content A')
await result.current.saveNote('/test/b.md', 'content B')
})
expect(updateContent).toHaveBeenCalledTimes(2)
expect(updateContent).toHaveBeenCalledWith('/test/a.md', 'content A')
expect(updateContent).toHaveBeenCalledWith('/test/b.md', 'content B')
})
it('propagates save errors to the caller', async () => {
mockInvokeFn.mockRejectedValueOnce(new Error('File is read-only'))
const { result } = renderHook(() => useSaveNote(updateContent))
await expect(
act(async () => {
await result.current.saveNote('/test/readonly.md', 'content')
})
).rejects.toThrow('File is read-only')
expect(updateContent).not.toHaveBeenCalled()
})
})