Review 25437, point 2: the loop named by its own smallest member, not by a smaller id under it (RM1); the page hands the window every category, not the tree (RM11); a category that is its own parent stays out of the budget, and displaysAsRoot says so (RM15). RM7 (depth counted on active categories at the service) is pinned in categoryService.test.ts by1a36d4e, RM2 bybe4b923. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
be4b923451
commit
7f25293669
3 changed files with 43 additions and 0 deletions
|
|
@ -359,6 +359,11 @@ describe("buildBudgetYearRows — a category whose parent is deleted or gone (#3
|
||||||
);
|
);
|
||||||
expect(rows.some((r) => r.category_id === 7 || r.category_id === 8)).toBe(false);
|
expect(rows.some((r) => r.category_id === 7 || r.category_id === 8)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("hides a category that is its own parent too, a loop of one member", () => {
|
||||||
|
const rows = buildBudgetYearRows([...CATEGORIES, cat(7, "Gifts", "expense", { parent_id: 7 })], [entry(7, 40)], []);
|
||||||
|
expect(rows.some((r) => r.category_id === 7)).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("buildBudgetYearRows + multi-level collapse (issue #289 end-to-end)", () => {
|
describe("buildBudgetYearRows + multi-level collapse (issue #289 end-to-end)", () => {
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,35 @@ describe("CategoriesPage — « Vérifier la hiérarchie » (#393)", () => {
|
||||||
expect(render()).toContain("t(categories.hierarchy.errors.too_deep)");
|
expect(render()).toContain("t(categories.hierarchy.errors.too_deep)");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("hands the window every category, not the tree: an orphan in a loop is named", () => {
|
||||||
|
const node = (id: number, name: string, parent_id: number | null) => ({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
parent_id,
|
||||||
|
color: null,
|
||||||
|
icon: null,
|
||||||
|
type: "expense",
|
||||||
|
is_active: true,
|
||||||
|
is_inputable: true,
|
||||||
|
sort_order: id,
|
||||||
|
keyword_count: 0,
|
||||||
|
i18n_key: null,
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
// 3 <-> 5: no root reaches them, so the tree holds root 1 (and 10 under it) only.
|
||||||
|
const categories = [node(1, "Logement", null), node(10, "Loyer", 1), node(3, "Cadeaux", 5), node(5, "Fêtes", 3)];
|
||||||
|
harness.state = {
|
||||||
|
isLoading: false,
|
||||||
|
categories,
|
||||||
|
tree: [{ ...categories[0], children: [categories[1]] }],
|
||||||
|
hierarchyCheck: { orphans: [{ id: 3, reason: "cycle" }], error: null },
|
||||||
|
};
|
||||||
|
const html = render();
|
||||||
|
expect(html).toContain(">Cadeaux</p>");
|
||||||
|
expect(html).toContain('aria-label="t(categories.hierarchy.newParentFor[name=Cadeaux])"');
|
||||||
|
expect(html).not.toContain(">—</p>");
|
||||||
|
});
|
||||||
|
|
||||||
it("translates a refused save of the form by its key, and shows any other failure as is", () => {
|
it("translates a refused save of the form by its key, and shows any other failure as is", () => {
|
||||||
harness.state = { error: { key: "categories.hierarchy.errors.parent_has_transactions" } };
|
harness.state = { error: { key: "categories.hierarchy.errors.parent_has_transactions" } };
|
||||||
expect(render()).toContain(">t(categories.hierarchy.errors.parent_has_transactions)</div>");
|
expect(render()).toContain(">t(categories.hierarchy.errors.parent_has_transactions)</div>");
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,10 @@ describe("displaysAsRoot", () => {
|
||||||
expect(displaysAsRoot({ id: 10, parent_id: 1 }, ids)).toBe(false);
|
expect(displaysAsRoot({ id: 10, parent_id: 1 }, ids)).toBe(false);
|
||||||
expect(displaysAsRoot({ id: 1, parent_id: 2 }, ids)).toBe(false);
|
expect(displaysAsRoot({ id: 1, parent_id: 2 }, ids)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("is not a root as its own parent: that one-member loop stays hidden, like the tree hides it", () => {
|
||||||
|
expect(displaysAsRoot({ id: 10, parent_id: 10 }, ids)).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("isActiveRow", () => {
|
describe("isActiveRow", () => {
|
||||||
|
|
@ -139,6 +143,11 @@ describe("findOrphans (#393)", () => {
|
||||||
expect(findOrphans(rows)).toEqual([{ id: 3, reason: "cycle" }]);
|
expect(findOrphans(rows)).toEqual([{ id: 3, reason: "cycle" }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("names the loop by its own smallest member, never by a smaller id that hangs under it", () => {
|
||||||
|
// 5 <-> 8, 2 under 5: reattaching 2 would leave 5 and 8 hidden.
|
||||||
|
expect(findOrphans([r(2, 5), r(5, 8), r(8, 5), r(1, null)])).toEqual([{ id: 5, reason: "cycle" }]);
|
||||||
|
});
|
||||||
|
|
||||||
it("lists a three-member cycle once, by its smallest id, whatever the order of the rows", () => {
|
it("lists a three-member cycle once, by its smallest id, whatever the order of the rows", () => {
|
||||||
// 6 -> 4 -> 9 -> 6, 12 under 9.
|
// 6 -> 4 -> 9 -> 6, 12 under 9.
|
||||||
const rows = [r(6, 4), r(4, 9), r(9, 6), r(12, 9), r(1, null)];
|
const rows = [r(6, 4), r(4, 9), r(9, 6), r(12, 9), r(1, null)];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue