How to use parsePseudo method in Appium Xcuitest Driver

Best JavaScript code snippet using appium-xcuitest-driver

Run Appium Xcuitest Driver automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Sign up Free
_

test-css-expr.js

Source: test-css-expr.js Github

copy
1/**
2 * Copyright 2017 The AMP HTML Authors. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS-IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {parseCss} from '../css-expr';
18import * as ast from '../css-expr-ast';
19
20
21describe('CSS parse', () => {
22
23  /**
24   * @param {string} cssString
25   * @return {string}
26   */
27  function parsePseudo(cssString) {
28    const node = parseCss(cssString);
29    if (node == null) {
30      return null;
31    }
32    return pseudo(node);
33  }
34
35
36  /**
37   * @param {!CssNode} n
38   * @return {string}
39   */
40  function pseudo(n) {
41    if (n instanceof ast.CssPassthroughNode) {
42      return n.css_;
43    }
44    if (n instanceof ast.CssUrlNode) {
45      return `URL<${n.url_}>`;
46    }
47    if (n instanceof ast.CssConcatNode) {
48      return `CON<${pseudoArray(n.array_)}>`;
49    }
50    if (n instanceof ast.CssNumericNode) {
51      return `${n.type_}<${n.num_}` +
52          `${n.units_ && n.units_ != '%' ? ' ' + n.units_.toUpperCase() : ''}>`;
53    }
54    if (n instanceof ast.CssTranslateNode) {
55      return 'TRANSLATE' +
56          `${n.suffix_ ? '-' + n.suffix_.toUpperCase() : ''}` +
57          `<${pseudoArray(n.args_)}>`;
58    }
59    if (n instanceof ast.CssDimSizeNode) {
60      return `DIM<${n.dim_}` +
61          `, ${n.selector_ ? '"' + n.selector_ + '"' : null}` +
62          `, ${n.selectionMethod_}>`;
63    }
64    if (n instanceof ast.CssRandNode) {
65      return `RAND<${n.left_ ? pseudo(n.left_) : null}` +
66          `, ${n.right_ ? pseudo(n.right_) : null}>`;
67    }
68    if (n instanceof ast.CssIndexNode) {
69      return 'INDEX<>';
70    }
71    if (n instanceof ast.CssVarNode) {
72      return `VAR<${n.varName_}${n.def_ ? ', ' + pseudo(n.def_) : ''}>`;
73    }
74    if (n instanceof ast.CssCalcNode) {
75      return `CALC<${pseudo(n.expr_)}>`;
76    }
77    if (n instanceof ast.CssCalcSumNode) {
78      return `${n.op_ == '+' ? 'ADD' : 'SUB'}` +
79          `<${pseudo(n.left_)}, ${pseudo(n.right_)}>`;
80    }
81    if (n instanceof ast.CssCalcProductNode) {
82      return `${n.op_ == '*' ? 'MUL' : 'DIV'}` +
83          `<${pseudo(n.left_)}, ${pseudo(n.right_)}>`;
84    }
85    if (n instanceof ast.CssFuncNode) {
86      return `${n.name_.toUpperCase()}<${pseudoArray(n.args_)}>`;
87    }
88    throw new Error('unknown node: ' + n);
89  }
90
91  /**
92   * @param {!Array<!CssNode>} array
93   * @return {string}
94   */
95  function pseudoArray(array) {
96    if (!array || array.length == 0) {
97      return '';
98    }
99    return array.map(n => pseudo(n)).join(', ');
100  }
101
102  it('should parse empty string as null', () => {
103    expect(parsePseudo('')).to.be.null;
104  });
105
106  it('should parse string', () => {
107    expect(parsePseudo('"abc"')).to.equal('"abc"');
108    expect(parsePseudo('\'abc\'')).to.equal('\'abc\'');
109  });
110
111  it('should parse ident', () => {
112    expect(parsePseudo('abc')).to.equal('abc');
113    expect(parsePseudo('-abc')).to.equal('-abc');
114    expect(parsePseudo('-abc1')).to.equal('-abc1');
115    expect(parsePseudo('-abc1a')).to.equal('-abc1a');
116  });
117
118  it('should parse number', () => {
119    expect(parsePseudo('0')).to.equal('NUM<0>');
120    expect(parsePseudo('1')).to.equal('NUM<1>');
121    expect(parsePseudo('10')).to.equal('NUM<10>');
122    expect(parsePseudo('0.5')).to.equal('NUM<0.5>');
123    expect(parsePseudo('.5')).to.equal('NUM<0.5>');
124    expect(parsePseudo('1.5')).to.equal('NUM<1.5>');
125    expect(parsePseudo('-0.5')).to.equal('NUM<-0.5>');
126    expect(parsePseudo('-.5')).to.equal('NUM<-0.5>');
127    expect(parsePseudo('+.5')).to.equal('NUM<0.5>');
128    expect(parsePseudo('+1.5')).to.equal('NUM<1.5>');
129    expect(parsePseudo('1e2')).to.equal('NUM<100>');
130    expect(parsePseudo('.1e2')).to.equal('NUM<10>');
131    expect(parsePseudo('1e+2')).to.equal('NUM<100>');
132    expect(parsePseudo('1e-2')).to.equal('NUM<0.01>');
133  });
134
135  it('should parse percent', () => {
136    expect(parsePseudo('0%')).to.equal('PRC<0>');
137    expect(parsePseudo('1%')).to.equal('PRC<1>');
138    expect(parsePseudo('0.5%')).to.equal('PRC<0.5>');
139    expect(parsePseudo('.5%')).to.equal('PRC<0.5>');
140    expect(parsePseudo('1e2%')).to.equal('PRC<100>');
141  });
142
143  it('should parse length', () => {
144    expect(parsePseudo('100px')).to.equal('LEN<100 PX>');
145    expect(parsePseudo('-100px')).to.equal('LEN<-100 PX>');
146    expect(parsePseudo('+100px')).to.equal('LEN<100 PX>');
147    expect(parsePseudo('100.5px')).to.equal('LEN<100.5 PX>');
148    expect(parsePseudo('0.5px')).to.equal('LEN<0.5 PX>');
149    expect(parsePseudo('.5px')).to.equal('LEN<0.5 PX>');
150
151    // Non-px units:
152    expect(parsePseudo('100em')).to.equal('LEN<100 EM>');
153    expect(parsePseudo('100rem')).to.equal('LEN<100 REM>');
154    expect(parsePseudo('100vh')).to.equal('LEN<100 VH>');
155    expect(parsePseudo('100vw')).to.equal('LEN<100 VW>');
156    expect(parsePseudo('100vmin')).to.equal('LEN<100 VMIN>');
157    expect(parsePseudo('100vmax')).to.equal('LEN<100 VMAX>');
158    expect(parsePseudo('100cm')).to.equal('LEN<100 CM>');
159    expect(parsePseudo('100mm')).to.equal('LEN<100 MM>');
160    expect(parsePseudo('100q')).to.equal('LEN<100 Q>');
161    expect(parsePseudo('100in')).to.equal('LEN<100 IN>');
162    expect(parsePseudo('100pc')).to.equal('LEN<100 PC>');
163    expect(parsePseudo('100pt')).to.equal('LEN<100 PT>');
164  });
165
166  it('should parse angle', () => {
167    expect(parsePseudo('10deg')).to.equal('ANG<10 DEG>');
168    expect(parsePseudo('-10deg')).to.equal('ANG<-10 DEG>');
169    expect(parsePseudo('+10deg')).to.equal('ANG<10 DEG>');
170    expect(parsePseudo('1.5deg')).to.equal('ANG<1.5 DEG>');
171    expect(parsePseudo('0.5deg')).to.equal('ANG<0.5 DEG>');
172    expect(parsePseudo('.5deg')).to.equal('ANG<0.5 DEG>');
173
174    // Non-deg units:
175    expect(parsePseudo('10rad')).to.equal('ANG<10 RAD>');
176    expect(parsePseudo('10grad')).to.equal('ANG<10 GRAD>');
177  });
178
179  it('should parse time', () => {
180    expect(parsePseudo('10ms')).to.equal('TME<10 MS>');
181    expect(parsePseudo('10s')).to.equal('TME<10 S>');
182  });
183
184  it('should parse url', () => {
185    expect(parsePseudo('url("https://acme.org/abc")'))
186        .to.equal('URL<https://acme.org/abc>');
187    expect(parsePseudo('url(\'https://acme.org/abc\')'))
188        .to.equal('URL<https://acme.org/abc>');
189    expect(parsePseudo('url(\'data:abc\')'))
190        .to.equal('URL<data:abc>');
191    // HTTP and relative are allowed at this stage.
192    expect(parsePseudo('url(\'http://acme.org/abc\')'))
193        .to.equal('URL<http://acme.org/abc>');
194    expect(parsePseudo('url(\'/relative\')'))
195        .to.equal('URL</relative>');
196  });
197
198  it('should parse hexcolor', () => {
199    expect(parsePseudo('#123456')).to.equal('#123456');
200    expect(parsePseudo('#AB3456')).to.equal('#AB3456');
201    expect(parsePseudo('#ABCDEF')).to.equal('#ABCDEF');
202    // Alpha-format:
203    expect(parsePseudo('#ABCDEF01')).to.equal('#ABCDEF01');
204    // Abbrv:
205    expect(parsePseudo('#FFF')).to.equal('#FFF');
206    expect(parsePseudo('#fff')).to.equal('#fff');
207  });
208
209  it('should parse a function', () => {
210    expect(parsePseudo('unknown()')).to.equal('UNKNOWN<>');
211    expect(parsePseudo('unknown( )')).to.equal('UNKNOWN<>');
212    expect(parsePseudo('rgb(10, 20, 30)'))
213        .to.equal('RGB<NUM<10>, NUM<20>, NUM<30>>');
214    expect(parsePseudo('translate(100px, 200px)'))
215        .to.equal('TRANSLATE<LEN<100 PX>, LEN<200 PX>>');
216  });
217
218  it('should parse a translate()', () => {
219    expect(parsePseudo('translate(100px)'))
220        .to.equal('TRANSLATE<LEN<100 PX>>');
221    expect(parsePseudo('translate(100px, 200px)'))
222        .to.equal('TRANSLATE<LEN<100 PX>, LEN<200 PX>>');
223    expect(parsePseudo('translateX(100px)'))
224        .to.equal('TRANSLATE-X<LEN<100 PX>>');
225    expect(parsePseudo('TRANSLATEX(100px)'))
226        .to.equal('TRANSLATE-X<LEN<100 PX>>');
227    expect(parsePseudo('translatex(100px)'))
228        .to.equal('TRANSLATE-X<LEN<100 PX>>');
229    expect(parsePseudo('translateY(100px)'))
230        .to.equal('TRANSLATE-Y<LEN<100 PX>>');
231    expect(parsePseudo('translateZ(100px)'))
232        .to.equal('TRANSLATE-Z<LEN<100 PX>>');
233    expect(parsePseudo('translate3d(1px, 2px, 3px)'))
234        .to.equal('TRANSLATE-3D<LEN<1 PX>, LEN<2 PX>, LEN<3 PX>>');
235  });
236
237  it('should parse a concat of functions', () => {
238    expect(parsePseudo('translateX(100px) rotate(45deg)'))
239        .to.equal('CON<TRANSLATE-X<LEN<100 PX>>, ROTATE<ANG<45 DEG>>>');
240  });
241
242  it('should allow two-way concatenation', () => {
243    // This is currently doesn't happen in parse, but by API possible with
244    // minor changes to parsing order. Thus it's re-tested separately here.
245    expect(pseudo(ast.CssConcatNode.concat(
246        new ast.CssConcatNode([new ast.CssPassthroughNode('A')]),
247        new ast.CssConcatNode([new ast.CssPassthroughNode('B')]))))
248        .to.equal('CON<A, B>');
249    expect(pseudo(ast.CssConcatNode.concat(
250        new ast.CssPassthroughNode('A'),
251        new ast.CssConcatNode([new ast.CssPassthroughNode('B')]))))
252        .to.equal('CON<A, B>');
253  });
254
255  it('should parse a dimension function', () => {
256    // Current.
257    expect(parsePseudo('width()'))
258        .to.equal('DIM<w, null, null>');
259    expect(parsePseudo('height()'))
260        .to.equal('DIM<h, null, null>');
261
262    // Query.
263    expect(parsePseudo('width(".sel")'))
264        .to.equal('DIM<w, ".sel", null>');
265    expect(parsePseudo('WIDTH(".sel > div")'))
266        .to.equal('DIM<w, ".sel > div", null>');
267    expect(parsePseudo('height(".sel")'))
268        .to.equal('DIM<h, ".sel", null>');
269
270    // Closest.
271    expect(parsePseudo('width(closest(".sel"))'))
272        .to.equal('DIM<w, ".sel", closest>');
273    expect(parsePseudo('height(closest(".sel"))'))
274        .to.equal('DIM<h, ".sel", closest>');
275  });
276
277  it('should parse a rand function', () => {
278    expect(parsePseudo('rand()'))
279        .to.equal('RAND<null, null>');
280    expect(parsePseudo('rand(10, 20)'))
281        .to.equal('RAND<NUM<10>, NUM<20>>');
282    expect(parsePseudo('rand(10px, 20px)'))
283        .to.equal('RAND<LEN<10 PX>, LEN<20 PX>>');
284    expect(parsePseudo('rand(10em, 20em)'))
285        .to.equal('RAND<LEN<10 EM>, LEN<20 EM>>');
286    expect(parsePseudo('rand(10px, 20em)'))
287        .to.equal('RAND<LEN<10 PX>, LEN<20 EM>>');
288    expect(parsePseudo('rand(10s, 20s)'))
289        .to.equal('RAND<TME<10 S>, TME<20 S>>');
290    expect(parsePseudo('rand(10rad, 20rad)'))
291        .to.equal('RAND<ANG<10 RAD>, ANG<20 RAD>>');
292    expect(parsePseudo('rand(10%, 20%)'))
293        .to.equal('RAND<PRC<10>, PRC<20>>');
294    expect(parsePseudo('rand(var(--x), var(--y))'))
295        .to.equal('RAND<VAR<--x>, VAR<--y>>');
296    expect(parsePseudo('rand(10px, var(--y))'))
297        .to.equal('RAND<LEN<10 PX>, VAR<--y>>');
298  });
299
300  it('should parse an index function', () => {
301    expect(parsePseudo('index()')).to.equal('INDEX<>');
302    expect(parsePseudo('INDEX()')).to.equal('INDEX<>');
303  });
304
305  it('should parse a var()', () => {
306    expect(parsePseudo('var(--abc)')).to.equal('VAR<--abc>');
307    expect(parsePseudo('var(--abc1)')).to.equal('VAR<--abc1>');
308    expect(parsePseudo('var(--abc-d)')).to.equal('VAR<--abc-d>');
309    expect(parsePseudo('VAR(--abc)')).to.equal('VAR<--abc>');
310    expect(parsePseudo('var(--ABC)')).to.equal('VAR<--ABC>');
311    expect(parsePseudo('var(--abc, 100px)'))
312        .to.equal('VAR<--abc, LEN<100 PX>>');
313    expect(parsePseudo('var(--abc, var(--def))'))
314        .to.equal('VAR<--abc, VAR<--def>>');
315    expect(parsePseudo('var(--abc, var(--def, 200px))'))
316        .to.equal('VAR<--abc, VAR<--def, LEN<200 PX>>>');
317    expect(parsePseudo('var(--abc, rgb(1, 2, 3))'))
318        .to.equal('VAR<--abc, RGB<NUM<1>, NUM<2>, NUM<3>>>');
319  });
320
321  it('should parse a calc()', () => {
322    expect(parsePseudo('calc(100px)'))
323        .to.equal('CALC<LEN<100 PX>>');
324    expect(parsePseudo('calc((100px))'))
325        .to.equal('CALC<LEN<100 PX>>');
326
327    // calc_sum
328    expect(parsePseudo('calc(100px + 200px)'))
329        .to.equal('CALC<ADD<LEN<100 PX>, LEN<200 PX>>>');
330    expect(parsePseudo('calc(100px - 200px)'))
331        .to.equal('CALC<SUB<LEN<100 PX>, LEN<200 PX>>>');
332    expect(parsePseudo('calc((100px + 200px))'))
333        .to.equal('CALC<ADD<LEN<100 PX>, LEN<200 PX>>>');
334
335    // calc_product
336    expect(parsePseudo('calc(100px * 2)'))
337        .to.equal('CALC<MUL<LEN<100 PX>, NUM<2>>>');
338    expect(parsePseudo('calc(2 * 100px)'))
339        .to.equal('CALC<MUL<NUM<2>, LEN<100 PX>>>');
340    expect(parsePseudo('calc(100px / 2)'))
341        .to.equal('CALC<DIV<LEN<100 PX>, NUM<2>>>');
342    expect(parsePseudo('calc((100px * 2))'))
343        .to.equal('CALC<MUL<LEN<100 PX>, NUM<2>>>');
344
345    // precedence
346    expect(parsePseudo('calc(100px + 200px + 300px)'))
347        .to.equal('CALC<ADD<ADD<LEN<100 PX>, LEN<200 PX>>, LEN<300 PX>>>');
348    expect(parsePseudo('calc(100px * 2 * 3)'))
349        .to.equal('CALC<MUL<MUL<LEN<100 PX>, NUM<2>>, NUM<3>>>');
350    expect(parsePseudo('calc(100px * 2 / 3)'))
351        .to.equal('CALC<DIV<MUL<LEN<100 PX>, NUM<2>>, NUM<3>>>');
352    expect(parsePseudo('calc(100px + 200px * 0.5)'))
353        .to.equal('CALC<ADD<LEN<100 PX>, MUL<LEN<200 PX>, NUM<0.5>>>>');
354    expect(parsePseudo('calc(100px - 200px / 0.5)'))
355        .to.equal('CALC<SUB<LEN<100 PX>, DIV<LEN<200 PX>, NUM<0.5>>>>');
356    expect(parsePseudo('calc((100px + 200px) * 0.5)'))
357        .to.equal('CALC<MUL<ADD<LEN<100 PX>, LEN<200 PX>>, NUM<0.5>>>');
358    expect(parsePseudo('calc((100px - 200px) / 0.5)'))
359        .to.equal('CALC<DIV<SUB<LEN<100 PX>, LEN<200 PX>>, NUM<0.5>>>');
360    expect(parsePseudo('calc(100px * 0.5 + 200px)'))
361        .to.equal('CALC<ADD<MUL<LEN<100 PX>, NUM<0.5>>, LEN<200 PX>>>');
362    expect(parsePseudo('calc(100px / 0.5 - 200px)'))
363        .to.equal('CALC<SUB<DIV<LEN<100 PX>, NUM<0.5>>, LEN<200 PX>>>');
364    expect(parsePseudo('calc(0.5 * (100px + 200px))'))
365        .to.equal('CALC<MUL<NUM<0.5>, ADD<LEN<100 PX>, LEN<200 PX>>>>');
366
367    // func
368    expect(parsePseudo('calc(var(--abc, 100px) + 200px)'))
369        .to.equal('CALC<ADD<VAR<--abc, LEN<100 PX>>, LEN<200 PX>>>');
370  });
371});
372
373
374describes.sandboxed('CSS resolve', {}, () => {
375  const normalize = true;
376  let context;
377  let contextMock;
378
379  beforeEach(() => {
380    context = new ast.CssContext();
381    contextMock = sandbox.mock(context);
382  });
383
384  afterEach(() => {
385    contextMock.verify();
386  });
387
388  function resolvedCss(node, opt_normalize) {
389    const resolved = node.resolve(context, opt_normalize);
390    return resolved ? resolved.css() : null;
391  }
392
393  it('should not resolve value for a const', () => {
394    const node = new ast.CssNode();
395    const nodeMock = sandbox.mock(node);
396    nodeMock.expects('css').returns('CSS').atLeast(1);
397    nodeMock.expects('isConst').returns(true).atLeast(1);
398    nodeMock.expects('calc').never();
399    expect(node.css()).to.equal('CSS');
400    expect(node.resolve(context)).to.equal(node);
401    expect(resolvedCss(node)).to.equal('CSS');
402    nodeMock.verify();
403  });
404
405  it('should resolve value for a non-const', () => {
406    const node = new ast.CssNode();
407    const node2 = new ast.CssNode();
408    node2.css = () => 'CSS2';
409    const nodeMock = sandbox.mock(node);
410    nodeMock.expects('isConst').returns(false).atLeast(1);
411    nodeMock.expects('css').returns('CSS').atLeast(1);
412    nodeMock.expects('calc').returns(node2).atLeast(1);
413    expect(node.css()).to.equal('CSS');
414    expect(node.resolve(context)).to.equal(node2);
415    expect(resolvedCss(node)).to.equal('CSS2');
416    nodeMock.verify();
417  });
418
419  it('should resolve a const concat node', () => {
420    expect(ast.isVarCss('css1 css2', false)).to.be.false;
421    expect(ast.isVarCss('css1 10px', false)).to.be.false;
422    expect(ast.isVarCss('css1 10em', false)).to.be.false;
423    expect(ast.isVarCss('css1 css2', normalize)).to.be.false;
424    expect(ast.isVarCss('css1 10px', normalize)).to.be.false;
425    expect(ast.isVarCss('css1 10em', normalize)).to.be.true;
426    expect(ast.isVarCss('css1 10EM', normalize)).to.be.true;
427    const node = new ast.CssConcatNode([
428      new ast.CssPassthroughNode('css1'),
429      new ast.CssPassthroughNode('css2'),
430    ]);
431    expect(node.isConst()).to.be.true;
432    expect(node.isConst(normalize)).to.be.true;
433    expect(node.resolve(context)).to.equal(node);
434    expect(node.resolve(context, normalize)).to.equal(node);
435    expect(node.css()).to.equal('css1 css2');
436    expect(resolvedCss(node)).to.equal('css1 css2');
437    expect(resolvedCss(node, normalize)).to.equal('css1 css2');
438  });
439
440  it('should resolve a var concat node', () => {
441    expect(ast.isVarCss('var(--x)', false)).to.be.true;
442    expect(ast.isVarCss('var(--x) css2', false)).to.be.true;
443    expect(ast.isVarCss('VAR(--x)', false)).to.be.true;
444    expect(ast.isVarCss('Var(--x)', false)).to.be.true;
445    expect(ast.isVarCss('var(--x)', normalize)).to.be.true;
446    contextMock.expects('getVar')
447        .withExactArgs('--var1')
448        .returns(new ast.CssPassthroughNode('val1'))
449        .twice();
450    const node = new ast.CssConcatNode([
451      new ast.CssPassthroughNode('css1'),
452      new ast.CssVarNode('--var1'),
453    ]);
454    expect(node.isConst()).to.be.false;
455    expect(node.isConst(normalize)).to.be.false;
456    expect(node.css()).to.equal('css1 var(--var1)');
457    expect(resolvedCss(node)).to.equal('css1 val1');
458    expect(resolvedCss(node, normalize)).to.equal('css1 val1');
459  });
460
461  it('should resolve a null concat node', () => {
462    contextMock.expects('getVar')
463        .withExactArgs('--var1')
464        .returns(null)
465        .twice();
466    const node = new ast.CssConcatNode([
467      new ast.CssPassthroughNode('css1'),
468      new ast.CssVarNode('--var1'),
469    ]);
470    expect(node.isConst()).to.be.false;
471    expect(node.isConst(normalize)).to.be.false;
472    expect(node.css()).to.equal('css1 var(--var1)');
473    expect(resolvedCss(node)).to.be.null;
474    expect(resolvedCss(node, normalize)).to.be.null;
475  });
476
477  it('should resolve a number node', () => {
478    expect(ast.isVarCss('11.5', false)).to.be.false;
479    expect(ast.isVarCss('11.5', normalize)).to.be.false;
480    const node = new ast.CssNumberNode(11.5);
481    expect(node.isConst()).to.be.true;
482    expect(node.isConst(normalize)).to.be.true;
483    expect(node.css()).to.equal('11.5');
484    expect(resolvedCss(node)).to.equal('11.5');
485    expect(resolvedCss(node, normalize)).to.equal('11.5');
486    expect(node.createSameUnits(20.5).css()).to.equal('20.5');
487  });
488
489  it('should resolve a percent node w/o dimension', () => {
490    expect(ast.isVarCss('11.5%', false)).to.be.false;
491    expect(ast.isVarCss('11.5%', normalize)).to.be.true;
492    const node = new ast.CssPercentNode(11.5);
493    expect(node.isConst()).to.be.true;
494    expect(node.isConst(normalize)).to.be.false;
495    expect(node.css()).to.equal('11.5%');
496    expect(resolvedCss(node)).to.equal('11.5%');
497    expect(resolvedCss(node, normalize)).to.equal('11.5%');
498    expect(node.createSameUnits(20.5).css()).to.equal('20.5%');
499  });
500
501  it('should resolve a percent node w/dimension', () => {
502    contextMock.expects('getDimension').returns('w').twice();
503    contextMock.expects('getCurrentElementSize')
504        .returns({width: 110, height: 220});
505    expect(ast.isVarCss('11.5%', false)).to.be.false;
506    const node = new ast.CssPercentNode(11.5);
507    expect(node.isConst()).to.be.true;
508    expect(node.isConst(normalize)).to.be.false;
509    expect(node.css()).to.equal('11.5%');
510    expect(resolvedCss(node)).to.equal('11.5%');
511    expect(resolvedCss(node, normalize)).to.equal('12.65px');
512    expect(node.createSameUnits(20.5).css()).to.equal('20.5%');
513  });
514
515  describe('url', () => {
516    it('should always consider as non-const', () => {
517      expect(ast.isVarCss('url("https://acme.org")', false)).to.be.true;
518      expect(ast.isVarCss('url("https://acme.org")', normalize)).to.be.true;
519    });
520
521    it('should resolve an absolute HTTPS URL', () => {
522      const node = new ast.CssUrlNode('https://acme.org/img');
523      expect(node.isConst()).to.be.true;
524      expect(node.isConst(normalize)).to.be.true;
525      expect(node.css()).to.equal('url("https://acme.org/img")');
526    });
527
528    it('should resolve an data URL', () => {
529      const node = new ast.CssUrlNode('data:abc');
530      expect(node.isConst()).to.be.true;
531      expect(node.isConst(normalize)).to.be.true;
532      expect(node.css()).to.equal('url("data:abc")');
533    });
534
535    it('should resolve an empty URL', () => {
536      const node = new ast.CssUrlNode('');
537      expect(node.isConst()).to.be.true;
538      expect(node.isConst(normalize)).to.be.true;
539      expect(node.css()).to.equal('');
540    });
541
542    it('should re-resolve an HTTP url', () => {
543      contextMock
544          .expects('resolveUrl')
545          .withExactArgs('http://acme.org/non-secure')
546          .returns('broken')
547          .twice();
548      const node = new ast.CssUrlNode('http://acme.org/non-secure');
549      expect(node.isConst()).to.be.false;
550      expect(node.isConst(normalize)).to.be.false;
551      expect(node.css()).to.equal('url("http://acme.org/non-secure")');
552      expect(node.calc(context).css()).to.equal('url("broken")');
553      expect(node.calc(context, normalize).css()).to.equal('url("broken")');
554    });
555
556    it('should re-resolve a relative url', () => {
557      contextMock
558          .expects('resolveUrl')
559          .withExactArgs('/relative')
560          .returns('https://acme.org/relative')
561          .twice();
562      const node = new ast.CssUrlNode('/relative');
563      expect(node.isConst()).to.be.false;
564      expect(node.isConst(normalize)).to.be.false;
565      expect(node.css()).to.equal('url("/relative")');
566      expect(node.calc(context).css())
567          .to.equal('url("https://acme.org/relative")');
568      expect(node.calc(context, normalize).css())
569          .to.equal('url("https://acme.org/relative")');
570    });
571
572    it('should fail when context resolution fails', () => {
573      contextMock
574          .expects('resolveUrl')
575          .withExactArgs('http://acme.org/non-secure')
576          .throws(new Error('intentional'))
577          .once();
578      const node = new ast.CssUrlNode('http://acme.org/non-secure');
579      expect(() => {
580        node.calc(context);
581      }).to.throw(/intentional/);
582    });
583  });
584
585  describe('length', () => {
586    it('should always consider as const', () => {
587      expect(ast.isVarCss('11.5px', false)).to.be.false;
588      expect(ast.isVarCss('11.5em', false)).to.be.false;
589
590      expect(ast.isVarCss('11.5px', normalize)).to.be.false;
591      expect(ast.isVarCss('11.5em', normalize)).to.be.true;
592      expect(ast.isVarCss('11.5rem', normalize)).to.be.true;
593      expect(ast.isVarCss('11.5vh', normalize)).to.be.true;
594      expect(ast.isVarCss('11.5vw', normalize)).to.be.true;
595      expect(ast.isVarCss('11.5vmin', normalize)).to.be.true;
596      expect(ast.isVarCss('11.5vmax', normalize)).to.be.true;
597      expect(ast.isVarCss('11.5EM', normalize)).to.be.true;
598    });
599
600    it('should resolve a px-length node', () => {
601      const node = new ast.CssLengthNode(11.5, 'px');
602      expect(node.isConst()).to.be.true;
603      expect(node.isConst(normalize)).to.be.true;
604      expect(node.css()).to.equal('11.5px');
605      expect(resolvedCss(node)).to.equal('11.5px');
606      expect(resolvedCss(node, normalize)).to.equal('11.5px');
607      expect(node.createSameUnits(20.5).css()).to.equal('20.5px');
608      expect(node.norm()).to.equal(node);
609      expect(node.norm().css()).to.equal('11.5px');
610    });
611
612    it('should resolve a em-length node', () => {
613      const node = new ast.CssLengthNode(11.5, 'em');
614      expect(node.isConst()).to.be.true;
615      expect(node.css()).to.equal('11.5em');
616      expect(resolvedCss(node)).to.equal('11.5em');
617      expect(node.createSameUnits(20.5).css()).to.equal('20.5em');
618
619      contextMock.expects('getCurrentFontSize').returns(10).twice();
620      const norm = node.norm(context);
621      expect(norm).to.not.equal(node);
622      expect(norm.css()).to.equal('115px');
623      expect(node.isConst(normalize)).to.be.false;
624      expect(resolvedCss(node, normalize)).to.equal('115px');
625    });
626
627    it('should resolve a rem-length node', () => {
628      const node = new ast.CssLengthNode(11.5, 'rem');
629      expect(node.isConst()).to.be.true;
630      expect(node.css()).to.equal('11.5rem');
631      expect(resolvedCss(node)).to.equal('11.5rem');
632      expect(node.createSameUnits(20.5).css()).to.equal('20.5rem');
633
634      contextMock.expects('getRootFontSize').returns(2).twice();
635      const norm = node.norm(context);
636      expect(norm).to.not.equal(node);
637      expect(norm.css()).to.equal('23px');
638      expect(node.isConst(normalize)).to.be.false;
639      expect(resolvedCss(node, normalize)).to.equal('23px');
640    });
641
642    describe('vw-length', () => {
643      beforeEach(() => {
644        contextMock.expects('getViewportSize')
645            .returns({width: 200, height: 400})
646            .atLeast(1);
647      });
648
649      it('should resolve a vw-length node', () => {
650        const node = new ast.CssLengthNode(11.5, 'vw');
651        expect(node.isConst()).to.be.true;
652        expect(node.css()).to.equal('11.5vw');
653        expect(resolvedCss(node)).to.equal('11.5vw');
654        expect(node.createSameUnits(20.5).css()).to.equal('20.5vw');
655        expect(node.norm(context).css()).to.equal('23px');
656        expect(node.isConst(normalize)).to.be.false;
657        expect(resolvedCss(node, normalize)).to.equal('23px');
658      });
659
660      it('should resolve a vh-length node', () => {
661        const node = new ast.CssLengthNode(11.5, 'vh');
662        expect(node.isConst()).to.be.true;
663        expect(node.css()).to.equal('11.5vh');
664        expect(resolvedCss(node)).to.equal('11.5vh');
665        expect(node.createSameUnits(20.5).css()).to.equal('20.5vh');
666        expect(node.norm(context).css()).to.equal('46px');
667        expect(node.isConst(normalize)).to.be.false;
668        expect(resolvedCss(node, normalize)).to.equal('46px');
669      });
670
671      it('should resolve a vmin-length node', () => {
672        const node = new ast.CssLengthNode(11.5, 'vmin');
673        expect(node.isConst()).to.be.true;
674        expect(node.css()).to.equal('11.5vmin');
675        expect(resolvedCss(node)).to.equal('11.5vmin');
676        expect(node.createSameUnits(20.5).css()).to.equal('20.5vmin');
677        expect(node.norm(context).css()).to.equal('23px');
678        expect(node.isConst(normalize)).to.be.false;
679        expect(resolvedCss(node, normalize)).to.equal('23px');
680      });
681
682      it('should resolve a vmax-length node', () => {
683        const node = new ast.CssLengthNode(11.5, 'vmax');
684        expect(node.isConst()).to.be.true;
685        expect(node.css()).to.equal('11.5vmax');
686        expect(resolvedCss(node)).to.equal('11.5vmax');
687        expect(node.createSameUnits(20.5).css()).to.equal('20.5vmax');
688        expect(node.norm(context).css()).to.equal('46px');
689        expect(node.isConst(normalize)).to.be.false;
690        expect(resolvedCss(node, normalize)).to.equal('46px');
691      });
692    });
693
694    it('should disallow a real-length node', () => {
695      expect(() => {
696        new ast.CssLengthNode(1, 'cm').norm(context);
697      }).to.throw(/unknown/);
698      expect(() => {
699        new ast.CssLengthNode(1, 'mm').norm(context);
700      }).to.throw(/unknown/);
701      expect(() => {
702        new ast.CssLengthNode(1, 'in').norm(context);
703      }).to.throw(/unknown/);
704      expect(() => {
705        new ast.CssLengthNode(1, 'pt').norm(context);
706      }).to.throw(/unknown/);
707      expect(() => {
708        new ast.CssLengthNode(1, 'q').norm(context);
709      }).to.throw(/unknown/);
710    });
711
712    it('should resolve a percent-length in w-direction', () => {
713      contextMock.expects('getDimension').returns('w');
714      contextMock.expects('getCurrentElementSize')
715          .returns({width: 110, height: 220})
716          .once();
717      const node = new ast.CssLengthNode(11.5, 'px');
718      const percent = node.calcPercent(10, context);
719      expect(percent.css()).to.equal('11px');
720    });
721
722    it('should resolve a percent-length in h-direction', () => {
723      contextMock.expects('getDimension').returns('h');
724      contextMock.expects('getCurrentElementSize')
725          .returns({width: 110, height: 220})
726          .once();
727      const node = new ast.CssLengthNode(11.5, 'px');
728      const percent = node.calcPercent(10, context);
729      expect(percent.css()).to.equal('22px');
730    });
731
732    it('should resolve a percent-length in unknown direction', () => {
733      contextMock.expects('getCurrentElementSize')
734          .returns({width: 110, height: 220})
735          .once();
736      const node = new ast.CssLengthNode(11.5, 'px');
737      const percent = node.calcPercent(10, context);
738      expect(percent.css()).to.equal('0px');
739    });
740  });
741
742  describe('angle', () => {
743    it('should always consider as const', () => {
744      expect(ast.isVarCss('11.5rad', false)).to.be.false;
745      expect(ast.isVarCss('11.5deg', false)).to.be.false;
746      expect(ast.isVarCss('11.5grad', false)).to.be.false;
747
748      expect(ast.isVarCss('11.5rad', normalize)).to.be.false;
749      expect(ast.isVarCss('11.5deg', normalize)).to.be.true;
750      expect(ast.isVarCss('11.5grad', normalize)).to.be.true;
751    });
752
753    it('should resolve a rad-angle node', () => {
754      const node = new ast.CssAngleNode(11.5, 'rad');
755      expect(node.isConst()).to.be.true;
756      expect(node.css()).to.equal('11.5rad');
757      expect(resolvedCss(node)).to.equal('11.5rad');
758      expect(node.createSameUnits(20.5).css()).to.equal('20.5rad');
759      expect(node.norm()).to.equal(node);
760      expect(node.norm().css()).to.equal('11.5rad');
761      expect(node.isConst(normalize)).to.be.true;
762      expect(resolvedCss(node, normalize)).to.equal('11.5rad');
763    });
764
765    it('should resolve a deg-length node', () => {
766      const node = new ast.CssAngleNode(11.5, 'deg');
767      expect(node.isConst()).to.be.true;
768      expect(node.css()).to.equal('11.5deg');
769      expect(resolvedCss(node)).to.equal('11.5deg');
770      expect(node.createSameUnits(20.5).css()).to.equal('20.5deg');
771
772      const norm = node.norm(context);
773      expect(norm).to.not.equal(node);
774      expect(norm.css()).to.match(/[\d\.]*rad/);
775      expect(norm.num_).to.closeTo(0.2007, 1e-4);
776      expect(node.isConst(normalize)).to.be.false;
777      expect(node.calc(context, normalize).units_).to.equal('rad');
778      expect(node.calc(context, normalize).num_).to.closeTo(0.2007, 1e-4);
779    });
780
781    it('should resolve a grad-length node', () => {
782      const node = new ast.CssAngleNode(11.5, 'grad');
783      expect(node.isConst()).to.be.true;
784      expect(node.css()).to.equal('11.5grad');
785      expect(resolvedCss(node)).to.equal('11.5grad');
786      expect(node.createSameUnits(20.5).css()).to.equal('20.5grad');
787
788      const norm = node.norm(context);
789      expect(norm).to.not.equal(node);
790      expect(norm.css()).to.match(/[\d\.]*rad/);
791      expect(norm.num_).to.closeTo(0.1806, 1e-4);
792      expect(node.isConst(normalize)).to.be.false;
793      expect(node.calc(context, normalize).units_).to.equal('rad');
794      expect(node.calc(context, normalize).num_).to.closeTo(0.1806, 1e-4);
795    });
796  });
797
798  describe('time', () => {
799    it('should always consider as const', () => {
800      expect(ast.isVarCss('11.5ms', false)).to.be.false;
801      expect(ast.isVarCss('11.5s', false)).to.be.false;
802
803      expect(ast.isVarCss('11.5ms', normalize)).to.be.false;
804      expect(ast.isVarCss('11.5s', normalize)).to.be.true;
805    });
806
807    it('should resolve a milliseconds node', () => {
808      const node = new ast.CssTimeNode(11.5, 'ms');
809      expect(node.isConst()).to.be.true;
810      expect(node.css()).to.equal('11.5ms');
811      expect(resolvedCss(node)).to.equal('11.5ms');
812      expect(node.createSameUnits(20.5).css()).to.equal('20.5ms');
813      expect(node.norm()).to.equal(node);
814      expect(node.norm().css()).to.equal('11.5ms');
815      expect(node.isConst(normalize)).to.be.true;
816      expect(resolvedCss(node, normalize)).to.equal('11.5ms');
817    });
818
819    it('should resolve a seconds node', () => {
820      const node = new ast.CssTimeNode(11.5, 's');
821      expect(node.isConst()).to.be.true;
822      expect(node.css()).to.equal('11.5s');
823      expect(resolvedCss(node)).to.equal('11.5s');
824      expect(node.createSameUnits(20.5).css()).to.equal('20.5s');
825
826      const norm = node.norm(context);
827      expect(norm).to.not.equal(node);
828      expect(norm.css()).to.equal('11500ms');
829      expect(node.isConst(normalize)).to.be.false;
830      expect(resolvedCss(node, normalize)).to.equal('11500ms');
831    });
832  });
833
834  describe('function', () => {
835    it('should resolve a const-arg function', () => {
836      contextMock.expects('withDimension').never();
837      const node = new ast.CssFuncNode('rgb', [
838        new ast.CssNumberNode(201),
839        new ast.CssNumberNode(202),
840        new ast.CssNumberNode(203),
841      ]);
842      expect(node.isConst()).to.be.true;
843      expect(node.css()).to.equal('rgb(201,202,203)');
844      expect(resolvedCss(node)).to.equal('rgb(201,202,203)');
845      expect(node.resolve(context)).to.equal(node);
846      expect(node.isConst(normalize)).to.be.true;
847      expect(resolvedCss(node, normalize)).to.equal('rgb(201,202,203)');
848    });
849
850    it('should resolve a var-arg function', () => {
851      contextMock.expects('withDimension').never();
852      contextMock.expects('getVar')
853          .withExactArgs('--var1')
854          .returns(new ast.CssNumberNode(11))
855          .twice();
856      const node = new ast.CssFuncNode('rgb', [
857        new ast.CssNumberNode(201),
858        new ast.CssNumberNode(202),
859        new ast.CssVarNode('--var1'),
860      ]);
861      expect(node.isConst()).to.be.false;
862      expect(node.css()).to.equal('rgb(201,202,var(--var1))');
863      expect(resolvedCss(node)).to.equal('rgb(201,202,11)');
864      expect(node.isConst(normalize)).to.be.false;
865      expect(resolvedCss(node, normalize)).to.equal('rgb(201,202,11)');
866    });
867
868    it('should norm function', () => {
869      contextMock.expects('getDimension').returns('w').atLeast(1);
870      contextMock.expects('getCurrentFontSize').returns(10).atLeast(1);
871      contextMock.expects('getCurrentElementSize')
872          .returns({width: 110, height: 220}).atLeast(1);
873      const node = new ast.CssFuncNode('xyz', [
874        new ast.CssLengthNode(11, 'px'),
875        new ast.CssLengthNode(11.5, 'em'),
876        new ast.CssPercentNode(11.5),
877      ]);
878      expect(node.isConst()).to.be.true;
879      expect(node.css()).to.equal('xyz(11px,11.5em,11.5%)');
880      expect(resolvedCss(node)).to.equal('xyz(11px,11.5em,11.5%)');
881      expect(node.isConst(normalize)).to.be.false;
882      expect(resolvedCss(node, normalize)).to.equal('xyz(11px,115px,12.65px)');
883    });
884
885    it('should push a dimension when specified', () => {
886      let index = 0;
887      const stack = [];
888      context.withDimension = function(dim, callback) {
889        stack.push(dim);
890        const res = callback();
891        stack.pop();
892        return res;
893      };
894
895      const arg1 = new ast.CssNumberNode(201);
896      arg1.resolve = function() {
897        expect(index).to.equal(0);
898        expect(stack).to.deep.equal(['w']);
899        index++;
900        return new ast.CssPassthroughNode('w');
901      };
902
903      const arg2 = new ast.CssNumberNode(202);
904      arg2.resolve = function() {
905        expect(index).to.equal(1);
906        expect(stack).to.deep.equal(['h']);
907        index++;
908        return new ast.CssPassthroughNode('h');
909      };
910
911      const arg3 = new ast.CssNumberNode(203);
912      arg3.resolve = function() {
913        expect(index).to.equal(2);
914        expect(stack).to.deep.equal([]);
915        index++;
916        return new ast.CssPassthroughNode('-');
917      };
918
919      const node = new ast.CssFuncNode('rgb',
920          [arg1, arg2, arg3], ['w', 'h']);
921      expect(node.calc(context).css()).to.equal('rgb(w,h,-)');
922      expect(index).to.equal(3);
923    });
924
925    it('should resolve a var-arg function with nulls', () => {
926      contextMock.expects('getVar')
927          .withExactArgs('--var1')
928          .returns(null)
929          .atLeast(1);
930      const node = new ast.CssFuncNode('rgb', [
931        new ast.CssNumberNode(201),
932        new ast.CssNumberNode(202),
933        new ast.CssVarNode('--var1'),
934      ]);
935      expect(node.isConst()).to.be.false;
936      expect(node.css()).to.equal('rgb(201,202,var(--var1))');
937      expect(resolvedCss(node)).to.be.null;
938      expect(node.isConst(normalize)).to.be.false;
939      expect(resolvedCss(node, normalize)).to.be.null;
940    });
941  });
942
943  describe('translate', () => {
944    let dimStack;
945
946    beforeEach(() => {
947      dimStack = [];
948      context.withDimension = function(dim, callback) {
949        dimStack.push(dim);
950        const res = callback();
951        dimStack.pop();
952        return res;
953      };
954      sandbox.stub(ast.CssPassthroughNode.prototype, 'resolve', function() {
955        return new ast.CssPassthroughNode(this.css_ + dimStack.join(''));
956      });
957    });
958
959    it('should always consider as const', () => {
960      expect(ast.isVarCss('translate(10px)', false)).to.be.false;
961      expect(ast.isVarCss('translateX(10px)', false)).to.be.false;
962      expect(ast.isVarCss('translateY(10px)', false)).to.be.false;
963      expect(ast.isVarCss('translate3d(10px)', false)).to.be.false;
964
965      expect(ast.isVarCss('translate(10px)', normalize)).to.be.false;
966      expect(ast.isVarCss('translate(10%)', normalize)).to.be.true;
967      expect(ast.isVarCss('translate(10em)', normalize)).to.be.true;
968      expect(ast.isVarCss('translate(10EM)', normalize)).to.be.true;
969    });
970
971    it('should resolve 2d translate', () => {
972      const node = new ast.CssTranslateNode('', [
973        new ast.CssPassthroughNode('X'),
974        new ast.CssPassthroughNode('Y'),
975      ]);
976      expect(node.isConst()).to.be.true;
977      expect(node.calc(context).css()).to.equal('translate(Xw,Yh)');
978      expect(node.isConst(normalize)).to.be.true;
979      expect(node.calc(context, normalize).css()).to.equal('translate(Xw,Yh)');
980    });
981
982    it('should resolve abbreviated 2d translate', () => {
983      const node = new ast.CssTranslateNode('', [
984        new ast.CssPassthroughNode('X'),
985      ]);
986      expect(node.calc(context).css()).to.equal('translate(Xw)');
987    });
988
989    it('should resolve translateX', () => {
990      const node = new ast.CssTranslateNode('x', [
991        new ast.CssPassthroughNode('X'),
992      ]);
993      expect(node.calc(context).css()).to.equal('translatex(Xw)');
994    });
995
996    it('should resolve translateY', () => {
997      const node = new ast.CssTranslateNode('y', [
998        new ast.CssPassthroughNode('Y'),
999      ]);
1000      expect(node.calc(context).css()).to.equal('translatey(Yh)');
1001    });
1002
1003    it('should resolve translateZ', () => {
1004      const node = new ast.CssTranslateNode('z', [
1005        new ast.CssPassthroughNode('Z'),
1006      ]);
1007      expect(node.calc(context).css()).to.equal('translatez(Zz)');
1008    });
1009
1010    it('should resolve translate3d', () => {
1011      const node = new ast.CssTranslateNode('3d', [
1012        new ast.CssPassthroughNode('X'),
1013        new ast.CssPassthroughNode('Y'),
1014        new ast.CssPassthroughNode('Z'),
1015      ]);
1016      expect(node.calc(context).css()).to.equal('translate3d(Xw,Yh,Zz)');
1017    });
1018
1019    it('should resolve translate with null args to null', () => {
1020      contextMock.expects('getVar')
1021          .withExactArgs('--var1')
1022          .returns(null)
1023          .once();
1024      const node = new ast.CssTranslateNode('', [
1025        new ast.CssPassthroughNode('X'),
1026        new ast.CssVarNode('--var1'),
1027      ]);
1028      expect(node.isConst()).to.be.false;
1029      expect(node.isConst(normalize)).to.be.false;
1030      expect(node.calc(context)).to.be.null;
1031    });
1032  });
1033
1034  describe('dimension', () => {
1035    let sizes;
1036
1037    beforeEach(() => {
1038      sizes = {
1039        'null(.class)': {width: 111, height: 222},
1040        'closest(.class > div)': {width: 112, height: 224},
1041      };
1042      context.getCurrentElementSize = function() {
1043        return {width: 110, height: 220};
1044      };
1045      context.getElementSize = function(selector, selectionMethod) {
1046        return sizes[`${selectionMethod}(${selector})`];
1047      };
1048    });
1049
1050    it('should always consider as non-const', () => {
1051      expect(ast.isVarCss('width()', false)).to.be.true;
1052      expect(ast.isVarCss('height()', false)).to.be.true;
1053      expect(ast.isVarCss('width("")')).to.be.true;
1054      expect(ast.isVarCss('height("")')).to.be.true;
1055    });
1056
1057    it('should be always a non-const and no css', () => {
1058      const node = new ast.CssDimSizeNode('?');
1059      expect(node.isConst()).to.be.false;
1060      expect(() => node.css()).to.throw(/no css/);
1061    });
1062
1063    it('should resolve width on the current node', () => {
1064      const node = new ast.CssDimSizeNode('w');
1065      expect(node.calc(context).css()).to.equal('110px');
1066    });
1067
1068    it('should resolve height on the current node', () => {
1069      const node = new ast.CssDimSizeNode('h');
1070      expect(node.calc(context).css()).to.equal('220px');
1071    });
1072
1073    it('should resolve width on the selected node', () => {
1074      const node = new ast.CssDimSizeNode('w', '.class');
1075      expect(node.calc(context).css()).to.equal('111px');
1076    });
1077
1078    it('should resolve height on the selected node', () => {
1079      const node = new ast.CssDimSizeNode('h', '.class');
1080      expect(node.calc(context).css()).to.equal('222px');
1081    });
1082
1083    it('should resolve width on the selected closest node', () => {
1084      const node = new ast.CssDimSizeNode('w', '.class > div', 'closest');
1085      expect(node.calc(context).css()).to.equal('112px');
1086    });
1087
1088    it('should resolve height on the selected closest node', () => {
1089      const node = new ast.CssDimSizeNode('h', '.class > div', 'closest');
1090      expect(node.calc(context).css()).to.equal('224px');
1091    });
1092  });
1093
1094  describe('rand', () => {
1095    beforeEach(() => {
1096      sandbox.stub(Math, 'random', () => 0.25);
1097    });
1098
1099    it('should always consider as non-const', () => {
1100      expect(ast.isVarCss('rand()')).to.be.true;
1101      expect(ast.isVarCss('rand(10px, 20px)')).to.be.true;
1102      expect(ast.isVarCss('rand(10px, 20px)', normalize)).to.be.true;
1103      expect(ast.isVarCss('rand(10em, 20em)', normalize)).to.be.true;
1104    });
1105
1106    it('should always be a non-const and no css', () => {
1107      const node = new ast.CssRandNode();
1108      expect(node.isConst()).to.equal(false);
1109      expect(() => node.css()).to.throw(/no css/);
1110    });
1111
1112    it('should resolve a no-arg rand', () => {
1113      const node = new ast.CssRandNode();
1114      expect(resolvedCss(node)).to.equal('0.25');
1115    });
1116
1117    it('should rand two numbers', () => {
1118      const node = new ast.CssRandNode(
1119          new ast.CssNumberNode(10),
1120          new ast.CssNumberNode(20));
1121      expect(resolvedCss(node)).to.equal('12.5');
1122    });
1123
1124    it('should rand two numbers in opposite direction', () => {
1125      const node = new ast.CssRandNode(
1126          new ast.CssNumberNode(200),
1127          new ast.CssNumberNode(100));
1128      expect(resolvedCss(node)).to.equal('125');
1129    });
1130
1131    it('should rand two same-unit values - length', () => {
1132      const node = new ast.CssRandNode(
1133          new ast.CssLengthNode(10, 'px'),
1134          new ast.CssLengthNode(20, 'px'));
1135      expect(resolvedCss(node)).to.equal('12.5px');
1136    });
1137
1138    it('should rand two same-unit values - times', () => {
1139      const node = new ast.CssRandNode(
1140          new ast.CssTimeNode(10, 's'),
1141          new ast.CssTimeNode(20, 's'));
1142      expect(resolvedCss(node)).to.equal('12.5s');
1143      expect(resolvedCss(node, normalize)).to.equal('12500ms');
1144    });
1145
1146    it('should rand two same-unit values - percent', () => {
1147      const node = new ast.CssRandNode(
1148          new ast.CssPercentNode(10),
1149          new ast.CssPercentNode(20));
1150      expect(resolvedCss(node)).to.equal('12.5%');
1151
1152      contextMock.expects('getDimension').returns('w').atLeast(1);
1153      contextMock.expects('getCurrentElementSize')
1154          .returns({width: 110, height: 220}).atLeast(1);
1155      expect(resolvedCss(node, normalize)).to.equal('13.75px');
1156    });
1157
1158    it('should resolve both parts', () => {
1159      contextMock.expects('getVar')
1160          .withExactArgs('--var1')
1161          .returns(new ast.CssLengthNode(100, 'px'))
1162          .once();
1163      contextMock.expects('getVar')
1164          .withExactArgs('--var2')
1165          .returns(new ast.CssLengthNode(200, 'px'))
1166          .once();
1167      const node = new ast.CssRandNode(
1168          new ast.CssVarNode('--var1'),
1169          new ast.CssVarNode('--var2'));
1170      expect(resolvedCss(node)).to.equal('125px');
1171    });
1172
1173    it('should resolve to null with null args', () => {
1174      contextMock.expects('getVar')
1175          .withExactArgs('--var1')
1176          .returns(new ast.CssLengthNode(100, 'px'))
1177          .once();
1178      contextMock.expects('getVar')
1179          .withExactArgs('--var2')
1180          .returns(null)
1181          .once();
1182      const node = new ast.CssRandNode(
1183          new ast.CssVarNode('--var1'),
1184          new ast.CssVarNode('--var2'));
1185      expect(resolvedCss(node)).to.be.null;
1186    });
1187
1188    it('should only allow numerics', () => {
1189      const node = new ast.CssRandNode(
1190          new ast.CssPassthroughNode('A'),
1191          new ast.CssPassthroughNode('B'));
1192      expect(() => {
1193        resolvedCss(node);
1194      }).to.throw(/both numerical/);
1195    });
1196
1197    it('should only allow same-type', () => {
1198      const node = new ast.CssRandNode(
1199          new ast.CssLengthNode(30, 'px'),
1200          new ast.CssTimeNode(20, 's'));
1201      expect(() => {
1202        resolvedCss(node);
1203      }).to.throw(/same type/);
1204    });
1205
1206    it('should normalize units', () => {
1207      contextMock.expects('getCurrentFontSize').returns(1).once();
1208      contextMock.expects('getRootFontSize').returns(2).once();
1209      const node = new ast.CssRandNode(
1210          new ast.CssLengthNode(10, 'em'),
1211          new ast.CssLengthNode(10, 'rem'));
1212      expect(resolvedCss(node)).to.equal('12.5px');
1213    });
1214  });
1215
1216  describe('index', () => {
1217    it('should always consider as non-const', () => {
1218      expect(ast.isVarCss('index()', false)).to.be.true;
1219      expect(ast.isVarCss('index()', normalize)).to.be.true;
1220    });
1221
1222    it('should always be a non-const and no css', () => {
1223      const node = new ast.CssIndexNode();
1224      expect(node.isConst()).to.be.false;
1225      expect(node.isConst(normalize)).to.be.false;
1226      expect(() => node.css()).to.throw(/no css/);
1227    });
1228
1229    it('should resolve a no-arg', () => {
1230      contextMock.expects('getCurrentIndex').withExactArgs().returns(11);
1231      const node = new ast.CssIndexNode();
1232      expect(resolvedCss(node)).to.equal('11');
1233    });
1234
1235    it('should combine with calc', () => {
1236      contextMock.expects('getCurrentIndex').withExactArgs().returns(11);
1237      const node = new ast.CssCalcProductNode(
1238          new ast.CssTimeNode(2, 's'),
1239          new ast.CssIndexNode(),
1240          '*');
1241      expect(node.isConst()).to.be.false;
1242      expect(resolvedCss(node)).to.equal('22s');
1243    });
1244  });
1245
1246  describe('var', () => {
1247    it('should always consider as non-const', () => {
1248      expect(ast.isVarCss('var(--x)')).to.be.true;
1249      expect(ast.isVarCss('var(--x)', normalize)).to.be.true;
1250      expect(ast.isVarCss('var(--x, 10px)', normalize)).to.be.true;
1251      expect(ast.isVarCss('var(--x, 10em)', normalize)).to.be.true;
1252    });
1253
1254    it('should resolve a var node', () => {
1255      contextMock.expects('getVar')
1256          .withExactArgs('--var1')
1257          .returns(new ast.CssPassthroughNode('val1'))
1258          .once();
1259      const node = new ast.CssVarNode('--var1');
1260      expect(node.isConst()).to.be.false;
1261      expect(node.css()).to.equal('var(--var1)');
1262      expect(resolvedCss(node)).to.equal('val1');
1263    });
1264
1265    it('should resolve a var node and normalize', () => {
1266      contextMock.expects('getDimension').returns('w').atLeast(1);
1267      contextMock.expects('getCurrentElementSize')
1268          .returns({width: 110, height: 220}).atLeast(1);
1269      contextMock.expects('getVar')
1270          .withExactArgs('--var1')
1271          .returns(new ast.CssPercentNode(11.5))
1272          .atLeast(1);
1273      const node = new ast.CssVarNode('--var1');
1274      expect(node.isConst()).to.be.false;
1275      expect(node.isConst(normalize)).to.be.false;
1276      expect(node.css()).to.equal('var(--var1)');
1277      expect(resolvedCss(node)).to.equal('11.5%');
1278      expect(resolvedCss(node, normalize)).to.equal('12.65px');
1279    });
1280
1281    it('should resolve a var node with def', () => {
1282      contextMock.expects('getVar')
1283          .withExactArgs('--var1')
1284          .returns(new ast.CssPassthroughNode('val1'))
1285          .once();
1286      const node = new ast.CssVarNode('--var1',
1287          new ast.CssPassthroughNode('10px'));
1288      expect(node.isConst()).to.be.false;
1289      expect(node.css()).to.equal('var(--var1,10px)');
1290      expect(resolvedCss(node)).to.equal('val1');
1291    });
1292
1293    it('should resolve a var node by fallback to def', () => {
1294      contextMock.expects('getVar').returns(null).once();
1295      const node = new ast.CssVarNode('--var1',
1296          new ast.CssPassthroughNode('10px'));
1297      expect(node.isConst()).to.be.false;
1298      expect(node.css()).to.equal('var(--var1,10px)');
1299      expect(resolvedCss(node)).to.equal('10px');
1300    });
1301
1302    it('should resolve a var node with def as var', () => {
1303      contextMock.expects('getVar')
1304          .withExactArgs('--var1')
1305          .returns(null)
1306          .once();
1307      contextMock.expects('getVar')
1308          .withExactArgs('--var2')
1309          .returns(new ast.CssPassthroughNode('val2'))
1310          .once();
1311      const node = new ast.CssVarNode('--var1',
1312          new ast.CssVarNode('--var2'));
1313      expect(node.isConst()).to.be.false;
1314      expect(node.css()).to.equal('var(--var1,var(--var2))');
1315      expect(resolvedCss(node)).to.equal('val2');
1316    });
1317
1318    it('should resolve a var node w/o fallback to def', () => {
1319      contextMock.expects('getVar')
1320          .withExactArgs('--var1')
1321          .returns(null)
1322          .atLeast(1);
1323      const node = new ast.CssVarNode('--var1');
1324      expect(node.css()).to.equal('var(--var1)');
1325      expect(node.isConst()).to.be.false;
1326      expect(node.resolve(context)).to.be.null;
1327      expect(node.calc(context)).to.be.null;
1328      expect(resolvedCss(node)).to.be.null;
1329    });
1330  });
1331
1332  describe('calc', () => {
1333    it('should always consider as non-const', () => {
1334      expect(ast.isVarCss('calc()')).to.be.true;
1335      expect(ast.isVarCss('calc()', normalize)).to.be.true;
1336      expect(ast.isVarCss('calc(10px)', normalize)).to.be.true;
1337      expect(ast.isVarCss('calc(10em)', normalize)).to.be.true;
1338    });
1339
1340    it('should resolve a single-value calc', () => {
1341      const node = new ast.CssCalcNode(new ast.CssLengthNode(10, 'px'));
1342      expect(node.isConst()).to.be.false;
1343      expect(node.css()).to.equal('calc(10px)');
1344      expect(resolvedCss(node)).to.equal('10px');
1345    });
1346
1347    describe('sum', () => {
1348      it('should add two same-unit values', () => {
1349        const node = new ast.CssCalcSumNode(
1350            new ast.CssLengthNode(10, 'px'),
1351            new ast.CssLengthNode(20, 'px'),
1352            '+');
1353        expect(node.isConst()).to.be.false;
1354        expect(node.css()).to.equal('10px + 20px');
1355        expect(resolvedCss(node)).to.equal('30px');
1356      });
1357
1358      it('should add two same-unit values and normalize', () => {
1359        const node = new ast.CssCalcSumNode(
1360            new ast.CssLengthNode(10, 'em'),
1361            new ast.CssLengthNode(20, 'em'),
1362            '+');
1363        expect(node.isConst()).to.be.false;
1364        expect(node.css()).to.equal('10em + 20em');
1365        expect(resolvedCss(node)).to.equal('30em');
1366
1367        contextMock.expects('getCurrentFontSize').returns(10).atLeast(1);
1368        expect(node.isConst(normalize)).to.be.false;
1369        expect(resolvedCss(node, normalize)).to.equal('300px');
1370      });
1371
1372      it('should add two same-unit values - times', () => {
1373        const node = new ast.CssCalcSumNode(
1374            new ast.CssTimeNode(10, 's'),
1375            new ast.CssTimeNode(20, 's'),
1376            '+');
1377        expect(node.isConst()).to.be.false;
1378        expect(node.css()).to.equal('10s + 20s');
1379        expect(resolvedCss(node)).to.equal('30s');
1380      });
1381
1382      it('should subtract two same-unit values', () => {
1383        const node = new ast.CssCalcSumNode(
1384            new ast.CssLengthNode(30, 'px'),
1385            new ast.CssLengthNode(20, 'px'),
1386            '-');
1387        expect(node.isConst()).to.be.false;
1388        expect(node.css()).to.equal('30px - 20px');
1389        expect(resolvedCss(node)).to.equal('10px');
1390      });
1391
1392      it('should resolve both parts', () => {
1393        contextMock.expects('getVar')
1394            .withExactArgs('--var1')
1395            .returns(new ast.CssLengthNode(5, 'px'))
1396            .once();
1397        contextMock.expects('getVar')
1398            .withExactArgs('--var2')
1399            .returns(new ast.CssLengthNode(1, 'px'))
1400            .once();
1401        const node = new ast.CssCalcSumNode(
1402            new ast.CssVarNode('--var1'),
1403            new ast.CssVarNode('--var2'),
1404            '+');
1405        expect(node.isConst()).to.be.false;
1406        expect(node.css()).to.equal('var(--var1) + var(--var2)');
1407        expect(resolvedCss(node)).to.equal('6px');
1408      });
1409
1410      it('should resolve to null with null args', () => {
1411        contextMock.expects('getVar')
1412            .withExactArgs('--var1')
1413            .returns(new ast.CssLengthNode(5, 'px'))
1414            .once();
1415        contextMock.expects('getVar')
1416            .withExactArgs('--var2')
1417            .returns(null)
1418            .once();
1419        const node = new ast.CssCalcSumNode(
1420            new ast.CssVarNode('--var1'),
1421            new ast.CssVarNode('--var2'),
1422            '+');
1423        expect(node.isConst()).to.be.false;
1424        expect(node.css()).to.equal('var(--var1) + var(--var2)');
1425        expect(resolvedCss(node)).to.be.null;
1426      });
1427
1428      it('should only allow numerics', () => {
1429        const node = new ast.CssCalcSumNode(
1430            new ast.CssPassthroughNode('A'),
1431            new ast.CssPassthroughNode('B'),
1432            '+');
1433        expect(() => {
1434          resolvedCss(node);
1435        }).to.throw(/both numerical/);
1436      });
1437
1438      it('should only allow same-type', () => {
1439        const node = new ast.CssCalcSumNode(
1440            new ast.CssLengthNode(30, 'px'),
1441            new ast.CssTimeNode(20, 's'),
1442            '+');
1443        expect(() => {
1444          resolvedCss(node);
1445        }).to.throw(/same type/);
1446      });
1447
1448      it('should normalize units', () => {
1449        contextMock.expects('getCurrentFontSize').returns(1).once();
1450        contextMock.expects('getRootFontSize').returns(2).once();
1451        const node = new ast.CssCalcSumNode(
1452            new ast.CssLengthNode(10, 'em'),
1453            new ast.CssLengthNode(10, 'rem'),
1454            '+');
1455        expect(node.isConst()).to.be.false;
1456        expect(node.css()).to.equal('10em + 10rem');
1457        // 1 * 10px + 2 * 10px = 30px
1458        expect(resolvedCss(node)).to.equal('30px');
1459      });
1460
1461      it('should resolve left as percent', () => {
1462        contextMock.expects('getDimension').returns('w');
1463        contextMock.expects('getCurrentElementSize')
1464            .returns({width: 110, height: 220})
1465            .once();
1466        const node = new ast.CssCalcSumNode(
1467            new ast.CssPercentNode(10),
1468            new ast.CssLengthNode(10, 'px'),
1469            '+');
1470        expect(node.isConst()).to.be.false;
1471        expect(node.css()).to.equal('10% + 10px');
1472        // 10% * 110 + 10px = 11px + 10px = 21px
1473        expect(resolvedCss(node)).to.equal('21px');
1474      });
1475
1476      it('should resolve right as percent', () => {
1477        contextMock.expects('getDimension').returns('h');
1478        contextMock.expects('getCurrentElementSize')
1479            .returns({width: 110, height: 220})
1480            .once();
1481        const node = new ast.CssCalcSumNode(
1482            new ast.CssLengthNode(10, 'px'),
1483            new ast.CssPercentNode(10),
1484            '+');
1485        expect(node.isConst()).to.be.false;
1486        expect(node.css()).to.equal('10px + 10%');
1487        // 10px + 10% * 220 = 10px + 22px = 32px
1488        expect(resolvedCss(node)).to.equal('32px');
1489      });
1490
1491      it('should normalize the non-percent part', () => {
1492        contextMock.expects('getCurrentFontSize').returns(2).once();
1493        contextMock.expects('getDimension').returns('h');
1494        contextMock.expects('getCurrentElementSize')
1495            .returns({width: 110, height: 220})
1496            .once();
1497        const node = new ast.CssCalcSumNode(
1498            new ast.CssLengthNode(10, 'em'),
1499            new ast.CssPercentNode(10),
1500            '+');
1501        expect(node.isConst()).to.be.false;
1502        expect(node.css()).to.equal('10em + 10%');
1503        // 10em * 2px + 10% * 220 = 20px + 22px = 42px
1504        expect(resolvedCss(node)).to.equal('42px');
1505      });
1506
1507      it('should resolve with dimension function', () => {
1508        contextMock.expects('getElementSize')
1509            .returns({width: 111, height: 222})
1510            .twice();
1511        const node = new ast.CssCalcSumNode(
1512            new ast.CssDimSizeNode('w', '.sel'),
1513            new ast.CssDimSizeNode('h', '.sel'),
1514            '+');
1515        // 111px + 222px = 333px
1516        expect(resolvedCss(node)).to.equal('333px');
1517      });
1518    });
1519
1520    describe('product', () => {
1521      it('should only allow numerics', () => {
1522        const node = new ast.CssCalcProductNode(
1523            new ast.CssPassthroughNode('A'),
1524            new ast.CssPassthroughNode('B'),
1525            '*');
1526        expect(() => {
1527          resolvedCss(node);
1528        }).to.throw(/both numerical/);
1529      });
1530
1531      it('should multiply with right number', () => {
1532        const node = new ast.CssCalcProductNode(
1533            new ast.CssLengthNode(10, 'px'),
1534            new ast.CssNumberNode(2),
1535            '*');
1536        expect(node.isConst()).to.be.false;
1537        expect(node.css()).to.equal('10px * 2');
1538        expect(resolvedCss(node)).to.equal('20px');
1539      });
1540
1541      it('should multiply with left number', () => {
1542        const node = new ast.CssCalcProductNode(
1543            new ast.CssNumberNode(2),
1544            new ast.CssLengthNode(10, 'px'),
1545            '*');
1546        expect(node.isConst()).to.be.false;
1547        expect(node.css()).to.equal('2 * 10px');
1548        expect(resolvedCss(node)).to.equal('20px');
1549      });
1550
1551      it('should multiply for non-norm', () => {
1552        const node = new ast.CssCalcProductNode(
1553            new ast.CssLengthNode(10, 'em'),
1554            new ast.CssNumberNode(2),
1555            '*');
1556        expect(node.isConst()).to.be.false;
1557        expect(node.css()).to.equal('10em * 2');
1558        expect(resolvedCss(node)).to.equal('20em');
1559      });
1560
1561      it('should multiply for time', () => {
1562        const node = new ast.CssCalcProductNode(
1563            new ast.CssTimeNode(10, 's'),
1564            new ast.CssNumberNode(2),
1565            '*');
1566        expect(node.isConst()).to.be.false;
1567        expect(node.css()).to.equal('10s * 2');
1568        expect(resolvedCss(node)).to.equal('20s');
1569      });
1570
1571      it('should require at least one number', () => {
1572        const node = new ast.CssCalcProductNode(
1573            new ast.CssLengthNode(10, 'px'),
1574            new ast.CssLengthNode(20, 'px'),
1575            '*');
1576        expect(() => {
1577          resolvedCss(node);
1578        }).to.throw(/one of sides in multiplication must be a number/);
1579      });
1580
1581      it('should divide with right number', () => {
1582        const node = new ast.CssCalcProductNode(
1583            new ast.CssLengthNode(10, 'px'),
1584            new ast.CssNumberNode(2),
1585            '/');
1586        expect(node.isConst()).to.be.false;
1587        expect(node.css()).to.equal('10px / 2');
1588        expect(resolvedCss(node)).to.equal('5px');
1589      });
1590
1591      it('should divide for non-norm', () => {
1592        const node = new ast.CssCalcProductNode(
1593            new ast.CssLengthNode(10, 'em'),
1594            new ast.CssNumberNode(2),
1595            '/');
1596        expect(node.isConst()).to.be.false;
1597        expect(node.css()).to.equal('10em / 2');
1598        expect(resolvedCss(node)).to.equal('5em');
1599      });
1600
1601      it('should divide for time', () => {
1602        const node = new ast.CssCalcProductNode(
1603            new ast.CssTimeNode(10, 's'),
1604            new ast.CssNumberNode(2),
1605            '/');
1606        expect(node.isConst()).to.be.false;
1607        expect(node.css()).to.equal('10s / 2');
1608        expect(resolvedCss(node)).to.equal('5s');
1609      });
1610
1611      it('should only allow number denominator', () => {
1612        const node = new ast.CssCalcProductNode(
1613            new ast.CssTimeNode(10, 's'),
1614            new ast.CssTimeNode(2, 's'),
1615            '/');
1616        expect(() => {
1617          resolvedCss(node);
1618        }).to.throw(/denominator must be a number/);
1619      });
1620
1621      it('should resolve divide-by-zero as null', () => {
1622        const node = new ast.CssCalcProductNode(
1623            new ast.CssLengthNode(10, 'px'),
1624            new ast.CssNumberNode(0),
1625            '/');
1626        expect(node.isConst()).to.be.false;
1627        expect(node.css()).to.equal('10px / 0');
1628        expect(resolvedCss(node)).to.be.null;
1629      });
1630
1631      it('should resolve both parts', () => {
1632        contextMock.expects('getVar')
1633            .withExactArgs('--var1')
1634            .returns(new ast.CssLengthNode(10, 'px'))
1635            .once();
1636        contextMock.expects('getVar')
1637            .withExactArgs('--var2')
1638            .returns(new ast.CssNumberNode(2))
1639            .once();
1640        const node = new ast.CssCalcProductNode(
1641            new ast.CssVarNode('--var1'),
1642            new ast.CssVarNode('--var2'),
1643            '*');
1644        expect(node.isConst()).to.be.false;
1645        expect(node.css()).to.equal('var(--var1) * var(--var2)');
1646        expect(resolvedCss(node)).to.equal('20px');
1647      });
1648
1649      it('should resolve to null for null args', () => {
1650        contextMock.expects('getVar')
1651            .withExactArgs('--var1')
1652            .returns(new ast.CssLengthNode(10, 'px'))
1653            .once();
1654        contextMock.expects('getVar')
1655            .withExactArgs('--var2')
1656            .returns(null)
1657            .once();
1658        const node = new ast.CssCalcProductNode(
1659            new ast.CssVarNode('--var1'),
1660            new ast.CssVarNode('--var2'),
1661            '*');
1662        expect(node.isConst()).to.be.false;
1663        expect(node.css()).to.equal('var(--var1) * var(--var2)');
1664        expect(resolvedCss(node)).to.be.null;
1665      });
1666    });
1667  });
1668});
1669
Full Screen

test-css-expr-parser.js

Source: test-css-expr-parser.js Github

copy
1/**
2 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS-IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import * as ast from '../parsers/css-expr-ast';
18import {parseCss} from '../parsers/css-expr';
19
20describes.sandboxed('CSS parse', {}, () => {
21  /**
22   * @param {string} cssString
23   * @return {string}
24   */
25  function parsePseudo(cssString) {
26    const node = parseCss(cssString);
27    if (node == null) {
28      return null;
29    }
30    return pseudo(node);
31  }
32
33  /**
34   * @param {!CssNode} n
35   * @return {string}
36   */
37  function pseudo(n) {
38    if (n instanceof ast.CssPassthroughNode) {
39      return n.css_;
40    }
41    if (n instanceof ast.CssUrlNode) {
42      return `URL<${n.url_}>`;
43    }
44    if (n instanceof ast.CssConcatNode) {
45      return `CON<${pseudoArray(n.array_, n.dimensions_, ' ')}>`;
46    }
47    if (n instanceof ast.CssNumericNode) {
48      return (
49        `${n.type_}<${n.num_}` +
50        `${n.units_ && n.units_ != '%' ? ' ' + n.units_.toUpperCase() : ''}>`
51      );
52    }
53    if (n instanceof ast.CssTranslateNode) {
54      return (
55        'TRANSLATE' +
56        `${n.suffix_ ? '-' + n.suffix_.toUpperCase() : ''}` +
57        `<${pseudoArray(n.args_, n.dimensions_)}>`
58      );
59    }
60    if (n instanceof ast.CssRectNode) {
61      return (
62        `RECT<${n.field_}` +
63        `, ${n.selector_ ? '"' + n.selector_ + '"' : null}` +
64        `, ${n.selectionMethod_}>`
65      );
66    }
67    if (n instanceof ast.CssNumConvertNode) {
68      return `NUMC<${n.value_ ? pseudo(n.value_) : null}>`;
69    }
70    if (n instanceof ast.CssRandNode) {
71      return (
72        `RAND<${n.left_ ? pseudo(n.left_) : null}` +
73        `, ${n.right_ ? pseudo(n.right_) : null}>`
74      );
75    }
76    if (n instanceof ast.CssIndexNode) {
77      return 'INDEX<>';
78    }
79    if (n instanceof ast.CssLengthFuncNode) {
80      return 'LENGTH<>';
81    }
82    if (n instanceof ast.CssVarNode) {
83      return `VAR<${n.varName_}${n.def_ ? ', ' + pseudo(n.def_) : ''}>`;
84    }
85    if (n instanceof ast.CssCalcNode) {
86      return `CALC<${pseudo(n.expr_)}>`;
87    }
88    if (n instanceof ast.CssCalcSumNode) {
89      return (
90        `${n.op_ == '+' ? 'ADD' : 'SUB'}` +
91        `<${pseudo(n.left_)}, ${pseudo(n.right_)}>`
92      );
93    }
94    if (n instanceof ast.CssCalcProductNode) {
95      return (
96        `${n.op_ == '*' ? 'MUL' : 'DIV'}` +
97        `<${pseudo(n.left_)}, ${pseudo(n.right_)}>`
98      );
99    }
100    if (n instanceof ast.CssFuncNode) {
101      return `${n.name_.toUpperCase()}<${pseudoArray(n.args_, n.dimensions_)}>`;
102    }
103    throw new Error('unknown node: ' + n);
104  }
105
106  /**
107   * @param {!Array<!CssNode>} array
108   * @param {?Array<string>} dims
109   * @param {string=} delim
110   * @return {string}
111   */
112  function pseudoArray(array, dims = null, delim = ', ') {
113    if (!array || array.length == 0) {
114      return '';
115    }
116    return array
117      .map((n, i) => {
118        const v = pseudo(n);
119        if (dims && i < dims.length) {
120          return `${v}|${dims[i]}`;
121        }
122        return v;
123      })
124      .join(delim);
125  }
126
127  it('should parse empty string as null', () => {
128    expect(parsePseudo('')).to.be.null;
129  });
130
131  it('should parse string', () => {
132    expect(parsePseudo('"abc"')).to.equal('"abc"');
133    expect(parsePseudo("'abc'")).to.equal("'abc'");
134  });
135
136  it('should parse "none" ident', () => {
137    expect(parsePseudo('none')).to.equal('none');
138  });
139
140  it('should parse ident', () => {
141    expect(parsePseudo('abc')).to.equal('abc');
142    expect(parsePseudo('-abc')).to.equal('-abc');
143    expect(parsePseudo('-abc1')).to.equal('-abc1');
144    expect(parsePseudo('-abc1a')).to.equal('-abc1a');
145  });
146
147  it('should parse number', () => {
148    expect(parsePseudo('0')).to.equal('NUM<0>');
149    expect(parsePseudo('1')).to.equal('NUM<1>');
150    expect(parsePseudo('10')).to.equal('NUM<10>');
151    expect(parsePseudo('0.5')).to.equal('NUM<0.5>');
152    expect(parsePseudo('.5')).to.equal('NUM<0.5>');
153    expect(parsePseudo('1.5')).to.equal('NUM<1.5>');
154    expect(parsePseudo('-0.5')).to.equal('NUM<-0.5>');
155    expect(parsePseudo('-.5')).to.equal('NUM<-0.5>');
156    expect(parsePseudo('+.5')).to.equal('NUM<0.5>');
157    expect(parsePseudo('+1.5')).to.equal('NUM<1.5>');
158    expect(parsePseudo('1e2')).to.equal('NUM<100>');
159    expect(parsePseudo('.1e2')).to.equal('NUM<10>');
160    expect(parsePseudo('1e+2')).to.equal('NUM<100>');
161    expect(parsePseudo('1e-2')).to.equal('NUM<0.01>');
162  });
163
164  it('should parse percent', () => {
165    expect(parsePseudo('0%')).to.equal('PRC<0>');
166    expect(parsePseudo('1%')).to.equal('PRC<1>');
167    expect(parsePseudo('0.5%')).to.equal('PRC<0.5>');
168    expect(parsePseudo('.5%')).to.equal('PRC<0.5>');
169    expect(parsePseudo('1e2%')).to.equal('PRC<100>');
170  });
171
172  it('should parse length', () => {
173    expect(parsePseudo('100px')).to.equal('LEN<100 PX>');
174    expect(parsePseudo('-100px')).to.equal('LEN<-100 PX>');
175    expect(parsePseudo('+100px')).to.equal('LEN<100 PX>');
176    expect(parsePseudo('100.5px')).to.equal('LEN<100.5 PX>');
177    expect(parsePseudo('0.5px')).to.equal('LEN<0.5 PX>');
178    expect(parsePseudo('.5px')).to.equal('LEN<0.5 PX>');
179
180    // Non-px units:
181    expect(parsePseudo('100em')).to.equal('LEN<100 EM>');
182    expect(parsePseudo('100rem')).to.equal('LEN<100 REM>');
183    expect(parsePseudo('100vh')).to.equal('LEN<100 VH>');
184    expect(parsePseudo('100vw')).to.equal('LEN<100 VW>');
185    expect(parsePseudo('100vmin')).to.equal('LEN<100 VMIN>');
186    expect(parsePseudo('100vmax')).to.equal('LEN<100 VMAX>');
187    expect(parsePseudo('100cm')).to.equal('LEN<100 CM>');
188    expect(parsePseudo('100mm')).to.equal('LEN<100 MM>');
189    expect(parsePseudo('100q')).to.equal('LEN<100 Q>');
190    expect(parsePseudo('100in')).to.equal('LEN<100 IN>');
191    expect(parsePseudo('100pc')).to.equal('LEN<100 PC>');
192    expect(parsePseudo('100pt')).to.equal('LEN<100 PT>');
193  });
194
195  it('should parse angle', () => {
196    expect(parsePseudo('10deg')).to.equal('ANG<10 DEG>');
197    expect(parsePseudo('-10deg')).to.equal('ANG<-10 DEG>');
198    expect(parsePseudo('+10deg')).to.equal('ANG<10 DEG>');
199    expect(parsePseudo('1.5deg')).to.equal('ANG<1.5 DEG>');
200    expect(parsePseudo('0.5deg')).to.equal('ANG<0.5 DEG>');
201    expect(parsePseudo('.5deg')).to.equal('ANG<0.5 DEG>');
202
203    // Non-deg units:
204    expect(parsePseudo('10rad')).to.equal('ANG<10 RAD>');
205    expect(parsePseudo('10grad')).to.equal('ANG<10 GRAD>');
206  });
207
208  it('should parse time', () => {
209    expect(parsePseudo('10ms')).to.equal('TME<10 MS>');
210    expect(parsePseudo('10s')).to.equal('TME<10 S>');
211  });
212
213  it('should parse url', () => {
214    expect(parsePseudo('url("https://acme.org/abc")')).to.equal(
215      'URL<https://acme.org/abc>'
216    );
217    expect(parsePseudo("url('https://acme.org/abc')")).to.equal(
218      'URL<https://acme.org/abc>'
219    );
220    expect(parsePseudo("url('data:abc')")).to.equal('URL<data:abc>');
221    // HTTP and relative are allowed at this stage.
222    expect(parsePseudo("url('http://acme.org/abc')")).to.equal(
223      'URL<http://acme.org/abc>'
224    );
225    expect(parsePseudo("url('/relative')")).to.equal('URL</relative>');
226  });
227
228  it('should parse hexcolor', () => {
229    expect(parsePseudo('#123456')).to.equal('#123456');
230    expect(parsePseudo('#AB3456')).to.equal('#AB3456');
231    expect(parsePseudo('#ABCDEF')).to.equal('#ABCDEF');
232    // Alpha-format:
233    expect(parsePseudo('#ABCDEF01')).to.equal('#ABCDEF01');
234    // Abbrv:
235    expect(parsePseudo('#FFF')).to.equal('#FFF');
236    expect(parsePseudo('#fff')).to.equal('#fff');
237  });
238
239  it('should parse a function', () => {
240    expect(parsePseudo('unknown()')).to.equal('UNKNOWN<>');
241    expect(parsePseudo('unknown( )')).to.equal('UNKNOWN<>');
242    expect(parsePseudo('rgb(10, 20, 30)')).to.equal(
243      'RGB<NUM<10>, NUM<20>, NUM<30>>'
244    );
245    expect(parsePseudo('translate(100px, 200px)')).to.equal(
246      'TRANSLATE<LEN<100 PX>|w, LEN<200 PX>|h>'
247    );
248  });
249
250  it('should parse a translate()', () => {
251    expect(parsePseudo('translate(100px)')).to.equal(
252      'TRANSLATE<LEN<100 PX>|w>'
253    );
254    expect(parsePseudo('translate(100px, 200px)')).to.equal(
255      'TRANSLATE<LEN<100 PX>|w, LEN<200 PX>|h>'
256    );
257    expect(parsePseudo('translateX(100px)')).to.equal(
258      'TRANSLATE-X<LEN<100 PX>|w>'
259    );
260    expect(parsePseudo('TRANSLATEX(100px)')).to.equal(
261      'TRANSLATE-X<LEN<100 PX>|w>'
262    );
263    expect(parsePseudo('translatex(100px)')).to.equal(
264      'TRANSLATE-X<LEN<100 PX>|w>'
265    );
266    expect(parsePseudo('translateY(100px)')).to.equal(
267      'TRANSLATE-Y<LEN<100 PX>|h>'
268    );
269    expect(parsePseudo('translateZ(100px)')).to.equal(
270      'TRANSLATE-Z<LEN<100 PX>|z>'
271    );
272    expect(parsePseudo('translate3d(1px, 2px, 3px)')).to.equal(
273      'TRANSLATE-3D<LEN<1 PX>|w, LEN<2 PX>|h, LEN<3 PX>|z>'
274    );
275  });
276
277  it('should parse a concat of functions', () => {
278    expect(parsePseudo('translateX(100px) rotate(45deg)')).to.equal(
279      'CON<TRANSLATE-X<LEN<100 PX>|w> ROTATE<ANG<45 DEG>>>'
280    );
281  });
282
283  it('should allow two-way concatenation', () => {
284    // This is currently doesn't happen in parse, but by API possible with
285    // minor changes to parsing order. Thus it's re-tested separately here.
286    expect(
287      pseudo(
288        ast.CssConcatNode.concat(
289          new ast.CssConcatNode([new ast.CssPassthroughNode('A')]),
290          new ast.CssConcatNode([new ast.CssPassthroughNode('B')])
291        )
292      )
293    ).to.equal('CON<A B>');
294    expect(
295      pseudo(
296        ast.CssConcatNode.concat(
297          new ast.CssPassthroughNode('A'),
298          new ast.CssConcatNode([new ast.CssPassthroughNode('B')])
299        )
300      )
301    ).to.equal('CON<A B>');
302  });
303
304  it('should parse a rect function', () => {
305    // Current.
306    expect(parsePseudo('width()')).to.equal('RECT<w, null, null>');
307    expect(parsePseudo('height()')).to.equal('RECT<h, null, null>');
308    expect(parsePseudo('x()')).to.equal('RECT<x, null, null>');
309    expect(parsePseudo('y()')).to.equal('RECT<y, null, null>');
310
311    // Query.
312    expect(parsePseudo('width(".sel")')).to.equal('RECT<w, ".sel", null>');
313    expect(parsePseudo('WIDTH(".sel > div")')).to.equal(
314      'RECT<w, ".sel > div", null>'
315    );
316    expect(parsePseudo('height(".sel")')).to.equal('RECT<h, ".sel", null>');
317    expect(parsePseudo('x(".sel")')).to.equal('RECT<x, ".sel", null>');
318    expect(parsePseudo('x(".sel > div")')).to.equal(
319      'RECT<x, ".sel > div", null>'
320    );
321    expect(parsePseudo('y(".sel")')).to.equal('RECT<y, ".sel", null>');
322
323    // Closest.
324    expect(parsePseudo('width(closest(".sel"))')).to.equal(
325      'RECT<w, ".sel", closest>'
326    );
327    expect(parsePseudo('height(closest(".sel"))')).to.equal(
328      'RECT<h, ".sel", closest>'
329    );
330    expect(parsePseudo('x(closest(".sel"))')).to.equal(
331      'RECT<x, ".sel", closest>'
332    );
333    expect(parsePseudo('y(closest(".sel"))')).to.equal(
334      'RECT<y, ".sel", closest>'
335    );
336  });
337
338  it('should parse a num-convert function', () => {
339    expect(parsePseudo('num(10)')).to.equal('NUMC<NUM<10>>');
340    expect(parsePseudo('num(10px)')).to.equal('NUMC<LEN<10 PX>>');
341    expect(parsePseudo('num(10em)')).to.equal('NUMC<LEN<10 EM>>');
342    expect(parsePseudo('num(10s)')).to.equal('NUMC<TME<10 S>>');
343    expect(parsePseudo('num(10rad)')).to.equal('NUMC<ANG<10 RAD>>');
344    expect(parsePseudo('num(10%)')).to.equal('NUMC<PRC<10>>');
345    expect(parsePseudo('num(var(--x))')).to.equal('NUMC<VAR<--x>>');
346  });
347
348  it('should parse a rand function', () => {
349    expect(parsePseudo('rand()')).to.equal('RAND<null, null>');
350    expect(parsePseudo('rand(10, 20)')).to.equal('RAND<NUM<10>, NUM<20>>');
351    expect(parsePseudo('rand(10px, 20px)')).to.equal(
352      'RAND<LEN<10 PX>, LEN<20 PX>>'
353    );
354    expect(parsePseudo('rand(10em, 20em)')).to.equal(
355      'RAND<LEN<10 EM>, LEN<20 EM>>'
356    );
357    expect(parsePseudo('rand(10px, 20em)')).to.equal(
358      'RAND<LEN<10 PX>, LEN<20 EM>>'
359    );
360    expect(parsePseudo('rand(10s, 20s)')).to.equal(
361      'RAND<TME<10 S>, TME<20 S>>'
362    );
363    expect(parsePseudo('rand(10rad, 20rad)')).to.equal(
364      'RAND<ANG<10 RAD>, ANG<20 RAD>>'
365    );
366    expect(parsePseudo('rand(10%, 20%)')).to.equal('RAND<PRC<10>, PRC<20>>');
367    expect(parsePseudo('rand(var(--x), var(--y))')).to.equal(
368      'RAND<VAR<--x>, VAR<--y>>'
369    );
370    expect(parsePseudo('rand(10px, var(--y))')).to.equal(
371      'RAND<LEN<10 PX>, VAR<--y>>'
372    );
373  });
374
375  it('should parse an index function', () => {
376    expect(parsePseudo('index()')).to.equal('INDEX<>');
377    expect(parsePseudo('INDEX()')).to.equal('INDEX<>');
378  });
379
380  it('should parse a length function', () => {
381    expect(parsePseudo('length()')).to.equal('LENGTH<>');
382    expect(parsePseudo('LENGTH()')).to.equal('LENGTH<>');
383  });
384
385  it('should parse clip-path:inset', () => {
386    // No radius.
387    // inset(<all>)
388    expect(parsePseudo('inset(10%)')).to.equal(
389      'INSET<CON<PRC<10>|h PRC<10>|w>>'
390    );
391    // inset(<vert horiz>)
392    expect(parsePseudo('inset(10% 10%)')).to.equal(
393      'INSET<CON<PRC<10>|h PRC<10>|w>>'
394    );
395    // inset(<top horiz bottom>)
396    expect(parsePseudo('inset(10% 20% 30%)')).to.equal(
397      'INSET<CON<PRC<10>|h PRC<20>|w PRC<30>|h>>'
398    );
399    // inset(<top right bottom left>)
400    expect(parsePseudo('inset(10% 20% 30% 40%)')).to.equal(
401      'INSET<CON<PRC<10>|h PRC<20>|w PRC<30>|h PRC<40>|w>>'
402    );
403
404    // With radius.
405    // inset(<all> round 50%)
406    expect(parsePseudo('inset(10% round 50% 20%)')).to.equal(
407      'INSET<CON<CON<PRC<10>|h PRC<10>|w> round CON<PRC<50> PRC<20>>>>'
408    );
409
410    // inset(<all> round 10px / 20px)
411    expect(parsePseudo('inset(10% round 10px / 20px)')).to.equal(
412      'INSET<CON<CON<PRC<10>|h PRC<10>|w> round CON<CON<LEN<10 PX>> / CON<LEN<20 PX>>>>>'
413    );
414
415    // Do not allow empty box.
416    expect(() => {
417      parsePseudo('inset()');
418    }).to.throw(/Parse error/);
419
420    // Do not allow more than 4 components in a box.
421    expect(() => {
422      parsePseudo('inset(1px 2px 3px 4px 5px)');
423    }).to.throw(/must have between 1 and 4 components/);
424
425    // Do not allow empty radius.
426    expect(() => {
427      parsePseudo('inset(10% round)');
428    }).to.throw(/Parse error/);
429
430    // Do not allow radius with more than 4 components.
431    expect(() => {
432      parsePseudo('inset(10% round 1px 2px 3px 4px 5px)');
433    }).to.throw(/must have between 1 and 4 components/);
434  });
435
436  it('should parse clip-path:circle', () => {
437    // No position.
438    // circle()
439    expect(parsePseudo('circle()')).to.equal('CIRCLE<>');
440    // circle(50%)
441    expect(parsePseudo('circle(50%)')).to.equal('CIRCLE<PRC<50>>');
442    // circle(50px)
443    expect(parsePseudo('circle(50px)')).to.equal('CIRCLE<LEN<50 PX>>');
444    // circle(farthest-side)
445    expect(parsePseudo('circle(farthest-side)')).to.equal(
446      'CIRCLE<farthest-side>'
447    );
448
449    // With radius.
450    // circle(at 10% 20%)
451    expect(parsePseudo('circle(at 10% 20%)')).to.equal(
452      'CIRCLE<CON<at CON<PRC<10>|w PRC<20>|h>>>'
453    );
454    // circle(50% at 10% 20%)
455    expect(parsePseudo('circle(50% at 10% 20%)')).to.equal(
456      'CIRCLE<CON<PRC<50> at CON<PRC<10>|w PRC<20>|h>>>'
457    );
458    // circle(50% at left top)
459    expect(parsePseudo('circle(50% at left top)')).to.equal(
460      'CIRCLE<CON<PRC<50> at CON<left|w top|h>>>'
461    );
462    // circle(50% at left 20%)
463    expect(parsePseudo('circle(50% at left 20%)')).to.equal(
464      'CIRCLE<CON<PRC<50> at CON<left|w PRC<20>|h>>>'
465    );
466    // circle(50% at 10% top)
467    expect(parsePseudo('circle(50% at 10% top)')).to.equal(
468      'CIRCLE<CON<PRC<50> at CON<PRC<10>|w top|h>>>'
469    );
470    // circle(50% at left 10% top 20%)
471    expect(parsePseudo('circle(50% at left 10% top 20%)')).to.equal(
472      'CIRCLE<CON<PRC<50> at CON<left|w PRC<10>|w top|h PRC<20>|h>>>'
473    );
474  });
475
476  it('should parse clip-path:ellipse', () => {
477    // No position.
478    // ellipse()
479    expect(parsePseudo('ellipse()')).to.equal('ELLIPSE<>');
480    // ellipse(50%)
481    expect(parsePseudo('ellipse(20% 30%)')).to.equal(
482      'ELLIPSE<CON<PRC<20> PRC<30>>>'
483    );
484    // ellipse(50px)
485    expect(parsePseudo('ellipse(20px 30px)')).to.equal(
486      'ELLIPSE<CON<LEN<20 PX> LEN<30 PX>>>'
487    );
488    // ellipse(farthest-side)
489    expect(parsePseudo('ellipse(closest-side farthest-side)')).to.equal(
490      'ELLIPSE<CON<closest-side farthest-side>>'
491    );
492
493    // With radius.
494    // ellipse(at 10% 20%)
495    expect(parsePseudo('ellipse(at 10% 20%)')).to.equal(
496      'ELLIPSE<CON<at CON<PRC<10>|w PRC<20>|h>>>'
497    );
498    // ellipse(30% 40% at 10% 20%)
499    expect(parsePseudo('ellipse(30% 40% at 10% 20%)')).to.equal(
500      'ELLIPSE<CON<CON<PRC<30> PRC<40>> at CON<PRC<10>|w PRC<20>|h>>>'
501    );
502    // ellipse(30% 40% at left top)
503    expect(parsePseudo('ellipse(30% 40% at left top)')).to.equal(
504      'ELLIPSE<CON<CON<PRC<30> PRC<40>> at CON<left|w top|h>>>'
505    );
506    // ellipse(30% 40% at left 20%)
507    expect(parsePseudo('ellipse(30% 40% at left 20%)')).to.equal(
508      'ELLIPSE<CON<CON<PRC<30> PRC<40>> at CON<left|w PRC<20>|h>>>'
509    );
510    // ellipse(30% 40% at 10% top)
511    expect(parsePseudo('ellipse(30% 40% at 10% top)')).to.equal(
512      'ELLIPSE<CON<CON<PRC<30> PRC<40>> at CON<PRC<10>|w top|h>>>'
513    );
514    // ellipse(30% 40% at left 10% top 20%)
515    expect(parsePseudo('ellipse(30% 40% at left 10% top 20%)')).to.equal(
516      'ELLIPSE<CON<CON<PRC<30> PRC<40>> at CON<left|w PRC<10>|w top|h PRC<20>|h>>>'
517    );
518    // ellipse(30% 40% at top 10% left 20%)
519    expect(parsePseudo('ellipse(30% 40% at top 10% left 20%)')).to.equal(
520      'ELLIPSE<CON<CON<PRC<30> PRC<40>> at CON<top|h PRC<10>|h left|w PRC<20>|w>>>'
521    );
522  });
523
524  it('should parse clip-path:polygon', () => {
525    // 1 tuple.
526    expect(parsePseudo('polygon(10px 20px)')).to.equal(
527      'POLYGON<CON<LEN<10 PX>|w LEN<20 PX>|h>>'
528    );
529    expect(parsePseudo('polygon(10% 20%)')).to.equal(
530      'POLYGON<CON<PRC<10>|w PRC<20>|h>>'
531    );
532
533    // 2 tuples.
534    expect(parsePseudo('polygon(10px 20px, 30px 40px)')).to.equal(
535      'POLYGON<CON<LEN<10 PX>|w LEN<20 PX>|h>, CON<LEN<30 PX>|w LEN<40 PX>|h>>'
536    );
537    expect(parsePseudo('polygon(10% 20%, 30% 40%)')).to.equal(
538      'POLYGON<CON<PRC<10>|w PRC<20>|h>, CON<PRC<30>|w PRC<40>|h>>'
539    );
540  });
541
542  it('should parse a var()', () => {
543    expect(parsePseudo('var(--abc)')).to.equal('VAR<--abc>');
544    expect(parsePseudo('var(--abc1)')).to.equal('VAR<--abc1>');
545    expect(parsePseudo('var(--abc-d)')).to.equal('VAR<--abc-d>');
546    expect(parsePseudo('VAR(--abc)')).to.equal('VAR<--abc>');
547    expect(parsePseudo('var(--ABC)')).to.equal('VAR<--ABC>');
548    expect(parsePseudo('var(--abc, 100px)')).to.equal(
549      'VAR<--abc, LEN<100 PX>>'
550    );
551    expect(parsePseudo('var(--abc, var(--def))')).to.equal(
552      'VAR<--abc, VAR<--def>>'
553    );
554    expect(parsePseudo('var(--abc, var(--def, 200px))')).to.equal(
555      'VAR<--abc, VAR<--def, LEN<200 PX>>>'
556    );
557    expect(parsePseudo('var(--abc, rgb(1, 2, 3))')).to.equal(
558      'VAR<--abc, RGB<NUM<1>, NUM<2>, NUM<3>>>'
559    );
560  });
561
562  it('should parse a calc()', () => {
563    expect(parsePseudo('calc(100px)')).to.equal('CALC<LEN<100 PX>>');
564    expect(parsePseudo('calc((100px))')).to.equal('CALC<LEN<100 PX>>');
565
566    // calc_sum
567    expect(parsePseudo('calc(100px + 200px)')).to.equal(
568      'CALC<ADD<LEN<100 PX>, LEN<200 PX>>>'
569    );
570    expect(parsePseudo('calc(100px - 200px)')).to.equal(
571      'CALC<SUB<LEN<100 PX>, LEN<200 PX>>>'
572    );
573    expect(parsePseudo('calc((100px + 200px))')).to.equal(
574      'CALC<ADD<LEN<100 PX>, LEN<200 PX>>>'
575    );
576
577    // calc_product
578    expect(parsePseudo('calc(100px * 2)')).to.equal(
579      'CALC<MUL<LEN<100 PX>, NUM<2>>>'
580    );
581    expect(parsePseudo('calc(2 * 100px)')).to.equal(
582      'CALC<MUL<NUM<2>, LEN<100 PX>>>'
583    );
584    expect(parsePseudo('calc(100px / 2)')).to.equal(
585      'CALC<DIV<LEN<100 PX>, NUM<2>>>'
586    );
587    expect(parsePseudo('calc((100px * 2))')).to.equal(
588      'CALC<MUL<LEN<100 PX>, NUM<2>>>'
589    );
590
591    // precedence
592    expect(parsePseudo('calc(100px + 200px + 300px)')).to.equal(
593      'CALC<ADD<ADD<LEN<100 PX>, LEN<200 PX>>, LEN<300 PX>>>'
594    );
595    expect(parsePseudo('calc(100px * 2 * 3)')).to.equal(
596      'CALC<MUL<MUL<LEN<100 PX>, NUM<2>>, NUM<3>>>'
597    );
598    expect(parsePseudo('calc(100px * 2 / 3)')).to.equal(
599      'CALC<DIV<MUL<LEN<100 PX>, NUM<2>>, NUM<3>>>'
600    );
601    expect(parsePseudo('calc(100px + 200px * 0.5)')).to.equal(
602      'CALC<ADD<LEN<100 PX>, MUL<LEN<200 PX>, NUM<0.5>>>>'
603    );
604    expect(parsePseudo('calc(100px - 200px / 0.5)')).to.equal(
605      'CALC<SUB<LEN<100 PX>, DIV<LEN<200 PX>, NUM<0.5>>>>'
606    );
607    expect(parsePseudo('calc((100px + 200px) * 0.5)')).to.equal(
608      'CALC<MUL<ADD<LEN<100 PX>, LEN<200 PX>>, NUM<0.5>>>'
609    );
610    expect(parsePseudo('calc((100px - 200px) / 0.5)')).to.equal(
611      'CALC<DIV<SUB<LEN<100 PX>, LEN<200 PX>>, NUM<0.5>>>'
612    );
613    expect(parsePseudo('calc(100px * 0.5 + 200px)')).to.equal(
614      'CALC<ADD<MUL<LEN<100 PX>, NUM<0.5>>, LEN<200 PX>>>'
615    );
616    expect(parsePseudo('calc(100px / 0.5 - 200px)')).to.equal(
617      'CALC<SUB<DIV<LEN<100 PX>, NUM<0.5>>, LEN<200 PX>>>'
618    );
619    expect(parsePseudo('calc(0.5 * (100px + 200px))')).to.equal(
620      'CALC<MUL<NUM<0.5>, ADD<LEN<100 PX>, LEN<200 PX>>>>'
621    );
622
623    // func
624    expect(parsePseudo('calc(var(--abc, 100px) + 200px)')).to.equal(
625      'CALC<ADD<VAR<--abc, LEN<100 PX>>, LEN<200 PX>>>'
626    );
627  });
628
629  it('should parse a min()/max()', () => {
630    expect(parsePseudo('min(100px)')).to.equal('MIN<LEN<100 PX>>');
631    expect(parsePseudo('max(100px)')).to.equal('MAX<LEN<100 PX>>');
632
633    // 2+ components.
634    expect(parsePseudo('min(100px, 200px, 300px)')).to.equal(
635      'MIN<LEN<100 PX>, LEN<200 PX>, LEN<300 PX>>'
636    );
637    expect(parsePseudo('max(100px, 200px, 300px)')).to.equal(
638      'MAX<LEN<100 PX>, LEN<200 PX>, LEN<300 PX>>'
639    );
640
641    // With calc_sum.
642    expect(parsePseudo('min(100px + 200px, 100px + 300px)')).to.equal(
643      'MIN<ADD<LEN<100 PX>, LEN<200 PX>>, ADD<LEN<100 PX>, LEN<300 PX>>>'
644    );
645
646    // With calc_product.
647    expect(parsePseudo('min(100px * 2, 100px / 2)')).to.equal(
648      'MIN<MUL<LEN<100 PX>, NUM<2>>, DIV<LEN<100 PX>, NUM<2>>>'
649    );
650
651    // With precedence.
652    expect(parsePseudo('min(100px + 200px + 300px, 250px)')).to.equal(
653      'MIN<ADD<ADD<LEN<100 PX>, LEN<200 PX>>, LEN<300 PX>>, LEN<250 PX>>'
654    );
655    expect(parsePseudo('min(100px * 2 * 3, 250px)')).to.equal(
656      'MIN<MUL<MUL<LEN<100 PX>, NUM<2>>, NUM<3>>, LEN<250 PX>>'
657    );
658    expect(parsePseudo('max(100px * 2 / 3, 250px)')).to.equal(
659      'MAX<DIV<MUL<LEN<100 PX>, NUM<2>>, NUM<3>>, LEN<250 PX>>'
660    );
661    expect(parsePseudo('min(100px + 200px * 0.5, 250px)')).to.equal(
662      'MIN<ADD<LEN<100 PX>, MUL<LEN<200 PX>, NUM<0.5>>>, LEN<250 PX>>'
663    );
664    expect(parsePseudo('max((100px + 200px) * 0.5, 250px)')).to.equal(
665      'MAX<MUL<ADD<LEN<100 PX>, LEN<200 PX>>, NUM<0.5>>, LEN<250 PX>>'
666    );
667
668    // With func.
669    expect(parsePseudo('min(var(--abc, 100px) + 200px, 250px)')).to.equal(
670      'MIN<ADD<VAR<--abc, LEN<100 PX>>, LEN<200 PX>>, LEN<250 PX>>'
671    );
672
673    // With calc.
674    expect(parsePseudo('calc(0.5 * max(100px, 200px))')).to.equal(
675      'CALC<MUL<NUM<0.5>, MAX<LEN<100 PX>, LEN<200 PX>>>>'
676    );
677  });
678
679  it('should parse a clamp()', () => {
680    expect(parsePseudo('clamp(100px, 200px, 300px)')).to.equal(
681      'CLAMP<LEN<100 PX>, LEN<200 PX>, LEN<300 PX>>'
682    );
683
684    // With calc_sum.
685    expect(
686      parsePseudo('clamp(100px + 1px, 100px + 2px, 100px + 3px)')
687    ).to.equal(
688      'CLAMP<ADD<LEN<100 PX>, LEN<1 PX>>, ADD<LEN<100 PX>, LEN<2 PX>>, ADD<LEN<100 PX>, LEN<3 PX>>>'
689    );
690
691    // With calc.
692    expect(parsePseudo('calc(0.5 * clamp(1px, 2px, 3px))')).to.equal(
693      'CALC<MUL<NUM<0.5>, CLAMP<LEN<1 PX>, LEN<2 PX>, LEN<3 PX>>>>'
694    );
695  });
696});
697
Full Screen