import type { RenderResult } from "@testing-library/react"; import { prettyDOM as prettyDom } from "@testing-library/dom"; type DiscoverySourceTypes = RenderResult | Element; export type QuerySingleElement = ( attributeName: string, attributeValue?: string, ) => { discovered: Element | null } & Discover; type Clickable = { click: () => void }; export type GetSingleElement = ( attributeName: string, attributeValue?: string, ) => { discovered: Element } & Discover & Clickable; export type QueryAllElements = (attributeName: string) => { discovered: Element[]; attributeValues: (string | null)[]; }; export interface Discover { querySingleElement: QuerySingleElement; queryAllElements: QueryAllElements; getSingleElement: GetSingleElement; } const getBaseElement = (source: DiscoverySourceTypes) => ("baseElement" in source ? source.baseElement : source); export function querySingleElement(getSource: () => DiscoverySourceTypes): QuerySingleElement { return (attributeName, attributeValue) => { const source = getSource(); const dataAttribute = `data-${attributeName}-test`; const selector = attributeValue ? `[${dataAttribute}="${attributeValue}"]` : `[${dataAttribute}]`; const discovered = getBaseElement(source).querySelector(selector); // eslint-disable-next-line @typescript-eslint/no-use-before-define const nestedDiscover = discoverFor(() => { if (!discovered) { throw new Error("Tried to do nested discover using source that does not exist"); } return discovered; }); return { discovered, ...nestedDiscover, }; }; } export function queryAllElements(getSource: () => DiscoverySourceTypes): QueryAllElements { return (attributeName) => { const source = getSource(); const dataAttribute = `data-${attributeName}-test`; const results = [...getBaseElement(source).querySelectorAll(`[${dataAttribute}]`)]; return { discovered: results, attributeValues: results.map((result) => result.getAttribute(dataAttribute)), }; }; } export function getSingleElement(getSource: () => DiscoverySourceTypes): GetSingleElement { return (attributeName, attributeValue) => { const dataAttribute = `data-${attributeName}-test`; const { discovered, ...nestedDiscover } = querySingleElement(getSource)(attributeName, attributeValue); if (!discovered) { // eslint-disable-next-line xss/no-mixed-html const html = prettyDom(getBaseElement(getSource())); if (attributeValue) { const validValues = queryAllElements(getSource)(attributeName).attributeValues; throw new Error( `Couldn't find HTML-element with attribute "${dataAttribute}" with value "${attributeValue}".\n\nPresent values are:\n\n"${validValues.join( '",\n"', )}"\n\nHTML is:\n\n${html}`, ); } throw new Error(`Couldn't find HTML-element with attribute "${dataAttribute}"\n\nHTML is:\n\n${html}`); } const click = () => { if ("click" in discovered && typeof discovered.click === "function") { discovered.click(); } else { throw new Error(`Tried to click something that was not clickable:\n\n${prettyDom(discovered)}`); } }; return { discovered, click, ...nestedDiscover }; }; } export function discoverFor(getSource: () => DiscoverySourceTypes): Discover { return { querySingleElement: querySingleElement(getSource), queryAllElements: queryAllElements(getSource), getSingleElement: getSingleElement(getSource), }; }