profileShare

rasmusjy / roundtable

Read-only snapshot

No repository description.

main default branch 181 files Expires Sep 13, 2026, 9:06 AM

Commit

test formatting/visual helpers and fix truncate length

commit ce5bb93

3 changed files with +81 and −2

Jump to a changed file
  1. src/lib/model-visuals.test.ts +34 −0
  2. src/lib/utils.test.ts +44 −0
  3. src/lib/utils.ts +3 −2
added src/lib/model-visuals.test.ts +34 −0
@@ -0,0 +1,34 @@
1 +import { describe, expect, it } from 'vitest';
2 +import { participantColor, participantTag, scoreColor, stageColorVar } from './model-visuals';
3 +
4 +describe('participantTag', () => {
5 + it('maps index to letters', () => {
6 + expect(participantTag(0)).toBe('A');
7 + expect(participantTag(2)).toBe('C');
8 + });
9 +});
10 +
11 +describe('participantColor', () => {
12 + it('is a stable hsl string and cycles the palette', () => {
13 + expect(participantColor(0)).toMatch(/^hsl\(/);
14 + expect(participantColor(0)).toBe(participantColor(6)); // 6-colour palette wraps
15 + expect(participantColor(0)).not.toBe(participantColor(1));
16 + });
17 +});
18 +
19 +describe('scoreColor', () => {
20 + it('shifts hue upward with the score', () => {
21 + const low = scoreColor(1);
22 + const high = scoreColor(10);
23 + const hueOf = (s: string) => Number(s.match(/hsl\(([\d.]+)/)![1]);
24 + expect(hueOf(high)).toBeGreaterThan(hueOf(low));
25 + });
26 +});
27 +
28 +describe('stageColorVar', () => {
29 + it('maps each stage to its css variable', () => {
30 + expect(stageColorVar('answer')).toBe('var(--stage-answer)');
31 + expect(stageColorVar('synthesis')).toBe('var(--stage-synthesis)');
32 + expect(stageColorVar('unknown')).toBe('var(--primary)');
33 + });
34 +});
added src/lib/utils.test.ts +44 −0
@@ -0,0 +1,44 @@
1 +import { describe, expect, it } from 'vitest';
2 +import { cn, formatDuration, formatLatency, formatTokens, formatUsd, truncate } from './utils';
3 +
4 +describe('formatUsd', () => {
5 + it('adapts precision to magnitude', () => {
6 + expect(formatUsd(0)).toBe('$0.00');
7 + expect(formatUsd(0.0005)).toBe('$0.0005');
8 + expect(formatUsd(0.25)).toBe('$0.250');
9 + expect(formatUsd(12.5)).toBe('$12.50');
10 + });
11 +});
12 +
13 +describe('formatTokens', () => {
14 + it('abbreviates thousands and millions', () => {
15 + expect(formatTokens(500)).toBe('500');
16 + expect(formatTokens(1500)).toBe('1.5k');
17 + expect(formatTokens(20000)).toBe('20k');
18 + expect(formatTokens(2_000_000)).toBe('2.0M');
19 + });
20 +});
21 +
22 +describe('formatLatency / formatDuration', () => {
23 + it('formats ms and seconds', () => {
24 + expect(formatLatency(450)).toBe('450ms');
25 + expect(formatLatency(1500)).toBe('1.5s');
26 + expect(formatDuration(45_000)).toBe('45s');
27 + expect(formatDuration(125_000)).toBe('2m 5s');
28 + });
29 +});
30 +
31 +describe('truncate', () => {
32 + it('leaves short strings and ellipsizes long ones within max length', () => {
33 + expect(truncate('hello', 10)).toBe('hello');
34 + expect(truncate('hello world', 5)).toBe('he...');
35 + expect(truncate('hello world', 5).length).toBe(5);
36 + });
37 +});
38 +
39 +describe('cn', () => {
40 + it('merges and de-conflicts tailwind classes', () => {
41 + expect(cn('p-2', 'p-4')).toBe('p-4');
42 + expect(cn('text-sm', false && 'hidden', 'font-bold')).toBe('text-sm font-bold');
43 + });
44 +});
modified src/lib/utils.ts +3 −2
@@ -45,7 +45,8 @@export function formatRelativeTime(date: Date | string): string {
45 45 return d.toLocaleDateString();
46 46 }
47 47
48 -/** Truncate to a max length with an ellipsis. */
48 +/** Truncate to a max length (including the trailing "..."). */
49 49 export function truncate(s: string, max: number): string {
50 - return s.length <= max ? s : `${s.slice(0, max - 1)}...`;
50 + if (s.length <= max) return s;
51 + return `${s.slice(0, Math.max(0, max - 3))}...`;
51 52 }