import { describe, expect, it } from 'vitest' import { appendQcGroup, computeFieldGroups, fieldRecipeSummary, sampleTotalFor, type SmartGroupField, type SmartGroupSampleRecord, } from '../../components/SmartGroupFieldRecipe.groups' const FIELDS: SmartGroupField[] = [ { key: 'Condition', role: 'Factor', vals: 'Control, Treatment', rep: false }, { key: 'Dose', role: 'Factor', vals: 'Vehicle, Low, High', rep: false }, { key: 'Replicate', role: 'Replicate', vals: 'Rep1, Rep2, Rep3', rep: true }, ] const BASE = [ { Condition: 'Control', Dose: 'Vehicle' }, { Condition: 'Treatment', Dose: 'Low' }, { Condition: 'Treatment', Dose: 'High' }, ] const SAMPLES: SmartGroupSampleRecord[] = BASE.flatMap(base => [1, 2, 3].map(rep => ({ ...base, Replicate: `Rep${rep}` })), ) const PALETTE = ['c1', 'c2', 'c3', 'c4', 'c5'] describe('computeFieldGroups', () => { it('should form three groups of three when grouping by Condition and Dose', () => { const groups = computeFieldGroups(SAMPLES, FIELDS, [true, true, false], PALETTE) expect(groups.map(g => g.name)).toEqual(['Control_Vehicle', 'Treatment_Low', 'Treatment_High']) expect(groups.map(g => g.count)).toEqual([3, 3, 3]) }) it('should shatter into nine singleton groups when Replicate is included', () => { const groups = computeFieldGroups(SAMPLES, FIELDS, [true, true, true], PALETTE) expect(groups).toHaveLength(9) expect(groups.every(g => g.count === 1)).toBe(true) }) it('should return no groups when no field is active', () => { expect(computeFieldGroups(SAMPLES, FIELDS, [false, false, false], PALETTE)).toEqual([]) }) it('should collapse to Condition only when just the first field is active', () => { const groups = computeFieldGroups(SAMPLES, FIELDS, [true, false, false], PALETTE) expect(groups.map(g => g.name)).toEqual(['Control', 'Treatment']) expect(groups.map(g => g.count)).toEqual([3, 6]) }) it('should cycle colours through the palette in group order', () => { const groups = computeFieldGroups(SAMPLES, FIELDS, [true, true, false], PALETTE) expect(groups.map(g => g.color)).toEqual(['c1', 'c2', 'c3']) }) it('should join field values following an explicit layer order', () => { const groups = computeFieldGroups(SAMPLES, FIELDS, [true, true, false], PALETTE, [1, 0]) expect(groups.map(g => g.name)).toEqual(['Vehicle_Control', 'Low_Treatment', 'High_Treatment']) }) }) describe('appendQcGroup', () => { const base = computeFieldGroups(SAMPLES, FIELDS, [true, true, false], PALETTE) it('should append a QC_Pool group of three when QC is mixed into groups', () => { const result = appendQcGroup(base, 'Mix into groups', 'qc') expect(result).toHaveLength(base.length + 1) expect(result.at(-1)).toEqual({ name: 'QC_Pool', count: 3, color: 'qc' }) }) it('should leave groups untouched when QC overlays or is excluded', () => { expect(appendQcGroup(base, 'Overlay only', 'qc')).toEqual(base) expect(appendQcGroup(base, 'Exclude', 'qc')).toEqual(base) }) }) describe('sampleTotalFor', () => { it('should drop the QC pool only when QC is excluded', () => { expect(sampleTotalFor('Exclude')).toBe(9) expect(sampleTotalFor('Overlay only')).toBe(12) expect(sampleTotalFor('Mix into groups')).toBe(12) }) }) describe('fieldRecipeSummary', () => { const baseGroups = computeFieldGroups(SAMPLES, FIELDS, [true, true, false], PALETTE) it('should be muted when no factor is active', () => { expect(fieldRecipeSummary({ activeFactors: [], repOn: false, baseGroups: [] }).kind).toBe('mute') }) it('should warn when a replicate field is switched on', () => { expect(fieldRecipeSummary({ activeFactors: ['Replicate'], repOn: true, baseGroups }).kind).toBe('warn') }) it('should inform when grouping by factors only', () => { expect( fieldRecipeSummary({ activeFactors: ['Condition', 'Dose'], repOn: false, baseGroups }).kind, ).toBe('info') }) }) describe('fieldRecipeSummary for groups of one', () => { const group = (name: string, count: number) => ({ name, count, color: '#000' }) it('should report singletons when every group has one sample', () => { const summary = fieldRecipeSummary({ activeFactors: ['A'], repOn: false, baseGroups: [group('a', 1), group('b', 1)] }) expect(summary.kind).toBe('singletons') }) }) describe('fieldRecipeSummary for a batch with some replicates', () => { const group = (name: string, count: number) => ({ name, count, color: '#000' }) it('should report singletons when half or more of the groups have one sample', () => { const baseGroups = [group('a', 1), group('b', 1), group('c', 2)] expect(fieldRecipeSummary({ activeFactors: ['A'], repOn: false, baseGroups }).kind).toBe('singletons') }) it('should report info when fewer than half of the groups have one sample', () => { const baseGroups = [group('a', 2), group('b', 2), group('c', 1)] expect(fieldRecipeSummary({ activeFactors: ['A'], repOn: false, baseGroups }).kind).toBe('info') }) })