// src/plugin/__tests__/editor-suggest-mode-toggle.test.ts /** * Regression test for plan decision #1: the dispatcher MUST be a top-level * extension, NOT inside livePreviewCompartment. Reconfiguring the live * preview compartment must NOT tear down the dispatcher's AbortController * or its dropdown DOM. */ import { describe, it, expect, afterEach } from 'vitest' import { EditorSuggest } from '../editor-suggest' import { getDispatcherController } from '../../editor/extensions/editor-suggest-dispatcher' import { livePreview } from '../../editor/extensions/live-preview' import { mountCM6EditorWithSourceMode, typeText, findDropdown, type MountedHarness, } from './editor-suggest-test-harness' class BracketSuggest extends EditorSuggest { onTrigger(cursor: { line: number; ch: number }, editor: { getLine(line: number): string }) { const line = editor.getLine(cursor.line) const before = line.slice(0, cursor.ch) const m = /\[\[([^\]\[]*)$/.exec(before) if (!m) return null return { start: { line: cursor.line, ch: m.index }, end: { line: cursor.line, ch: cursor.ch }, query: m[1], } } async getSuggestions() { return ['a', 'b'] } renderSuggestion(item: string, el: HTMLElement) { el.textContent = item } selectSuggestion() {} } let harness: MountedHarness | null = null afterEach(() => { harness?.destroy() harness = null document.body.innerHTML = '' }) async function flush(): Promise { await new Promise((r) => setTimeout(r, 80)) } describe('mode-toggle regression (decision #1)', () => { it('survives source ↔ live-preview compartment reconfigure (dispatcher persists)', async () => { const h = await mountCM6EditorWithSourceMode() harness = h const sug = new BracketSuggest(h.app) h.app.workspace.registerEditorSuggest(sug) typeText(h.view, '[[') await flush() expect(findDropdown()).not.toBeNull() const dropdownBefore = findDropdown() // Dropdown active means controller is non-null AND not aborted. const ctrlBefore = getDispatcherController(h.view, h.app) expect(ctrlBefore).not.toBeNull() // Reconfigure the livePreview compartment to enable live-preview // decorations. The dispatcher (top-level) must survive this without // re-creating its state. h.view.dispatch({ effects: h.livePreviewCompartment.reconfigure( livePreview({ wikilink: { onNavigate: () => {}, resolveLink: () => true }, }), ), }) await flush() // Dropdown DOM still in the document. const dropdownAfter = findDropdown() expect(dropdownAfter).not.toBeNull() expect(dropdownAfter).toBe(dropdownBefore) // Same AbortController instance — dispatcher was NOT re-created. const ctrlAfter = getDispatcherController(h.view, h.app) expect(ctrlAfter).toBe(ctrlBefore) }) })