/* * 상태별 내림 — **수 하나가 뜻을 바꾸지 않게** (2026-08-23). * * 커널이 관측 흡수에서 완료된 작업을 떨어뜨리지 않게 고쳐진 뒤, 개념 지도의 작업 카드가 0 에서 2,820 이 * 되었다. 카드는 상태의 인구조사이므로 그 수가 맞지만, 사람은 「지금 할 일 2,820건」으로 읽는다. * 수를 줄여 맞는 척하게 만들지 않고, 무엇으로 이루어진 수인지 함께 낸다. * * 이 시험이 지키는 것: * ① 상태별로 구분해 세고, **많은 것부터** 낸다(카드가 한 줄로 보이므로 순서가 뜻을 만든다). * ② 낱말은 커널의 것 그대로 — 여기서 묶거나 고쳐 부르지 않는다. * ③ 상태를 말하지 않는 축은 **필드를 만들지 않는다**(빈 표는 「상태 없음」과 「못 읽음」을 구분하지 못한다). */ import { test } from 'node:test' import assert from 'node:assert/strict' import { statusTally } from '../server/service/twin-model/status-tally.ts' test('상태별로 구분해 세고 많은 것부터 낸다', () => { const r = statusTally([ { id: 'a', status: 'completed' }, { id: 'b', status: 'in-progress' }, { id: 'c', status: 'completed' }, { id: 'd', status: 'completed' } ]) assert.deepEqual(r.statusCounts, [ { status: 'completed', count: 3 }, { status: 'in-progress', count: 1 } ]) }) test('낱말은 커널의 것 그대로 — 묶거나 고쳐 부르지 않는다', () => { const r = statusTally([{ status: 'assigned' }, { status: 'created' }]) assert.deepEqual( r.statusCounts?.map(s => s.status).sort(), ['assigned', 'created'], '「대기」처럼 앱의 낱말로 바꾸면 커널이 늘린 낱말을 화면이 알리지 않고 잘못 부른다' ) }) test('상태를 말하지 않는 축은 필드를 만들지 않는다', () => { assert.deepEqual(statusTally([{ id: 'loc-1' }, { id: 'loc-2' }]), {}, '빈 표를 내지 않는다') assert.deepEqual(statusTally([]), {}) assert.deepEqual(statusTally(null), {}) assert.deepEqual(statusTally({ id: 'not-a-list' }), {}, '목록이 아니면 셀 것이 없다') }) test('상태를 말하는 것만 센다 — 없는 것을 「모름」이라는 갈래로 만들지 않는다', () => { const r = statusTally([{ status: 'completed' }, { id: 'x' }, { status: '' }, { status: 7 }]) assert.deepEqual(r.statusCounts, [{ status: 'completed', count: 1 }]) }) test('한 갈래뿐이면 그대로 한 갈래다 — 화면이 그때는 내림을 감춘다', () => { const r = statusTally([{ status: 'completed' }, { status: 'completed' }]) assert.deepEqual(r.statusCounts, [{ status: 'completed', count: 2 }], '판단은 화면이 하고, 사실은 그대로 낸다') })