diff --git a/lib/parsers.js b/lib/parsers.js index bcf4e7c..fcd7f09 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -8,39 +8,39 @@ const { resolve: resolveColor, utils } = require('@asamuzakjp/css-color'); const { cssCalc, isColor, isGradient, splitValue } = utils; exports.TYPES = { - INTEGER: 1, - NUMBER: 2, - LENGTH: 3, - PERCENT: 4, - URL: 5, - COLOR: 6, - STRING: 7, - ANGLE: 8, - KEYWORD: 9, - NULL_OR_EMPTY_STR: 10, - CALC: 11, - VAR: 12, - GRADIENT: 13, + UNDEFINED: 0, + NULL_OR_EMPTY_STR: 1, + VAR: 2, + NUMBER: 4, + PERCENT: 8, + LENGTH: 0x10, + ANGLE: 0x20, + CALC: 0x40, + COLOR: 0x80, + STRING: 0x100, + KEYWORD: 0x200, + UNIDENT: 0x8000, }; +// CSS global values +exports.GLOBAL_VALUES = Object.freeze(['initial', 'inherit', 'unset', 'revert', 'revert-layer']); + // regular expressions var DIGIT = '(?:0|[1-9]\\d*)'; var NUMBER = `[+-]?(?:${DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${DIGIT})?`; -var integerRegEx = new RegExp(`^[+-]?${DIGIT}$`); -var numberRegEx = new RegExp(`^${NUMBER}$`); -var lengthRegEx = new RegExp( - `^${NUMBER}(?:[cm]m|[dls]?v(?:[bhiw]|max|min)|in|p[ctx]|q|r?(?:[cl]h|cap|e[mx]|ic))$` -); -var percentRegEx = new RegExp(`^${NUMBER}%$`); +var unitRegEx = new RegExp(`^(${NUMBER})([a-z]+|%)?$`); var angleRegEx = new RegExp(`^${NUMBER}(?:deg|g?rad|turn)$`); var urlRegEx = /^url\(\s*((?:[^)]|\\\))*)\s*\)$/; +var keywordRegEx = /^[a-z]+(?:\-[a-z]+)*$/i; var stringRegEx = /^("[^"]*"|'[^']*')$/; -var varRegEx = /^var\(|(?<=[*/\s(])var\(/; +var varRegEx = /^var\(/; +var varContainedRegEx = /(?<=[*/\s(])var\(/; var calcRegEx = /^(?:a?(?:cos|sin|tan)|abs|atan2|calc|clamp|exp|hypot|log|max|min|mod|pow|rem|round|sign|sqrt)\(/; // This will return one of the above types based on the passed in string exports.valueType = function valueType(val) { + // see https://webidl.spec.whatwg.org/#LegacyNullToEmptyString if (val === '' || val === null) { return exports.TYPES.NULL_OR_EMPTY_STR; } @@ -48,25 +48,7 @@ exports.valueType = function valueType(val) { val = val.toString(); } if (typeof val !== 'string') { - return undefined; - } - if (integerRegEx.test(val)) { - return exports.TYPES.INTEGER; - } - if (numberRegEx.test(val)) { - return exports.TYPES.NUMBER; - } - if (lengthRegEx.test(val)) { - return exports.TYPES.LENGTH; - } - if (percentRegEx.test(val)) { - return exports.TYPES.PERCENT; - } - if (val.startsWith('url(') && val.endsWith(')')) { - if (urlRegEx.test(val)) { - return exports.TYPES.URL; - } - return undefined; + return exports.TYPES.UNDEFINED; } if (varRegEx.test(val)) { return exports.TYPES.VAR; @@ -74,20 +56,47 @@ exports.valueType = function valueType(val) { if (calcRegEx.test(val)) { return exports.TYPES.CALC; } - if (stringRegEx.test(val)) { - return exports.TYPES.STRING; - } - if (angleRegEx.test(val)) { - return exports.TYPES.ANGLE; + if (unitRegEx.test(val)) { + var [, , unit] = unitRegEx.exec(val); + if (!unit) { + return exports.TYPES.NUMBER; + } + if (unit === '%') { + return exports.TYPES.PERCENT; + } + if (/^(?:[cm]m|[dls]?v(?:[bhiw]|max|min)|in|p[ctx]|q|r?(?:[cl]h|cap|e[mx]|ic))$/.test(unit)) { + return exports.TYPES.LENGTH; + } + if (/^(?:deg|g?rad|turn)$/.test(unit)) { + return exports.TYPES.ANGLE; + } } if (isColor(val)) { return exports.TYPES.COLOR; } - if (isGradient(val)) { - return exports.TYPES.GRADIENT; + if (stringRegEx.test(val)) { + return exports.TYPES.STRING; } switch (val.toLowerCase()) { + // system color keywords + case 'accentcolor': + case 'accentcolortext': + case 'activetext': + case 'buttonborder': + case 'buttonface': + case 'buttontext': + case 'canvas': + case 'canvastext': + case 'field': + case 'fieldtext': + case 'graytext': + case 'highlight': + case 'highlighttext': + case 'linktext': + case 'mark': + case 'marktext': + case 'visitedtext': // the following are deprecated in CSS3 case 'activeborder': case 'activecaption': @@ -119,93 +128,121 @@ exports.valueType = function valueType(val) { case 'windowtext': return exports.TYPES.COLOR; default: - return exports.TYPES.KEYWORD; - } -}; - -exports.parseInteger = function parseInteger(val) { - var type = exports.valueType(val); - if (type === exports.TYPES.NULL_OR_EMPTY_STR) { - return val; - } - if (type !== exports.TYPES.INTEGER) { - return undefined; + if (keywordRegEx.test(val)) { + return exports.TYPES.KEYWORD; + } + return exports.TYPES.UNIDENT; } - return String(parseInt(val, 10)); }; exports.parseNumber = function parseNumber(val) { var type = exports.valueType(val); - if (type === exports.TYPES.NULL_OR_EMPTY_STR) { - return val; - } - if (type !== exports.TYPES.NUMBER && type !== exports.TYPES.INTEGER) { - return undefined; + switch (type) { + case exports.TYPES.NULL_OR_EMPTY_STR: + case exports.TYPES.VAR: + return val; + case exports.TYPES.NUMBER: + return `${parseFloat(val)}`; + case exports.TYPES.CALC: + return cssCalc(val, { + format: 'specifiedValue', + }); + default: + if (varContainedRegEx.test(val)) { + return val; + } + return undefined; } - return String(parseFloat(val)); }; exports.parseLength = function parseLength(val) { - if (val === 0 || val === '0') { - return '0px'; - } var type = exports.valueType(val); - if (type === exports.TYPES.NULL_OR_EMPTY_STR) { - return val; - } - if (type !== exports.TYPES.LENGTH) { - return undefined; + switch (type) { + case exports.TYPES.NULL_OR_EMPTY_STR: + case exports.TYPES.VAR: + return val; + case exports.TYPES.CALC: + return cssCalc(val, { + format: 'specifiedValue', + }); + case exports.TYPES.LENGTH: { + var [, numVal, unit] = unitRegEx.exec(val); + return `${parseFloat(numVal)}${unit}`; + } + default: + if (varContainedRegEx.test(val)) { + return val; + } + if (type === exports.TYPES.NUMBER && parseFloat(val) === 0) { + return '0px'; + } + return undefined; } - return val; }; exports.parsePercent = function parsePercent(val) { - if (val === 0 || val === '0') { - return '0%'; - } var type = exports.valueType(val); - if (type === exports.TYPES.NULL_OR_EMPTY_STR) { - return val; - } - if (type !== exports.TYPES.PERCENT) { - return undefined; + switch (type) { + case exports.TYPES.NULL_OR_EMPTY_STR: + case exports.TYPES.VAR: + return val; + case exports.TYPES.CALC: + return cssCalc(val, { + format: 'specifiedValue', + }); + case exports.TYPES.PERCENT: { + var [, numVal, unit] = unitRegEx.exec(val); + return `${parseFloat(numVal)}${unit}`; + } + default: + if (varContainedRegEx.test(val)) { + return val; + } + if (type === exports.TYPES.NUMBER && parseFloat(val) === 0) { + return '0%'; + } + return undefined; } - return val; }; // either a length or a percent exports.parseMeasurement = function parseMeasurement(val) { var type = exports.valueType(val); - if (type === exports.TYPES.VAR) { - return val; - } - if (type === exports.TYPES.CALC) { - return cssCalc(val, { - format: 'specifiedValue', - }); - } - - var length = exports.parseLength(val); - if (length !== undefined) { - return length; + switch (type) { + case exports.TYPES.NULL_OR_EMPTY_STR: + case exports.TYPES.VAR: + return val; + case exports.TYPES.CALC: + return cssCalc(val, { + format: 'specifiedValue', + }); + case exports.TYPES.LENGTH: + case exports.TYPES.PERCENT: { + var [, numVal, unit] = unitRegEx.exec(val); + return `${parseFloat(numVal)}${unit}`; + } + default: + if (varContainedRegEx.test(val)) { + return val; + } + if (type === exports.TYPES.NUMBER && parseFloat(val) === 0) { + return '0px'; + } + return undefined; } - return exports.parsePercent(val); }; -exports.parseInheritingMeasurement = function parseInheritingMeasurement(v) { - if (String(v).toLowerCase() === 'auto') { - return 'auto'; +exports.parseInheritingMeasurement = function parseInheritingMeasurement(val) { + if (/^(?:auto|inherit)$/i.test(val)) { + return val.toLowerCase(); } - if (String(v).toLowerCase() === 'inherit') { - return 'inherit'; - } - return exports.parseMeasurement(v); + return exports.parseMeasurement(val); }; exports.parseUrl = function parseUrl(val) { var type = exports.valueType(val); if (type === exports.TYPES.NULL_OR_EMPTY_STR) { - return val; + return ''; } var res = urlRegEx.exec(val); // does it match the regex? @@ -262,10 +299,11 @@ exports.parseUrl = function parseUrl(val) { return 'url("' + urlstr + '")'; }; +// NOTE: seems not in use? exports.parseString = function parseString(val) { var type = exports.valueType(val); if (type === exports.TYPES.NULL_OR_EMPTY_STR) { - return val; + return ''; } if (type !== exports.TYPES.STRING) { return undefined; @@ -289,11 +327,38 @@ exports.parseString = function parseString(val) { return val; }; +exports.parseKeyword = function parseKeyword(val, validKeywords = []) { + var type = exports.valueType(val); + if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + return ''; + } + if (type === exports.TYPES.VAR) { + return val; + } + if (type !== exports.TYPES.KEYWORD) { + return undefined; + } + val = val.toString().toLowerCase(); + if (validKeywords.includes(val) || exports.GLOBAL_VALUES.includes(val)) { + return val; + } + return undefined; +}; + exports.parseColor = function parseColor(val) { var type = exports.valueType(val); if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + return ''; + } + if (type === exports.TYPES.UNDEFINED) { + return undefined; + } + if (type === exports.TYPES.VAR) { return val; } + if (type === exports.TYPES.KEYWORD) { + return exports.parseKeyword(val); + } if (/^[a-z]+$/i.test(val) && type === exports.TYPES.COLOR) { return val; } @@ -306,10 +371,13 @@ exports.parseColor = function parseColor(val) { return undefined; }; +// FIXME: +// This function seems to be incorrect. +// However, this has no impact so far, as this function is only used by the deprecated `azimuth` property. exports.parseAngle = function parseAngle(val) { var type = exports.valueType(val); if (type === exports.TYPES.NULL_OR_EMPTY_STR) { - return val; + return ''; } if (type !== exports.TYPES.ANGLE) { return undefined; @@ -331,46 +399,36 @@ exports.parseAngle = function parseAngle(val) { return flt + 'deg'; }; -exports.parseKeyword = function parseKeyword(val, valid_keywords) { +exports.parseImage = function parseImage(val) { var type = exports.valueType(val); if (type === exports.TYPES.NULL_OR_EMPTY_STR) { - return val; + return ''; } - if (type !== exports.TYPES.KEYWORD) { + if (type === exports.TYPES.UNDEFINED) { return undefined; } - val = val.toString().toLowerCase(); - var i; - for (i = 0; i < valid_keywords.length; i++) { - if (valid_keywords[i].toLowerCase() === val) { - return valid_keywords[i]; - } - } - return undefined; -}; - -exports.parseImage = function parseImage(val) { - if (/^(?:none|inherit)$/i.test(val)) { + if (type === exports.TYPES.VAR) { return val; } - var type = exports.valueType(val); - if (type === exports.TYPES.NULL_OR_EMPTY_STR || type === exports.TYPES.VAR) { - return val; + if (type === exports.TYPES.KEYWORD) { + return exports.parseKeyword(val, ['none']); } - var values = splitValue(val, ','); + var values = splitValue(val, { + delimiter: ',', + preserveComment: varContainedRegEx.test(val), + }); var isImage = !!values.length; var i; for (i = 0; i < values.length; i++) { var image = values[i]; - var t = exports.valueType(image); - if (t === exports.TYPES.NULL_OR_EMPTY_STR) { + if (exports.valueType(image) === exports.TYPES.NULL_OR_EMPTY_STR) { return image; } - if (t === exports.TYPES.GRADIENT || /^(?:none|inherit)$/i.test(image)) { + if (isGradient(image) || /^(?:none|inherit)$/i.test(image)) { continue; } var imageUrl = exports.parseUrl(image); - if (exports.valueType(imageUrl) === exports.TYPES.URL) { + if (imageUrl) { values[i] = imageUrl; } else { isImage = false; @@ -384,11 +442,15 @@ exports.parseImage = function parseImage(val) { }; // utility to translate from border-width to borderWidth -var dashedToCamelCase = function (dashed) { - var i; +exports.dashedToCamelCase = function (dashed) { + if (dashed.startsWith('--')) { + return dashed; + } + // skip leading hyphen in vendor prefixed value, e.g. -webkit-foo + var i = /^\-[a-z]/.test(dashed) ? 1 : 0; var camel = ''; var nextCap = false; - for (i = 0; i < dashed.length; i++) { + for (; i < dashed.length; i++) { if (dashed[i] !== '-') { camel += nextCap ? dashed[i].toUpperCase() : dashed[i]; nextCap = false; @@ -398,93 +460,93 @@ var dashedToCamelCase = function (dashed) { } return camel; }; -exports.dashedToCamelCase = dashedToCamelCase; -var is_space = /\s/; -var opening_deliminators = ['"', "'", '(']; -var closing_deliminators = ['"', "'", ')']; // this splits on whitespace, but keeps quoted and parened parts together var getParts = function (str) { - var deliminator_stack = []; + var isSpace = /\s/; + var openingDeliminators = ['"', "'", '(']; + var closingDeliminators = ['"', "'", ')']; + var deliminatorStack = []; var length = str.length; var i; var parts = []; - var current_part = ''; - var opening_index; - var closing_index; + var currentPart = ''; + var openingIndex; + var closingIndex; for (i = 0; i < length; i++) { - opening_index = opening_deliminators.indexOf(str[i]); - closing_index = closing_deliminators.indexOf(str[i]); - if (is_space.test(str[i])) { - if (deliminator_stack.length === 0) { - if (current_part !== '') { - parts.push(current_part); + openingIndex = openingDeliminators.indexOf(str[i]); + closingIndex = closingDeliminators.indexOf(str[i]); + if (isSpace.test(str[i])) { + if (deliminatorStack.length === 0) { + if (currentPart !== '') { + parts.push(currentPart); } - current_part = ''; + currentPart = ''; } else { - current_part += str[i]; + currentPart += str[i]; } } else { if (str[i] === '\\') { i++; - current_part += str[i]; + currentPart += str[i]; } else { - current_part += str[i]; - if ( - closing_index !== -1 && - closing_index === deliminator_stack[deliminator_stack.length - 1] - ) { - deliminator_stack.pop(); - } else if (opening_index !== -1) { - deliminator_stack.push(opening_index); + currentPart += str[i]; + if (closingIndex !== -1 && closingIndex === deliminatorStack[deliminatorStack.length - 1]) { + deliminatorStack.pop(); + } else if (openingIndex !== -1) { + deliminatorStack.push(openingIndex); } } } } - if (current_part !== '') { - parts.push(current_part); + if (currentPart !== '') { + parts.push(currentPart); } return parts; }; +// FIXME: need additional argument which indicates syntax +// and/or use Map() for shorthandFor to ensure order of the longhand properties /* * this either returns undefined meaning that it isn't valid * or returns an object where the keys are dashed short * hand properties and the values are the values to set * on them */ -exports.shorthandParser = function parse(v, shorthand_for) { +exports.shorthandParser = function parse(v, shorthandFor) { var obj = {}; var type = exports.valueType(v); if (type === exports.TYPES.NULL_OR_EMPTY_STR) { - Object.keys(shorthand_for).forEach(function (property) { + Object.keys(shorthandFor).forEach(function (property) { obj[property] = ''; }); return obj; } - + if (type === exports.TYPES.UNDEFINED) { + return undefined; + } if (typeof v === 'number') { v = v.toString(); } - if (typeof v !== 'string') { return undefined; } - if (v.toLowerCase() === 'inherit') { return {}; } - var parts = getParts(v); + var parts = splitValue(v); var valid = true; parts.forEach(function (part, i) { - var part_valid = false; - Object.keys(shorthand_for).forEach(function (property) { - if (shorthand_for[property].isValid(part, i)) { - part_valid = true; + var partValid = false; + Object.keys(shorthandFor).forEach(function (property) { + if (shorthandFor[property].isValid(part, i)) { + partValid = true; obj[property] = part; } }); - valid = valid && part_valid; + if (valid) { + valid = partValid; + } }); if (!valid) { return undefined; @@ -492,17 +554,22 @@ exports.shorthandParser = function parse(v, shorthand_for) { return obj; }; -exports.shorthandSetter = function (property, shorthand_for) { +exports.shorthandSetter = function (property, shorthandFor) { return function (v) { - var obj = exports.shorthandParser(v, shorthand_for); + if (v === undefined) { + return; + } + if (v === null) { + v = ''; + } + var obj = exports.shorthandParser(v, shorthandFor); if (obj === undefined) { return; } - //console.log('shorthandSetter for:', property, 'obj:', obj); Object.keys(obj).forEach(function (subprop) { // in case subprop is an implicit property, this will clear // *its* subpropertiesX - var camel = dashedToCamelCase(subprop); + var camel = exports.dashedToCamelCase(subprop); this[camel] = obj[subprop]; // in case it gets translated into something else (0 -> 0px) obj[subprop] = this[camel]; @@ -512,7 +579,7 @@ exports.shorthandSetter = function (property, shorthand_for) { this._values[subprop] = obj[subprop]; } }, this); - Object.keys(shorthand_for).forEach(function (subprop) { + Object.keys(shorthandFor).forEach(function (subprop) { if (!obj.hasOwnProperty(subprop)) { this.removeProperty(subprop); delete this._values[subprop]; @@ -523,19 +590,19 @@ exports.shorthandSetter = function (property, shorthand_for) { // if it already exists, then call the shorthandGetter, if it's an empty // string, don't set the property this.removeProperty(property); - var calculated = exports.shorthandGetter(property, shorthand_for).call(this); + var calculated = exports.shorthandGetter(property, shorthandFor).call(this); if (calculated !== '') { this._setProperty(property, calculated); } }; }; -exports.shorthandGetter = function (property, shorthand_for) { +exports.shorthandGetter = function (property, shorthandFor) { return function () { if (this._values[property] !== undefined) { return this.getPropertyValue(property); } - return Object.keys(shorthand_for) + return Object.keys(shorthandFor) .map(function (subprop) { return this.getPropertyValue(subprop); }, this) @@ -551,12 +618,12 @@ exports.shorthandGetter = function (property, shorthand_for) { // if two, the first applies to the top and bottom, and the second to left and right // if three, the first applies to the top, the second to left and right, the third bottom // if four, top, right, bottom, left -exports.implicitSetter = function (property_before, property_after, isValid, parser) { - property_after = property_after || ''; - if (property_after !== '') { - property_after = '-' + property_after; +exports.implicitSetter = function (propertyBefore, propertyAfter, isValid, parser) { + propertyAfter = propertyAfter || ''; + if (propertyAfter !== '') { + propertyAfter = '-' + propertyAfter; } - var part_names = ['top', 'right', 'bottom', 'left']; + var partNames = ['top', 'right', 'bottom', 'left']; return function (v) { if (typeof v === 'number') { @@ -582,7 +649,7 @@ exports.implicitSetter = function (property_before, property_after, isValid, par parts = parts.map(function (part) { return parser(part); }); - this._setProperty(property_before + property_after, parts.join(' ')); + this._setProperty(propertyBefore + propertyAfter, parts.join(' ')); if (parts.length === 1) { parts[1] = parts[0]; } @@ -594,7 +661,7 @@ exports.implicitSetter = function (property_before, property_after, isValid, par } for (var i = 0; i < 4; i++) { - var property = property_before + '-' + part_names[i] + property_after; + var property = propertyBefore + '-' + partNames[i] + propertyAfter; this.removeProperty(property); if (parts[i] !== '') { this._values[property] = parts[i]; @@ -656,14 +723,14 @@ exports.subImplicitSetter = function (prefix, part, isValid, parser) { }; }; -var camel_to_dashed = /[A-Z]/g; -var first_segment = /^\([^-]\)-/; -var vendor_prefixes = ['o', 'moz', 'ms', 'webkit']; -exports.camelToDashed = function (camel_case) { +exports.camelToDashed = function (camelCase) { + var upperCase = /[A-Z]/g; + var firstSegment = /^\([^-]\)-/; + var vendorPrefixes = ['o', 'moz', 'ms', 'webkit']; var match; - var dashed = camel_case.replace(camel_to_dashed, '-$&').toLowerCase(); - match = dashed.match(first_segment); - if (match && vendor_prefixes.indexOf(match[1]) !== -1) { + var dashed = camelCase.replace(upperCase, '-$&').toLowerCase(); + match = dashed.match(firstSegment); + if (match && vendorPrefixes.indexOf(match[1]) !== -1) { dashed = '-' + dashed; } return dashed; diff --git a/lib/properties/margin.js b/lib/properties/margin.js index fc6f031..339d65b 100644 --- a/lib/properties/margin.js +++ b/lib/properties/margin.js @@ -13,7 +13,7 @@ var isValid = function (v) { type === TYPES.LENGTH || type === TYPES.PERCENT || type === TYPES.CALC || - (type === TYPES.INTEGER && (v === '0' || v === 0)) + (type === TYPES.NUMBER && parseFloat(v) === 0) ); }; diff --git a/lib/properties/padding.js b/lib/properties/padding.js index a82900b..792f65f 100644 --- a/lib/properties/padding.js +++ b/lib/properties/padding.js @@ -10,7 +10,7 @@ var isValid = function (v) { type === TYPES.LENGTH || type === TYPES.PERCENT || type === TYPES.CALC || - (type === TYPES.INTEGER && (v === '0' || v === 0)) + (type === TYPES.NUMBER && parseFloat(v) === 0) ); }; diff --git a/package-lock.json b/package-lock.json index dc40f2a..ba94145 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "4.3.1", "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^3.1.3", + "@asamuzakjp/css-color": "^3.1.7", "rrweb-cssom": "^0.8.0" }, "devDependencies": { @@ -30,9 +30,9 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.3.tgz", - "integrity": "sha512-u25AyjuNrRFGb1O7KmWEu0ExN6iJMlUmDSlOPW/11JF8khOrIGG6oCoYpC+4mZlthNVhFUahk68lNrNI91f6Yg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.7.tgz", + "integrity": "sha512-Ok5fYhtwdyJQmU1PpEv6Si7Y+A4cYb8yNM9oiIJC9TzXPMuN9fvdonKJqcnz9TbFqV6bQ8z0giRq0iaOpGZV2g==", "license": "MIT", "dependencies": { "@csstools/css-calc": "^2.1.3", diff --git a/package.json b/package.json index 05b41e2..bf4b322 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ ], "main": "./lib/CSSStyleDeclaration.js", "dependencies": { - "@asamuzakjp/css-color": "^3.1.3", + "@asamuzakjp/css-color": "^3.1.7", "rrweb-cssom": "^0.8.0" }, "devDependencies": { diff --git a/test/parsers.js b/test/parsers.js index de16abd..bb0c944 100644 --- a/test/parsers.js +++ b/test/parsers.js @@ -23,14 +23,14 @@ describe('valueType', () => { let input = undefined; let output = parsers.valueType(input); - assert.strictEqual(output, undefined); + assert.strictEqual(output, parsers.TYPES.UNDEFINED); }); - it('returns integer for 1', () => { + it('returns number for 1', () => { let input = 1; let output = parsers.valueType(input); - assert.strictEqual(output, parsers.TYPES.INTEGER); + assert.strictEqual(output, parsers.TYPES.NUMBER); }); it('returns number for 1.1', () => { @@ -40,6 +40,13 @@ describe('valueType', () => { assert.strictEqual(output, parsers.TYPES.NUMBER); }); + it('returns number for ".1"', () => { + let input = '.1'; + let output = parsers.valueType(input); + + assert.strictEqual(output, parsers.TYPES.NUMBER); + }); + it('returns length for 100ch', () => { let input = '100ch'; let output = parsers.valueType(input); @@ -54,46 +61,46 @@ describe('valueType', () => { assert.strictEqual(output, parsers.TYPES.PERCENT); }); - it('returns url for url(https://example.com)', () => { + it('returns unidentified for url(https://example.com)', () => { let input = 'url(https://example.com)'; let output = parsers.valueType(input); - assert.strictEqual(output, parsers.TYPES.URL); + assert.strictEqual(output, parsers.TYPES.UNIDENT); }); - it('returns url for url("https://example.com")', () => { + it('returns unidentified for url("https://example.com")', () => { let input = 'url("https://example.com")'; let output = parsers.valueType(input); - assert.strictEqual(output, parsers.TYPES.URL); + assert.strictEqual(output, parsers.TYPES.UNIDENT); }); - it('returns url for url(foo.png)', () => { + it('returns unidentified for url(foo.png)', () => { let input = 'url(foo.png)'; let output = parsers.valueType(input); - assert.strictEqual(output, parsers.TYPES.URL); + assert.strictEqual(output, parsers.TYPES.UNIDENT); }); - it('returns url for url("foo.png")', () => { + it('returns unidentified for url("foo.png")', () => { let input = 'url("foo.png")'; let output = parsers.valueType(input); - assert.strictEqual(output, parsers.TYPES.URL); + assert.strictEqual(output, parsers.TYPES.UNIDENT); }); - it('returns undefined for url(var(--foo))', () => { + it('returns unidentified for url(var(--foo))', () => { let input = 'url(var(--foo))'; let output = parsers.valueType(input); - assert.strictEqual(output, undefined); + assert.strictEqual(output, parsers.TYPES.UNIDENT); }); - it('returns var from calc(100px * var(--foo))', () => { + it('returns calc from calc(100px * var(--foo))', () => { let input = 'calc(100px * var(--foo))'; let output = parsers.valueType(input); - assert.strictEqual(output, parsers.TYPES.VAR); + assert.strictEqual(output, parsers.TYPES.CALC); }); it('returns var from var(--foo)', () => { @@ -215,11 +222,18 @@ describe('valueType', () => { assert.strictEqual(output, parsers.TYPES.COLOR); }); - it('returns gradient for linear-gradient(red, blue)', () => { + it('returns unidentified for linear-gradient(red, blue)', () => { let input = 'linear-gradient(red, blue)'; let output = parsers.valueType(input); - assert.strictEqual(output, parsers.TYPES.GRADIENT); + assert.strictEqual(output, parsers.TYPES.UNIDENT); + }); + + it('returns color for accentcolor', () => { + let input = 'AccentColor'; + let output = parsers.valueType(input); + + assert.strictEqual(output, parsers.TYPES.COLOR); }); it('returns color for legacy activeborder', () => { @@ -229,27 +243,196 @@ describe('valueType', () => { assert.strictEqual(output, parsers.TYPES.COLOR); }); - it('returns keyword for else', () => { + it('returns keyword for foo', () => { let input = 'foo'; let output = parsers.valueType(input); assert.strictEqual(output, parsers.TYPES.KEYWORD); }); -}); -describe('parseInteger', () => { - it.todo('test'); + it('returns keyword for foo-bar', () => { + let input = 'foo-bar'; + let output = parsers.valueType(input); + + assert.strictEqual(output, parsers.TYPES.KEYWORD); + }); + + it('returns unidentified for foo(bar)', () => { + let input = 'foo(bar)'; + let output = parsers.valueType(input); + + assert.strictEqual(output, parsers.TYPES.UNIDENT); + }); }); + describe('parseNumber', () => { - it.todo('test'); + it('should return null', () => { + let input = null; + let output = parsers.parseNumber(input); + + assert.strictEqual(output, null); + }); + + it('should return empty string', () => { + let input = ''; + let output = parsers.parseNumber(input); + + assert.strictEqual(output, ''); + }); + + it('should return undefined', () => { + let input = 'foo'; + let output = parsers.parseNumber(input); + + assert.strictEqual(output, undefined); + }); + + it('should return undefined', () => { + let input = undefined; + let output = parsers.parseNumber(input); + + assert.strictEqual(output, undefined); + }); + + it('should return "1"', () => { + let input = 1; + let output = parsers.parseNumber(input); + + assert.strictEqual(output, '1'); + }); + + it('should return "1"', () => { + let input = '1'; + let output = parsers.parseNumber(input); + + assert.strictEqual(output, '1'); + }); + + it('should return "0.5"', () => { + let input = 0.5; + let output = parsers.parseNumber(input); + + assert.strictEqual(output, '0.5'); + }); + + it('should return "0.5"', () => { + let input = '0.5'; + let output = parsers.parseNumber(input); + + assert.strictEqual(output, '0.5'); + }); + + it('should return "0.5"', () => { + let input = '.5'; + let output = parsers.parseNumber(input); + + assert.strictEqual(output, '0.5'); + }); + + it('should return calculated value', () => { + let input = 'calc(2 / 3)'; + let output = parsers.parseLength(input); + + assert.strictEqual(output, 'calc(0.666667)'); + }); }); + describe('parseLength', () => { - it.todo('test'); + it('should return null', () => { + let input = null; + let output = parsers.parseLength(input); + + assert.strictEqual(output, null); + }); + + it('should return empty string', () => { + let input = ''; + let output = parsers.parseLength(input); + + assert.strictEqual(output, ''); + }); + + it('should return value as is', () => { + let input = 'var(/* comment */ --foo)'; + let output = parsers.parseLength(input); + + assert.strictEqual(output, 'var(/* comment */ --foo)'); + }); + + it('should return calculated value', () => { + let input = 'calc(2em / 3)'; + let output = parsers.parseLength(input); + + assert.strictEqual(output, 'calc(0.666667em)'); + }); + + it('should return serialized value', () => { + let input = 'calc(10px + 20%)'; + let output = parsers.parseLength(input); + + assert.strictEqual(output, 'calc(20% + 10px)'); + }); + + it('should return serialized value', () => { + let input = 'calc(100vh + 10px)'; + let output = parsers.parseLength(input); + + assert.strictEqual(output, 'calc(10px + 100vh)'); + }); }); + describe('parsePercent', () => { - it.todo('test'); + it('should return null', () => { + let input = null; + let output = parsers.parsePercent(input); + + assert.strictEqual(output, null); + }); + + it('should return empty string', () => { + let input = ''; + let output = parsers.parsePercent(input); + + assert.strictEqual(output, ''); + }); + + it('should return value as is', () => { + let input = 'var(/* comment */ --foo)'; + let output = parsers.parsePercent(input); + + assert.strictEqual(output, 'var(/* comment */ --foo)'); + }); + + it('should return calculated value', () => { + let input = 'calc(100% / 3)'; + let output = parsers.parsePercent(input); + + assert.strictEqual(output, 'calc(33.3333%)'); + }); + + it('should return serialized value', () => { + let input = 'calc(10px + 20%)'; + let output = parsers.parsePercent(input); + + assert.strictEqual(output, 'calc(20% + 10px)'); + }); }); + describe('parseMeasurement', () => { + it('should return null', () => { + let input = null; + let output = parsers.parseMeasurement(input); + + assert.strictEqual(output, null); + }); + + it('should return empty string', () => { + let input = ''; + let output = parsers.parseMeasurement(input); + + assert.strictEqual(output, ''); + }); + it('should return value with em unit', () => { let input = '1em'; let output = parsers.parseMeasurement(input); @@ -293,15 +476,86 @@ describe('parseMeasurement', () => { }); it('should return serialized value', () => { - let input = 'calc(10px + 100vh)'; + let input = 'calc(100vh + 10px)'; let output = parsers.parseMeasurement(input); assert.strictEqual(output, 'calc(10px + 100vh)'); }); - it.todo('test'); + it('should return 0px for 0', () => { + let input = 0; + let output = parsers.parseMeasurement(input); + + assert.strictEqual(output, '0px'); + }); + + it('should return 0px for "0"', () => { + let input = '0'; + let output = parsers.parseMeasurement(input); + + assert.strictEqual(output, '0px'); + }); }); + +describe('parseInheritingMeasurement', () => { + it('should return auto', () => { + let input = 'auto'; + let output = parsers.parseInheritingMeasurement(input); + + assert.strictEqual(output, 'auto'); + }); + + it('should return auto', () => { + let input = 'AUTO'; + let output = parsers.parseInheritingMeasurement(input); + + assert.strictEqual(output, 'auto'); + }); + + it('should return inherit', () => { + let input = 'inherit'; + let output = parsers.parseInheritingMeasurement(input); + + assert.strictEqual(output, 'inherit'); + }); + + it('should return inherit', () => { + let input = 'INHERIT'; + let output = parsers.parseInheritingMeasurement(input); + + assert.strictEqual(output, 'inherit'); + }); + + it('should return value with em unit', () => { + let input = '1em'; + let output = parsers.parseInheritingMeasurement(input); + + assert.strictEqual(output, '1em'); + }); + + it('should return value with percent', () => { + let input = '100%'; + let output = parsers.parseInheritingMeasurement(input); + + assert.strictEqual(output, '100%'); + }); +}); + describe('parseUrl', () => { + it('should return empty string', () => { + let input = null; + let output = parsers.parseUrl(input); + + assert.strictEqual(output, ''); + }); + + it('should return empty string', () => { + let input = ''; + let output = parsers.parseUrl(input); + + assert.strictEqual(output, ''); + }); + it('should return undefined', () => { let input = 'url(var(--foo))'; let output = parsers.parseUrl(input); @@ -309,6 +563,13 @@ describe('parseUrl', () => { assert.strictEqual(output, undefined); }); + it('should return undefined', () => { + let input = undefined; + let output = parsers.parseUrl(input); + + assert.strictEqual(output, undefined); + }); + it('should return quoted url string', () => { let input = 'url(sample.png)'; let output = parsers.parseUrl(input); @@ -428,13 +689,41 @@ describe('parseUrl', () => { assert.strictEqual(output, 'url("")'); }); - - it.todo('test'); }); + describe('parseString', () => { it.todo('test'); }); + describe('parseColor', () => { + it('should return empty string', () => { + let input = null; + let output = parsers.parseColor(input); + + assert.strictEqual(output, ''); + }); + + it('should return empty string', () => { + let input = ''; + let output = parsers.parseColor(input); + + assert.strictEqual(output, ''); + }); + + it('should return undefined', () => { + let input = undefined; + let output = parsers.parseColor(input); + + assert.strictEqual(output, undefined); + }); + + it('should return inherit', () => { + let input = 'inherit'; + let output = parsers.parseColor(input); + + assert.strictEqual(output, 'inherit'); + }); + it('should convert hsl to rgb values', () => { let input = 'hsla(0, 1%, 2%)'; let output = parsers.parseColor(input); @@ -519,27 +808,61 @@ describe('parseColor', () => { assert.strictEqual(output, 'transparent'); }); - it.todo('Add more tests'); + it('should return value as is with var()', () => { + let input = 'rgb(var(--my-var, 0, 0, 0))'; + let output = parsers.parseColor(input); + + assert.strictEqual(output, 'rgb(var(--my-var, 0, 0, 0))'); + }); }); + describe('parseAngle', () => { it.todo('test'); }); + describe('parseKeyword', () => { - it.todo('test'); + it('should return value', () => { + let input = 'inherit'; + let output = parsers.parseKeyword(input); + + assert.strictEqual(output, 'inherit'); + }); + + it('should return value', () => { + let input = 'foo'; + let output = parsers.parseKeyword(input, ['foo', 'bar']); + + assert.strictEqual(output, 'foo'); + }); + + it('should return value', () => { + let input = 'Bar'; + let output = parsers.parseKeyword(input, ['foo', 'bar']); + + assert.strictEqual(output, 'bar'); + }); + + it('should return undefined', () => { + let input = 'baz'; + let output = parsers.parseKeyword(input, ['foo', 'bar']); + + assert.strictEqual(output, undefined); + }); }); + describe('parseImage', () => { - it('should return value', () => { - let input = 'none'; + it('should return empty string', () => { + let input = ''; let output = parsers.parseImage(input); - assert.strictEqual(output, 'none'); + assert.strictEqual(output, ''); }); - it('should return value', () => { - let input = 'inherit'; + it('should return empty string', () => { + let input = null; let output = parsers.parseImage(input); - assert.strictEqual(output, 'inherit'); + assert.strictEqual(output, ''); }); it('should return undefined', () => { @@ -549,18 +872,25 @@ describe('parseImage', () => { assert.strictEqual(output, undefined); }); - it('should return undefined for negative radii', () => { - let input = 'radial-gradient(circle -10px at center, red, blue)'; + it('should return none', () => { + let input = 'none'; let output = parsers.parseImage(input); - assert.strictEqual(output, undefined); + assert.strictEqual(output, 'none'); }); - it('should return empty string', () => { - let input = ''; + it('should return inherit', () => { + let input = 'inherit'; let output = parsers.parseImage(input); - assert.strictEqual(output, ''); + assert.strictEqual(output, 'inherit'); + }); + + it('should return undefined for negative radii', () => { + let input = 'radial-gradient(circle -10px at center, red, blue)'; + let output = parsers.parseImage(input); + + assert.strictEqual(output, undefined); }); it('should return value', () => { @@ -597,25 +927,143 @@ describe('parseImage', () => { assert.strictEqual( output, - 'radial-gradient(transparent, /* comment */ var(--custom-color)), url(example.png)' + 'radial-gradient(transparent, /* comment */ var(--custom-color)), url("example.png")' ); }); - it('should return value as is if var() is included and even if invalid image type is included', () => { + it('should return undefined if invalid image type is included', () => { let input = 'radial-gradient(transparent, var(--custom-color)), red'; let output = parsers.parseImage(input); - assert.strictEqual(output, 'radial-gradient(transparent, var(--custom-color)), red'); + assert.strictEqual(output, undefined); }); - it.todo('test'); + it('should return undefined if value is not image type', () => { + let input = 'rgb(var(--my-var, 0, 0, 0))'; + let output = parsers.parseImage(input); + + assert.strictEqual(output, undefined); + }); }); + describe('dashedToCamelCase', () => { - it.todo('test'); + it('should not camelize custom property', () => { + let input = '--foo-bar-baz'; + let output = parsers.dashedToCamelCase(input); + + assert.strictEqual(output, '--foo-bar-baz'); + }); + + it('should camelize value', () => { + let input = 'foo-bar-baz'; + let output = parsers.dashedToCamelCase(input); + + assert.strictEqual(output, 'fooBarBaz'); + }); + + it('should camelize vendor prefixed value', () => { + let input = '-webkit-foo'; + let output = parsers.dashedToCamelCase(input); + + assert.strictEqual(output, 'webkitFoo'); + }); + + it('should not camelize snake cased value', () => { + let input = 'foo_bar_baz'; + let output = parsers.dashedToCamelCase(input); + + assert.strictEqual(output, 'foo_bar_baz'); + }); }); + describe('shorthandParser', () => { + const flexGrow = require('../lib/properties/flexGrow'); + const flexShrink = require('../lib/properties/flexShrink'); + const flexBasis = require('../lib/properties/flexBasis'); + const shorthandFor = { + 'flex-grow': flexGrow, + 'flex-shrink': flexShrink, + 'flex-basis': flexBasis, + }; + + it('should return undefined for keyword', () => { + let input = 'none'; + let output = parsers.shorthandParser(input, shorthandFor); + + assert.strictEqual(output, undefined); + }); + + it('should return object', () => { + let input = '0 0 auto'; + let output = parsers.shorthandParser(input, shorthandFor); + + assert.deepEqual(output, { + 'flex-grow': '0', + 'flex-shrink': '0', + 'flex-basis': 'auto', + }); + }); + + it('should return object', () => { + let input = '0 1 auto'; + let output = parsers.shorthandParser(input, shorthandFor); + + assert.deepEqual(output, { + 'flex-grow': '0', + 'flex-shrink': '1', + 'flex-basis': 'auto', + }); + }); + + it('should return object', () => { + let input = '2'; + let output = parsers.shorthandParser(input, shorthandFor); + + assert.deepEqual(output, { + 'flex-grow': '2', + }); + }); + + it('should return object', () => { + let input = '2 1'; + let output = parsers.shorthandParser(input, shorthandFor); + + assert.deepEqual(output, { + 'flex-grow': '2', + 'flex-shrink': '1', + }); + }); + + it('should return object', () => { + let input = '10px'; + let output = parsers.shorthandParser(input, shorthandFor); + + assert.deepEqual(output, { + 'flex-basis': '10px', + }); + }); + + it('should return object', () => { + let input = '2 10px'; + let output = parsers.shorthandParser(input, shorthandFor); + + assert.deepEqual(output, { + 'flex-grow': '2', + 'flex-basis': '10px', + }); + }); + + // FIXME: + it.skip('should return undefined', () => { + let input = '2 10px 20px'; + let output = parsers.shorthandParser(input, shorthandFor); + + assert.deepEqual(output, undefined); + }); + it.todo('test'); }); + describe('shorthandSetter', () => { it.todo('test'); });