미리보기 액션 적용 분리

This commit is contained in:
2026-07-29 10:30:24 +09:00
parent 980ef92a68
commit d8cfa77080
10 changed files with 456 additions and 228 deletions

View File

@@ -0,0 +1,171 @@
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, '첫 줄셋째 줄')
})