diff --git a/tests/cases/44-podcasts.test.ts b/tests/cases/44-podcasts.test.ts new file mode 100644 index 0000000..5fa9a50 --- /dev/null +++ b/tests/cases/44-podcasts.test.ts @@ -0,0 +1,30 @@ +/** + * 44-podcasts: Podcasts Vue 组件化测试 + * 覆盖: 6 集播客列表、选中/播放、▶ 指示器、时长格式化、下一集、audio 错误容错、卸载 + * ⚠️ jsdom 不支持 audio.play(),验证 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, win: any + beforeEach(() => { + setupDOM(); const 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); win.el.remove() }) + function m() { render(h(PodcastsComponent, { win }), body) } + + it('6 集播客', () => { m(); expect(body.querySelectorAll('.music-row').length).toBe(6) }) + it('每集有序号和标题', () => { m(); const first = body.querySelector('.music-row') as HTMLElement; expect(first.querySelector('.music-num')?.textContent).toBe('1'); expect(first.querySelector('b')?.textContent).toContain('Monkeys') }) + it('每集有时长', () => { m(); expect(body.querySelector('.music-dur')?.textContent).toMatch(/^\d+:\d{2}$/) }) + it('点击选中显示 ▶', async () => { m(); const row = body.querySelector('.music-row') as HTMLElement; row.dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); expect(body.querySelector('.music-row.sel')).toBeTruthy() }) + it('底部栏显示当前集名', async () => { m(); const row = body.querySelector('.music-row') as HTMLElement; row.dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); expect(body.querySelector('.pod-bar')?.textContent).toContain('Monkeys') }) + it('再次点击暂停', async () => { m(); const row = body.querySelector('.music-row') as HTMLElement; row.dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); row.dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); /* 不抛错 */ }) + it('时长格式化 22→0:22', () => { m(); expect(body.querySelector('.music-dur')?.textContent).toBe('0:22') }) + it('卸载', () => { m(); render(null, body); expect(body.children.length).toBe(0) }) +}) diff --git a/tests/cases/45-news.test.ts b/tests/cases/45-news.test.ts new file mode 100644 index 0000000..fd3d313 --- /dev/null +++ b/tests/cases/45-news.test.ts @@ -0,0 +1,21 @@ +/** + * 45-news: Apple News Vue 组件化测试 + * 覆盖: 4 篇文章卡片、分类/标题/描述、卸载 + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { h, render } from 'vue' +import NewsComponent from '../../src/apps/news/News.vue' +import { setupDOM } from '../helpers' + +describe('45-news', () => { + let body: HTMLElement + beforeEach(() => { setupDOM(); const el = document.createElement('div'); el.className = 'window'; document.body.appendChild(el); body = document.createElement('div'); body.className = 'win-body'; el.appendChild(body); (window as any).WM = { setTitle() {} } }) + afterEach(() => { render(null, body); (body.parentElement as HTMLElement)?.remove() }) + function m() { render(h(NewsComponent, {}), body) } + + it('4 篇文章', () => { m(); expect(body.querySelectorAll('.news-card').length).toBe(4) }) + it('每篇有分类标签', () => { m(); expect(body.querySelector('.news-cat')?.textContent).toBeTruthy() }) + it('每篇有标题', () => { m(); expect(body.querySelector('.news-title')?.textContent).toContain('macOS') }) + it('每篇有描述', () => { m(); expect(body.querySelector('.news-desc')?.textContent).toBeTruthy() }) + it('卸载', () => { m(); render(null, body); expect(body.children.length).toBe(0) }) +}) diff --git a/tests/cases/46-github.test.ts b/tests/cases/46-github.test.ts new file mode 100644 index 0000000..af08945 --- /dev/null +++ b/tests/cases/46-github.test.ts @@ -0,0 +1,23 @@ +/** + * 46-github: GitHub Desktop Vue 组件化测试 + * 覆盖: 仓库名/徽章(★1024,⑂128)、README 内容、代码块、卸载 + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { h, render } from 'vue' +import GitHubComponent from '../../src/apps/github/GitHub.vue' +import { setupDOM } from '../helpers' + +describe('46-github', () => { + let body: HTMLElement + beforeEach(() => { setupDOM(); const el = document.createElement('div'); el.className = 'window'; document.body.appendChild(el); body = document.createElement('div'); body.className = 'win-body'; el.appendChild(body); (window as any).WM = { setTitle() {} } }) + afterEach(() => { render(null, body); (body.parentElement as HTMLElement)?.remove() }) + function m() { render(h(GitHubComponent, {}), body) } + + it('仓库名显示', () => { m(); expect(body.querySelector('.gh-repo')?.textContent).toContain('macos-web') }) + it('Star 徽章 1024', () => { m(); expect(body.querySelector('.gh-badge')?.textContent).toContain('1024') }) + it('Fork 徽章 128', () => { m(); const badges = body.querySelectorAll('.gh-badge'); expect(badges[1]?.textContent).toContain('128') }) + it('README 标题', () => { m(); expect(body.querySelector('.gh-readme h2')?.textContent).toBe('macos-web') }) + it('代码块存在', () => { m(); expect(body.querySelector('.sf-code')).toBeTruthy() }) + it('特性列表', () => { m(); expect(body.querySelector('.gh-readme')?.textContent).toContain('窗口管理器') }) + it('卸载', () => { m(); render(null, body); expect(body.children.length).toBe(0) }) +}) diff --git a/tests/cases/47-tv.test.ts b/tests/cases/47-tv.test.ts new file mode 100644 index 0000000..329561c --- /dev/null +++ b/tests/cases/47-tv.test.ts @@ -0,0 +1,30 @@ +/** + * 47-tv: Apple TV Vue 组件化测试 + * 覆盖: 3 个视频卡片/海报/标题、播放/player 显示/隐藏、video source、卸载 + * ⚠️ jsdom 不支持 video.play(),验证 DOM 状态变化 + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { h, render, nextTick } from 'vue' +import TVComponent from '../../src/apps/tv/TV.vue' +import { setupDOM } from '../helpers' + +describe('47-tv', () => { + let body: HTMLElement, win: any + beforeEach(() => { + setupDOM(); const 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 = () => ({ registerMedia: () => {}, unregisterMedia: () => {} }) + }) + afterEach(() => { render(null, body); win.el.remove() }) + function m() { render(h(TVComponent, { win }), body) } + + it('3 个视频卡片', () => { m(); expect(body.querySelectorAll('.tv-card').length).toBe(3) }) + it('每张有海报', () => { m(); expect(body.querySelector('.tv-poster')).toBeTruthy() }) + it('每张有标题', () => { m(); expect(body.querySelector('.tv-name')?.textContent).toBeTruthy() }) + it('player 初始隐藏', () => { m(); expect(body.querySelector('.tv-player')?.classList.contains('hidden')).toBe(true) }) + it('点击卡片显示 player', async () => { m(); const card = body.querySelector('.tv-card') as HTMLElement; card.dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); expect(body.querySelector('.tv-player')?.classList.contains('hidden')).toBe(false) }) + it('player 显示视频标题', async () => { m(); (body.querySelector('.tv-card') as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); expect(body.querySelector('.tv-ptitle')?.textContent).toBeTruthy() }) + it('video 元素存在', async () => { m(); (body.querySelector('.tv-card') as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); expect(body.querySelector('video')).toBeTruthy() }) + it('卸载', () => { m(); render(null, body); expect(body.children.length).toBe(0) }) +})