第八轮最小粒度测试:修复CSS选择器/JSX结构不匹配的一整类bug,清理Tinode品牌残留
第八轮改用"读CSS选择器+核对JSX结构"的方式复测全部11章,命中了7轮纯接口测试从未发现的一类 问题——选择器写对了但目标元素的实际DOM结构/className对不上,样式静默失效: - 语音通话最小化胶囊按钮尺寸被同特异度规则覆盖;移动端悬浮通话窗口left/right/width三值同时 生效导致过度约束;来电全屏页认证徽章/姓名标题缺少专属样式覆盖 - 我的收藏-语音卡片三列grid对应实际只有1个子元素,播放器被压缩到卡片左侧一角;链接/记录头部 完全没有基础样式,退化成浏览器默认外观 - 多选模式下"收到的"消息气泡被flex auto-margin一起拖到最右侧;.avatar-box/#send-message-panel 两处逗号断句错误导致后半句选择器丢失祖先限定 - 按住说话悬浮反馈渲染成内联胶囊,而参考图要求的居中悬浮卡片样式其实已经存在(挂在了另一个非 生产demo组件上);批量转发确认框沿用了80%视口高度;audio-player.jsx的playback类点在canvas 上而非包裹层,波形和计时器可能纵向堆叠 - "消息已撤回"占位符误拼了日期分隔条的class而非专属的deleted 品牌清理:浏览器标签页标题、未读数标题更新均硬编码"Tinode";LINK_CONTACT_US等三个链接常量 指向开源Tinode项目自己的域名(联系我们改为跟用户条款/隐私协议一致的toast,不再发起外部mailto)。 以及:空会话丢失.chat-box样式类、青少年模式PIN定位、若干收藏/文件类型展示细节。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5e173c55e8
commit
d759f67b11
@@ -535,14 +535,15 @@ function GroupProfile({topic, myUserId, onToast}) {
|
||||
<button type="button" onClick={() => navigate('group-my-nickname', topic)}>
|
||||
<span>群昵称</span><small>{nicknameLabel}</small><Icon>chevron_right</Icon>
|
||||
</button>
|
||||
{canEdit ? <button type="button" onClick={() => navigate('group-nickname', topic)}>
|
||||
<span>群名称</span><small>{group.name}</small><Icon>chevron_right</Icon>
|
||||
</button> :
|
||||
<div><span>群名称</span><small>{group.name}</small></div>}
|
||||
{canEdit ? <button type="button" onClick={() => navigate('group-description', topic)}>
|
||||
<span>群简介</span><small>{group.description || '群主很懒,还没有群简介'}</small><Icon>chevron_right</Icon>
|
||||
</button> :
|
||||
<div><span>群简介</span><small>{group.description || '群主很懒,还没有群简介'}</small></div>}
|
||||
{/* Swapping to a bare <div> when !canEdit used to drop every .group-profile row rule (all of
|
||||
them are scoped to > button/> label) -- same fix as the avatar row just above: keep
|
||||
<button>, gate it with disabled like that row already does. */}
|
||||
<button type="button" disabled={!canEdit} onClick={() => canEdit && navigate('group-nickname', topic)}>
|
||||
<span>群名称</span><small>{group.name}</small><Icon>chevron_right</Icon>
|
||||
</button>
|
||||
<button type="button" disabled={!canEdit} onClick={() => canEdit && navigate('group-description', topic)}>
|
||||
<span>群简介</span><small>{group.description || '群主很懒,还没有群简介'}</small><Icon>chevron_right</Icon>
|
||||
</button>
|
||||
<button type="button" className="group-profile-qr" onClick={() => navigate('group-qr', topic)}><span>群二维码</span><Icon>qr_code</Icon><Icon>chevron_right</Icon></button>
|
||||
<button type="button" onClick={() => navigate('group-members', topic)}><span>群成员</span><small>共{group.member_count}位成员</small><Icon>chevron_right</Icon></button>
|
||||
</main>
|
||||
@@ -637,7 +638,7 @@ const GOVERNANCE_FIELDS = [
|
||||
['member_private_chat', '群成员私信权限', '关闭后,不允许发送名片消息,且普通成员在群聊界面、群组信息内点击头像无反应。']
|
||||
];
|
||||
|
||||
function Governance({topic, tinode, onToast}) {
|
||||
function Governance({topic, myRole, tinode, onToast}) {
|
||||
// Router page for AdminList/Muted/OwnerSelect/the avatar editor -- remounts (per GroupManagementView's
|
||||
// `key={view}` note) and reflashes its 5 toggles every time the user finishes one of those sub-tasks
|
||||
// and returns, so this is one of the highest-traffic pages in the file.
|
||||
@@ -659,6 +660,11 @@ function Governance({topic, tinode, onToast}) {
|
||||
.catch(err => onToast(err.message || '设置失败'));
|
||||
};
|
||||
const dissolve = () => {
|
||||
// Backend already refuses to actually destroy the group for a non-owner (the same native
|
||||
// {del hard:true} call it silently downgrades into "remove me from this group" instead) --
|
||||
// but that response is indistinguishable from a real success, so without this gate a member
|
||||
// who somehow lands on this page got told "群聊已解散" when they'd really just left it.
|
||||
if (myRole !== 'owner') return;
|
||||
if (!window.confirm('解散后群聊将无法恢复,确定要解散该群聊吗?')) return;
|
||||
const t = tinode ? tinode.getTopic(topic) : null;
|
||||
if (!t) return;
|
||||
@@ -683,7 +689,7 @@ function Governance({topic, tinode, onToast}) {
|
||||
<span><strong>{label}</strong><small>{desc}</small></span>
|
||||
<Toggle checked={!!gov[key]} disabled={gov[key + '_editable'] === false} onChange={v => setField(key, v)} />
|
||||
</div>)}</section>
|
||||
<button type="button" className="group-danger" disabled={dissolving} onClick={dissolve}>解散群组</button>
|
||||
<button type="button" className="group-danger" disabled={dissolving || myRole !== 'owner'} onClick={dissolve}>解散群组</button>
|
||||
</main>
|
||||
</div>;
|
||||
}
|
||||
@@ -752,7 +758,7 @@ function GroupMembers({topic, onToast}) {
|
||||
setMembers(items);
|
||||
}).catch(() => { onToast('加载失败'); setLoadError(true); });
|
||||
}, [topic]);
|
||||
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').includes(query));
|
||||
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').toLowerCase().includes(query.toLowerCase()));
|
||||
const groups = groupBy(visible, m => alphaInitial(m.group_nickname || m.display_name));
|
||||
const roleLabel = role => role === 'owner' ? '群主' : role === 'admin' ? '管理员' : '';
|
||||
return <div className="group-page">
|
||||
@@ -787,7 +793,7 @@ function MemberPicker({type, topic, onToast}) {
|
||||
.then(items => setMembers(items.filter(m => m.eligible !== false)))
|
||||
.catch(() => { onToast('加载失败'); setLoadError(true); });
|
||||
}, [topic, mute]);
|
||||
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').includes(query));
|
||||
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').toLowerCase().includes(query.toLowerCase()));
|
||||
const groups = groupBy(visible, m => alphaInitial(m.group_nickname || m.display_name));
|
||||
const toggle = id => setSelected(v => v.includes(id) ? v.filter(x => x != id) : v.concat(id));
|
||||
const done = () => {
|
||||
@@ -901,7 +907,7 @@ function OwnerSelect({topic, onToast}) {
|
||||
.then(items => setMembers(items.filter(m => m.eligible !== false)))
|
||||
.catch(() => { onToast('加载失败'); setLoadError(true); });
|
||||
}, [topic]);
|
||||
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').includes(query));
|
||||
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').toLowerCase().includes(query.toLowerCase()));
|
||||
const admins = visible.filter(m => m.role === 'admin');
|
||||
const others = visible.filter(m => m.role !== 'admin');
|
||||
const reset = () => { setTarget(null); setPasswordStep(false); setPassword(''); };
|
||||
@@ -1148,7 +1154,7 @@ export default function GroupManagementView({view, hashParams, myUserId, tinode,
|
||||
else if (view === 'group-my-nickname') content = <MyNicknameEditor topic={topic} myUserId={myUserId} onToast={setToast} />;
|
||||
else if (view === 'group-members') content = <GroupMembers topic={topic} onToast={setToast} />;
|
||||
else if (view === 'group-qr') content = <GroupQr topic={topic} onToast={setToast} />;
|
||||
else if (view === 'group-governance') content = <Governance topic={topic} tinode={tinode} onToast={setToast} />;
|
||||
else if (view === 'group-governance') content = <Governance topic={topic} myRole={myRole} tinode={tinode} onToast={setToast} />;
|
||||
else if (['group-admins', 'group-admin-success', 'group-admin-partial', 'group-admin-removed'].includes(view)) content = <AdminList topic={topic} onToast={setToast} />;
|
||||
else if (view === 'group-admin-add') content = <MemberPicker type="admin-add" topic={topic} onToast={setToast} />;
|
||||
else if (view === 'group-mute-add') content = <MemberPicker type="mute-add" topic={topic} onToast={setToast} />;
|
||||
|
||||
Reference in New Issue
Block a user