mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
288 lines
8.6 KiB
TypeScript
288 lines
8.6 KiB
TypeScript
import { aggregate } from './_utils';
|
|
|
|
describe('aggregate', () => {
|
|
describe('basic aggregation', () => {
|
|
it('should aggregate entries with matching comparator attribute', () => {
|
|
const input = [
|
|
{ id: 1, name: 'John', entries: ['entry1'] },
|
|
{ id: 2, name: 'Jane', entries: ['entry2'] },
|
|
{ id: 1, name: 'John', entries: ['entry3'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0]).toEqual({
|
|
id: 1,
|
|
name: 'John',
|
|
entries: ['entry1', 'entry3'],
|
|
});
|
|
expect(result[1]).toEqual({
|
|
id: 2,
|
|
name: 'Jane',
|
|
entries: ['entry2'],
|
|
});
|
|
});
|
|
|
|
it('should preserve order of first occurrence', () => {
|
|
const input = [
|
|
{ id: 2, entries: ['a'] },
|
|
{ id: 1, entries: ['b'] },
|
|
{ id: 2, entries: ['c'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result[0].id).toBe(2);
|
|
expect(result[1].id).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('no matching entries', () => {
|
|
it('should return all entries unchanged when no comparator matches', () => {
|
|
const input = [
|
|
{ id: 1, name: 'John', entries: ['entry1'] },
|
|
{ id: 2, name: 'Jane', entries: ['entry2'] },
|
|
{ id: 3, name: 'Bob', entries: ['entry3'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result).toHaveLength(3);
|
|
expect(result).toEqual(input);
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should return empty array when input is empty', () => {
|
|
const result = aggregate([], 'id', 'entries');
|
|
|
|
expect(result).toEqual([]);
|
|
expect(result).toHaveLength(0);
|
|
});
|
|
|
|
it('should return single entry unchanged when input has one item', () => {
|
|
const input = [{ id: 1, name: 'John', entries: ['entry1'] }];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toEqual({
|
|
id: 1,
|
|
name: 'John',
|
|
entries: ['entry1'],
|
|
});
|
|
});
|
|
|
|
it('should handle multiple entries with same comparator value', () => {
|
|
const input = [
|
|
{ id: 1, entries: ['a'] },
|
|
{ id: 1, entries: ['b'] },
|
|
{ id: 1, entries: ['c'] },
|
|
{ id: 1, entries: ['d'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].entries).toEqual(['a', 'b', 'c', 'd']);
|
|
});
|
|
});
|
|
|
|
describe('different comparator attributes', () => {
|
|
it('should work with string comparator attribute', () => {
|
|
const input = [
|
|
{ name: 'Product A', category: 'Electronics', entries: ['item1'] },
|
|
{ name: 'Product B', category: 'Books', entries: ['item2'] },
|
|
{ name: 'Product C', category: 'Electronics', entries: ['item3'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'category', 'entries');
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0]).toEqual({
|
|
name: 'Product A',
|
|
category: 'Electronics',
|
|
entries: ['item1', 'item3'],
|
|
});
|
|
expect(result[1]).toEqual({
|
|
name: 'Product B',
|
|
category: 'Books',
|
|
entries: ['item2'],
|
|
});
|
|
});
|
|
|
|
it('should not aggregate items with undefined comparator values', () => {
|
|
const input = [
|
|
{ id: undefined, entries: ['a'] },
|
|
{ id: 1, entries: ['b'] },
|
|
{ id: undefined, entries: ['c'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result).toHaveLength(3);
|
|
// Items with undefined id are NOT aggregated - each remains separate
|
|
expect(result[0].entries).toEqual(['a']);
|
|
expect(result[1].entries).toEqual(['b']);
|
|
expect(result[2].entries).toEqual(['c']);
|
|
});
|
|
|
|
it('should handle null comparator values separately', () => {
|
|
const input = [
|
|
{ id: null, entries: ['a'] },
|
|
{ id: 1, entries: ['b'] },
|
|
{ id: null, entries: ['c'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result).toHaveLength(3);
|
|
expect(result[0].entries).toEqual(['a']);
|
|
expect(result[1].entries).toEqual(['b']);
|
|
expect(result[2].entries).toEqual(['c']);
|
|
});
|
|
|
|
it('should not aggregate items missing the comparatorAttr property', () => {
|
|
const input = [
|
|
{ id: 1, entries: ['a'] },
|
|
{ name: 'No ID', entries: ['b'] }, // missing 'id' property
|
|
{ id: 1, entries: ['c'] },
|
|
{ entries: ['d'] }, // also missing 'id' property
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
// 3 entries: aggregated id:1, and two separate items without 'id' property
|
|
expect(result).toHaveLength(3);
|
|
// Items with id: 1 are aggregated
|
|
expect(result[0]).toEqual({ id: 1, entries: ['a', 'c'] });
|
|
// Items missing 'id' are NOT aggregated - each remains separate
|
|
expect(result[1]).toEqual({ name: 'No ID', entries: ['b'] });
|
|
expect(result[2]).toEqual({ entries: ['d'] });
|
|
});
|
|
});
|
|
|
|
describe('different group attributes', () => {
|
|
it('should work with different groupOn attribute name', () => {
|
|
const input = [
|
|
{ id: 1, items: ['item1'] },
|
|
{ id: 1, items: ['item2'] },
|
|
{ id: 2, items: ['item3'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'items');
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0].items).toEqual(['item1', 'item2']);
|
|
expect(result[1].items).toEqual(['item3']);
|
|
});
|
|
});
|
|
|
|
describe('complex entries', () => {
|
|
it('should aggregate entries containing objects', () => {
|
|
const input = [
|
|
{ invoiceId: 'INV-001', entries: [{ itemId: 1, quantity: 2 }] },
|
|
{ invoiceId: 'INV-002', entries: [{ itemId: 2, quantity: 1 }] },
|
|
{ invoiceId: 'INV-001', entries: [{ itemId: 3, quantity: 5 }] },
|
|
];
|
|
|
|
const result = aggregate(input, 'invoiceId', 'entries');
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0].entries).toEqual([
|
|
{ itemId: 1, quantity: 2 },
|
|
{ itemId: 3, quantity: 5 },
|
|
]);
|
|
expect(result[1].entries).toEqual([{ itemId: 2, quantity: 1 }]);
|
|
});
|
|
|
|
it('should aggregate entries with multiple items in each entry', () => {
|
|
const input = [
|
|
{ id: 1, entries: ['a', 'b'] },
|
|
{ id: 1, entries: ['c', 'd'] },
|
|
{ id: 2, entries: ['e'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0].entries).toEqual(['a', 'b', 'c', 'd']);
|
|
expect(result[1].entries).toEqual(['e']);
|
|
});
|
|
});
|
|
|
|
describe('numeric comparator values', () => {
|
|
it('should correctly compare numeric values', () => {
|
|
const input = [
|
|
{ id: 1, entries: ['a'] },
|
|
{ id: 2, entries: ['b'] },
|
|
{ id: 1, entries: ['c'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result.find((r) => r.id === 1).entries).toEqual(['a', 'c']);
|
|
expect(result.find((r) => r.id === 2).entries).toEqual(['b']);
|
|
});
|
|
|
|
it('should treat 0 as a valid comparator value', () => {
|
|
const input = [
|
|
{ id: 0, entries: ['a'] },
|
|
{ id: 1, entries: ['b'] },
|
|
{ id: 0, entries: ['c'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0].entries).toEqual(['a', 'c']);
|
|
expect(result[1].entries).toEqual(['b']);
|
|
});
|
|
});
|
|
|
|
describe('preserving other properties', () => {
|
|
it('should preserve all properties from the first matching entry', () => {
|
|
const input = [
|
|
{ id: 1, name: 'First', extra: 'data1', entries: ['a'] },
|
|
{ id: 1, name: 'Second', extra: 'data2', entries: ['b'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].name).toBe('First');
|
|
expect(result[0].extra).toBe('data1');
|
|
expect(result[0].entries).toEqual(['a', 'b']);
|
|
});
|
|
});
|
|
|
|
describe('empty entries arrays', () => {
|
|
it('should handle empty entries arrays', () => {
|
|
const input = [
|
|
{ id: 1, entries: [] },
|
|
{ id: 1, entries: ['a'] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].entries).toEqual(['a']);
|
|
});
|
|
|
|
it('should handle all empty entries arrays', () => {
|
|
const input = [
|
|
{ id: 1, entries: [] },
|
|
{ id: 1, entries: [] },
|
|
];
|
|
|
|
const result = aggregate(input, 'id', 'entries');
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].entries).toEqual([]);
|
|
});
|
|
});
|
|
});
|
|
|