에디터 모드 전환 포커스 분리
This commit is contained in:
161
tests/useAdminMarkdownFocusBridge.test.js
Normal file
161
tests/useAdminMarkdownFocusBridge.test.js
Normal file
@@ -0,0 +1,161 @@
|
||||
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()
|
||||
})
|
||||
Reference in New Issue
Block a user