Skip to content

Commit ec34b6c

Browse files
authored
feat: An HTMLElement can be provided to query functions in order to contain what elements are found.
Searching within an element
2 parents 3feba77 + 303bc71 commit ec34b6c

File tree

2 files changed

+143
-15
lines changed

2 files changed

+143
-15
lines changed

src/happy-path.spec.tsx

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { render, screen } from '@testing-library/react';
1+
import { buildQueries, getByRole, render, screen, within } from '@testing-library/react';
22
import React, { useEffect, useState } from 'react';
33
import { setupMockComponent, getByMockComponent } from './index';
44
import { findAllByMockComponent, findByMockComponent, getAllByMockComponent, queryAllByMockComponent, queryByMockComponent } from './queries';
@@ -64,7 +64,7 @@ it('should find via getAllByMockComponent', () => {
6464
expect(all).toHaveLength(2);
6565
expect(all[0]).toBeInTheDocument();
6666
expect(all[1]).toBeInTheDocument();
67-
expect(all[0]).not.toBe(all[2]);
67+
expect(all[0]).not.toBe(all[1]);
6868
});
6969

7070
it('should find via queryByMockComponent', () => {
@@ -122,3 +122,114 @@ it('should find via findAllByMockComponent', async () => {
122122
expect(all[1]).toBeInTheDocument();
123123
expect(all[0]).not.toBe(all[2]);
124124
});
125+
126+
describe('Searching within a containing element', () => {
127+
it('should find within an element via getByMockComponent', () => {
128+
render(
129+
<ul>{['A', 'B'].map(value => <li key={value}><MockedComponent value={value} /></li>)}</ul>
130+
);
131+
132+
const container = screen.getAllByRole('listitem')[0];
133+
expect(getByMockComponent(MockedComponent, {container}).props.value).toEqual('A');
134+
});
135+
136+
it('should find within an element via getAllByMockComponent', () => {
137+
render(
138+
<ul>{['A', 'B'].map(value =>
139+
<li key={value}>
140+
<MockedComponent value={value} />
141+
<MockedComponent value={value} />
142+
</li>)}
143+
</ul>
144+
);
145+
146+
const container = screen.getAllByRole('listitem')[0];
147+
const all = getAllByMockComponent(MockedComponent, {container});
148+
expect(all).toHaveLength(2);
149+
expect(all[0].props.value).toEqual('A');
150+
expect(all[1].props.value).toEqual('A');
151+
expect(all[0]).not.toBe(all[1]);
152+
});
153+
154+
it('should find within an element via queryByMockComponent', () => {
155+
render(
156+
<ul>{['A', 'B'].map(value =>
157+
<li key={value}>
158+
<MockedComponent value={value} />
159+
</li>)}
160+
</ul>
161+
);
162+
163+
const container = screen.getAllByRole('listitem')[0];
164+
expect(queryByMockComponent(MockedComponent, {container})?.props.value).toEqual('A');
165+
});
166+
167+
it('should find within an element via queryAllByMockComponent', () => {
168+
render(
169+
<ul>{['A', 'B'].map(value =>
170+
<li key={value}>
171+
<TestedComponent value={value} />
172+
<TestedComponent value={value} />
173+
</li>)}
174+
</ul>
175+
);
176+
177+
const container = screen.getAllByRole('listitem')[0];
178+
const all = queryAllByMockComponent(MockedComponent, {container});
179+
expect(all).toHaveLength(2);
180+
expect(all[0]).toBeInTheDocument();
181+
expect(all[1]).toBeInTheDocument();
182+
expect(all[0]).not.toBe(all[1]);
183+
});
184+
185+
it('should find within an element via findByMockComponent', async () => {
186+
const TimedFunction = () => {
187+
const [visible, setVisible] = useState(false);
188+
useEffect(() => {
189+
const handle = setTimeout(() => setVisible(true), 10);
190+
return () => clearTimeout(handle);
191+
}, []);
192+
return <div>{visible && <MockedComponent value='' />}</div>;
193+
};
194+
render(
195+
<ul>{['A', 'B'].map(value =>
196+
<li key={value}>
197+
<TimedFunction />
198+
</li>)}
199+
</ul>
200+
);
201+
202+
const container = screen.getAllByRole('listitem')[0];
203+
expect(await findByMockComponent(MockedComponent, {container})).toBeInTheDocument();
204+
});
205+
206+
it('should find within an element via findAllByMockComponent', async () => {
207+
const TimedFunction = () => {
208+
const [visible, setVisible] = useState(false);
209+
useEffect(() => {
210+
const handle = setTimeout(() => setVisible(true), 10);
211+
return () => clearTimeout(handle);
212+
}, []);
213+
return (
214+
<div>
215+
{visible && <span><MockedComponent value='' /><MockedComponent value='' /></span>}
216+
</div>
217+
);
218+
};
219+
render(
220+
<ul>{['A', 'B'].map(value =>
221+
<li key={value}>
222+
<TimedFunction />
223+
<TimedFunction />
224+
</li>)}
225+
</ul>
226+
);
227+
228+
const container = screen.getAllByRole('listitem')[0];
229+
const all = await findAllByMockComponent(MockedComponent, {container});
230+
expect(all).toHaveLength(2);
231+
expect(all[0]).toBeInTheDocument();
232+
expect(all[1]).toBeInTheDocument();
233+
expect(all[0]).not.toBe(all[1]);
234+
});
235+
});

src/queries.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { FC } from "react";
22
import { MockedElement } from "./setupMockComponent";
3-
import {MatcherFunction, screen} from '@testing-library/react';
3+
import {MatcherFunction, screen, within} from '@testing-library/react';
44

55
function buildMatcherFunctionForComponent<PropType>(mockComponent: FC<PropType>): MatcherFunction {
66
return (_, element) => {
@@ -9,44 +9,61 @@ function buildMatcherFunctionForComponent<PropType>(mockComponent: FC<PropType>)
99
};
1010
}
1111

12+
function resolveQueries(options?: QueryOptions) {
13+
if (options?.container) {
14+
return within(options?.container);
15+
}
16+
return screen;
17+
}
18+
19+
/**
20+
* A set of options that can be passed to the `*ByMockComponent` functions
21+
*/
22+
export interface QueryOptions {
23+
/**
24+
* If provided, search for mocked components only within this container
25+
*/
26+
container: HTMLElement
27+
}
28+
1229
/**
1330
* Attempt to find the mocked element. Throws if not exactly one is found.
1431
*/
15-
export function getByMockComponent<PropType>(mockComponent: FC<PropType>): MockedElement<PropType> {
16-
return screen.getByTestId(buildMatcherFunctionForComponent(mockComponent));
32+
export function getByMockComponent<PropType>(mockComponent: FC<PropType>, options?: QueryOptions): MockedElement<PropType> {
33+
return resolveQueries(options).getByTestId(buildMatcherFunctionForComponent(mockComponent));
1734
}
1835

1936
/**
2037
* Attempt to find all mocked elements. Throws if none found.
2138
*/
22-
export function getAllByMockComponent<PropType>(mockComponent: FC<PropType>): MockedElement<PropType>[] {
23-
return screen.getAllByTestId(buildMatcherFunctionForComponent(mockComponent));
39+
export function getAllByMockComponent<PropType>(mockComponent: FC<PropType>, options?: QueryOptions): MockedElement<PropType>[] {
40+
return resolveQueries(options).getAllByTestId(buildMatcherFunctionForComponent(mockComponent));
2441
}
2542

2643
/**
2744
* Attempt to find the mocked element. Returns null if not found. Throws if multiple found.
2845
*/
29-
export function queryByMockComponent<PropType>(mockComponent: FC<PropType>): MockedElement<PropType> | null {
30-
return screen.queryByTestId(buildMatcherFunctionForComponent(mockComponent));
46+
export function queryByMockComponent<PropType>(mockComponent: FC<PropType>, options?: QueryOptions): MockedElement<PropType> | null {
47+
return resolveQueries(options).queryByTestId(buildMatcherFunctionForComponent(mockComponent));
3148
}
3249

3350
/**
3451
* Attempt to find all mocked element. Returns empty array if not found.
3552
*/
36-
export function queryAllByMockComponent<PropType>(mockComponent: FC<PropType>): MockedElement<PropType>[] {
37-
return screen.queryAllByTestId(buildMatcherFunctionForComponent(mockComponent));
53+
export function queryAllByMockComponent<PropType>(mockComponent: FC<PropType>, options?: QueryOptions): MockedElement<PropType>[] {
54+
return resolveQueries(options).queryAllByTestId(buildMatcherFunctionForComponent(mockComponent));
3855
}
3956

4057
/**
4158
* Attempt to find the mocked element. Reject if not eventually found or multiple are found.
4259
*/
43-
export function findByMockComponent<PropType>(mockComponent: FC<PropType>): Promise<MockedElement<PropType>> {
44-
return screen.findByTestId(buildMatcherFunctionForComponent(mockComponent));
60+
export function findByMockComponent<PropType>(mockComponent: FC<PropType>, options?: QueryOptions): Promise<MockedElement<PropType>> {
61+
return resolveQueries(options).findByTestId(buildMatcherFunctionForComponent(mockComponent));
4562
}
4663

4764
/**
4865
* Attempt to find all the mocked element. Rejects if non are eventually found.
4966
*/
50-
export function findAllByMockComponent<PropType>(mockComponent: FC<PropType>): Promise<MockedElement<PropType>[]> {
51-
return screen.findAllByTestId(buildMatcherFunctionForComponent(mockComponent));
67+
export function findAllByMockComponent<PropType>(mockComponent: FC<PropType>, options?: QueryOptions): Promise<MockedElement<PropType>[]> {
68+
return resolveQueries(options).findAllByTestId(buildMatcherFunctionForComponent(mockComponent));
5269
}

0 commit comments

Comments
 (0)