fix(client): sidebar-кеш не должен стирать icon/title на field-only событии (ревью #360)

invalidateOnUpdatePage для sidebar-pages кеша спредил сыро {...sidebarPage, title,
icon} — при title-only событии icon приходит undefined и затирался (и наоборот).
Embed-tree путь 20 строками выше уже гардит undefined; sidebar-ветка пропустила
тот же гард. Применён тот же паттерн: ...(title!==undefined?{title}:{}) +
...(icon!==undefined?{icon}:{}). +2 теста (sidebar title-only/icon-only:
непереданное поле не затирается); мутационно (вернуть сырой спред -> тесты краснеют).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 05:08:43 +03:00
parent 6e59793643
commit 6bfb1e645a
2 changed files with 67 additions and 1 deletions
@@ -75,6 +75,65 @@ describe("invalidateOnUpdatePage — pointwise embed-cache patch", () => {
expect(p1.title).toBe("Keep");
});
// The sidebar-pages cache (InfiniteData) is patched on the same event. It must
// carry the SAME undefined-guard as the embed path above — otherwise a
// title-only event's icon:undefined would wipe the sidebar entry's icon.
const sidebarKey = ["sidebar-pages", { pageId: "parent-1", spaceId: "s1" }];
const seedSidebar = () =>
h.qc.setQueryData(sidebarKey, {
pageParams: [undefined],
pages: [
{
items: [
{ id: "p1", title: "Old", icon: "📄", spaceId: "s1" } as IPage,
{ id: "p2", title: "Other", icon: "📁", spaceId: "s1" } as IPage,
],
},
],
});
const sidebarItem = (id: string) => {
const data = h.qc.getQueryData(sidebarKey) as {
pages: { items: IPage[] }[];
};
return data.pages[0].items.find((p) => p.id === id)!;
};
it("sidebar cache: title-only event updates title but PRESERVES the icon", () => {
seedSidebar();
invalidateOnUpdatePage(
"s1",
"parent-1",
"p1",
"New Title",
undefined as unknown as string,
);
const p1 = sidebarItem("p1");
expect(p1.title).toBe("New Title");
expect(p1.icon).toBe("📄"); // preserved, not wiped
// Sibling untouched.
const p2 = sidebarItem("p2");
expect(p2.title).toBe("Other");
expect(p2.icon).toBe("📁");
});
it("sidebar cache: icon-only event updates icon but PRESERVES the title", () => {
seedSidebar();
invalidateOnUpdatePage(
"s1",
"parent-1",
"p1",
undefined as unknown as string,
"🚀",
);
const p1 = sidebarItem("p1");
expect(p1.icon).toBe("🚀");
expect(p1.title).toBe("Old"); // preserved, not wiped
});
it("does not touch a subtree that lacks the updated node", () => {
const otherKey = ["page-tree", "unrelated"];
const before = [
@@ -636,7 +636,14 @@ export function invalidateOnUpdatePage(
...page,
items: page.items.map((sidebarPage: IPage) =>
sidebarPage.id === id
? { ...sidebarPage, title: title, icon: icon }
? {
...sidebarPage,
// Guard undefined so a title-only event can't wipe the icon
// (and vice versa) in the sidebar-pages cache — mirrors the
// embed-cache patch above.
...(title !== undefined ? { title } : {}),
...(icon !== undefined ? { icon } : {}),
}
: sidebarPage,
),
})),