feat(viewer): likes, bubbles, visit reporting and hot sorting
Task 10 viewer 互动化: - 点赞:useLikes 批次状态机(500ms 防抖 / 5 次满额即冲 / 失败同 batchId 退避重试 3 次后回滚丢弃),乐观计数 + 服务端 total 权威校准 (countVersion 单调),大图与格子双入口; - WS 实时:RealtimeClient 纯状态机(半频心跳、指数退避重连、三败降级、 重连成功 resync 第 1 页重取),消息路由器做 likes.burst 自身过滤 (originBatchIds 求交)、隐藏页暂停与减少动画下仅校准不出气泡; photo.hidden/deleted 即时移出序列并回退邻居大图;降级后 8s 轮询接管; - 浏览上报:visitorId 跨标签 BroadcastChannel first-writer-wins 协调, 可见时上报、requestId 持久幂等、网络失败指数重试; - 排序:最新/热门 chips(热门走 ov 冷快照),大图打开期间禁用切换; 顶栏「减少动画」偏好开关; - 契约包新增 ./realtime 子路径出口(浏览器面不可引桶出口——其连带 internal-sync 的 node:crypto 会被 Vite 外置化致整页崩溃); - e2e 注入钩子 __viewerWsUrl/__viewerWsRetryBaseMs/__viewerRealtimePollMs/ __viewerBubbleMs 与 .activity[data-connection] 连接态锚。 vitest:viewer 105(新增 36:likes 批次 8、visitor 协调 5、上报门控 4、 路由 8、WS 状态机 7、排序 4);Playwright:viewer 21(新增 likes.spec 7、 journey 补互动腿断言)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -10,16 +10,27 @@ import { makeAlbum, makeEnAlbum } from './fixtures';
|
||||
|
||||
// ActivityPage(Task 6)组件测试:api client 以真实模块为底、仅替身 getPublicAlbum /
|
||||
// getPublicPhotos(Task 7 起活动页内嵌 PhotoFeed,需喂空页避免真实 fetch),
|
||||
// 保证 ApiError/errors 映射等真实逻辑参与断言。
|
||||
// 保证 ApiError/errors 映射等真实逻辑参与断言。Task 10 起活动页组装 WS/浏览
|
||||
// 上报:RealtimeClient 以无操作桩替换(jsdom WebSocket 会发起真实连接)、
|
||||
// postAlbumVisit 替身防真实 fetch。
|
||||
vi.mock('../api/client', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../api/client')>();
|
||||
return {
|
||||
...actual,
|
||||
getPublicAlbum: vi.fn(),
|
||||
getPublicPhotos: vi.fn().mockResolvedValue({ items: [], nextCursor: null, hasMore: false }),
|
||||
postAlbumVisit: vi.fn().mockResolvedValue({ viewCount: 0, counted: false }),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../realtime/ws', () => {
|
||||
class RealtimeClientStub {
|
||||
start(): void {}
|
||||
stop(): void {}
|
||||
}
|
||||
return { RealtimeClient: RealtimeClientStub, wsUrlFromApiBase: () => 'ws://test/v1/realtime' };
|
||||
});
|
||||
|
||||
const mockedGetPublicAlbum = vi.mocked(getPublicAlbum);
|
||||
|
||||
// 逐挂载独立 i18n 实例 + 逐用例卸载:vue-i18n 在 app.unmount 时 dispose 共享实例
|
||||
@@ -67,7 +78,7 @@ describe('activity page (Task 6)', () => {
|
||||
// Asia/Shanghai:UTC 10-01 02:00/12:00 → 本地 10-01 10:00/20:00。
|
||||
expect(wrapper.find('.meta').text()).toContain('10月1日');
|
||||
|
||||
const chips = wrapper.findAll('.chip');
|
||||
const chips = wrapper.findAll('.categories .chip');
|
||||
expect(chips.map((chip) => chip.text())).toEqual(['全部', '签到墙', '开场']);
|
||||
expect(chips.every((chip) => chip.attributes('disabled') === undefined)).toBe(true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user