@accounter/client
Version:
Accounter client application
155 lines (134 loc) • 6.13 kB
text/typescript
import { describe, expect, it } from 'vitest';
import { isLegacyTemplateNodes, migrateLegacyTemplateNodes } from '../utils/legacy-migration.js';
import type { LegacyTemplateNode } from '../utils/legacy-migration.js';
import { REPORT_ROOT } from '../utils/report-tree.js';
import type { DynamicReportQuery } from '../../../../gql/graphql.js';
// ── fixture helpers ───────────────────────────────────────────────────────────
type BusinessSum = Extract<
NonNullable<DynamicReportQuery['businessTransactionsSumFromLedgerRecords']>,
{ __typename?: 'BusinessTransactionsSumFromLedgerRecordsSuccessfulResult' }
>['businessTransactionsSum'][number];
function legacyBranch(
id: string | number,
parent: string | number,
opts: {
sortCode?: number;
descendantFinancialEntities?: string[];
descendantSortCodes?: number[];
} = {},
): LegacyTemplateNode {
return {
id,
parent,
text: String(id),
droppable: true,
data: {
isOpen: false,
sortCode: opts.sortCode ?? null,
descendantFinancialEntities: opts.descendantFinancialEntities ?? null,
descendantSortCodes: opts.descendantSortCodes ?? null,
mergedSortCodes: null,
},
};
}
function legacyLeaf(id: string, parent: string | number): LegacyTemplateNode {
return { id, parent, text: id, droppable: false, data: { isOpen: false } };
}
function bizSum(id: string, name: string, totalRaw: number): BusinessSum {
return {
business: { __typename: 'LtdFinancialEntity', id, name, sortCode: null },
credit: { __typename: 'FinancialAmount', formatted: '', raw: 0 },
debit: { __typename: 'FinancialAmount', formatted: '', raw: 0 },
total: { __typename: 'FinancialAmount', formatted: '', raw: totalRaw },
} as BusinessSum;
}
const UUID_A = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
const UUID_B = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
// ── tests ─────────────────────────────────────────────────────────────────────
describe('isLegacyTemplateNodes', () => {
it('returns true when any node.data has descendantSortCodes key', () => {
const nodes = [legacyBranch('br-1', 'report', { descendantSortCodes: [100] })];
expect(isLegacyTemplateNodes(nodes)).toBe(true);
});
it('returns false for new-format nodes (no descendantSortCodes key)', () => {
const nodes: LegacyTemplateNode[] = [
{ id: 'br-1', parent: 'report', text: 'Branch', droppable: true, data: { isOpen: false } },
];
expect(isLegacyTemplateNodes(nodes)).toBe(false);
});
});
describe('migrateLegacyTemplateNodes', () => {
describe('sort-code branch + 2 UUIDs in descendantFinancialEntities, both in businessSums', () => {
const nodes = [
legacyBranch('br-sc', 'report', {
sortCode: 100,
descendantSortCodes: [100],
descendantFinancialEntities: [UUID_A, UUID_B],
}),
];
const sums = [bizSum(UUID_A, 'Alpha Corp', 200), bizSum(UUID_B, 'Beta Ltd', -80)];
const result = migrateLegacyTemplateNodes(nodes, sums);
it('branch is present with nodeType sort-code-branch', () => {
const branch = result.find(n => n.id === 'br-sc');
expect(branch).toBeDefined();
expect(branch!.data.nodeType).toBe('sort-code-branch');
});
it('branch has no hint arrays in output data', () => {
const branch = result.find(n => n.id === 'br-sc');
expect(branch!.data).not.toHaveProperty('descendantFinancialEntities');
expect(branch!.data).not.toHaveProperty('descendantSortCodes');
expect(branch!.data).not.toHaveProperty('mergedSortCodes');
});
it('two explicit leaf nodes are inserted with correct parent', () => {
const leafA = result.find(n => n.id === UUID_A);
const leafB = result.find(n => n.id === UUID_B);
expect(leafA).toBeDefined();
expect(leafA!.parent).toBe('br-sc');
expect(leafA!.data.nodeType).toBe('financial-entity');
expect(leafB).toBeDefined();
expect(leafB!.parent).toBe('br-sc');
});
it('leaf text is hydrated from businessSums', () => {
expect(result.find(n => n.id === UUID_A)!.text).toBe('Alpha Corp');
expect(result.find(n => n.id === UUID_B)!.text).toBe('Beta Ltd');
});
});
describe('UUID in descendantFinancialEntities but NOT in businessSums', () => {
const MISSING = 'cccccccc-cccc-cccc-cccc-cccccccccccc';
const nodes = [
legacyBranch('br-1', 'report', {
descendantSortCodes: [50],
descendantFinancialEntities: [UUID_A, MISSING],
}),
];
const sums = [bizSum(UUID_A, 'Present', 10)];
const result = migrateLegacyTemplateNodes(nodes, sums);
it('missing UUID is silently dropped', () => {
expect(result.find(n => n.id === MISSING)).toBeUndefined();
});
it('present UUID is still inserted', () => {
expect(result.find(n => n.id === UUID_A)).toBeDefined();
});
});
describe('synthetic branch (no sortCode)', () => {
it('nodeType is synthetic-branch', () => {
const nodes = [
legacyBranch('synth', 'report', { descendantSortCodes: [10] }),
];
const result = migrateLegacyTemplateNodes(nodes, []);
expect(result.find(n => n.id === 'synth')!.data.nodeType).toBe('synthetic-branch');
});
});
describe('parent: 0 → REPORT_ROOT', () => {
it('branch with parent 0 gets parent = REPORT_ROOT', () => {
const nodes = [legacyBranch('br-1', 0, { descendantSortCodes: [] })];
const result = migrateLegacyTemplateNodes(nodes, []);
expect(result.find(n => n.id === 'br-1')!.parent).toBe(REPORT_ROOT);
});
it('explicit leaf with parent 0 gets parent = REPORT_ROOT', () => {
const nodes = [legacyLeaf(UUID_A, 0)];
const result = migrateLegacyTemplateNodes(nodes, [bizSum(UUID_A, 'Root Leaf', 5)]);
expect(result.find(n => n.id === UUID_A)!.parent).toBe(REPORT_ROOT);
});
});
});