/* * SonarQube * Copyright (C) 2009-2024 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { BoundFunction, GetByBoundAttribute, GetByRole, GetByText, screen, waitForOptions, within, } from '@testing-library/react'; function maybeScreen(container?: HTMLElement) { return container ? within(container) : screen; } interface ReactTestingQuery { find( container?: HTMLElement, waitForOptions?: waitForOptions, ): Promise; findAll( container?: HTMLElement, waitForOptions?: waitForOptions, ): Promise; get(container?: HTMLElement): T; getAll(container?: HTMLElement): T[]; query(container?: HTMLElement): T | null; queryAll(container?: HTMLElement): T[] | null; } export class QuerySelector { dispatchQuery: ReactTestingQuery; constructor(dispatchQuery: ReactTestingQuery) { this.dispatchQuery = dispatchQuery; } find( container?: HTMLElement, waitForOptions?: waitForOptions, ): Promise { return this.dispatchQuery.find(container, waitForOptions); } findAll( container?: HTMLElement, waitForOptions?: waitForOptions, ): Promise { return this.dispatchQuery.findAll(container, waitForOptions); } get(container?: HTMLElement): T { return this.dispatchQuery.get(container); } getAll(container?: HTMLElement): T[] { return this.dispatchQuery.getAll(container); } query(container?: HTMLElement): T | null { return this.dispatchQuery.query(container); } queryAll(container?: HTMLElement): T[] | null { return this.dispatchQuery.queryAll(container); } getAt(index: number, container?: HTMLElement): T { return this.getAll(container)[index]; } async findAt( index: number, container?: HTMLElement, waitForOptions?: waitForOptions, ): Promise { return (await this.findAll(container, waitForOptions))[index]; } queryAt(index: number, container?: HTMLElement): T | null { const all = this.queryAll(container); if (all) { return all[index]; } return null; } by(selector: ReactTestingQuery): QuerySelector { return new ChainDispatch(this, selector); } byText(...args: Parameters>): QuerySelector { return this.by(new DispatchByText(args)); } byRole(...args: Parameters>): QuerySelector { return this.by(new DispatchByRole(args)); } byPlaceholderText(...args: Parameters>): QuerySelector { return this.by(new DispatchByPlaceholderText(args)); } byLabelText(...args: Parameters>): QuerySelector { return this.by(new DispatchByLabelText(args)); } byTestId(...args: Parameters>): QuerySelector { return this.by(new DispatchByTestId(args)); } byDisplayValue(...args: Parameters>): QuerySelector { return this.by(new DispatchByDisplayValue(args)); } byTitle(...args: Parameters>): ReactTestingQuery { return this.by(new DispatchByTitle(args)); } } class ChainDispatch extends QuerySelector { innerQuery: QuerySelector; constructor(insideQuery: QuerySelector, elementQuery: ReactTestingQuery) { super(elementQuery); this.innerQuery = insideQuery; } async find( container?: HTMLElement, waitForOptions?: waitForOptions, ) { let inside: HTMLElement; try { inside = await this.innerQuery.find(container, waitForOptions); } catch (e) { const elements = this.innerQuery.getAll(container); const all = ( await Promise.all( elements.map((e) => this.dispatchQuery.findAll(e, waitForOptions).catch(() => null)), ) ) .flat() .filter((e) => e !== null); if (all.length !== 1) { throw e; } return all[0] as T; } return this.dispatchQuery.find(inside, waitForOptions); } async findAll( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return this.dispatchQuery.findAll(await this.innerQuery.find(container, waitForOptions)); } get(container?: HTMLElement) { let inside: HTMLElement; try { inside = this.innerQuery.get(container); } catch (e) { const elements = this.innerQuery.getAll(container); const all = elements.map((e) => this.dispatchQuery.query(e)).filter((e) => e !== null); if (all.length !== 1) { throw e; } return all[0] as T; } return this.dispatchQuery.get(inside); } getAll(container?: HTMLElement) { const containers = this.innerQuery.getAll(container); return containers.reduce( (acc, item) => [...acc, ...(this.dispatchQuery.queryAll(item) ?? [])], [], ); } query(container?: HTMLElement) { let inside: HTMLElement | null; try { inside = this.innerQuery.query(container); } catch (e) { const elements = this.innerQuery.queryAll(container); const all = elements?.map((e) => this.dispatchQuery.query(e)).filter((e) => e !== null); if (all?.length !== 1) { throw e; } return all[0]; } if (inside) { return this.dispatchQuery.query(inside); } return null; } queryAll(container?: HTMLElement) { const innerContainer = this.innerQuery.query(container); if (innerContainer) { return this.dispatchQuery.queryAll(innerContainer); } return null; } } class DispatchByText implements ReactTestingQuery { readonly args: Parameters>; constructor(args: Parameters>) { this.args = args; } find( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findByText(...this.args, waitForOptions); } findAll( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findAllByText(...this.args, waitForOptions); } get(container?: HTMLElement) { return maybeScreen(container).getByText(...this.args); } getAll(container?: HTMLElement) { return maybeScreen(container).getAllByText(...this.args); } query(container?: HTMLElement) { return maybeScreen(container).queryByText(...this.args); } queryAll(container?: HTMLElement) { return maybeScreen(container).queryAllByText(...this.args); } } class DispatchByLabelText implements ReactTestingQuery { readonly args: Parameters>; constructor(args: Parameters>) { this.args = args; } find( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findByLabelText(...this.args, waitForOptions); } findAll( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findAllByLabelText(...this.args, waitForOptions); } get(container?: HTMLElement) { return maybeScreen(container).getByLabelText(...this.args); } getAll(container?: HTMLElement) { return maybeScreen(container).getAllByLabelText(...this.args); } query(container?: HTMLElement) { return maybeScreen(container).queryByLabelText(...this.args); } queryAll(container?: HTMLElement) { return maybeScreen(container).queryAllByLabelText(...this.args); } } class DispatchByRole implements ReactTestingQuery { readonly args: Parameters>; constructor(args: Parameters>) { this.args = args; } find( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findByRole(...this.args, waitForOptions); } findAll( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findAllByRole(...this.args, waitForOptions); } get(container?: HTMLElement) { return maybeScreen(container).getByRole(...this.args); } getAll(container?: HTMLElement) { return maybeScreen(container).getAllByRole(...this.args); } query(container?: HTMLElement) { return maybeScreen(container).queryByRole(...this.args); } queryAll(container?: HTMLElement) { return maybeScreen(container).queryAllByRole(...this.args); } } class DispatchByTestId implements ReactTestingQuery { readonly args: Parameters>; constructor(args: Parameters>) { this.args = args; } find( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findByTestId(...this.args, waitForOptions); } findAll( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findAllByTestId(...this.args, waitForOptions); } get(container?: HTMLElement) { return maybeScreen(container).getByTestId(...this.args); } getAll(container?: HTMLElement) { return maybeScreen(container).getAllByTestId(...this.args); } query(container?: HTMLElement) { return maybeScreen(container).queryByTestId(...this.args); } queryAll(container?: HTMLElement) { return maybeScreen(container).queryAllByTestId(...this.args); } } class DispatchByTitle implements ReactTestingQuery { readonly args: Parameters>; constructor(args: Parameters>) { this.args = args; } find( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findByTitle(...this.args, waitForOptions); } findAll( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findAllByTitle(...this.args, waitForOptions); } get(container?: HTMLElement) { return maybeScreen(container).getByTitle(...this.args); } getAll(container?: HTMLElement) { return maybeScreen(container).getAllByTitle(...this.args); } query(container?: HTMLElement) { return maybeScreen(container).queryByTitle(...this.args); } queryAll(container?: HTMLElement) { return maybeScreen(container).queryAllByTitle(...this.args); } } class DispatchByDisplayValue implements ReactTestingQuery { readonly args: Parameters>; constructor(args: Parameters>) { this.args = args; } find( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findByDisplayValue(...this.args, waitForOptions); } findAll( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findAllByDisplayValue(...this.args, waitForOptions); } get(container?: HTMLElement) { return maybeScreen(container).getByDisplayValue(...this.args); } getAll(container?: HTMLElement) { return maybeScreen(container).getAllByDisplayValue(...this.args); } query(container?: HTMLElement) { return maybeScreen(container).queryByDisplayValue(...this.args); } queryAll(container?: HTMLElement) { return maybeScreen(container).queryAllByDisplayValue(...this.args); } } class DispatchByPlaceholderText implements ReactTestingQuery { readonly args: Parameters>; constructor(args: Parameters>) { this.args = args; } find( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findByPlaceholderText(...this.args, waitForOptions); } findAll( container?: HTMLElement, waitForOptions?: waitForOptions, ) { return maybeScreen(container).findAllByPlaceholderText(...this.args, waitForOptions); } get(container?: HTMLElement) { return maybeScreen(container).getByPlaceholderText(...this.args); } getAll(container?: HTMLElement) { return maybeScreen(container).getAllByPlaceholderText(...this.args); } query(container?: HTMLElement) { return maybeScreen(container).queryByPlaceholderText(...this.args); } queryAll(container?: HTMLElement) { return maybeScreen(container).queryAllByPlaceholderText(...this.args); } } export function byText(...args: Parameters>) { return new QuerySelector(new DispatchByText(args)); } export function byRole(...args: Parameters>) { return new QuerySelector(new DispatchByRole(args)); } export function byPlaceholderText(...args: Parameters>) { return new QuerySelector(new DispatchByPlaceholderText(args)); } export function byLabelText(...args: Parameters>) { return new QuerySelector(new DispatchByLabelText(args)); } export function byTestId(...args: Parameters>) { return new QuerySelector(new DispatchByTestId(args)); } export function byTitle(...args: Parameters>) { return new QuerySelector(new DispatchByTitle(args)); } export function byDisplayValue(...args: Parameters>) { return new QuerySelector(new DispatchByDisplayValue(args)); }