162 lines
4.9 KiB
JavaScript
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()
|
|
})
|