1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

fix scroll-spy.test.tsx

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-04-12 12:07:53 -04:00
parent fa9683a170
commit 49faa2f9b7
3 changed files with 37 additions and 46 deletions

View File

@ -5,7 +5,7 @@
import React from "react"; import React from "react";
import "@testing-library/jest-dom/extend-expect"; 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 { ScrollSpy } from "../scroll-spy";
import { RecursiveTreeView } from "../../tree-view"; import { RecursiveTreeView } from "../../tree-view";
@ -23,7 +23,6 @@ describe("<ScrollSpy/>", () => {
it("renders w/o errors", () => { it("renders w/o errors", () => {
const { container } = render(( const { container } = render((
<ScrollSpy <ScrollSpy
htmlFor=""
render={() => ( render={() => (
<div> <div>
<section id="application"> <section id="application">
@ -40,7 +39,6 @@ describe("<ScrollSpy/>", () => {
it("calls intersection observer", () => { it("calls intersection observer", () => {
render(( render((
<ScrollSpy <ScrollSpy
htmlFor=""
render={() => ( render={() => (
<div> <div>
<section id="application"> <section id="application">
@ -55,9 +53,8 @@ describe("<ScrollSpy/>", () => {
}); });
it("renders dataTree component", async () => { it("renders dataTree component", async () => {
const { queryByTestId } = render(( render((
<ScrollSpy <ScrollSpy
htmlFor=""
render={dataTree => ( render={dataTree => (
<div> <div>
<nav> <nav>
@ -71,9 +68,7 @@ describe("<ScrollSpy/>", () => {
/> />
)); ));
await waitFor(() => { expect(await screen.findByTestId("TreeView")).toBeInTheDocument();
expect(queryByTestId("TreeView")).toBeInTheDocument();
});
}); });
it("throws if no sections founded", () => { it("throws if no sections founded", () => {
@ -84,7 +79,6 @@ describe("<ScrollSpy/>", () => {
expect(() => render(( expect(() => render((
<ScrollSpy <ScrollSpy
htmlFor=""
render={() => ( render={() => (
<div> <div>
Content Content
@ -101,9 +95,8 @@ describe("<ScrollSpy/>", () => {
describe("<TreeView/> dataTree inside <ScrollSpy/>", () => { describe("<TreeView/> dataTree inside <ScrollSpy/>", () => {
it("contains links to all sections", async () => { it("contains links to all sections", async () => {
const { queryByTitle } = render(( render((
<ScrollSpy <ScrollSpy
htmlFor=""
render={dataTree => ( render={dataTree => (
<div> <div>
<nav> <nav>
@ -124,17 +117,14 @@ describe("<TreeView/> dataTree inside <ScrollSpy/>", () => {
/> />
)); ));
await waitFor(() => { expect(await screen.findByTitle("Application")).toBeInTheDocument();
expect(queryByTitle("Application")).toBeInTheDocument(); expect(await screen.findByTitle("Appearance")).toBeInTheDocument();
expect(queryByTitle("Appearance")).toBeInTheDocument(); expect(await screen.findByTitle("Theme")).toBeInTheDocument();
expect(queryByTitle("Theme")).toBeInTheDocument();
});
}); });
it("not showing links to sections without id", async () => { it("not showing links to sections without id", async () => {
const { queryByTitle } = render(( const { queryByTitle } = render((
<ScrollSpy <ScrollSpy
htmlFor=""
render={dataTree => ( render={dataTree => (
<div> <div>
<nav> <nav>
@ -154,17 +144,17 @@ describe("<TreeView/> dataTree inside <ScrollSpy/>", () => {
/> />
)); ));
expect(await screen.findByTitle("Application")).toBeInTheDocument();
expect(await screen.findByTitle("Appearance")).toBeInTheDocument();
await waitFor(() => { await waitFor(() => {
expect(queryByTitle("Application")).toBeInTheDocument();
expect(queryByTitle("Appearance")).toBeInTheDocument();
expect(queryByTitle("Kubectl")).not.toBeInTheDocument(); expect(queryByTitle("Kubectl")).not.toBeInTheDocument();
}); });
}); });
it("expands parent sections", async () => { it("expands parent sections", async () => {
const { queryByTitle } = render(( render((
<ScrollSpy <ScrollSpy
htmlFor=""
render={dataTree => ( render={dataTree => (
<div> <div>
<nav> <nav>
@ -191,16 +181,13 @@ describe("<TreeView/> dataTree inside <ScrollSpy/>", () => {
/> />
)); ));
await waitFor(() => { expect(await screen.findByTitle("Application")).toHaveAttribute("aria-expanded");
expect(queryByTitle("Application")).toHaveAttribute("aria-expanded"); expect(await screen.findByTitle("Kubernetes")).toHaveAttribute("aria-expanded");
expect(queryByTitle("Kubernetes")).toHaveAttribute("aria-expanded");
});
}); });
it("skips sections without headings", async () => { it("skips sections without headings", async () => {
const { queryByTitle } = render(( render((
<ScrollSpy <ScrollSpy
htmlFor=""
render={dataTree => ( render={dataTree => (
<div> <div>
<nav> <nav>
@ -220,10 +207,11 @@ describe("<TreeView/> dataTree inside <ScrollSpy/>", () => {
/> />
)); ));
expect(await screen.findByTitle("Application")).toBeInTheDocument();
await waitFor(() => { await waitFor(() => {
expect(queryByTitle("Application")).toBeInTheDocument(); expect(screen.queryByTitle("appearance")).not.toBeInTheDocument();
expect(queryByTitle("appearance")).not.toBeInTheDocument(); expect(screen.queryByTitle("Appearance")).not.toBeInTheDocument();
expect(queryByTitle("Appearance")).not.toBeInTheDocument();
}); });
}); });
}); });

View File

@ -10,7 +10,7 @@ import type { NavigationTree } from "../tree-view";
export interface ScrollSpyProps extends React.DOMAttributes<HTMLElement> { export interface ScrollSpyProps extends React.DOMAttributes<HTMLElement> {
render: (data: NavigationTree[]) => JSX.Element; 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 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<NavigationTree[]>([]); const [tree, setTree] = useState<NavigationTree[]>([]);
const [activeElementId, setActiveElementId] = useState(""); const [activeElementId, setActiveElementId] = useState("");
const setSections = () => { const setSections = (): NodeListOf<HTMLElement> => {
sections.current = parent.current?.querySelectorAll("section"); sections.current = parent.current?.querySelectorAll("section");
if (!sections.current?.length) { if (!sections.current?.length) {
throw new Error("No <section/> tag founded! Content should be placed inside <section></section> elements to activate navigation."); throw new Error("No <section/> tag founded! Content should be placed inside <section></section> elements to activate navigation.");
} }
return sections.current;
}; };
const getSectionsParentElement = () => { const getSectionsParentElement = () => {
@ -33,14 +35,14 @@ export const ScrollSpy = observer(({ render, htmlFor, rootMargin = "0px 0px -100
}; };
const updateNavigation = () => { const updateNavigation = () => {
const parentSection = getSectionsParentElement(); setTree(getNavigation(getSectionsParentElement()));
if (parentSection) {
setTree(getNavigation(parentSection));
}
}; };
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 sections = element.querySelectorAll(":scope > section"); // Searching only direct children of an element. Impossible without :scope
const children: NavigationTree[] = []; 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 name = section.querySelector("h1, h2, h3, h4, h5, h6")?.textContent;
const selected = id === activeElementId; const selected = id === activeElementId;
if (!name || !id || !parentId) { if (!name || !id) {
return; return;
} }
@ -74,13 +76,13 @@ export const ScrollSpy = observer(({ render, htmlFor, rootMargin = "0px 0px -100
} }
}; };
const observeSections = () => { const observeSections = (list: NodeListOf<HTMLElement>) => {
const options: IntersectionObserverInit = { const options: IntersectionObserverInit = {
root: document.getElementById(htmlFor) || getSectionsParentElement(), root: (htmlFor && document.getElementById(htmlFor)) || getSectionsParentElement(),
rootMargin, rootMargin,
}; };
sections.current?.forEach((section) => { list.forEach((section) => {
const observer = new IntersectionObserver(handleIntersect, options); const observer = new IntersectionObserver(handleIntersect, options);
const target = section.querySelector("section") || section; const target = section.querySelector("section") || section;
@ -89,9 +91,10 @@ export const ScrollSpy = observer(({ render, htmlFor, rootMargin = "0px 0px -100
}; };
useEffect(() => { useEffect(() => {
setSections(); const list = setSections();
observeSections();
}, []); observeSections(list);
}, [parent.current]);
useEffect(() => { useEffect(() => {
updateNavigation(); updateNavigation();

View File

@ -18,7 +18,7 @@ const deepDash = getDeepDash(_);
export interface NavigationTree { export interface NavigationTree {
id: string; id: string;
parentId: string; parentId?: string;
name: string; name: string;
selected?: boolean; selected?: boolean;
children?: NavigationTree[]; children?: NavigationTree[];