import assert from 'node:assert/strict' import test from 'node:test' import { nextTick, ref } from 'vue' import { useAdminMarkdownPreviewActions } from '../composables/useAdminMarkdownPreviewActions.js' /** * 테스트용 미리보기 액션 상태를 만든다. * @param {Object} options - 테스트 옵션 * @returns {Object} 테스트 상태 */ const createPreviewActionsState = (options = {}) => { const calls = { focusPreview: [], resetBlockPanel: 0, replacedRanges: [] } const value = ref(options.value ?? [ ':::gallery', '![](/a.png)', '![](/b.png)', ':::', '아래' ].join('\n')) const activeLogicalLineIndex = ref(options.activeLogicalLineIndex ?? 0) const liveSlashInsertLine = ref(options.liveSlashInsertLine ?? null) const actions = useAdminMarkdownPreviewActions({ value, activeLogicalLineIndex, liveSlashInsertLine, replaceLineRange: (startLine, endLine, replacementLines, focusEditor = false) => { calls.replacedRanges.push({ startLine, endLine, replacementLines, focusEditor }) const lines = String(value.value || '').split('\n') lines.splice(startLine, endLine - startLine + 1, ...replacementLines) value.value = lines.join('\n') }, resolveCurrentFencedBlockEndLine: (startLine, fallbackEndLine) => { return options.resolveCurrentFencedBlockEndLine?.(startLine, fallbackEndLine) ?? fallbackEndLine }, resetBlockPanelContext: () => { calls.resetBlockPanel += 1 }, focusPreviewEditableAtLine: (...args) => { calls.focusPreview.push(args) } }) return { actions, activeLogicalLineIndex, calls, liveSlashInsertLine, value } } test('갤러리 순서 변경 payload를 Markdown 값에 반영한다', () => { const state = createPreviewActionsState() state.actions.onPreviewGalleryReorder({ startLine: 0, endLine: 3, images: [ { url: '/b.png' }, { url: '/a.png' } ] }) assert.equal(state.value.value, [ ':::gallery', '![](/b.png)', '![](/a.png)', ':::', '아래' ].join('\n')) }) test('갤러리 이미지 삭제 후 활성 줄을 삭제 결과 기준으로 갱신한다', () => { const state = createPreviewActionsState() state.actions.onPreviewRemoveGalleryImage({ startLine: 0, endLine: 3, imageIndex: 1 }) assert.equal(state.value.value, [ '![](/a.png)', '아래' ].join('\n')) assert.equal(state.activeLogicalLineIndex.value, 0) }) test('미디어 슬래시 삽입 대기 줄의 지연 commit은 무시한다', () => { const state = createPreviewActionsState({ value: '', liveSlashInsertLine: 2 }) state.actions.onPreviewBlockContentChange({ startLine: 2, endLine: 2, replacementLines: ['/image'] }) assert.equal(state.value.value, '') assert.deepEqual(state.calls.replacedRanges, []) }) test('미리보기 블록 본문 변경은 fenced 닫는 줄 보정 후 줄 범위를 교체한다', () => { const state = createPreviewActionsState({ value: [ ':::callout', '기존', ':::', '아래' ].join('\n'), resolveCurrentFencedBlockEndLine: () => 2 }) state.actions.onPreviewBlockContentChange({ startLine: 0, endLine: 0, replacementLines: [':::callout title=수정', '변경', ':::'] }) assert.equal(state.value.value, [ ':::callout title=수정', '변경', ':::', '아래' ].join('\n')) assert.deepEqual(state.calls.replacedRanges, [{ startLine: 0, endLine: 2, replacementLines: [':::callout title=수정', '변경', ':::'], focusEditor: false }]) }) test('지정 줄 삽입 후 블록 패널 컨텍스트를 초기화하고 미리보기 포커스를 예약한다', async () => { const state = createPreviewActionsState({ value: '첫 줄' }) state.actions.onPreviewInsertAfterLine({ afterLine: 0, lines: ['둘째 줄'], focusLine: 1 }) await nextTick() assert.equal(state.value.value, '첫 줄\n둘째 줄') assert.equal(state.activeLogicalLineIndex.value, 1) assert.equal(state.calls.resetBlockPanel, 1) assert.deepEqual(state.calls.focusPreview, [[1, 0, 'start']]) }) test('줄 삭제와 이전 줄 병합 결과를 본문에 반영한다', () => { const state = createPreviewActionsState({ value: ['첫 줄', '둘째 줄', '셋째 줄'].join('\n') }) state.actions.onPreviewDeleteLine(1) assert.equal(state.value.value, '첫 줄\n셋째 줄') state.actions.onPreviewMergeWithPreviousLine({ lineIndex: 1, mergedLine: '첫 줄셋째 줄' }) assert.equal(state.value.value, '첫 줄셋째 줄') })