From 9ae30bd412c504adc0bb845232a9ae5cacd66896 Mon Sep 17 00:00:00 2001 From: Igor Velho Date: Mon, 24 Aug 2026 14:47:59 +0100 Subject: [PATCH] test: add computeSubtreeCost coverage (flat, nested, cycle, zero-cost, unknown id) --- .../chat/work-status/subagentCost.test.ts | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/chat/work-status/subagentCost.test.ts b/packages/ui/src/components/chat/work-status/subagentCost.test.ts index 8c3fbeb3..059f2724 100644 --- a/packages/ui/src/components/chat/work-status/subagentCost.test.ts +++ b/packages/ui/src/components/chat/work-status/subagentCost.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'bun:test'; import type { Session } from '@opencode-ai/sdk/v2'; -import { buildChildrenIndex, formatCost } from './subagentCost'; +import { buildChildrenIndex, computeSubtreeCost, formatCost } from './subagentCost'; function makeSession(id: string, cost: number, parentID?: string): Session { return { id, cost, parentID } as unknown as Session; @@ -23,3 +23,50 @@ describe('formatCost', () => { expect(formatCost(2)).toBe('$2'); }); }); + +describe('computeSubtreeCost', () => { + test('sums a flat root with two direct children', () => { + const root = makeSession('root', 1); + const childA = makeSession('a', 2, 'root'); + const childB = makeSession('b', 3, 'root'); + const sessions = [root, childA, childB]; + const sessionsById = new Map(sessions.map((s) => [s.id, s])); + const childrenByParent = buildChildrenIndex(sessions); + expect(computeSubtreeCost('root', sessionsById, childrenByParent)).toBe(6); + }); + + test('rolls up cost through nested descendants', () => { + const root = makeSession('root', 1); + const child = makeSession('child', 2, 'root'); + const grandchild = makeSession('grandchild', 4, 'child'); + const sessions = [root, child, grandchild]; + const sessionsById = new Map(sessions.map((s) => [s.id, s])); + const childrenByParent = buildChildrenIndex(sessions); + expect(computeSubtreeCost('root', sessionsById, childrenByParent)).toBe(7); + expect(computeSubtreeCost('child', sessionsById, childrenByParent)).toBe(6); + }); + + test('does not double-count or infinite-loop on a cycle', () => { + const a = makeSession('a', 1, 'b'); + const b = makeSession('b', 2, 'a'); + const sessions = [a, b]; + const sessionsById = new Map(sessions.map((s) => [s.id, s])); + const childrenByParent = buildChildrenIndex(sessions); + expect(computeSubtreeCost('a', sessionsById, childrenByParent)).toBe(3); + }); + + test('treats zero and undefined cost as zero, not a break', () => { + const root = makeSession('root', 0); + const child = { id: 'child', parentID: 'root' } as unknown as Session; + const sessions = [root, child]; + const sessionsById = new Map(sessions.map((s) => [s.id, s])); + const childrenByParent = buildChildrenIndex(sessions); + expect(computeSubtreeCost('root', sessionsById, childrenByParent)).toBe(0); + }); + + test('returns 0 for an unknown id', () => { + const sessionsById = new Map(); + const childrenByParent = new Map(); + expect(computeSubtreeCost('missing', sessionsById, childrenByParent)).toBe(0); + }); +});