feat: editor image removal QOLs

This commit is contained in:
fsylvain committed 2026-09-29 02:44:31 +02:00
1 parent 0fc00ceeff
commit b29d0ed045
23 files changed
+1398 -151

No files matched your search

@@ -0,0 +1,108 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import uiPlugin from '@nuxt/ui/vue-plugin'
import router from '@/router'
import { useSessionStore } from '@/stores/session'
import RootComponent from '@/App.vue'
import ArticleEditor from '@/components/ArticleEditor.vue'
vi.mock('@nuxt/ui/composables', () => ({
useOverlay: () => ({ create: () => ({ open: () => Promise.resolve(false) }) }),
useToast: () => ({ add: vi.fn() }),
}))
function mockResponse(status: number, body: unknown = null) {
return {
ok: status >= 200 && status < 300,
status,
headers: { get: () => null },
json: async () => body,
} as unknown as Response
}
let currentWrapper: Awaited<ReturnType<typeof mount>> | null = null
async function mountArticleEditor(body = '<p>Hello</p>') {
setActivePinia(createPinia())
const session = useSessionStore()
session.session = { verified: true, username: 'marta' }
session.status = 'ready'
global.fetch = vi.fn(() =>
Promise.resolve(
mockResponse(200, {
id: 12,
title: 'T',
body,
images: [],
is_online: false,
created_at: '2026-01-01T00:00:00Z',
updated_at: '2026-01-01T00:00:00Z',
}),
),
) as unknown as typeof fetch
await router.push('/article/12/edit')
const wrapper = mount(RootComponent, {
attachTo: document.body,
global: { plugins: [router, uiPlugin] },
})
currentWrapper = wrapper
await flushPromises()
return wrapper
}
afterEach(async () => {
currentWrapper?.unmount()
currentWrapper = null
await router.push('/login')
})
describe('insertImage stamps the inserted image with its numeric id', () => {
it('renders data-image-id on the img and emits the updated body', async () => {
const wrapper = await mountArticleEditor()
const editor = wrapper.findComponent(ArticleEditor)
;(
editor.vm as unknown as { insertImage: (src: string, alt: string, id: number) => void }
).insertImage('/static/uploadedImages/photo.png', 'alt text', 10)
await flushPromises()
expect(editor.props('modelValue')).toContain(
'<img src="/static/uploadedImages/photo.png" alt="alt text" data-image-id="10">',
)
})
})
describe('removeImage removes only the node whose data-image-id matches', () => {
it('deletes the matching image node and leaves the rest of the content', async () => {
const wrapper = await mountArticleEditor()
const editor = wrapper.findComponent(ArticleEditor)
const vm = editor.vm as unknown as {
insertImage: (src: string, alt: string, id: number) => void
removeImage: (id: number) => void
}
vm.insertImage('/static/uploadedImages/a.png', '', 10)
await flushPromises()
vm.insertImage('/static/uploadedImages/b.png', '', 11)
await flushPromises()
vm.removeImage(10)
await flushPromises()
const body = editor.props('modelValue') as string
expect(body).not.toContain('data-image-id="10"')
expect(body).toContain('data-image-id="11"')
})
it('does nothing when no image carries the given id', async () => {
const wrapper = await mountArticleEditor('<p>Hello</p>')
const editor = wrapper.findComponent(ArticleEditor)
const vm = editor.vm as unknown as { removeImage: (id: number) => void }
vm.removeImage(999)
await flushPromises()
expect(editor.emitted('update:modelValue')).toBeFalsy()
})
})
@@ -112,7 +112,7 @@ const Host = defineComponent({
const open = ref(false)
return {
open,
onUploaded: (location: string) => emit('uploaded', location),
onUploaded: (location: string, imageId: number) => emit('uploaded', location, imageId),
onClose: () => emit('close'),
}
},
@@ -309,10 +309,13 @@ describe('A successful upload emits the location and auto-closes 600 ms later',
await chooseRasterFileAndConfirmUpload(fileFixture('north-wall.jpg', 'image/jpeg', 3.9))
const xhr = latestXHR()
xhr.resolve(201, JSON.stringify({ location: '/static/uploadedImages/north-wall.jpg' }))
xhr.resolve(
201,
JSON.stringify({ id: 7, location: '/static/uploadedImages/north-wall.jpg' }),
)
await vi.advanceTimersByTimeAsync(0)
expect(wrapper.emitted('uploaded')).toEqual([['/static/uploadedImages/north-wall.jpg']])
expect(wrapper.emitted('uploaded')).toEqual([['/static/uploadedImages/north-wall.jpg', 7]])
expect(document.body.textContent).toContain('Image added')
expect(document.querySelector('[role="dialog"]')).toBeTruthy()