fix: reload the category tree even when the check after a reattachment fails (#393)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m13s

The reattachment is committed before the orphan list is checked again: if
that check failed, the tree kept showing the categories as they were. The
tree now reloads in a finally block, and the failure shows in the window.

Resolves #393

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
le king fu 2026-10-18 19:24:31 -04:00
parent 03e32afc02
commit cb0e2d2536
2 changed files with 25 additions and 4 deletions

View file

@ -172,6 +172,23 @@ describe("useCategories — « Vérifier la hiérarchie » (#393)", () => {
expect(state.isSaving).toBe(false); expect(state.isSaving).toBe(false);
}); });
it("reloads the tree even when the check after a committed reattachment fails", async () => {
vi.mocked(findOrphanCategories)
.mockResolvedValueOnce([CYCLE])
.mockRejectedValueOnce(new Error("database is locked"));
vi.mocked(reattachCategory).mockResolvedValue(2);
let api = await mount();
await api.checkHierarchy();
api = harness.render(useCategories);
await api.reattachOrphan(CYCLE, null);
expect(reattachCategory).toHaveBeenCalledWith(3, null);
expect(logInfo).toHaveBeenCalledTimes(1);
expect(getAllCategoriesWithCounts).toHaveBeenCalledTimes(2);
const { state } = harness.render(useCategories);
expect(state.hierarchyCheck?.error).toEqual({ message: "database is locked" });
expect(state.isSaving).toBe(false);
});
it("shows a refused reattachment in the window by its translated code, with no Journal line", async () => { it("shows a refused reattachment in the window by its translated code, with no Journal line", async () => {
vi.mocked(findOrphanCategories).mockResolvedValue([CYCLE]); vi.mocked(findOrphanCategories).mockResolvedValue([CYCLE]);
vi.mocked(reattachCategory).mockRejectedValue(new CategoryHierarchyError("parent_has_transactions")); vi.mocked(reattachCategory).mockRejectedValue(new CategoryHierarchyError("parent_has_transactions"));

View file

@ -417,7 +417,8 @@ export function useCategories() {
/** /**
* Reattaches an orphan under `parentId`, or to the root with `null` (#393). * Reattaches an orphan under `parentId`, or to the root with `null` (#393).
* On success: one Journal line (counts, no name), then the orphan list and * On success: one Journal line (counts, no name), then the orphan list and
* the tree reload. A refusal shows in the window, by its translated code. * the tree reload; the tree reloads even if the new check fails, since the
* write is committed. A refusal shows in the window, by its translated code.
*/ */
const reattachOrphan = useCallback( const reattachOrphan = useCallback(
async (orphan: OrphanCategory, parentId: number | null) => { async (orphan: OrphanCategory, parentId: number | null) => {
@ -425,9 +426,12 @@ export function useCategories() {
try { try {
const restored = await reattachCategory(orphan.id, parentId); const restored = await reattachCategory(orphan.id, parentId);
logInfo(reattachLogLine(orphan.reason, restored)); logInfo(reattachLogLine(orphan.reason, restored));
try {
const orphans = await findOrphanCategories(); const orphans = await findOrphanCategories();
dispatch({ type: "SET_ORPHANS", payload: orphans }); dispatch({ type: "SET_ORPHANS", payload: orphans });
} finally {
await loadCategories(); await loadCategories();
}
dispatch({ type: "SET_SAVING", payload: false }); dispatch({ type: "SET_SAVING", payload: false });
} catch (e) { } catch (e) {
dispatch({ type: "SET_HIERARCHY_ERROR", payload: categoriesErrorOf(e) }); dispatch({ type: "SET_HIERARCHY_ERROR", payload: categoriesErrorOf(e) });