/** * 43-keynote: Keynote — 深层测试 * 覆盖: 缩略图(3)+舞台(2)+导航(5)+播放(5)+边界(3)+卸载 */ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { h, render, nextTick } from 'vue' import KeynoteComponent from '../../src/apps/keynote/Keynote.vue' import { setupDOM } from '../helpers' describe('43-keynote', () => { let body: HTMLElement, el: HTMLElement, win: any beforeEach(() => { setupDOM(); el = document.createElement('div'); el.className = 'window'; document.body.appendChild(el); body = document.createElement('div'); body.className = 'win-body'; el.appendChild(body); win = { el, body, timers: [], data: {} } }) afterEach(() => { render(null, body); el.remove() }) function m() { render(h(KeynoteComponent, { win }), body) } function title() { return body.querySelector('.kn-title')?.textContent || '' } function clickThumb(i: number) { (body.querySelectorAll('.kn-thumb')[i] as HTMLElement)?.dispatchEvent(new MouseEvent('click', { bubbles: true })) } function key(k: string) { el.dispatchEvent(new KeyboardEvent('keydown', { key: k })) } // --- 缩略图 --- it('4 张幻灯片', () => { m(); expect(body.querySelectorAll('.kn-thumb').length).toBe(4) }) it('每张有标题', () => { m(); body.querySelectorAll('.kn-thumb').forEach(t => expect(t.textContent).toBeTruthy()) }) it('每张有渐变色背景', () => { m(); body.querySelectorAll('.kn-thumb').forEach(t => expect((t as HTMLElement).style.background).toBeTruthy()) }) // --- 舞台 --- it('kn-slide 存在', () => { m(); expect(body.querySelector('.kn-slide')).toBeTruthy() }) it('默认标题含 macOS', () => { m(); expect(title()).toContain('macOS') }) // --- 导航 --- it('点击缩略图切换', async () => { m(); clickThumb(1); await nextTick(); expect(title()).toContain('虚拟文件') }) it('ArrowRight→下一张', async () => { m(); await nextTick(); key('ArrowRight'); await nextTick(); expect(title()).toContain('虚拟文件') }) it('ArrowLeft→上一张', async () => { m(); await nextTick(); key('ArrowRight'); await nextTick(); key('ArrowLeft'); await nextTick(); expect(title()).toContain('macOS') }) it('到最后一张不越界', async () => { m(); await nextTick(); for (let i = 0; i < 10; i++) key('ArrowRight'); await nextTick(); expect(title()).toContain('谢谢观看') }) it('到第一张不越界', async () => { m(); await nextTick(); for (let i = 0; i < 10; i++) key('ArrowUp'); await nextTick(); expect(title()).toContain('macOS') }) // --- 播放 --- it('appState.play 存在', () => { m(); expect(typeof win.appState.play).toBe('function') }) it('play→全屏显示', async () => { m(); win.appState.play(); await nextTick(); expect(body.querySelector('.kn-play')).toBeTruthy() }) it('播放中 Space→翻页', async () => { m(); win.appState.play(); await nextTick(); (body.querySelector('.kn-play') as HTMLElement).dispatchEvent(new KeyboardEvent('keydown', { key: ' ' })); await nextTick(); expect(body.querySelector('.kn-play .kn-title')?.textContent).toContain('虚拟文件') }) it('Escape→退出播放', async () => { m(); win.appState.play(); await nextTick(); (body.querySelector('.kn-play') as HTMLElement).dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await nextTick(); expect(body.querySelector('.kn-play')).toBeNull() }) it('翻到最后播放结束', async () => { m(); win.appState.play(); await nextTick(); const play = body.querySelector('.kn-play') as HTMLElement; for (let i = 0; i < 5; i++) { play.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' })); await nextTick() }; expect(body.querySelector('.kn-play')).toBeNull() }) // --- 边界 --- it('播放时键盘导航被拦截', async () => { m(); win.appState.play(); await nextTick(); key('ArrowRight'); await nextTick(); expect(title()).toContain('macOS') }) it('非播放时 Space 不触发', async () => { m(); await nextTick(); el.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' })); await nextTick(); expect(title()).toContain('macOS') }) it('卸载时移除 eventListener', () => { m(); render(null, body); expect(() => el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }))).not.toThrow() }) // --- 卸载 --- it('卸载后 DOM 为空', () => { m(); render(null, body); expect(body.children.length).toBe(0) }) })