import { describe, it, expect } from 'vitest' import { serializeFingerprint, restoreFingerprint } from '../../../composables/autoGroup/fingerprint' import type { ClassSchema } from '../../../types/auto-group' const schemas: ClassSchema[] = [ { classKind: 'biological', subKind: 'Tissues', columns: [ { index: 0, name: 'Tissue', sourceIndices: [0], uniqueValues: ['Kidney', 'Liver'], cardinality: 2, role: 'factor', ops: { alias: { Kidney: 'kidney_panel' } }, }, ], groupBy: [0], }, ] describe('fingerprint round-trip', () => { it('serializes to a JSON-stringify-safe object (no Maps)', () => { const fp = serializeFingerprint(schemas) const stringified = JSON.stringify(fp) const reparsed = JSON.parse(stringified) expect(reparsed.version).toBe(1) expect(reparsed.classes[0].columns[0].ops.alias.Kidney).toBe('kidney_panel') }) it('round-trips through restore preserving group-by and ops', () => { const fp = serializeFingerprint(schemas) const restored = restoreFingerprint(fp, schemas) expect(restored[0].groupBy).toEqual([0]) expect(restored[0].columns[0].ops?.alias?.Kidney).toBe('kidney_panel') }) it('reports a clear error when current schema column count differs', () => { const fp = serializeFingerprint(schemas) const mismatched: ClassSchema[] = [ { ...schemas[0], columns: [], // empty }, ] expect(() => restoreFingerprint(fp, mismatched)).toThrow(/column count/i) }) it('matches a pre-token-length snapshot to the schema with the same column count', () => { // An OLD fingerprint (saved before token-length splitting) has no tokenLength. const oldFp = { version: 1 as const, classes: [ { kind: 'biological' as const, subKind: 'Plasma', columns: [ { name: 'A', role: 'factor' as const, sourceIndices: [0] }, ], groupBy: [0], }, ], } // Current data split into two Plasma schemas; the 2-column one is listed FIRST. const current: ClassSchema[] = [ { classKind: 'biological', subKind: 'Plasma', tokenLength: 5, columns: [ { index: 0, name: 'A', sourceIndices: [0], uniqueValues: ['x'], cardinality: 1, role: 'factor' }, { index: 1, name: 'B', sourceIndices: [1], uniqueValues: ['y'], cardinality: 1, role: 'factor' }, ], groupBy: [0, 1], }, { classKind: 'biological', subKind: 'Plasma', tokenLength: 4, columns: [ { index: 0, name: 'A', sourceIndices: [0], uniqueValues: ['x'], cardinality: 1, role: 'factor' }, ], groupBy: [0], }, ] // Must match the 1-column schema (same column count) rather than throwing on the 2-column first hit. const restored = restoreFingerprint(oldFp, current) expect(restored).toHaveLength(1) expect(restored[0].columns).toHaveLength(1) }) it('disambiguates schemas that share kind/subKind but differ in token length', () => { const ragged: ClassSchema[] = [ { classKind: 'biological', subKind: 'Plasma', tokenLength: 4, columns: [ { index: 0, name: 'A', sourceIndices: [0], uniqueValues: ['x'], cardinality: 1, role: 'factor' }, ], groupBy: [0], }, { classKind: 'biological', subKind: 'Plasma', tokenLength: 5, columns: [ { index: 0, name: 'A', sourceIndices: [0], uniqueValues: ['x'], cardinality: 1, role: 'factor' }, { index: 1, name: 'B', sourceIndices: [1], uniqueValues: ['y'], cardinality: 1, role: 'factor' }, ], groupBy: [0, 1], }, ] const fp = serializeFingerprint(ragged) // Restore against the SAME schemas in reversed order: each snapshot must // match its same-token-length schema, not collide on kind/subKind (which // would mis-apply columns or throw on a column-count mismatch). const restored = restoreFingerprint(fp, [ragged[1], ragged[0]]) const four = restored.find(s => s.tokenLength === 4) const five = restored.find(s => s.tokenLength === 5) expect(four?.columns).toHaveLength(1) expect(five?.columns).toHaveLength(2) }) }) describe('fingerprint of an overlay class', () => { it('should restore a snapshot that holds a token length when the current overlay class has none', () => { const column = { index: 0, name: 'A', sourceIndices: [0], uniqueValues: ['x'], cardinality: 1, role: 'constant' as const } const saved = serializeFingerprint([{ classKind: 'iqc', tokenLength: 5, columns: [column], groupBy: [] }]) const restored = restoreFingerprint(saved, [{ classKind: 'iqc', columns: [column], groupBy: [] }]) expect(restored).toHaveLength(1) }) })