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:
@@ -382,7 +382,7 @@ test.describe.serial('cross-frontend journey (admin → api/worker → viewer)',
|
||||
.poll(async () => banner.evaluate((img) => (img as HTMLImageElement).naturalWidth))
|
||||
.toBeGreaterThan(0);
|
||||
await expect(page.locator('h1.title')).toHaveText(zhTitle);
|
||||
await expect(page.locator('.chip')).toHaveCount(2); // 全部 + 主舞台
|
||||
await expect(page.locator('.categories .chip')).toHaveCount(2); // 全部 + 主舞台
|
||||
|
||||
await page.locator('button.locale-switcher').click();
|
||||
await expect(page.locator('h1.title')).toHaveText(enTitle);
|
||||
@@ -396,6 +396,10 @@ test.describe.serial('cross-frontend journey (admin → api/worker → viewer)',
|
||||
await page.getByRole('button', { name: '全部' }).click();
|
||||
await expect(page.locator('.cell')).toHaveCount(3);
|
||||
|
||||
// Task 10 互动化:浏览计数行(首访上报后权威回填)+ 排序 chips 在位。
|
||||
await expect(page.locator('.view-count')).toHaveText('1', { timeout: 15_000 });
|
||||
await expect(page.locator('.sort .chip')).toHaveText(['最新', '热门']);
|
||||
|
||||
// 大图:点击开图 → 真实字节 → 滚轮缩放 → 双击复位 → 关闭回列表。
|
||||
const lightboxPhoto = photoIds[1]!; // 800×500(后续 admin 腿按尺寸锚定隐藏)
|
||||
await page.locator('.cell').nth(1).click();
|
||||
@@ -403,6 +407,9 @@ test.describe.serial('cross-frontend journey (admin → api/worker → viewer)',
|
||||
await expect(
|
||||
page.locator('img.photo'),
|
||||
).toHaveAttribute('src', new RegExp(lightboxPhoto));
|
||||
// Task 10:大图内真实点赞 1 次 → 防抖冲批 → 权威计数 1。
|
||||
await page.locator('.control.like-button').click();
|
||||
await expect(page.locator('.like-count')).toHaveText('1');
|
||||
await expect
|
||||
.poll(async () =>
|
||||
page.locator('img.photo').evaluate((img) => (img as HTMLImageElement).naturalWidth),
|
||||
|
||||
Reference in New Issue
Block a user