Веб: вики — предки выбранной страницы раскрываются, когда её узел появился (#91)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-24 03:28:07 +04:00
parent a37ce0a59c
commit 01c28c890a
2 changed files with 15 additions and 4 deletions

View File

@ -59,4 +59,12 @@ describe("Tree", () => {
expect(item("B")).toHaveAttribute("aria-expanded", "true");
expect(item("E")).toBeInTheDocument();
});
it("выбранная страница появилась в nodes после монтирования — предки раскрываются", () => {
const { rerender } = render(<Tree nodes={[N("A", 0, null)]} selected="C" onOpen={() => {}} />);
expect(screen.queryByRole("treeitem", { name: "Стр C" })).toBeNull();
rerender(<Tree nodes={NODES} selected="C" onOpen={() => {}} />);
expect(item("B")).toHaveAttribute("aria-expanded", "true");
expect(item("C")).toBeInTheDocument();
});
});

View File

@ -12,17 +12,20 @@ export function Tree({ nodes, selected, onOpen }: { nodes: WikiNode[]; selected:
const [focus, setFocus] = useState<string | null>(null);
const refs = useRef(new Map<string, HTMLDivElement>());
// предки выбранной страницы раскрываются только при смене 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++) {