From 8ec7322a80f058f1693e4f240c5de1cc00b05599 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Fri, 30 Sep 2022 18:02:40 +0200 Subject: [PATCH] #344@trivial: Continues on CSSStyleDeclaration. --- packages/happy-dom/src/css/CSSParser.ts | 6 +- .../AbstractCSSStyleDeclaration.ts | 5 +- .../utilities/CSSStyleDeclarationCSSParser.ts | 35 ++ .../utilities/CSSStyleDeclarationElement.ts | 204 +++++--- .../CSSStyleDeclarationElementDefaultCSS.ts | 130 +++++ ...tyleDeclarationElementDefaultProperties.ts | 484 ------------------ ...leDeclarationElementInheritedProperties.ts | 78 +-- .../CSSStyleDeclarationPropertyManager.ts | 31 +- .../CSSStyleDeclarationPropertySetParser.ts | 394 +++++++++++++- .../CSSStyleDeclarationValueParser.ts | 19 +- .../src/css/rules/CSSFontFaceRule.ts | 17 +- .../src/css/rules/CSSKeyframeRule.ts | 17 +- .../happy-dom/src/css/rules/CSSStyleRule.ts | 17 +- .../declaration/CSSStyleDeclaration.test.ts | 1 + packages/happy-dom/test/window/Window.test.ts | 36 +- 15 files changed, 831 insertions(+), 643 deletions(-) create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationCSSParser.ts create mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementDefaultCSS.ts delete mode 100644 packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementDefaultProperties.ts diff --git a/packages/happy-dom/src/css/CSSParser.ts b/packages/happy-dom/src/css/CSSParser.ts index caa0e5568..a2b8fecc7 100644 --- a/packages/happy-dom/src/css/CSSParser.ts +++ b/packages/happy-dom/src/css/CSSParser.ts @@ -4,7 +4,6 @@ import CSSStyleRule from './rules/CSSStyleRule'; import CSSKeyframeRule from './rules/CSSKeyframeRule'; import CSSKeyframesRule from './rules/CSSKeyframesRule'; import CSSMediaRule from './rules/CSSMediaRule'; -import CSSStyleDeclaration from './declaration/CSSStyleDeclaration'; const COMMENT_REGEXP = /\/\*[^*]*\*\//gm; @@ -86,10 +85,7 @@ export default class CSSParser { case CSSRule.FONT_FACE_RULE: case CSSRule.KEYFRAME_RULE: case CSSRule.STYLE_RULE: - const style = new CSSStyleDeclaration(); - style.cssText = cssText; - (style.parentRule) = parentRule; - ((parentRule).style) = style; + (parentRule)._cssText = cssText; break; } } diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts index ca586628d..010e5727f 100644 --- a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts +++ b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts @@ -10,7 +10,6 @@ import CSSStyleDeclarationPropertyManager from './utilities/CSSStyleDeclarationP * CSS Style Declaration. */ export default abstract class AbstractCSSStyleDeclaration { - // Other properties public readonly parentRule: CSSRule = null; protected _style: CSSStyleDeclarationPropertyManager = null; protected _ownerElement: IElement; @@ -73,7 +72,7 @@ export default abstract class AbstractCSSStyleDeclaration { } if (this._ownerElement) { - const style = new CSSStyleDeclarationPropertyManager(cssText); + const style = new CSSStyleDeclarationPropertyManager({ cssText }); if (!style.size()) { delete this._ownerElement['_attributes']['style']; } else { @@ -86,7 +85,7 @@ export default abstract class AbstractCSSStyleDeclaration { this._ownerElement['_attributes']['style'].value = style.toString(); } } else { - this._style = new CSSStyleDeclarationPropertyManager(cssText); + this._style = new CSSStyleDeclarationPropertyManager({ cssText }); } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationCSSParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationCSSParser.ts new file mode 100644 index 000000000..f62282808 --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationCSSParser.ts @@ -0,0 +1,35 @@ +/** + * CSS parser. + */ +export default class CSSStyleDeclarationCSSParser { + /** + * Class construtor. + * + * @param cssText CSS string. + * @param callback Callback. + */ + public static parse( + cssText: string, + callback: (name: string, value: string, important: boolean) => void + ): void { + const parts = cssText.split(';'); + + for (const part of parts) { + if (part) { + const [name, value]: string[] = part.trim().split(':'); + if (value) { + const trimmedName = name.trim(); + const trimmedValue = value.trim(); + if (trimmedName && trimmedValue) { + const important = trimmedValue.endsWith(' !important'); + const valueWithoutImportant = trimmedValue.replace(' !important', ''); + + if (valueWithoutImportant) { + callback(trimmedName, valueWithoutImportant, important); + } + } + } + } + } + } +} diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts index c08e1b5c8..0b27db83b 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElement.ts @@ -4,14 +4,16 @@ import IDocument from '../../../nodes/document/IDocument'; import IHTMLStyleElement from '../../../nodes/html-style-element/IHTMLStyleElement'; import INodeList from '../../../nodes/node/INodeList'; import CSSStyleDeclarationPropertyManager from './CSSStyleDeclarationPropertyManager'; -import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue'; import NodeTypeEnum from '../../../nodes/node/NodeTypeEnum'; import CSSRuleTypeEnum from '../../CSSRuleTypeEnum'; import CSSMediaRule from '../../rules/CSSMediaRule'; import CSSRule from '../../CSSRule'; import CSSStyleRule from '../../rules/CSSStyleRule'; -import CSSStyleDeclarationElementDefaultProperties from './CSSStyleDeclarationElementDefaultProperties'; +import CSSStyleDeclarationElementDefaultCSS from './CSSStyleDeclarationElementDefaultCSS'; import CSSStyleDeclarationElementInheritedProperties from './CSSStyleDeclarationElementInheritedProperties'; +import CSSStyleDeclarationCSSParser from './CSSStyleDeclarationCSSParser'; + +const CSS_VARIABLE_REGEXP = /var\( *(--[^) ]+)\)/g; /** * CSS Style Declaration utility @@ -32,7 +34,9 @@ export default class CSSStyleDeclarationElement { return this.getComputedElementStyle(element); } - return new CSSStyleDeclarationPropertyManager(element['_attributes']['style']?.value); + return new CSSStyleDeclarationPropertyManager({ + cssText: element['_attributes']['style']?.value + }); } /** @@ -44,7 +48,6 @@ export default class CSSStyleDeclarationElement { private static getComputedElementStyle(element: IElement): CSSStyleDeclarationPropertyManager { const documentElements: Array<{ element: IElement; cssText: string }> = []; const parentElements: Array<{ element: IElement; cssText: string }> = []; - const inheritedProperties: { [k: string]: ICSSStyleDeclarationPropertyValue } = {}; let styleAndElement = { element: element, cssText: '' }; let shadowRootElements: Array<{ element: IElement; cssText: string }> = []; @@ -52,7 +55,7 @@ export default class CSSStyleDeclarationElement { return new CSSStyleDeclarationPropertyManager(); } - // Walks through all parent elements and applies style to them. + // Walks through all parent elements and stores them in an array with element and matching CSS text. while (styleAndElement.element) { if (styleAndElement.element.nodeType === NodeTypeEnum.elementNode) { const rootNode = styleAndElement.element.getRootNode(); @@ -66,28 +69,39 @@ export default class CSSStyleDeclarationElement { if (styleAndElement.element === element.ownerDocument) { const styleSheets = >( - element.ownerDocument.querySelectorAll('style') + element.ownerDocument.querySelectorAll('style,link[rel="stylesheet"]') ); for (const styleSheet of styleSheets) { - this.applyCSSTextToElements(documentElements, styleSheet.sheet.cssRules); + const sheet = styleSheet.sheet; + if (sheet) { + this.parseCSSRules({ + elements: documentElements, + cssRules: sheet.cssRules + }); + } } styleAndElement = { element: null, cssText: '' }; } else if ((styleAndElement.element).host) { const styleSheets = >( - (styleAndElement.element).querySelectorAll('style') + (styleAndElement.element).querySelectorAll('style,link[rel="stylesheet"]') ); + styleAndElement = { element: (styleAndElement.element).host, cssText: '' }; + for (const styleSheet of styleSheets) { - this.applyCSSTextToElements( - shadowRootElements, - styleSheet.sheet.cssRules, - <{ element: IElement; cssText: string }>styleAndElement - ); + const sheet = styleSheet.sheet; + if (sheet) { + this.parseCSSRules({ + elements: shadowRootElements, + cssRules: sheet.cssRules, + hostElement: <{ element: IElement; cssText: string }>styleAndElement + }); + } } shadowRootElements = []; } else { @@ -95,91 +109,112 @@ export default class CSSStyleDeclarationElement { } } + // Concatenates all parent element CSS to one string. const targetElement = parentElements[parentElements.length - 1]; + let inheritedCSSText = CSSStyleDeclarationElementDefaultCSS.default; - // Walks through all parent elements and merges inherited properties. for (const parentElement of parentElements) { if (parentElement !== targetElement) { - const propertyManager = new CSSStyleDeclarationPropertyManager( - parentElement.cssText + (parentElement.element['_attributes']['style']?.value || '') - ); - const properties = Object.assign( - {}, - CSSStyleDeclarationElementDefaultProperties.default, - CSSStyleDeclarationElementDefaultProperties[parentElement.element.tagName], - propertyManager.properties - ); - for (const name of Object.keys(properties)) { - if (CSSStyleDeclarationElementInheritedProperties.includes(name)) { - if (!inheritedProperties[name]?.important || properties[name].important) { - inheritedProperties[name] = properties[name]; - } - } - } + inheritedCSSText += + (CSSStyleDeclarationElementDefaultCSS[parentElement.element.tagName] || '') + + parentElement.cssText + + (parentElement.element['_attributes']['style']?.value || ''); } } - // Merges together styles in the target element with inherited properties. - const targetPropertyManager = new CSSStyleDeclarationPropertyManager( - targetElement.cssText + (targetElement.element['_attributes']['style']?.value || '') - ); - - const targetProperties = Object.assign( - {}, - CSSStyleDeclarationElementDefaultProperties.default, - CSSStyleDeclarationElementDefaultProperties[targetElement.element.tagName], - inheritedProperties - ); - - for (const name of Object.keys(targetPropertyManager.properties)) { - if (!targetProperties[name]?.important || targetPropertyManager.properties[name].important) { - targetProperties[name] = targetPropertyManager.properties[name]; + const cssVariables: { [k: string]: string } = {}; + const properties = {}; + + // Parses the parent element CSS and stores CSS variables and inherited properties. + CSSStyleDeclarationCSSParser.parse(inheritedCSSText, (name, value, important) => { + if (name.startsWith('--')) { + const cssValue = this.getCSSValue(value, cssVariables); + if (cssValue) { + cssVariables[name] = cssValue; + } + return; } - } - targetPropertyManager.properties = targetProperties; + if (CSSStyleDeclarationElementInheritedProperties[name]) { + const cssValue = this.getCSSValue(value, cssVariables); + if (cssValue && (!properties[name]?.important || important)) { + properties[name] = { + value: cssValue, + important + }; + } + } + }); + + // Parses the target element CSS. + const targetCSSText = + (CSSStyleDeclarationElementDefaultCSS[targetElement.element.tagName] || '') + + targetElement.cssText + + (targetElement.element['_attributes']['style']?.value || ''); + + CSSStyleDeclarationCSSParser.parse(targetCSSText, (name, value, important) => { + if (name.startsWith('--')) { + const cssValue = this.getCSSValue(value, cssVariables); + if (cssValue && (!properties[name]?.important || important)) { + cssVariables[name] = cssValue; + properties[name] = { + value, + important + }; + } + } else { + const cssValue = this.getCSSValue(value, cssVariables); + if (cssValue && (!properties[name]?.important || important)) { + properties[name] = { + value: cssValue, + important + }; + } + } + }); + + const propertyManager = new CSSStyleDeclarationPropertyManager(); + + for (const name of Object.keys(properties)) { + propertyManager.set(name, properties[name].value, properties[name].important); + } - return targetPropertyManager; + return propertyManager; } /** * Applies CSS text to elements. * - * @param elements Elements. - * @param cssRules CSS rules. - * @param [hostElement] Host element. - * @param [hostElement.element] Element. - * @param [hostElement.cssText] CSS text. + * @param options Options. + * @param options.elements Elements. + * @param options.cssRules CSS rules. + * @param [options.hostElement] Host element. + * @param [options.hostElement.element] Element. + * @param [options.hostElement.cssText] CSS text. */ - private static applyCSSTextToElements( - elements: Array<{ element: IElement; cssText: string }>, - cssRules: CSSRule[], - hostElement?: { element: IElement; cssText: string } - ): void { - if (!elements.length) { + private static parseCSSRules(options: { + cssRules: CSSRule[]; + elements: Array<{ element: IElement; cssText: string }>; + hostElement?: { element: IElement; cssText: string }; + }): void { + if (!options.elements.length) { return; } - const defaultView = elements[0].element.ownerDocument.defaultView; + const defaultView = options.elements[0].element.ownerDocument.defaultView; - for (const rule of cssRules) { + for (const rule of options.cssRules) { if (rule.type === CSSRuleTypeEnum.styleRule) { const selectorText: string = (rule).selectorText; if (selectorText) { if (selectorText.startsWith(':host')) { - if (hostElement) { - const cssText = rule.cssText; - const firstBracket = cssText.indexOf('{'); - const lastBracket = cssText.lastIndexOf('}'); - hostElement.cssText += cssText.substring(firstBracket + 1, lastBracket); + if (options.hostElement) { + options.hostElement.cssText += (rule)._cssText; } } else { - for (const element of elements) { + for (const element of options.elements) { if (element.element.matches(selectorText)) { - const cssText = rule.cssText; - const firstBracket = cssText.indexOf('{'); - const lastBracket = cssText.lastIndexOf('}'); - element.cssText += cssText.substring(firstBracket + 1, lastBracket); + element.cssText += (rule)._cssText; } } } @@ -188,8 +223,33 @@ export default class CSSStyleDeclarationElement { rule.type === CSSRuleTypeEnum.mediaRule && defaultView.matchMedia((rule).conditionalText).matches ) { - this.applyCSSTextToElements(elements, (rule).cssRules, hostElement); + this.parseCSSRules({ + elements: options.elements, + cssRules: (rule).cssRules, + hostElement: options.hostElement + }); + } + } + } + + /** + * Returns CSS value. + * + * @param value Value. + * @param cssVariables CSS variables. + * @returns CSS value. + */ + private static getCSSValue(value: string, cssVariables: { [k: string]: string }): string { + const regexp = new RegExp(CSS_VARIABLE_REGEXP); + let newValue = value; + let match; + while ((match = regexp.exec(value)) !== null) { + const cssVariableValue = cssVariables[match[1]]; + if (!cssVariableValue) { + return null; } + newValue = newValue.replace(match[0], cssVariableValue); } + return value !== newValue ? newValue : value; } } diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementDefaultCSS.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementDefaultCSS.ts new file mode 100644 index 000000000..b745e01ff --- /dev/null +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementDefaultCSS.ts @@ -0,0 +1,130 @@ +export default { + default: 'display: inline;', + A: '', + ABBR: '', + ADDRESS: 'display: block;', + AREA: '', + ARTICLE: 'display: block;', + ASIDE: 'display: block;', + AUDIO: 'display: none;', + B: '', + BASE: 'display: none;', + BDI: '', + BDO: '', + BLOCKQUAOTE: '', + BODY: 'display: block;', + TEMPLATE: 'display: none;', + FORM: 'display: block;', + INPUT: 'display: inline-block;', + TEXTAREA: 'display: inline-block;', + SCRIPT: 'display: none;', + IMG: '', + LINK: 'display: none;', + STYLE: 'display: none;', + LABEL: '', + SLOT: 'display: contents;', + SVG: '', + CIRCLE: '', + ELLIPSE: '', + LINE: '', + PATH: '', + POLYGON: '', + POLYLINE: '', + RECT: '', + STOP: '', + USE: '', + META: 'display: none;', + BLOCKQUOTE: 'display: block;', + BR: '', + BUTTON: 'display: inline-block;', + CANVAS: '', + CAPTION: 'display: table-caption;', + CITE: '', + CODE: '', + COL: 'display: table-column;', + COLGROUP: 'display: table-column-group;', + DATA: '', + DATALIST: 'display: none;', + DD: 'display: block;', + DEL: '', + DETAILS: 'display: block;', + DFN: '', + DIALOG: 'display: none;', + DIV: 'display: block;', + DL: 'display: block;', + DT: 'display: block;', + EM: '', + EMBED: '', + FIELDSET: 'display: block;', + FIGCAPTION: 'display: block;', + FIGURE: 'display: block;', + FOOTER: 'display: block;', + H1: 'display: block;', + H2: 'display: block;', + H3: 'display: block;', + H4: 'display: block;', + H5: 'display: block;', + H6: 'display: block;', + HEAD: 'display: none;', + HEADER: 'display: block;', + HGROUP: 'display: block;', + HR: 'display: block;', + HTML: 'display: block;direction: ltr;', + I: '', + IFRAME: '', + INS: '', + KBD: '', + LEGEND: 'display: block;', + LI: 'display: list-item;', + MAIN: 'display: block;', + MAP: '', + MARK: '', + MATH: '', + MENU: 'display: block;', + MENUITEM: '', + METER: 'display: inline-block;', + NAV: 'display: block;', + NOSCRIPT: '', + OBJECT: '', + OL: 'display: block;', + OPTGROUP: 'display: block;', + OPTION: 'display: block;', + OUTPUT: 'unicode-bidi: isolate;', + P: 'display: block;', + PARAM: 'display: none;', + PICTURE: '', + PRE: 'display: block;', + PROGRESS: 'display: inline-block;', + Q: '', + RB: '', + RP: 'display: none;', + RT: '', + RTC: '', + RUBY: '', + S: '', + SAMP: '', + SECTION: 'display: block;', + SELECT: 'display: inline-block;', + SMALL: '', + SOURCE: '', + SPAN: '', + STRONG: '', + SUB: '', + SUMMARY: 'display: block;', + SUP: '', + TABLE: 'display: table;', + TBODY: 'display: table-row-group;', + TD: 'display: table-cell;', + TFOOT: 'display: table-footer-group;', + TH: 'display: table-cell;', + THEAD: 'display: table-header-group;', + TIME: '', + TITLE: 'display: none;', + TR: 'display: table-row;', + TRACK: '', + U: '', + UL: 'display: block;', + VAR: '', + VIDEO: '', + WBR: '' +}; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementDefaultProperties.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementDefaultProperties.ts deleted file mode 100644 index ccadd2b41..000000000 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementDefaultProperties.ts +++ /dev/null @@ -1,484 +0,0 @@ -export default { - default: { - display: { - value: 'inline', - important: false - } - }, - A: {}, - ABBR: {}, - ADDRESS: { - display: { - value: 'block', - important: false - } - }, - AREA: {}, - ARTICLE: { - display: { - value: 'block', - important: false - } - }, - ASIDE: { - display: { - value: 'block', - important: false - } - }, - AUDIO: { - display: { - value: 'none', - important: false - } - }, - B: {}, - BASE: { - display: { - value: 'none', - important: false - } - }, - BDI: {}, - BDO: {}, - BLOCKQUAOTE: {}, - BODY: { - display: { - value: 'block', - important: false - } - }, - TEMPLATE: { - display: { - value: 'none', - important: false - } - }, - FORM: { - display: { - value: 'block', - important: false - } - }, - INPUT: { - display: { - value: 'inline-block', - important: false - } - }, - TEXTAREA: { - display: { - value: 'inline-block', - important: false - } - }, - SCRIPT: { - display: { - value: 'none', - important: false - } - }, - IMG: {}, - LINK: { - display: { - value: 'none', - important: false - } - }, - STYLE: { - display: { - value: 'none', - important: false - } - }, - LABEL: {}, - SLOT: { - display: { - value: 'contents', - important: false - } - }, - SVG: {}, - CIRCLE: {}, - ELLIPSE: {}, - LINE: {}, - PATH: {}, - POLYGON: {}, - POLYLINE: {}, - RECT: {}, - STOP: {}, - USE: {}, - META: { - display: { - value: 'none', - important: false - } - }, - BLOCKQUOTE: { - display: { - value: 'block', - important: false - } - }, - BR: {}, - BUTTON: { - display: { - value: 'inline-block', - important: false - } - }, - CANVAS: {}, - CAPTION: { - display: { - value: 'table-caption', - important: false - } - }, - CITE: {}, - CODE: {}, - COL: { - display: { - value: 'table-column', - important: false - } - }, - COLGROUP: { - display: { - value: 'table-column-group', - important: false - } - }, - DATA: {}, - DATALIST: { - display: { - value: 'none', - important: false - } - }, - DD: { - display: { - value: 'block', - important: false - } - }, - DEL: {}, - DETAILS: { - display: { - value: 'block', - important: false - } - }, - DFN: {}, - DIALOG: { - display: { - value: 'none', - important: false - } - }, - DIV: { - display: { - value: 'block', - important: false - } - }, - DL: { - display: { - value: 'block', - important: false - } - }, - DT: { - display: { - value: 'block', - important: false - } - }, - EM: {}, - EMBED: {}, - FIELDSET: { - display: { - value: 'block', - important: false - } - }, - FIGCAPTION: { - display: { - value: 'block', - important: false - } - }, - FIGURE: { - display: { - value: 'block', - important: false - } - }, - FOOTER: { - display: { - value: 'block', - important: false - } - }, - H1: { - display: { - value: 'block', - important: false - } - }, - H2: { - display: { - value: 'block', - important: false - } - }, - H3: { - display: { - value: 'block', - important: false - } - }, - H4: { - display: { - value: 'block', - important: false - } - }, - H5: { - display: { - value: 'block', - important: false - } - }, - H6: { - display: { - value: 'block', - important: false - } - }, - HEAD: { - display: { - value: 'none', - important: false - } - }, - HEADER: { - display: { - value: 'block', - important: false - } - }, - HGROUP: { - display: { - value: 'block', - important: false - } - }, - HR: { - display: { - value: 'block', - important: false - } - }, - HTML: { - display: { - value: 'block', - important: false - }, - direction: { - value: 'ltr', - important: false - } - }, - I: {}, - IFRAME: {}, - INS: {}, - KBD: {}, - LEGEND: { - display: { - value: 'block', - important: false - } - }, - LI: { - display: { - value: 'list-item', - important: false - } - }, - MAIN: { - display: { - value: 'block', - important: false - } - }, - MAP: {}, - MARK: {}, - MATH: {}, - MENU: { - display: { - value: 'block', - important: false - } - }, - MENUITEM: {}, - METER: { - display: { - value: 'inline-block', - important: false - } - }, - NAV: { - display: { - value: 'block', - important: false - } - }, - NOSCRIPT: {}, - OBJECT: {}, - OL: { - display: { - value: 'block', - important: false - } - }, - OPTGROUP: { - display: { - value: 'block', - important: false - } - }, - OPTION: { - display: { - value: 'block', - important: false - } - }, - OUTPUT: { - 'unicode-bidi': { - value: 'isolate', - important: false - } - }, - P: { - display: { - value: 'block', - important: false - } - }, - PARAM: { - display: { - value: 'none', - important: false - } - }, - PICTURE: {}, - PRE: { - display: { - value: 'block', - important: false - } - }, - PROGRESS: { - display: { - value: 'inline-block', - important: false - } - }, - Q: {}, - RB: {}, - RP: { - display: { - value: 'none', - important: false - } - }, - RT: {}, - RTC: {}, - RUBY: {}, - S: {}, - SAMP: {}, - SECTION: { - display: { - value: 'block', - important: false - } - }, - SELECT: { - display: { - value: 'inline-block', - important: false - } - }, - SMALL: {}, - SOURCE: {}, - SPAN: {}, - STRONG: {}, - SUB: {}, - SUMMARY: { - display: { - value: 'block', - important: false - } - }, - SUP: {}, - TABLE: { - display: { - value: 'table', - important: false - } - }, - TBODY: { - display: { - value: 'table-row-group', - important: false - } - }, - TD: { - display: { - value: 'table-cell', - important: false - } - }, - TFOOT: { - display: { - value: 'table-footer-group', - important: false - } - }, - TH: { - display: { - value: 'table-cell', - important: false - } - }, - THEAD: { - display: { - value: 'table-header-group', - important: false - } - }, - TIME: {}, - TITLE: { - display: { - value: 'none', - important: false - } - }, - TR: { - display: { - value: 'table-row', - important: false - } - }, - TRACK: {}, - U: {}, - UL: { - display: { - value: 'block', - important: false - } - }, - VAR: {}, - VIDEO: {}, - WBR: {} -}; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementInheritedProperties.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementInheritedProperties.ts index 1b1d4f5bc..5a587a070 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementInheritedProperties.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationElementInheritedProperties.ts @@ -1,39 +1,39 @@ -export default [ - 'border-collapse', - 'border-spacing', - 'caption-side', - 'color', - 'cursor', - 'direction', - 'empty-cells', - 'font-family', - 'font-size', - 'font-style', - 'font-variant', - 'font-weight', - 'font-size-adjust', - 'font-stretch', - 'font', - 'letter-spacing', - 'line-height', - 'list-style-image', - 'list-style-position', - 'list-style-type', - 'list-style', - 'orphans', - 'quotes', - 'tab-size', - 'text-align', - 'text-align-last', - 'text-decoration-color', - 'text-indent', - 'text-justify', - 'text-shadow', - 'text-transform', - 'visibility', - 'white-space', - 'widows', - 'word-break', - 'word-spacing', - 'word-wrap' -]; +export default { + 'border-collapse': true, + 'border-spacing': true, + 'caption-side': true, + color: true, + cursor: true, + direction: true, + 'empty-cells': true, + 'font-family': true, + 'font-size': true, + 'font-style': true, + 'font-variant': true, + 'font-weight': true, + 'font-size-adjust': true, + 'font-stretch': true, + font: true, + 'letter-spacing': true, + 'line-height': true, + 'list-style-image': true, + 'list-style-position': true, + 'list-style-type': true, + 'list-style': true, + orphans: true, + quotes: true, + 'tab-size': true, + 'text-align': true, + 'text-align-last': true, + 'text-decoration-color': true, + 'text-indent': true, + 'text-justify': true, + 'text-shadow': true, + 'text-transform': true, + visibility: true, + 'white-space': true, + widows: true, + 'word-break': true, + 'word-spacing': true, + 'word-wrap': true +}; diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts index 5768d70f4..52185bde8 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts @@ -2,6 +2,7 @@ import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyVal import CSSStyleDeclarationPropertySetParser from './CSSStyleDeclarationPropertySetParser'; import CSSStyleDeclarationValueParser from './CSSStyleDeclarationValueParser'; import CSSStyleDeclarationPropertyGetParser from './CSSStyleDeclarationPropertyGetParser'; +import CSSStyleDeclarationCSSParser from './CSSStyleDeclarationCSSParser'; /** * Computed this.properties property parser. @@ -15,29 +16,16 @@ export default class CSSStyleDeclarationPropertyManager { /** * Class construtor. * - * @param [cssString] CSS string. + * @param [options] Options. + * @param [options.cssText] CSS string. */ - constructor(cssString?: string) { - if (cssString) { - const parts = cssString.split(';'); - - for (const part of parts) { - if (part) { - const [name, value]: string[] = part.trim().split(':'); - if (value) { - const trimmedName = name.trim(); - const trimmedValue = value.trim(); - if (trimmedName && trimmedValue) { - const important = trimmedValue.endsWith(' !important'); - const valueWithoutImportant = trimmedValue.replace(' !important', ''); - - if (valueWithoutImportant && (important || !this.get(trimmedName)?.important)) { - this.set(trimmedName, valueWithoutImportant, important); - } - } - } + constructor(options?: { cssText?: string }) { + if (options?.cssText) { + CSSStyleDeclarationCSSParser.parse(options.cssText, (name, value, important) => { + if (important || !this.get(name)?.important) { + this.set(name, value, important); } - } + }); } } @@ -51,7 +39,6 @@ export default class CSSStyleDeclarationPropertyManager { if (this.properties[name]) { return this.properties[name]; } - switch (name) { case 'margin': return CSSStyleDeclarationPropertyGetParser.getMargin(this.properties); diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts index 36012c9a3..44bd9373d 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertySetParser.ts @@ -107,6 +107,10 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-collapse': { value: variable, important } }; + } const lowerValue = value.toLowerCase(); if ( CSSStyleDeclarationValueParser.getGlobal(lowerValue) || @@ -130,6 +134,10 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { display: { value: variable, important } }; + } const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || DISPLAY.includes(lowerValue)) { return { display: { value: lowerValue, important } }; @@ -150,6 +158,10 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { direction: { value: variable, important } }; + } const lowerValue = value.toLowerCase(); if ( CSSStyleDeclarationValueParser.getGlobal(lowerValue) || @@ -174,6 +186,10 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { width: { value: variable, important } }; + } const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getContentMeasurement(value); @@ -193,6 +209,10 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { top: { value: variable, important } }; + } const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getContentMeasurement(value); @@ -212,6 +232,10 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { right: { value: variable, important } }; + } const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getContentMeasurement(value); @@ -231,6 +255,10 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { bottom: { value: variable, important } }; + } const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getContentMeasurement(value); @@ -250,6 +278,10 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { left: { value: variable, important } }; + } const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getContentMeasurement(value); @@ -269,6 +301,10 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { clear: { value: variable, important } }; + } const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || CLEAR.includes(lowerValue)) { return { clear: { value: lowerValue, important } }; @@ -292,6 +328,10 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { clip: { value: variable, important } }; + } const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || lowerValue === 'auto') { return { clip: { value: lowerValue, important } }; @@ -325,6 +365,10 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { float: { value: variable, important } }; + } const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FLOAT.includes(lowerValue)) { return { float: { value: lowerValue, important } }; @@ -345,6 +389,10 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'css-float': { value: variable, important } }; + } const float = this.getFloat(value, important); return float ? { 'css-float': float['float'] } : null; } @@ -360,6 +408,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { border: { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -408,6 +461,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-width': { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -449,6 +507,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-style': { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -491,6 +554,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-color': { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -533,6 +601,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-image': { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -614,6 +687,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-image-source': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || lowerValue === 'none') { @@ -654,6 +732,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-image-slice': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { @@ -721,6 +804,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-image-width': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { @@ -768,6 +856,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-image-outset': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { @@ -815,6 +908,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-image-repeat': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { @@ -859,6 +957,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-top-width': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); const parsedValue = BORDER_WIDTH.includes(lowerValue) || CSSStyleDeclarationValueParser.getGlobal(lowerValue) @@ -885,6 +988,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-right-width': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); const parsedValue = BORDER_WIDTH.includes(lowerValue) || CSSStyleDeclarationValueParser.getGlobal(lowerValue) @@ -911,6 +1019,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-bottom-width': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); const parsedValue = BORDER_WIDTH.includes(lowerValue) || CSSStyleDeclarationValueParser.getGlobal(lowerValue) @@ -937,6 +1050,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-left-width': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); const parsedValue = BORDER_WIDTH.includes(lowerValue) || CSSStyleDeclarationValueParser.getGlobal(lowerValue) @@ -963,6 +1081,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-top-style': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || BORDER_STYLE.includes(lowerValue)) { return { @@ -985,6 +1108,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-right-style': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || BORDER_STYLE.includes(lowerValue)) { return { @@ -1007,6 +1135,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-bottom-style': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || BORDER_STYLE.includes(lowerValue)) { return { @@ -1029,6 +1162,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-left-style': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || BORDER_STYLE.includes(lowerValue)) { return { @@ -1051,6 +1189,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-top-color': { value: variable, important } }; + } + const color = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getColor(value); @@ -1074,6 +1217,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-right-color': { value: variable, important } }; + } + const color = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getColor(value); @@ -1097,6 +1245,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-bottom-color': { value: variable, important } }; + } + const color = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getColor(value); @@ -1120,6 +1273,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-left-color': { value: variable, important } }; + } + const color = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getColor(value); @@ -1141,6 +1299,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-radius': { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -1181,6 +1344,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-top-left-radius': { value: variable, important } }; + } + const radius = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getMeasurement(value); @@ -1198,6 +1366,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-top-right-radius': { value: variable, important } }; + } + const radius = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getMeasurement(value); @@ -1215,6 +1388,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-bottom-right-radius': { value: variable, important } }; + } + const radius = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getMeasurement(value); @@ -1232,6 +1410,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-bottom-left-radius': { value: variable, important } }; + } + const radius = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getMeasurement(value); @@ -1249,6 +1432,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-top': { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -1293,6 +1481,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-right': { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -1337,6 +1530,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-bottom': { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -1381,6 +1579,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'border-left': { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -1424,6 +1627,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { padding: { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -1464,6 +1672,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'padding-top': { value: variable, important } }; + } + const padding = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getMeasurement(value); @@ -1481,6 +1694,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'padding-right': { value: variable, important } }; + } + const padding = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getMeasurement(value); @@ -1498,6 +1716,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'padding-bottom': { value: variable, important } }; + } + const padding = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getMeasurement(value); @@ -1515,6 +1738,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'padding-left': { value: variable, important } }; + } + const padding = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getMeasurement(value); @@ -1532,6 +1760,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { margin: { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -1572,6 +1805,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'margin-top': { value: variable, important } }; + } + const margin = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getAutoMeasurement(value); @@ -1589,6 +1827,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'margin-right': { value: variable, important } }; + } + const margin = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getAutoMeasurement(value); @@ -1606,6 +1849,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'margin-bottom': { value: variable, important } }; + } + const margin = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getAutoMeasurement(value); @@ -1623,6 +1871,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'margin-left': { value: variable, important } }; + } + const margin = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getAutoMeasurement(value); @@ -1640,6 +1893,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { flex: { value: variable, important } }; + } + const lowerValue = value.trim().toLowerCase(); const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); @@ -1705,6 +1963,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'flex-basis': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FLEX_BASIS.includes(lowerValue)) { return { 'flex-basis': { value: lowerValue, important } }; @@ -1726,6 +1989,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'flex-shrink': { value: variable, important } }; + } + const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getFloat(value); @@ -1745,6 +2013,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'flex-grow': { value: variable, important } }; + } + const parsedValue = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getFloat(value); @@ -1763,6 +2036,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { background: { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -1879,6 +2157,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'background-size': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { return { 'background-size': { value: lowerValue, important } }; @@ -1930,6 +2213,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'background-origin': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if ( CSSStyleDeclarationValueParser.getGlobal(lowerValue) || @@ -1953,6 +2241,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'background-clip': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if ( CSSStyleDeclarationValueParser.getGlobal(lowerValue) || @@ -1976,6 +2269,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'background-repeat': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if ( CSSStyleDeclarationValueParser.getGlobal(lowerValue) || @@ -1999,6 +2297,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'background-attachment': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if ( CSSStyleDeclarationValueParser.getGlobal(lowerValue) || @@ -2022,6 +2325,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'background-position': { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { return { @@ -2132,6 +2440,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'background-position-x': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { @@ -2178,6 +2491,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'background-position-y': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { @@ -2222,6 +2540,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'background-color': { value: variable, important } }; + } + const color = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getColor(value); @@ -2244,6 +2567,11 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'background-image': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || lowerValue === 'none') { @@ -2279,15 +2607,16 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { color: { value: variable, important } }; + } + const color = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getColor(value); - return color - ? { - ['color']: { important, value: color } - } - : null; + return color ? { color: { important, value: color } } : null; } /** @@ -2301,15 +2630,15 @@ export default class CSSStyleDeclarationPropertySetParser { value: string, important: boolean ): { [key: string]: ICSSStyleDeclarationPropertyValue } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'flood-color': { value: variable, important } }; + } const color = CSSStyleDeclarationValueParser.getGlobal(value) || CSSStyleDeclarationValueParser.getColor(value); - return color - ? { - ['flood-color']: { important, value: color } - } - : null; + return color ? { 'flood-color': { important, value: color } } : null; } /** @@ -2325,6 +2654,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { font: { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) { @@ -2410,6 +2744,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'font-style': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FONT_STYLE.includes(lowerValue)) { return { 'font-style': { value: lowerValue, important } }; @@ -2435,6 +2774,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'font-variant': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); return CSSStyleDeclarationValueParser.getGlobal(lowerValue) || lowerValue === 'normal' || @@ -2456,6 +2800,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'font-stretch': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FONT_STRETCH.includes(lowerValue)) { return { 'font-stretch': { value: lowerValue, important } }; @@ -2477,6 +2826,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'font-weight': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FONT_WEIGHT.includes(lowerValue)) { return { 'font-weight': { value: lowerValue, important } }; @@ -2498,6 +2852,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'font-size': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FONT_SIZE.includes(lowerValue)) { return { 'font-size': { value: lowerValue, important } }; @@ -2519,6 +2878,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'line-height': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || lowerValue === 'normal') { return { 'line-height': { value: lowerValue, important } }; @@ -2542,6 +2906,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'font-family': { value: variable, important } }; + } + const globalValue = CSSStyleDeclarationValueParser.getGlobal(value); if (globalValue) { @@ -2607,6 +2976,11 @@ export default class CSSStyleDeclarationPropertySetParser { ): { [key: string]: ICSSStyleDeclarationPropertyValue; } { + const variable = CSSStyleDeclarationValueParser.getVariable(value); + if (variable) { + return { 'text-transform': { value: variable, important } }; + } + const lowerValue = value.toLowerCase(); const parsedValue = CSSStyleDeclarationValueParser.getGlobal(lowerValue) || diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts index b07bcc522..6c508a105 100644 --- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts +++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationValueParser.ts @@ -8,7 +8,7 @@ const URL_REGEXP = /^url\(\s*([^)]*)\s*\)$/; const INTEGER_REGEXP = /^[0-9]+$/; const FLOAT_REGEXP = /^[0-9.]+$/; const CALC_REGEXP = /^calc\([^^)]+\)$/; -const CSS_VARIABLE_REGEXP = /^var\( *--[^)]+\)$/; +const CSS_VARIABLE_REGEXP = /^var\( *(--[^) ]+)\)$/; const FIT_CONTENT_REGEXP = /^fit-content\([^^)]+\)$/; const GRADIENT_REGEXP = /^(repeating-linear|linear|radial|repeating-radial|conic|repeating-conic)-gradient\([^)]+\)$/; @@ -399,6 +399,20 @@ export default class CSSStyleDeclarationValueParser { return value.toLowerCase() === 'initial' ? 'initial' : null; } + /** + * Returns CSS variable. + * + * @param value Value. + * @returns Parsed value. + */ + public static getVariable(value: string): string { + const cssVariableMatch = value.match(CSS_VARIABLE_REGEXP); + if (cssVariableMatch) { + return `var(${cssVariableMatch[1]})`; + } + return null; + } + /** * Returns global. * @@ -406,9 +420,6 @@ export default class CSSStyleDeclarationValueParser { * @returns Parsed value. */ public static getGlobal(value: string): string { - if (CSS_VARIABLE_REGEXP.test(value)) { - return value; - } const lowerValue = value.toLowerCase(); return GLOBALS.includes(lowerValue) ? lowerValue : null; } diff --git a/packages/happy-dom/src/css/rules/CSSFontFaceRule.ts b/packages/happy-dom/src/css/rules/CSSFontFaceRule.ts index 35f96c17d..8c2ebfbe3 100644 --- a/packages/happy-dom/src/css/rules/CSSFontFaceRule.ts +++ b/packages/happy-dom/src/css/rules/CSSFontFaceRule.ts @@ -6,5 +6,20 @@ import CSSStyleDeclaration from '../declaration/CSSStyleDeclaration'; */ export default class CSSFontFaceRule extends CSSRule { public readonly type = CSSRule.FONT_FACE_RULE; - public readonly style: CSSStyleDeclaration; + public _cssText = ''; + private _style: CSSStyleDeclaration = null; + + /** + * Returns style. + * + * @returns Style. + */ + public get style(): CSSStyleDeclaration { + if (!this._style) { + this._style = new CSSStyleDeclaration(); + (this._style.parentRule) = this; + this._style.cssText = this._cssText; + } + return this._style; + } } diff --git a/packages/happy-dom/src/css/rules/CSSKeyframeRule.ts b/packages/happy-dom/src/css/rules/CSSKeyframeRule.ts index 3b7159cc1..b88711968 100644 --- a/packages/happy-dom/src/css/rules/CSSKeyframeRule.ts +++ b/packages/happy-dom/src/css/rules/CSSKeyframeRule.ts @@ -6,8 +6,23 @@ import CSSStyleDeclaration from '../declaration/CSSStyleDeclaration'; */ export default class CSSKeyframeRule extends CSSRule { public readonly type = CSSRule.KEYFRAME_RULE; - public readonly style: CSSStyleDeclaration; public readonly keyText: string; + public _cssText = ''; + private _style: CSSStyleDeclaration = null; + + /** + * Returns style. + * + * @returns Style. + */ + public get style(): CSSStyleDeclaration { + if (!this._style) { + this._style = new CSSStyleDeclaration(); + (this._style.parentRule) = this; + this._style.cssText = this._cssText; + } + return this._style; + } /** * Returns css text. diff --git a/packages/happy-dom/src/css/rules/CSSStyleRule.ts b/packages/happy-dom/src/css/rules/CSSStyleRule.ts index c6308e7e7..517f70f42 100644 --- a/packages/happy-dom/src/css/rules/CSSStyleRule.ts +++ b/packages/happy-dom/src/css/rules/CSSStyleRule.ts @@ -6,9 +6,24 @@ import CSSStyleDeclaration from '../declaration/CSSStyleDeclaration'; */ export default class CSSStyleRule extends CSSRule { public readonly type = CSSRule.STYLE_RULE; - public readonly style: CSSStyleDeclaration; public readonly selectorText = ''; public readonly styleMap = new Map(); + public _cssText = ''; + private _style: CSSStyleDeclaration = null; + + /** + * Returns style. + * + * @returns Style. + */ + public get style(): CSSStyleDeclaration { + if (!this._style) { + this._style = new CSSStyleDeclaration(); + (this._style.parentRule) = this; + this._style.cssText = this._cssText; + } + return this._style; + } /** * Returns css text. diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts index 7fc60ca9a..a76d4054d 100644 --- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts +++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts @@ -1693,6 +1693,7 @@ describe('CSSStyleDeclaration', () => { expect(declaration.font).toBe('small-caps bold 24px / 1 sans-serif'); element.setAttribute('style', 'font: caption'); + debugger; expect(declaration.font).toBe('caption'); }); }); diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts index a04dfe981..9d2f70342 100644 --- a/packages/happy-dom/test/window/Window.test.ts +++ b/packages/happy-dom/test/window/Window.test.ts @@ -278,7 +278,7 @@ describe('Window', () => { expect(computedStyle.cursor).toBe('default'); }); - it('Returns a CSSStyleDeclaration object with computed styles from style sheets.', () => { + it('Returns a CSSStyleDeclaration object with computed styles from style sheets for elements in a HTMLShadowRoot.', () => { const element = document.createElement('span'); const elementStyle = document.createElement('style'); const customElement = document.createElement('custom-element'); @@ -306,6 +306,40 @@ describe('Window', () => { '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'); + const computedStyle = window.getComputedStyle(element); + const parentStyle = document.createElement('style'); + const elementStyle = document.createElement('style'); + + window.happyDOM.setInnerWidth(1024); + + parentStyle.innerHTML = ` + div { + --color-variable: #000; + --valid-variable: 1px solid var(--color-variable); + --invalid-variable: invalid; + } + `; + + elementStyle.innerHTML = ` + span { + border: var(--valid-variable); + font: var(--invalid-variable); + } + `; + + parent.appendChild(elementStyle); + parent.appendChild(element); + + document.body.appendChild(parentStyle); + document.body.appendChild(parent); + + expect(computedStyle.border).toBe('1px solid #000'); + expect(computedStyle.font).toBe(''); + }); }); describe('eval()', () => {