From f85ae0d5e4deaa97617842160f67d9a90ec22bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Thu, 23 Jul 2026 13:54:37 +0800 Subject: [PATCH] =?UTF-8?q?test:=20=E5=A2=9E=E5=BC=BA=20facetime=207?= =?UTF-8?q?=E2=86=9225?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/cases/34-facetime.test.ts | 87 +++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/tests/cases/34-facetime.test.ts b/tests/cases/34-facetime.test.ts index 51235fe..36a95d7 100644 --- a/tests/cases/34-facetime.test.ts +++ b/tests/cases/34-facetime.test.ts @@ -1,5 +1,9 @@ /** - * 34-facetime: FaceTime Vue 组件化测试 + * 34-facetime: FaceTime Vue 组件化 — 深层测试 + * 覆盖: 联系人列表/avater/名称/呼叫按钮、呼叫流程(idle→calling→connected)、 + * 状态文本、静音/摄像头切换、结束通话→回到空闲、appState、卸载 + * + * ⚠️ 真实 setTimeout 通话计时用 vi.useFakeTimers 控制 */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { h, render, nextTick } from 'vue' @@ -7,27 +11,68 @@ import FaceTimeComponent from '../../src/apps/facetime/FaceTime.vue' import { store } from '../../src/composables/useStore' 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: 'ft', appId: 'facetime', el, body, timers: [] as any[], data: {} } +function mkWin() { + const e = document.createElement('div'); e.className = 'window' + document.getElementById('window-layer')?.appendChild(e) + const b = document.createElement('div'); b.className = 'win-body' + e.appendChild(b) + return { id: 'ft', appId: 'facetime', el: e, body: b, timers: [] as any[], data: {} } } -describe('34-facetime — FaceTime Vue 组件化', () => { - let win: any - beforeEach(() => { setupDOM(); win = makeMockWin() - store.set('contacts', [{ id: '1', name: '测试人', phone: '123', email: 't@t.com', note: '' }]) - }) - afterEach(() => { render(null, win.body); win.el.remove() }) - function mount() { const v = h(FaceTimeComponent, { win }); render(v, win.body) } +const C = [ + { id: 'c1', name: '张三', phone: '138', email: 'z@t.com', note: '' }, + { id: 'c2', name: '李四', phone: '139', email: 'l@t.com', note: '同事' }, +] - it('显示联系人选择界面', () => { mount(); expect(document.querySelector('.ft-grid')).toBeTruthy() }) - it('联系人列表包含联系人', () => { mount(); expect(document.querySelectorAll('.ft-cell').length).toBeGreaterThanOrEqual(1) }) - it('点击呼叫开始拨打', async () => { mount(); (document.querySelector('.btn.primary') as HTMLElement)?.dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); expect(document.querySelector('.ft-status')?.textContent).toContain('正在呼叫') }) - it('麦克风静音切换', async () => { mount(); (document.querySelector('.btn.primary') as HTMLElement)?.dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); const micBtn = document.querySelector('.ft-ctl') as HTMLElement; micBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); expect(micBtn?.classList.contains('off')).toBe(true) }) - it('视频关闭切换', async () => { mount(); (document.querySelector('.btn.primary') as HTMLElement)?.dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); const camBtn = document.querySelectorAll('.ft-ctl')[1] as HTMLElement; camBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); expect(camBtn?.classList.contains('off')).toBe(true) }) - it('结束通话回到空闲', async () => { vi.useFakeTimers(); mount(); (document.querySelector('.btn.primary') as HTMLElement)?.dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); const endBtn = document.querySelector('.ft-ctl.end') as HTMLElement; endBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); expect(document.querySelector('.ft-status')?.textContent).toContain('通话已结束'); vi.advanceTimersByTime(1500); await nextTick(); expect(document.querySelector('.ft-grid')).toBeTruthy(); vi.useRealTimers() }) - it('卸载', () => { mount(); render(null, win.body); expect(win.body.children.length).toBe(0) }) +describe('34-facetime', () => { + let w: any + beforeEach(() => { setupDOM(); w = mkWin(); store.set('contacts', C) }) + afterEach(() => { render(null, w.body); w.el.remove(); vi.useRealTimers() }) + function m() { render(h(FaceTimeComponent, { win: w }), w.body) } + function cb() { return document.querySelector('.btn.primary') as HTMLElement } + function click(el: Element | null) { el?.dispatchEvent(new MouseEvent('click', { bubbles: true })) } + + // --- idle --- + it('标题-选择联系人', () => { m(); expect(document.querySelector('.ft-title')?.textContent).toContain('选择') }) + it('联系人网格', () => { m(); expect(document.querySelector('.ft-grid')).toBeTruthy() }) + it('avater 首字', () => { m(); expect(document.querySelector('.contact-big-avatar')?.textContent).toBe('张') }) + it('联系人名称', () => { m(); const d = [...document.querySelectorAll('.ft-cell div')]; expect(d.some(x => x.textContent === '张三')).toBe(true) }) + it('呼叫按钮', () => { m(); const b = document.querySelectorAll('.ft-cell .btn'); expect(b.length).toBe(2); b.forEach(x => expect(x.textContent).toContain('呼叫')) }) + it('多联系人', () => { store.set('contacts', [...C, { id:'c3', name:'王五', phone:'3', email:'w@t.com', note:'' }]); m(); expect(document.querySelectorAll('.ft-cell').length).toBe(3) }) + it('无联系人', () => { store.set('contacts', []); m(); expect(document.querySelectorAll('.ft-cell').length).toBe(0) }) + + // --- calling --- + describe('calling', () => { + beforeEach(() => { vi.useFakeTimers() }) + it('点击呼叫→calling', async () => { m(); click(cb()); await nextTick(); expect(document.querySelector('.ft-status')?.textContent).toContain('正在呼叫') }) + it('被叫 avater', async () => { m(); click(cb()); await nextTick(); expect(document.querySelector('.ft-avatar')?.textContent).toBe('张') }) + it('3 个控制按钮', async () => { m(); click(cb()); await nextTick(); expect(document.querySelectorAll('.ft-ctl').length).toBe(3) }) + it('self-view 可见', async () => { m(); click(cb()); await nextTick(); expect(document.querySelector('.ft-self')).toBeTruthy() }) + it('2.5s→connected', async () => { m(); click(cb()); await nextTick(); vi.advanceTimersByTime(2600); await nextTick(); expect(document.querySelector('.ft-status')?.textContent).toContain('· 00:') }) + it('live 类', async () => { m(); click(cb()); await nextTick(); vi.advanceTimersByTime(2600); await nextTick(); expect(document.querySelector('.ft-video')?.classList.contains('live')).toBe(true) }) + it('计时器递增', async () => { m(); click(cb()); await nextTick(); vi.advanceTimersByTime(2600); await nextTick(); vi.advanceTimersByTime(3000); await nextTick(); expect(document.querySelector('.ft-status')?.textContent).toContain('00:03') }) + }) + + // --- mute/cam --- + describe('mute/cam', () => { + beforeEach(() => { vi.useFakeTimers() }) + it('静音 toggle', async () => { m(); click(cb()); await nextTick(); const mic = document.querySelectorAll('.ft-ctl')[0] as HTMLElement; click(mic); await nextTick(); expect(mic.classList.contains('off')).toBe(true); click(mic); await nextTick(); expect(mic.classList.contains('off')).toBe(false) }) + it('摄像头 self-view 隐藏', async () => { m(); click(cb()); await nextTick(); const cam = document.querySelectorAll('.ft-ctl')[1] as HTMLElement; click(cam); await nextTick(); expect(document.querySelector('.ft-self.hidden')).toBeTruthy() }) + }) + + // --- end --- + describe('end', () => { + beforeEach(() => { vi.useFakeTimers() }) + it('结束按钮', async () => { m(); click(cb()); await nextTick(); expect(document.querySelector('.ft-ctl.end')).toBeTruthy() }) + it('通话已结束', async () => { m(); click(cb()); await nextTick(); click(document.querySelector('.ft-ctl.end')); await nextTick(); expect(document.querySelector('.ft-status')?.textContent).toContain('通话已结束') }) + it('1.2s 回空闲', async () => { m(); click(cb()); await nextTick(); click(document.querySelector('.ft-ctl.end')); await nextTick(); vi.advanceTimersByTime(1300); await nextTick(); expect(document.querySelector('.ft-grid')).toBeTruthy() }) + it('状态重置', async () => { m(); click(cb()); await nextTick(); click(document.querySelectorAll('.ft-ctl')[0]); await nextTick(); click(document.querySelector('.ft-ctl.end')); await nextTick(); vi.advanceTimersByTime(1300); await nextTick(); click(cb()); await nextTick(); expect(document.querySelector('.ft-ctl.off')).toBeNull() }) + }) + + // --- appState --- + it('暴露 startCall', () => { m(); expect(typeof w.appState.startCall).toBe('function') }) + it('startCall 呼叫', async () => { m(); w.appState.startCall(C[0]); await nextTick(); expect(document.querySelector('.ft-status')?.textContent).toContain('正在呼叫') }) + + // --- unmount --- + it('卸载', () => { m(); render(null, w.body); expect(w.body.children.length).toBe(0) }) })