Spaces:
Sleeping
Sleeping
| import { describe, it, expect } from 'vitest'; | |
| import * as THREE from 'three'; | |
| import { | |
| parseCompareInput, | |
| attachCompareGroupMeta, | |
| buildGroupLabels, | |
| mergeCompareOverlayLabels, | |
| enrichLabelsWithGroupMeta, | |
| splitCompareTokens, | |
| stripOuterQuotes, | |
| } from '../src/ui/parseCompareGroups.js'; | |
| describe('parseCompareInput', () => { | |
| it('parses flat comma/space lists without group headers', () => { | |
| const r = parseCompareInput('wheel, engine brake\nclutch'); | |
| expect(r.mode).toBe('flat'); | |
| expect(r.tokens).toEqual(['wheel', 'engine', 'brake', 'clutch']); | |
| expect(r.tokenMeta.every((m) => m === null)).toBe(true); | |
| expect(r.groups).toEqual([]); | |
| }); | |
| it('parses GROUP_name = "…" blocks and concatenates in order', () => { | |
| const r = parseCompareInput(` | |
| GROUP_1 = "car, truck, van" | |
| GROUP_2 = sophia, isabella, grace | |
| `); | |
| expect(r.mode).toBe('grouped'); | |
| expect(r.tokens).toEqual(['car', 'truck', 'van', 'sophia', 'isabella', 'grace']); | |
| expect(r.tokenMeta[0]).toEqual({ groupId: 'GROUP_1', groupLabel: 'GROUP_1' }); | |
| expect(r.tokenMeta[3]).toEqual({ groupId: 'GROUP_2', groupLabel: 'GROUP_2' }); | |
| expect(r.groups.map((g) => g.id)).toEqual(['GROUP_1', 'GROUP_2']); | |
| }); | |
| it('keeps duplicates across groups', () => { | |
| const r = parseCompareInput('A = wheel, tire\nB = wheel, brake'); | |
| expect(r.tokens).toEqual(['wheel', 'tire', 'wheel', 'brake']); | |
| }); | |
| it('puts leading loose tokens into UNGROUPED', () => { | |
| const r = parseCompareInput('solo\nG1 = a, b'); | |
| expect(r.tokens[0]).toBe('solo'); | |
| expect(r.tokenMeta[0].groupId).toBe('UNGROUPED'); | |
| expect(r.groups[0].id).toBe('UNGROUPED'); | |
| }); | |
| it('respects maxTokens', () => { | |
| const r = parseCompareInput('G = a b c d e', 3); | |
| expect(r.tokens).toEqual(['a', 'b', 'c']); | |
| }); | |
| }); | |
| describe('splitCompareTokens / stripOuterQuotes', () => { | |
| it('strips matching outer quotes', () => { | |
| expect(stripOuterQuotes('"a, b"')).toBe('a, b'); | |
| expect(splitCompareTokens(stripOuterQuotes('"a, b"'))).toEqual(['a', 'b']); | |
| }); | |
| }); | |
| describe('attachCompareGroupMeta + buildGroupLabels', () => { | |
| it('attaches meta by index and builds centroids', () => { | |
| const meta = [ | |
| { groupId: 'GROUP_1', groupLabel: 'GROUP_1' }, | |
| { groupId: 'GROUP_1', groupLabel: 'GROUP_1' }, | |
| { groupId: 'GROUP_2', groupLabel: 'GROUP_2' }, | |
| ]; | |
| const data = attachCompareGroupMeta({ | |
| count: 3, | |
| items: [ | |
| { id: 'tok_0', text: 'a', embedding: [1] }, | |
| { id: 'tok_1', text: 'b', embedding: [1] }, | |
| { id: 'tok_2', text: 'c', embedding: [1] }, | |
| ], | |
| }, meta); | |
| expect(data.items[0].groupId).toBe('GROUP_1'); | |
| expect(data.items[2].groupLabel).toBe('GROUP_2'); | |
| const tokenLabels = [ | |
| { id: 'tok_0', text: 'a', type: 'compare', groupId: 'GROUP_1', groupLabel: 'GROUP_1', origin3D: new THREE.Vector3(0, 10, 0) }, | |
| { id: 'tok_1', text: 'b', type: 'compare', groupId: 'GROUP_1', groupLabel: 'GROUP_1', origin3D: new THREE.Vector3(0, 0, 0) }, | |
| { id: 'tok_2', text: 'c', type: 'compare', groupId: 'GROUP_2', groupLabel: 'GROUP_2', origin3D: new THREE.Vector3(0, -10, 0) }, | |
| ]; | |
| const groups = buildGroupLabels(tokenLabels); | |
| expect(groups).toHaveLength(2); | |
| expect(groups[0].text).toBe('GROUP_1'); | |
| expect(groups[0].origin3D.y).toBeCloseTo(5, 5); | |
| expect(groups[1].text).toBe('GROUP_2'); | |
| const merged = mergeCompareOverlayLabels(tokenLabels); | |
| expect(merged).toHaveLength(2); | |
| expect(merged.every((l) => l.type === 'group')).toBe(true); | |
| expect(merged.map((l) => l.text)).toEqual(['GROUP_1', 'GROUP_2']); | |
| }); | |
| it('keeps token labels when no group meta', () => { | |
| const tokenLabels = [ | |
| { id: 'tok_0', text: 'a', type: 'compare', origin3D: new THREE.Vector3(0, 0, 0) }, | |
| ]; | |
| expect(mergeCompareOverlayLabels(tokenLabels)).toHaveLength(1); | |
| expect(mergeCompareOverlayLabels(tokenLabels)[0].type).toBe('compare'); | |
| }); | |
| it('enrichLabelsWithGroupMeta recovers ids from compare items', () => { | |
| const labels = [ | |
| { id: 'tok_0', text: 'car', type: 'compare', origin3D: new THREE.Vector3(0, 1, 0) }, | |
| { id: 'tok_1', text: 'grace', type: 'compare', origin3D: new THREE.Vector3(0, -1, 0) }, | |
| ]; | |
| const items = [ | |
| { id: 'tok_0', groupId: 'GROUP_1', groupLabel: 'GROUP_1' }, | |
| { id: 'tok_1', groupId: 'GROUP_2', groupLabel: 'GROUP_2' }, | |
| ]; | |
| const enriched = enrichLabelsWithGroupMeta(labels, items); | |
| const merged = mergeCompareOverlayLabels(enriched); | |
| expect(merged).toHaveLength(2); | |
| expect(merged.map((l) => l.text)).toEqual(['GROUP_1', 'GROUP_2']); | |
| }); | |
| it('attachCompareGroupMeta exposes groups summary', () => { | |
| const meta = [ | |
| { groupId: 'GROUP_1', groupLabel: 'GROUP_1' }, | |
| { groupId: 'GROUP_1', groupLabel: 'GROUP_1' }, | |
| { groupId: 'GROUP_2', groupLabel: 'GROUP_2' }, | |
| ]; | |
| const data = attachCompareGroupMeta({ | |
| count: 3, | |
| items: [ | |
| { id: 'tok_0', text: 'a' }, | |
| { id: 'tok_1', text: 'b' }, | |
| { id: 'tok_2', text: 'c' }, | |
| ], | |
| }, meta); | |
| expect(data.groups).toEqual([ | |
| { id: 'GROUP_1', label: 'GROUP_1', count: 2 }, | |
| { id: 'GROUP_2', label: 'GROUP_2', count: 1 }, | |
| ]); | |
| }); | |
| }); | |
| describe('applySaeToCompare preserves group meta', () => { | |
| it('keeps groupId/groupLabel on every item', async () => { | |
| const { applySaeToCompare } = await import('../src/core/saeReplace.js'); | |
| const raw = { | |
| count: 2, | |
| items: [ | |
| { id: 'tok_0', text: 'a', embedding: [1, 0], groupId: 'GROUP_1', groupLabel: 'GROUP_1' }, | |
| { id: 'tok_1', text: 'b', embedding: [0, 1], groupId: 'GROUP_2', groupLabel: 'GROUP_2' }, | |
| ], | |
| }; | |
| const next = applySaeToCompare(raw, [ | |
| [1, 0, 0], | |
| [0, 1, 0], | |
| ]); | |
| expect(next.items[0].groupId).toBe('GROUP_1'); | |
| expect(next.items[0].groupLabel).toBe('GROUP_1'); | |
| expect(next.items[1].groupId).toBe('GROUP_2'); | |
| expect(next.items[1].groupLabel).toBe('GROUP_2'); | |
| expect(next.featureSpace).toBe('SAE'); | |
| }); | |
| }); | |