|
@@ -0,0 +1,98 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * 验证「点卡片 → 打开哪一条详情」的下标反查逻辑。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 背景(2026-09-17 用户报的 bug):同一条政策下的多个申报事项会各生成一张卡片,
|
|
|
|
|
+ * 但适配层把 `title` 与 `declaration_item` 都取自 `card.name.text`(只有政策名),
|
|
|
|
|
+ * 后端又不下发 `id`/`policy_id`(实测恒为 undefined)——这些卡片在字段上**无法区分**。
|
|
|
|
|
+ * 原先的 findIndex 只要任一条件命中就返回,于是点第二张卡片也跳到第一张,
|
|
|
|
|
+ * 表现为「两张卡片的详情内容一模一样」。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 验证的不变量:
|
|
|
|
|
+ * ① 卡片区点击(originalData 是 policies 里的同一对象)必须精确命中自己那一条
|
|
|
|
|
+ * ② 字段比对**只有唯一命中**才采信;多个候选时返回 -1(交给兜底,不张冠李戴)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 怎么跑(在项目根目录):
|
|
|
|
|
+ * npx esbuild harness/tools/_entry-policy-match.ts --bundle --format=esm \
|
|
|
|
|
+ * --outfile=harness/tools/_policy-match.mjs
|
|
|
|
|
+ * node harness/tools/verify-policy-detail-index.mjs
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+import { resolvePolicyDetailIndex } from './_policy-match.mjs';
|
|
|
|
|
+
|
|
|
|
|
+let pass = 0;
|
|
|
|
|
+let fail = 0;
|
|
|
|
|
+const check = (name, cond, extra = '') => {
|
|
|
|
|
+ if (cond) {
|
|
|
|
|
+ pass++;
|
|
|
|
|
+ console.log(' ok ' + name);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fail++;
|
|
|
|
|
+ console.log(' FAIL ' + name + ' ' + extra);
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// ── 复现用户场景:同一政策的两个申报事项 ────────────────────────────────
|
|
|
|
|
+// 适配层产出的两个条目:declaration_item 都是政策名,都没有 id
|
|
|
|
|
+const POLICY_NAME = '关于进一步完善本区创业扶持政策有关事项的通知';
|
|
|
|
|
+const rawA = { declaration_item: POLICY_NAME, support_objects: '本区户籍创业人员' };
|
|
|
|
|
+const rawB = { declaration_item: POLICY_NAME, support_objects: '区级考核优秀/良好/合格基地' };
|
|
|
|
|
+const policies = [
|
|
|
|
|
+ { declaration_item: POLICY_NAME, originalData: rawA, support_objects: rawA.support_objects },
|
|
|
|
|
+ { declaration_item: POLICY_NAME, originalData: rawB, support_objects: rawB.support_objects },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+console.log('场景:同政策的两个申报事项,declaration_item 与 id 都无法区分\n');
|
|
|
|
|
+
|
|
|
|
|
+console.log('【1】卡片区点击(对象同一性——精确路径)');
|
|
|
|
|
+check('点第 1 张 → 下标 0', resolvePolicyDetailIndex(policies, policies[0]) === 0, String(resolvePolicyDetailIndex(policies, policies[0])));
|
|
|
|
|
+check(
|
|
|
|
|
+ '点第 2 张 → 下标 1(**修复前这里会返回 0**)',
|
|
|
|
|
+ resolvePolicyDetailIndex(policies, policies[1]) === 1,
|
|
|
|
|
+ String(resolvePolicyDetailIndex(policies, policies[1]))
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+console.log('\n【2】字段无法区分时不得猜(唯一性要求)');
|
|
|
|
|
+// 一个不在 policies 里的对象,但 declaration_item 与两张卡片都相同
|
|
|
|
|
+const foreign = { originalData: { declaration_item: POLICY_NAME }, declaration_item: POLICY_NAME };
|
|
|
|
|
+check(
|
|
|
|
|
+ '外来条目 + 两个候选 → 返回 -1(不张冠李戴,交给兜底)',
|
|
|
|
|
+ resolvePolicyDetailIndex(policies, foreign) === -1,
|
|
|
|
|
+ String(resolvePolicyDetailIndex(policies, foreign))
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+console.log('\n【3】字段唯一命中时仍应采信(退化路径保留)');
|
|
|
|
|
+const uniqueRaw = { declaration_item: '某个独占的申报事项' };
|
|
|
|
|
+const uniquePolicies = [
|
|
|
|
|
+ { declaration_item: POLICY_NAME, originalData: rawA },
|
|
|
|
|
+ { declaration_item: '某个独占的申报事项', originalData: uniqueRaw },
|
|
|
|
|
+];
|
|
|
|
|
+check(
|
|
|
|
|
+ '外来条目 + 唯一 declaration_item 命中 → 正确的下标',
|
|
|
|
|
+ resolvePolicyDetailIndex(uniquePolicies, { originalData: { declaration_item: '某个独占的申报事项' } }) === 1,
|
|
|
|
|
+ String(resolvePolicyDetailIndex(uniquePolicies, { originalData: { declaration_item: '某个独占的申报事项' } }))
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+const idRaw = { id: 'k_abc123' };
|
|
|
|
|
+const idPolicies = [
|
|
|
|
|
+ { declaration_item: POLICY_NAME, originalData: rawA },
|
|
|
|
|
+ { declaration_item: POLICY_NAME, originalData: idRaw },
|
|
|
|
|
+];
|
|
|
|
|
+check(
|
|
|
|
|
+ '按 id 唯一命中 → 正确的下标',
|
|
|
|
|
+ resolvePolicyDetailIndex(idPolicies, { originalData: { id: 'k_abc123' } }) === 1,
|
|
|
|
|
+ String(resolvePolicyDetailIndex(idPolicies, { originalData: { id: 'k_abc123' } }))
|
|
|
|
|
+);
|
|
|
|
|
+check(
|
|
|
|
|
+ 'policy_id 同样可用',
|
|
|
|
|
+ resolvePolicyDetailIndex(idPolicies, { originalData: { policy_id: 'k_abc123' } }) === 1,
|
|
|
|
|
+ String(resolvePolicyDetailIndex(idPolicies, { originalData: { policy_id: 'k_abc123' } }))
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+console.log('\n【4】边界');
|
|
|
|
|
+check('policies 为空 → -1', resolvePolicyDetailIndex([], policies[0]) === -1);
|
|
|
|
|
+check('policies 为 null → -1', resolvePolicyDetailIndex(null, policies[0]) === -1);
|
|
|
|
|
+check('item 为 null → -1', resolvePolicyDetailIndex(policies, null) === -1);
|
|
|
|
|
+check('完全对不上 → -1', resolvePolicyDetailIndex(policies, { originalData: { declaration_item: '不存在的事项' } }) === -1);
|
|
|
|
|
+
|
|
|
|
|
+console.log(`\n===== 通过 ${pass} 项,失败 ${fail} 项 =====`);
|
|
|
|
|
+process.exit(fail ? 1 : 0);
|