feat: editor image removal QOLs
This commit is contained in:
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()
|
||||
|
||||
|
||||
Reference in new issue
Block a user