diff --git a/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx b/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx index 5860239fd3..5fa2576cba 100644 --- a/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx +++ b/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx @@ -5,7 +5,7 @@ import React from "react"; import "@testing-library/jest-dom/extend-expect"; -import { render, waitFor } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import { ScrollSpy } from "../scroll-spy"; import { RecursiveTreeView } from "../../tree-view"; @@ -23,7 +23,6 @@ describe("", () => { it("renders w/o errors", () => { const { container } = render(( ( @@ -40,7 +39,6 @@ describe("", () => { it("calls intersection observer", () => { render(( ( @@ -55,9 +53,8 @@ describe("", () => { }); it("renders dataTree component", async () => { - const { queryByTestId } = render(( + render(( ( @@ -71,9 +68,7 @@ describe("", () => { /> )); - await waitFor(() => { - expect(queryByTestId("TreeView")).toBeInTheDocument(); - }); + expect(await screen.findByTestId("TreeView")).toBeInTheDocument(); }); it("throws if no sections founded", () => { @@ -84,7 +79,6 @@ describe("", () => { expect(() => render(( ( Content @@ -101,9 +95,8 @@ describe("", () => { describe(" dataTree inside ", () => { it("contains links to all sections", async () => { - const { queryByTitle } = render(( + render(( ( @@ -124,17 +117,14 @@ describe(" dataTree inside ", () => { /> )); - await waitFor(() => { - expect(queryByTitle("Application")).toBeInTheDocument(); - expect(queryByTitle("Appearance")).toBeInTheDocument(); - expect(queryByTitle("Theme")).toBeInTheDocument(); - }); + expect(await screen.findByTitle("Application")).toBeInTheDocument(); + expect(await screen.findByTitle("Appearance")).toBeInTheDocument(); + expect(await screen.findByTitle("Theme")).toBeInTheDocument(); }); it("not showing links to sections without id", async () => { const { queryByTitle } = render(( ( @@ -154,17 +144,17 @@ describe(" dataTree inside ", () => { /> )); + expect(await screen.findByTitle("Application")).toBeInTheDocument(); + expect(await screen.findByTitle("Appearance")).toBeInTheDocument(); + await waitFor(() => { - expect(queryByTitle("Application")).toBeInTheDocument(); - expect(queryByTitle("Appearance")).toBeInTheDocument(); expect(queryByTitle("Kubectl")).not.toBeInTheDocument(); }); }); it("expands parent sections", async () => { - const { queryByTitle } = render(( + render(( ( @@ -191,16 +181,13 @@ describe(" dataTree inside ", () => { /> )); - await waitFor(() => { - expect(queryByTitle("Application")).toHaveAttribute("aria-expanded"); - expect(queryByTitle("Kubernetes")).toHaveAttribute("aria-expanded"); - }); + expect(await screen.findByTitle("Application")).toHaveAttribute("aria-expanded"); + expect(await screen.findByTitle("Kubernetes")).toHaveAttribute("aria-expanded"); }); it("skips sections without headings", async () => { - const { queryByTitle } = render(( + render(( ( @@ -220,10 +207,11 @@ describe(" dataTree inside ", () => { /> )); + expect(await screen.findByTitle("Application")).toBeInTheDocument(); + await waitFor(() => { - expect(queryByTitle("Application")).toBeInTheDocument(); - expect(queryByTitle("appearance")).not.toBeInTheDocument(); - expect(queryByTitle("Appearance")).not.toBeInTheDocument(); + expect(screen.queryByTitle("appearance")).not.toBeInTheDocument(); + expect(screen.queryByTitle("Appearance")).not.toBeInTheDocument(); }); }); }); diff --git a/src/renderer/components/scroll-spy/scroll-spy.tsx b/src/renderer/components/scroll-spy/scroll-spy.tsx index 17c0e013d7..b31e1cc701 100644 --- a/src/renderer/components/scroll-spy/scroll-spy.tsx +++ b/src/renderer/components/scroll-spy/scroll-spy.tsx @@ -10,7 +10,7 @@ import type { NavigationTree } from "../tree-view"; export interface ScrollSpyProps extends React.DOMAttributes { render: (data: NavigationTree[]) => JSX.Element; - htmlFor: string; // Id of the element to put observers on + htmlFor?: string; // Id of the element to put observers on rootMargin?: string; // https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#creating_an_intersection_observer } @@ -20,12 +20,14 @@ export const ScrollSpy = observer(({ render, htmlFor, rootMargin = "0px 0px -100 const [tree, setTree] = useState([]); const [activeElementId, setActiveElementId] = useState(""); - const setSections = () => { + const setSections = (): NodeListOf => { sections.current = parent.current?.querySelectorAll("section"); if (!sections.current?.length) { throw new Error("No tag founded! Content should be placed inside elements to activate navigation."); } + + return sections.current; }; const getSectionsParentElement = () => { @@ -33,14 +35,14 @@ export const ScrollSpy = observer(({ render, htmlFor, rootMargin = "0px 0px -100 }; const updateNavigation = () => { - const parentSection = getSectionsParentElement(); - - if (parentSection) { - setTree(getNavigation(parentSection)); - } + setTree(getNavigation(getSectionsParentElement())); }; - const getNavigation = (element: Element) => { + const getNavigation = (element: Element | null | undefined): NavigationTree[] => { + if (!element) { + return []; + } + const sections = element.querySelectorAll(":scope > section"); // Searching only direct children of an element. Impossible without :scope const children: NavigationTree[] = []; @@ -50,7 +52,7 @@ export const ScrollSpy = observer(({ render, htmlFor, rootMargin = "0px 0px -100 const name = section.querySelector("h1, h2, h3, h4, h5, h6")?.textContent; const selected = id === activeElementId; - if (!name || !id || !parentId) { + if (!name || !id) { return; } @@ -74,13 +76,13 @@ export const ScrollSpy = observer(({ render, htmlFor, rootMargin = "0px 0px -100 } }; - const observeSections = () => { + const observeSections = (list: NodeListOf) => { const options: IntersectionObserverInit = { - root: document.getElementById(htmlFor) || getSectionsParentElement(), + root: (htmlFor && document.getElementById(htmlFor)) || getSectionsParentElement(), rootMargin, }; - sections.current?.forEach((section) => { + list.forEach((section) => { const observer = new IntersectionObserver(handleIntersect, options); const target = section.querySelector("section") || section; @@ -89,9 +91,10 @@ export const ScrollSpy = observer(({ render, htmlFor, rootMargin = "0px 0px -100 }; useEffect(() => { - setSections(); - observeSections(); - }, []); + const list = setSections(); + + observeSections(list); + }, [parent.current]); useEffect(() => { updateNavigation(); diff --git a/src/renderer/components/tree-view/tree-view.tsx b/src/renderer/components/tree-view/tree-view.tsx index 1d8c346a50..8672f9597f 100644 --- a/src/renderer/components/tree-view/tree-view.tsx +++ b/src/renderer/components/tree-view/tree-view.tsx @@ -18,7 +18,7 @@ const deepDash = getDeepDash(_); export interface NavigationTree { id: string; - parentId: string; + parentId?: string; name: string; selected?: boolean; children?: NavigationTree[];