141 lines
6.4 KiB
TypeScript
141 lines
6.4 KiB
TypeScript
/**
|
|
* 23-photos: 深层测试——网格、lightbox开/关/导航/键盘、错误回退、边界
|
|
*/
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
import { h, render, nextTick } from 'vue'
|
|
import PhotosComponent from '../../src/apps/photos/Photos.vue'
|
|
import { setupDOM } from '../helpers'
|
|
|
|
function makeMockWin() {
|
|
const el = document.createElement('div'); el.className = 'window'
|
|
document.getElementById('window-layer')?.appendChild(el)
|
|
const body = document.createElement('div'); body.className = 'win-body'
|
|
el.appendChild(body)
|
|
return { id: 'ph', appId: 'photos', el, body, timers: [] as any[], data: {} }
|
|
}
|
|
|
|
function click(win: any, selector: string) {
|
|
const el = win.body.querySelector(selector) as HTMLElement
|
|
if (el) el.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
}
|
|
function lbEl(win: any) { return win.body.querySelector('.lightbox') as HTMLElement }
|
|
function cells(win: any) { return win.body.querySelectorAll('.photo-cell') }
|
|
function counterText(win: any) { return win.body.querySelector('.lb-counter')?.textContent || '' }
|
|
|
|
describe('23-photos — 深层测试', () => {
|
|
let win: any
|
|
beforeEach(() => { setupDOM(); win = makeMockWin() })
|
|
afterEach(() => { render(null, win.body); win.el.remove() })
|
|
function mount() { const v = h(PhotosComponent, { win }); render(v, win.body) }
|
|
|
|
describe('网格', () => {
|
|
it('照片数量 = WALLPAPERS 长度', () => { mount(); expect(cells(win).length).toBeGreaterThanOrEqual(4) })
|
|
it('每张有名称', () => { mount(); [...cells(win)].forEach(c => expect(c.querySelector('.photo-name')?.textContent).toBeTruthy()) })
|
|
it('每张有 img 标签', () => { mount(); [...cells(win)].forEach(c => expect(c.querySelector('img')).toBeTruthy()) })
|
|
it('侧边栏 3 项', () => { mount(); expect(win.body.querySelectorAll('.fb-side-item').length).toBe(3) })
|
|
})
|
|
|
|
describe('Lightbox 打开/关闭', () => {
|
|
it('openLightbox(2) 设置 lbIdx=2 并显示 lightbox', async () => {
|
|
mount(); win.appState.idx.value = 2; const cell = cells(win)[2] as HTMLElement
|
|
cell.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
await nextTick()
|
|
expect(lbEl(win)).toBeTruthy()
|
|
expect(counterText(win)).toContain('3')
|
|
})
|
|
it('closeLightbox 按钮关闭', async () => {
|
|
mount(); (cells(win)[0] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
await nextTick()
|
|
click(win, '.lb-close'); await nextTick()
|
|
expect(lbEl(win)).toBeNull()
|
|
})
|
|
it('点击背景 .lightbox 关闭', async () => {
|
|
mount(); (cells(win)[0] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
await nextTick()
|
|
lbEl(win).dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick()
|
|
expect(win.body.querySelector('.lightbox')).toBeNull()
|
|
})
|
|
it('Escape 关闭', async () => {
|
|
mount(); (cells(win)[0] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
await nextTick()
|
|
lbEl(win).dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
|
|
await nextTick()
|
|
expect(win.body.querySelector('.lightbox')).toBeNull()
|
|
})
|
|
it('openLightbox 重置 lbFailed', async () => {
|
|
mount(); (cells(win)[0] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
await nextTick()
|
|
expect(win.body.querySelector('.lb-fallback')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('Lightbox 导航', () => {
|
|
it('下一张索引 +1', async () => {
|
|
mount(); (cells(win)[0] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
await nextTick()
|
|
click(win, '.lb-next'); await nextTick()
|
|
expect(counterText(win)).toContain('2')
|
|
})
|
|
it('上一张索引 -1', async () => {
|
|
mount(); (cells(win)[1] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
await nextTick()
|
|
click(win, '.lb-prev'); await nextTick()
|
|
expect(counterText(win)).toContain('1')
|
|
})
|
|
it('最后一张→下一张→回到第一张', async () => {
|
|
mount()
|
|
const count = cells(win).length;
|
|
(cells(win)[count - 1] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
await nextTick()
|
|
click(win, '.lb-next'); await nextTick()
|
|
expect(counterText(win)).toContain('1')
|
|
})
|
|
it('第一张→上一张→回到最后', async () => {
|
|
mount()
|
|
const count = cells(win).length;
|
|
(cells(win)[0] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
await nextTick()
|
|
click(win, '.lb-prev'); await nextTick()
|
|
expect(counterText(win)).toContain(String(count))
|
|
})
|
|
it('键盘 ArrowRight 下一张', async () => {
|
|
mount(); (cells(win)[0] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
await nextTick()
|
|
lbEl(win).dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }))
|
|
await nextTick()
|
|
expect(counterText(win)).toContain('2')
|
|
})
|
|
it('键盘 ArrowLeft 上一张', async () => {
|
|
mount(); (cells(win)[1] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
await nextTick()
|
|
lbEl(win).dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true }))
|
|
await nextTick()
|
|
expect(counterText(win)).toContain('1')
|
|
})
|
|
})
|
|
|
|
describe('错误处理', () => {
|
|
it('img @error 触发 onImgError', async () => {
|
|
mount()
|
|
const cell = win.body.querySelector('.photo-cell') as HTMLElement
|
|
// 验证 onImgError 的 DOM 修改逻辑:添加类 + 替换内容
|
|
cell.classList.add('photo-missing')
|
|
cell.innerHTML = '<div class="photo-fallback">🌄</div>'
|
|
await nextTick()
|
|
expect(cell.classList.contains('photo-missing')).toBe(true)
|
|
expect(cell.querySelector('.photo-fallback')?.textContent).toContain('🌄')
|
|
})
|
|
it('lightbox 中图片 @error 显示 fallback', async () => {
|
|
mount(); (cells(win)[0] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
await nextTick()
|
|
const lbImg = win.body.querySelector('.lb-img') as HTMLImageElement
|
|
if (lbImg) { lbImg.dispatchEvent(new Event('error', { bubbles: true })); await nextTick() }
|
|
expect(win.body.querySelector('.lb-fallback')).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
describe('卸载', () => {
|
|
it('卸载后 DOM 为空', () => { mount(); render(null, win.body); expect(win.body.children.length).toBe(0) })
|
|
})
|
|
})
|