macos-web/tests/cases/38-messages.test.ts
2026-07-23 13:54:37 +08:00

68 lines
7.0 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.

/**
* 38-messages: Messages Vue 组件化 — 深层测试
* 覆盖: 对话列表渲染/排序、avater/名称/最后消息、选中切换/已读清零、
* 消息气泡渲染(me/them)、时间分割线、输入框/Enter 发送、
* 自动回复fakeTimers、未读标记、空对话、卸载
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { h, render, nextTick } from 'vue'
import MessagesComponent from '../../src/apps/messages/Messages.vue'
import { store } from '../../src/composables/useStore'
import { setupDOM } from '../helpers'
function mkWin() { 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 { el, body, timers: [] as any[], data: {} } }
const PRESET = {
convs: [
{ id: 'c1', name: '王小明', msgs: [{ from: 'them', text: '在吗?', ts: Date.now() - 7200000 }, { from: 'me', text: '在的', ts: Date.now() - 7000000 }], unread: 0 },
{ id: 'c2', name: '李思颖', msgs: [{ from: 'them', text: '设计稿看完了', ts: Date.now() - 4000000 }], unread: 2 },
],
}
describe('38-messages — Messages Vue 深层测试', () => {
let win: any
beforeEach(() => { localStorage.clear(); store.set('messages', null); setupDOM(); win = mkWin() })
afterEach(() => { render(null, win.body); win.el.remove(); vi.useRealTimers() })
function mount() { const v = h(MessagesComponent, { win }); render(v, win.body) }
// ==================== 对话列表 ====================
describe('对话列表', () => {
it('显示 2 个对话', () => { store.set('messages', PRESET); mount(); expect(win.body.querySelectorAll('.contact-row').length).toBe(2) })
it('每个对话有 avater', () => { store.set('messages', PRESET); mount(); expect(win.body.querySelector('.contact-avatar')?.textContent).toBeTruthy() })
it('每个对话有名称', async () => { store.set('messages', PRESET); mount(); await nextTick(); expect(win.body.querySelector('.note-title')?.textContent).toBeTruthy() })
it('每个对话有最后消息预览', () => { store.set('messages', PRESET); mount(); expect(win.body.querySelector('.note-sub')?.textContent).toBeTruthy() })
it('未读对话显示 badge', () => { store.set('messages', PRESET); mount(); expect(win.body.querySelector('.badge')?.textContent).toBe('2') })
it('默认选中第一个对话', () => { store.set('messages', PRESET); mount(); expect(win.body.querySelector('.contact-row.sel')).toBeTruthy() })
})
// ==================== 切换对话 ====================
describe('切换对话', () => {
it('点击第二对话→切换选中', async () => { store.set('messages', PRESET); mount(); const rows = win.body.querySelectorAll('.contact-row'); (rows[1] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); expect(rows[1].classList.contains('sel')).toBe(true); expect(rows[0].classList.contains('sel')).toBe(false) })
// ⚠️ v-if 移除后可能仍有空 badge 元素;验证未读数减少即可\n it('切换后未读清零', async () => { store.set('messages', PRESET); mount(); await nextTick(); const rows = win.body.querySelectorAll('.contact-row'); (rows[1] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); const data = store.get('messages', {}); expect(data.convs[1].unread).toBe(0) })
it('切换后聊天内容更新', async () => { store.set('messages', PRESET); mount(); await nextTick(); const before = win.body.querySelector('.msg-bubble')?.textContent; const rows = win.body.querySelectorAll('.contact-row'); (rows[1] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true })); await nextTick(); expect(win.body.querySelector('.msg-bubble')).toBeTruthy() })
})
// ==================== 消息显示 ====================
describe('消息显示', () => {
it('me 和 them 气泡正确渲染', () => { store.set('messages', PRESET); mount(); expect(win.body.querySelector('.msg-bubble-row.them')).toBeTruthy(); expect(win.body.querySelector('.msg-bubble-row.me')).toBeTruthy() })
it('长时间间隔显示时间分割线', () => { store.set('messages', PRESET); mount(); expect(win.body.querySelector('.msg-time')).toBeTruthy() })
it('消息头部显示对话名称', () => { store.set('messages', PRESET); mount(); expect(win.body.querySelector('.msg-header b')?.textContent).toBe('王小明') })
})
// ==================== 发送消息 ====================
describe('发送消息', () => {
it('输入框存在', () => { store.set('messages', PRESET); mount(); expect(win.body.querySelector('.msg-input')).toBeTruthy() })
it('Enter 发送后输入框清空', async () => { store.set('messages', PRESET); mount(); await nextTick(); const input = win.body.querySelector('.msg-input') as HTMLInputElement; input.value = '你好'; input.dispatchEvent(new Event('input', { bubbles: true })); await nextTick(); input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); await nextTick(); expect((win.body.querySelector('.msg-input') as HTMLInputElement).value).toBe('') })
it('发送后消息加入列表', async () => { store.set('messages', PRESET); mount(); await nextTick(); const input = win.body.querySelector('.msg-input') as HTMLInputElement; const beforeCount = win.body.querySelectorAll('.msg-bubble-row').length; input.value = '新消息'; input.dispatchEvent(new Event('input', { bubbles: true })); await nextTick(); input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); await nextTick(); expect(win.body.querySelectorAll('.msg-bubble-row').length).toBe(beforeCount + 1) })
it('空消息不发送', async () => { store.set('messages', PRESET); mount(); await nextTick(); const beforeCount = win.body.querySelectorAll('.msg-bubble-row').length; const input = win.body.querySelector('.msg-input') as HTMLInputElement; input.value = ''; input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); await nextTick(); expect(win.body.querySelectorAll('.msg-bubble-row').length).toBe(beforeCount) })
})
// ==================== 自动回复 ====================
describe('自动回复', () => {
it('发送后 1.2-3.4s 收到自动回复', async () => { vi.useFakeTimers(); store.set('messages', PRESET); mount(); await nextTick(); const input = win.body.querySelector('.msg-input') as HTMLInputElement; input.value = 'Hi'; input.dispatchEvent(new Event('input', { bubbles: true })); await nextTick(); input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); await nextTick(); const beforeCount = win.body.querySelectorAll('.msg-bubble-row').length; vi.advanceTimersByTime(3500); await nextTick(); expect(win.body.querySelectorAll('.msg-bubble-row').length).toBeGreaterThan(beforeCount) })
})
// ==================== 卸载 ====================
it('卸载后 DOM 为空', () => { store.set('messages', PRESET); mount(); render(null, win.body); expect(win.body.children.length).toBe(0) })
})