Skip to content

Commit a1f5ab6

Browse files
feat: queryAll (#24)
1 parent 9272441 commit a1f5ab6

4 files changed

Lines changed: 67 additions & 19 deletions

File tree

src/__tests__/host-element.test.tsx

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expect, jest, test } from "@jest/globals";
1+
import { beforeEach, expect, jest, test } from "@jest/globals";
22

33
import type { HostElement } from "../host-element";
44
import { ReactWorkTag } from "../react-constants";
@@ -71,7 +71,7 @@ test("can access composite parent props", async () => {
7171
expect(fiber.return!.memoizedProps).toEqual({ className: "test-class", onChange: handleChange });
7272
});
7373

74-
test("findAll should find all elements that match the predicate", async () => {
74+
test("queryAll should find all elements that match the predicate", async () => {
7575
const renderer = createRoot();
7676
await renderWithAct(
7777
renderer,
@@ -82,7 +82,7 @@ test("findAll should find all elements that match the predicate", async () => {
8282
</body>,
8383
);
8484

85-
const elements = renderer.container.findAll((element) => element.type === "div");
85+
const elements = renderer.container.queryAll((element) => element.type === "div");
8686
expect(elements).toHaveLength(2);
8787
expect(elements[0]).toMatchInlineSnapshot(`
8888
<div>
@@ -96,7 +96,7 @@ test("findAll should find all elements that match the predicate", async () => {
9696
`);
9797
});
9898

99-
test("findAll should find all elements that match the predicate with matchDeepestOnly option", async () => {
99+
test("queryAll should find all elements that match the predicate with matchDeepestOnly option", async () => {
100100
const renderer = createRoot();
101101
await renderWithAct(
102102
renderer,
@@ -110,7 +110,7 @@ test("findAll should find all elements that match the predicate with matchDeepes
110110
</body>,
111111
);
112112

113-
const elements = renderer.container.findAll((element) => element.type === "div", {
113+
const elements = renderer.container.queryAll((element) => element.type === "div", {
114114
matchDeepestOnly: true,
115115
});
116116
expect(elements).toHaveLength(2);
@@ -125,3 +125,45 @@ test("findAll should find all elements that match the predicate with matchDeepes
125125
</div>
126126
`);
127127
});
128+
129+
test("queryAll should not return self by default", async () => {
130+
const renderer = createRoot();
131+
await renderWithAct(
132+
renderer,
133+
<body className="yes">
134+
<div className="yes">Hello!</div>
135+
<span className="yes">World!</span>
136+
<div>Foo!</div>
137+
</body>,
138+
);
139+
140+
const elements = getRootElement(renderer).queryAll(
141+
(element) => element.props.className === "yes",
142+
);
143+
expect(elements).toHaveLength(2);
144+
expect(elements[0].type).toBe("div");
145+
expect(elements[1].type).toBe("span");
146+
});
147+
148+
test("queryAll should return self if 'includeSelf' is true", async () => {
149+
const renderer = createRoot();
150+
await renderWithAct(
151+
renderer,
152+
<body className="yes">
153+
<div className="yes">Hello!</div>
154+
<span className="yes">World!</span>
155+
<div>Foo!</div>
156+
</body>,
157+
);
158+
159+
const elements = getRootElement(renderer).queryAll(
160+
(element) => element.props.className === "yes",
161+
{
162+
includeSelf: true,
163+
},
164+
);
165+
expect(elements).toHaveLength(3);
166+
expect(elements[0].type).toBe("body");
167+
expect(elements[1].type).toBe("div");
168+
expect(elements[2].type).toBe("span");
169+
});

src/host-element.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { Fiber } from "react-reconciler";
22

33
import { Tag } from "./constants";
4-
import type { FindAllOptions } from "./find-all";
5-
import { findAll } from "./find-all";
4+
import type { QueryOptions } from "./query-all";
5+
import { queryAll } from "./query-all";
66
import type { Container, Instance, TextInstance } from "./reconciler";
77
import type { JsonNode } from "./render-to-json";
88
import { renderToJson } from "./render-to-json";
@@ -59,8 +59,11 @@ export class HostElement {
5959
return renderToJson(this.instance);
6060
}
6161

62-
findAll(predicate: (element: HostElement) => boolean, options?: FindAllOptions): HostElement[] {
63-
return findAll(this, predicate, options);
62+
queryAll(
63+
predicate: (element: HostElement, options?: QueryOptions) => boolean,
64+
options?: QueryOptions,
65+
): HostElement[] {
66+
return queryAll(this, predicate, options);
6467
}
6568

6669
/** @internal */

src/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
export { createRoot } from "./renderer";
2-
export { findAll } from "./find-all";
32

4-
export type { Root, RootOptions } from "./renderer";
3+
export type { Root, RootOptions, ErrorHandler, ErrorInfo } from "./renderer";
54
export type { HostElement, HostElementProps, HostNode } from "./host-element";
65
export type { JsonElement, JsonNode } from "./render-to-json";
7-
export type { FindAllOptions } from "./find-all";
6+
export type { QueryOptions } from "./query-all";
87

98
export type { Fiber } from "react-reconciler";

src/find-all.ts renamed to src/query-all.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import type { HostElement } from "./host-element";
22

3-
export interface FindAllOptions {
4-
/* Exclude any ancestors of deepest matched elements even if they match the predicate */
3+
export interface QueryOptions {
4+
/** Include the element itself in the results if it matches the predicate. Defaults to false. */
5+
includeSelf?: boolean;
6+
7+
/** Exclude any ancestors of deepest matched elements even if they match the predicate. Defaults to false. */
58
matchDeepestOnly?: boolean;
69
}
710

8-
export function findAll(
11+
export function queryAll(
912
element: HostElement,
1013
predicate: (element: HostElement) => boolean,
11-
options?: FindAllOptions,
14+
options?: QueryOptions,
1215
): HostElement[] {
16+
const includeSelf = options?.includeSelf ?? false;
1317
const matchDeepestOnly = options?.matchDeepestOnly ?? false;
18+
1419
const results: HostElement[] = [];
1520

1621
// Match descendants first but do not add them to results yet.
@@ -21,12 +26,11 @@ export function findAll(
2126
return;
2227
}
2328

24-
matchingDescendants.push(...findAll(child, predicate, options));
29+
matchingDescendants.push(...queryAll(child, predicate, { ...options, includeSelf: true }));
2530
});
2631

27-
const isHostElement = "props" in element;
2832
if (
29-
isHostElement &&
33+
includeSelf &&
3034
// When matchDeepestOnly = true: add current element only if no descendants match
3135
(matchingDescendants.length === 0 || !matchDeepestOnly) &&
3236
predicate(element)

0 commit comments

Comments
 (0)