From 805fde6fa3c3cec584b97abc52d11f1e3ec23969 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Sun, 14 Jan 2024 16:28:40 +0100 Subject: [PATCH] #1188@minor: Adds support for Document.adoptedStyleSheets and ShadowRoot.adoptedStyleSheets to Window.getComputedStyle(). --- .../CSSStyleDeclarationElementStyle.ts | 21 ++++- .../happy-dom/src/nodes/document/IDocument.ts | 1 + .../happy-dom/src/nodes/element/Element.ts | 2 +- .../src/nodes/shadow-root/IShadowRoot.ts | 2 + .../src/nodes/shadow-root/ShadowRoot.ts | 4 +- .../test/AdoptedStyleSheetCustomElement.ts | 83 +++++++++++++++++++ packages/happy-dom/test/fetch/Fetch.test.ts | 36 ++++---- .../happy-dom/test/fetch/SyncFetch.test.ts | 42 +++++----- .../test/window/BrowserWindow.test.ts | 38 +++++++++ .../test/lit-element/LitElement.test.ts | 6 +- 10 files changed, 186 insertions(+), 49 deletions(-) create mode 100644 packages/happy-dom/test/AdoptedStyleSheetCustomElement.ts diff --git a/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts b/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts index d6af77314..0ab9754f8 100644 --- a/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts +++ b/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts @@ -137,17 +137,25 @@ export default class CSSStyleDeclarationElementStyle { } } + for (const styleSheet of this.element[PropertySymbol.ownerDocument].adoptedStyleSheets) { + this.parseCSSRules({ + elements: documentElements, + cssRules: styleSheet.cssRules + }); + } + styleAndElement = { element: null, cssTexts: [] }; } else if ( styleAndElement.element[PropertySymbol.nodeType] === NodeTypeEnum.documentFragmentNode && (styleAndElement.element).host ) { + const shadowRoot = styleAndElement.element; const styleSheets = >( - (styleAndElement.element).querySelectorAll('style,link[rel="stylesheet"]') + shadowRoot.querySelectorAll('style,link[rel="stylesheet"]') ); styleAndElement = { - element: (styleAndElement.element).host, + element: shadowRoot.host, cssTexts: [] }; @@ -161,6 +169,15 @@ export default class CSSStyleDeclarationElementStyle { }); } } + + for (const styleSheet of shadowRoot.adoptedStyleSheets) { + this.parseCSSRules({ + elements: shadowRootElements, + cssRules: styleSheet.cssRules, + hostElement: styleAndElement + }); + } + shadowRootElements = []; } else { styleAndElement = { diff --git a/packages/happy-dom/src/nodes/document/IDocument.ts b/packages/happy-dom/src/nodes/document/IDocument.ts index 0f05c29ee..fb6f3953e 100644 --- a/packages/happy-dom/src/nodes/document/IDocument.ts +++ b/packages/happy-dom/src/nodes/document/IDocument.ts @@ -50,6 +50,7 @@ export default interface IDocument extends IParentNode { readonly links: IHTMLCollection; readonly referrer: string; readonly currentScript: IHTMLScriptElement; + adoptedStyleSheets: CSSStyleSheet[]; cookie: string; title: string; diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts index 9af69c540..b77be0f80 100644 --- a/packages/happy-dom/src/nodes/element/Element.ts +++ b/packages/happy-dom/src/nodes/element/Element.ts @@ -788,7 +788,7 @@ export default class Element extends Node implements IElement { * @param init.mode Shadow root mode. * @returns Shadow root. */ - public attachShadow(init: { mode: 'open' | 'closed' }): IShadowRoot { + public attachShadow(init: { mode: string }): IShadowRoot { if (this[PropertySymbol.shadowRoot]) { throw new DOMException('Shadow root has already been attached.'); } diff --git a/packages/happy-dom/src/nodes/shadow-root/IShadowRoot.ts b/packages/happy-dom/src/nodes/shadow-root/IShadowRoot.ts index ccc42a528..68bb5be25 100644 --- a/packages/happy-dom/src/nodes/shadow-root/IShadowRoot.ts +++ b/packages/happy-dom/src/nodes/shadow-root/IShadowRoot.ts @@ -1,6 +1,7 @@ import IDocumentFragment from '../document-fragment/IDocumentFragment.js'; import IElement from '../element/IElement.js'; import Event from '../../event/Event.js'; +import { CSSStyleSheet } from '../../index.js'; /** * ShadowRoot. @@ -9,6 +10,7 @@ export default interface IShadowRoot extends IDocumentFragment { mode: string; innerHTML: string; host: IElement; + adoptedStyleSheets: CSSStyleSheet[]; readonly activeElement: IElement | null; // Events diff --git a/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts b/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts index 1b59cab38..b419f11b3 100644 --- a/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts +++ b/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts @@ -17,7 +17,7 @@ export default class ShadowRoot extends DocumentFragment implements IShadowRoot // Internal properties public [PropertySymbol.adoptedStyleSheets]: CSSStyleSheet[] = []; - public [PropertySymbol.mode]: 'open' | 'closed' = 'open'; + public [PropertySymbol.mode] = 'open'; public [PropertySymbol.host]: IElement | null = null; /** @@ -25,7 +25,7 @@ export default class ShadowRoot extends DocumentFragment implements IShadowRoot * * @returns Mode. */ - public get mode(): 'open' | 'closed' { + public get mode(): string { return this[PropertySymbol.mode]; } diff --git a/packages/happy-dom/test/AdoptedStyleSheetCustomElement.ts b/packages/happy-dom/test/AdoptedStyleSheetCustomElement.ts new file mode 100644 index 000000000..11ba5c3bd --- /dev/null +++ b/packages/happy-dom/test/AdoptedStyleSheetCustomElement.ts @@ -0,0 +1,83 @@ +import IShadowRoot from '../src/nodes/shadow-root/IShadowRoot.js'; +import HTMLElement from '../src/nodes/html-element/HTMLElement.js'; +import CSSStyleSheet from '../src/css/CSSStyleSheet.js'; + +/** + * CustomElement test class. + */ +export default class AdoptedStyleSheetCustomElement extends HTMLElement { + public static observedAttributesCallCount = 0; + public static shadowRootMode = 'open'; + public changedAttributes: Array<{ + name: string; + oldValue: string | null; + newValue: string | null; + }> = []; + private internalShadowRoot: IShadowRoot; + + /** + * Constructor. + */ + constructor() { + super(); + this.internalShadowRoot = this.attachShadow({ + mode: AdoptedStyleSheetCustomElement.shadowRootMode + }); + const styleSheet = new CSSStyleSheet(); + styleSheet.replaceSync(` + :host { + display: block; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; + } + span { + color: pink; + } + .propKey { + color: yellow; + } + `); + this.internalShadowRoot.adoptedStyleSheets = [styleSheet]; + + // Test to create a node while constructing this node. + this.ownerDocument.createElement('div'); + } + + /** + * Returns a list of observed attributes. + * + * @returns Observered attributes. + */ + public static get observedAttributes(): string[] { + this.observedAttributesCallCount++; + return ['key1', 'key2']; + } + + /** + * @override + */ + public attributeChangedCallback(name: string, oldValue: string, newValue: string): void { + this.changedAttributes.push({ name, oldValue, newValue }); + } + + /** + * @override + */ + public connectedCallback(): void { + this.internalShadowRoot.innerHTML = ` +
+ + key1 is "${this.getAttribute('key1')}" and key2 is "${this.getAttribute( + 'key2' + )}". + + ${this.childNodes + .map( + (child) => + '#' + child['nodeType'] + (child['tagName'] || '') + child.textContent + ) + .join(', ')} + +
+ `; + } +} diff --git a/packages/happy-dom/test/fetch/Fetch.test.ts b/packages/happy-dom/test/fetch/Fetch.test.ts index 4c94ccab7..25d21e12a 100644 --- a/packages/happy-dom/test/fetch/Fetch.test.ts +++ b/packages/happy-dom/test/fetch/Fetch.test.ts @@ -3585,7 +3585,7 @@ describe('Fetch', () => { expect(requestCount).toBe(1); }); - it('Revalidates cache with a "If-Modified-Since" request for a GET response with "Cache-Control" set to "max-age=0.020".', async () => { + it('Revalidates cache with a "If-Modified-Since" request for a GET response with "Cache-Control" set to "max-age=0.05".', async () => { const window = new Window({ url: 'https://localhost:8080/' }); const url = 'https://localhost:8080/some/path'; const responseText = 'some text'; @@ -3612,7 +3612,7 @@ describe('Fetch', () => { 'last-modified', 'Mon, 11 Dec 2023 02:00:00 GMT', 'cache-control', - 'max-age=0.020' + 'max-age=0.05' ]; callback(response); @@ -3632,7 +3632,7 @@ describe('Fetch', () => { 'content-length', String(responseText.length), 'cache-control', - 'max-age=0.020', + 'max-age=0.05', 'last-modified', 'Mon, 11 Dec 2023 01:00:00 GMT' ]; @@ -3677,7 +3677,7 @@ describe('Fetch', () => { expect(headers1).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText.length), - 'cache-control': `max-age=0.020`, + 'cache-control': `max-age=0.05`, 'last-modified': 'Mon, 11 Dec 2023 01:00:00 GMT' }); @@ -3690,7 +3690,7 @@ describe('Fetch', () => { expect(headers2).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText.length), - 'Cache-Control': 'max-age=0.020', + 'Cache-Control': 'max-age=0.05', 'Last-Modified': 'Mon, 11 Dec 2023 02:00:00 GMT' }); @@ -3737,7 +3737,7 @@ describe('Fetch', () => { ]); }); - it('Updates cache after a failed revalidation with a "If-Modified-Since" request for a GET response with "Cache-Control" set to "max-age=0.020".', async () => { + it('Updates cache after a failed revalidation with a "If-Modified-Since" request for a GET response with "Cache-Control" set to "max-age=0.05".', async () => { const window = new Window({ url: 'https://localhost:8080/' }); const url = '/some/path'; const responseText1 = 'some text'; @@ -3771,7 +3771,7 @@ describe('Fetch', () => { 'content-length', String(responseText2.length), 'cache-control', - 'max-age=0.020', + 'max-age=0.05', 'last-modified', 'Mon, 11 Dec 2023 02:00:00 GMT' ]; @@ -3793,7 +3793,7 @@ describe('Fetch', () => { 'content-length', String(responseText1.length), 'cache-control', - 'max-age=0.004', + 'max-age=0.05', 'last-modified', 'Mon, 11 Dec 2023 01:00:00 GMT' ]; @@ -3814,7 +3814,7 @@ describe('Fetch', () => { }); const text1 = await response1.text(); - await new Promise((resolve) => setTimeout(resolve, 50)); + await new Promise((resolve) => setTimeout(resolve, 100)); const response2 = await window.fetch(url); const text2 = await response2.text(); @@ -3846,7 +3846,7 @@ describe('Fetch', () => { expect(headers1).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText1.length), - 'cache-control': `max-age=0.004`, + 'cache-control': `max-age=0.05`, 'last-modified': 'Mon, 11 Dec 2023 01:00:00 GMT' }); @@ -3859,7 +3859,7 @@ describe('Fetch', () => { expect(headers2).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText2.length), - 'cache-control': 'max-age=0.020', + 'cache-control': 'max-age=0.05', 'last-modified': 'Mon, 11 Dec 2023 02:00:00 GMT' }); @@ -3963,7 +3963,7 @@ describe('Fetch', () => { 'content-length', String(responseText.length), 'cache-control', - 'max-age=0.020', + 'max-age=0.05', 'last-modified', 'Mon, 11 Dec 2023 01:00:00 GMT', 'etag', @@ -4013,7 +4013,7 @@ describe('Fetch', () => { expect(headers1).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText.length), - 'cache-control': `max-age=0.020`, + 'cache-control': `max-age=0.05`, 'last-modified': 'Mon, 11 Dec 2023 01:00:00 GMT', etag: etag1 }); @@ -4027,7 +4027,7 @@ describe('Fetch', () => { expect(headers2).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText.length), - 'cache-control': `max-age=0.020`, + 'cache-control': `max-age=0.05`, 'Last-Modified': 'Mon, 11 Dec 2023 02:00:00 GMT', ETag: etag2 }); @@ -4111,7 +4111,7 @@ describe('Fetch', () => { 'content-length', String(responseText2.length), 'cache-control', - 'max-age=0.020', + 'max-age=0.05', 'last-modified', 'Mon, 11 Dec 2023 02:00:00 GMT', 'etag', @@ -4135,7 +4135,7 @@ describe('Fetch', () => { 'content-length', String(responseText1.length), 'cache-control', - 'max-age=0.020', + 'max-age=0.05', 'last-modified', 'Mon, 11 Dec 2023 01:00:00 GMT', 'etag', @@ -4182,7 +4182,7 @@ describe('Fetch', () => { expect(headers1).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText1.length), - 'cache-control': `max-age=0.020`, + 'cache-control': `max-age=0.05`, 'last-modified': 'Mon, 11 Dec 2023 01:00:00 GMT', etag: etag1 }); @@ -4196,7 +4196,7 @@ describe('Fetch', () => { expect(headers2).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText2.length), - 'cache-control': `max-age=0.020`, + 'cache-control': `max-age=0.05`, 'last-modified': 'Mon, 11 Dec 2023 02:00:00 GMT', etag: etag2 }); diff --git a/packages/happy-dom/test/fetch/SyncFetch.test.ts b/packages/happy-dom/test/fetch/SyncFetch.test.ts index 5b7346c2b..e1df2ddfc 100644 --- a/packages/happy-dom/test/fetch/SyncFetch.test.ts +++ b/packages/happy-dom/test/fetch/SyncFetch.test.ts @@ -2195,7 +2195,7 @@ describe('SyncFetch', () => { expect(requestCount).toBe(1); }); - it('Revalidates cache with a "If-Modified-Since" request for a GET response with "Cache-Control" set to "max-age=0.020".', async () => { + it('Revalidates cache with a "If-Modified-Since" request for a GET response with "Cache-Control" set to "max-age=0.05".', async () => { browserFrame.url = 'https://localhost:8080/'; const url = 'https://localhost:8080/some/path'; @@ -2216,7 +2216,7 @@ describe('SyncFetch', () => { 'last-modified', 'Mon, 11 Dec 2023 02:00:00 GMT', 'cache-control', - 'max-age=0.020' + 'max-age=0.05' ], data: '' } @@ -2233,7 +2233,7 @@ describe('SyncFetch', () => { 'content-length', String(responseText.length), 'cache-control', - 'max-age=0.020', + 'max-age=0.05', 'last-modified', 'Mon, 11 Dec 2023 01:00:00 GMT' ], @@ -2255,7 +2255,7 @@ describe('SyncFetch', () => { }).send(); const text1 = response1.body.toString(); - await new Promise((resolve) => setTimeout(resolve, 50)); + await new Promise((resolve) => setTimeout(resolve, 100)); const response2 = new SyncFetch({ browserFrame, window, url }).send(); const text2 = response2.body.toString(); @@ -2279,7 +2279,7 @@ describe('SyncFetch', () => { expect(headers1).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText.length), - 'cache-control': `max-age=0.020`, + 'cache-control': `max-age=0.05`, 'last-modified': 'Mon, 11 Dec 2023 01:00:00 GMT' }); @@ -2292,7 +2292,7 @@ describe('SyncFetch', () => { expect(headers2).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText.length), - 'Cache-Control': 'max-age=0.020', + 'Cache-Control': 'max-age=0.05', 'Last-Modified': 'Mon, 11 Dec 2023 02:00:00 GMT' }); @@ -2329,7 +2329,7 @@ describe('SyncFetch', () => { ]); }); - it('Updates cache after a failed revalidation with a "If-Modified-Since" request for a GET response with "Cache-Control" set to "max-age=0.020".', async () => { + it('Updates cache after a failed revalidation with a "If-Modified-Since" request for a GET response with "Cache-Control" set to "max-age=0.05".', async () => { browserFrame.url = 'https://localhost:8080/'; const url = 'https://localhost:8080/some/path'; @@ -2353,7 +2353,7 @@ describe('SyncFetch', () => { 'content-length', String(responseText2.length), 'cache-control', - 'max-age=0.020', + 'max-age=0.05', 'last-modified', 'Mon, 11 Dec 2023 02:00:00 GMT' ], @@ -2372,7 +2372,7 @@ describe('SyncFetch', () => { 'content-length', String(responseText1.length), 'cache-control', - 'max-age=0.020', + 'max-age=0.05', 'last-modified', 'Mon, 11 Dec 2023 01:00:00 GMT' ], @@ -2394,7 +2394,7 @@ describe('SyncFetch', () => { }).send(); const text1 = response1.body.toString(); - await new Promise((resolve) => setTimeout(resolve, 50)); + await new Promise((resolve) => setTimeout(resolve, 100)); const response2 = new SyncFetch({ browserFrame, window, url }).send(); const text2 = response2.body.toString(); @@ -2426,7 +2426,7 @@ describe('SyncFetch', () => { expect(headers1).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText1.length), - 'cache-control': `max-age=0.020`, + 'cache-control': `max-age=0.05`, 'last-modified': 'Mon, 11 Dec 2023 01:00:00 GMT' }); @@ -2439,7 +2439,7 @@ describe('SyncFetch', () => { expect(headers2).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText2.length), - 'cache-control': 'max-age=0.020', + 'cache-control': 'max-age=0.05', 'last-modified': 'Mon, 11 Dec 2023 02:00:00 GMT' }); @@ -2519,7 +2519,7 @@ describe('SyncFetch', () => { 'content-length', String(responseText.length), 'cache-control', - 'max-age=0.020', + 'max-age=0.05', 'last-modified', 'Mon, 11 Dec 2023 01:00:00 GMT', 'etag', @@ -2544,7 +2544,7 @@ describe('SyncFetch', () => { }).send(); const text1 = response1.body.toString(); - await new Promise((resolve) => setTimeout(resolve, 50)); + await new Promise((resolve) => setTimeout(resolve, 100)); const response2 = new SyncFetch({ browserFrame, @@ -2575,7 +2575,7 @@ describe('SyncFetch', () => { expect(headers1).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText.length), - 'cache-control': `max-age=0.020`, + 'cache-control': `max-age=0.05`, 'last-modified': 'Mon, 11 Dec 2023 01:00:00 GMT', etag: etag1 }); @@ -2589,7 +2589,7 @@ describe('SyncFetch', () => { expect(headers2).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText.length), - 'cache-control': `max-age=0.020`, + 'cache-control': `max-age=0.05`, 'Last-Modified': 'Mon, 11 Dec 2023 02:00:00 GMT', ETag: etag2 }); @@ -2653,7 +2653,7 @@ describe('SyncFetch', () => { 'content-length', String(responseText2.length), 'cache-control', - 'max-age=0.020', + 'max-age=0.05', 'last-modified', 'Mon, 11 Dec 2023 02:00:00 GMT', 'etag', @@ -2674,7 +2674,7 @@ describe('SyncFetch', () => { 'content-length', String(responseText1.length), 'cache-control', - 'max-age=0.020', + 'max-age=0.05', 'last-modified', 'Mon, 11 Dec 2023 01:00:00 GMT', 'etag', @@ -2698,7 +2698,7 @@ describe('SyncFetch', () => { }).send(); const text1 = response1.body.toString(); - await new Promise((resolve) => setTimeout(resolve, 50)); + await new Promise((resolve) => setTimeout(resolve, 100)); const response2 = new SyncFetch({ browserFrame, window, url }).send(); const text2 = response2.body.toString(); @@ -2722,7 +2722,7 @@ describe('SyncFetch', () => { expect(headers1).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText1.length), - 'cache-control': `max-age=0.020`, + 'cache-control': `max-age=0.05`, 'last-modified': 'Mon, 11 Dec 2023 01:00:00 GMT', etag: etag1 }); @@ -2736,7 +2736,7 @@ describe('SyncFetch', () => { expect(headers2).toEqual({ 'content-type': 'text/html', 'content-length': String(responseText2.length), - 'cache-control': `max-age=0.020`, + 'cache-control': `max-age=0.05`, 'last-modified': 'Mon, 11 Dec 2023 02:00:00 GMT', etag: etag2 }); diff --git a/packages/happy-dom/test/window/BrowserWindow.test.ts b/packages/happy-dom/test/window/BrowserWindow.test.ts index cd77b1a3d..734d3a4a2 100644 --- a/packages/happy-dom/test/window/BrowserWindow.test.ts +++ b/packages/happy-dom/test/window/BrowserWindow.test.ts @@ -33,6 +33,8 @@ import IBrowser from '../../src/browser/types/IBrowser.js'; import IBrowserFrame from '../../src/browser/types/IBrowserFrame.js'; import IBrowserPage from '../../src/browser/types/IBrowserPage.js'; import BrowserWindow from '../../src/window/BrowserWindow.js'; +import AdoptedStyleSheetCustomElement from '../AdoptedStyleSheetCustomElement.js'; +import CSSStyleSheet from '../../src/css/CSSStyleSheet.js'; import '../types.d.js'; const GET_NAVIGATOR_PLATFORM = (): string => { @@ -59,6 +61,10 @@ describe('BrowserWindow', () => { window = browserFrame.window; document = window.document; window.customElements.define('custom-element', CustomElement); + window.customElements.define( + 'adopted-style-sheet-custom-element', + AdoptedStyleSheetCustomElement + ); }); afterEach(() => { @@ -447,6 +453,38 @@ describe('BrowserWindow', () => { ); }); + it('Returns a CSSStyleDeclaration object with computed styles from adopted style sheets for elements in a HTMLShadowRoot.', () => { + const element = document.createElement('span'); + const customElement = ( + document.createElement('adopted-style-sheet-custom-element') + ); + const elementComputedStyle = window.getComputedStyle(element); + + const styleSheet = new CSSStyleSheet(); + styleSheet.replaceSync(` + span { + color: green; + } + `); + document.adoptedStyleSheets = [styleSheet]; + + document.body.appendChild(element); + document.body.appendChild(customElement); + + const customElementComputedStyle = window.getComputedStyle( + customElement.shadowRoot?.querySelector('span') + ); + + // Default value on HTML is "16px Times New Roman" + expect(elementComputedStyle.font).toBe('16px "Times New Roman"'); + expect(elementComputedStyle.color).toBe('green'); + + expect(customElementComputedStyle.color).toBe('yellow'); + expect(customElementComputedStyle.font).toBe( + '14px "Lucida Grande", Helvetica, Arial, sans-serif' + ); + }); + it('Returns values defined by a CSS variables.', () => { const parent = document.createElement('div'); const element = document.createElement('span'); diff --git a/packages/jest-environment/test/lit-element/LitElement.test.ts b/packages/jest-environment/test/lit-element/LitElement.test.ts index b48ffe66c..b44abd061 100644 --- a/packages/jest-environment/test/lit-element/LitElement.test.ts +++ b/packages/jest-environment/test/lit-element/LitElement.test.ts @@ -21,6 +21,7 @@ describe('LitElementComponent', () => { ); expect(shadowRoot.querySelector('span').innerText).toBe(PROP1); + expect(window.getComputedStyle(shadowRoot.querySelector('span')).color).toBe('green'); expect( shadowRoot.innerHTML .replace(/[\s]/gm, '') @@ -29,11 +30,6 @@ describe('LitElementComponent', () => { ` Some text ${PROP1}! - `.replace(/[\s]/gm, '') ); });