// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { assert } from 'chai';
import sinon from 'sinon';
import * as Common from '../../core/common/common.js';
import * as SDK from '../../core/sdk/sdk.js';
import { Printer } from '../../testing/PropertyParser.js';
function matchSingleValue(name, value, matcher) {
    const ast = SDK.CSSPropertyParser.tokenizeDeclaration(name, value);
    if (!ast) {
        return { ast, match: null, text: value };
    }
    const matchedResult = SDK.CSSPropertyParser.BottomUpTreeMatching.walk(ast, [matcher]);
    const matchedNode = SDK.CSSPropertyParser.TreeSearch.find(ast, n => matchedResult.getMatch(n) instanceof matcher.matchType);
    const match = matchedNode && matchedResult.getMatch(matchedNode);
    return {
        ast,
        match: match instanceof matcher.matchType ? match : null,
        text: Printer.walk(ast).get(),
    };
}
function injectVariableSubstitutions(variables) {
    const { getComputedText, getComputedTextRange, getMatch } = SDK.CSSPropertyParser.BottomUpTreeMatching.prototype;
    const variableNames = new Map();
    function injectChunk(matching) {
        if (matching.computedText.chunkCount === 0) {
            const propertyOffset = matching.ast.rule.indexOf(matching.ast.propertyName ?? '--');
            assert.isAbove(propertyOffset, 0);
            for (const [varName, value] of Object.entries(variables)) {
                const varText = `var(${varName})`;
                for (let offset = matching.ast.rule.indexOf(varText); offset >= 0; offset = matching.ast.rule.indexOf(varText, offset + 1)) {
                    matching.computedText.push({ text: varText, computedText: () => value, node: {} }, offset - propertyOffset);
                }
                variableNames.set(varText, { varName, value });
            }
        }
    }
    sinon.stub(SDK.CSSPropertyParser.BottomUpTreeMatching.prototype, 'getComputedText')
        .callsFake(function (node) {
        injectChunk(this);
        return getComputedText.call(this, node);
    });
    sinon.stub(SDK.CSSPropertyParser.BottomUpTreeMatching.prototype, 'getComputedTextRange')
        .callsFake(function (from, to) {
        injectChunk(this);
        return getComputedTextRange.call(this, from, to);
    });
    sinon.stub(SDK.CSSPropertyParser.BottomUpTreeMatching.prototype, 'getMatch')
        .callsFake(function (node) {
        injectChunk(this);
        const resolvedValue = variableNames.get(this.ast.text(node));
        if (!resolvedValue) {
            return getMatch.call(this, node);
        }
        return new SDK.CSSPropertyParserMatchers.BaseVariableMatch(this.ast.text(node), node, resolvedValue.varName, [], this, () => resolvedValue.value);
    });
}
describe('Matchers for SDK.CSSPropertyParser.BottomUpTreeMatching', () => {
    it('parses colors', () => {
        for (const fail of ['red-blue', '#f', '#foobar', '', 'rgbz(1 2 2)', 'tan(45deg)']) {
            const { match, text } = matchSingleValue('color', fail, new SDK.CSSPropertyParserMatchers.ColorMatcher());
            assert.isNull(match, text);
        }
        for (const succeed of ['rgb(/* R */155, /* G */51, /* B */255)', 'red', 'rgb(0 0 0)', 'rgba(0 0 0)', '#fff', '#ffff',
            '#ffffff', '#ffffffff']) {
            const { match, text } = matchSingleValue('color', succeed, new SDK.CSSPropertyParserMatchers.ColorMatcher());
            assert.exists(match, text);
            assert.strictEqual(match.text, succeed);
        }
        // The property name matters:
        for (const fail of ['rgb(/* R */155, /* G */51, /* B */255)', 'red', 'rgb(0 0 0)', 'rgba(0 0 0)', '#fff', '#ffff',
            '#ffffff', '#ffffffff']) {
            const { match, text } = matchSingleValue('width', fail, new SDK.CSSPropertyParserMatchers.ColorMatcher());
            assert.isNull(match, text);
        }
    });
    it('parses colors in logical border properties', () => {
        for (const success of ['border-inline', 'border-block', 'border-inline-color', 'border-block-color', 'border-block-end',
            'border-block-end-color', 'border-block-start', 'border-block-start-color', 'border-inline-end',
            'border-inline-end-color', 'border-inline-start', 'border-inline-start-color']) {
            const { ast, match, text } = matchSingleValue(success, 'red', new SDK.CSSPropertyParserMatchers.ColorMatcher());
            assert.exists(match, text);
            assert.strictEqual(match.text, 'red');
            assert.strictEqual(ast?.propertyName, success);
        }
    });
    it('parses colors in SVG color properties', () => {
        for (const success of ['flood-color', 'lighting-color', 'stop-color']) {
            const { ast, match, text } = matchSingleValue(success, 'red', new SDK.CSSPropertyParserMatchers.ColorMatcher());
            assert.exists(match, text);
            assert.strictEqual(match.text, 'red');
            assert.strictEqual(ast?.propertyName, success);
        }
    });
    it('parses linear gradients', () => {
        for (const succeed of ['linear-gradient(90deg, red, blue)', 'linear-gradient(to top left, red, blue)',
            'linear-gradient(in oklab, red, blue)']) {
            const { match, text } = matchSingleValue('background', succeed, new SDK.CSSPropertyParserMatchers.LinearGradientMatcher());
            assert.exists(match, text);
            assert.strictEqual(match.text, succeed);
        }
        for (const fail of ['linear-gradient(90deg, red, blue)', 'linear-gradient(to top left, red, blue)',
            'linear-gradient(in oklab, red, blue)']) {
            const { match, text } = matchSingleValue('width', fail, new SDK.CSSPropertyParserMatchers.ColorMatcher());
            assert.isNull(match, text);
        }
    });
    it('parses colors in masks', () => {
        for (const succeed of ['mask', 'mask-image', 'mask-border', 'mask-border-source']) {
            const ast = SDK.CSSPropertyParser.tokenizeDeclaration(succeed, 'linear-gradient(to top, red, var(--other))');
            assert.exists(ast, succeed);
            const matching = SDK.CSSPropertyParser.BottomUpTreeMatching.walk(ast, [new SDK.CSSPropertyParserMatchers.ColorMatcher()]);
            const colorNode = SDK.CSSPropertyParser.TreeSearch.find(ast, node => ast.text(node) === 'red');
            assert.exists(colorNode);
            const match = matching.getMatch(colorNode);
            assert.exists(match);
            assert.instanceOf(match, SDK.CSSPropertyParserMatchers.ColorMatch);
            assert.strictEqual(match.text, 'red');
        }
    });
    it('parses color-mix with vars', () => {
        injectVariableSubstitutions({
            '--interpolation': 'shorter',
            '--color1': 'red',
            '--percentage': '13%',
            '--rgb': 'shorter',
            '--space': 'in srgb',
            '--color2': '25% blue',
            '--multiple-colors': 'red, blue',
        });
        {
            const { ast, match, text } = matchSingleValue('color', 'color-mix(in srgb var(--interpolation) hue, red var(--percentage), rgb(var(--rgb)))', new SDK.CSSPropertyParserMatchers.ColorMixMatcher());
            assert.exists(ast, text);
            assert.exists(match, text);
            assert.deepEqual(match.space.map(n => ast.text(n)), ['in', 'srgb', 'var(--interpolation)', 'hue']);
            assert.strictEqual(match.color1.map(n => ast.text(n)).join(), 'red,var(--percentage)');
            assert.strictEqual(match.color2.map(n => ast.text(n)).join(), 'rgb(var(--rgb))');
        }
        {
            const { ast, match, text } = matchSingleValue('color', 'color-mix(var(--space), var(--color1), var(--color2))', new SDK.CSSPropertyParserMatchers.ColorMixMatcher());
            assert.exists(ast, text);
            assert.exists(match, text);
            assert.strictEqual(match.space.map(n => ast.text(n)).join(), 'var(--space)');
            assert.strictEqual(match.color1.map(n => ast.text(n)).join(), 'var(--color1)');
            assert.strictEqual(match.color2.map(n => ast.text(n)).join(), 'var(--color2)');
        }
        for (const fail of ['color-mix(var(--color1), var(--color1), var(--color2))',
            'color-mix(var(--space), var(--color1) var(--percentage) var(--percentage), var(--color2))',
            'color-mix(var(--space), var(--color1) 10% var(--percentage), var(--color2))',
            'color-mix(var(--space), var(--color1), var(--color2) 15%)',
            'color-mix(var(--space), var(--color1), var(--color2) var(--percentage))',
            'color-mix(var(--space), var(--multiple-colors))',
        ]) {
            const { ast, match, text } = matchSingleValue('color', fail, new SDK.CSSPropertyParserMatchers.ColorMixMatcher());
            assert.exists(ast, text);
            assert.isNull(match, text);
        }
    });
    it('parses color-mix', () => {
        function check(space, color1, color2) {
            const { ast, match, text } = matchSingleValue('color', `color-mix(${space}, ${color1}, ${color2})`, new SDK.CSSPropertyParserMatchers.ColorMixMatcher());
            assert.exists(ast, text);
            assert.exists(match, text);
            assert.deepEqual(match.space.map(n => ast.text(n)).join(' '), space, text);
            assert.strictEqual(match.color1.map(n => ast.text(n)).join(' '), color1, text);
            assert.strictEqual(match.color2.map(n => ast.text(n)).join(' '), color2, text);
        }
        function checkFailure(space, color1, color2) {
            const { match, text } = matchSingleValue('color', `color-mix(${space}, ${color1}, ${color2})`, new SDK.CSSPropertyParserMatchers.ColorMixMatcher());
            assert.isNull(match, text);
        }
        check('in srgb shorter hue', 'red 35%', 'blue');
        check('in /*asd*/ srgb shorter hue', 'red 35%', 'blue');
        check('in srgb', 'red 35%', 'blue');
        check('in srgb', '35% red', 'blue 16%');
        check('in srgb', '/*a*/ 35% /*b*/ red /*c*/', '/*a*/ blue /*b*/ 16% /*c*/');
        checkFailure('insrgb shorter hue', 'red 35%', 'blue');
        checkFailure('/*asd*/srgb in', 'red 35%', 'blue');
        checkFailure('in srgb', '0% red', 'blue 0%');
    });
    it('parses contrast-color', () => {
        function check(color) {
            const { ast, match, text } = matchSingleValue('color', `contrast-color(${color})`, new SDK.CSSPropertyParserMatchers.ContrastColorMatcher());
            assert.exists(ast, text);
            assert.exists(match, text);
            assert.strictEqual(match.color.map(n => ast.text(n)).join(' '), color, text);
        }
        function checkFailure(color) {
            const { match, text } = matchSingleValue('color', `contrast-color(${color})`, new SDK.CSSPropertyParserMatchers.ContrastColorMatcher());
            assert.isNull(match, text);
        }
        check('red');
        check('/*asd*/ srgb');
        check('var(--color)');
        checkFailure('red, blue');
    });
    it('parses contrast-color with vars', () => {
        injectVariableSubstitutions({
            '--color': 'red',
        });
        const { ast, match, text } = matchSingleValue('color', 'contrast-color(var(--color))', new SDK.CSSPropertyParserMatchers.ContrastColorMatcher());
        assert.exists(ast, text);
        assert.exists(match, text);
        assert.strictEqual(match.color.map(n => ast.text(n)).join(''), 'var(--color)');
    });
    it('parses URLs', () => {
        const url = 'http://example.com';
        {
            const { match, text } = matchSingleValue('background-image', `url(${url})`, new SDK.CSSPropertyParserMatchers.URLMatcher());
            assert.exists(match);
            assert.strictEqual(match.url, url, text);
        }
        {
            const { match, text } = matchSingleValue('background-image', `url("${url}")`, new SDK.CSSPropertyParserMatchers.URLMatcher());
            assert.exists(match);
            assert.strictEqual(match.url, url, text);
        }
    });
    it('parses angles correctly', () => {
        for (const succeed of ['45deg', '1.3rad', '-25grad', '2.3turn']) {
            const { ast, match, text } = matchSingleValue('transform', succeed, new SDK.CSSPropertyParserMatchers.AngleMatcher());
            assert.exists(ast, succeed);
            assert.exists(match, text);
            assert.strictEqual(match.text, succeed);
        }
        for (const fail of ['0DEG', '0', '123', '2em']) {
            const { match, text } = matchSingleValue('transform', fail, new SDK.CSSPropertyParserMatchers.AngleMatcher());
            assert.isNull(match, text);
        }
    });
    it('parses linkable names correctly', () => {
        function match(name, value) {
            const ast = SDK.CSSPropertyParser.tokenizeDeclaration(name, value);
            assert.exists(ast);
            const matchedResult = SDK.CSSPropertyParser.BottomUpTreeMatching.walk(ast, [
                new SDK.CSSPropertyParserMatchers.LinkableNameMatcher(),
            ]);
            const matches = SDK.CSSPropertyParser.TreeSearch.findAll(ast, node => matchedResult.getMatch(node) instanceof SDK.CSSPropertyParserMatchers.LinkableNameMatch);
            return matches.map(m => matchedResult.getMatch(m)?.text);
        }
        assert.deepEqual(match('animation-name', 'first, second, -moz-third'), ['first', 'second', '-moz-third']);
        assert.deepEqual(match('animation-name', 'first'), ['first']);
        assert.deepEqual(match('font-palette', 'first'), ['first']);
        {
            assert.deepEqual(match('position-try-fallbacks', 'flip-block'), []);
            assert.deepEqual(match('position-try-fallbacks', '--one'), ['--one']);
            assert.deepEqual(match('position-try-fallbacks', '--one, --two'), ['--one', '--two']);
        }
        {
            assert.deepEqual(match('position-try', 'flip-block'), []);
            assert.deepEqual(match('position-try', '--one'), ['--one']);
            assert.deepEqual(match('position-try', '--one, --two'), ['--one', '--two']);
        }
        {
            assert.deepEqual(match('list-style-type', 'custom'), ['custom']);
            assert.deepEqual(match('list-style-type', 'georgian'), []);
            assert.deepEqual(match('list-style', 'custom outside'), ['custom']);
        }
        {
            injectVariableSubstitutions({
                '--duration-and-easing': '1s linear',
            });
            assert.deepEqual(match('animation', '1s linear --animation-name'), ['--animation-name']);
            assert.deepEqual(match('animation', '1s linear linear'), ['linear']);
            assert.deepEqual(match('animation', '1s linear --first-name, 1s ease-in --second-name'), ['--first-name', '--second-name']);
            assert.deepEqual(match('animation', '1s linear'), []);
            // Matching to variable names inside `var()` functions are fine as it is handled by variable renderer in usage.
            assert.deepEqual(match('animation', 'var(--duration-and-easing) linear'), ['--duration-and-easing', 'linear']);
            assert.deepEqual(match('animation', '1s linear var(--non-existent, --animation-name)'), ['--non-existent', '--animation-name']);
            assert.deepEqual(match('animation', '1s step-start 0s kf'), ['kf']);
            assert.deepEqual(match('animation', '1s step-end 0s kf'), ['kf']);
            assert.deepEqual(match('animation', '1s steps(1, jump-start) 0s kf'), ['kf']);
            assert.deepEqual(match('animation', '1s steps(1, jump-end) 0s kf'), ['kf']);
            assert.deepEqual(match('animation', '1s steps(1, jump-none) 0s kf'), ['kf']);
            assert.deepEqual(match('animation', '1s steps(1, start) 0s kf'), ['kf']);
            assert.deepEqual(match('animation', '1s steps(1, end) 0s kf'), ['kf']);
        }
    });
    it('parses easing functions properly', () => {
        for (const succeed of ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear(0 0%, 1 100%)',
            'cubic-bezier(0.3, 0.3, 0.3, 0.3)']) {
            const { ast, match, text } = matchSingleValue('animation-timing-function', succeed, new SDK.CSSPropertyParserMatchers.BezierMatcher());
            assert.exists(ast, succeed);
            assert.exists(match, text);
            assert.strictEqual(match.text, succeed);
        }
        const { ast, match, text } = matchSingleValue('border', 'ease-in', new SDK.CSSPropertyParserMatchers.BezierMatcher());
        assert.exists(ast, 'border');
        assert.isNull(match, text);
    });
    it('parses strings correctly', () => {
        function match(name, value) {
            const ast = SDK.CSSPropertyParser.tokenizeDeclaration(name, value);
            assert.exists(ast);
            const matchedResult = SDK.CSSPropertyParser.BottomUpTreeMatching.walk(ast, [new SDK.CSSPropertyParserMatchers.StringMatcher()]);
            assert.exists(matchedResult);
            const match = SDK.CSSPropertyParser.TreeSearch.find(ast, node => matchedResult.getMatch(node) instanceof SDK.CSSPropertyParserMatchers.StringMatch);
            assert.exists(match);
        }
        match('quotes', '"\'" "\'"');
        match('content', '"foobar"');
        match('--image-file-accelerometer-back', 'url("devtools\:\/\/devtools\/bundled\/Images\/accelerometer-back\.svg")');
    });
    it('parses shadows correctly', () => {
        const { match, text } = matchSingleValue('box-shadow', '/*0*/3px 3px red, -1em 0 .4em /*a*/ olive /*b*/', new SDK.CSSPropertyParserMatchers.ShadowMatcher());
        assert.exists(match, text);
        assert.strictEqual(match.text, '/*0*/3px 3px red, -1em 0 .4em /*a*/ olive');
    });
    it('parses grid templates correctly', () => {
        injectVariableSubstitutions({
            '--row': '"a a b"',
            '--row-with-names': '[name1] "a a" [name2]',
            '--line-name': '[name1]',
            '--double-row': '"a b" "b c"',
        });
        {
            const { ast, match, text } = matchSingleValue('grid', '"a a"', new SDK.CSSPropertyParserMatchers.GridTemplateMatcher());
            assert.exists(ast, text);
            assert.exists(match, text);
            assert.strictEqual(match.lines.map(line => line.map(n => ast.text(n)).join(' ')).join('\n'), '"a a"');
        }
        {
            const { ast, match, text } = matchSingleValue('grid-template-areas', '"a a a" "b b b" "c c c"', new SDK.CSSPropertyParserMatchers.GridTemplateMatcher());
            assert.exists(ast, text);
            assert.exists(match, text);
            assert.deepEqual(match.lines.map(line => line.map(n => ast.text(n)).join(' ')), ['"a a a"', '"b b b"', '"c c c"']);
        }
        {
            const { ast, match, text } = matchSingleValue('grid-template', '"a a a" var(--row) / auto 1fr auto', new SDK.CSSPropertyParserMatchers.GridTemplateMatcher());
            assert.exists(ast, text);
            assert.exists(match, text);
            assert.deepEqual(match.lines.map(line => line.map(n => ast.text(n)).join(' ')), ['"a a a"', 'var(--row) / auto 1fr auto']);
        }
        {
            const { ast, match, text } = matchSingleValue('grid', '[header-top] "a a" var(--row-with-names) [main-top] "b b b" 1fr [main-bottom] / auto 1fr auto;', new SDK.CSSPropertyParserMatchers.GridTemplateMatcher());
            assert.exists(ast, text);
            assert.exists(match, text);
            assert.deepEqual(match.lines.map(line => line.map(n => ast.text(n)).join(' ')), ['[header-top] "a a" var(--row-with-names)', '[main-top] "b b b" 1fr [main-bottom] / auto 1fr auto']);
        }
        {
            const { ast, match, text } = matchSingleValue('grid', '[header-top] "a a" "b b b" var(--line-name) "c c" / auto 1fr auto;', new SDK.CSSPropertyParserMatchers.GridTemplateMatcher());
            assert.exists(ast, text);
            assert.exists(match, text);
            assert.deepEqual(match.lines.map(line => line.map(n => ast.text(n)).join(' ')), ['[header-top] "a a"', '"b b b" var(--line-name)', '"c c" / auto 1fr auto']);
        }
        {
            const { ast, match, text } = matchSingleValue('grid', '[line1] "a a" [line2] var(--double-row) "b b" / auto 1fr auto;', new SDK.CSSPropertyParserMatchers.GridTemplateMatcher());
            assert.exists(ast, text);
            assert.exists(match, text);
            assert.deepEqual(match.lines.map(line => line.map(n => ast.text(n)).join(' ')), ['[line1] "a a" [line2]', 'var(--double-row)', '"b b" / auto 1fr auto']);
        }
        {
            const { ast, match, text } = matchSingleValue('grid', '"a a" var(--unresolved) / auto 1fr auto;', new SDK.CSSPropertyParserMatchers.GridTemplateMatcher());
            assert.exists(ast, text);
            assert.exists(match, text);
            assert.deepEqual(match.lines.map(line => line.map(n => ast.text(n)).join(' ')), ['"a a" var(--unresolved) / auto 1fr auto']);
        }
    });
    it('parses light-dark correctly', () => {
        for (const fail of ['light-dark()', 'light-dark(red)', 'light-dark(var(--foo))']) {
            const { match, text } = matchSingleValue('color', fail, new SDK.CSSPropertyParserMatchers.LightDarkColorMatcher(sinon.createStubInstance(SDK.CSSStyleDeclaration.CSSStyleDeclaration)));
            assert.isNull(match, text);
        }
        for (const succeed of ['light-dark(red, blue)', 'light-dark(var(--foo), red)', 'light-dark(red, var(--foo))',
            'light-dark(var(--foo), var(--bar))']) {
            const { ast, match, text } = matchSingleValue('color', succeed, new SDK.CSSPropertyParserMatchers.LightDarkColorMatcher(sinon.createStubInstance(SDK.CSSStyleDeclaration.CSSStyleDeclaration)));
            assert.exists(ast, text);
            assert.exists(match, text);
            const [light, dark] = succeed.slice('light-dark('.length, -1).split(', ');
            assert.lengthOf(match.light, 1);
            assert.lengthOf(match.dark, 1);
            assert.strictEqual(ast.text(match.light[0]), light);
            assert.strictEqual(ast.text(match.dark[0]), dark);
        }
        // light-dark only applies to color properties
        const { match, text } = matchSingleValue('width', 'light-dark(red, blue)', new SDK.CSSPropertyParserMatchers.LightDarkColorMatcher(sinon.createStubInstance(SDK.CSSStyleDeclaration.CSSStyleDeclaration)));
        assert.isNull(match, text);
    });
    it('parses auto-base correctly', () => {
        for (const fail of ['-internal-auto-base()', '-internal-auto-base(block)', '-internal-auto-base(var(--foo))']) {
            const { match, text } = matchSingleValue('display', fail, new SDK.CSSPropertyParserMatchers.AutoBaseMatcher());
            assert.isNull(match, text);
        }
        for (const [succeed, propertyName] of [
            ['-internal-auto-base(red, blue)', 'color'],
            ['-internal-auto-base(var(--foo), red)', 'color'],
            ['-internal-auto-base(red, var(--foo))', 'color'],
            ['-internal-auto-base(var(--foo), var(--bar))', 'color'],
            ['-internal-auto-base(gray, coral)', 'background-color'],
            ['-internal-auto-base(inline, block)', 'display'],
            ['-internal-auto-base(center, right)', 'text-align'],
            ['-internal-auto-base(serif, cursive)', 'font-family'],
            ['-internal-auto-base(solid, dashed)', 'border-style'],
            ['-internal-auto-base(0, 0.5em)', 'border-radius'],
            ['-internal-auto-base(2px, 0.25em)', 'padding'],
            ['-internal-auto-base(1en, 3pt)', 'margin'],
        ]) {
            const { ast, match, text } = matchSingleValue(propertyName, succeed, new SDK.CSSPropertyParserMatchers.AutoBaseMatcher());
            assert.exists(ast, text);
            assert.exists(match, text);
            const [auto, base] = succeed.slice('-internal-auto-base('.length, -1).split(', ');
            assert.lengthOf(match.auto, 1);
            assert.lengthOf(match.base, 1);
            assert.strictEqual(ast.text(match.auto[0]), auto);
            assert.strictEqual(ast.text(match.base[0]), base);
        }
    });
    describe('AnchorFunctionMatcher', () => {
        it('should not match when it is not a call expression', () => {
            const { match, text } = matchSingleValue('left', 'anchor', new SDK.CSSPropertyParserMatchers.AnchorFunctionMatcher());
            assert.isNull(match, text);
        });
        it('should not match anchor() call without arguments', () => {
            const { match: anchorMatch } = matchSingleValue('left', 'anchor()', new SDK.CSSPropertyParserMatchers.AnchorFunctionMatcher());
            assert.isNull(anchorMatch);
        });
        it('should match anchor-size() call without arguments', () => {
            const { match: anchorSizeMatch, text: anchorSizeText } = matchSingleValue('width', 'anchor-size()', new SDK.CSSPropertyParserMatchers.AnchorFunctionMatcher());
            assert.exists(anchorSizeMatch, anchorSizeText);
        });
        it('should match if it is an anchor() or anchor-size() call', () => {
            const { match: anchorMatch, text: anchorText } = matchSingleValue('left', 'anchor(left)', new SDK.CSSPropertyParserMatchers.AnchorFunctionMatcher());
            assert.exists(anchorMatch, anchorText);
            const { match: anchorSizeMatch, text: anchorSizeText } = matchSingleValue('width', 'anchor-size(width)', new SDK.CSSPropertyParserMatchers.AnchorFunctionMatcher());
            assert.exists(anchorSizeMatch, anchorSizeText);
        });
        it('should match dashed identifier as name from the first argument', () => {
            const { match: anchorMatch, text: anchorText } = matchSingleValue('left', 'anchor(--dashed-ident left)', new SDK.CSSPropertyParserMatchers.AnchorFunctionMatcher());
            assert.exists(anchorMatch, anchorText);
            assert.strictEqual(anchorMatch.text, '--dashed-ident');
            const { match: anchorSizeMatch, text: anchorSizeText } = matchSingleValue('width', 'anchor-size(--dashed-ident width)', new SDK.CSSPropertyParserMatchers.AnchorFunctionMatcher());
            assert.exists(anchorSizeMatch, anchorSizeText);
            assert.strictEqual(anchorSizeMatch.text, '--dashed-ident');
        });
        it('should match dashed identifier as name from the second argument', () => {
            const { match: anchorMatch, text: anchorText } = matchSingleValue('left', 'anchor(right --dashed-ident)', new SDK.CSSPropertyParserMatchers.AnchorFunctionMatcher());
            assert.exists(anchorMatch, anchorText);
            assert.strictEqual(anchorMatch.text, '--dashed-ident');
            const { match: anchorSizeMatch, text: anchorSizeText } = matchSingleValue('width', 'anchor-size(height --dashed-ident)', new SDK.CSSPropertyParserMatchers.AnchorFunctionMatcher());
            assert.exists(anchorSizeMatch, anchorSizeText);
            assert.strictEqual(anchorSizeMatch.text, '--dashed-ident');
        });
    });
    describe('PositionAnchorMatcher', () => {
        it('should match `position-anchor` property with dashed identifier', () => {
            const { match, text } = matchSingleValue('position-anchor', '--dashed-ident', new SDK.CSSPropertyParserMatchers.PositionAnchorMatcher());
            assert.exists(match, text);
            assert.strictEqual(match.text, '--dashed-ident');
        });
        it('should not match `position-anchor` property when it is not a dashed identifier', () => {
            const { match } = matchSingleValue('position-anchor', 'something-non-dashed', new SDK.CSSPropertyParserMatchers.PositionAnchorMatcher());
            assert.isNull(match);
        });
    });
    describe('PositionTryMatcher', () => {
        it('should match `position-try[-fallbacks]` property with linkable names', () => {
            {
                const { match, text } = matchSingleValue('position-try', 'flip-block, --top, --bottom', new SDK.CSSPropertyParserMatchers.PositionTryMatcher());
                assert.exists(match, text);
                assert.strictEqual(match.text, 'flip-block, --top, --bottom');
                assert.lengthOf(match.preamble, 0);
            }
            {
                const { ast, match, text } = matchSingleValue('position-try', '/* comment */ most-height --top, --bottom', new SDK.CSSPropertyParserMatchers.PositionTryMatcher());
                assert.exists(ast, text);
                assert.exists(match, text);
                assert.strictEqual(match.text, '/* comment */ most-height --top, --bottom');
                assert.strictEqual(ast.textRange(match.preamble[0], match.preamble[match.preamble.length - 1]), '/* comment */ most-height');
            }
            {
                const { match, text } = matchSingleValue('position-try-fallbacks', '/* comment */ flip-block, --top, /* comment */ --bottom', new SDK.CSSPropertyParserMatchers.PositionTryMatcher());
                assert.exists(match, text);
                assert.strictEqual(match.text, '/* comment */ flip-block, --top, /* comment */ --bottom');
                assert.lengthOf(match.preamble, 0);
            }
            {
                const { match } = matchSingleValue('position-try', 'revert', new SDK.CSSPropertyParserMatchers.PositionTryMatcher());
                assert.isNull(match);
            }
        });
    });
    describe('MathFunctionMatcher', () => {
        it('matches selecting functions', () => {
            const success = ['clamp(1px, 2px, 3px)', 'min(1, 2)', 'max(3, 4)'];
            for (const value of success) {
                const { match, text } = matchSingleValue('width', value, new SDK.CSSPropertyParserMatchers.MathFunctionMatcher());
                assert.exists(match, text);
                assert.strictEqual(match.text, value);
                assert.strictEqual(match.func, value.substr(0, value.indexOf('(')));
                assert.isAbove(match.args.length, 0);
            }
            const failure = ['clomp(1px, 2px, 3px)', 'min()'];
            for (const value of failure) {
                const { match, text } = matchSingleValue('width', value, new SDK.CSSPropertyParserMatchers.MathFunctionMatcher());
                assert.notExists(match, text);
            }
        });
    });
    describe('CustomFunctionMatcher', () => {
        it('matches custom functions', () => {
            const success = ['--darklight(blue, green)', '--riemann-zeta(2.0, 1.0)'];
            for (const value of success) {
                const { match, text } = matchSingleValue('width', value, new SDK.CSSPropertyParserMatchers.CustomFunctionMatcher());
                assert.exists(match, text);
                assert.strictEqual(match.text, value);
                assert.strictEqual(match.func, value.substr(0, value.indexOf('(')));
                assert.isAbove(match.args.length, 0);
            }
            const failure = ['clamp(1px, 2px, 3px)', '-foo()'];
            for (const value of failure) {
                const { match, text } = matchSingleValue('width', value, new SDK.CSSPropertyParserMatchers.CustomFunctionMatcher());
                assert.notExists(match, text);
            }
        });
    });
    it('matches lengths', () => {
        for (const unit of SDK.CSSPropertyParserMatchers.LengthMatcher.LENGTH_UNITS) {
            const { match, text } = matchSingleValue('min-width', `100${unit}`, new SDK.CSSPropertyParserMatchers.LengthMatcher());
            assert.exists(match, text);
            assert.strictEqual(match.text, `100${unit}`);
        }
    });
    it('match css keywords', () => {
        const propertyStub = sinon.createStubInstance(SDK.CSSProperty.CSSProperty);
        const matchedStylesStub = sinon.createStubInstance(SDK.CSSMatchedStyles.CSSMatchedStyles);
        for (const keyword of SDK.CSSMetadata.CSSWideKeywords) {
            const { match, text } = matchSingleValue('--property', keyword, new SDK.CSSPropertyParserMatchers.CSSWideKeywordMatcher(propertyStub, matchedStylesStub));
            assert.exists(match, text);
            assert.strictEqual(match.text, keyword);
        }
        const { match, text } = matchSingleValue('--property', '1px inherits', new SDK.CSSPropertyParserMatchers.CSSWideKeywordMatcher(propertyStub, matchedStylesStub));
        assert.notExists(match, text);
    });
    it('match flex, grid, and grid-lanes values', () => {
        const good = [
            'flex',
            'grid',
            'grid-lanes',
            'inline-flex',
            'inline-grid',
            'inline-grid-lanes',
            'block flex',
            'block grid',
            'block grid-lanes',
            'inline   flex',
            'inline grid',
            'inline grid-lanes',
            'inline grid !important',
            'grid /* comment */',
        ];
        const bad = ['flex block', 'grid inline', 'block', 'inline'];
        for (const value of good) {
            const { match, text } = matchSingleValue('display', value, new SDK.CSSPropertyParserMatchers.FlexGridGridLanesMatcher());
            assert.exists(match, text);
            if (match.text.includes('flex')) {
                assert.strictEqual(match.layoutType, "flex" /* SDK.CSSPropertyParserMatchers.LayoutType.FLEX */);
            }
            else if (match.text.includes('grid-lanes')) {
                assert.strictEqual(match.layoutType, "grid-lanes" /* SDK.CSSPropertyParserMatchers.LayoutType.GRID_LANES */);
            }
            else if (match.text.includes('grid')) {
                assert.strictEqual(match.layoutType, "grid" /* SDK.CSSPropertyParserMatchers.LayoutType.GRID */);
            }
        }
        for (const value of bad) {
            const { match, text } = matchSingleValue('display', value, new SDK.CSSPropertyParserMatchers.FlexGridGridLanesMatcher());
            assert.notExists(match, text);
        }
    });
    it('match color channels for relative colors', () => {
        function expectedColor(channel) {
            switch (channel) {
                case "l" /* Common.Color.ColorChannel.L */:
                    return new Common.Color.Lab(0.5, 0, 0.0, null, 'lab(0.5 0 0)');
                case "a" /* Common.Color.ColorChannel.A */:
                    return new Common.Color.Lab(1, 0.5, 0, null);
                case "c" /* Common.Color.ColorChannel.C */:
                    return new Common.Color.LCH(1, 0.5, 0, null);
                case "h" /* Common.Color.ColorChannel.H */:
                    return new Common.Color.LCH(1, 1, 0.5, null);
                case "r" /* Common.Color.ColorChannel.R */:
                    return new Common.Color.Legacy([0.5, 0, 0], "rgb" /* Common.Color.Format.RGB */);
                case "g" /* Common.Color.ColorChannel.G */:
                    return new Common.Color.Legacy([0, 0.5, 0], "rgb" /* Common.Color.Format.RGB */);
                case "b" /* Common.Color.ColorChannel.B */:
                    return new Common.Color.Legacy([0, 0, 0.5], "rgb" /* Common.Color.Format.RGB */);
                case "alpha" /* Common.Color.ColorChannel.ALPHA */:
                    return new Common.Color.Legacy([0, 0, 0, 0.5], "rgba" /* Common.Color.Format.RGBA */);
                case "s" /* Common.Color.ColorChannel.S */:
                    return new Common.Color.HSL(0.8, 0.5, 0.9, null);
                case "w" /* Common.Color.ColorChannel.W */:
                    return new Common.Color.HWB(0, 0.5, 0, null);
                case "x" /* Common.Color.ColorChannel.X */:
                    return new Common.Color.ColorFunction("xyz-d50" /* Common.Color.Format.XYZ_D50 */, 0.5, 0, 0, null);
                case "y" /* Common.Color.ColorChannel.Y */:
                    return new Common.Color.ColorFunction("xyz-d50" /* Common.Color.Format.XYZ_D50 */, 0, 0.5, 0, null);
                case "z" /* Common.Color.ColorChannel.Z */:
                    return new Common.Color.ColorFunction("xyz-d50" /* Common.Color.Format.XYZ_D50 */, 0, 0, 0.5, null);
                default:
                    throw new Error('Unexpected channel');
            }
        }
        for (const good of ['r', 'g', 'b', 'alpha', 'x', 'y', 'z', 'l', 'c', 'h', 'a', 'b', 's', 'w']) {
            const { match, text } = matchSingleValue('color', `calc(1 * ${good})`, new SDK.CSSPropertyParserMatchers.RelativeColorChannelMatcher());
            assert.exists(match, text);
            assert.strictEqual(match.text, good);
            const expected = expectedColor(good);
            const baseColor = new SDK.CSSPropertyParserMatchers.ColorMatch(expected.getAuthoredText() ?? expected.asString(), match.node);
            assert.strictEqual(match.getColorChannelValue({ baseColor, colorSpace: expected.format() })?.toFixed(1), '0.5', good);
        }
        const { match, text } = matchSingleValue('color', 'calc(1 * r)', new SDK.CSSPropertyParserMatchers.RelativeColorChannelMatcher());
        assert.exists(match, text);
        const expected = expectedColor('y');
        const baseColor = new SDK.CSSPropertyParserMatchers.ColorMatch(expected.getAuthoredText() ?? expected.asString(), match.node);
        assert.isNull(match.getColorChannelValue({ baseColor, colorSpace: expected.format() }));
    });
    it('match env() functions', () => {
        // Matched when the var resolves
        for (const good of ['env(a)', 'env(a, d)', 'env(a /* aa */, b c)', 'env(a, b, c)']) {
            const matchedStyles = sinon.createStubInstance(SDK.CSSMatchedStyles.CSSMatchedStyles);
            matchedStyles.environmentVariable.callsFake(name => name === 'a' ? 'A' : 'B');
            const { match, text } = matchSingleValue('--env', good, new SDK.CSSPropertyParserMatchers.EnvFunctionMatcher(matchedStyles));
            assert.exists(match, text);
            assert.strictEqual(match.varName, 'a');
            assert.strictEqual(match.value, 'A');
        }
        // Matched when the var is not resolved
        for (const good of ['env(a)', 'env(a, d)', 'env(a /* aa */, b c)', 'env(a, b, c)']) {
            const matchedStyles = sinon.createStubInstance(SDK.CSSMatchedStyles.CSSMatchedStyles);
            matchedStyles.environmentVariable.callsFake(name => name === 'a' ? undefined : 'B');
            const { match, text } = matchSingleValue('--env', good, new SDK.CSSPropertyParserMatchers.EnvFunctionMatcher(matchedStyles));
            assert.exists(match, text);
            assert.strictEqual(match.varName, 'a');
            assert.oneOf(match.value, [null, 'd', 'b c', 'b, c']);
        }
        // Not matched
        for (const bad of ['env', 'env()']) {
            const matchedStyles = sinon.createStubInstance(SDK.CSSMatchedStyles.CSSMatchedStyles);
            const { match, ast, text } = matchSingleValue('--env', bad, new SDK.CSSPropertyParserMatchers.EnvFunctionMatcher(matchedStyles));
            assert.notExists(match, text);
            assert.exists(ast, text);
        }
    });
    it('matches variable names in style queries', () => {
        const matchedStyles = sinon.createStubInstance(SDK.CSSMatchedStyles.CSSMatchedStyles);
        const style = sinon.createStubInstance(SDK.CSSStyleDeclaration.CSSStyleDeclaration);
        {
            // 1. Simple inline if() with style query
            const { match } = matchSingleValue('width', 'if(style(--foo: bar): 10px)', new SDK.CSSPropertyParserMatchers.VariableNameMatcher(matchedStyles, style));
            assert.exists(match);
            assert.strictEqual(match.text, '--foo');
        }
        {
            // 2. Parenthesized boolean subexpression
            const { match } = matchSingleValue('width', 'if(not (style(--a: b) and style(--c: d)): 10px)', new SDK.CSSPropertyParserMatchers.VariableNameMatcher(matchedStyles, style));
            assert.exists(match);
            assert.strictEqual(match.text, '--a');
        }
        {
            // 3. Ensure variables in value positions are NOT matched by VariableNameMatcher
            const { match } = matchSingleValue('width', 'if((style((calc(var(--a)) > 10px) and not (--c: d))): 10px)', new SDK.CSSPropertyParserMatchers.VariableNameMatcher(matchedStyles, style));
            assert.exists(match);
            assert.strictEqual(match.text, '--c');
        }
        {
            // 4. LHS of range comparison
            const { match } = matchSingleValue('width', 'if(style(--q > 3): 10px)', new SDK.CSSPropertyParserMatchers.VariableNameMatcher(matchedStyles, style));
            assert.exists(match);
            assert.strictEqual(match.text, '--q');
        }
        {
            // 5. RHS of range comparison
            const { match } = matchSingleValue('width', 'if(style(3 = --q): 10px)', new SDK.CSSPropertyParserMatchers.VariableNameMatcher(matchedStyles, style));
            assert.exists(match);
            assert.strictEqual(match.text, '--q');
        }
        {
            // 6. Range comparisons with calculations inside style() calling var()
            const { match } = matchSingleValue('width', 'if(style(--foo > calc(var(--bar) + 10px)): 10px)', new SDK.CSSPropertyParserMatchers.VariableNameMatcher(matchedStyles, style));
            assert.exists(match);
            assert.strictEqual(match.text, '--foo');
        }
    });
});
//# sourceMappingURL=CSSPropertyParserMatchers.test.js.map