diff --git a/.changeset/unlucky-bulldogs-applaud.md b/.changeset/unlucky-bulldogs-applaud.md new file mode 100644 index 0000000..cba4021 --- /dev/null +++ b/.changeset/unlucky-bulldogs-applaud.md @@ -0,0 +1,5 @@ +--- +'@tokens-studio/sd-transforms': patch +--- + +Improve math compute utility to better deal with mixed units computations. Expand on tests. diff --git a/src/checkAndEvaluateMath.ts b/src/checkAndEvaluateMath.ts index eecf39b..f9e1397 100644 --- a/src/checkAndEvaluateMath.ts +++ b/src/checkAndEvaluateMath.ts @@ -120,8 +120,17 @@ function parseAndReduce(expr: string, fractionDigits = defaultFractionDigits): s } if (typeof result !== 'number') { + let exprToParse = noPixExpr; + // math operators, excluding * + // (** or ^ exponent would theoretically be fine, but postcss-calc-ast-parser does not support it + const operatorsRegex = /[/+%-]/g; + // if we only have * operator, we can consider expression as unitless and compute it that way + // we already know we dont have mixed units from (foundUnits.size > 1) guard above + if (!exprToParse.match(operatorsRegex)) { + exprToParse = exprToParse.replace(new RegExp(resultUnit, 'g'), ''); + } // Try to evaluate as postcss-calc-ast-parser expression - const calcParsed = parse(noPixExpr, { allowInlineCommnets: false }); + const calcParsed = parse(exprToParse, { allowInlineCommnets: false }); // Attempt to reduce the math expression const reduced = reduceExpression(calcParsed); diff --git a/test/spec/checkAndEvaluateMath.spec.ts b/test/spec/checkAndEvaluateMath.spec.ts index f047ad7..5f080d7 100644 --- a/test/spec/checkAndEvaluateMath.spec.ts +++ b/test/spec/checkAndEvaluateMath.spec.ts @@ -29,13 +29,21 @@ describe('check and evaluate math', () => { expect(checkAndEvaluateMath({ value: '4 * 7px * 8px', type: 'dimension' })).to.equal('224px'); }); - it('cannot evaluate math expressions where more than one token has a unit, when unit is not px', () => { - expect(checkAndEvaluateMath({ value: '4em * 7em', type: 'dimension' })).to.equal('4em * 7em'); - expect(checkAndEvaluateMath({ value: '4 * 7em * 8em', type: 'dimension' })).to.equal( - '4 * 7em * 8em', - ); - // exception for pixels, it strips px, making it 4 * 7em = 28em = 448px, where 4px * 7em would be 4px * 112px = 448px as well + it('can evaluate math expressions where more than one token has a unit, assuming no mixed units are used', () => { + expect(checkAndEvaluateMath({ value: '4em + 7em', type: 'dimension' })).to.equal('11em'); + expect(checkAndEvaluateMath({ value: '4 + 7rem', type: 'dimension' })).to.equal('4 + 7rem'); + expect(checkAndEvaluateMath({ value: '4em + 7rem', type: 'dimension' })).to.equal('4em + 7rem'); + }); + + it('can evaluate mixed units if operators are exclusively multiplication and the mix is px or unitless', () => { + expect(checkAndEvaluateMath({ value: '4 * 7em * 8em', type: 'dimension' })).to.equal('224em'); expect(checkAndEvaluateMath({ value: '4px * 7em', type: 'dimension' })).to.equal('28em'); + // 50em would be incorrect when dividing, as em grows, result should shrink, but doesn't + expect(checkAndEvaluateMath({ value: '1000 / 20em', type: 'dimension' })).to.equal( + '1000 / 20em', + ); + // cannot be expressed/resolved without knowing the value of em + expect(checkAndEvaluateMath({ value: '4px + 7em', type: 'dimension' })).to.equal('4px + 7em'); }); it('can evaluate math expressions where more than one token has a unit, as long as for each piece of the expression the unit is the same', () => {