diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration.js index b89f8757..c52ca676 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration.js @@ -3,13 +3,19 @@ * https://github.com/NV/CSSOM */ "use strict"; -const CSSOM = require("rrweb-cssom"); const allExtraProperties = require("./allExtraProperties"); const { shorthandProperties } = require("./shorthandProperties"); const allProperties = require("./generated/allProperties"); const implementedProperties = require("./generated/implementedProperties"); const generatedProperties = require("./generated/properties"); -const { hasVarFunc, parseKeyword, parseShorthand, prepareValue, splitValue } = require("./parsers"); +const { + hasVarFunc, + isValidPropertyValue, + parseCSS, + parseShorthand, + prepareValue, + splitValue +} = require("./parsers"); const { dashedToCamelCase } = require("./utils/camelize"); const { getPropertyDescriptor } = require("./utils/propertyDescriptors"); const { asciiLowercase } = require("./utils/strings"); @@ -157,21 +163,37 @@ class CSSStyleDeclaration { return; } this._setInProgress = true; - let dummyRule; try { - dummyRule = CSSOM.parse(`#bogus{${value}}`).cssRules[0].style; + const valueObj = parseCSS( + value, + { + context: "declarationList", + parseValue: false + }, + true + ); + if (valueObj?.children) { + for (const item of valueObj.children) { + const { + important, + property, + value: { value: rawValue } + } = item; + const isCustomProperty = property.startsWith("--"); + if ( + isCustomProperty || + hasVarFunc(rawValue) || + isValidPropertyValue(property, rawValue) + ) { + this.setProperty(property, rawValue, important ? "important" : ""); + } else { + this.removeProperty(property); + } + } + } } catch { - // Malformed css, just return. return; } - for (let i = 0; i < dummyRule.length; i++) { - const property = dummyRule[i]; - this.setProperty( - property, - dummyRule.getPropertyValue(property), - dummyRule.getPropertyPriority(property) - ); - } this._setInProgress = false; if (typeof this._onChange === "function") { this._onChange(this.cssText); @@ -505,22 +527,18 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { if (part) { part = `-${part}`; } + const shorthandProp = `${prefix}${part}`; let parts = []; if (val === "") { parts.push(val); - } else { - const key = parseKeyword(val); - if (key) { - parts.push(key); - } else { - parts.push(...splitValue(val)); - } + } else if (isValidPropertyValue(shorthandProp, val)) { + parts.push(...splitValue(val)); } if (!parts.length || parts.length > positions.length || !parts.every(isValid)) { return; } parts = parts.map((p) => parser(p)); - this._setProperty(`${prefix}${part}`, parts.join(" ")); + this._setProperty(shorthandProp, parts.join(" ")); switch (positions.length) { case 4: if (parts.length === 1) { diff --git a/lib/parsers.js b/lib/parsers.js index 1237138e..8036f39c 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -4,11 +4,14 @@ */ "use strict"; -const { resolve: resolveColor, utils } = require("@asamuzakjp/css-color"); +const { + resolve: resolveColor, + utils: { cssCalc, isColor, resolveGradient, splitValue } +} = require("@asamuzakjp/css-color"); +const { next: syntaxes } = require("@csstools/css-syntax-patches-for-csstree"); +const csstree = require("css-tree"); const { asciiLowercase } = require("./utils/strings"); -const { cssCalc, isColor, resolveGradient, splitValue } = utils; - // CSS global values // @see https://drafts.csswg.org/css-cascade-5/#defaulting-keywords const GLOBAL_VALUE = Object.freeze(["initial", "inherit", "unset", "revert", "revert-layer"]); @@ -111,6 +114,9 @@ const getNumericType = function getNumericType(val) { return NUM_TYPE.UNDEFINED; }; +// Patch css-tree +const cssTree = csstree.fork(syntaxes); + // Prepare stringified value. exports.prepareValue = function prepareValue(value, globalObject = globalThis) { // `null` is converted to an empty string. @@ -508,6 +514,31 @@ exports.parseShorthand = function parseShorthand(val, shorthandFor, preserve = f return obj; }; +exports.parseCSS = function parseCSS(val, opt, toObject = false) { + const ast = cssTree.parse(val, opt); + if (toObject) { + return cssTree.toPlainObject(ast); + } + return ast; +}; + +// Returns `false` for custom property and/or var(). +exports.isValidPropertyValue = function isValidPropertyValue(prop, val) { + // cssTree.lexer does not support deprecated system colors + // @see https://github.com/w3c/webref/issues/1519#issuecomment-3120290261 + if (SYS_COLOR.includes(asciiLowercase(val))) { + if (/^(?:-webkit-)?(?:[a-z][a-z\d]*-)*color$/i.test(prop)) { + return true; + } + return false; + } + const ast = exports.parseCSS(val, { + context: "value" + }); + const { error, matched } = cssTree.lexer.matchProperty(prop, ast); + return error === null && matched !== null; +}; + // Returns `false` for global values, e.g. "inherit". exports.isValidColor = function isValidColor(val) { if (SYS_COLOR.includes(asciiLowercase(val))) { diff --git a/package-lock.json b/package-lock.json index a0487e71..f011ffc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "MIT", "dependencies": { "@asamuzakjp/css-color": "^4.0.3", - "rrweb-cssom": "^0.8.0" + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" }, "devDependencies": { "@babel/generator": "^7.26.9", @@ -260,6 +261,28 @@ "@csstools/css-tokenizer": "^3.0.4" } }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz", + "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@csstools/css-tokenizer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", @@ -332,9 +355,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -347,9 +370,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz", - "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -357,9 +380,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -370,9 +393,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", - "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -407,13 +430,16 @@ } }, "node_modules/@eslint/js": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz", - "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { @@ -427,13 +453,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { @@ -587,9 +613,9 @@ "license": "MIT" }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -851,6 +877,19 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -1138,20 +1177,20 @@ } }, "node_modules/eslint": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz", - "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.1.0", - "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "9.22.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -1162,9 +1201,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1243,9 +1282,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -1260,9 +1299,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1273,15 +1312,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2277,6 +2316,12 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -2306,6 +2351,25 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2683,7 +2747,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/pidtree": { @@ -2719,6 +2782,35 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2858,12 +2950,6 @@ "node": ">=4" } }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "license": "MIT" - }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -3090,6 +3176,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", diff --git a/package.json b/package.json index 31b25593..cc93a3f1 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "main": "./lib/CSSStyleDeclaration.js", "dependencies": { "@asamuzakjp/css-color": "^4.0.3", - "rrweb-cssom": "^0.8.0" + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" }, "devDependencies": { "@babel/generator": "^7.26.9", diff --git a/test/CSSStyleDeclaration.test.js b/test/CSSStyleDeclaration.test.js index 5eaafab4..eb0edee6 100644 --- a/test/CSSStyleDeclaration.test.js +++ b/test/CSSStyleDeclaration.test.js @@ -2,7 +2,6 @@ const { describe, it } = require("node:test"); const assert = require("node:assert/strict"); -const CSSOM = require("rrweb-cssom"); const { CSSStyleDeclaration } = require("../lib/CSSStyleDeclaration"); const allExtraProperties = require("../lib/allExtraProperties"); const allProperties = require("../lib/generated/allProperties"); @@ -140,8 +139,51 @@ describe("CSSStyleDeclaration", () => { assert.strictEqual(style.cssText, "color: green;"); }); + it("sets empty string for invalid cssText", () => { + const node = { + nodeType: 1, + style: {}, + ownerDocument: { + defaultView: { + DOMException: globalThis.DOMException + } + } + }; + const style = new CSSStyleDeclaration(null, { + context: node + }); + style.cssText = "color: green!"; + assert.strictEqual(style.cssText, ""); + }); + + it("sets internals for Element", () => { + const node = { + nodeType: 1, + style: {}, + ownerDocument: { + defaultView: { + DOMException: globalThis.DOMException + } + } + }; + const style = new CSSStyleDeclaration(null, { + context: node + }); + style.cssText = "color: light-dark(#008000, #0000ff)"; + assert.strictEqual(style.cssText, "color: light-dark(rgb(0, 128, 0), rgb(0, 0, 255));"); + }); + it("sets internals for CSSRule", () => { - const rule = CSSOM.parse(`body { color: green; }`).cssRules[0]; + const rule = { + parentRule: {}, + parentStyleSheet: { + ownerDocument: { + defaultView: { + DOMException: globalThis.DOMException + } + } + } + }; const style = new CSSStyleDeclaration(null, { context: rule }); diff --git a/test/parsers.test.js b/test/parsers.test.js index a7c47265..371fb88d 100644 --- a/test/parsers.test.js +++ b/test/parsers.test.js @@ -1099,6 +1099,170 @@ describe("parseShorthand", () => { }); }); +describe("parseCSS", () => { + it("should get ast", () => { + const input = "color: green !important;"; + const opt = { + context: "declarationList", + parseValue: false + }; + const output = parsers.parseCSS(input, opt); + assert.strictEqual(output.type, "DeclarationList"); + assert.strictEqual(Object.hasOwn(output, "children"), true); + }); + + it("should get ast", () => { + const input = "green"; + const opt = { + context: "value", + parseValue: false + }; + const output = parsers.parseCSS(input, opt); + assert.strictEqual(output.type, "Value"); + assert.strictEqual(Object.hasOwn(output, "children"), true); + }); + + it("should get object", () => { + const input = "color: green !important;"; + const opt = { + context: "declarationList", + parseValue: false + }; + const output = parsers.parseCSS(input, opt, true); + const [ + { + important, + property, + value: { value } + } + ] = output.children; + assert.strictEqual(important, true); + assert.strictEqual(property, "color"); + assert.strictEqual(value, "green"); + }); +}); + +describe("isValidPropertyValue", () => { + it("should return false", () => { + const input = "foo"; + const output = parsers.isValidPropertyValue("color", input); + + assert.strictEqual(output, false); + }); + + it("should return true", () => { + const input = "red"; + const output = parsers.isValidPropertyValue("color", input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "initial"; + const output = parsers.isValidPropertyValue("color", input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "canvas"; + const output = parsers.isValidPropertyValue("color", input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "Canvas"; + const output = parsers.isValidPropertyValue("color", input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "canvas"; + const output = parsers.isValidPropertyValue("-webkit-border-after-color", input); + + assert.strictEqual(output, true); + }); + + it("should return false", () => { + const input = "canvas"; + const output = parsers.isValidPropertyValue("-moz-border-bottom-color", input); + + assert.strictEqual(output, false); + }); + + it("should return true", () => { + const input = "canvas"; + const output = parsers.isValidPropertyValue("background-color", input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "appworkspace"; + const output = parsers.isValidPropertyValue("color", input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "AppWorkSpace"; + const output = parsers.isValidPropertyValue("color", input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "light-dark(green, blue)"; + const output = parsers.isValidPropertyValue("color", input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "light"; + const output = parsers.isValidPropertyValue("color-scheme", input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "dark"; + const output = parsers.isValidPropertyValue("color-scheme", input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "normal"; + const output = parsers.isValidPropertyValue("color-scheme", input); + + assert.strictEqual(output, true); + }); + + it("should return true", () => { + const input = "only dark"; + const output = parsers.isValidPropertyValue("color-scheme", input); + + assert.strictEqual(output, true); + }); + + it("should return false for custom property", () => { + const input = "red"; + const output = parsers.isValidPropertyValue("--foo", input); + + assert.strictEqual(output, false); + }); + + it("should return false for var()", () => { + const input = "var(--foo)"; + const output = parsers.isValidPropertyValue("color", input); + + assert.strictEqual(output, false); + }); +}); + describe("isValidColor", () => { it("should return false", () => { const input = "foo"; diff --git a/test/properties.test.js b/test/properties.test.js index 9c7c3211..f322b040 100644 --- a/test/properties.test.js +++ b/test/properties.test.js @@ -1307,6 +1307,14 @@ describe("color", () => { testPropertyValue("color", "rgb(0 128 0)", "rgb(0, 128, 0)"); }); + it("color should set / get color function", () => { + testPropertyValue( + "color", + "light-dark(#008000, #0000ff)", + "light-dark(rgb(0, 128, 0), rgb(0, 0, 255))" + ); + }); + it("opacity should set / get keyword", () => { testPropertyValue("opacity", "inherit", "inherit"); });