Skip to content

Commit fb02d65

Browse files
committed
feat: 🎸 DOMSelector support for query parentSelector
Allow usage of byTestId(), byText(), byRole(), etc in query's parentSelector
1 parent 156bb57 commit fb02d65

File tree

8 files changed

+117
-17
lines changed

8 files changed

+117
-17
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,10 +556,13 @@ For example, in this following HTML `byText('foobar', {selector: 'div'})` won't
556556
```
557557

558558
### Parent Selector
559-
Spectator allows you to query for nested elements within a parent element. This is useful when you have multiple instances of the same component on the page and you want to query for children within a specific one. The parent selector is a string selector that is used to find the parent element. The parent selector is passed as the second parameter to the query methods. For example:
559+
Spectator allows you to query for nested elements within a parent element. This is useful when you have multiple instances of the same component on the page and you want to query for children within a specific one. The parent selector is a string selector or `DOMSelector` that is used to find the parent element. The parent selector is passed as the second parameter to the query methods. For example:
560560
```ts
561561
spectator.query(ChildComponent, { parentSelector: '#parent-component-1' });
562562
spectator.queryAll(ChildComponent, { parentSelector: '#parent-component-1' });
563+
564+
spectator.query(ChildComponent, { parentSelector: byTestId('parent-component-1') });
565+
spectator.queryAll(ChildComponent, { parentSelector: byTestId('parent-component-1') });
563566
```
564567

565568
#### Testing Select Elements

docs/docs/queries.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,13 @@ configure({ testIdAttribute: 'data-test' });
7474
```
7575

7676
### Parent Selector
77-
Spectator allows you to query for nested elements within a parent element. This is useful when you have multiple instances of the same component on the page and you want to query for children within a specific one. The parent selector is a string selector that is used to find the parent element. The parent selector is passed as the second parameter to the query methods. For example:
77+
Spectator allows you to query for nested elements within a parent element. This is useful when you have multiple instances of the same component on the page and you want to query for children within a specific one. The parent selector is a string selector or `DOMSelector` that is used to find the parent element. The parent selector is passed as the second parameter to the query methods. For example:
7878
```ts
7979
spectator.query(ChildComponent, { parentSelector: '#parent-component-1' });
8080
spectator.queryAll(ChildComponent, { parentSelector: '#parent-component-1' });
81+
82+
spectator.query(ChildComponent, { parentSelector: byTestId('parent-component-1') });
83+
spectator.queryAll(ChildComponent, { parentSelector: byTestId('parent-component-1') });
8184
```
8285

8386
## Testing Select Elements

projects/spectator/jest/test/dom-selectors/dom-selectors.component.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,35 @@ describe('DomSelectorsComponent', () => {
7171
element = spectator.queryAll(DomSelectorsNestedComponent, { parentSelector: '#nested-components-1' });
7272
expect(element.length).toBe(1);
7373
});
74+
75+
it('should allow querying multiple element by parent testId', () => {
76+
let element = spectator.queryAll(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-2') });
77+
expect(element.length).toBe(2);
78+
element = spectator.queryAll(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-1') });
79+
expect(element.length).toBe(1);
80+
});
81+
82+
it('should allow querying first element by parent selector', () => {
83+
{
84+
let element = spectator.query(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-1') });
85+
expect(element).toHaveId('alone-in-group');
86+
}
87+
{
88+
let element = spectator.query(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-2') });
89+
expect(element).toHaveId('first');
90+
}
91+
});
92+
93+
it('should allow querying last element by parent selector', () => {
94+
{
95+
let element = spectator.queryLast(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-1') });
96+
expect(element).toHaveId('alone-in-group');
97+
}
98+
{
99+
let element = spectator.queryLast(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-2') });
100+
expect(element).toHaveId('last');
101+
}
102+
});
74103
});
75104

76105
describe('byTextContent', () => {

projects/spectator/src/lib/base/dom-spectator.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ export abstract class DomSpectator<I> extends BaseSpectator {
4646

4747
public query<R extends Element>(selector: string | DOMSelector, options?: { root: boolean }): R | null;
4848
public query<R>(directive: Type<R>, options?: { root: boolean }): R | null;
49-
public query<R extends Element>(selector: string | DOMSelector, options?: { parentSelector: Type<any> | string }): R | null;
50-
public query<R>(directive: Type<R>, options?: { parentSelector?: Type<any> | string }): R | null;
49+
public query<R extends Element>(selector: string | DOMSelector, options?: { parentSelector: Type<any> | string | DOMSelector }): R | null;
50+
public query<R>(directive: Type<R>, options?: { parentSelector?: Type<any> | string | DOMSelector }): R | null;
5151
public query<R>(
5252
directiveOrSelector: Type<any> | string,
53-
options: { read: Token<R>; root?: boolean; parentSelector?: Type<any> | string },
53+
options: { read: Token<R>; root?: boolean; parentSelector?: Type<any> | string | DOMSelector },
5454
): R | null;
5555
public query<R>(directiveOrSelector: QueryType, options?: QueryOptions<R>): R | Element | null {
5656
if ((options || {}).root) {
@@ -86,11 +86,11 @@ export abstract class DomSpectator<I> extends BaseSpectator {
8686

8787
public queryAll<R extends Element>(selector: string | DOMSelector, options?: { root: boolean }): R[];
8888
public queryAll<R>(directive: Type<R>, options?: { root: boolean }): R[];
89-
public queryAll<R extends Element>(selector: string | DOMSelector, options?: { parentSelector: Type<any> | string }): R[];
90-
public queryAll<R>(directive: Type<R>, options?: { parentSelector: Type<any> | string }): R[];
89+
public queryAll<R extends Element>(selector: string | DOMSelector, options?: { parentSelector: Type<any> | string | DOMSelector }): R[];
90+
public queryAll<R>(directive: Type<R>, options?: { parentSelector: Type<any> | string | DOMSelector }): R[];
9191
public queryAll<R>(
9292
directiveOrSelector: Type<any> | string,
93-
options: { read: Token<R>; root?: boolean; parentSelector?: Type<any> | string },
93+
options: { read: Token<R>; root?: boolean; parentSelector?: Type<any> | string | DOMSelector },
9494
): R[];
9595
public queryAll<R>(directiveOrSelector: QueryType, options?: QueryOptions<R>): R[] | Element[] {
9696
if ((options || {}).root) {
@@ -124,11 +124,14 @@ export abstract class DomSpectator<I> extends BaseSpectator {
124124

125125
public queryLast<R extends Element>(selector: string | DOMSelector, options?: { root: boolean }): R | null;
126126
public queryLast<R>(directive: Type<R>, options?: { root: boolean }): R | null;
127-
public queryLast<R extends Element>(selector: string | DOMSelector, options?: { parentSelector: Type<any> | string }): R | null;
128-
public queryLast<R>(directive: Type<R>, options?: { parentSelector: Type<any> | string }): R | null;
127+
public queryLast<R extends Element>(
128+
selector: string | DOMSelector,
129+
options?: { parentSelector: Type<any> | string | DOMSelector },
130+
): R | null;
131+
public queryLast<R>(directive: Type<R>, options?: { parentSelector: Type<any> | string | DOMSelector }): R | null;
129132
public queryLast<R>(
130133
directiveOrSelector: Type<any> | string,
131-
options: { read: Token<R>; root?: boolean; parentSelector?: Type<any> | string },
134+
options: { read: Token<R>; root?: boolean; parentSelector?: Type<any> | string | DOMSelector },
132135
): R | null;
133136
public queryLast<R>(directiveOrSelector: QueryType, options?: QueryOptions<R>): R | Element | null {
134137
let result: (R | Element)[] = [];
@@ -366,13 +369,15 @@ export abstract class DomSpectator<I> extends BaseSpectator {
366369
}
367370

368371
private getDebugElement(
369-
directiveOrSelector: string | DebugElement | Type<unknown>,
372+
directiveOrSelector: string | DebugElement | Type<unknown> | DOMSelector,
370373
options?: { root: boolean },
371374
): DebugElement | undefined {
372375
const debugElement = options?.root ? this.getRootDebugElement() : this.debugElement;
373376

374377
if (isString(directiveOrSelector)) {
375378
return debugElement.query(By.css(directiveOrSelector));
379+
} else if (directiveOrSelector instanceof DOMSelector) {
380+
return new DebugElement(directiveOrSelector.execute(document as unknown as HTMLElement)[0]);
376381
} else if (directiveOrSelector instanceof DebugElement) {
377382
return directiveOrSelector;
378383
} else {

projects/spectator/src/lib/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export type QueryType = Type<any> | DOMSelector | string;
2525
export interface QueryOptions<R> {
2626
read?: Token<R>;
2727
root?: boolean;
28-
parentSelector?: Type<any> | string;
28+
parentSelector?: Type<any> | string | DOMSelector;
2929
}
3030

3131
export type OutputType<P> = P extends EventEmitter<infer T> ? T : P extends OutputRef<infer T> ? T : never;

projects/spectator/test/dom-selectors/dom-selectors.component.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,35 @@ describe('DomSelectorsComponent', () => {
7272
element = spectator.queryAll(DomSelectorsNestedComponent, { parentSelector: '#nested-components-1' });
7373
expect(element.length).toBe(1);
7474
});
75+
76+
it('should allow querying multiple element by parent testId', () => {
77+
let element = spectator.queryAll(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-2') });
78+
expect(element.length).toBe(2);
79+
element = spectator.queryAll(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-1') });
80+
expect(element.length).toBe(1);
81+
});
82+
83+
it('should allow querying first element by parent selector', () => {
84+
{
85+
let element = spectator.query(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-1') });
86+
expect(element).toHaveId('alone-in-group');
87+
}
88+
{
89+
let element = spectator.query(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-2') });
90+
expect(element).toHaveId('first');
91+
}
92+
});
93+
94+
it('should allow querying last element by parent selector', () => {
95+
{
96+
let element = spectator.queryLast(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-1') });
97+
expect(element).toHaveId('alone-in-group');
98+
}
99+
{
100+
let element = spectator.queryLast(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-2') });
101+
expect(element).toHaveId('last');
102+
}
103+
});
75104
});
76105

77106
describe('byTextContent', () => {

projects/spectator/test/dom-selectors/dom-selectors.component.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import { Component } from '@angular/core';
1+
import { Component, Input } from '@angular/core';
22

33
@Component({
44
selector: 'app-dom-selectors-nested-components',
55
template: `<p id="by-text-p">Nested Component</p>`,
66
standalone: true,
77
})
8-
export class DomSelectorsNestedComponent {}
8+
export class DomSelectorsNestedComponent {
9+
@Input() id: string = '';
10+
}
911

1012
@Component({
1113
selector: 'app-dom-selectors',
@@ -45,10 +47,10 @@ export class DomSelectorsNestedComponent {}
4547
</section>
4648
</div>
4749
48-
<div id="nested-components-1">
50+
<div id="nested-components-1" data-testid="nested-parent-1">
4951
<app-dom-selectors-nested-components id="alone-in-group"></app-dom-selectors-nested-components>
5052
</div>
51-
<div id="nested-components-2">
53+
<div id="nested-components-2" data-testid="nested-parent-2">
5254
<app-dom-selectors-nested-components id="first"></app-dom-selectors-nested-components>
5355
<app-dom-selectors-nested-components id="last"></app-dom-selectors-nested-components>
5456
</div>

projects/spectator/vitest/test/dom-selectors/dom-selectors.component.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,35 @@ describe('DomSelectorsComponent', () => {
7171
element = spectator.queryAll(DomSelectorsNestedComponent, { parentSelector: '#nested-components-1' });
7272
expect(element.length).toBe(1);
7373
});
74+
75+
it('should allow querying multiple element by parent testId', () => {
76+
let element = spectator.queryAll(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-2') });
77+
expect(element.length).toBe(2);
78+
element = spectator.queryAll(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-1') });
79+
expect(element.length).toBe(1);
80+
});
81+
82+
it('should allow querying first element by parent selector', () => {
83+
{
84+
let element = spectator.query(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-1') });
85+
expect(element).toHaveId('alone-in-group');
86+
}
87+
{
88+
let element = spectator.query(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-2') });
89+
expect(element).toHaveId('first');
90+
}
91+
});
92+
93+
it('should allow querying last element by parent selector', () => {
94+
{
95+
let element = spectator.queryLast(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-1') });
96+
expect(element).toHaveId('alone-in-group');
97+
}
98+
{
99+
let element = spectator.queryLast(DomSelectorsNestedComponent, { parentSelector: byTestId('nested-parent-2') });
100+
expect(element).toHaveId('last');
101+
}
102+
});
74103
});
75104

76105
describe('byTextContent', () => {

0 commit comments

Comments
 (0)