macos-web/tests/cases/34-facetime.test.ts

101 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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'
import FaceTimeComponent from '../../src/apps/facetime/FaceTime.vue'
import { store } from '../../src/composables/useStore'
import { setupDOM } from '../helpers'
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: {} }
}
const C = [
{ id: 'c1', name: '张三', phone: '138', email: 'z@t.com', note: '' },
{ id: 'c2', name: '李四', phone: '139', email: 'l@t.com', note: '同事' },
]
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('正在呼叫') })
// --- integration: store 空时不预设数据,模拟真实首次打开 ---
describe('store 集成', () => {
it('store 有数据时显示联系人', () => { m(); expect(document.querySelector('.ft-cell')).toBeTruthy() })
// ⚠️ 回归:真实流程 Contacts mount→persist→FaceTime mount 可读
it('Contacts mount 后 FaceTime 可读取', async () => {
// 模拟真实流程:先 mount Contacts 写 store再 mount FaceTime 读
const ContactsComponent = (await import('../../src/apps/contacts/Contacts.vue')).default
const cEl = document.createElement('div'); cEl.className = 'window'; document.body.appendChild(cEl)
const cBody = document.createElement('div'); cBody.className = 'win-body'; cEl.appendChild(cBody)
const cWin = { id: 'ct', appId: 'contacts', el: cEl, body: cBody, timers: [], data: {} }
render(h(ContactsComponent, { win: cWin }), cBody)
await nextTick()
// 验证 Contacts 已 persist
const contacts = store.get('contacts', [])
expect(contacts.length).toBeGreaterThanOrEqual(1)
// FaceTime 应能读取
render(null, w.body); m()
expect(document.querySelector('.ft-cell')).toBeTruthy()
render(null, cBody); cEl.remove()
})
})
// --- unmount ---
it('卸载', () => { m(); render(null, w.body); expect(w.body.children.length).toBe(0) })
})