186 lines
4.9 KiB
JavaScript
186 lines
4.9 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
import { effectScope, nextTick, ref } from 'vue'
|
|
import { useAdminMarkdownTextareaFocus } from '../composables/useAdminMarkdownTextareaFocus.js'
|
|
|
|
/**
|
|
* 테스트용 textarea DOM 대역을 만든다.
|
|
* @param {Object} options - textarea 옵션
|
|
* @returns {Object} textarea 대역
|
|
*/
|
|
const createTextareaStub = (options = {}) => {
|
|
const calls = {
|
|
focus: 0,
|
|
selections: []
|
|
}
|
|
|
|
const textarea = {
|
|
value: options.value ?? '첫 줄\n둘째 줄\n셋째 줄',
|
|
selectionStart: options.selectionStart ?? 0,
|
|
selectionEnd: options.selectionEnd ?? options.selectionStart ?? 0,
|
|
scrollHeight: options.scrollHeight ?? 700,
|
|
scrollTop: options.scrollTop ?? 0,
|
|
clientWidth: options.clientWidth ?? 720,
|
|
style: {
|
|
height: ''
|
|
},
|
|
focus: () => {
|
|
calls.focus += 1
|
|
},
|
|
setSelectionRange: (start, end) => {
|
|
calls.selections.push([start, end])
|
|
textarea.selectionStart = start
|
|
textarea.selectionEnd = end
|
|
},
|
|
getBoundingClientRect: () => ({
|
|
top: 0,
|
|
bottom: 700,
|
|
height: 700
|
|
}),
|
|
calls
|
|
}
|
|
|
|
return textarea
|
|
}
|
|
|
|
/**
|
|
* 테스트용 textarea 포커스 composable 상태를 만든다.
|
|
* @param {Object} options - 테스트 옵션
|
|
* @returns {Object} 테스트 상태
|
|
*/
|
|
const createTextareaFocusState = (options = {}) => {
|
|
const scope = effectScope()
|
|
const value = ref(options.value ?? '첫 줄\n둘째 줄\n셋째 줄')
|
|
const activeMode = ref(options.activeMode ?? 'write')
|
|
const writeModeContextValue = ref(null)
|
|
const lastSelectionState = ref(options.lastSelectionState ?? { start: 0, end: 0 })
|
|
const activeLogicalLineIndex = ref(0)
|
|
const gutterLineHeights = ref([28, 28, 28])
|
|
const editorRootRef = ref(null)
|
|
const textarea = options.textarea ?? createTextareaStub({ value: value.value })
|
|
const calls = {
|
|
syncBlockPanelState: 0,
|
|
syncWriteSlashState: 0
|
|
}
|
|
let focusState
|
|
|
|
scope.run(() => {
|
|
focusState = useAdminMarkdownTextareaFocus({
|
|
value,
|
|
activeMode,
|
|
writeModeContextValue,
|
|
lastSelectionState,
|
|
activeLogicalLineIndex,
|
|
gutterLineHeights,
|
|
editorRootRef,
|
|
minTextareaHeight: 620,
|
|
getWriteTextareaElement: () => textarea,
|
|
getLineStartOffset: (lineIndex) => {
|
|
const lines = String(value.value).split('\n')
|
|
return lines.slice(0, lineIndex).join('\n').length + (lineIndex > 0 ? 1 : 0)
|
|
},
|
|
syncBlockPanelState: () => {
|
|
calls.syncBlockPanelState += 1
|
|
},
|
|
syncWriteSlashState: () => {
|
|
calls.syncWriteSlashState += 1
|
|
}
|
|
})
|
|
})
|
|
|
|
return {
|
|
activeLogicalLineIndex,
|
|
calls,
|
|
focusState,
|
|
lastSelectionState,
|
|
textarea,
|
|
value,
|
|
writeModeContextValue,
|
|
stop: () => scope.stop()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* composable 내부 nextTick을 비운다.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
const flushTextareaFocusTicks = async () => {
|
|
await nextTick()
|
|
await nextTick()
|
|
await nextTick()
|
|
}
|
|
|
|
test('textarea 선택 영역을 마지막 선택 상태로 기억한다', () => {
|
|
const textarea = createTextareaStub({
|
|
value: 'abc\ndef',
|
|
selectionStart: 5,
|
|
selectionEnd: 7
|
|
})
|
|
const state = createTextareaFocusState({
|
|
value: 'abc\ndef',
|
|
textarea
|
|
})
|
|
|
|
state.focusState.rememberTextareaSelection()
|
|
|
|
assert.deepEqual(state.lastSelectionState.value, {
|
|
start: 5,
|
|
end: 7
|
|
})
|
|
state.stop()
|
|
})
|
|
|
|
test('커서 위치 기준으로 작성 컨텍스트와 활성 줄을 갱신한다', async () => {
|
|
const textarea = createTextareaStub({
|
|
value: '첫 줄\n둘째 줄\n셋째 줄',
|
|
selectionStart: 8,
|
|
selectionEnd: 8
|
|
})
|
|
const state = createTextareaFocusState({ textarea })
|
|
|
|
state.focusState.refreshCaretLogicalLine()
|
|
await flushTextareaFocusTicks()
|
|
|
|
assert.equal(state.writeModeContextValue.value, '첫 줄\n둘째 줄\n셋째 줄')
|
|
assert.deepEqual(state.lastSelectionState.value, {
|
|
start: 8,
|
|
end: 8
|
|
})
|
|
assert.equal(state.activeLogicalLineIndex.value, 1)
|
|
assert.equal(state.calls.syncBlockPanelState, 1)
|
|
assert.equal(textarea.style.height, '700px')
|
|
state.stop()
|
|
})
|
|
|
|
test('지정 줄로 textarea 포커스와 커서를 이동한다', async () => {
|
|
const textarea = createTextareaStub()
|
|
const state = createTextareaFocusState({ textarea })
|
|
|
|
state.focusState.focusTextareaAtLine(2)
|
|
await flushTextareaFocusTicks()
|
|
|
|
assert.equal(textarea.calls.focus, 1)
|
|
assert.deepEqual(textarea.calls.selections[0], [9, 9])
|
|
assert.equal(state.activeLogicalLineIndex.value, 2)
|
|
state.stop()
|
|
})
|
|
|
|
test('마지막 선택 상태로 textarea 포커스를 복원한다', async () => {
|
|
const textarea = createTextareaStub()
|
|
const state = createTextareaFocusState({
|
|
textarea,
|
|
lastSelectionState: {
|
|
start: 5,
|
|
end: 8
|
|
}
|
|
})
|
|
|
|
state.focusState.restoreTextareaFocus()
|
|
await flushTextareaFocusTicks()
|
|
|
|
assert.equal(textarea.calls.focus, 1)
|
|
assert.deepEqual(textarea.calls.selections[0], [5, 8])
|
|
assert.equal(state.activeLogicalLineIndex.value, 1)
|
|
state.stop()
|
|
})
|