feat: 实时刷新档案会话列表

收到新消息后增量更新会话摘要和时间

自动按最新消息重新排序并保留当前选择状态
This commit is contained in:
Wxw-Gu
2026-09-13 11:15:30 +08:00
parent add16885fe
commit cef1ad493c
4 changed files with 190 additions and 5 deletions
+63
View File
@@ -276,6 +276,69 @@ test('CHAT-REALTIME-01 archive refreshes from a native message-shard event', asy
}
})
test('CHAT-REALTIME-02 refreshes and reorders conversations without losing selection or filter', async () => {
const fixture = await launchTestApp({ realtimeReorder: true })
try {
const names = fixture.page.locator('.conversation-item-name')
await expect(names).toHaveText(['产品测试群', '自动刷新 B', '自动刷新 A'])
await fixture.page.getByRole('button', { name: /自动刷新 A/ }).click()
await fixture.page.getByRole('searchbox', { name: '搜索会话' }).fill('自动刷新')
await expect(fixture.page.getByRole('searchbox', { name: '搜索会话' })).toHaveValue('自动刷新')
await expect(fixture.page.locator('.conversation-item-name')).toHaveText([
'自动刷新 B',
'自动刷新 A',
'自动刷新 D',
'自动刷新 C'
])
const readsBeforeMessage = await fixture.page.evaluate(() =>
window.electron.ipcRenderer.invoke('test:getContactReadCount')
)
await fixture.page.evaluate(() =>
window.electron.ipcRenderer.invoke('test:messageChange', {
reorderContactMd5: 'realtime-group-a-md5',
event: { db: 'message_0.db', table: 'message', action: 'insert' }
})
)
await expect
.poll(async () => fixture.page.locator('.conversation-item-name').allTextContents())
.toEqual(['自动刷新 A', '自动刷新 B', '自动刷新 D', '自动刷新 C'])
expect(await fixture.page.getByRole('searchbox', { name: '搜索会话' }).inputValue()).toBe(
'自动刷新'
)
await expect(
fixture.page.locator('.conversation-item.active .conversation-item-name')
).toHaveText('自动刷新 A')
const readsAfterSingleMessage = await fixture.page.evaluate(() =>
window.electron.ipcRenderer.invoke('test:getContactReadCount')
)
expect(readsAfterSingleMessage - readsBeforeMessage).toBe(1)
await Promise.all(
Array.from({ length: 50 }, () =>
fixture.page.evaluate(() =>
window.electron.ipcRenderer.invoke('test:messageChange', {
reorderContactMd5: 'realtime-direct-c-md5',
event: { db: 'message_0.db', table: 'message', action: 'insert' }
})
)
)
)
await expect
.poll(async () => fixture.page.locator('.conversation-item-name').allTextContents())
.toEqual(['自动刷新 A', '自动刷新 B', '自动刷新 C', '自动刷新 D'])
const readsAfterBurst = await fixture.page.evaluate(() =>
window.electron.ipcRenderer.invoke('test:getContactReadCount')
)
expect(readsAfterBurst - readsAfterSingleMessage).toBe(1)
await expect(fixture.page.locator('.conversation-item-name')).toHaveCount(4)
} finally {
await fixture.close()
}
})
test('CHAT-02 personal WeChat send dialog is keyboard-safe and fits the viewport', async () => {
test.skip(process.platform !== 'darwin', 'Personal WeChat sending is currently macOS-only')
const fixture = await launchTestApp()
+39
View File
@@ -230,12 +230,41 @@ for (let index = 0; index < extraContacts; index += 1) {
type: index % 5 === 0 ? 'group' : 'user'
})
}
if (process.env.WXE_E2E_REALTIME_REORDER === '1') {
contacts.push(
{
m_nsUsrName: 'realtime_group_b@chatroom',
m_nsNickName: '自动刷新 B',
md5: 'realtime-group-b-md5',
type: 'group'
},
{
m_nsUsrName: 'realtime_group_a@chatroom',
m_nsNickName: '自动刷新 A',
md5: 'realtime-group-a-md5',
type: 'group'
},
{
m_nsUsrName: 'realtime_direct_d',
m_nsNickName: '自动刷新 D',
md5: 'realtime-direct-d-md5',
type: 'user'
},
{
m_nsUsrName: 'realtime_direct_c',
m_nsNickName: '自动刷新 C',
md5: 'realtime-direct-c-md5',
type: 'user'
}
)
}
const handlers = new Map()
const handle = (channel, fn) => {
handlers.set(channel, fn)
ipcMain.handle(channel, async (event, ...args) => fn(...args))
}
let contactReadCount = 0
const groupExitEvent = {
id: 'fixture-exit-event',
@@ -658,11 +687,13 @@ handle('db:getBootstrapCache', () =>
process.env.WXE_E2E_CORRUPT_CACHE === '1' ? null : startupCache()
)
handle('db:getContacts', (filter) => {
contactReadCount += 1
const query = String(filter || '').toLowerCase()
return query
? contacts.filter((contact) => contact.m_nsNickName.toLowerCase().includes(query))
: contacts
})
handle('test:getContactReadCount', () => contactReadCount)
handle('db:getContactAvatars', (usernames) =>
Object.fromEntries(
contacts
@@ -690,6 +721,14 @@ handle('test:messageChange', (payload = {}) => {
if (md5 && message && Array.isArray(fixture.messages[md5])) {
fixture.messages[md5].push(message)
}
const reorderContactMd5 = String(payload.reorderContactMd5 || '')
if (reorderContactMd5) {
const index = contacts.findIndex((contact) => contact.md5 === reorderContactMd5)
if (index >= 0) {
const [contact] = contacts.splice(index, 1)
contacts.unshift(contact)
}
}
const event = payload.event || { db: 'message_0.db', table: 'message', action: 'update' }
const json = typeof event === 'string' ? event : JSON.stringify(event)
for (const window of BrowserWindow.getAllWindows()) {
+2
View File
@@ -22,6 +22,7 @@ export async function launchTestApp(
aiFailure?: string
updateSimulation?: boolean
unsignedMacUpdate?: boolean
realtimeReorder?: boolean
now?: number
appearanceTheme?: 'light' | 'dark'
stableUserData?: string
@@ -52,6 +53,7 @@ export async function launchTestApp(
WXE_E2E_AI_FAILURE: options.aiFailure || '',
WXE_E2E_UPDATE_SIMULATION: options.updateSimulation ? '1' : '0',
WXE_E2E_UNSIGNED_MAC_UPDATE: options.unsignedMacUpdate ? '1' : '0',
WXE_E2E_REALTIME_REORDER: options.realtimeReorder ? '1' : '0',
WXE_E2E_NOW_MS: options.now ? String(options.now) : '',
WXE_E2E_APPEARANCE_THEME: options.appearanceTheme || 'light'
}