Files
tolaria/src/hooks/useAiAgent.test.ts
Test d83f04c6ff fix: AI-created notes now visible — onVaultChanged fallback for missed file ops
detectFileOperation silently failed when tool input was undefined (timing
issues with NDJSON event ordering). Added onVaultChanged callback as fallback:
when Write/Edit/Bash tools complete but specific file can't be determined,
vault.reloadVault() is triggered. Also added safety net in onDone handler.

Threaded onVaultChanged through AiPanel → EditorRightPanel → Editor → App.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 14:19:41 +01:00

179 lines
6.8 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest'
import { detectFileOperation, parseBashFileCreation } from './useAiAgent'
import type { AgentFileCallbacks } from './useAiAgent'
const VAULT = '/Users/luca/Laputa'
function makeCallbacks() {
return {
onFileCreated: vi.fn(),
onFileModified: vi.fn(),
onVaultChanged: vi.fn(),
} satisfies AgentFileCallbacks
}
describe('detectFileOperation', () => {
it('calls onFileCreated for Write tool with .md in vault', () => {
const cb = makeCallbacks()
detectFileOperation('Write', JSON.stringify({ file_path: `${VAULT}/note/test.md` }), VAULT, cb)
expect(cb.onFileCreated).toHaveBeenCalledWith('note/test.md')
expect(cb.onFileModified).not.toHaveBeenCalled()
})
it('calls onFileModified for Edit tool with .md in vault', () => {
const cb = makeCallbacks()
detectFileOperation('Edit', JSON.stringify({ file_path: `${VAULT}/note/test.md` }), VAULT, cb)
expect(cb.onFileModified).toHaveBeenCalledWith('note/test.md')
expect(cb.onFileCreated).not.toHaveBeenCalled()
})
it('ignores non-md files', () => {
const cb = makeCallbacks()
detectFileOperation('Write', JSON.stringify({ file_path: `${VAULT}/image.png` }), VAULT, cb)
expect(cb.onFileCreated).not.toHaveBeenCalled()
})
it('ignores files outside vault', () => {
const cb = makeCallbacks()
detectFileOperation('Write', JSON.stringify({ file_path: '/tmp/other.md' }), VAULT, cb)
expect(cb.onFileCreated).not.toHaveBeenCalled()
})
it('ignores unknown tool names', () => {
const cb = makeCallbacks()
detectFileOperation('Read', JSON.stringify({ file_path: `${VAULT}/note/test.md` }), VAULT, cb)
expect(cb.onFileCreated).not.toHaveBeenCalled()
expect(cb.onFileModified).not.toHaveBeenCalled()
})
it('calls onVaultChanged when Write input is undefined', () => {
const cb = makeCallbacks()
detectFileOperation('Write', undefined, VAULT, cb)
expect(cb.onFileCreated).not.toHaveBeenCalled()
expect(cb.onVaultChanged).toHaveBeenCalled()
})
it('calls onVaultChanged when Edit input is undefined', () => {
const cb = makeCallbacks()
detectFileOperation('Edit', undefined, VAULT, cb)
expect(cb.onFileModified).not.toHaveBeenCalled()
expect(cb.onVaultChanged).toHaveBeenCalled()
})
it('calls onVaultChanged when Write has malformed JSON input', () => {
const cb = makeCallbacks()
detectFileOperation('Write', 'not-json', VAULT, cb)
expect(cb.onFileCreated).not.toHaveBeenCalled()
expect(cb.onVaultChanged).toHaveBeenCalled()
})
it('does not call onVaultChanged when Write detects specific file', () => {
const cb = makeCallbacks()
detectFileOperation('Write', JSON.stringify({ file_path: `${VAULT}/note/test.md` }), VAULT, cb)
expect(cb.onFileCreated).toHaveBeenCalledWith('note/test.md')
expect(cb.onVaultChanged).not.toHaveBeenCalled()
})
it('does not call onVaultChanged for Read tool', () => {
const cb = makeCallbacks()
detectFileOperation('Read', undefined, VAULT, cb)
expect(cb.onVaultChanged).not.toHaveBeenCalled()
})
it('calls onVaultChanged when Bash input is undefined', () => {
const cb = makeCallbacks()
detectFileOperation('Bash', undefined, VAULT, cb)
expect(cb.onFileCreated).not.toHaveBeenCalled()
expect(cb.onVaultChanged).toHaveBeenCalled()
})
it('handles undefined callbacks gracefully', () => {
expect(() => detectFileOperation('Write', JSON.stringify({ file_path: `${VAULT}/note/test.md` }), VAULT, undefined)).not.toThrow()
})
it('detects Bash redirect creating .md file in vault', () => {
const cb = makeCallbacks()
detectFileOperation('Bash', JSON.stringify({ command: `echo "# Note" > ${VAULT}/note/new.md` }), VAULT, cb)
expect(cb.onFileCreated).toHaveBeenCalledWith('note/new.md')
})
it('detects Bash append redirect creating .md file', () => {
const cb = makeCallbacks()
detectFileOperation('Bash', JSON.stringify({ command: `cat content.txt >> ${VAULT}/daily/2026-03-07.md` }), VAULT, cb)
expect(cb.onFileCreated).toHaveBeenCalledWith('daily/2026-03-07.md')
})
it('detects Bash tee command creating .md file', () => {
const cb = makeCallbacks()
detectFileOperation('Bash', JSON.stringify({ command: `echo "content" | tee ${VAULT}/note/tee-note.md` }), VAULT, cb)
expect(cb.onFileCreated).toHaveBeenCalledWith('note/tee-note.md')
})
it('ignores Bash commands without file creation', () => {
const cb = makeCallbacks()
detectFileOperation('Bash', JSON.stringify({ command: 'ls -la' }), VAULT, cb)
expect(cb.onFileCreated).not.toHaveBeenCalled()
})
it('ignores Bash creating non-md files', () => {
const cb = makeCallbacks()
detectFileOperation('Bash', JSON.stringify({ command: `echo "data" > ${VAULT}/config.json` }), VAULT, cb)
expect(cb.onFileCreated).not.toHaveBeenCalled()
})
it('ignores Bash creating .md files outside vault', () => {
const cb = makeCallbacks()
detectFileOperation('Bash', JSON.stringify({ command: 'echo "text" > /tmp/other.md' }), VAULT, cb)
expect(cb.onFileCreated).not.toHaveBeenCalled()
})
it('uses path field when file_path is missing', () => {
const cb = makeCallbacks()
detectFileOperation('Write', JSON.stringify({ path: `${VAULT}/note/alt.md` }), VAULT, cb)
expect(cb.onFileCreated).toHaveBeenCalledWith('note/alt.md')
})
})
describe('parseBashFileCreation', () => {
it('returns null for undefined input', () => {
expect(parseBashFileCreation(undefined, VAULT)).toBeNull()
})
it('returns null for malformed JSON', () => {
expect(parseBashFileCreation('not json', VAULT)).toBeNull()
})
it('returns null when no command field', () => {
expect(parseBashFileCreation(JSON.stringify({ other: 'value' }), VAULT)).toBeNull()
})
it('returns null when command is not a string', () => {
expect(parseBashFileCreation(JSON.stringify({ command: 42 }), VAULT)).toBeNull()
})
it('parses simple redirect', () => {
const input = JSON.stringify({ command: `echo "# Title" > ${VAULT}/note.md` })
expect(parseBashFileCreation(input, VAULT)).toBe('note.md')
})
it('parses append redirect', () => {
const input = JSON.stringify({ command: `echo "line" >> ${VAULT}/sub/note.md` })
expect(parseBashFileCreation(input, VAULT)).toBe('sub/note.md')
})
it('parses tee command', () => {
const input = JSON.stringify({ command: `echo "data" | tee ${VAULT}/new.md` })
expect(parseBashFileCreation(input, VAULT)).toBe('new.md')
})
it('parses tee -a (append) command', () => {
const input = JSON.stringify({ command: `echo "extra" | tee -a ${VAULT}/new.md` })
expect(parseBashFileCreation(input, VAULT)).toBe('new.md')
})
it('returns null for non-md redirect', () => {
const input = JSON.stringify({ command: `echo "x" > ${VAULT}/file.txt` })
expect(parseBashFileCreation(input, VAULT)).toBeNull()
})
})