Archived
契约:launchOption 加 group/groupLabel(x-options.group 从字典表现读人读名)、 launchParam 加 linkGroup(x-link 结组);api-report 已随 --update 入库。 前端 launch-link.ts 纯函数管三条口径:锚摆全量、其余按锚的分组键收窄 (未标类型殿后一组,新增照走);锚粘着 —— 清空只解除收窄不换锚,人改得了主意。 服务端不据此立墙:combo 本就收候选之外的值,类型一致归 S1 收步判据 sub_eval_part_distinct_chip_type_id = 1(数得出来的门)。 W05 清单两格挂上 x-link: chip-type;文档同步。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 启动参数的按分组联动(2026-08-31 用户定:原内部料号 / 候选内部料号按芯片类型互相收窄)。
|
||||
*
|
||||
* 三条口径由用户当场定死:
|
||||
* ①**组内第一个填上值的那格是锚**,锚自己永远摆全量;锚粘着不让位 —— 把它清空,收窄整组解除
|
||||
* (不把锚顺手交给另一格,否则「清空锚再改主意」当场被另一格锁住);整组都空了才重新认锚。
|
||||
* ②未标类型的老料**列在末尾单独一组**,不是不列。
|
||||
* ③锚改了类型之后,另一格里失配的值**清空**(人自己新增的值不动 —— 它压根不在候选里)。
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { LaunchParam } from '@contracts';
|
||||
import { comboFace, nextAnchors, staleLinked } from '../internal/launch-link.ts';
|
||||
|
||||
const OPTIONS = [
|
||||
{ value: 'LM2596S-5.0', label: 'LM2596S-5.0', group: '1', groupLabel: 'DC-DC 降压稳压器' },
|
||||
{ value: 'XL2596S-5.0', label: 'XL2596S-5.0', group: '1', groupLabel: 'DC-DC 降压稳压器' },
|
||||
{ value: 'AMS1117-3.3', label: 'AMS1117-3.3', group: '2', groupLabel: 'LDO 线性稳压器' },
|
||||
{ value: 'OLD-001', label: 'OLD-001', group: '' },
|
||||
];
|
||||
const combo = (key: string, label: string): LaunchParam => ({ key, label, type: 'combo', required: true, options: OPTIONS, linkGroup: 'chip-type' });
|
||||
const PARAMS: LaunchParam[] = [combo('originalInternalPn', '原内部料号'), combo('candidateInternalPn', '候选内部料号')];
|
||||
/** 不联动的一格:同一张表上还有别的 combo,不该被卷进来 */
|
||||
const LOOSE: LaunchParam = { key: 'customerCode', label: '客户', type: 'combo', options: [{ value: 'CU-0142', label: 'Itelma' }] };
|
||||
|
||||
const face = (key: string, values: Record<string, string>, anchors: Record<string, string> = {}) =>
|
||||
comboFace(PARAMS.find((p) => p.key === key)!, [...PARAMS, LOOSE], values, anchors);
|
||||
|
||||
describe('nextAnchors —— 谁是锚', () => {
|
||||
it('组内第一个填上值的那格入座;另一格随后填上也不改朝换代', () => {
|
||||
const first = nextAnchors(PARAMS, { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: '' }, {});
|
||||
expect(first).toEqual({ 'chip-type': 'originalInternalPn' });
|
||||
expect(nextAnchors(PARAMS, { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: 'XL2596S-5.0' }, first)).toEqual(first);
|
||||
});
|
||||
|
||||
it('先填候选那格,锚就是它(反过来一样)', () => {
|
||||
expect(nextAnchors(PARAMS, { originalInternalPn: '', candidateInternalPn: 'XL2596S-5.0' }, {})).toEqual({ 'chip-type': 'candidateInternalPn' });
|
||||
});
|
||||
|
||||
it('**锚粘着**:把锚清空,收窄整组解除 —— 不把锚顺手交给还填着值的另一格', () => {
|
||||
const anchors = { 'chip-type': 'originalInternalPn' };
|
||||
const after = nextAnchors(PARAMS, { originalInternalPn: '', candidateInternalPn: 'XL2596S-5.0' }, anchors);
|
||||
expect(after).toEqual(anchors);
|
||||
// 锚空着 = 没有可收窄的依据:另一格摆全量(四条,分两类 + 未标类型)
|
||||
expect(face('candidateInternalPn', { originalInternalPn: '', candidateInternalPn: 'XL2596S-5.0' }, after).narrow).toBeUndefined();
|
||||
});
|
||||
|
||||
it('整组都空了才重新认锚 —— 这次谁先填谁当', () => {
|
||||
const anchors = { 'chip-type': 'originalInternalPn' };
|
||||
const cleared = nextAnchors(PARAMS, { originalInternalPn: '', candidateInternalPn: '' }, anchors);
|
||||
expect(cleared).toEqual({});
|
||||
expect(nextAnchors(PARAMS, { originalInternalPn: '', candidateInternalPn: 'AMS1117-3.3' }, cleared)).toEqual({ 'chip-type': 'candidateInternalPn' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('comboFace —— 这一格摆哪些候选', () => {
|
||||
it('没有锚:全量,按类型分组,**未标类型殿后**', () => {
|
||||
const f = face('originalInternalPn', {});
|
||||
expect(f.narrow).toBeUndefined();
|
||||
expect(f.groups.map((g) => [g.label, g.options.map((o) => o.value)])).toEqual([
|
||||
['DC-DC 降压稳压器', ['LM2596S-5.0', 'XL2596S-5.0']],
|
||||
['LDO 线性稳压器', ['AMS1117-3.3']],
|
||||
['未标类型', ['OLD-001']],
|
||||
]);
|
||||
});
|
||||
|
||||
it('锚自己永远摆全量 —— 人想换类型,回去改锚就行', () => {
|
||||
const anchors = { 'chip-type': 'originalInternalPn' };
|
||||
const f = face('originalInternalPn', { originalInternalPn: 'LM2596S-5.0' }, anchors);
|
||||
expect(f.narrow).toBeUndefined();
|
||||
expect(f.groups.flatMap((g) => g.options.map((o) => o.value))).toEqual(['LM2596S-5.0', 'XL2596S-5.0', 'AMS1117-3.3', 'OLD-001']);
|
||||
});
|
||||
|
||||
it('另一格按锚的类型收窄:同类型在前、未标类型殿后;点名按谁的什么类型收的', () => {
|
||||
const f = face('candidateInternalPn', { originalInternalPn: 'LM2596S-5.0' }, { 'chip-type': 'originalInternalPn' });
|
||||
expect(f.narrow).toEqual({ by: '原内部料号', label: 'DC-DC 降压稳压器' });
|
||||
expect(f.groups.map((g) => [g.label, g.options.map((o) => o.value)])).toEqual([
|
||||
['DC-DC 降压稳压器', ['LM2596S-5.0', 'XL2596S-5.0']],
|
||||
['未标类型', ['OLD-001']],
|
||||
]);
|
||||
});
|
||||
|
||||
it('**锚本身没有类型**(未标类型的老料)→ 另一格摆全部(用户原话)', () => {
|
||||
const f = face('candidateInternalPn', { originalInternalPn: 'OLD-001' }, { 'chip-type': 'originalInternalPn' });
|
||||
expect(f.narrow).toBeUndefined();
|
||||
expect(f.groups.flatMap((g) => g.options)).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('**锚填的是新增料号**(库里没有这个值)→ 收窄无从谈起,另一格摆全部', () => {
|
||||
const f = face('candidateInternalPn', { originalInternalPn: 'BRAND-NEW-PN' }, { 'chip-type': 'originalInternalPn' });
|
||||
expect(f.narrow).toBeUndefined();
|
||||
expect(f.groups.flatMap((g) => g.options)).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('类型只有键没有名(字典读不到)→ 分组标题退回摆键本身,不编一个名字', () => {
|
||||
const bare: LaunchParam = { ...PARAMS[1]!, options: [{ value: 'A', label: 'A', group: '7' }] };
|
||||
const f = comboFace(bare, [PARAMS[0]!, bare], {}, {});
|
||||
expect(f.groups.map((g) => g.label)).toEqual(['7']);
|
||||
});
|
||||
|
||||
it('不联动的 combo(没有 linkGroup / 候选不带分组键)→ 一组无标题,谁也不管它', () => {
|
||||
const f = comboFace(LOOSE, [...PARAMS, LOOSE], { originalInternalPn: 'LM2596S-5.0' }, { 'chip-type': 'originalInternalPn' });
|
||||
expect(f.narrow).toBeUndefined();
|
||||
expect(f.groups).toEqual([{ label: undefined, options: LOOSE.options }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('staleLinked —— 锚换了类型,另一格里失配的值', () => {
|
||||
it('在全量候选里、却不在收窄后的候选里 → 点名清空', () => {
|
||||
expect(staleLinked(PARAMS, { originalInternalPn: 'AMS1117-3.3', candidateInternalPn: 'XL2596S-5.0' }, { 'chip-type': 'originalInternalPn' })).toEqual(['candidateInternalPn']);
|
||||
});
|
||||
|
||||
it('同类型的、未标类型的、人自己新增的、空的 —— 一个都不动', () => {
|
||||
const anchors = { 'chip-type': 'originalInternalPn' };
|
||||
const keep = (candidate: string) => staleLinked(PARAMS, { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: candidate }, anchors);
|
||||
expect(keep('XL2596S-5.0')).toEqual([]);
|
||||
expect(keep('OLD-001')).toEqual([]);
|
||||
// 新增的值压根不在候选里 —— 它是人自己写的新料号,不是失配的旧选择
|
||||
expect(keep('BRAND-NEW-PN')).toEqual([]);
|
||||
expect(keep('')).toEqual([]);
|
||||
});
|
||||
|
||||
it('锚自己不算失配(它摆的就是全量)', () => {
|
||||
expect(staleLinked(PARAMS, { originalInternalPn: 'AMS1117-3.3', candidateInternalPn: '' }, { 'chip-type': 'originalInternalPn' })).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user