import { describe, expect, it } from 'vitest'; import { summarizeFieldOptions, describeChoiceCondition, choiceRows, formatFieldDetail, } from './field-display'; describe('summarizeFieldOptions (field list --with-options column)', () => { it('counts select choices without conditions', () => { const field = { type: 'singleSelect', options: { choices: [{ name: 'A' }, { name: 'B' }, { name: 'C' }] }, }; expect(summarizeFieldOptions(field)).toBe('3 choices'); }); it('counts conditional choices (visibilityFilter)', () => { const field = { type: 'singleSelect', options: { choices: [ { name: 'A' }, { name: 'B', visibilityFilter: { conjunction: 'and', filterSet: [] } }, { name: 'C', visibilityFilter: { conjunction: 'and', filterSet: [] } }, ], }, }; expect(summarizeFieldOptions(field)).toBe('3 choices · 2 conditional'); }); it('summarizes rating options', () => { expect(summarizeFieldOptions({ type: 'rating', options: { max: 5, icon: 'star' } })).toBe( 'max=5 icon=star' ); }); it('summarizes number precision', () => { expect( summarizeFieldOptions({ type: 'number', options: { formatting: { precision: 2 } }, }) ).toBe('precision=2'); }); it('summarizes date formatting', () => { expect( summarizeFieldOptions({ type: 'date', options: { formatting: { date: 'YYYY-MM-DD', time: 'HH:mm' } }, }) ).toBe('YYYY-MM-DD HH:mm'); }); it('returns dash when no recognizable options', () => { expect(summarizeFieldOptions({ type: 'singleLineText' })).toBe('—'); expect(summarizeFieldOptions({ type: 'checkbox', options: {} })).toBe('—'); }); }); describe('describeChoiceCondition (visibilityFilter summary)', () => { it('returns (always) when no filter', () => { expect(describeChoiceCondition(undefined)).toBe('(always)'); expect(describeChoiceCondition(null)).toBe('(always)'); }); it('renders a single is rule with resolved field name', () => { const vf = { conjunction: 'and', filterSet: [{ fieldId: 'fldA', operator: 'is', value: '物流问题-丢件' }], }; expect(describeChoiceCondition(vf, { fldA: '问题类型' })).toBe('问题类型 is 物流问题-丢件'); }); it('falls back to fieldId when no name map', () => { const vf = { conjunction: 'and', filterSet: [{ fieldId: 'fldA', operator: 'is', value: 'X' }], }; expect(describeChoiceCondition(vf)).toBe('fldA is X'); }); it('joins rules with AND / OR and array values with |', () => { const vfOr = { conjunction: 'or', filterSet: [ { fieldId: 'fldA', operator: 'is', value: ['A1', 'A2'] }, { fieldId: 'fldA', operator: 'is', value: 'B' }, ], }; expect(describeChoiceCondition(vfOr, { fldA: '问题类型' })).toBe( '问题类型 is A1|A2 OR 问题类型 is B' ); const vfAnd = { conjunction: 'and', filterSet: [ { fieldId: 'fldA', operator: 'isNot', value: 'X' }, { fieldId: 'fldB', operator: 'isNotEmpty', value: null }, ], }; expect(describeChoiceCondition(vfAnd, { fldA: '甲', fldB: '乙' })).toBe( '甲 isNot X AND 乙 isNotEmpty' ); }); it('truncates long rule lists with +N more', () => { const vf = { conjunction: 'or', filterSet: Array.from({ length: 6 }, (_, i) => ({ fieldId: 'fldA', operator: 'is', value: `value-number-${i}`, })), }; const out = describeChoiceCondition(vf, { fldA: 'F' }); // 4 rules fit the budget → the marker must report exactly the 2 omitted. expect(out).toMatch(/\+2 more$/); expect(out).not.toContain('value-number-4'); expect(out).not.toContain('value-number-5'); }); }); describe('choiceRows (select field → display rows)', () => { it('maps choices to name/color/when rows', () => { const field = { type: 'singleSelect', options: { choices: [ { name: 'A', color: 'red' }, { name: 'B', visibilityFilter: { conjunction: 'and', filterSet: [{ fieldId: 'fldX', operator: 'is', value: 'V' }], }, }, ], }, }; const rows = choiceRows(field, { fldX: 'X' }); expect(rows).toEqual([ { name: 'A', color: 'red', when: '(always)' }, { name: 'B', color: '', when: 'X is V' }, ]); }); it('returns [] for non-select fields', () => { expect(choiceRows({ type: 'number', options: {} })).toEqual([]); expect(choiceRows({ type: 'singleSelect', options: {} })).toEqual([]); }); }); describe('formatFieldDetail (field get human mode)', () => { it('expands select choices into lines with conditions', () => { const field = { id: 'fld1', name: '处理反馈', type: 'singleSelect', unique: false, options: { defaultValue: 'A', choices: [ { name: 'A', color: 'blue' }, { name: 'B', visibilityFilter: { conjunction: 'and', filterSet: [{ fieldId: 'fldT', operator: 'is', value: '丢件' }], }, }, ], }, }; const out = formatFieldDetail(field, { fldT: '问题类型' }); expect(out).toContain('处理反馈'); expect(out).toMatch(/choices \(2 · 1 conditional\)/); expect(out).toContain('(always)'); expect(out).toContain('问题类型 is 丢件'); expect(out).toContain('defaultValue'); // non-choice options still visible }); it('keeps non-select options as a JSON line', () => { const field = { id: 'fld2', name: 'Score', type: 'rating', options: { max: 5, icon: 'star' }, }; const out = formatFieldDetail(field); expect(out).toContain('max'); expect(out).toContain('"max":5'); }); });