Skip to content
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(platform-browser): Fixes Shadowdom so it meets spec
BREAKING CHANGE: Fixes Shadowdom to not include external styles that are not declared by the component. Prevents style bleed, so components are scoped correctly. Now adheres to the spec for Shadowdom. Adds test cases.
  • Loading branch information
ryan-bendel committed Jun 29, 2025
commit 1dec08eda4226c78e3343fafa5eb2b1e12b8e0be
109 changes: 96 additions & 13 deletions packages/platform-browser/test/dom/dom_renderer_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('DefaultDomRendererV2', () => {
declarations: [
TestCmp,
SomeApp,
ShadowComponentParentApp,
SomeAppForCleanUp,
CmpEncapsulationEmulated,
CmpEncapsulationNone,
Expand Down Expand Up @@ -109,22 +110,65 @@ describe('DefaultDomRendererV2', () => {
});
});

it('should allow to style components with emulated encapsulation and no encapsulation inside of components with shadow DOM', () => {
it('should style non-descendant components correctly with different types of encapsulation', () => {
const fixture = TestBed.createComponent(SomeApp);
fixture.detectChanges();

const cmp = fixture.debugElement.query(By.css('cmp-shadow')).nativeElement;
const shadow = cmp.shadowRoot.querySelector('.shadow');

const shadowRoot = cmp.shadowRoot;
const shadow = shadowRoot.querySelector('.shadow');
expect(window.getComputedStyle(shadow).color).toEqual('rgb(255, 0, 0)');

const emulated = cmp.shadowRoot.querySelector('.emulated');
const emulated = fixture.debugElement.query(By.css('.emulated')).nativeElement;
expect(window.getComputedStyle(emulated).color).toEqual('rgb(0, 0, 255)');

const none = cmp.shadowRoot.querySelector('.none');
const none = fixture.debugElement.query(By.css('.none')).nativeElement;
expect(window.getComputedStyle(none).color).toEqual('rgb(0, 255, 0)');
});

it('should encapsulate shadow DOM components, with child components inheriting from shadow styles not global styles', () => {
const fixture = TestBed.createComponent(ShadowComponentParentApp);
fixture.detectChanges();

const cmp = fixture.debugElement.query(By.css('cmp-shadow')).nativeElement;
const shadowRoot = cmp.shadowRoot;
const shadow = shadowRoot.querySelector('.shadow');
expect(window.getComputedStyle(shadow).color).toEqual('rgb(255, 0, 0)');

const emulated = fixture.debugElement.query(By.css('.emulated')).nativeElement;
expect(window.getComputedStyle(emulated).color).toEqual('rgb(255, 0, 0)');

const none = fixture.debugElement.query(By.css('.none')).nativeElement;
expect(window.getComputedStyle(none).color).toEqual('rgb(255, 0, 0)');
});

it('child components of shadow components should inherit browser defaults rather than their component styles', () => {
const fixture = TestBed.createComponent(ShadowComponentParentApp);
fixture.detectChanges();

const cmp = fixture.debugElement.query(By.css('cmp-shadow')).nativeElement;
const shadowRoot = cmp.shadowRoot;
const shadow = shadowRoot.querySelector('.shadow');
expect(window.getComputedStyle(shadow).backgroundColor).toEqual('rgba(0, 0, 0, 0)');

const emulated = fixture.debugElement.query(By.css('.emulated')).nativeElement;
expect(window.getComputedStyle(emulated).backgroundColor).toEqual('rgba(0, 0, 0, 0)');

const none = fixture.debugElement.query(By.css('.none')).nativeElement;
expect(window.getComputedStyle(none).backgroundColor).toEqual('rgba(0, 0, 0, 0)');
});

it('shadow components should not be polluted by child components styles', () => {
const fixture = TestBed.createComponent(ShadowComponentParentApp);
fixture.detectChanges();

const cmp = fixture.debugElement.query(By.css('cmp-shadow')).nativeElement;
const shadowRoot = cmp.shadowRoot;
const shadow = shadowRoot.querySelector('.shadow');
expect(window.getComputedStyle(shadow).backgroundColor).not.toEqual('rgb(0, 0, 255)');
expect(window.getComputedStyle(shadow).backgroundColor).not.toEqual('rgb(0, 255, 0)');
});

it('should be able to append children to a <template> element', () => {
const template = document.createElement('template');
const child = document.createElement('div');
Expand Down Expand Up @@ -378,35 +422,62 @@ async function styleCount(

@Component({
selector: 'cmp-emulated',
template: `<div class="emulated"></div>`,
styles: [`.emulated { color: blue; }`],
template: `
<div class="emulated"></div>`,
styles: [
`.emulated {
background-color: blue;
color: blue;
}`,
],
encapsulation: ViewEncapsulation.Emulated,
standalone: false,
})
class CmpEncapsulationEmulated {}

@Component({
selector: 'cmp-none',
template: `<div class="none"></div>`,
styles: [`.none { color: lime; }`],
template: `
<div class="none"></div>`,
styles: [
`.none {
background-color: lime;
color: lime;
}`,
],
encapsulation: ViewEncapsulation.None,
standalone: false,
})
class CmpEncapsulationNone {}

@Component({
selector: 'cmp-none',
template: `<div class="none"></div>`,
styles: [`.none { color: lime; }\n/*# sourceMappingURL=cmp-none.css.map */`],
template: `
<div class="none"></div>`,
styles: [
`.none {
background-color: lime;
color: lime;
}

/*# sourceMappingURL=cmp-none.css.map */`,
],
encapsulation: ViewEncapsulation.None,
standalone: false,
})
class CmpEncapsulationNoneWithSourceMap {}

@Component({
selector: 'cmp-shadow',
template: `<div class="shadow"></div><cmp-emulated></cmp-emulated><cmp-none></cmp-none>`,
styles: [`.shadow { color: red; }`],
template: `
<div class="shadow">
<ng-content></ng-content>
</div>`,
styles: [
`.shadow {
color: red;
}`,
],
encapsulation: ViewEncapsulation.ShadowDom,
standalone: false,
})
Expand All @@ -423,6 +494,18 @@ class CmpEncapsulationShadow {}
})
export class SomeApp {}

@Component({
selector: 'shadow-parent-app',
template: `
<cmp-shadow>
<cmp-emulated></cmp-emulated>
<cmp-none></cmp-none>
</cmp-shadow>
`,
standalone: false,
})
export class ShadowComponentParentApp {}

@Component({
selector: 'test-cmp',
template: '',
Expand Down