// @vitest-environment jsdom
// happy-dom reports `window.location.origin` as "null", and the viewer page
// URL is built against it — jsdom has a real origin.
import { afterEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import { mount } from '@vue/test-utils'
import ContentLightbox from '@/components/ui/ContentLightbox.vue'
import { useLightbox } from '@/composables/useLightbox.js'
const { openContent, close } = useLightbox()
let wrapper = null
function mountLightbox() {
wrapper = mount(ContentLightbox, { attachTo: document.body })
return wrapper
}
async function open(payload) {
openContent(payload)
await nextTick()
await nextTick()
}
afterEach(() => {
close()
wrapper?.unmount()
wrapper = null
vi.unstubAllGlobals()
})
describe('ContentLightbox', () => {
it('renders nothing until something is opened', () => {
mountLightbox()
expect(document.body.querySelector('.lightbox-dialog')).toBeNull()
})
it('shows a text file as highlighted source', async () => {
vi.stubGlobal('fetch', vi.fn(async () => ({
ok: true,
text: async () => 'const answer = 42\n',
})))
mountLightbox()
await open({ url: '/api/files/notes.js', type: 'text', title: 'notes.js' })
// Let the fetch resolve.
await vi.waitFor(() => {
expect(document.body.querySelector('.lightbox-code code')).not.toBeNull()
})
const code = document.body.querySelector('.lightbox-code code')
expect(code.className).toContain('language-javascript')
expect(code.textContent).toContain('const answer = 42')
expect(fetch).toHaveBeenCalledWith('/api/files/notes.js')
})
it('reports a failed text fetch instead of an empty viewer', async () => {
vi.stubGlobal('fetch', vi.fn(async () => ({ ok: false, status: 404 })))
mountLightbox()
await open({ url: '/api/files/missing.txt', type: 'text', title: 'missing.txt' })
await vi.waitFor(() => {
expect(document.body.querySelector('.lightbox-state-error')).not.toBeNull()
})
expect(document.body.querySelector('.lightbox-state-error').textContent)
.toContain('404')
})
it('gives audio its own player around a hidden audio element', async () => {
mountLightbox()
await open({ url: '/api/files/track.mp3', type: 'audio', title: 'track.mp3' })
const audio = document.body.querySelector('audio.lightbox-audio-el')
expect(audio).not.toBeNull()
expect(audio.getAttribute('src')).toBe('/api/files/track.mp3')
expect(audio.hasAttribute('controls')).toBe(false)
expect(document.body.querySelector('.lightbox-audio-controls')).not.toBeNull()
// play/pause, seek and the time read-outs are kit-rendered
expect(document.body.querySelector('.lightbox-audio-controls .btn-icon')).not.toBeNull()
expect(document.body.querySelectorAll('.lightbox-audio-controls .range').length).toBe(2)
})
it('keeps images and video on their native branches', async () => {
mountLightbox()
await open({ url: '/api/files/photo.png', type: 'image', title: 'photo.png' })
expect(document.body.querySelector('img.lightbox-img')?.getAttribute('src'))
.toBe('/api/files/photo.png')
await open({ url: '/api/files/clip.mp4', type: 'video', title: 'clip.mp4' })
expect(document.body.querySelector('video.lightbox-video')?.getAttribute('src'))
.toBe('/api/files/clip.mp4')
})
it('loads the iframe viewer page for documents', async () => {
mountLightbox()
await open({ url: '/api/files/report.pdf', type: 'pdf', title: 'report.pdf' })
const frame = document.body.querySelector('iframe.lightbox-frame')
expect(frame).not.toBeNull()
expect(frame.getAttribute('src')).toContain('/content-viewers/pdf.html?url=')
})
it('closes on Escape', async () => {
mountLightbox()
await open({ url: '/api/files/photo.png', type: 'image' })
expect(document.body.querySelector('.lightbox-dialog')).not.toBeNull()
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
await nextTick()
expect(document.body.querySelector('.lightbox-dialog')).toBeNull()
})
it('offers the raw file for download in its header', async () => {
mountLightbox()
await open({ url: '/api/files/photo.png', type: 'image', title: 'photo.png' })
const link = document.body.querySelector('.lightbox-tools a')
expect(link.getAttribute('href')).toBe('/api/files/photo.png')
expect(link.hasAttribute('download')).toBe(true)
})
})