/** * 44-podcasts: 播客 — 深层测试 * 覆盖: DOM(3)+列表(5)+播放(4)+音频(3)+边界(3)+卸载 * ⚠️ audio.play/pause jsdom 不支持,验证 DOM 状态 */ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { h, render, nextTick } from 'vue' import PodcastsComponent from '../../src/apps/podcasts/Podcasts.vue' import { setupDOM } from '../helpers' describe('44-podcasts', () => { 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: {} }; (window as any).Sys = () => ({ unregisterMedia: () => {} }) }) afterEach(() => { render(null, body); el.remove() }) function m() { render(h(PodcastsComponent, { win }), body) } function row(i: number) { return body.querySelectorAll('.music-row')[i] as HTMLElement } function clickRow(i: number) { row(i)?.dispatchEvent(new MouseEvent('click', { bubbles: true })) } // --- DOM --- it('music-body 容器', () => { m(); expect(body.querySelector('.music-body')).toBeTruthy() }) it('pod-bar 存在', () => { m(); expect(body.querySelector('.pod-bar')).toBeTruthy() }) it('audio 元素存在', () => { m(); expect(body.querySelector('audio')).toBeTruthy() }) // --- 列表 --- it('6 集播客', () => { m(); expect(body.querySelectorAll('.music-row').length).toBe(6) }) it('每集有序号', () => { m(); [...body.querySelectorAll('.music-num')].forEach((n, i) => { if (i < 6) expect(n.textContent).toBe(String(i + 1)) }) }) it('每集有标题', () => { m(); [...body.querySelectorAll('.music-row b')].forEach(b => expect(b.textContent).toBeTruthy()) }) it('每集有时长', () => { m(); [...body.querySelectorAll('.music-dur')].forEach(d => expect(d.textContent).toMatch(/^\d+:\d{2}$/)) }) it('每集有 show 名', () => { m(); [...body.querySelectorAll('.music-row small')].forEach(s => expect(s.textContent).toContain('Web 桌面谈')) }) // --- 播放 --- it('点击选中→高亮', async () => { m(); clickRow(0); await nextTick(); expect(row(0).classList.contains('sel')).toBe(true) }) it('选中后高亮变化', async () => { m(); clickRow(0); await nextTick(); expect(row(0).classList.contains('sel')).toBe(true) }) it('底部栏显示当前集名', async () => { m(); clickRow(0); await nextTick(); expect(body.querySelector('.pod-bar')?.textContent).toContain('Monkeys') }) it('切换集更新底部栏', async () => { m(); clickRow(0); await nextTick(); clickRow(1); await nextTick(); expect(body.querySelector('.pod-bar')?.textContent).toContain('Fluffing') }) // --- 音频 --- it('点击暂停切换', async () => { m(); clickRow(0); await nextTick(); clickRow(0); await nextTick(); /* play→pause 不抛错 */ }) it('播放下一集', async () => { m(); clickRow(5); await nextTick(); /* audio ended→next 不抛错 */ }) it('时长格式化', () => { m(); expect(body.querySelector('.music-dur')?.textContent).toBe('0:22') }) // --- 边界 --- it('快速双击不崩', async () => { m(); clickRow(0); clickRow(0); await nextTick() }) it('选中集内再次点击不切换', async () => { m(); clickRow(0); await nextTick(); clickRow(0); await nextTick(); expect(row(0).classList.contains('sel')).toBe(true) }) it('底部栏初始状态', () => { m(); expect(body.querySelector('.pod-bar')?.textContent).toContain('选择一集') }) // --- 卸载 --- it('卸载后 DOM 为空', () => { m(); render(null, body); expect(body.children.length).toBe(0) }) })