Skip to content

Commit 304ffbf

Browse files
committed
test(platform-browser): verify that Angular supports bootstrapping under shadow roots
This tests bootstrapping Angular underneath a shadow root and that styles are applied and removed at the correct locations.
1 parent 54aee9a commit 304ffbf

File tree

1 file changed

+152
-1
lines changed

1 file changed

+152
-1
lines changed

packages/platform-browser/test/dom/shadow_dom_spec.ts

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {Component, NgModule, ViewEncapsulation} from '@angular/core';
1010
import {TestBed} from '@angular/core/testing';
11-
import {BrowserModule} from '../../index';
11+
import {BrowserModule, createApplication} from '../../index';
1212
import {expect} from '@angular/private/testing/matchers';
1313
import {isNode} from '@angular/private/testing';
1414

@@ -23,6 +23,10 @@ describe('ShadowDOM Support', () => {
2323
TestBed.configureTestingModule({imports: [TestModule]});
2424
});
2525

26+
beforeEach(() => {
27+
for (const node of Array.from(document.body.childNodes)) node.remove();
28+
});
29+
2630
it('should attach and use a shadowRoot when ViewEncapsulation.ShadowDom is set', () => {
2731
const compEl = TestBed.createComponent(ShadowComponent).nativeElement;
2832
expect(compEl.shadowRoot!.textContent).toEqual('Hello World');
@@ -81,6 +85,153 @@ describe('ShadowDOM Support', () => {
8185
expect(articleContent.assignedSlot).toBe(articleSlot);
8286
expect(articleSubcontent.assignedSlot).toBe(articleSlot);
8387
});
88+
89+
it('should support bootstrapping under a shadow root', async () => {
90+
@Component({
91+
selector: 'app-root',
92+
template: '<div>Hello, World!</div>',
93+
styles: `
94+
div {
95+
color: red;
96+
}
97+
`,
98+
})
99+
class Root {}
100+
101+
const container = document.createElement('div');
102+
container.attachShadow({mode: 'open'});
103+
const root = document.createElement('app-root');
104+
container.shadowRoot!.append(root);
105+
document.body.append(container);
106+
107+
const appRef = await createApplication();
108+
appRef.bootstrap(Root, root);
109+
110+
expect(getComputedStyle(root.querySelector('div')!).color).toBe('rgb(255, 0, 0)');
111+
112+
expect(document.head.innerHTML).not.toContain('<style>');
113+
114+
appRef.destroy();
115+
116+
expect(container.shadowRoot!.innerHTML).not.toContain('<style>');
117+
});
118+
119+
it('should support bootstrapping multiple root components under different shadow roots', async () => {
120+
const appRef = await createApplication();
121+
122+
{
123+
@Component({
124+
selector: 'app-root',
125+
template: '<div>Hello, World!</div>',
126+
styles: `
127+
div {
128+
color: red;
129+
}
130+
`,
131+
})
132+
class Root {}
133+
134+
const container = document.createElement('div');
135+
container.attachShadow({mode: 'open'});
136+
const root = document.createElement('app-root');
137+
container.shadowRoot!.append(root);
138+
document.body.append(container);
139+
140+
appRef.bootstrap(Root, root);
141+
expect(getComputedStyle(root.querySelector('div')!).color).toBe('rgb(255, 0, 0)');
142+
}
143+
144+
{
145+
@Component({
146+
selector: 'app-root-2',
147+
template: '<div>Hello, World!</div>',
148+
styles: `
149+
div {
150+
color: lime;
151+
}
152+
`,
153+
})
154+
class Root {}
155+
156+
const container = document.createElement('div');
157+
container.attachShadow({mode: 'open'});
158+
const root = document.createElement('app-root-2');
159+
container.shadowRoot!.append(root);
160+
document.body.append(container);
161+
162+
appRef.bootstrap(Root, root);
163+
expect(getComputedStyle(root.querySelector('div')!).color).toBe('rgb(0, 255, 0)');
164+
}
165+
166+
expect(document.head.innerHTML).not.toContain('<style>');
167+
168+
appRef.destroy();
169+
170+
const containers = Array.from(document.querySelectorAll('div'));
171+
const [shadowRoot1, shadowRoot2] = containers.map((container) => container.shadowRoot!);
172+
expect(shadowRoot1.innerHTML).not.toContain('<style>');
173+
expect(shadowRoot2.innerHTML).not.toContain('<style>');
174+
});
175+
176+
it('should not leak styles into previously used shadow roots', async () => {
177+
const container1 = document.createElement('div');
178+
container1.attachShadow({mode: 'open'});
179+
document.body.append(container1);
180+
181+
const container2 = document.createElement('div');
182+
container2.attachShadow({mode: 'open'});
183+
document.body.append(container2);
184+
185+
const appRef = await createApplication();
186+
187+
{
188+
@Component({
189+
selector: 'app-root',
190+
template: '<div>Hello, World!</div>',
191+
styles: `
192+
div {
193+
color: red;
194+
}
195+
`,
196+
})
197+
class Root {}
198+
199+
const rootEl = document.createElement('app-root');
200+
container1.shadowRoot!.append(rootEl);
201+
202+
const root = appRef.bootstrap(Root, rootEl);
203+
expect(getComputedStyle(rootEl.querySelector('div')!).color).toBe('rgb(255, 0, 0)');
204+
root.destroy();
205+
206+
expect(container1.shadowRoot!.innerHTML).not.toContain('<style>');
207+
}
208+
209+
{
210+
@Component({
211+
selector: 'app-root-2',
212+
template: '<div>Hello, World!</div>',
213+
styles: `
214+
div {
215+
color: lime;
216+
}
217+
`,
218+
})
219+
class Root2 {}
220+
221+
const root2El = document.createElement('app-root-2');
222+
container2.shadowRoot!.append(root2El);
223+
224+
const root = appRef.bootstrap(Root2, root2El);
225+
expect(getComputedStyle(root2El.querySelector('div')!).color).toBe('rgb(0, 255, 0)');
226+
227+
// Should not leak into `container1`.
228+
expect(container1.shadowRoot!.innerHTML).not.toContain('<style>');
229+
230+
root.destroy();
231+
}
232+
233+
appRef.destroy();
234+
});
84235
});
85236

86237
@Component({

0 commit comments

Comments
 (0)