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

154 lines
4.6 KiB
JavaScript

import assert from 'node:assert/strict'
import test from 'node:test'
import { nextTick, ref } from 'vue'
import { useAdminMarkdownBlockActions } from '../composables/useAdminMarkdownBlockActions.js'
/**
* 테스트용 블록 액션 상태를 만든다.
* @param {Object} options - 테스트 옵션
* @returns {Object} 테스트 상태
*/
const createBlockActionsState = (options = {}) => {
const calls = {
applyPlans: [],
ensure: 0,
focusPreview: [],
insertGallery: [],
previewFocusRequests: [],
textareaSelections: []
}
const value = ref(options.value ?? '![기존](/old.png)')
const activeMediaBlock = ref(options.activeMediaBlock ?? {
kind: 'image',
startLine: 0,
endLine: 0,
images: [{ alt: '기존', url: '/old.png' }]
})
const activeBlockContext = ref(options.activeBlockContext ?? activeMediaBlock.value)
const inlineColorSelectionContext = ref(options.inlineColorSelectionContext ?? null)
const actions = useAdminMarkdownBlockActions({
value,
activeMediaBlock,
activeBlockContext,
inlineColorSelectionContext,
applyLineReplacementPlan: (plan, focusEditor = false) => {
calls.applyPlans.push({ plan, focusEditor })
if (!plan) {
return false
}
const lines = String(value.value || '').split('\n')
lines.splice(plan.startLine, plan.endLine - plan.startLine + 1, ...plan.replacementLines)
value.value = lines.join('\n')
return true
},
ensureBlockPanelEngaged: () => {
calls.ensure += 1
},
insertGallery: (images) => {
calls.insertGallery.push(images)
},
setTextareaSelection: (...args) => {
calls.textareaSelections.push(args)
},
requestPreviewFocus: (focus) => {
calls.previewFocusRequests.push(focus)
},
focusPreviewEditableAtLine: (...args) => {
calls.focusPreview.push(args)
}
})
return {
actions,
activeBlockContext,
activeMediaBlock,
calls,
inlineColorSelectionContext,
value
}
}
test('이미지 표시 너비 변경을 현재 미디어 줄에 적용한다', () => {
const state = createBlockActionsState()
state.actions.updateActiveMediaImage(0, { pixelWidth: 240 })
assert.equal(state.value.value, '![](/old.png){size=240}')
assert.equal(state.calls.ensure, 1)
assert.equal(state.calls.applyPlans.at(-1).focusEditor, false)
})
test('파일명 캡션 사용은 이미지 URL 기준 기본 캡션을 채운다', () => {
const state = createBlockActionsState({
value: '![](/uploads/posts/2026/07/sample.png)',
activeMediaBlock: {
kind: 'image',
startLine: 0,
endLine: 0,
images: [{ alt: '', caption: '', url: '/uploads/posts/2026/07/sample.png' }]
}
})
state.actions.setActiveMediaUseAlt(0, true)
assert.equal(state.value.value, '![](/uploads/posts/2026/07/sample.png "sample.png")')
assert.equal(state.calls.ensure, 1)
})
test('활성 갤러리가 없으면 갤러리 삽입 함수로 위임한다', () => {
const state = createBlockActionsState({
activeMediaBlock: null
})
const images = [{ url: '/a.png', alt: 'A' }]
state.actions.appendImagesToActiveGallery(images)
assert.deepEqual(state.calls.insertGallery, [images])
assert.equal(state.calls.applyPlans.length, 0)
})
test('소스 모드 인라인 색상 적용 후 textarea 선택 범위를 갱신한다', () => {
const state = createBlockActionsState({
value: '강조 문장',
inlineColorSelectionContext: {
kind: 'inline-color',
mode: 'source',
start: 0,
end: 2,
text: '강조'
}
})
state.actions.updateActiveInlineColor('red')
assert.equal(state.value.value, '::red[강조] 문장')
assert.equal(state.inlineColorSelectionContext.value, null)
assert.deepEqual(state.calls.textareaSelections, [[9, 9, { preserveScroll: true }]])
assert.equal(state.calls.ensure, 1)
})
test('라이브 모드 인라인 색상 적용 후 미리보기 포커스를 예약한다', async () => {
const state = createBlockActionsState({
value: '> 문장 테스트',
inlineColorSelectionContext: {
kind: 'inline-color',
mode: 'live',
sourceLine: 0,
startOffset: 0,
endOffset: 2,
bodyText: '문장 테스트',
text: '문장'
}
})
state.actions.updateActiveInlineColor('blue')
await nextTick()
assert.equal(state.value.value, '> ::blue[문장] 테스트')
assert.equal(state.inlineColorSelectionContext.value, null)
assert.deepEqual(state.calls.previewFocusRequests, [{ line: 0, offset: 2 }])
assert.deepEqual(state.calls.focusPreview, [[0, 0, 'auto', 2, 'nearest']])
})