import { describe, expect, it } from 'vitest'; import { enforceBudget } from '../budget'; import type { CSTNode } from '../../cst/types'; describe('enforceBudget', () => { it('leaves a small snapshot untouched', () => { const children: CSTNode[] = [ { type: 'text', content: 'short' }, { type: 'interactive', role: 'button', ref: '@e1', name: 'Go' }, ]; const result = enforceBudget(children, 8_000); expect(result.degradation).toBe('none'); expect(result.children).toEqual(children); }); it('truncates long text when over budget', () => { const children: CSTNode[] = [ { type: 'text', content: 'x'.repeat(5_000) }, ]; const result = enforceBudget(children, 100); expect(result.degradation).toBe('truncated'); const text = result.children[0]; expect(text.type).toBe('text'); if (text.type === 'text') { expect(text.content).toContain('(truncated)'); expect(text.content.length).toBeLessThan(200); } }); it('falls back to interactive-only when truncation is not enough', () => { // Many text nodes — truncation alone still over a tiny budget. const children: CSTNode[] = Array.from({ length: 200 }, (_, i) => ({ type: 'text' as const, content: `paragraph ${i} `.repeat(20), })); children.push({ type: 'interactive', role: 'button', ref: '@e1', name: 'Submit', }); const result = enforceBudget(children, 50); expect(result.degradation).toBe('interactive-only'); // Only the interactive node survives. expect(result.children.every((n) => n.type === 'interactive')).toBe(true); expect(result.children).toHaveLength(1); }); });