test(10-10): promote OsDragOverlay stubs to 8 RED failing tests

- Replace 7 it.todo stubs with 8 real tests covering UX-09 contract
- Tests dispatch DragEvent/Event with synthetic dataTransfer on window
- Covers: hidden by default, Files type guard, depth counter, nested enter/leave,
  drop emit, drop reset, z-[9998] class on overlay element
- All 8 tests RED (OsDragOverlay.vue does not yet exist)
This commit is contained in:
curo1305
2026-06-15 20:43:58 +02:00
parent 1cb63d690d
commit 71f55b84a9
@@ -1,11 +1,158 @@
import { describe, it } from 'vitest' import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount } from '@vue/test-utils'
import OsDragOverlay from '../OsDragOverlay.vue'
function makeDragEvent(type, types = ['Files'], files = []) {
const event = new Event(type, { bubbles: true, cancelable: true })
Object.defineProperty(event, 'dataTransfer', {
value: { types, files, preventDefault: vi.fn() },
writable: false,
})
event.preventDefault = vi.fn()
return event
}
describe('UX-09: OsDragOverlay shows when OS files are dragged over the window', () => { describe('UX-09: OsDragOverlay shows when OS files are dragged over the window', () => {
it.todo('overlay hidden by default (dragDepth=0)') it('overlay hidden by default (dragDepth=0)', () => {
it.todo('dragenter with dataTransfer.types including "Files" shows overlay') const w = mount(OsDragOverlay, {
it.todo('dragenter without "Files" type is ignored (in-app element drag)') attachTo: document.body,
it.todo('dragleave decrements depth; overlay hides when depth reaches 0') global: { stubs: { Teleport: true, AppIcon: true } },
it.todo('drop event emits files-dropped with the file list') })
it.todo('drop resets dragDepth to 0 and hides overlay') expect(w.vm.showOverlay).toBe(false)
it.todo('overlay is teleported to body with z-[9998] (below toast z-[9999])') expect(w.vm.dragDepth).toBe(0)
w.unmount()
})
it('dragenter with dataTransfer.types including "Files" shows overlay (dragDepth=1)', async () => {
const w = mount(OsDragOverlay, {
attachTo: document.body,
global: { stubs: { Teleport: true, AppIcon: true } },
})
const event = makeDragEvent('dragenter', ['Files'])
window.dispatchEvent(event)
await w.vm.$nextTick()
expect(w.vm.showOverlay).toBe(true)
expect(w.vm.dragDepth).toBe(1)
w.unmount()
})
it('dragenter without "Files" type is ignored (in-app element drag)', async () => {
const w = mount(OsDragOverlay, {
attachTo: document.body,
global: { stubs: { Teleport: true, AppIcon: true } },
})
const event = makeDragEvent('dragenter', ['text/plain'])
window.dispatchEvent(event)
await w.vm.$nextTick()
expect(w.vm.showOverlay).toBe(false)
expect(w.vm.dragDepth).toBe(0)
w.unmount()
})
it('dragleave decrements depth; overlay hides when depth reaches 0', async () => {
const w = mount(OsDragOverlay, {
attachTo: document.body,
global: { stubs: { Teleport: true, AppIcon: true } },
})
window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
await w.vm.$nextTick()
expect(w.vm.dragDepth).toBe(1)
expect(w.vm.showOverlay).toBe(true)
window.dispatchEvent(new Event('dragleave'))
await w.vm.$nextTick()
expect(w.vm.dragDepth).toBe(0)
expect(w.vm.showOverlay).toBe(false)
w.unmount()
})
it('nested dragenter+dragleave maintains overlay until depth=0', async () => {
const w = mount(OsDragOverlay, {
attachTo: document.body,
global: { stubs: { Teleport: true, AppIcon: true } },
})
window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
await w.vm.$nextTick()
expect(w.vm.dragDepth).toBe(2)
expect(w.vm.showOverlay).toBe(true)
window.dispatchEvent(new Event('dragleave'))
await w.vm.$nextTick()
expect(w.vm.dragDepth).toBe(1)
expect(w.vm.showOverlay).toBe(true)
window.dispatchEvent(new Event('dragleave'))
await w.vm.$nextTick()
expect(w.vm.dragDepth).toBe(0)
expect(w.vm.showOverlay).toBe(false)
w.unmount()
})
it('drop event emits files-dropped with the file list', async () => {
const w = mount(OsDragOverlay, {
attachTo: document.body,
global: { stubs: { Teleport: true, AppIcon: true } },
})
window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
await w.vm.$nextTick()
const file = new File(['x'], 'a.txt', { type: 'text/plain' })
const dropEvent = new Event('drop', { bubbles: true, cancelable: true })
Object.defineProperty(dropEvent, 'dataTransfer', {
value: { files: [file] },
writable: false,
})
dropEvent.preventDefault = vi.fn()
window.dispatchEvent(dropEvent)
await w.vm.$nextTick()
const emitted = w.emitted('files-dropped')
expect(emitted).toBeTruthy()
expect(emitted[0][0]).toHaveLength(1)
expect(emitted[0][0][0]).toBeInstanceOf(File)
w.unmount()
})
it('drop resets dragDepth to 0 and hides overlay', async () => {
const w = mount(OsDragOverlay, {
attachTo: document.body,
global: { stubs: { Teleport: true, AppIcon: true } },
})
window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
await w.vm.$nextTick()
expect(w.vm.dragDepth).toBe(3)
const dropEvent = new Event('drop', { bubbles: true, cancelable: true })
Object.defineProperty(dropEvent, 'dataTransfer', {
value: { files: [] },
writable: false,
})
dropEvent.preventDefault = vi.fn()
window.dispatchEvent(dropEvent)
await w.vm.$nextTick()
expect(w.vm.dragDepth).toBe(0)
expect(w.vm.showOverlay).toBe(false)
window.dispatchEvent(new Event('dragleave'))
await w.vm.$nextTick()
expect(w.vm.dragDepth).toBe(0)
w.unmount()
})
it('overlay is teleported to body with z-[9998] (below toast z-[9999])', async () => {
const w = mount(OsDragOverlay, {
attachTo: document.body,
})
window.dispatchEvent(makeDragEvent('dragenter', ['Files']))
await w.vm.$nextTick()
const overlay = document.querySelector('[data-test="os-drag-overlay"]')
expect(overlay).toBeTruthy()
expect(overlay.classList.contains('z-[9998]')).toBe(true)
w.unmount()
})
}) })