/** 清单一句话与检索 */ import { describe, expect, it } from 'vitest'; import type { InstanceSummary } from '@contracts'; import { staffIdSchema } from '@contracts'; import { fieldSpecsOf } from '../activity-ops/launch-params.ts'; import { applyInstanceFilters, clip, inputFilesOf, inputSummaryOf, outputSummaryOf, pageOf, statsOf } from '../activity-ops/summaries.ts'; const specs = fieldSpecsOf({ properties: { customerCode: { type: 'string' }, requestDescription: { type: 'string', 'x-control': 'textarea' }, datasheets: { type: 'array', format: 'file' }, }, }); describe('inputSummaryOf / inputFilesOf / outputSummaryOf', () => { it('textarea 那格优先;没有就最长的字符串;再没有退回来件主题;文件字段不算', () => { expect(inputSummaryOf({ customerCode: 'CU-0142', requestDescription: ' 工业电源项目\n在用 LM2596S-5.0 ' }, specs)).toBe('工业电源项目 在用 LM2596S-5.0'); expect(inputSummaryOf({ customerCode: 'CU-0142', datasheets: [{ ref: 'r', name: 'a-very-long-file-name.pdf' }] }, specs)).toBe('CU-0142'); expect(inputSummaryOf({}, specs, 'RE: 询价')).toBe('RE: 询价'); expect(inputSummaryOf({}, specs)).toBe(''); expect(clip('x'.repeat(100))).toHaveLength(80); expect(clip('x'.repeat(100)).endsWith('…')).toBe(true); }); it('文件只认带 ref 的;index 跨字段连续', () => { const files = inputFilesOf({ datasheets: [{ name: 'a.pdf', contentType: 'application/pdf', size: 10, ref: 'u-1' }, { name: 'no-ref' }, 'junk'] }, specs); expect(files).toEqual([{ index: 0, name: 'a.pdf', contentType: 'application/pdf', size: 10, ref: 'u-1' }]); }); it('产出:有文件摆文件名;没有摆发口;都没有 null', () => { expect(outputSummaryOf(['报告.docx', '对照表.xlsx'], ['x'])).toBe('报告.docx、对照表.xlsx'); expect(outputSummaryOf([], ['assessment-released'])).toBe('已发口:assessment-released'); expect(outputSummaryOf([], [])).toBeNull(); }); }); const owner = { id: 's-1', name: '柴浩' }; const mk = (id: string, no: string, state: InstanceSummary['state'], startedAt: string, ownerId = 's-1'): InstanceSummary => ({ id, no, state, owner: { id: staffIdSchema.parse(ownerId), name: owner.name }, input: 'x', output: null, startedAt, }); const items = [ mk('t-aaa', 'INS-24', 'running', '2026-08-25T02:12:00.000Z'), mk('t-bbb', 'INS-17', 'completed', '2026-08-24T09:03:00.000Z', 's-2'), mk('t-ccc', 'INS-9', 'failed', '2026-08-20T01:00:00.000Z'), ]; describe('applyInstanceFilters / statsOf / pageOf', () => { it('实例号模糊、负责人精确、状态精确、日期段含两端整日(本地时区)', () => { expect(applyInstanceFilters(items, { id: 'ins-2' }).map((i) => i.no)).toEqual(['INS-24']); expect(applyInstanceFilters(items, { id: 'bbb' }).map((i) => i.no)).toEqual(['INS-17']); expect(applyInstanceFilters(items, { ownerId: 's-2' }).map((i) => i.no)).toEqual(['INS-17']); expect(applyInstanceFilters(items, { state: 'failed' }).map((i) => i.no)).toEqual(['INS-9']); const day = (iso: string) => { const d = new Date(iso); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; }; expect(applyInstanceFilters(items, { startedFrom: day('2026-08-24T09:03:00.000Z'), startedTo: day('2026-08-24T09:03:00.000Z') }).map((i) => i.no)).toEqual(['INS-17']); expect(applyInstanceFilters(items, { startedFrom: day('2026-08-24T09:03:00.000Z') }).map((i) => i.no)).toEqual(['INS-24', 'INS-17']); expect(applyInstanceFilters(items, { startedTo: day('2026-08-20T01:00:00.000Z') }).map((i) => i.no)).toEqual(['INS-9']); expect(applyInstanceFilters(items, {})).toHaveLength(3); }); it('统计五键齐;分页越界给空', () => { expect(statsOf(items)).toEqual({ total: 3, byState: { pending: 0, running: 1, completed: 1, rejected: 0, failed: 1 } }); expect(pageOf(items, 1, 2).map((i) => i.no)).toEqual(['INS-24', 'INS-17']); expect(pageOf(items, 2, 2).map((i) => i.no)).toEqual(['INS-9']); expect(pageOf(items, 3, 2)).toEqual([]); }); });