101 lines
7.1 KiB
TypeScript
101 lines
7.1 KiB
TypeScript
/**
|
||
* 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) })
|
||
})
|