buildBudgetYearRows sorted each top group by depth, so an expanded
subcategory showed at the end of its group instead of under its parent.
It now builds one block per child (lone leaf, or [subtotal, ...children]),
sorts the blocks once ("(direct)" first, then by name, leaves and
sub-groups mixed, as the Budget vs Actual report) and concatenates them;
top groups are sorted by type, sort_order and name.
reorderRows put a leaf that follows a sibling sub-group into that
sub-group's block. A row at the subtotal's depth or shallower now closes
the block, which also fixes "subtotals at the bottom" in the Budget vs
Actual and Compare reports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
2.7 KiB
TypeScript
91 lines
2.7 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { reorderRows } from "./reorderRows";
|
|
|
|
// Minimal row: the two fields `reorderRows` reads, plus a name to assert on.
|
|
interface Row {
|
|
name: string;
|
|
is_parent?: boolean;
|
|
depth?: number;
|
|
}
|
|
|
|
const leaf = (name: string, depth: number): Row => ({ name, is_parent: false, depth });
|
|
const sub = (name: string, depth: number): Row => ({ name, is_parent: true, depth });
|
|
const names = (rows: Row[]) => rows.map((r) => r.name);
|
|
|
|
// Two levels, depth-first: a root group whose leaves sit before AND after a
|
|
// sibling sub-group, then a lone root leaf.
|
|
// Finances
|
|
// Cadeaux
|
|
// Impôts (sub-group)
|
|
// Fédéral
|
|
// Provincial
|
|
// Retrait cash <- follows the sibling sub-group "Impôts"
|
|
// Divers (root leaf) <- follows the root group "Finances"
|
|
const TWO_LEVELS: Row[] = [
|
|
sub("Finances", 0),
|
|
leaf("Cadeaux", 1),
|
|
sub("Impôts", 1),
|
|
leaf("Fédéral", 2),
|
|
leaf("Provincial", 2),
|
|
leaf("Retrait cash", 1),
|
|
leaf("Divers", 0),
|
|
];
|
|
|
|
// Three levels, as the Compare report tree allows (depth is not capped at 2).
|
|
// A
|
|
// A1 (sub-group)
|
|
// A1a (sub-group)
|
|
// x
|
|
// y <- follows the sibling sub-group "A1a"
|
|
// z <- follows the sibling sub-group "A1"
|
|
const THREE_LEVELS: Row[] = [
|
|
sub("A", 0),
|
|
sub("A1", 1),
|
|
sub("A1a", 2),
|
|
leaf("x", 3),
|
|
leaf("y", 2),
|
|
leaf("z", 1),
|
|
];
|
|
|
|
describe("reorderRows — subtotals at the bottom (issue #424)", () => {
|
|
it("two levels: a leaf that follows a sibling sub-group stays outside its block", () => {
|
|
expect(names(reorderRows(TWO_LEVELS, false))).toEqual([
|
|
"Cadeaux",
|
|
"Fédéral",
|
|
"Provincial",
|
|
"Impôts",
|
|
"Retrait cash",
|
|
"Finances",
|
|
"Divers",
|
|
]);
|
|
});
|
|
|
|
it("three levels: each subtotal follows its own rows only", () => {
|
|
expect(names(reorderRows(THREE_LEVELS, false))).toEqual(["x", "A1a", "y", "A1", "z", "A"]);
|
|
});
|
|
|
|
it("consecutive sibling sub-groups each close on their own leaves", () => {
|
|
const rows: Row[] = [
|
|
sub("Revenus", 0),
|
|
leaf("Autres", 1),
|
|
sub("Emploi", 1),
|
|
leaf("Paie", 2),
|
|
sub("Gouv", 1),
|
|
leaf("Alloc", 2),
|
|
];
|
|
expect(names(reorderRows(rows, false))).toEqual(["Autres", "Paie", "Emploi", "Alloc", "Gouv", "Revenus"]);
|
|
});
|
|
|
|
it("keeps every row exactly once", () => {
|
|
const out = reorderRows(TWO_LEVELS, false);
|
|
expect(out).toHaveLength(TWO_LEVELS.length);
|
|
expect(new Set(out)).toEqual(new Set(TWO_LEVELS));
|
|
});
|
|
});
|
|
|
|
describe("reorderRows — subtotals on top", () => {
|
|
it("returns the list unchanged", () => {
|
|
expect(reorderRows(TWO_LEVELS, true)).toBe(TWO_LEVELS);
|
|
expect(reorderRows(THREE_LEVELS, true)).toBe(THREE_LEVELS);
|
|
});
|
|
});
|