mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-17 03:27:00 +08:00
feat: 优化聊天档案移动端与账号切换体验
- 限制移动端横向滚动并修复发送消息头像裁切 - 调整顶部搜索、筛选与独立计数布局,七类筛选保持单行 - 移除聊天选项消息数和顶部更新时间,简化单聊天展示 - 使用头像、名称、切换图标与自定义弹层替代原生账号下拉控件 - 补充桌面及移动端的布局、切换和无横向溢出回归测试
This commit is contained in:
@@ -91,8 +91,6 @@ body {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.title { font-size: 18px; font-weight: 750; }
|
||||
.meta { color: var(--muted); margin-left: 12px; font-size: 13px; }
|
||||
.archive-heading .meta { margin-left: 0; }
|
||||
.controls { display: flex; gap: 8px; align-items: center; justify-content: flex-end; }
|
||||
.controls input, .filter-button {
|
||||
border: 1px solid var(--border);
|
||||
@@ -137,47 +135,141 @@ body {
|
||||
.conversation-filter {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex: 0 1 320px;
|
||||
min-width: 210px;
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
height: 42px;
|
||||
border-radius: 10px;
|
||||
transition: background .15s ease;
|
||||
}
|
||||
.conversation-filter[hidden] { display: none; }
|
||||
.conversation-filter::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 50%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-right: 2px solid var(--accent);
|
||||
border-bottom: 2px solid var(--accent);
|
||||
transform: translateY(-65%) rotate(45deg);
|
||||
pointer-events: none;
|
||||
.conversation-trigger {
|
||||
display: flex;
|
||||
width: auto;
|
||||
max-width: 280px;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
padding: 5px 9px 5px 6px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: background .15s ease;
|
||||
}
|
||||
.conversation-select {
|
||||
appearance: none;
|
||||
.conversation-trigger:hover, .conversation-trigger[aria-expanded=true] { background: #edf5f1; }
|
||||
.conversation-trigger:focus-visible { outline: 2px solid #5b9785; outline-offset: 1px; }
|
||||
.conversation-trigger-avatar {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
flex: 0 0 30px;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
place-items: center;
|
||||
background: #dcebe4;
|
||||
color: var(--accent);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.conversation-trigger-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.conversation-trigger-avatar.multiple { overflow: visible; background: transparent; }
|
||||
.conversation-trigger-avatar.multiple img {
|
||||
position: absolute;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
border: 2px solid #f7fbf9;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.conversation-trigger-avatar.multiple img:first-child { top: 0; left: 0; }
|
||||
.conversation-trigger-avatar.multiple img:last-child { right: 0; bottom: 0; }
|
||||
.conversation-trigger-name {
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
color: var(--text);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.conversation-switch-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex: 0 0 14px;
|
||||
color: #5c6963;
|
||||
stroke-width: 2;
|
||||
}
|
||||
.conversation-menu {
|
||||
position: absolute;
|
||||
z-index: 12;
|
||||
top: calc(100% + 7px);
|
||||
left: 0;
|
||||
width: max(100%, 240px);
|
||||
max-width: calc(100vw - 32px);
|
||||
max-height: min(360px, 55vh);
|
||||
overflow-y: auto;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
padding: 6px;
|
||||
background: #fff;
|
||||
box-shadow: 0 10px 30px #19392d2b, 0 2px 8px #19392d14;
|
||||
}
|
||||
.conversation-menu[hidden] { display: none; }
|
||||
.conversation-option {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 42px;
|
||||
padding: 0 42px 0 14px;
|
||||
border: 1px solid #b8cec5;
|
||||
border-radius: 10px;
|
||||
outline: 0;
|
||||
background: #f7fbf9;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
padding: 8px;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 8px #29483b12;
|
||||
transition: border-color .15s ease, background .15s ease, box-shadow .15s ease;
|
||||
}
|
||||
.conversation-select:hover { border-color: #70a392; background: #fff; }
|
||||
.conversation-select:focus {
|
||||
border-color: var(--accent);
|
||||
background: #fff;
|
||||
box-shadow: 0 0 0 3px #176b571c;
|
||||
.conversation-option:hover, .conversation-option:focus-visible { background: #f0f6f3; outline: 0; }
|
||||
.conversation-option[aria-selected=true] { background: var(--accent-soft); color: var(--accent); }
|
||||
.conversation-option-avatar {
|
||||
display: grid;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
flex: 0 0 30px;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
place-items: center;
|
||||
background: #dcebe4;
|
||||
color: var(--accent);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.conversation-option-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.conversation-option-avatar.multiple { position: relative; overflow: visible; background: transparent; }
|
||||
.conversation-option-avatar.multiple img {
|
||||
position: absolute;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.conversation-option-avatar.multiple img:first-child { top: 0; left: 0; }
|
||||
.conversation-option-avatar.multiple img:last-child { right: 0; bottom: 0; }
|
||||
.conversation-option-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.conversation-option-check { width: 16px; flex: 0 0 16px; color: var(--accent); text-align: center; }
|
||||
.timeline {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
@@ -583,7 +675,6 @@ body {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.archive-heading .meta { display: none; }
|
||||
.conversation-filter:not([hidden]) {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
@@ -592,20 +683,14 @@ body {
|
||||
flex: 1 1 auto;
|
||||
height: 36px;
|
||||
}
|
||||
.conversation-filter::after {
|
||||
right: 10px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
.conversation-select {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 36px;
|
||||
padding: 0 24px 0 8px;
|
||||
border-radius: 9px;
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
box-shadow: none;
|
||||
.conversation-trigger { width: 100%; max-width: none; gap: 6px; padding: 4px 6px 4px 4px; }
|
||||
.conversation-trigger-avatar { width: 26px; height: 26px; flex-basis: 26px; border-radius: 7px; }
|
||||
.conversation-trigger-avatar.multiple img { width: 18px; height: 18px; border-radius: 5px; }
|
||||
.conversation-trigger-name { flex: 1; max-width: none; font-size: 12px; }
|
||||
.conversation-switch-icon { width: 13px; height: 13px; flex-basis: 13px; }
|
||||
.conversation-menu {
|
||||
width: min(250px, calc(100vw - 20px));
|
||||
max-width: none;
|
||||
}
|
||||
.controls { grid-column: 2; min-width: 0; justify-content: flex-start; }
|
||||
.controls input[type=search] {
|
||||
@@ -736,11 +821,13 @@ const renderExportScript = (name: string): string => `
|
||||
const list = document.querySelector('#messages')
|
||||
const layout = document.querySelector('.archive-layout')
|
||||
const conversationFilter = document.querySelector('#conversation-filter')
|
||||
const conversationSelect = document.querySelector('#conversation-select')
|
||||
const conversationTrigger = document.querySelector('#conversation-trigger')
|
||||
const conversationTriggerAvatar = document.querySelector('#conversation-trigger-avatar')
|
||||
const conversationTriggerName = document.querySelector('#conversation-trigger-name')
|
||||
const conversationMenu = document.querySelector('#conversation-menu')
|
||||
const timeline = document.querySelector('#timeline')
|
||||
const query = document.querySelector('#query')
|
||||
const count = document.querySelector('#count')
|
||||
const meta = document.querySelector('#archive-meta')
|
||||
const title = document.querySelector('#archive-title')
|
||||
const filters = document.querySelector('#filters')
|
||||
const box = document.querySelector('#lightbox')
|
||||
@@ -1064,12 +1151,78 @@ const renderExportScript = (name: string): string => `
|
||||
layout.classList.add('single-conversation')
|
||||
return
|
||||
}
|
||||
const option = (id, label) => '<option value="' + esc(id) + '">' + esc(label) + '</option>'
|
||||
title.hidden = true
|
||||
conversationFilter.hidden = false
|
||||
conversationSelect.innerHTML = option('all', '全部聊天') +
|
||||
conversations.map((conversation) => option(conversation.id, conversation.name)).join('')
|
||||
conversationSelect.value = activeConversation
|
||||
const allAvatarUrls = conversations.map((item) => item.avatarUrl).filter(Boolean).slice(0, 2)
|
||||
const avatar = (conversation, urls) => {
|
||||
if (urls.length) {
|
||||
return '<span class="conversation-option-avatar' + (urls.length > 1 ? ' multiple' : '') + '">' +
|
||||
urls.map((url) => '<img src="' + esc(url) + '" alt="">').join('') + '</span>'
|
||||
}
|
||||
return '<span class="conversation-option-avatar">' +
|
||||
esc(conversation ? String(conversation.name || '聊').slice(0, 1) : '全') + '</span>'
|
||||
}
|
||||
const option = (id, label, avatarMarkup) =>
|
||||
'<button class="conversation-option" type="button" role="option" data-conversation-id="' +
|
||||
esc(id) + '" aria-selected="' + String(id === activeConversation) + '">' + avatarMarkup +
|
||||
'<span class="conversation-option-name">' + esc(label) + '</span>' +
|
||||
'<span class="conversation-option-check" aria-hidden="true">' +
|
||||
(id === activeConversation ? '✓' : '') + '</span></button>'
|
||||
conversationMenu.innerHTML = option('all', '全部聊天', avatar(null, allAvatarUrls)) +
|
||||
conversations.map((conversation) => option(
|
||||
conversation.id,
|
||||
conversation.name,
|
||||
avatar(conversation, conversation.avatarUrl ? [conversation.avatarUrl] : [])
|
||||
)).join('')
|
||||
updateConversationTrigger()
|
||||
}
|
||||
|
||||
const updateConversationTrigger = () => {
|
||||
const conversation = conversations.find((item) => item.id === activeConversation)
|
||||
const avatarUrls = activeConversation === 'all'
|
||||
? conversations.map((item) => item.avatarUrl).filter(Boolean).slice(0, 2)
|
||||
: conversation && conversation.avatarUrl ? [conversation.avatarUrl] : []
|
||||
const label = conversation ? conversation.name : '全部聊天'
|
||||
conversationTriggerName.textContent = label
|
||||
conversationTriggerAvatar.classList.toggle('multiple', avatarUrls.length > 1)
|
||||
if (avatarUrls.length) {
|
||||
conversationTriggerAvatar.innerHTML = avatarUrls.map((url) =>
|
||||
'<img src="' + esc(url) + '" alt="">'
|
||||
).join('')
|
||||
} else {
|
||||
conversationTriggerAvatar.textContent = conversation
|
||||
? String(conversation.name || '聊').slice(0, 1)
|
||||
: '全'
|
||||
}
|
||||
conversationMenu.querySelectorAll('[data-conversation-id]').forEach((option) => {
|
||||
const selected = option.dataset.conversationId === activeConversation
|
||||
option.setAttribute('aria-selected', String(selected))
|
||||
const check = option.querySelector('.conversation-option-check')
|
||||
if (check) check.innerHTML = selected ? '✓' : ''
|
||||
})
|
||||
}
|
||||
|
||||
const setConversationMenuOpen = (open, focusSelected = false) => {
|
||||
conversationMenu.hidden = !open
|
||||
conversationTrigger.setAttribute('aria-expanded', String(open))
|
||||
if (open && focusSelected) {
|
||||
nextFrame(() => {
|
||||
const selected = conversationMenu.querySelector('[aria-selected=true]')
|
||||
if (selected) selected.focus()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const selectConversation = (id) => {
|
||||
if (id !== 'all' && !conversations.some((item) => item.id === id)) return
|
||||
if (id !== activeConversation) {
|
||||
rememberTabPosition()
|
||||
activeConversation = id
|
||||
updateConversationTrigger()
|
||||
applyFilters(true)
|
||||
}
|
||||
setConversationMenuOpen(false)
|
||||
conversationTrigger.focus()
|
||||
}
|
||||
|
||||
const setExpandedTimelineYear = (year) => {
|
||||
@@ -1402,10 +1555,34 @@ const renderExportScript = (name: string): string => `
|
||||
setActiveKind(button.dataset.kind)
|
||||
applyFilters(true)
|
||||
})
|
||||
conversationSelect.addEventListener('change', () => {
|
||||
rememberTabPosition()
|
||||
activeConversation = conversationSelect.value
|
||||
applyFilters(true)
|
||||
conversationTrigger.addEventListener('click', () => {
|
||||
setConversationMenuOpen(conversationMenu.hidden)
|
||||
})
|
||||
conversationTrigger.addEventListener('keydown', (event) => {
|
||||
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return
|
||||
event.preventDefault()
|
||||
setConversationMenuOpen(true, true)
|
||||
})
|
||||
conversationMenu.addEventListener('click', (event) => {
|
||||
const option = event.target.closest('[data-conversation-id]')
|
||||
if (option) selectConversation(option.dataset.conversationId)
|
||||
})
|
||||
conversationMenu.addEventListener('keydown', (event) => {
|
||||
const options = Array.from(conversationMenu.querySelectorAll('[data-conversation-id]'))
|
||||
const currentIndex = options.indexOf(document.activeElement)
|
||||
let nextIndex = currentIndex
|
||||
if (event.key === 'ArrowDown') nextIndex = Math.min(options.length - 1, currentIndex + 1)
|
||||
else if (event.key === 'ArrowUp') nextIndex = Math.max(0, currentIndex - 1)
|
||||
else if (event.key === 'Home') nextIndex = 0
|
||||
else if (event.key === 'End') nextIndex = options.length - 1
|
||||
else if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
setConversationMenuOpen(false)
|
||||
conversationTrigger.focus()
|
||||
return
|
||||
} else return
|
||||
event.preventDefault()
|
||||
if (options[nextIndex]) options[nextIndex].focus()
|
||||
})
|
||||
timeline.addEventListener('click', (event) => {
|
||||
const yearButton = event.target.closest('[data-year]')
|
||||
@@ -1452,15 +1629,16 @@ const renderExportScript = (name: string): string => `
|
||||
})
|
||||
closeButton.addEventListener('click', closeLightbox)
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape') closeLightbox()
|
||||
if (event.key === 'Escape') {
|
||||
closeLightbox()
|
||||
setConversationMenuOpen(false)
|
||||
}
|
||||
})
|
||||
document.addEventListener('click', (event) => {
|
||||
if (!conversationFilter.contains(event.target)) setConversationMenuOpen(false)
|
||||
})
|
||||
|
||||
title.textContent = archive.name || ${JSON.stringify(name)}
|
||||
const updatedAt = archive.exportedAt
|
||||
? new Date(archive.exportedAt).toLocaleString('zh-CN', { hour12: false })
|
||||
: '未知时间'
|
||||
meta.textContent = (conversations.length > 1 ? '' : allMessages.length.toLocaleString() + ' 条消息 · ') +
|
||||
'更新于 ' + updatedAt
|
||||
renderConversations()
|
||||
applyFilters()
|
||||
finishLoading()
|
||||
@@ -1518,10 +1696,19 @@ export function renderExportPage(name: string): string {
|
||||
<header class="toolbar">
|
||||
<div class="archive-heading">
|
||||
<span class="title" id="archive-title">${safe(name)}</span>
|
||||
<label class="conversation-filter" id="conversation-filter" hidden>
|
||||
<select class="conversation-select" id="conversation-select" aria-label="筛选聊天"></select>
|
||||
</label>
|
||||
<span class="meta" id="archive-meta">正在读取消息…</span>
|
||||
<div class="conversation-filter" id="conversation-filter" hidden>
|
||||
<button class="conversation-trigger" id="conversation-trigger" type="button" aria-label="筛选聊天" aria-haspopup="listbox" aria-expanded="false" aria-controls="conversation-menu">
|
||||
<span class="conversation-trigger-avatar" id="conversation-trigger-avatar">全</span>
|
||||
<span class="conversation-trigger-name" id="conversation-trigger-name">全部聊天</span>
|
||||
<svg class="conversation-switch-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="m16 3 4 4-4 4"></path>
|
||||
<path d="M20 7H4"></path>
|
||||
<path d="m8 21-4-4 4-4"></path>
|
||||
<path d="M4 17h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="conversation-menu" id="conversation-menu" role="listbox" aria-label="选择聊天" hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<input id="query" type="search" placeholder="搜索发送者或消息内容…" aria-label="搜索消息">
|
||||
|
||||
@@ -109,8 +109,22 @@ test('EXPORT-ARCHIVE-01 merged v2 archive is usable offline on desktop and mobil
|
||||
name: '合并聊天档案',
|
||||
exportedAt: '2026-08-04T00:00:00.000Z',
|
||||
conversations: [
|
||||
{ id: 'alpha', name: '项目群', type: 'group', messageCount: 3 },
|
||||
{ id: 'beta', name: '文件传输助手', type: 'user', messageCount: 1 }
|
||||
{
|
||||
id: 'alpha',
|
||||
name: '项目群',
|
||||
type: 'group',
|
||||
avatarUrl:
|
||||
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath fill='%23176b57' d='M0 0h10v10H0z'/%3E%3C/svg%3E",
|
||||
messageCount: 3
|
||||
},
|
||||
{
|
||||
id: 'beta',
|
||||
name: '文件传输助手',
|
||||
type: 'user',
|
||||
avatarUrl:
|
||||
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath fill='%23d9f0e2' d='M0 0h10v10H0z'/%3E%3C/svg%3E",
|
||||
messageCount: 1
|
||||
}
|
||||
],
|
||||
messages: [
|
||||
archiveMessage('alpha-1', 'alpha', '项目群', '项目群第一条', 1_764_547_200),
|
||||
@@ -140,30 +154,62 @@ test('EXPORT-ARCHIVE-01 merged v2 archive is usable offline on desktop and mobil
|
||||
|
||||
await page.setViewportSize({ width: 1440, height: 900 })
|
||||
await page.goto(pathToFileURL(offlineIndex).href)
|
||||
const conversationSelect = page.getByLabel('筛选聊天')
|
||||
await expect(conversationSelect).toHaveValue('all')
|
||||
await expect(conversationSelect.locator('option')).toHaveCount(3)
|
||||
await expect(conversationSelect.locator('option')).toHaveText([
|
||||
'全部聊天',
|
||||
'项目群',
|
||||
'文件传输助手'
|
||||
])
|
||||
const conversationTrigger = page.getByRole('button', { name: '筛选聊天' })
|
||||
const conversationMenu = page.getByRole('listbox', { name: '选择聊天' })
|
||||
const chooseConversation = async (name: string): Promise<void> => {
|
||||
await conversationTrigger.click()
|
||||
await conversationMenu.getByRole('option', { name, exact: true }).click()
|
||||
}
|
||||
await expect(conversationTrigger).toHaveAttribute('aria-expanded', 'false')
|
||||
await expect(conversationMenu).toBeHidden()
|
||||
await expect(conversationTrigger.locator('.conversation-switch-icon')).toBeVisible()
|
||||
await expect(
|
||||
conversationTrigger.locator('.conversation-trigger-name + .conversation-switch-icon')
|
||||
).toBeVisible()
|
||||
await expect(conversationTrigger.locator('.conversation-chevron')).toHaveCount(0)
|
||||
await expect(page.locator('#conversation-trigger-name')).toHaveText('全部聊天')
|
||||
await expect(page.locator('#conversation-trigger-avatar img')).toHaveCount(2)
|
||||
await expect(page.locator('#archive-title')).toBeHidden()
|
||||
await expect(page.locator('.archive-heading #conversation-filter')).toBeVisible()
|
||||
await expect(page.locator('#archive-meta')).toHaveText(/^更新于 /)
|
||||
await expect(page.locator('#archive-meta')).not.toContainText('条消息')
|
||||
await expect(page.locator('#archive-meta')).toHaveCount(0)
|
||||
await expect(page.locator('.message')).toHaveCount(4)
|
||||
await expect(page.locator('.conversation-source')).toHaveCount(4)
|
||||
const desktopSwitcherBounds = await page.evaluate(() => {
|
||||
const trigger = document.querySelector('#conversation-trigger')!.getBoundingClientRect()
|
||||
const name = document.querySelector('#conversation-trigger-name')!.getBoundingClientRect()
|
||||
const icon = document.querySelector('.conversation-switch-icon')!.getBoundingClientRect()
|
||||
return { triggerWidth: trigger.width, nameToIcon: icon.left - name.right }
|
||||
})
|
||||
expect(desktopSwitcherBounds.triggerWidth).toBeLessThan(200)
|
||||
expect(desktopSwitcherBounds.nameToIcon).toBeLessThanOrEqual(12)
|
||||
expect(
|
||||
await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)
|
||||
).toBe(true)
|
||||
await conversationTrigger.click()
|
||||
await expect(conversationTrigger).toHaveAttribute('aria-expanded', 'true')
|
||||
await expect(conversationMenu).toBeVisible()
|
||||
await expect(conversationMenu.getByRole('option')).toHaveCount(3)
|
||||
await expect(conversationMenu.getByRole('option', { name: '全部聊天' })).toBeVisible()
|
||||
await expect(conversationMenu.getByRole('option', { name: '项目群' })).toBeVisible()
|
||||
await expect(conversationMenu.getByRole('option', { name: '文件传输助手' })).toBeVisible()
|
||||
await expect(conversationMenu).not.toContainText('条消息')
|
||||
await expect(conversationMenu.getByRole('option', { name: '全部聊天' })).toHaveAttribute(
|
||||
'aria-selected',
|
||||
'true'
|
||||
)
|
||||
await expect(conversationTrigger).toHaveCSS('border-top-width', '0px')
|
||||
await page.screenshot({ path: testInfo.outputPath('conversation-menu-1440.png') })
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(conversationMenu).toBeHidden()
|
||||
await page.screenshot({ path: testInfo.outputPath('merged-archive-1440.png'), fullPage: true })
|
||||
|
||||
await conversationSelect.selectOption('beta')
|
||||
await chooseConversation('文件传输助手')
|
||||
await expect(page.locator('.message')).toHaveCount(1)
|
||||
await expect(page.locator('#conversation-trigger-name')).toHaveText('文件传输助手')
|
||||
await expect(page.locator('#conversation-trigger-avatar img')).toHaveCount(1)
|
||||
await expect(page.locator('.conversation-source')).toHaveCount(0)
|
||||
|
||||
await conversationSelect.selectOption('all')
|
||||
await chooseConversation('全部聊天')
|
||||
await page.setViewportSize({ width: 390, height: 844 })
|
||||
await expect(page.locator('.timeline-year')).toHaveCount(2)
|
||||
await expect(page.locator('.timeline-year').first()).toBeVisible()
|
||||
@@ -188,7 +234,7 @@ test('EXPORT-ARCHIVE-01 merged v2 archive is usable offline on desktop and mobil
|
||||
expect(positions.documentWidth).toBeLessThanOrEqual(positions.viewportWidth)
|
||||
await expect(page.locator('.message')).toHaveCount(4)
|
||||
const searchInput = page.getByLabel('搜索消息')
|
||||
await expect(conversationSelect).toBeVisible()
|
||||
await expect(conversationTrigger).toBeVisible()
|
||||
const compactControlBounds = await page.evaluate(() => {
|
||||
const conversations = document.querySelector('#conversation-filter')!.getBoundingClientRect()
|
||||
const search = document.querySelector('#query')!.getBoundingClientRect()
|
||||
@@ -210,9 +256,22 @@ test('EXPORT-ARCHIVE-01 merged v2 archive is usable offline on desktop and mobil
|
||||
).toBeLessThanOrEqual(1)
|
||||
expect(compactControlBounds.searchWidth).toBeGreaterThan(compactControlBounds.conversationWidth)
|
||||
expect(compactControlBounds.searchFontSize).toBe('16px')
|
||||
await conversationSelect.selectOption('beta')
|
||||
await expect(conversationTrigger.locator('.conversation-switch-icon')).toHaveCSS(
|
||||
'width',
|
||||
'13px'
|
||||
)
|
||||
await conversationTrigger.click()
|
||||
await expect(conversationMenu).toBeVisible()
|
||||
const mobileMenuBounds = await conversationMenu.evaluate((element) => {
|
||||
const bounds = element.getBoundingClientRect()
|
||||
return { left: bounds.left, right: bounds.right, viewportWidth: window.innerWidth }
|
||||
})
|
||||
expect(mobileMenuBounds.left).toBeGreaterThanOrEqual(0)
|
||||
expect(mobileMenuBounds.right).toBeLessThanOrEqual(mobileMenuBounds.viewportWidth)
|
||||
await page.screenshot({ path: testInfo.outputPath('conversation-menu-390.png') })
|
||||
await conversationMenu.getByRole('option', { name: '文件传输助手' }).click()
|
||||
await expect(page.locator('.message')).toHaveCount(1)
|
||||
await conversationSelect.selectOption('all')
|
||||
await chooseConversation('全部聊天')
|
||||
await expect(page.locator('.message')).toHaveCount(4)
|
||||
await expect(searchInput).toBeVisible()
|
||||
await searchInput.fill('个人聊天消息')
|
||||
@@ -319,7 +378,7 @@ test('EXPORT-ARCHIVE-02 legacy single-chat archive keeps its original layout', a
|
||||
await expect(page.locator('#conversation-filter')).toBeHidden()
|
||||
await expect(page.locator('#archive-title')).toBeVisible()
|
||||
await expect(page.locator('#archive-title')).toHaveText('单聊天档案')
|
||||
await expect(page.locator('#archive-meta')).toContainText('1 条消息 · 更新于 ')
|
||||
await expect(page.locator('#archive-meta')).toHaveCount(0)
|
||||
await expect(page.locator('.archive-layout')).toHaveClass(/single-conversation/)
|
||||
await expect(page.locator('.message')).toHaveCount(1)
|
||||
await page.screenshot({ path: testInfo.outputPath('single-archive-1440.png'), fullPage: true })
|
||||
|
||||
Reference in New Issue
Block a user