117 lines
3.6 KiB
TypeScript
117 lines
3.6 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { act, renderHook } from '@testing-library/react'
|
|
import { useEditorTabSwap } from './useEditorTabSwap'
|
|
|
|
const initialBlocks = [{
|
|
type: 'paragraph',
|
|
content: [{ type: 'text', text: 'Old body', styles: {} }],
|
|
children: [],
|
|
}]
|
|
|
|
function makeTab(path: string, content: string) {
|
|
return {
|
|
entry: { path, title: 'Note A', filename: 'note-a.md', type: 'Note', status: 'Active', aliases: [], isA: '' } as never,
|
|
content,
|
|
}
|
|
}
|
|
|
|
function makeEditor(docRef: { current: unknown[] }) {
|
|
const editor = {
|
|
get prosemirrorView() { return {} },
|
|
onMount: (callback: () => void) => {
|
|
callback()
|
|
return () => {}
|
|
},
|
|
replaceBlocks: vi.fn((_oldBlocks, nextBlocks) => { docRef.current = nextBlocks as unknown[] }),
|
|
insertBlocks: vi.fn(),
|
|
blocksToMarkdownLossy: vi.fn(() => 'Old body'),
|
|
tryParseMarkdownToBlocks: vi.fn(() => [{
|
|
type: 'paragraph',
|
|
content: [{ type: 'text', text: 'Fresh after background reload.', styles: {} }],
|
|
children: [],
|
|
}]),
|
|
_tiptapEditor: {
|
|
state: { doc: { content: { size: 8 } } },
|
|
commands: {
|
|
setContent: vi.fn(),
|
|
setTextSelection: vi.fn(),
|
|
},
|
|
},
|
|
}
|
|
Object.defineProperty(editor, 'document', { get: () => docRef.current })
|
|
return editor
|
|
}
|
|
|
|
function installFrameAndScrollSpies() {
|
|
vi.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
|
|
callback(0)
|
|
return 0
|
|
})
|
|
vi.spyOn(document, 'querySelector').mockReturnValue({ scrollTop: 0 } as unknown as Element)
|
|
}
|
|
|
|
async function flushEditorTick() {
|
|
await act(() => new Promise<void>((resolve) => setTimeout(resolve, 0)))
|
|
}
|
|
|
|
function appendFocusedEditorSelection() {
|
|
const container = document.createElement('div')
|
|
container.className = 'editor__blocknote-container'
|
|
const editable = document.createElement('div')
|
|
editable.contentEditable = 'true'
|
|
editable.textContent = 'Old current note caret'
|
|
container.appendChild(editable)
|
|
document.body.appendChild(container)
|
|
|
|
const range = document.createRange()
|
|
range.setStart(editable.firstChild!, 8)
|
|
range.collapse(true)
|
|
const selection = window.getSelection()!
|
|
selection.removeAllRanges()
|
|
selection.addRange(range)
|
|
editable.focus()
|
|
|
|
return { container, editable, selection }
|
|
}
|
|
|
|
describe('useEditorTabSwap selection refresh', () => {
|
|
afterEach(() => {
|
|
document.body.innerHTML = ''
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('clears stale editor DOM selection before refreshing the current note content', async () => {
|
|
installFrameAndScrollSpies()
|
|
const docRef = { current: initialBlocks as unknown[] }
|
|
const editor = makeEditor(docRef)
|
|
const tabA = makeTab('a.md', '---\ntitle: Note A\n---\n\n# Note A\n\nOld body.')
|
|
const refreshedTabA = makeTab(
|
|
'a.md',
|
|
'---\ntitle: Note A\n---\n\n# Note A\n\nFresh after background reload.',
|
|
)
|
|
|
|
const rendered = renderHook(
|
|
({ tabs }) => useEditorTabSwap({
|
|
tabs,
|
|
activeTabPath: 'a.md',
|
|
editor: editor as never,
|
|
}),
|
|
{ initialProps: { tabs: [tabA] } },
|
|
)
|
|
await flushEditorTick()
|
|
|
|
const { editable, selection } = appendFocusedEditorSelection()
|
|
const selectionChanges = vi.fn()
|
|
document.addEventListener('selectionchange', selectionChanges)
|
|
expect(selection.rangeCount).toBe(1)
|
|
|
|
rendered.rerender({ tabs: [refreshedTabA] })
|
|
await flushEditorTick()
|
|
|
|
expect(window.getSelection()?.rangeCount).toBe(0)
|
|
expect(selectionChanges).toHaveBeenCalled()
|
|
expect(document.activeElement).not.toBe(editable)
|
|
document.removeEventListener('selectionchange', selectionChanges)
|
|
})
|
|
})
|