Skip to content

Commit b755beb

Browse files
Merge pull request #2090 from mainmatter/root-element-shadow-dom
Support ShadowRoot, add tests for rootElement, fixes #2089
2 parents 30feace + e9ef45a commit b755beb

File tree

5 files changed

+168
-5
lines changed

5 files changed

+168
-5
lines changed

API.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ Once installed the DOM element assertions are available at `assert.dom(...).*`:
123123
**Parameters**
124124

125125
* `target` **([string][114] | [HTMLElement][115])** A CSS selector that can be used to find elements using [`querySelector()`][116], or an \[HTMLElement]\[] (Not all assertions support both target types.) (optional, default `rootElement` or `document`)
126-
* `rootElement` **[HTMLElement][115]?** The root element of the DOM in which to search for the `target` (optional, default `document`)
126+
* `rootElement` **([HTMLElement][115] | [Document][152] | [ShadowRoot][153] | [null][154])?** The root element of the DOM in which to search for the `target` (optional, defaults `document` when `null` or not provided)
127127

128128
**Examples**
129129

@@ -1194,3 +1194,9 @@ assert.dom('section#block').doesNotHaveTagName('div');
11941194
[150]: #hasValue
11951195

11961196
[151]: https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName
1197+
1198+
[152]: https://developer.mozilla.org/en-US/docs/Web/API/Document
1199+
1200+
[153]: https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot
1201+
1202+
[154]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { describe, beforeEach, test, expect } from 'vitest';
2+
3+
import TestAssertions from '../helpers/test-assertions';
4+
5+
describe('assert.dom(..., rootElement)', () => {
6+
let assert: TestAssertions;
7+
8+
beforeEach(() => {
9+
assert = new TestAssertions();
10+
});
11+
12+
test('passing an Element as rootElement', () => {
13+
document.body.innerHTML = `
14+
<span class="target">dedcoy<span>
15+
16+
<h1 class="parent">
17+
<span class="target">real target<span>
18+
</h1>
19+
`;
20+
21+
const rootElement = document.querySelector('.parent');
22+
23+
assert.dom('.target', rootElement).exists({ count: 1 });
24+
assert.dom('.target', rootElement).hasText('real target');
25+
26+
expect(assert.results).toEqual([
27+
{
28+
result: true,
29+
actual: 'Element .target exists once',
30+
expected: 'Element .target exists once',
31+
message: 'Element .target exists once',
32+
},
33+
{
34+
result: true,
35+
actual: 'real target',
36+
expected: 'real target',
37+
message: 'Element .target has text "real target"',
38+
},
39+
]);
40+
});
41+
42+
test('not passing anything as rootElement', () => {
43+
document.body.innerHTML = `
44+
<span class="target">decoy<span>
45+
46+
<h1 class="parent">
47+
<span class="target">real target<span>
48+
</h1>
49+
`;
50+
51+
assert.dom('.target').exists({ count: 2 });
52+
53+
expect(assert.results).toEqual([
54+
{
55+
result: true,
56+
actual: 'Element .target exists twice',
57+
expected: 'Element .target exists twice',
58+
message: 'Element .target exists twice',
59+
},
60+
]);
61+
});
62+
63+
test('passing document as rootElement', () => {
64+
document.body.innerHTML = `
65+
<span class="target">decoy<span>
66+
67+
<h1 class="parent">
68+
<span class="target">real target<span>
69+
</h1>
70+
`;
71+
72+
assert.dom('.target', document).exists({ count: 2 });
73+
74+
expect(assert.results).toEqual([
75+
{
76+
result: true,
77+
actual: 'Element .target exists twice',
78+
expected: 'Element .target exists twice',
79+
message: 'Element .target exists twice',
80+
},
81+
]);
82+
});
83+
84+
test('passing null as rootElement', () => {
85+
document.body.innerHTML = `
86+
<span class="target">decoy<span>
87+
88+
<h1 class="parent">
89+
<span class="target">real target<span>
90+
</h1>
91+
`;
92+
93+
assert.dom('.target', null).exists({ count: 2 });
94+
95+
expect(assert.results).toEqual([
96+
{
97+
result: true,
98+
actual: 'Element .target exists twice',
99+
expected: 'Element .target exists twice',
100+
message: 'Element .target exists twice',
101+
},
102+
]);
103+
});
104+
105+
test('passing shadow root as rootElement', () => {
106+
document.body.innerHTML = `
107+
<div id="container">
108+
<span class="target">decoy<span>
109+
</div>
110+
`;
111+
112+
const container = document.getElementById('container');
113+
const shadowRoot = container.attachShadow({ mode: 'closed' });
114+
115+
shadowRoot.innerHTML = '<span class="target">real target<span>';
116+
117+
assert.dom('.target').exists({ count: 1 }, 'Only decoy element is found outside shadow root');
118+
assert.dom('.target').hasText('real target', 'decoy element text');
119+
120+
assert.dom('.target', shadowRoot).exists({ count: 1 }, 'Only target found in shadow root');
121+
assert.dom('.target', shadowRoot).hasText('real target', 'Target element text');
122+
123+
console.log(assert.results);
124+
125+
expect(assert.results).toEqual([
126+
{
127+
result: true,
128+
actual: 'Element .target exists once',
129+
expected: 'Element .target exists once',
130+
message: 'Only decoy element is found outside shadow root',
131+
},
132+
{
133+
result: false,
134+
actual: 'decoy',
135+
expected: 'real target',
136+
message: 'decoy element text',
137+
},
138+
{
139+
result: true,
140+
actual: 'Element .target exists once',
141+
expected: 'Element .target exists once',
142+
message: 'Only target found in shadow root',
143+
},
144+
{
145+
result: true,
146+
actual: 'real target',
147+
expected: 'real target',
148+
message: 'Target element text',
149+
},
150+
]);
151+
});
152+
});

packages/qunit-dom/lib/assertions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type ActualCSSStyleDeclaration = Partial<Record<CSSStyleDeclarationProperty, unk
3131
export default class DOMAssertions {
3232
constructor(
3333
private target: string | Element | null,
34-
private rootElement: Element | Document,
34+
private rootElement: RootElement,
3535
private testContext: Assert
3636
) {}
3737

packages/qunit-dom/lib/helpers/test-assertions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import DOMAssertions, { type AssertionResult } from '../assertions.js';
33
export default class TestAssertions {
44
public results: AssertionResult[] = [];
55

6-
dom(target: string | Element | null, rootElement?: Element) {
6+
dom(target: string | Element | null, rootElement?: RootElement) {
77
return new DOMAssertions(target, rootElement || document, this as any);
88
}
99

packages/qunit-dom/lib/install.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@ import DOMAssertions from './assertions.js';
22
import { getRootElement } from './root-element.js';
33

44
declare global {
5+
type RootElement = Element | Document | ShadowRoot | null;
6+
57
interface Assert {
6-
dom(target?: string | Element | null, rootElement?: Element): DOMAssertions;
8+
dom(target?: string | Element | null, rootElement?: RootElement): DOMAssertions;
79
}
810
}
911

1012
export default function (assert: Assert) {
11-
assert.dom = function (target?: string | Element | null, rootElement?: Element): DOMAssertions {
13+
assert.dom = function (
14+
target?: string | Element | null,
15+
rootElement?: RootElement
16+
): DOMAssertions {
1217
if (!isValidRootElement(rootElement)) {
1318
throw new Error(`${rootElement} is not a valid root element`);
1419
}

0 commit comments

Comments
 (0)