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

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()
})