Files
koc-loop/tests/interaction-fixes.test.mjs
ABAPPLO c686003cfb fix: 完成态判据统一为曝光+阅读齐+交互断点三处修复
- 创作者凭证提交(manual_metrics/submit_creator_metrics)的完成判据从
  d7_likes(全库无赋值方,永不触发)改为 exposure+views 齐,与采集侧口径
  对齐;管理端手动回填后同步 refreshTaskStatus 联动任务状态
- 回收队列头部改为「已发布 N / 总数」口径,未回填链接行不再计入
- 行内更新/补填笔记链接从原生 prompt 改为弹窗表单,失败保留已输入内容
- 顶栏「获取KOC领取链接」从导航跳转改为弹窗直接列出各任务领取链接,
  一键复制;弹窗内点击不冒泡误关
2026-10-08 15:17:14 +08:00

106 lines
4.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
// 2026-10-08 第二轮交互走查修复:
// 1) 完成态判据统一为「曝光量+阅读量齐即 complete」(原 d7_likes 条件全库无赋值方,永不触发)
// 2) 回收队列头部统计改为「已发布 N / 总数」口径,未回填行不再计入
// 3) 行内更新/补填链接从原生 prompt() 改为弹窗表单,失败保留已输入内容
// 4) 顶栏「获取KOC领取链接」从导航跳转改为直接弹出各任务领取链接列表
test("creator metrics completion follows the exposure+views criterion", async () => {
const sources = await Promise.all([
readFile(new URL("../app/api/partner/route.ts", import.meta.url), "utf8"),
readFile(new URL("../app/api/action/route.ts", import.meta.url), "utf8"),
]);
for (const source of sources) {
assert.match(
source,
/status = CASE WHEN exposure IS NOT NULL AND views IS NOT NULL THEN 'complete' ELSE status END/,
);
}
// 死条件必须清除:d7_likes 只被置 NULL 从不赋值,不能作为完成判据
for (const source of sources) {
assert.doesNotMatch(
source,
/CASE WHEN d7_likes IS NOT NULL THEN 'complete'/,
);
}
});
test("manual metrics refreshes the task status afterwards", async () => {
const source = await readFile(
new URL("../app/api/action/route.ts", import.meta.url),
"utf8",
);
assert.match(source, /import \{ refreshTaskStatus \} from "\.\.\/\.\.\/\.\.\/lib\/task-service";/);
const manual = source.slice(
source.indexOf('body.action === "manual_metrics"'),
source.indexOf('body.action === "bind_wecom_external_id"'),
);
assert.ok(manual.includes("manual_metrics"), "必须截取到 manual_metrics 分支");
assert.match(manual, /SELECT task_id FROM distributions WHERE id = \?/);
assert.match(manual, /refreshTaskStatus\(ownerTask\.task_id, db\)/);
});
test("recovery queue header counts only published rows", async () => {
const source = await readFile(
new URL("../app/admin-app.tsx", import.meta.url),
"utf8",
);
assert.match(
source,
/const publishedCount = taskDistributions\.filter\(/,
);
assert.match(
source,
/已发布 \{publishedCount\} \/ \{taskDistributions\.length\} 篇/,
);
});
test("publish url editing uses a modal instead of window.prompt", async () => {
const source = await readFile(
new URL("../app/admin-app.tsx", import.meta.url),
"utf8",
);
assert.match(source, /const \[publishUrlModal, setPublishUrlModal\]/);
assert.match(source, /onSubmit=\{submitPublishUrl\}/);
assert.match(source, /支持长链、短链或完整分享文案/);
// 提交失败保留弹窗内容(runAction 失败返回 false)
assert.match(source, /if \(updated\) setPublishUrlModal\(null\);/);
// 更新链接流程本身不再用 prompt(剪贴板复制失败兜底的 prompt 保留)
const updateFn = source.slice(
source.indexOf("const updateDistributionPublishUrl"),
source.indexOf("const submitPublishUrl"),
);
assert.ok(updateFn.includes("updateDistributionPublishUrl"), "必须截取到函数体");
assert.doesNotMatch(updateFn, /prompt/);
});
test("claim links button opens a modal listing every task link", async () => {
const source = await readFile(
new URL("../app/admin-app.tsx", import.meta.url),
"utf8",
);
assert.match(source, /const \[claimLinksOpen, setClaimLinksOpen\] = useState\(false\);/);
// 顶栏按钮不再是导航跳转
assert.match(source, /onClick=\{\(\) => setClaimLinksOpen\(true\)\}/);
assert.doesNotMatch(
source,
/onClick=\{\(\) => setActiveNav\("tasks"\)\}\s*\n\s*获取KOC领取链接/,
);
// 弹窗里每个任务给出复制按钮,无链接的任务明示未生成
assert.match(source, /claim-links-list/);
assert.match(source, /KOC领取链接已复制/);
assert.match(source, /尚未生成领取链接/);
// 弹窗内点击(含复制按钮)不能冒泡到 backdrop 导致误关闭
const modal = source.slice(
source.indexOf('className="modal-card compact claim-links-modal"'),
source.indexOf('<div className="claim-links-list">'),
);
assert.match(
modal,
/onMouseDown=\{\(event\) => event\.stopPropagation\(\)\}/,
);
});