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