import assert from 'node:assert/strict' import test from 'node:test' import { ref } from 'vue' import { LIVE_IMAGE_DRAG_MIME, getContentBlockInsertBeforeLine, toContentImageDragPayload, useContentMarkdownImageDrag } from '../composables/useContentMarkdownImageDrag.js' /** * 테스트용 드래그 이벤트를 만든다. * @returns {Object} 드래그 이벤트 대역 객체 */ const createDragEvent = () => { const data = new Map() return { dataTransfer: { dropEffect: '', effectAllowed: '', setData: (type, value) => data.set(type, value), getData: (type) => data.get(type) || '' }, target: null, relatedTarget: null, currentTarget: { contains: () => false }, defaultPrevented: false, preventDefault() { this.defaultPrevented = true } } } /** * 테스트용 이미지 블록을 만든다. * @param {Object} options - 블록 옵션 * @returns {Object} 이미지 블록 */ const createImageBlock = (options) => ({ id: options.id, type: 'image', url: options.url, caption: options.caption || '', width: options.width, useAlt: Boolean(options.useAlt), legacyBracketLabel: Boolean(options.legacyBracketLabel), meta: { startLine: options.startLine } }) /** * 테스트용 갤러리 블록을 만든다. * @returns {Object} 갤러리 블록 */ const createGalleryBlock = () => ({ id: 'gallery-a', type: 'gallery', images: [ { url: '/a.jpg', caption: 'A' }, { url: '/b.jpg', caption: 'B' }, { url: '/c.jpg', caption: 'C' } ], meta: { startLine: 10, endLine: 14 } }) /** * 테스트용 드래그 상태를 만든다. * @param {Object} calls - 이벤트 기록 객체 * @returns {Object} 이미지 드래그 상태 */ const createImageDragState = (calls = {}) => useContentMarkdownImageDrag({ interactive: ref(true), tailInsertBeforeLine: ref(99), onMergeImagesToGallery: (payload) => { calls.merge = payload }, onInsertImageToGallery: (payload) => { calls.insert = payload }, onExtractGalleryImage: (payload) => { calls.extract = payload }, onGalleryReorder: (payload) => { calls.reorder = payload } }) test('라이브 이미지 드래그 payload는 단일 이미지 블록 데이터를 보존한다', () => { const block = createImageBlock({ id: 'image-a', url: '/uploads/a.jpg', caption: '캡션', width: 360, useAlt: true, legacyBracketLabel: true, startLine: 3 }) assert.deepEqual(toContentImageDragPayload(block), { url: '/uploads/a.jpg', caption: '캡션', width: 360, useAlt: true, legacyBracketLabel: true }) assert.equal(getContentBlockInsertBeforeLine(block), 3) assert.equal(getContentBlockInsertBeforeLine({}), null) }) test('단일 이미지를 다른 단일 이미지에 드롭하면 갤러리 병합 이벤트를 보낸다', () => { const calls = {} const drag = createImageDragState(calls) const source = createImageBlock({ id: 'image-a', url: '/a.jpg', startLine: 2 }) const target = createImageBlock({ id: 'image-b', url: '/b.jpg', startLine: 8 }) const event = createDragEvent() drag.onImageBlockDragStart(event, source) assert.equal(event.dataTransfer.effectAllowed, 'move') assert.equal(JSON.parse(event.dataTransfer.getData(LIVE_IMAGE_DRAG_MIME)).sourceLine, 2) drag.onImageBlockDragOver(event, target) assert.equal(event.defaultPrevented, true) assert.deepEqual(drag.imageBlockDropTarget.value, { blockId: 'image-b' }) drag.onImageBlockDrop(event, target) assert.deepEqual(calls.merge, { sourceLine: 2, targetLine: 8 }) assert.equal(drag.liveImageDragState.value, null) }) test('단일 이미지를 갤러리 이미지 위에 드롭하면 갤러리 내부 삽입 이벤트를 보낸다', () => { const calls = {} const drag = createImageDragState(calls) const source = createImageBlock({ id: 'image-a', url: '/a.jpg', startLine: 2 }) const gallery = createGalleryBlock() const event = createDragEvent() drag.onImageBlockDragStart(event, source) drag.onGalleryDragOverItem(event, gallery, 1) drag.onGalleryDrop(event, gallery, 1) assert.deepEqual(calls.insert, { sourceLine: 2, startLine: 10, endLine: 14, targetIndex: 2, image: { url: '/a.jpg', caption: '', width: undefined, useAlt: false, legacyBracketLabel: false } }) assert.equal(drag.liveImageDragState.value, null) }) test('갤러리 이미지를 같은 갤러리 안에서 드롭하면 이미지 순서 변경 이벤트를 보낸다', () => { const calls = {} const drag = createImageDragState(calls) const gallery = createGalleryBlock() const event = createDragEvent() drag.onGalleryDragStart(event, gallery, 0) drag.onGalleryDragOverItem(event, gallery, 2) drag.onGalleryDrop(event, gallery, 2) assert.deepEqual(calls.reorder, { startLine: 10, endLine: 14, images: [ { url: '/b.jpg', caption: 'B' }, { url: '/c.jpg', caption: 'C' }, { url: '/a.jpg', caption: 'A' } ] }) assert.equal(drag.liveImageDragState.value, null) }) test('갤러리 이미지를 블록 사이와 문서 하단에 드롭하면 단일 이미지 분리 이벤트를 보낸다', () => { const calls = {} const drag = createImageDragState(calls) const gallery = createGalleryBlock() const insertTarget = { id: 'paragraph-a', meta: { startLine: 30 } } const event = createDragEvent() drag.onGalleryDragStart(event, gallery, 1) drag.onBlockInsertDragOver(event, insertTarget) assert.deepEqual(drag.blockInsertDropTarget.value, { insertBeforeLine: 30 }) drag.onBlockInsertDrop(event, insertTarget) assert.deepEqual(calls.extract, { startLine: 10, endLine: 14, imageIndex: 1, insertBeforeLine: 30, image: { url: '/b.jpg', caption: 'B' } }) drag.onGalleryDragStart(event, gallery, 2) drag.onTailInsertDragOver(event) drag.onTailInsertDrop(event) assert.deepEqual(calls.extract, { startLine: 10, endLine: 14, imageIndex: 2, insertBeforeLine: 99, image: { url: '/c.jpg', caption: 'C' } }) })