/** * 验证「点卡片 → 打开哪一条详情」的下标反查逻辑。 * * 背景(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);