import assert from 'node:assert/strict' import test from 'node:test' import { effectScope, nextTick, ref } from 'vue' import { useAdminMarkdownBlockPanel } from '../composables/useAdminMarkdownBlockPanel.js' globalThis.window ??= { setTimeout, clearTimeout } /** * 테스트용 블록 패널 상태를 만든다. * @param {Object} options - 테스트 옵션 * @returns {Object} 테스트 상태 */ const createBlockPanelState = (options = {}) => { const scope = effectScope() const activeMode = ref(options.activeMode ?? 'write') const value = ref(options.value ?? '![이미지](/a.png)') const writeModeContextValue = ref(null) const activeLogicalLineIndex = ref(options.activeLogicalLineIndex ?? 0) const isMediaPickerOpen = ref(false) const currentBlock = ref(options.block ?? { kind: 'image', startLine: 0, endLine: 0 }) const inlineContext = ref(null) const emitted = [] let panel scope.run(() => { panel = useAdminMarkdownBlockPanel({ activeMode, value, writeModeContextValue, activeLogicalLineIndex, isMediaPickerOpen, resolveBlockContext: () => currentBlock.value, getInlineColorSelectionContext: () => inlineContext.value, emitBlockPanel: (payload) => { emitted.push(payload) } }) }) return { activeLogicalLineIndex, currentBlock, emitted, inlineContext, isMediaPickerOpen, panel, stop: () => scope.stop() } } /** * 패널 watch와 내부 nextTick을 비운다. * @returns {Promise} */ const flushBlockPanelTicks = async () => { await nextTick() await nextTick() } test('textarea 포커스가 있으면 활성 블록 패널을 연다', () => { const state = createBlockPanelState() state.panel.handleTextareaFocusIn() state.panel.syncBlockPanelState() assert.equal(state.panel.isBlockPanelVisible.value, true) assert.deepEqual(state.emitted.at(-1), { open: true, panel: { kind: 'image', startLine: 0, endLine: 0 } }) state.stop() }) test('인라인 색상 선택 컨텍스트는 일반 블록보다 우선 표시된다', () => { const state = createBlockPanelState() state.inlineContext.value = { kind: 'inline-color', mode: 'source', start: 0, end: 2, text: '색상' } state.panel.syncInlineColorSelectionContext() state.panel.syncBlockPanelState() assert.equal(state.panel.isBlockPanelEngaged.value, true) assert.equal(state.panel.activePanelContext.value.kind, 'inline-color') assert.deepEqual(state.emitted.at(-1).panel, state.inlineContext.value) state.stop() }) test('IME 조합 중 활성 블록이 잠시 비어도 직전 블록 패널을 유지한다', async () => { const state = createBlockPanelState() state.panel.handleTextareaFocusIn() state.panel.handleTextareaCompositionStart() state.currentBlock.value = null state.activeLogicalLineIndex.value = 0 await flushBlockPanelTicks() assert.equal(state.panel.activePanelContext.value.kind, 'image') state.stop() }) test('라이브 줄 포커스는 해당 줄 블록 패널을 편집 중 상태로 연다', async () => { const state = createBlockPanelState({ activeMode: 'preview', block: { kind: 'callout', startLine: 2, endLine: 4 } }) await state.panel.handleLiveLineFocus(3) await flushBlockPanelTicks() assert.equal(state.activeLogicalLineIndex.value, 3) assert.equal(state.panel.isBlockPanelEngaged.value, true) assert.equal(state.panel.activePanelContext.value.kind, 'callout') assert.equal(state.emitted.at(-1).open, true) state.stop() }) test('패널 닫기는 편집 상태와 인라인 색상 컨텍스트를 초기화한다', () => { const state = createBlockPanelState() state.inlineContext.value = { kind: 'inline-color', mode: 'source', start: 0, end: 2, text: '색상' } state.panel.syncInlineColorSelectionContext() state.panel.closeBlockPanel() assert.equal(state.panel.isBlockPanelEngaged.value, false) assert.equal(state.panel.inlineColorSelectionContext.value, null) assert.equal(state.emitted.at(-1).open, false) state.stop() })