에디터 미디어 삽입 실행 분리

This commit is contained in:
2026-07-29 12:44:23 +09:00
parent 92e37f3ae8
commit ebc4a95b37
10 changed files with 394 additions and 153 deletions

View File

@@ -0,0 +1,165 @@
import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import { ref } from 'vue'
import { useAdminMarkdownMediaActions } from '../composables/useAdminMarkdownMediaActions.js'
/**
* 미디어 액션 테스트 상태를 만든다.
* @param {Object} [overrides] - 상태 재정의
* @returns {Object} 테스트 상태
*/
const createMediaActionHarness = (overrides = {}) => {
const value = ref(overrides.value ?? '본문')
const mediaPickerTarget = ref(overrides.target ?? 'image')
const liveSlashInsertLine = ref(overrides.liveSlashLine ?? null)
const isUploading = ref(overrides.isUploading ?? false)
const isMediaPickerOpen = ref(overrides.isMediaPickerOpen ?? false)
const activeMediaPickerTab = ref(overrides.activeMediaPickerTab ?? 'upload')
const calls = {
insertedBlocks: [],
activeGalleryImages: [],
closed: 0,
fetched: 0,
toasts: []
}
const selectedItems = ref(overrides.selectedItems ?? [])
const uploadedItems = ref(overrides.uploadedItems ?? [])
const modalUploadedItems = ref(overrides.modalUploadedItems ?? [])
const actions = useAdminMarkdownMediaActions({
value,
mediaPickerTarget,
liveSlashInsertLine,
isUploading,
isMediaPickerOpen,
activeMediaPickerTab,
resolveCurrentSelectedMediaItems: () => selectedItems.value,
uploadMediaFilesWithState: async () => uploadedItems.value,
uploadFilesIntoMediaPicker: async () => modalUploadedItems.value,
fetchMediaItems: async () => {
calls.fetched += 1
},
closeMediaPicker: () => {
calls.closed += 1
},
insertBlockSnippet: (snippet) => {
calls.insertedBlocks.push(snippet)
value.value = `${value.value}\n${snippet}`
},
appendImagesToActiveGallery: (images) => {
calls.activeGalleryImages.push(images)
},
showToast: (type, message) => {
calls.toasts.push({ type, message })
},
resolveUploadFetchErrorMessage: (error) => `업로드 오류: ${error.message}`
})
return {
value,
mediaPickerTarget,
liveSlashInsertLine,
isUploading,
isMediaPickerOpen,
activeMediaPickerTab,
selectedItems,
uploadedItems,
modalUploadedItems,
calls,
actions
}
}
describe('useAdminMarkdownMediaActions', () => {
it('일반 이미지와 갤러리를 블록 조각으로 삽입한다', () => {
const imageHarness = createMediaActionHarness({
selectedItems: [{ url: '/uploads/a.png' }]
})
imageHarness.actions.applyMediaSelection()
assert.deepEqual(imageHarness.calls.insertedBlocks, ['![](/uploads/a.png)'])
assert.equal(imageHarness.calls.closed, 1)
const galleryHarness = createMediaActionHarness({
target: 'gallery',
selectedItems: [{ url: '/uploads/a.png' }, { url: '/uploads/b.png' }]
})
galleryHarness.actions.applyMediaSelection()
assert.deepEqual(galleryHarness.calls.insertedBlocks, [
[':::gallery', '![](/uploads/a.png)', '![](/uploads/b.png)', ':::'].join('\n')
])
})
it('활성 갤러리 대상은 블록 삽입 대신 갤러리 추가 콜백을 호출한다', () => {
const harness = createMediaActionHarness({
target: 'active-gallery',
selectedItems: [{ url: '/uploads/a.png' }, { url: '/uploads/b.png' }]
})
harness.actions.applyMediaSelection()
assert.equal(harness.calls.insertedBlocks.length, 0)
assert.deepEqual(harness.calls.activeGalleryImages, [[
{ url: '/uploads/a.png', useAlt: false, caption: '' },
{ url: '/uploads/b.png', useAlt: false, caption: '' }
]])
})
it('슬래시 미디어 대기 줄에는 해당 줄 위치로 삽입하고 대기 상태를 초기화한다', () => {
const harness = createMediaActionHarness({
value: '첫 줄\n/image\n마지막 줄',
target: 'image',
liveSlashLine: 1,
selectedItems: [{ url: '/uploads/a.png' }]
})
harness.actions.applyMediaSelection()
assert.equal(harness.value.value, '첫 줄\n![](/uploads/a.png)\n마지막 줄')
assert.equal(harness.liveSlashInsertLine.value, null)
})
it('커서 업로드 삽입 후 라이브러리 탭이 열려 있으면 목록을 갱신한다', async () => {
const harness = createMediaActionHarness({
target: 'image',
uploadedItems: [{ url: '/uploads/a.png' }],
isMediaPickerOpen: true,
activeMediaPickerTab: 'library'
})
await harness.actions.uploadAndInsert([{ name: 'a.png' }], 'image')
assert.deepEqual(harness.calls.insertedBlocks, ['![](/uploads/a.png)'])
assert.equal(harness.calls.fetched, 1)
})
it('업로드 중이면 삽입과 모달 업로드를 실행하지 않는다', async () => {
const harness = createMediaActionHarness({
isUploading: true,
uploadedItems: [{ url: '/uploads/a.png' }],
modalUploadedItems: [{ url: '/uploads/b.png' }]
})
await harness.actions.uploadAndInsert([{ name: 'a.png' }], 'image')
await harness.actions.uploadFromMediaModal([{ name: 'b.png' }])
assert.equal(harness.calls.insertedBlocks.length, 0)
assert.equal(harness.calls.toasts.length, 0)
})
it('미디어 모달 업로드 성공 시 완료 토스트를 표시한다', async () => {
const harness = createMediaActionHarness({
modalUploadedItems: [{ url: '/uploads/a.png' }]
})
await harness.actions.uploadFromMediaModal([{ name: 'a.png' }])
assert.deepEqual(harness.calls.toasts, [{
type: 'success',
message: '업로드가 완료되어 파일이 선택되었습니다.'
}])
})
})