Files
sori.studio/tests/useAdminMarkdownFocusBridge.test.js

162 lines
4.9 KiB
JavaScript

import assert from 'node:assert/strict'
import test from 'node:test'
import { effectScope, nextTick, ref } from 'vue'
import { useAdminMarkdownFocusBridge } from '../composables/useAdminMarkdownFocusBridge.js'
/**
* 테스트용 관리자 Markdown 포커스 브리지를 만든다.
* @param {Object} options - 테스트 옵션
* @returns {Object} 테스트 상태
*/
const createFocusBridgeState = (options = {}) => {
const scope = effectScope()
const activeMode = ref(options.activeMode ?? 'write')
const value = ref(options.value ?? '첫 줄\n둘째 줄\n셋째 줄')
const lastSelectionState = ref(options.selection ?? { start: 0, end: 0 })
const activeLogicalLineIndex = ref(options.activeLogicalLineIndex ?? 1)
const textarea = options.textarea ?? { selectionStart: 0 }
const calls = {
syncTextareaHeight: 0,
focusTextareaAtLine: [],
restoreTextareaFocus: 0,
scrollPreviewToTop: 0,
focusPreviewEditableAtLine: []
}
let currentPreviewSourceLine = options.currentPreviewSourceLine ?? null
let bridge
scope.run(() => {
bridge = useAdminMarkdownFocusBridge({
activeMode,
value,
lastSelectionState,
activeLogicalLineIndex,
getWriteTextareaElement: () => textarea,
getLineIndexAtOffset: (offset) => String(value.value).slice(0, offset).split('\n').length - 1,
getLineStartOffset: (lineIndex) => {
const lines = String(value.value).split('\n')
return lines.slice(0, lineIndex).join('\n').length + (lineIndex > 0 ? 1 : 0)
},
resolvePreviewFocusPosition: (line, offset) => ({
line: line + 10,
offset: offset + 1
}),
getCurrentPreviewSourceLine: () => currentPreviewSourceLine,
syncTextareaHeight: () => {
calls.syncTextareaHeight += 1
},
focusTextareaAtLine: (lineIndex) => {
calls.focusTextareaAtLine.push(lineIndex)
},
restoreTextareaFocus: () => {
calls.restoreTextareaFocus += 1
},
scrollPreviewToTop: () => {
calls.scrollPreviewToTop += 1
},
focusPreviewEditableAtLine: (...args) => {
calls.focusPreviewEditableAtLine.push(args)
}
})
})
return {
activeMode,
calls,
bridge,
setCurrentPreviewSourceLine: (line) => {
currentPreviewSourceLine = line
},
stop: () => scope.stop()
}
}
/**
* 포커스 브리지 watch와 내부 nextTick을 모두 비운다.
* @returns {Promise<void>}
*/
const flushFocusBridgeTicks = async () => {
await nextTick()
await nextTick()
await nextTick()
}
test('작성 위치를 라이브 포커스 대상으로 저장하고 preview 전환 시 소비한다', async () => {
const state = createFocusBridgeState({
textarea: { selectionStart: 6 }
})
state.bridge.rememberWritePositionForPreview()
assert.deepEqual(state.bridge.pendingPreviewFocus.value, {
line: 11,
offset: 3
})
state.activeMode.value = 'preview'
await flushFocusBridgeTicks()
assert.equal(state.bridge.pendingPreviewFocus.value, null)
assert.deepEqual(state.calls.focusPreviewEditableAtLine, [[11, 0, 'auto', 3, 'center']])
state.stop()
})
test('라이브 위치를 작성 포커스 대상으로 저장하고 write 전환 시 소비한다', async () => {
const state = createFocusBridgeState({
activeMode: 'preview',
currentPreviewSourceLine: 4
})
state.bridge.rememberPreviewPosition()
assert.equal(state.bridge.pendingWriteFocusLine.value, 4)
state.activeMode.value = 'write'
await flushFocusBridgeTicks()
assert.equal(state.bridge.pendingWriteFocusLine.value, null)
assert.equal(state.calls.syncTextareaHeight, 1)
assert.deepEqual(state.calls.focusTextareaAtLine, [4])
assert.equal(state.calls.restoreTextareaFocus, 0)
state.stop()
})
test('작성 복원 대상이 없으면 마지막 textarea 선택을 복원한다', async () => {
const state = createFocusBridgeState({
activeMode: 'preview'
})
state.activeMode.value = 'write'
await flushFocusBridgeTicks()
assert.equal(state.calls.syncTextareaHeight, 1)
assert.deepEqual(state.calls.focusTextareaAtLine, [])
assert.equal(state.calls.restoreTextareaFocus, 1)
state.stop()
})
test('라이브 복원 대상이 없으면 미리보기를 상단으로 이동한다', async () => {
const state = createFocusBridgeState()
state.activeMode.value = 'preview'
await flushFocusBridgeTicks()
assert.equal(state.calls.scrollPreviewToTop, 1)
assert.deepEqual(state.calls.focusPreviewEditableAtLine, [])
state.stop()
})
test('요청한 라이브 포커스는 다음 preview 전환 때 사용한다', async () => {
const state = createFocusBridgeState()
state.bridge.requestPreviewFocus({ line: 3, offset: 7 })
assert.deepEqual(state.bridge.pendingPreviewFocus.value, {
line: 3,
offset: 7
})
state.activeMode.value = 'preview'
await flushFocusBridgeTicks()
assert.deepEqual(state.calls.focusPreviewEditableAtLine, [[3, 0, 'auto', 7, 'center']])
state.stop()
})