Spaces:
Sleeping
Sleeping
| import { describe, it, expect } from 'vitest'; | |
| import { | |
| resolveHoverTelemetry, | |
| formatActivationValue, | |
| formatHoverTokenLabel, | |
| estimateMonospaceChars, | |
| activationDisplayBudget, | |
| MAX_ACTIVATION_DECIMALS, | |
| } from '../src/ui/hoverTelemetry.js'; | |
| describe('resolveHoverTelemetry', () => { | |
| it('returns null for empty hover', () => { | |
| expect(resolveHoverTelemetry(null)).toBeNull(); | |
| expect(resolveHoverTelemetry(undefined)).toBeNull(); | |
| }); | |
| it('reads activation from pointsData[index] (POINTS mesh)', () => { | |
| const data = { | |
| index: 1, | |
| point: { x: 10, y: 2, z: 0 }, | |
| userData: { | |
| pointsData: [ | |
| { activation: 0.01, meta: { type: 'compare', token: 'cat', dim: 0, val: 0.01 } }, | |
| { activation: -0.0421, meta: { type: 'compare', token: 'cat', dim: 1, val: -0.0421 } }, | |
| ], | |
| }, | |
| }; | |
| const t = resolveHoverTelemetry(data); | |
| expect(t.activation).toBe(-0.0421); | |
| expect(t.token).toBe('cat'); | |
| expect(t.tokenLabel).toBe('cat'); | |
| expect(t.groupLabel).toBeNull(); | |
| expect(t.type).toBe('COMPARE'); | |
| expect(t.dim).toBe(1); | |
| expect(t.point).toEqual({ x: 10, y: 2, z: 0 }); | |
| }); | |
| it('formats grouped token as group/token without spaces', () => { | |
| expect(formatHoverTokenLabel('car', 'vehicles')).toBe('vehicles/car'); | |
| expect(formatHoverTokenLabel('car', null)).toBe('car'); | |
| expect(formatHoverTokenLabel('car', '')).toBe('car'); | |
| const t = resolveHoverTelemetry({ | |
| index: 0, | |
| userData: { | |
| pointsData: [{ | |
| activation: 0.2, | |
| groupId: 'vehicles', | |
| groupLabel: 'vehicles', | |
| meta: { | |
| type: 'compare', | |
| token: 'car', | |
| dim: 0, | |
| val: 0.2, | |
| groupId: 'vehicles', | |
| groupLabel: 'vehicles', | |
| }, | |
| }], | |
| }, | |
| }); | |
| expect(t.token).toBe('car'); | |
| expect(t.groupLabel).toBe('vehicles'); | |
| expect(t.tokenLabel).toBe('vehicles/car'); | |
| }); | |
| it('maps ribbon actIdx through sourceDims when present', () => { | |
| const data = { | |
| index: 4, | |
| face: { a: 6, b: 7, c: 8 }, | |
| userData: { | |
| kind: 'wideRibbon', | |
| activations: [0.1, 0.2, 0.3, 0.4], | |
| sourceDims: [10, 20, 30, 40], | |
| token: 'dog', | |
| type: 'compare', | |
| }, | |
| }; | |
| const t = resolveHoverTelemetry(data); | |
| expect(t.activation).toBe(0.4); | |
| expect(t.dim).toBe(40); | |
| }); | |
| it('does not fall back to 0 when pointsData has a real value (regression)', () => { | |
| // Legacy HUD read userData.val / data.activation — both missing on Points meshes | |
| const data = { | |
| index: 0, | |
| point: { x: 1, y: 0, z: 0 }, | |
| userData: { | |
| pointsData: [{ activation: 1.23e-7, meta: { dim: 0, val: 1.23e-7, type: 'res' } }], | |
| }, | |
| }; | |
| expect(resolveHoverTelemetry(data).activation).toBe(1.23e-7); | |
| }); | |
| it('reads activations[] on wideRibbon via face vertex index', () => { | |
| const data = { | |
| index: 4, | |
| face: { a: 6, b: 7, c: 8 }, | |
| userData: { | |
| kind: 'wideRibbon', | |
| activations: [0.1, 0.2, 0.3, 0.4, 0.5], | |
| token: 'dog', | |
| type: 'compare', | |
| }, | |
| }; | |
| const t = resolveHoverTelemetry(data); | |
| expect(t.activation).toBe(0.4); // floor(6/2) = 3 | |
| expect(t.token).toBe('dog'); | |
| }); | |
| it('falls back to DIM #n when no token meta', () => { | |
| const t = resolveHoverTelemetry({ | |
| index: 12, | |
| userData: { | |
| pointsData: [{ activation: 0.5, meta: { dim: 12, val: 0.5 } }], | |
| }, | |
| }); | |
| expect(t.token).toBe('DIM #12'); | |
| }); | |
| }); | |
| describe('formatActivationValue', () => { | |
| it('caps decimals at 32', () => { | |
| expect(MAX_ACTIVATION_DECIMALS).toBe(32); | |
| const tiny = 1e-30; | |
| const s = formatActivationValue(tiny, { maxDecimals: 32, maxChars: 40 }); | |
| expect(s.includes('e') || s.length <= 40).toBe(true); | |
| // Must not be the old HUD "0.0000" | |
| expect(s).not.toBe('0.0000'); | |
| expect(Number(s)).not.toBe(0); | |
| }); | |
| it('shows small magnitudes that toFixed(4) would hide', () => { | |
| const s = formatActivationValue(0.00001234, { maxDecimals: 32, maxChars: 20 }); | |
| expect(s).not.toMatch(/^0\.0+$/); | |
| expect(Math.abs(Number(s) - 0.00001234)).toBeLessThan(1e-8); | |
| }); | |
| it('shrinks to fit maxChars without overflowing', () => { | |
| const s = formatActivationValue(0.123456789012345, { maxDecimals: 32, maxChars: 8 }); | |
| expect(s.length).toBeLessThanOrEqual(8); | |
| expect(s).not.toBe('0'); | |
| }); | |
| it('uses scientific when fixed form cannot fit', () => { | |
| const s = formatActivationValue(1.23456789e-20, { maxDecimals: 32, maxChars: 10 }); | |
| expect(s.length).toBeLessThanOrEqual(10); | |
| expect(s).toMatch(/e/i); | |
| }); | |
| it('formats zero as 0', () => { | |
| expect(formatActivationValue(0, { maxChars: 10 })).toBe('0'); | |
| }); | |
| }); | |
| describe('activationDisplayBudget', () => { | |
| it('estimates monospace capacity', () => { | |
| expect(estimateMonospaceChars(120, 12)).toBeGreaterThan(10); | |
| }); | |
| it('switches to compact ACT: label when slot is narrow (mobile)', () => { | |
| const wide = activationDisplayBudget(400, 'ACTIVATION: ', 12); | |
| expect(wide.compactLabel).toBe(false); | |
| const narrow = activationDisplayBudget(90, 'ACTIVATION: ', 11); | |
| expect(narrow.compactLabel).toBe(true); | |
| expect(narrow.maxChars).toBeGreaterThanOrEqual(4); | |
| }); | |
| }); | |