diff --git a/web/src/modules/wiki/Tree.test.tsx b/web/src/modules/wiki/Tree.test.tsx index c0b2d06..bfd307f 100644 --- a/web/src/modules/wiki/Tree.test.tsx +++ b/web/src/modules/wiki/Tree.test.tsx @@ -59,4 +59,12 @@ describe("Tree", () => { expect(item("B")).toHaveAttribute("aria-expanded", "true"); expect(item("E")).toBeInTheDocument(); }); + + it("выбранная страница появилась в nodes после монтирования — предки раскрываются", () => { + const { rerender } = render( {}} />); + expect(screen.queryByRole("treeitem", { name: "Стр C" })).toBeNull(); + rerender( {}} />); + expect(item("B")).toHaveAttribute("aria-expanded", "true"); + expect(item("C")).toBeInTheDocument(); + }); }); diff --git a/web/src/modules/wiki/Tree.tsx b/web/src/modules/wiki/Tree.tsx index a381e56..8b24edb 100644 --- a/web/src/modules/wiki/Tree.tsx +++ b/web/src/modules/wiki/Tree.tsx @@ -12,17 +12,20 @@ export function Tree({ nodes, selected, onOpen }: { nodes: WikiNode[]; selected: const [focus, setFocus] = useState(null); const refs = useRef(new Map()); - // предки выбранной страницы раскрываются только при смене selected: новый массив nodes (перечитанное дерево) и ручное - // сворачивание родителя этого не повторяют; актуальные узлы читаются через ref и в зависимости не входят + // предки выбранной страницы раскрываются при смене selected и когда её узел впервые появился в nodes (дерево из кэша + // дополнилось свежим набором). Новый массив nodes, добавление других узлов и ручное сворачивание родителя этого не + // повторяют: selectedKnown меняется только с false на true. Актуальные узлы читаются через byCodeRef; его эффект + // объявлен ВЫШЕ эффекта раскрытия, поэтому в одном проходе ref уже свежий + const selectedKnown = !!selected && byCode.has(selected); const byCodeRef = useRef(byCode); useEffect(() => { byCodeRef.current = byCode; }); useEffect(() => { - if (!selected) return; + if (!selected || !selectedKnown) return; const map = byCodeRef.current; const up: string[] = []; for (let n = map.get(selected)?.parent_code; n && !up.includes(n); n = map.get(n)?.parent_code ?? null) up.push(n); if (up.length) setOpen((o) => (up.every((c) => o.has(c)) ? o : new Set([...o, ...up]))); - }, [selected]); + }, [selected, selectedKnown]); const visible = useMemo(() => nodes.filter((n) => { for (let p = n.parent_code, guard = 0; p && guard < nodes.length; p = byCode.get(p)?.parent_code ?? null, guard++) {