test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
import { useState } from 'react'
|
|
|
|
|
import { describe, it, expect, vi } from 'vitest'
|
2026-04-24 23:57:12 +02:00
|
|
|
import {
|
|
|
|
|
createEvent,
|
|
|
|
|
fireEvent,
|
|
|
|
|
render,
|
|
|
|
|
screen,
|
|
|
|
|
waitFor,
|
|
|
|
|
} from '@testing-library/react'
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
import { WikilinkChatInput } from './WikilinkChatInput'
|
2026-04-25 20:41:56 +02:00
|
|
|
import { extractDroppedPathText } from './inlineWikilinkDropText'
|
2026-04-25 19:23:00 +02:00
|
|
|
import {
|
|
|
|
|
UNSUPPORTED_INLINE_PASTE_MESSAGE,
|
|
|
|
|
} from './InlineWikilinkInput'
|
|
|
|
|
import { isInsertBeforeInput } from './inlineWikilinkBeforeInput'
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
import type { VaultEntry } from '../types'
|
|
|
|
|
|
|
|
|
|
const makeEntry = (overrides: Partial<VaultEntry> = {}): VaultEntry => ({
|
|
|
|
|
path: '/vault/note/test.md',
|
|
|
|
|
filename: 'test.md',
|
|
|
|
|
title: 'Test Note',
|
|
|
|
|
isA: 'Note',
|
|
|
|
|
aliases: [],
|
|
|
|
|
belongsTo: [],
|
|
|
|
|
relatedTo: [],
|
|
|
|
|
status: null,
|
|
|
|
|
owner: null,
|
|
|
|
|
cadence: null,
|
|
|
|
|
archived: false,
|
|
|
|
|
modifiedAt: 1700000000,
|
|
|
|
|
createdAt: 1700000000,
|
|
|
|
|
fileSize: 100,
|
|
|
|
|
snippet: '',
|
|
|
|
|
wordCount: 0,
|
|
|
|
|
relationships: {},
|
|
|
|
|
icon: null,
|
|
|
|
|
color: null,
|
|
|
|
|
order: null,
|
|
|
|
|
outgoingLinks: [],
|
|
|
|
|
...overrides,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const entries: VaultEntry[] = [
|
|
|
|
|
makeEntry({ path: '/vault/alpha.md', title: 'Alpha', filename: 'alpha.md', isA: 'Project' }),
|
2026-04-12 13:14:18 +02:00
|
|
|
makeEntry({ path: '/vault/beta.md', title: 'Beta', filename: 'beta.md', isA: 'Person', aliases: ['BLT'] }),
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
makeEntry({ path: '/vault/gamma.md', title: 'Gamma', filename: 'gamma.md' }),
|
|
|
|
|
]
|
|
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
function Controlled({
|
|
|
|
|
onSend,
|
2026-04-24 13:52:58 +02:00
|
|
|
onUnsupportedPaste,
|
2026-04-12 13:14:18 +02:00
|
|
|
disabled = false,
|
|
|
|
|
placeholder,
|
2026-04-24 23:57:12 +02:00
|
|
|
onDraftChange,
|
2026-04-12 13:14:18 +02:00
|
|
|
}: {
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
onSend?: (text: string, refs: Array<{ title: string; path: string; type: string | null }>) => void
|
2026-04-24 13:52:58 +02:00
|
|
|
onUnsupportedPaste?: (message: string) => void
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
disabled?: boolean
|
|
|
|
|
placeholder?: string
|
2026-04-24 23:57:12 +02:00
|
|
|
onDraftChange?: (value: string) => void
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
}) {
|
|
|
|
|
const [value, setValue] = useState('')
|
2026-04-24 23:57:12 +02:00
|
|
|
const handleChange = (nextValue: string) => {
|
|
|
|
|
onDraftChange?.(nextValue)
|
|
|
|
|
setValue(nextValue)
|
|
|
|
|
}
|
2026-04-12 13:14:18 +02:00
|
|
|
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
return (
|
|
|
|
|
<WikilinkChatInput
|
2026-04-12 13:14:18 +02:00
|
|
|
entries={entries}
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
value={value}
|
2026-04-24 23:57:12 +02:00
|
|
|
onChange={handleChange}
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
onSend={onSend ?? vi.fn()}
|
2026-04-24 13:52:58 +02:00
|
|
|
onUnsupportedPaste={onUnsupportedPaste}
|
2026-04-12 13:14:18 +02:00
|
|
|
disabled={disabled}
|
|
|
|
|
placeholder={placeholder}
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
function setSelection(editor: HTMLElement, offset: number) {
|
|
|
|
|
const selection = window.getSelection()
|
|
|
|
|
if (!selection) return
|
|
|
|
|
|
|
|
|
|
const targetNode = editor.firstChild ?? editor
|
|
|
|
|
const safeOffset = targetNode.nodeType === Node.TEXT_NODE
|
|
|
|
|
? Math.min(offset, targetNode.textContent?.length ?? 0)
|
|
|
|
|
: Math.min(offset, targetNode.childNodes.length)
|
|
|
|
|
|
|
|
|
|
const range = document.createRange()
|
|
|
|
|
range.setStart(targetNode, safeOffset)
|
|
|
|
|
range.collapse(true)
|
|
|
|
|
selection.removeAllRanges()
|
|
|
|
|
selection.addRange(range)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateEditorText(text: string) {
|
|
|
|
|
const editor = screen.getByTestId('agent-input')
|
|
|
|
|
fireEvent.focus(editor)
|
|
|
|
|
editor.textContent = text
|
|
|
|
|
setSelection(editor, text.length)
|
|
|
|
|
fireEvent.input(editor)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clickFirstSuggestion() {
|
|
|
|
|
const rows = screen.getByTestId('wikilink-menu').querySelectorAll('[class*="cursor-pointer"]')
|
|
|
|
|
expect(rows.length).toBeGreaterThan(0)
|
|
|
|
|
fireEvent.click(rows[0])
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-24 23:57:12 +02:00
|
|
|
function fireComposingKeyDown(editor: HTMLElement, key: string) {
|
|
|
|
|
const event = createEvent.keyDown(editor, {
|
|
|
|
|
key,
|
|
|
|
|
keyCode: 229,
|
|
|
|
|
which: 229,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(event, 'isComposing', {
|
|
|
|
|
configurable: true,
|
|
|
|
|
value: true,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
fireEvent(editor, event)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 20:41:56 +02:00
|
|
|
function createFileLikeDataTransfer({
|
|
|
|
|
plainText = '',
|
|
|
|
|
uriList = '',
|
|
|
|
|
}: {
|
|
|
|
|
plainText?: string
|
|
|
|
|
uriList?: string
|
|
|
|
|
}) {
|
|
|
|
|
return {
|
|
|
|
|
getData: vi.fn((type: string) => {
|
|
|
|
|
if (type === 'text/plain') return plainText
|
|
|
|
|
if (type === 'text/uri-list') return uriList
|
|
|
|
|
return ''
|
|
|
|
|
}),
|
|
|
|
|
files: [new File(['folder'], 'Projects')],
|
|
|
|
|
items: [{ kind: 'file', type: '' }],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
describe('WikilinkChatInput', () => {
|
2026-04-12 13:14:18 +02:00
|
|
|
it('renders the placeholder overlay for an empty draft', () => {
|
|
|
|
|
render(<Controlled placeholder="Ask something..." />)
|
|
|
|
|
expect(screen.getByText('Ask something...')).toBeInTheDocument()
|
|
|
|
|
expect(screen.getByTestId('agent-input')).toHaveAttribute('aria-placeholder', 'Ask something...')
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
})
|
|
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
it('calls onChange when the draft changes', () => {
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
const onChange = vi.fn()
|
|
|
|
|
render(
|
|
|
|
|
<WikilinkChatInput entries={entries} value="" onChange={onChange} onSend={vi.fn()} />,
|
|
|
|
|
)
|
2026-04-12 13:14:18 +02:00
|
|
|
|
|
|
|
|
updateEditorText('hello')
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
expect(onChange).toHaveBeenCalledWith('hello')
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
it('shows wikilink suggestions after typing [[', () => {
|
|
|
|
|
render(<Controlled />)
|
|
|
|
|
updateEditorText('[[a')
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
|
|
|
|
|
const menu = screen.getByTestId('wikilink-menu')
|
|
|
|
|
expect(menu.textContent).toContain('Alpha')
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
it('matches suggestions by alias', () => {
|
|
|
|
|
render(<Controlled />)
|
|
|
|
|
updateEditorText('[[BLT')
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
expect(screen.getByTestId('wikilink-menu').textContent).toContain('Beta')
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
})
|
|
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
it('renders selected wikilinks inline instead of in a separate pill strip', () => {
|
|
|
|
|
render(<Controlled />)
|
|
|
|
|
updateEditorText('edit my [[alp')
|
|
|
|
|
clickFirstSuggestion()
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
expect(screen.queryByTestId('reference-pill')).toBeNull()
|
|
|
|
|
expect(screen.getByTestId('inline-wikilink-chip')).toBeInTheDocument()
|
|
|
|
|
expect(screen.getByTestId('agent-input').textContent).toContain('Alpha')
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
})
|
|
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
it('selects a suggestion with Enter before sending the draft', () => {
|
|
|
|
|
const onSend = vi.fn()
|
|
|
|
|
render(<Controlled onSend={onSend} />)
|
|
|
|
|
updateEditorText('edit my [[alp')
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
fireEvent.keyDown(screen.getByTestId('agent-input'), { key: 'Enter' })
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
expect(screen.getByTestId('inline-wikilink-chip')).toBeInTheDocument()
|
|
|
|
|
expect(onSend).not.toHaveBeenCalled()
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
})
|
|
|
|
|
|
2026-04-24 23:57:12 +02:00
|
|
|
it('does not hijack Enter while IME composition is active', async () => {
|
|
|
|
|
const onDraftChange = vi.fn()
|
|
|
|
|
const onSend = vi.fn()
|
|
|
|
|
render(<Controlled onDraftChange={onDraftChange} onSend={onSend} />)
|
|
|
|
|
|
|
|
|
|
const editor = screen.getByTestId('agent-input')
|
|
|
|
|
fireEvent.focus(editor)
|
|
|
|
|
fireEvent.compositionStart(editor)
|
|
|
|
|
editor.textContent = 'ni'
|
|
|
|
|
setSelection(editor, 2)
|
|
|
|
|
fireEvent.input(editor)
|
|
|
|
|
|
|
|
|
|
expect(onDraftChange).not.toHaveBeenCalled()
|
|
|
|
|
|
|
|
|
|
fireComposingKeyDown(editor, 'Enter')
|
|
|
|
|
expect(onSend).not.toHaveBeenCalled()
|
|
|
|
|
|
|
|
|
|
editor.textContent = '你'
|
|
|
|
|
setSelection(editor, 1)
|
|
|
|
|
fireEvent.compositionEnd(editor)
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(onDraftChange).toHaveBeenCalledWith('你')
|
|
|
|
|
})
|
|
|
|
|
expect(editor.textContent).toContain('你')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('does not select wikilink suggestions while IME composition is active', () => {
|
|
|
|
|
render(<Controlled />)
|
|
|
|
|
updateEditorText('[[a')
|
|
|
|
|
|
|
|
|
|
const editor = screen.getByTestId('agent-input')
|
|
|
|
|
fireEvent.compositionStart(editor)
|
|
|
|
|
fireComposingKeyDown(editor, 'Enter')
|
|
|
|
|
|
|
|
|
|
expect(screen.queryByTestId('inline-wikilink-chip')).toBeNull()
|
|
|
|
|
expect(screen.getByTestId('wikilink-menu').textContent).toContain('Alpha')
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-25 23:46:31 +02:00
|
|
|
it('clears IME-injected stray text nodes after composition ends', async () => {
|
|
|
|
|
const onDraftChange = vi.fn()
|
|
|
|
|
render(<Controlled onDraftChange={onDraftChange} />)
|
|
|
|
|
const initialEditor = screen.getByTestId('agent-input') as HTMLDivElement
|
|
|
|
|
initialEditor.focus()
|
|
|
|
|
|
|
|
|
|
fireEvent.compositionStart(initialEditor)
|
|
|
|
|
initialEditor.appendChild(document.createTextNode('你'))
|
|
|
|
|
fireEvent.input(initialEditor)
|
|
|
|
|
fireEvent.compositionEnd(initialEditor)
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(onDraftChange).toHaveBeenCalledWith('你')
|
|
|
|
|
})
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
const editor = screen.getByTestId('agent-input') as HTMLDivElement
|
|
|
|
|
expect(editor.textContent).toBe('你')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('does not steal focus back if it was moved elsewhere during composition end', async () => {
|
|
|
|
|
const onDraftChange = vi.fn()
|
|
|
|
|
render(
|
|
|
|
|
<>
|
|
|
|
|
<Controlled onDraftChange={onDraftChange} />
|
|
|
|
|
<button data-testid="other-target">Other</button>
|
|
|
|
|
</>,
|
|
|
|
|
)
|
|
|
|
|
const initialEditor = screen.getByTestId('agent-input') as HTMLDivElement
|
|
|
|
|
const otherTarget = screen.getByTestId('other-target') as HTMLButtonElement
|
|
|
|
|
initialEditor.focus()
|
|
|
|
|
|
|
|
|
|
fireEvent.compositionStart(initialEditor)
|
|
|
|
|
initialEditor.appendChild(document.createTextNode('你'))
|
|
|
|
|
fireEvent.input(initialEditor)
|
|
|
|
|
|
|
|
|
|
otherTarget.focus()
|
|
|
|
|
fireEvent.compositionEnd(initialEditor)
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(onDraftChange).toHaveBeenCalledWith('你')
|
|
|
|
|
})
|
|
|
|
|
expect(document.activeElement).toBe(otherTarget)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('does not reset the DOM selection while IME composition is active', () => {
|
|
|
|
|
const removeAllRanges = vi.spyOn(Selection.prototype, 'removeAllRanges')
|
|
|
|
|
render(<Controlled />)
|
|
|
|
|
const editor = screen.getByTestId('agent-input') as HTMLDivElement
|
|
|
|
|
editor.focus()
|
|
|
|
|
|
|
|
|
|
fireEvent.compositionStart(editor)
|
|
|
|
|
editor.appendChild(document.createTextNode('ni'))
|
|
|
|
|
setSelection(editor, 2)
|
|
|
|
|
|
|
|
|
|
removeAllRanges.mockClear()
|
|
|
|
|
fireEvent.keyUp(editor)
|
|
|
|
|
fireEvent.click(editor)
|
|
|
|
|
fireEvent.mouseUp(editor)
|
|
|
|
|
|
|
|
|
|
expect(removeAllRanges).not.toHaveBeenCalled()
|
|
|
|
|
expect(editor.textContent).toContain('ni')
|
|
|
|
|
|
|
|
|
|
removeAllRanges.mockRestore()
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-26 05:24:05 +02:00
|
|
|
it('lets committed composed characters reach the native input pipeline once', () => {
|
|
|
|
|
const onDraftChange = vi.fn()
|
|
|
|
|
render(<Controlled onDraftChange={onDraftChange} />)
|
|
|
|
|
const editor = screen.getByTestId('agent-input')
|
|
|
|
|
editor.focus()
|
|
|
|
|
|
|
|
|
|
const portugueseText = 'á é í ç ã õ'
|
|
|
|
|
const accentedKey = createEvent.keyDown(editor, { key: 'á' })
|
|
|
|
|
fireEvent(editor, accentedKey)
|
|
|
|
|
expect(accentedKey.defaultPrevented).toBe(false)
|
|
|
|
|
|
|
|
|
|
editor.textContent = portugueseText
|
|
|
|
|
setSelection(editor, portugueseText.length)
|
|
|
|
|
fireEvent.input(editor)
|
|
|
|
|
expect(onDraftChange).toHaveBeenLastCalledWith(portugueseText)
|
|
|
|
|
|
|
|
|
|
const cjkKey = createEvent.keyDown(editor, { key: '你' })
|
|
|
|
|
fireEvent(editor, cjkKey)
|
|
|
|
|
expect(cjkKey.defaultPrevented).toBe(false)
|
|
|
|
|
|
|
|
|
|
editor.textContent = `${portugueseText}你`
|
|
|
|
|
setSelection(editor, portugueseText.length + 1)
|
|
|
|
|
fireEvent.input(editor)
|
|
|
|
|
expect(onDraftChange).toHaveBeenLastCalledWith(`${portugueseText}你`)
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
it('deletes an inline chip with a single Backspace', () => {
|
|
|
|
|
render(<Controlled />)
|
|
|
|
|
updateEditorText('edit my [[alp')
|
|
|
|
|
clickFirstSuggestion()
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
const editor = screen.getByTestId('agent-input')
|
|
|
|
|
fireEvent.keyDown(editor, { key: 'Backspace' })
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
expect(screen.queryByTestId('inline-wikilink-chip')).toBeNull()
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
})
|
|
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
it('submits serialized wikilink text and resolved references', () => {
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
const onSend = vi.fn()
|
2026-04-12 13:14:18 +02:00
|
|
|
render(<Controlled onSend={onSend} />)
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
updateEditorText('edit my [[alpha]] essay')
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
fireEvent.keyDown(screen.getByTestId('agent-input'), { key: 'Enter' })
|
|
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
expect(onSend).toHaveBeenCalledWith('edit my [[alpha]] essay', [
|
|
|
|
|
{ title: 'Alpha', path: '/vault/alpha.md', type: 'Project' },
|
|
|
|
|
])
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
})
|
|
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
it('does not send on Shift+Enter', () => {
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
const onSend = vi.fn()
|
2026-04-12 13:14:18 +02:00
|
|
|
render(<Controlled onSend={onSend} />)
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
updateEditorText('hello')
|
|
|
|
|
fireEvent.keyDown(screen.getByTestId('agent-input'), { key: 'Enter', shiftKey: true })
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
expect(onSend).not.toHaveBeenCalled()
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
})
|
|
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
it('marks the editor disabled when disabled is true', () => {
|
|
|
|
|
render(<Controlled disabled />)
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
|
2026-04-12 13:14:18 +02:00
|
|
|
const editor = screen.getByTestId('agent-input')
|
|
|
|
|
expect(editor).toHaveAttribute('contenteditable', 'false')
|
|
|
|
|
expect(editor).toHaveAttribute('aria-disabled', 'true')
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
})
|
2026-04-24 13:52:58 +02:00
|
|
|
|
|
|
|
|
it('rejects pasted images without freezing the editor', () => {
|
|
|
|
|
const onUnsupportedPaste = vi.fn()
|
|
|
|
|
render(<Controlled onUnsupportedPaste={onUnsupportedPaste} />)
|
|
|
|
|
|
|
|
|
|
const editor = screen.getByTestId('agent-input')
|
|
|
|
|
const clipboardData = {
|
|
|
|
|
getData: vi.fn(() => ''),
|
|
|
|
|
files: [new File(['image'], 'paste.png', { type: 'image/png' })],
|
|
|
|
|
items: [{ kind: 'file', type: 'image/png' }],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fireEvent.paste(editor, { clipboardData })
|
|
|
|
|
|
|
|
|
|
expect(onUnsupportedPaste).toHaveBeenCalledWith(UNSUPPORTED_INLINE_PASTE_MESSAGE)
|
|
|
|
|
|
|
|
|
|
updateEditorText('still works')
|
|
|
|
|
expect(editor.textContent).toContain('still works')
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-25 20:41:56 +02:00
|
|
|
it('extracts dropped folder paths from text/plain payloads', () => {
|
|
|
|
|
expect(extractDroppedPathText(
|
|
|
|
|
createFileLikeDataTransfer({
|
|
|
|
|
plainText: '/Users/test/Projects',
|
|
|
|
|
}) as DataTransfer,
|
|
|
|
|
)).toBe('/Users/test/Projects')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('falls back to file URLs exposed through uri lists', () => {
|
|
|
|
|
expect(extractDroppedPathText(
|
|
|
|
|
createFileLikeDataTransfer({
|
|
|
|
|
uriList: 'file:///Users/test/My%20Folder',
|
|
|
|
|
}) as DataTransfer,
|
|
|
|
|
)).toBe('"/Users/test/My Folder"')
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-25 19:23:00 +02:00
|
|
|
it('treats missing inputType as a non-insert beforeinput event', () => {
|
|
|
|
|
expect(() => isInsertBeforeInput({} as InputEvent)).not.toThrow()
|
|
|
|
|
expect(isInsertBeforeInput({} as InputEvent)).toBe(false)
|
|
|
|
|
expect(isInsertBeforeInput({ inputType: 'insertFromPaste' } as InputEvent)).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('ignores beforeinput events without inputType instead of crashing', () => {
|
|
|
|
|
render(<Controlled />)
|
|
|
|
|
|
|
|
|
|
const editor = screen.getByTestId('agent-input')
|
|
|
|
|
const beforeInputEvent = new Event('beforeinput', {
|
|
|
|
|
bubbles: true,
|
|
|
|
|
cancelable: true,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(() => fireEvent(editor, beforeInputEvent)).not.toThrow()
|
|
|
|
|
|
|
|
|
|
updateEditorText('still works')
|
|
|
|
|
expect(editor.textContent).toContain('still works')
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-24 13:52:58 +02:00
|
|
|
it('recovers if unsupported media lands in the editor DOM', async () => {
|
|
|
|
|
const onUnsupportedPaste = vi.fn()
|
|
|
|
|
render(<Controlled onUnsupportedPaste={onUnsupportedPaste} />)
|
|
|
|
|
|
|
|
|
|
const editor = screen.getByTestId('agent-input')
|
|
|
|
|
editor.innerHTML = '<img alt="paste" src="data:image/png;base64,abc" />'
|
|
|
|
|
|
|
|
|
|
fireEvent.input(editor)
|
|
|
|
|
|
|
|
|
|
expect(onUnsupportedPaste).toHaveBeenCalledWith(UNSUPPORTED_INLINE_PASTE_MESSAGE)
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(screen.getByTestId('agent-input').querySelector('img')).toBeNull()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
updateEditorText('still works')
|
|
|
|
|
expect(screen.getByTestId('agent-input').textContent).toContain('still works')
|
|
|
|
|
})
|
test: add tests for WikilinkChatInput and buildContextSnapshot
- WikilinkChatInput: 18 tests covering menu trigger, filtering, pill creation,
dedup, removal, keyboard nav, Enter select, send with refs, disabled state
- buildContextSnapshot: 10 tests for structured context JSON output
including activeNote, openTabs, vault summary, references, frontmatter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:19:23 +01:00
|
|
|
})
|