// @vitest-environment jsdom // CostDock presentation: renders the running session cost from the // openRouterCost projection plus the subagent subtree read off the session // list, and nothing at all until a step was priced. import { act, cleanup, render, screen } from '@testing-library/react' import { afterEach, describe, expect, it, vi } from 'vitest' import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime' import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts' import { CostDock } from '../src/client/CostDock.tsx' import { zh } from '../src/client/locales.ts' afterEach(cleanup) const t = makeTranslate(zh, commonZh) const PARENT = 'session-parent' /** One session-list row as much of it as the dock reads. */ interface Row { id: string parentId?: string origin?: 'subagent' cost?: { totalUsd: number; pricedSteps: number; unknownModelSteps: number } } /** A `useSessions` stub serving `byId` built from the given rows. */ function sessionsHook(rows: readonly Row[]) { const byId: Record = {} for (const row of rows) { byId[row.id] = { id: row.id, ...row.parentId === undefined ? {} : { parentId: row.parentId }, ...row.origin === undefined ? {} : { origin: row.origin }, ...row.cost === undefined ? {} : { projectionValues: { openRouterCost: { ...row.cost, currency: 'USD' } } }, } } return (selector: (state: { byId: Record }) => unknown) => selector({ byId }) } function makeProps(over: Partial[0]> = {}, rows: readonly Row[] = []) { return { sessionId: PARENT, useProjection: vi.fn(() => undefined), useSessions: sessionsHook(rows), t, ...over, } as unknown as Parameters[0] } describe('CostDock', () => { it('renders nothing while the projection is absent or priced zero steps', () => { for (const value of [undefined, { totalUsd: 0, pricedSteps: 0, unknownModelSteps: 0, currency: 'USD' }]) { const { container } = render( value })} />) expect(container.firstChild).toBeNull() cleanup() } }) it('renders the priced session cost as a USD figure', () => { render( ({ totalUsd: 1.2345, pricedSteps: 2, unknownModelSteps: 0, currency: 'USD' }), })} />) expect(screen.getByText('会话费用 $1.23')).toBeTruthy() }) it('surfaces the unknown-pricing step count in a tooltip when present', () => { render( ({ totalUsd: 0.5, pricedSteps: 1, unknownModelSteps: 3, currency: 'USD' }), })} />) expect(screen.getByText('会话费用 $0.50').getAttribute('title')).toBe('包含 3 个未知定价步骤') }) it('omits the tooltip when every step priced', () => { render( ({ totalUsd: 0.5, pricedSteps: 1, unknownModelSteps: 0, currency: 'USD' }), })} />) expect(screen.getByText('会话费用 $0.50').getAttribute('title')).toBeNull() }) it('reads the projection through the openRouterCost key', () => { const useProjection = vi.fn(() => undefined) render() act(() => {}) expect(useProjection).toHaveBeenCalledWith('openRouterCost') }) it('adds the subagent subtree to the figure and names its share', () => { render( ({ totalUsd: 1, pricedSteps: 1, unknownModelSteps: 0, currency: 'USD' }) }, [{ id: 'child', parentId: PARENT, origin: 'subagent', cost: { totalUsd: 0.5, pricedSteps: 1, unknownModelSteps: 1 } }], )} />) const row = screen.getByText('会话费用 $1.50(含子代理 $0.50)') expect(row.getAttribute('title')).toBe('包含 1 个未知定价步骤') }) it('follows a nested subagent chain and ignores non-subagent children', () => { render( ({ totalUsd: 1, pricedSteps: 1, unknownModelSteps: 0, currency: 'USD' }) }, [ { id: 'child', parentId: PARENT, origin: 'subagent', cost: { totalUsd: 0.25, pricedSteps: 1, unknownModelSteps: 0 } }, { id: 'grandchild', parentId: 'child', origin: 'subagent', cost: { totalUsd: 0.125, pricedSteps: 1, unknownModelSteps: 0 } }, // An ordinary fork of the same parent is not delegated work. { id: 'sibling', parentId: PARENT, cost: { totalUsd: 9, pricedSteps: 1, unknownModelSteps: 0 } }, ], )} />) expect(screen.getByText('会话费用 $1.38(含子代理 $0.38)')).toBeTruthy() }) it('shows the subagent subtree alone when the session itself priced nothing', () => { render( undefined }, [{ id: 'child', parentId: PARENT, origin: 'subagent', cost: { totalUsd: 0.75, pricedSteps: 2, unknownModelSteps: 0 } }], )} />) expect(screen.getByText('会话费用 $0.75(含子代理 $0.75)')).toBeTruthy() }) it('skips a descendant the client holds no projection value for', () => { render( ({ totalUsd: 2, pricedSteps: 1, unknownModelSteps: 0, currency: 'USD' }) }, [{ id: 'cold-child', parentId: PARENT, origin: 'subagent' }], )} />) expect(screen.getByText('会话费用 $2.00')).toBeTruthy() }) it('terminates on a parentId cycle', () => { render( ({ totalUsd: 1, pricedSteps: 1, unknownModelSteps: 0, currency: 'USD' }) }, [ { id: 'a', parentId: PARENT, origin: 'subagent', cost: { totalUsd: 0.5, pricedSteps: 1, unknownModelSteps: 0 } }, { id: PARENT, parentId: 'a', origin: 'subagent', cost: { totalUsd: 99, pricedSteps: 1, unknownModelSteps: 0 } }, ], )} />) expect(screen.getByText('会话费用 $1.50(含子代理 $0.50)')).toBeTruthy() }) })