// Copyright 2020 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 Host from '../../core/host/host.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as ComputedStyle from '../../models/computed_style/computed_style.js';
import { raf, renderElementIntoDOM } from '../../testing/DOMHelpers.js';
import { createTarget, describeWithEnvironment, updateHostConfig, } from '../../testing/EnvironmentHelpers.js';
import { expectCall } from '../../testing/ExpectStubCall.js';
import { setupLocaleHooks } from '../../testing/LocaleHelpers.js';
import { MockCDPConnection } from '../../testing/MockCDPConnection.js';
import { createStubbedDomNodeWithModels, getMatchedStyles } from '../../testing/StyleHelpers.js';
import * as TextEditor from '../../ui/components/text_editor/text_editor.js';
import * as InlineEditor from '../../ui/legacy/components/inline_editor/inline_editor.js';
import * as UI from '../../ui/legacy/legacy.js';
import { html } from '../../ui/lit/lit.js';
import * as PanelsCommon from '../common/common.js';
import * as Elements from './elements.js';
describe('StylesSidebarPane', () => {
    let node;
    beforeEach(() => {
        ({ node } = createStubbedDomNodeWithModels({ nodeId: 1 }));
        UI.Context.Context.instance().setFlavor(SDK.DOMModel.DOMNode, node);
    });
    describeWithEnvironment('StylesSidebarPane', () => {
        let connection;
        beforeEach(() => {
            connection = new MockCDPConnection();
            const target = createTarget({ connection });
            const cssModel = target.model(SDK.CSSModel.CSSModel);
            sinon.stub(ComputedStyle.ComputedStyleModel.ComputedStyleModel.prototype, 'cssModel').returns(cssModel);
            sinon.stub(Host.AidaClient.HostConfigTracker, 'instance').returns({
                addEventListener: () => { },
                removeEventListener: () => { },
                dispose: () => { },
            });
        });
        it('unescapes CSS strings', () => {
            assert.strictEqual(Elements.StylesSidebarPane.unescapeCssString(String.raw `"I\F1 t\EB rn\E2 ti\F4 n\E0 liz\E6 ti\F8 n\2603 \1F308  can be \t\r\ic\k\y"`), '"I\xF1t\xEBrn\xE2ti\xF4n\xE0liz\xE6ti\xF8n\u2603\u{1F308} can be tricky"');
            assert.strictEqual(Elements.StylesSidebarPane.unescapeCssString(String.raw `"_\DBFF_\\DBFF_\\\DBFF_\\\\DBFF_\\\\\DBFF_"`), '"_\uFFFD_\\DBFF_\\\\DBFF_\\\\\\DBFF_\\\\\\\\DBFF_"');
            assert.strictEqual(Elements.StylesSidebarPane.unescapeCssString(String.raw `"\0_\DBFF_\DFFF_\110000"`), '"\uFFFD_\uFFFD_\uFFFD_\uFFFD"', 'U+0000, lone surrogates, and values above U+10FFFF should become U+FFFD');
            assert.strictEqual(Elements.StylesSidebarPane.unescapeCssString(String.raw `"_\D83C\DF08_"`), '"_\uFFFD\uFFFD_"', 'surrogates should not be combined');
            assert.strictEqual(Elements.StylesSidebarPane.unescapeCssString('"_\\41\n_\\41\t_\\41\x20_"'), '"_A_A_A_"', 'certain trailing whitespace characters should be consumed as part of the escape sequence');
        });
        it('escapes URL as CSS comments', () => {
            assert.strictEqual(Elements.StylesSidebarPane.escapeUrlAsCssComment('https://abc.com/'), 'https://abc.com/');
            assert.strictEqual(Elements.StylesSidebarPane.escapeUrlAsCssComment('https://abc.com/*/'), 'https://abc.com/*/');
            assert.strictEqual(Elements.StylesSidebarPane.escapeUrlAsCssComment('https://abc.com/*/?q=*'), 'https://abc.com/*/?q=*');
            assert.strictEqual(Elements.StylesSidebarPane.escapeUrlAsCssComment('https://abc.com/*/?q=*/'), 'https://abc.com/*/?q=*%2F');
            assert.strictEqual(Elements.StylesSidebarPane.escapeUrlAsCssComment('https://abc.com/*/?q=*/#hash'), 'https://abc.com/*/?q=*%2F#hash');
        });
        describe('rebuildSectionsForMatchedStyleRulesForTest', () => {
            it('should add @position-try section', async () => {
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node: sinon.createStubInstance(SDK.DOMModel.DOMNode),
                    positionTryRules: [{
                            name: { text: '--try-one' },
                            origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                            style: {
                                cssProperties: [{ name: 'bottom', value: 'anchor(--anchor-name bottom)' }],
                                shorthandEntries: [],
                            },
                            active: false,
                        }],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                assert.lengthOf(sectionBlocks, 2);
                assert.strictEqual(sectionBlocks[1].titleElement()?.textContent, '@position-try --try-one');
                assert.lengthOf(sectionBlocks[1].sections, 1);
                assert.instanceOf(sectionBlocks[1].sections[0], Elements.StylePropertiesSection.PositionTryRuleSection);
            });
            it('correctly hides and shows nested section blocks when filtering', async () => {
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                node.nodeName.returns('div');
                node.id = 1;
                const parentNode = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                node.parentNode = parentNode;
                parentNode.nodeName.returns('body');
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node,
                    matchedPayload: [{
                            rule: {
                                selectorList: { selectors: [{ text: 'div' }], text: 'div' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'background', value: 'red' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        }],
                    inheritedPayload: [{
                            matchedCSSRules: [{
                                    rule: {
                                        selectorList: { selectors: [{ text: 'body' }], text: 'body' },
                                        origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                        style: {
                                            cssProperties: [{ name: 'color', value: 'blue' }],
                                            shorthandEntries: [],
                                        },
                                        layers: [{ text: 'mylayer' }],
                                    },
                                    matchingSelectors: [0],
                                }],
                        }],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                assert.lengthOf(sectionBlocks, 3);
                const inheritedBlock = sectionBlocks[1];
                assert.strictEqual(inheritedBlock.titleElement()?.textContent, 'Inherited from ');
                assert.lengthOf(inheritedBlock.childBlocks, 1);
                const layerBlock = inheritedBlock.childBlocks[0];
                assert.strictEqual(layerBlock.titleElement()?.textContent, 'Layermylayer');
                const elementStyleSections = sectionBlocks[0].sections;
                const inheritedStyleSections = layerBlock.sections;
                // Filter to something that only matches the inherited style.
                stylesSidebarPane.setFilter(/color/i);
                sectionBlocks.forEach(block => block.updateFilter());
                assert.isTrue(elementStyleSections[0].element.classList.contains('hidden'));
                assert.isFalse(inheritedStyleSections[0].element.classList.contains('hidden'));
                assert.isFalse(inheritedBlock.titleElement()?.classList.contains('hidden'));
                assert.isFalse(layerBlock.titleElement()?.classList.contains('hidden'));
                // Filter to something that matches nothing.
                stylesSidebarPane.setFilter(/display/i);
                sectionBlocks.forEach(block => block.updateFilter());
                assert.isTrue(elementStyleSections[0].element.classList.contains('hidden'));
                assert.isTrue(inheritedStyleSections[0].element.classList.contains('hidden'));
                assert.isTrue(inheritedBlock.titleElement()?.classList.contains('hidden'));
                assert.isTrue(layerBlock.titleElement()?.classList.contains('hidden'));
                // Clear filter.
                stylesSidebarPane.setFilter(null);
                sectionBlocks.forEach(block => block.updateFilter());
                assert.isFalse(elementStyleSections[0].element.classList.contains('hidden'));
                assert.isFalse(inheritedStyleSections[0].element.classList.contains('hidden'));
                assert.isFalse(inheritedBlock.titleElement()?.classList.contains('hidden'));
                assert.isFalse(layerBlock.titleElement()?.classList.contains('hidden'));
            });
        });
        describe('collapsing non-contributing sections', () => {
            const enableCollapse = () => Common.Settings.Settings.instance().moduleSetting('collapse-non-contributing-css-rules').set(true);
            it('collapses a section where all properties are overloaded', async () => {
                enableCollapse();
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                node.id = 1;
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node,
                    matchedPayload: [
                        // Lower specificity rule: color will be overloaded by the higher specificity rule.
                        {
                            rule: {
                                selectorList: { selectors: [{ text: 'div' }], text: 'div' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'blue' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                        // Higher specificity rule: color is active here.
                        {
                            rule: {
                                selectorList: { selectors: [{ text: '#id' }], text: '#id' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'red' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                    ],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                assert.lengthOf(sectionBlocks[0].sections, 2);
                const activeSection = sectionBlocks[0].sections.find(section => section.headerText() === '#id');
                const overloadedSection = sectionBlocks[0].sections.find(section => section.headerText() === 'div');
                if (!activeSection || !overloadedSection) {
                    assert.fail('Expected #id and div sections to exist');
                }
                const overloadedProperty = overloadedSection.style().leadingProperties()[0];
                if (!overloadedProperty) {
                    assert.fail('Expected overloaded section to have at least one property');
                }
                assert.strictEqual(matchedStyles.propertyState(overloadedProperty), "Overloaded" /* SDK.CSSMatchedStyles.PropertyState.OVERLOADED */, 'Expected div{color:blue} to be overloaded by #id{color:red}');
                // The section with all overloaded properties should be collapsed.
                assert.isTrue(overloadedSection.element.classList.contains('collapsed'), 'Section with all overloaded properties should have collapsed class');
                // The section with active properties should NOT be collapsed.
                assert.isFalse(activeSection.element.classList.contains('collapsed'), 'Section with active properties should not be collapsed');
            });
            it('does not collapse non-contributing sections when the setting is disabled', async () => {
                Common.Settings.Settings.instance().moduleSetting('collapse-non-contributing-css-rules').set(false);
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                node.id = 1;
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node,
                    matchedPayload: [
                        {
                            rule: {
                                selectorList: { selectors: [{ text: 'div' }], text: 'div' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'blue' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                        {
                            rule: {
                                selectorList: { selectors: [{ text: '#id' }], text: '#id' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'red' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                    ],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                const overloadedSection = sectionBlocks[0].sections.find(section => section.headerText() === 'div');
                if (!overloadedSection) {
                    assert.fail('Expected div section to exist');
                }
                assert.isFalse(overloadedSection.element.classList.contains('collapsed'), 'Section with all overloaded properties should not collapse when the setting is disabled');
                assert.isFalse(overloadedSection.element.classList.contains('collapsible'), 'Section should not be marked collapsible when automatic collapsing is disabled');
            });
            it('collapses an empty section (no leading properties)', async () => {
                enableCollapse();
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                node.id = 1;
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node,
                    matchedPayload: [
                        // Empty rule (no properties -- common with CSS nesting).
                        {
                            rule: {
                                selectorList: { selectors: [{ text: '.empty' }], text: '.empty' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                        // Non-empty rule.
                        {
                            rule: {
                                selectorList: { selectors: [{ text: '#id' }], text: '#id' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'red' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                    ],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                assert.lengthOf(sectionBlocks[0].sections, 2);
                const activeSection = sectionBlocks[0].sections[0]; // #id - has properties
                const emptySection = sectionBlocks[0].sections[1]; // .empty - no properties
                assert.isTrue(emptySection.element.classList.contains('collapsed'), 'Empty section should be collapsed');
                assert.isFalse(activeSection.element.classList.contains('collapsed'), 'Non-empty section should not be collapsed');
            });
            it('does NOT collapse a section containing only disabled properties', async () => {
                enableCollapse();
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                node.id = 1;
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node,
                    matchedPayload: [
                        // Rule with a disabled (user-toggled-off) property.
                        {
                            rule: {
                                selectorList: { selectors: [{ text: '.disabled-props' }], text: '.disabled-props' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'blue', disabled: true }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                        // Another rule so the disabled one has something to compare against.
                        {
                            rule: {
                                selectorList: { selectors: [{ text: '#id' }], text: '#id' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'background', value: 'white' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                    ],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                assert.lengthOf(sectionBlocks[0].sections, 2);
                const disabledSection = sectionBlocks[0].sections[1]; // .disabled-props
                // A section with disabled properties should NOT be collapsed.
                // Disabled properties are user-intentional and should remain visible.
                assert.isFalse(disabledSection.element.classList.contains('collapsed'), 'Section with disabled properties should not be collapsed');
            });
            it('does NOT collapse a section with at least one active property', async () => {
                enableCollapse();
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                node.id = 1;
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node,
                    matchedPayload: [
                        // Rule with one overloaded and one active property.
                        {
                            rule: {
                                selectorList: { selectors: [{ text: 'div' }], text: 'div' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [
                                        { name: 'color', value: 'blue' }, // will be overloaded
                                        { name: 'margin', value: '10px' }, // will be active (not set elsewhere)
                                    ],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                        // Higher specificity rule that overrides color.
                        {
                            rule: {
                                selectorList: { selectors: [{ text: '#id' }], text: '#id' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'red' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                    ],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                assert.lengthOf(sectionBlocks[0].sections, 2);
                const mixedSection = sectionBlocks[0].sections[1]; // div - has one active, one overloaded
                assert.isFalse(mixedSection.element.classList.contains('collapsed'), 'Section with at least one active property should not be collapsed');
            });
            it('expands a collapsed section when jump-to targets it', async () => {
                enableCollapse();
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                node.id = 1;
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node,
                    matchedPayload: [
                        // Lower specificity -- all overloaded.
                        {
                            rule: {
                                selectorList: { selectors: [{ text: 'div' }], text: 'div' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'blue' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                        // Higher specificity.
                        {
                            rule: {
                                selectorList: { selectors: [{ text: '#id' }], text: '#id' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'red' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                    ],
                });
                stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                // In production, requestUpdate() stores these blocks internally.
                // In this unit test we call rebuildSectionsForMatchedStyleRulesForTest()
                // directly, so wire them into the pane explicitly for jump-to lookup.
                stylesSidebarPane.sectionBlocks =
                    sectionBlocks;
                const overloadedSection = sectionBlocks[0].sections.find(section => section.headerText() === 'div');
                if (!overloadedSection) {
                    assert.fail('Expected div section to exist');
                }
                // Reveal the overloaded property via jump-to.
                const overloadedProperty = overloadedSection.style().leadingProperties()[0];
                if (!overloadedProperty) {
                    assert.fail('Expected overloaded section to have at least one property');
                }
                assert.strictEqual(matchedStyles.propertyState(overloadedProperty), "Overloaded" /* SDK.CSSMatchedStyles.PropertyState.OVERLOADED */, 'Expected div{color:blue} to be overloaded by #id{color:red}');
                // Verify section is initially collapsed.
                assert.isTrue(overloadedSection.element.classList.contains('collapsed'), 'Section should be initially collapsed');
                stylesSidebarPane.revealProperty(overloadedProperty);
                // After reveal, the section should be expanded (not collapsed).
                assert.isFalse(overloadedSection.element.classList.contains('collapsed'), 'Section should be expanded after revealProperty');
            });
            it('can be manually expanded and re-collapsed via toggle', async () => {
                enableCollapse();
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                node.id = 1;
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node,
                    matchedPayload: [
                        {
                            rule: {
                                selectorList: { selectors: [{ text: 'div' }], text: 'div' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'blue' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                        {
                            rule: {
                                selectorList: { selectors: [{ text: '#id' }], text: '#id' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'red' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                    ],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                const overloadedSection = sectionBlocks[0].sections.find(section => section.headerText() === 'div');
                if (!overloadedSection) {
                    assert.fail('Expected div section to exist');
                }
                // Initially collapsed.
                assert.isTrue(overloadedSection.isCollapsed(), 'Section should be initially collapsed');
                assert.isTrue(overloadedSection.element.classList.contains('collapsible'), 'Section should have collapsible class');
                // Expand manually.
                overloadedSection.expand();
                assert.isFalse(overloadedSection.isCollapsed(), 'Section should be expanded after expand()');
                assert.isFalse(overloadedSection.element.classList.contains('collapsed'), 'Section should not have collapsed class after expand()');
                // Still marked collapsible so the icon remains visible.
                assert.isTrue(overloadedSection.element.classList.contains('collapsible'), 'Section should retain collapsible class after manual expand');
            });
            it('reacts to toggling the collapse-non-contributing-css-rules setting at runtime', async () => {
                enableCollapse();
                const collapseSetting = Common.Settings.Settings.instance().moduleSetting('collapse-non-contributing-css-rules');
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                node.id = 1;
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node,
                    matchedPayload: [
                        {
                            rule: {
                                selectorList: { selectors: [{ text: 'div' }], text: 'div' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'blue' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                        {
                            rule: {
                                selectorList: { selectors: [{ text: '#id' }], text: '#id' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'red' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        },
                    ],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                // `allSections()` reads from `sectionBlocks`, which the test helper doesn't populate.
                stylesSidebarPane.sectionBlocks =
                    sectionBlocks;
                const overloadedSection = sectionBlocks[0].sections.find(section => section.headerText() === 'div');
                if (!overloadedSection) {
                    assert.fail('Expected div section to exist');
                }
                assert.isTrue(overloadedSection.element.classList.contains('collapsed'), 'Section should be collapsed while the setting is enabled');
                collapseSetting.set(false);
                assert.isFalse(overloadedSection.element.classList.contains('collapsed'), 'Section should expand when the setting is turned off');
                assert.isFalse(overloadedSection.element.classList.contains('collapsible'), 'Section should drop the collapsible marker when the setting is turned off');
                collapseSetting.set(true);
                assert.isTrue(overloadedSection.element.classList.contains('collapsed'), 'Section should collapse again when the setting is re-enabled');
                assert.isTrue(overloadedSection.element.classList.contains('collapsible'), 'Section should be marked collapsible again when the setting is re-enabled');
            });
            it('expands collapsed sections before adding a new blank property', async () => {
                enableCollapse();
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                node.id = 1;
                const styleSheetId = '0';
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node,
                    matchedPayload: [
                        {
                            rule: {
                                selectorList: { selectors: [{ text: 'div' }], text: 'div' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'blue' }],
                                    shorthandEntries: [],
                                    styleSheetId,
                                    range: { startLine: 0, startColumn: 0, endLine: 0, endColumn: 15 },
                                },
                            },
                            matchingSelectors: [0],
                        },
                        {
                            rule: {
                                selectorList: { selectors: [{ text: '#id' }], text: '#id' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'red' }],
                                    shorthandEntries: [],
                                    styleSheetId,
                                    range: { startLine: 1, startColumn: 0, endLine: 1, endColumn: 15 },
                                },
                            },
                            matchingSelectors: [0],
                        },
                    ],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                const overloadedSection = sectionBlocks[0].sections.find(section => section.headerText() === 'div');
                if (!overloadedSection) {
                    assert.fail('Expected div section to exist');
                }
                assert.isTrue(overloadedSection.isCollapsed(), 'Section should be initially collapsed');
                const treeElement = overloadedSection.addNewBlankProperty();
                assert.isFalse(overloadedSection.isCollapsed(), 'Section should expand before adding a new property');
                assert.exists(treeElement, 'Expected a new blank property tree element to be created');
            });
            it('collapses inherited sections with all overloaded properties', async () => {
                enableCollapse();
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                node.nodeName.returns('div');
                node.id = 1;
                const parentNode = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                parentNode.id = 2;
                node.parentNode = parentNode;
                parentNode.nodeName.returns('body');
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node,
                    matchedPayload: [{
                            rule: {
                                selectorList: { selectors: [{ text: 'div' }], text: 'div' },
                                origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                style: {
                                    cssProperties: [{ name: 'color', value: 'red' }],
                                    shorthandEntries: [],
                                },
                            },
                            matchingSelectors: [0],
                        }],
                    inheritedPayload: [{
                            matchedCSSRules: [{
                                    rule: {
                                        selectorList: { selectors: [{ text: 'body' }], text: 'body' },
                                        origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                                        style: {
                                            // color is inheritable but overloaded by the div rule above.
                                            cssProperties: [{ name: 'color', value: 'blue' }],
                                            shorthandEntries: [],
                                        },
                                    },
                                    matchingSelectors: [0],
                                }],
                        }],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                // Block 0: element styles (div rule), Block 1: inherited from body.
                assert.isTrue(sectionBlocks.length >= 2);
                const inheritedSection = sectionBlocks[1].sections.find(section => section.headerText() === 'body');
                if (!inheritedSection) {
                    assert.fail('Expected inherited body section to exist');
                }
                const inheritedProperty = inheritedSection.style().leadingProperties()[0];
                if (!inheritedProperty) {
                    assert.fail('Expected inherited section to have at least one property');
                }
                assert.strictEqual(matchedStyles.propertyState(inheritedProperty), "Overloaded" /* SDK.CSSMatchedStyles.PropertyState.OVERLOADED */, 'Expected inherited color to be overloaded by element color');
                // The inherited section's color property is overloaded by the div rule,
                // so it should be collapsed.
                assert.isTrue(inheritedSection.element.classList.contains('collapsed'), 'Inherited section with all overloaded properties should be collapsed');
            });
        });
        it('should add "Other @rules" section to the end', async () => {
            const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
            const matchedStyles = await getMatchedStyles({
                connection,
                cssModel: stylesSidebarPane.cssModel(),
                node: sinon.createStubInstance(SDK.DOMModel.DOMNode),
                atRules: [
                    {
                        name: { text: '--palette' },
                        type: "font-palette-values" /* Protocol.CSS.CSSAtRuleType.FontPaletteValues */,
                        origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                        style: {
                            cssProperties: [{ name: 'font-family', value: 'Bixa' }, { name: 'override-colors', value: '0 red' }],
                            shorthandEntries: [],
                        },
                    },
                    {
                        type: "font-face" /* Protocol.CSS.CSSAtRuleType.FontFace */,
                        origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                        style: {
                            cssProperties: [{ name: 'font-family', value: 'Bixa' }, { name: 'src', value: 'local(Bixa)' }],
                            shorthandEntries: [],
                        },
                    },
                    {
                        type: "font-feature-values" /* Protocol.CSS.CSSAtRuleType.FontFeatureValues */,
                        name: { text: 'Bixa' },
                        subsection: "swash" /* Protocol.CSS.CSSAtRuleSubsection.Swash */,
                        origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                        style: {
                            cssProperties: [{ name: 'fancy', value: '1' }],
                            shorthandEntries: [],
                        },
                    },
                ],
            });
            const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
            assert.lengthOf(sectionBlocks, 2);
            assert.strictEqual(sectionBlocks[1].titleElement()?.textContent, 'Other @rules');
            assert.lengthOf(sectionBlocks[1].sections, 3);
            const contents = [
                '@font-palette-values --palette {    font-family: Bixa;    override-colors: 0 red;}',
                '@font-face {    font-family: Bixa;    src: local(Bixa);}',
                '@font-feature-values Bixa {    @swash {        fancy: 1;    }}',
            ];
            for (let i = 0; i < 3; i++) {
                const section = sectionBlocks[1].sections[i];
                assert.instanceOf(section, Elements.StylePropertiesSection.AtRuleSection);
                assert.strictEqual(section.element.deepTextContent(), contents[i]);
            }
        });
        it('should add @function section to the end', async () => {
            const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
            const matchedStyles = await getMatchedStyles({
                connection,
                cssModel: stylesSidebarPane.cssModel(),
                node: sinon.createStubInstance(SDK.DOMModel.DOMNode),
                functionRules: [{
                        name: { text: '--f' },
                        parameters: [{ name: '--x', type: '*' }, { name: '--y', type: '*' }],
                        origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                        children: [
                            {
                                condition: {
                                    media: {
                                        text: '(width > 400px)',
                                        source: "mediaRule" /* Protocol.CSS.CSSMediaSource.MediaRule */,
                                    },
                                    conditionText: '<unused>',
                                    children: [
                                        {
                                            condition: {
                                                containerQueries: {
                                                    text: '(width > 300px)',
                                                    conditionText: '--foo (width > 300px)',
                                                },
                                                conditionText: '<unused>',
                                                children: [
                                                    {
                                                        condition: {
                                                            supports: {
                                                                text: '(color: red)',
                                                                active: true,
                                                            },
                                                            conditionText: '<unused>',
                                                            children: [
                                                                {
                                                                    style: {
                                                                        cssProperties: [{ name: 'result', value: 'var(--y)' }],
                                                                        shorthandEntries: [],
                                                                    }
                                                                },
                                                            ],
                                                        },
                                                    },
                                                ],
                                            },
                                        },
                                    ],
                                },
                            },
                            {
                                style: {
                                    cssProperties: [{ name: 'result', value: 'var(--x)' }],
                                    shorthandEntries: [],
                                }
                            },
                        ],
                    }],
            });
            const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
            assert.lengthOf(sectionBlocks, 2);
            assert.strictEqual(sectionBlocks[1].titleElement()?.textContent, '@function');
            assert.lengthOf(sectionBlocks[1].sections, 1);
            assert.instanceOf(sectionBlocks[1].sections[0], Elements.StylePropertiesSection.FunctionRuleSection);
            assert.strictEqual(sectionBlocks[1].sections[0].element.deepTextContent().replaceAll(/\s+/g, ' ').trim(), '--f(--x, --y) { @media (width > 400px) { @container --foo (width > 300px) { @supports (color: red) { result: var(--y); } } } result: var(--x);}');
        });
        it('should add @functions with same name but different tree scope depths', async () => {
            const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
            const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
            node.id = 1;
            node.backendNodeId.returns(1);
            const parent = sinon.createStubInstance(SDK.DOMModel.DOMNode);
            parent.id = 2;
            parent.backendNodeId.returns(2);
            node.parentNode = parent;
            const grandparent = sinon.createStubInstance(SDK.DOMModel.DOMNode);
            grandparent.id = 3;
            grandparent.backendNodeId.returns(3);
            parent.parentNode = grandparent;
            node.getTreeRoot.returns(node);
            const matchedStyles = await getMatchedStyles({
                connection,
                cssModel: stylesSidebarPane.cssModel(),
                node,
                functionRules: [
                    {
                        name: { text: '--f' },
                        parameters: [],
                        origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                        children: [{
                                style: {
                                    cssProperties: [{ name: 'result', value: 'red' }],
                                    shorthandEntries: [],
                                }
                            }],
                        originTreeScopeNodeId: 2,
                    },
                    {
                        name: { text: '--f' },
                        parameters: [],
                        origin: "regular" /* Protocol.CSS.StyleSheetOrigin.Regular */,
                        children: [{
                                style: {
                                    cssProperties: [{ name: 'result', value: 'blue' }],
                                    shorthandEntries: [],
                                }
                            }],
                        originTreeScopeNodeId: 3,
                    }
                ],
            });
            const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
            assert.lengthOf(sectionBlocks, 2);
            assert.strictEqual(sectionBlocks[1].titleElement()?.textContent, '@function');
            assert.lengthOf(sectionBlocks[1].sections, 2);
            assert.instanceOf(sectionBlocks[1].sections[0], Elements.StylePropertiesSection.FunctionRuleSection);
            assert.instanceOf(sectionBlocks[1].sections[1], Elements.StylePropertiesSection.FunctionRuleSection);
            assert.strictEqual(sectionBlocks[1].sections[0].element.deepTextContent().replaceAll(/\s+/g, ' ').trim(), '--f() { result: red;}');
            assert.strictEqual(sectionBlocks[1].sections[1].element.deepTextContent().replaceAll(/\s+/g, ' ').trim(), '--f() { result: blue;}');
            assert.strictEqual(sectionBlocks[1].sections[0].treeScopeDistance(), 1);
            assert.strictEqual(sectionBlocks[1].sections[1].treeScopeDistance(), 2);
        });
        describe('Animation styles', () => {
            function mockGetAnimatedComputedStyles(response) {
                connection.setHandler('CSS.getAnimatedStylesForNode', null);
                connection.setSuccessHandler('CSS.getAnimatedStylesForNode', () => response);
            }
            beforeEach(() => {
                sinon.stub(PanelsCommon.DOMLinkifier.Linkifier.instance(), 'linkify').returns(html `<div></div>`);
                sinon.stub(UI.ViewManager.ViewManager.instance(), 'isViewVisible').returns(false);
                updateHostConfig({
                    devToolsAnimationStylesInStylesTab: {
                        enabled: true,
                    },
                });
            });
            it('should not render transition & animation styles when the animations panel is not visible', async () => {
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node: sinon.createStubInstance(SDK.DOMModel.DOMNode),
                    animationStylesPayload: [
                        {
                            name: '--animation-name',
                            style: {
                                cssProperties: [{
                                        name: 'background-color',
                                        value: 'blue',
                                    }],
                                shorthandEntries: [],
                            },
                        },
                    ],
                    transitionsStylePayload: {
                        cssProperties: [{
                                name: 'color',
                                value: 'red',
                            }],
                        shorthandEntries: [],
                    },
                    inheritedAnimatedPayload: [],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                assert.lengthOf(sectionBlocks[0].sections, 0);
            });
            it('should render transition & animation styles when the animations panel is not visible but the css-animations-only-when-animations-tab-open setting is disabled', async () => {
                Common.Settings.Settings.instance().moduleSetting('css-animations-only-when-animations-tab-open').set(false);
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node: sinon.createStubInstance(SDK.DOMModel.DOMNode),
                    animationStylesPayload: [
                        {
                            name: '--animation-name',
                            style: {
                                cssProperties: [{
                                        name: 'background-color',
                                        value: 'blue',
                                    }],
                                shorthandEntries: [],
                            },
                        },
                    ],
                    transitionsStylePayload: {
                        cssProperties: [{
                                name: 'color',
                                value: 'red',
                            }],
                        shorthandEntries: [],
                    },
                    inheritedAnimatedPayload: [],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                assert.lengthOf(sectionBlocks[0].sections, 2);
            });
            it('should render transition & animation styles in the styles tab when the animations panel is visible', async () => {
                UI.ViewManager.ViewManager.instance().isViewVisible.returns(true);
                const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                const matchedStyles = await getMatchedStyles({
                    connection,
                    cssModel: stylesSidebarPane.cssModel(),
                    node: sinon.createStubInstance(SDK.DOMModel.DOMNode),
                    animationStylesPayload: [
                        {
                            name: '--animation-name',
                            style: {
                                cssProperties: [{
                                        name: 'background-color',
                                        value: 'blue',
                                    }],
                                shorthandEntries: [],
                            },
                        },
                        {
                            style: {
                                cssProperties: [{
                                        name: 'color',
                                        value: 'blue',
                                    }],
                                shorthandEntries: [],
                            },
                        },
                    ],
                    transitionsStylePayload: {
                        cssProperties: [{
                                name: 'color',
                                value: 'red',
                            }],
                        shorthandEntries: [],
                    },
                    inheritedAnimatedPayload: [],
                });
                const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                assert.lengthOf(sectionBlocks[0].sections, 3);
                assert.strictEqual(sectionBlocks[0].sections[0].headerText(), 'transitions style');
                assert.strictEqual(sectionBlocks[0].sections[1].headerText(), '--animation-name animation');
                assert.strictEqual(sectionBlocks[0].sections[2].headerText(), 'animation style');
            });
            describe('should auto update animated style sections when onComputedStyleChanged called', () => {
                beforeEach(() => {
                    UI.ViewManager.ViewManager.instance().isViewVisible.returns(true);
                });
                describe('transition styles', () => {
                    it('should trigger re-render when there was no transition style before', async () => {
                        mockGetAnimatedComputedStyles({
                            transitionsStyle: {
                                cssProperties: [{
                                        name: 'color',
                                        value: 'red',
                                    }],
                                shorthandEntries: [],
                            },
                        });
                        const { node } = createStubbedDomNodeWithModels({ nodeId: 1 });
                        const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel(node));
                        const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
                        const matchedStyles = await getMatchedStyles({
                            connection,
                            cssModel: stylesSidebarPane.cssModel(),
                            node,
                            transitionsStylePayload: null,
                        });
                        stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
                        const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                        assert.lengthOf(sectionBlocks[0].sections, 0);
                        const handledComputedStyleChanged = expectCall(sinon.stub(stylesSidebarPane, 'handledComputedStyleChangedForTest'));
                        stylesSidebarPane.onComputedStyleChanged();
                        await handledComputedStyleChanged;
                        sinon.assert.called(resetUpdateSpy);
                    });
                    it('should update value only when there was a transition style before', async () => {
                        mockGetAnimatedComputedStyles({
                            transitionsStyle: {
                                cssProperties: [{
                                        name: 'color',
                                        value: 'red',
                                    }],
                                shorthandEntries: [],
                            },
                        });
                        const { node } = createStubbedDomNodeWithModels({ nodeId: 1 });
                        const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel(node));
                        const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
                        const matchedStyles = await getMatchedStyles({
                            connection,
                            cssModel: stylesSidebarPane.cssModel(),
                            node,
                            transitionsStylePayload: {
                                cssProperties: [{
                                        name: 'color',
                                        value: 'blue',
                                    }],
                                shorthandEntries: [],
                            },
                        });
                        stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
                        const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                        assert.lengthOf(sectionBlocks[0].sections, 1);
                        assert.include(sectionBlocks[0].sections[0].propertiesTreeOutline.contentElement.textContent, 'color: blue;');
                        const handledComputedStyleChanged = expectCall(sinon.stub(stylesSidebarPane, 'handledComputedStyleChangedForTest'));
                        stylesSidebarPane.onComputedStyleChanged();
                        await handledComputedStyleChanged;
                        assert.include(sectionBlocks[0].sections[0].propertiesTreeOutline.contentElement.textContent, 'color: red;');
                        sinon.assert.notCalled(resetUpdateSpy);
                    });
                });
                describe('animation styles', () => {
                    it('should trigger re-render when there was no animation style before', async () => {
                        mockGetAnimatedComputedStyles({
                            animationStyles: [{
                                    name: '--animation',
                                    style: {
                                        cssProperties: [{
                                                name: 'color',
                                                value: 'red',
                                            }],
                                        shorthandEntries: [],
                                    },
                                }],
                        });
                        const { node } = createStubbedDomNodeWithModels({ nodeId: 1 });
                        const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel(node));
                        const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
                        const matchedStyles = await getMatchedStyles({
                            connection,
                            cssModel: stylesSidebarPane.cssModel(),
                            node,
                            animationStylesPayload: [],
                        });
                        stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
                        const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                        assert.lengthOf(sectionBlocks[0].sections, 0);
                        const handledComputedStyleChanged = expectCall(sinon.stub(stylesSidebarPane, 'handledComputedStyleChangedForTest'));
                        stylesSidebarPane.onComputedStyleChanged();
                        await handledComputedStyleChanged;
                        sinon.assert.called(resetUpdateSpy);
                    });
                    it('should trigger re-render when there is no animation style after', async () => {
                        mockGetAnimatedComputedStyles({
                            animationStyles: [],
                        });
                        const { node } = createStubbedDomNodeWithModels({ nodeId: 1 });
                        const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel(node));
                        const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
                        const matchedStyles = await getMatchedStyles({
                            connection,
                            cssModel: stylesSidebarPane.cssModel(),
                            node,
                            animationStylesPayload: [{
                                    style: {
                                        cssProperties: [{
                                                name: 'color',
                                                value: 'blue',
                                            }],
                                        shorthandEntries: [],
                                    },
                                }],
                        });
                        stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
                        const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                        assert.lengthOf(sectionBlocks[0].sections, 1);
                        const handledComputedStyleChanged = expectCall(sinon.stub(stylesSidebarPane, 'handledComputedStyleChangedForTest'));
                        stylesSidebarPane.onComputedStyleChanged();
                        await handledComputedStyleChanged;
                        sinon.assert.called(resetUpdateSpy);
                    });
                    it('should update value only when there was the animation style before', async () => {
                        mockGetAnimatedComputedStyles({
                            animationStyles: [{
                                    style: {
                                        cssProperties: [{
                                                name: 'color',
                                                value: 'red',
                                            }],
                                        shorthandEntries: [],
                                    },
                                }],
                        });
                        const { node } = createStubbedDomNodeWithModels({ nodeId: 1 });
                        const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel(node));
                        const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
                        const matchedStyles = await getMatchedStyles({
                            connection,
                            cssModel: stylesSidebarPane.cssModel(),
                            node,
                            animationStylesPayload: [{
                                    style: {
                                        cssProperties: [{
                                                name: 'color',
                                                value: 'blue',
                                            }],
                                        shorthandEntries: [],
                                    },
                                }],
                        });
                        stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
                        const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                        assert.lengthOf(sectionBlocks[0].sections, 1);
                        assert.include(sectionBlocks[0].sections[0].propertiesTreeOutline.contentElement.textContent, 'color: blue;');
                        const handledComputedStyleChanged = expectCall(sinon.stub(stylesSidebarPane, 'handledComputedStyleChangedForTest'));
                        stylesSidebarPane.onComputedStyleChanged();
                        await handledComputedStyleChanged;
                        assert.include(sectionBlocks[0].sections[0].propertiesTreeOutline.contentElement.textContent, 'color: red;');
                        sinon.assert.notCalled(resetUpdateSpy);
                    });
                });
                describe('inherited animated styles', () => {
                    describe('transition styles', () => {
                        it('should trigger re-render when there was no inherited transition style but there is a new one now', async () => {
                            mockGetAnimatedComputedStyles({
                                inherited: [{
                                        transitionsStyle: {
                                            cssProperties: [{
                                                    name: 'color',
                                                    value: 'red',
                                                }],
                                            shorthandEntries: [],
                                        },
                                    }],
                            });
                            const { node } = createStubbedDomNodeWithModels({ nodeId: 1 });
                            node.parentNode = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                            const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel(node));
                            const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
                            const matchedStyles = await getMatchedStyles({
                                connection,
                                cssModel: stylesSidebarPane.cssModel(),
                                node,
                            });
                            stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
                            const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                            assert.lengthOf(sectionBlocks[0].sections, 0);
                            const handledComputedStyleChanged = expectCall(sinon.stub(stylesSidebarPane, 'handledComputedStyleChangedForTest'));
                            stylesSidebarPane.onComputedStyleChanged();
                            await handledComputedStyleChanged;
                            sinon.assert.called(resetUpdateSpy);
                        });
                        it('should not trigger re-render when there was no inherited transition style and the new one does not contain inherited property', async () => {
                            mockGetAnimatedComputedStyles({
                                inherited: [{
                                        transitionsStyle: {
                                            cssProperties: [{
                                                    name: 'background-color',
                                                    value: 'red',
                                                }],
                                            shorthandEntries: [],
                                        },
                                    }],
                            });
                            const { node } = createStubbedDomNodeWithModels({ nodeId: 1 });
                            node.parentNode = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                            const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel(node));
                            const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
                            const matchedStyles = await getMatchedStyles({
                                connection,
                                cssModel: stylesSidebarPane.cssModel(),
                                node,
                            });
                            stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
                            const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                            assert.lengthOf(sectionBlocks[0].sections, 0);
                            const handledComputedStyleChanged = expectCall(sinon.stub(stylesSidebarPane, 'handledComputedStyleChangedForTest'));
                            stylesSidebarPane.onComputedStyleChanged();
                            await handledComputedStyleChanged;
                            sinon.assert.notCalled(resetUpdateSpy);
                        });
                        it('should update value only when there is no new inherited transition style and the value is updated', async () => {
                            mockGetAnimatedComputedStyles({
                                inherited: [{
                                        transitionsStyle: {
                                            cssProperties: [{
                                                    name: 'color',
                                                    value: 'red',
                                                }],
                                            shorthandEntries: [],
                                        },
                                    }],
                            });
                            const { node } = createStubbedDomNodeWithModels({ nodeId: 1 });
                            node.parentNode = sinon.createStubInstance(SDK.DOMModel.DOMNode);
                            const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel(node));
                            const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
                            const matchedStyles = await getMatchedStyles({
                                connection,
                                cssModel: stylesSidebarPane.cssModel(),
                                node,
                                inheritedPayload: [{
                                        matchedCSSRules: [],
                                    }],
                                inheritedAnimatedPayload: [{
                                        transitionsStyle: {
                                            cssProperties: [{
                                                    name: 'color',
                                                    value: 'blue',
                                                }],
                                            shorthandEntries: [],
                                        },
                                    }],
                            });
                            stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
                            const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map(), null);
                            assert.lengthOf(sectionBlocks[1].sections, 1);
                            assert.include(sectionBlocks[1].sections[0].propertiesTreeOutline.contentElement.textContent, 'color: blue;');
                            const handledComputedStyleChanged = expectCall(sinon.stub(stylesSidebarPane, 'handledComputedStyleChangedForTest'));
                            stylesSidebarPane.onComputedStyleChanged();
                            await handledComputedStyleChanged;
                            assert.include(sectionBlocks[1].sections[0].propertiesTreeOutline.contentElement.textContent, 'color: red;');
                            sinon.assert.notCalled(resetUpdateSpy);
                        });
                    });
                });
            });
        });
        describe('ai code completion provider callbacks', () => {
            let stylesWrapper;
            let stylesSidebarPane;
            beforeEach(async () => {
                updateHostConfig({
                    devToolsAiCodeCompletionStyles: {
                        enabled: true,
                    },
                    aidaAvailability: {
                        enabled: true,
                        blockedByAge: false,
                        blockedByGeo: false,
                    },
                });
                const aiCodeCompletionProviderStub = sinon.createStubInstance(TextEditor.AiCodeCompletionProvider.AiCodeCompletionProvider);
                aiCodeCompletionProviderStub.extension.returns([]);
                sinon.stub(TextEditor.AiCodeCompletionProvider.AiCodeCompletionProvider, 'createInstance')
                    .returns(aiCodeCompletionProviderStub);
                Common.Settings.Settings.instance().createSetting('ai-code-completion-enabled', true);
                stylesWrapper = new UI.Widget.VBox();
                stylesWrapper.element.classList.add('style-panes-wrapper');
                stylesSidebarPane =
                    new Elements.StylesSidebarPane.StylesSidebarPane(new ComputedStyle.ComputedStyleModel.ComputedStyleModel());
                stylesSidebarPane.show(stylesWrapper.element);
            });
            it('initializes toolbar when the feature is enabled', async () => {
                const providerConfig = stylesSidebarPane.aiCodeCompletionConfig;
                assert.exists(providerConfig);
                providerConfig.onFeatureEnabled();
                assert.exists(stylesWrapper.contentElement.querySelector('div.ai-code-completion-summary-toolbar-container'));
            });
            it('cleans up toolbar when the feature is disabled', async () => {
                const providerConfig = stylesSidebarPane.aiCodeCompletionConfig;
                assert.exists(providerConfig);
                providerConfig.onFeatureEnabled();
                assert.exists(stylesWrapper.contentElement.querySelector('div.ai-code-completion-summary-toolbar-container'));
                providerConfig.onFeatureDisabled();
                assert.notExists(stylesWrapper.contentElement.querySelector('div.ai-code-completion-summary-toolbar-container'));
            });
            it('shows a loading state when a request is triggered', async () => {
                const setLoadingSpy = sinon.stub(PanelsCommon.AiCodeCompletionSummaryToolbar.AiCodeCompletionSummaryToolbar.prototype, 'setLoading');
                const providerConfig = stylesSidebarPane.aiCodeCompletionConfig;
                assert.exists(providerConfig);
                providerConfig.onFeatureEnabled();
                providerConfig.onRequestTriggered();
                sinon.assert.calledOnce(setLoadingSpy);
                assert.isTrue(setLoadingSpy.firstCall.args[0]);
            });
            it('hides the loading indicator when a response is received', async () => {
                const setLoadingSpy = sinon.stub(PanelsCommon.AiCodeCompletionSummaryToolbar.AiCodeCompletionSummaryToolbar.prototype, 'setLoading');
                const providerConfig = stylesSidebarPane.aiCodeCompletionConfig;
                assert.exists(providerConfig);
                providerConfig.onFeatureEnabled();
                providerConfig.onRequestTriggered();
                sinon.assert.calledOnce(setLoadingSpy);
                assert.isTrue(setLoadingSpy.firstCall.args[0]);
                providerConfig.onResponseReceived();
                sinon.assert.calledTwice(setLoadingSpy);
                assert.isFalse(setLoadingSpy.secondCall.args[0]);
            });
            it('attaches the citations toolbar when a suggestion with citations is accepted', async () => {
                const updateCitationsSpy = sinon.spy(PanelsCommon.AiCodeCompletionSummaryToolbar.AiCodeCompletionSummaryToolbar.prototype, 'updateCitations');
                const providerConfig = stylesSidebarPane.aiCodeCompletionConfig;
                assert.exists(providerConfig);
                providerConfig.onFeatureEnabled();
                providerConfig.onResponseReceived();
                providerConfig.onSuggestionAccepted([{ uri: 'https://example.com/source' }]);
                sinon.assert.calledOnce(updateCitationsSpy);
                assert.deepEqual(updateCitationsSpy.firstCall.args, [['https://example.com/source']]);
            });
            it('does not attach the citations toolbar if there are no citations', async () => {
                const updateCitationsSpy = sinon.spy(PanelsCommon.AiCodeCompletionSummaryToolbar.AiCodeCompletionSummaryToolbar.prototype, 'updateCitations');
                const providerConfig = stylesSidebarPane.aiCodeCompletionConfig;
                assert.exists(providerConfig);
                providerConfig.onFeatureEnabled();
                providerConfig.onResponseReceived();
                providerConfig.onSuggestionAccepted([]);
                sinon.assert.notCalled(updateCitationsSpy);
            });
        });
    });
    describe('IdleCallbackManager', () => {
        // IdleCallbackManager delegates work using requestIdleCallback, which does not generally execute requested callbacks
        // in order. This test verifies that callbacks do happen in order even if timeouts are run out.
        it('schedules callbacks in order', async () => {
            /** Override the default timeout with a very short one **/
            class QuickIdleCallbackManager extends Elements.StylesSidebarPane.IdleCallbackManager {
                scheduleIdleCallback(_) {
                    super.scheduleIdleCallback(1);
                }
            }
            const timeout = (time) => new Promise(resolve => setTimeout(resolve, time));
            const elements = [];
            const callbacks = new QuickIdleCallbackManager();
            callbacks.schedule(() => elements.push(0));
            callbacks.schedule(() => elements.push(1));
            callbacks.schedule(() => elements.push(2));
            callbacks.schedule(() => elements.push(3));
            await timeout(10);
            callbacks.schedule(() => elements.push(4));
            callbacks.schedule(() => elements.push(5));
            callbacks.schedule(() => elements.push(6));
            callbacks.schedule(() => elements.push(7));
            await timeout(10);
            await callbacks.awaitDone();
            assert.deepEqual(elements, [0, 1, 2, 3, 4, 5, 6, 7]);
        });
    });
    describe('CSSPropertyPrompt', () => {
        setupLocaleHooks();
        const CSSPropertyPrompt = Elements.StylesSidebarPane.CSSPropertyPrompt;
        const CSS_VARIABLES_FOR_TEST = {
            '--rgb-color': 'rgb(0 0 0)',
            '--wide-gamut-color': 'lch(0 0 0)',
        };
        let aiCodeCompletionProvider;
        let section;
        let mockTreeItem;
        const noop = () => { };
        beforeEach(() => {
            section = sinon.createStubInstance(Elements.StylePropertiesSection.StylePropertiesSection);
            let activeAiSuggestion;
            Object.defineProperty(section, 'activeAiSuggestion', {
                get: () => activeAiSuggestion,
                set: aiSuggestion => {
                    activeAiSuggestion = aiSuggestion;
                },
                configurable: true,
            });
            sinon.stub(section, 'activeAiSuggestion').get(() => activeAiSuggestion);
            section.commitActiveAiSuggestion.resolves();
            mockTreeItem = {
                property: {
                    name: 'color',
                },
                node() {
                    return {
                        isSVGNode() {
                            return false;
                        },
                        domModel() {
                            return {
                                cssModel() {
                                    return {
                                        getComputedStyle() {
                                            return new Map();
                                        },
                                    };
                                },
                            };
                        },
                    };
                },
                matchedStyles() {
                    return {
                        availableCSSVariables() {
                            return ['--rgb-color', '--wide-gamut-color'];
                        },
                        computeCSSVariable(_, completion) {
                            return { value: CSS_VARIABLES_FOR_TEST[completion], declaration: null };
                        },
                    };
                },
                section() {
                    return section;
                },
                showGhostTextInValue(_text) { },
                stylesContainer() {
                    const pane = sinon.createStubInstance(Elements.StylesSidebarPane.StylesSidebarPane);
                    const cssModel = sinon.createStubInstance(SDK.CSSModel.CSSModel);
                    pane.cssModel.returns(cssModel);
                    aiCodeCompletionProvider =
                        sinon.createStubInstance(Elements.StylesAiCodeCompletionProvider.StylesAiCodeCompletionProvider);
                    pane.aiCodeCompletionProvider = aiCodeCompletionProvider;
                    return pane;
                }
            };
        });
        describeWithEnvironment('value autocompletion', () => {
            it('shows autocomplete item with color swatch for CSS variables with RGB color', async () => {
                const attachedElement = document.createElement('div');
                renderElementIntoDOM(attachedElement);
                const cssPropertyPrompt = new CSSPropertyPrompt(mockTreeItem, false);
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                const spyObj = sinon.spy(cssPropertyPrompt.suggestBoxForTest());
                cssPropertyPrompt.setText('var(--rgb');
                await cssPropertyPrompt.complete(true);
                const colorCompletions = spyObj?.updateSuggestions.firstCall.args[1];
                const renderedElement = colorCompletions?.[0].subtitleRenderer?.();
                assert.instanceOf(renderedElement, InlineEditor.ColorSwatch.ColorSwatch);
            });
            it('shows autocomplete item with color swatch for CSS variables with wide gamut color', async () => {
                const attachedElement = document.createElement('div');
                renderElementIntoDOM(attachedElement);
                const cssPropertyPrompt = new CSSPropertyPrompt(mockTreeItem, false);
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                const spyObj = sinon.spy(cssPropertyPrompt.suggestBoxForTest());
                cssPropertyPrompt.setText('var(--wide');
                await cssPropertyPrompt.complete(true);
                const colorCompletions = spyObj?.updateSuggestions.firstCall.args[1];
                const renderedElement = colorCompletions?.[0].subtitleRenderer?.();
                assert.instanceOf(renderedElement, InlineEditor.ColorSwatch.ColorSwatch);
            });
            it('shows autocomplete property names for CSS aliases', async () => {
                const attachedElement = document.createElement('div');
                renderElementIntoDOM(attachedElement);
                const cssPropertyPrompt = new CSSPropertyPrompt(mockTreeItem, true);
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                const spyObj = sinon.spy(cssPropertyPrompt.suggestBoxForTest());
                cssPropertyPrompt.setText('word-wra');
                await cssPropertyPrompt.complete(true);
                const completions = spyObj?.updateSuggestions.firstCall.args[1];
                assert.strictEqual(completions?.[0].text, 'word-wrap');
                assert.strictEqual(completions?.[1].text, 'overflow-wrap');
                assert.strictEqual(completions?.[1].subtitle, '= word-wrap');
            });
            it('returns no completions when property name contains invalid characters', async () => {
                const attachedElement = document.createElement('div');
                renderElementIntoDOM(attachedElement);
                const cssPropertyPrompt = new CSSPropertyPrompt(mockTreeItem, true);
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                const suggestBox = cssPropertyPrompt.suggestBoxForTest();
                assert.exists(suggestBox);
                const spyObj = sinon.spy(suggestBox);
                cssPropertyPrompt.setText('height"');
                await cssPropertyPrompt.complete(true);
                sinon.assert.notCalled(spyObj.updateSuggestions);
            });
            it('allows completions for valid property names', async () => {
                const attachedElement = document.createElement('div');
                renderElementIntoDOM(attachedElement);
                const cssPropertyPrompt = new CSSPropertyPrompt(mockTreeItem, true);
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                const spyObj = sinon.spy(cssPropertyPrompt.suggestBoxForTest());
                cssPropertyPrompt.setText('backgrou');
                await cssPropertyPrompt.complete(true);
                assert.isTrue(spyObj?.updateSuggestions.called);
                const completions = spyObj?.updateSuggestions.firstCall.args[1];
                assert.isAbove(completions.length, 0);
            });
        });
        describe('AI code completion', () => {
            let attachedElement;
            let cssPropertyPrompt;
            beforeEach(() => {
                updateHostConfig({
                    devToolsAiCodeCompletionStyles: {
                        enabled: true,
                    },
                    aidaAvailability: {
                        enabled: true,
                        blockedByAge: false,
                        blockedByGeo: false,
                    }
                });
                attachedElement = document.createElement('div');
                renderElementIntoDOM(attachedElement);
                cssPropertyPrompt = new Elements.StylesSidebarPane.CSSPropertyPrompt(mockTreeItem, false);
            });
            it('getCompletionHint returns null if suggestBox is not visible', () => {
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                assert.exists(cssPropertyPrompt.aiCodeCompletionProvider?.getCompletionHint);
                assert.isNull(cssPropertyPrompt.aiCodeCompletionProvider.getCompletionHint());
            });
            it('getCompletionHint returns the correct completion hint', async () => {
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                cssPropertyPrompt.setText('var(--rgb');
                await cssPropertyPrompt.complete(true);
                assert.strictEqual(cssPropertyPrompt.aiCodeCompletionProvider?.getCompletionHint?.(), '-color)');
            });
            it('debounces triggerAiCodeCompletion', async () => {
                const clock = sinon.useFakeTimers();
                const triggerAiCodeCompletionStub = aiCodeCompletionProvider.triggerAiCodeCompletion.resolves();
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                cssPropertyPrompt.setText('backgr');
                cssPropertyPrompt.onInput(new Event('input'));
                cssPropertyPrompt.setText('backgro');
                cssPropertyPrompt.onInput(new Event('input'));
                cssPropertyPrompt.setText('backgrou');
                cssPropertyPrompt.onInput(new Event('input'));
                await clock.tickAsync(TextEditor.AiCodeCompletionProvider.AIDA_REQUEST_DEBOUNCE_TIMEOUT_MS + 1);
                sinon.assert.calledOnce(triggerAiCodeCompletionStub);
                assert.strictEqual(triggerAiCodeCompletionStub.firstCall.args[0], 'backgrou');
                assert.strictEqual(triggerAiCodeCompletionStub.firstCall.args[1], 8);
                clock.restore();
            });
            it('cancels pending requests and clears suggestions on deletion', async () => {
                const clock = sinon.useFakeTimers();
                const triggerAiCodeCompletionStub = aiCodeCompletionProvider.triggerAiCodeCompletion.resolves();
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                // First call triggerAiCodeCompletion by typing a letter
                cssPropertyPrompt.setText('b');
                cssPropertyPrompt.onInput(new Event('input'));
                // Perform deletion
                cssPropertyPrompt.setText('');
                const deleteEvent = new InputEvent('input', { inputType: 'deleteContentBackward' });
                cssPropertyPrompt.onInput(deleteEvent);
                await clock.tickAsync(TextEditor.AiCodeCompletionProvider.AIDA_REQUEST_DEBOUNCE_TIMEOUT_MS + 1);
                // Verify no request was made
                sinon.assert.notCalled(triggerAiCodeCompletionStub);
                // Verify active suggestion was cleared
                assert.isUndefined(mockTreeItem.section().activeAiSuggestion);
                clock.restore();
            });
            it('triggerAiCodeCompletion calls the provider with correct arguments', () => {
                const clock = sinon.useFakeTimers();
                const triggerAiCodeCompletionStub = aiCodeCompletionProvider.triggerAiCodeCompletion.resolves();
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                cssPropertyPrompt.setText('backgrou');
                cssPropertyPrompt.onInput(new Event('input'));
                clock.tick(TextEditor.AiCodeCompletionProvider.AIDA_REQUEST_DEBOUNCE_TIMEOUT_MS + 1);
                sinon.assert.calledOnce(triggerAiCodeCompletionStub);
                assert.strictEqual(triggerAiCodeCompletionStub.firstCall.args[0], 'backgrou');
                assert.strictEqual(triggerAiCodeCompletionStub.firstCall.args[1], 8);
                clock.restore();
            });
            it('setAiAutoCompletion sets activeAiSuggestion on the section', async () => {
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                cssPropertyPrompt.aiCodeCompletionProvider?.setAiAutoCompletion?.({
                    text: 'color: pink;',
                    from: 0,
                    startTime: 0,
                    clearCachedRequest: () => { },
                    onImpression: () => { },
                    citations: [],
                });
                assert.exists(section.activeAiSuggestion);
                assert.deepEqual(section.activeAiSuggestion.properties, [{ name: 'color', value: 'pink' }]);
            });
            it('setAiAutoCompletion correctly parses complex CSS and sets activeAiSuggestion on the section', async () => {
                const cssPropertyPrompt = new CSSPropertyPrompt(mockTreeItem, true);
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                const complexCss = `background-image: url("https://example.com/image;v=1?query:part=true");
content: "This is a semicolon; and this is a colon: inside a string";
--custom-property: var(--other, "fallback;value");
width: calc(100% - 20px);
color: pink !important;`;
                cssPropertyPrompt.aiCodeCompletionProvider?.setAiAutoCompletion?.({
                    text: complexCss,
                    from: 0,
                    startTime: 0,
                    clearCachedRequest: () => { },
                    onImpression: () => { },
                    citations: [],
                });
                assert.exists(section.activeAiSuggestion);
                assert.deepEqual(section.activeAiSuggestion.properties, [
                    { name: 'background-image', value: 'url("https://example.com/image;v=1?query:part=true")' },
                    { name: 'content', value: '"This is a semicolon; and this is a colon: inside a string"' },
                    { name: '--custom-property', value: 'var(--other, "fallback;value")' },
                    { name: 'width', value: 'calc(100% - 20px)' }, { name: 'color', value: 'pink !important' }
                ]);
            });
            it('only hides suggest box on Escape when suggest box is visible but does not clear AI suggestion', async () => {
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                cssPropertyPrompt.setText('var(--rgb');
                await cssPropertyPrompt.complete(true);
                cssPropertyPrompt.aiCodeCompletionProvider?.setAiAutoCompletion?.({
                    text: 'color: var(--rgb-color);',
                    from: 0,
                    startTime: 0,
                    clearCachedRequest: () => { },
                    onImpression: () => { },
                    citations: [],
                });
                assert.isTrue(cssPropertyPrompt.isSuggestBoxVisible());
                const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' });
                cssPropertyPrompt.onKeyDown(escapeEvent);
                assert.isFalse(cssPropertyPrompt.isSuggestBoxVisible());
                assert.strictEqual(section.activeAiSuggestion?.text, 'color: var(--rgb-color);');
            });
            it('clears active AI suggestion on ArrowDown', async () => {
                cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                cssPropertyPrompt.setText('var(--rgb');
                await cssPropertyPrompt.complete(true);
                cssPropertyPrompt.aiCodeCompletionProvider?.setAiAutoCompletion?.({
                    text: 'color: var(--rgb-color);',
                    from: 0,
                    startTime: 0,
                    clearCachedRequest: () => { },
                    onImpression: () => { },
                    citations: [],
                });
                assert.strictEqual(section.activeAiSuggestion?.text, 'color: var(--rgb-color);');
                assert.isTrue(cssPropertyPrompt.isSuggestBoxVisible());
                const arrowDownEvent = new KeyboardEvent('keydown', { key: 'ArrowDown' });
                cssPropertyPrompt.onKeyDown(arrowDownEvent);
                assert.notExists(section.activeAiSuggestion);
            });
            describe('acceptAiCodeComplete', () => {
                it('accepts suggestion on Tab when suggest box is hidden', async () => {
                    cssPropertyPrompt = new Elements.StylesSidebarPane.CSSPropertyPrompt(mockTreeItem, true);
                    cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                    const onSuggestionAcceptedStub = aiCodeCompletionProvider.onSuggestionAccepted.returns();
                    cssPropertyPrompt.aiCodeCompletionProvider?.setAiAutoCompletion?.({
                        text: 'color: pink;',
                        from: 0,
                        startTime: 0,
                        clearCachedRequest: () => { },
                        onImpression: () => { },
                        citations: [{ uri: 'https://example.com' }],
                        sampleId: 1,
                        rpcGlobalId: 1,
                    });
                    const tabEvent = new KeyboardEvent('keydown', { key: 'Tab' });
                    cssPropertyPrompt.onKeyDown(tabEvent);
                    // Required to make sure section.commitActiveAiSuggestion resolves
                    await raf();
                    sinon.assert.calledOnce(section.commitActiveAiSuggestion);
                    sinon.assert.calledOnce(onSuggestionAcceptedStub);
                    assert.deepEqual(onSuggestionAcceptedStub.firstCall.args, [[{ uri: 'https://example.com' }], 1, 1]);
                });
                it('accepts auto complete suggestion and re-applies ghost text on first Tab accept when suggest box is visible', async () => {
                    const cssPropertyPrompt = new CSSPropertyPrompt(mockTreeItem, false, ['green']);
                    cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                    cssPropertyPrompt.setText('gre');
                    await cssPropertyPrompt.complete(true);
                    cssPropertyPrompt.aiCodeCompletionProvider?.setAiAutoCompletion?.({
                        text: 'color: greenyellow;',
                        from: 0,
                        startTime: 0,
                        clearCachedRequest: () => { },
                        onImpression: () => { },
                        citations: [],
                    });
                    assert.isTrue(cssPropertyPrompt.isSuggestBoxVisible());
                    const applySuggestionSpy = sinon.spy(cssPropertyPrompt, 'applySuggestion');
                    const tabEvent = new KeyboardEvent('keydown', { key: 'Tab' });
                    cssPropertyPrompt.onKeyDown(tabEvent);
                    // On first Tab, the suggestion from auto complete menu is applied.
                    // And the AI suggestion text is set as ghost text.
                    assert.strictEqual(applySuggestionSpy.lastCall.args[0]?.text, 'greenyellow');
                    assert.strictEqual(cssPropertyPrompt.text(), 'green');
                });
                it('accepts AI suggestion on second Tab when suggest box is visible', async () => {
                    cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                    cssPropertyPrompt.setText('var(--rgb');
                    await cssPropertyPrompt.complete(true);
                    cssPropertyPrompt.aiCodeCompletionProvider?.setAiAutoCompletion?.({
                        text: 'color: var(--rgb-color); background-color: white;',
                        from: 0,
                        startTime: 0,
                        clearCachedRequest: () => { },
                        onImpression: () => { },
                        citations: [],
                    });
                    assert.isTrue(cssPropertyPrompt.isSuggestBoxVisible());
                    const tabEvent = new KeyboardEvent('keydown', { key: 'Tab' });
                    cssPropertyPrompt.onKeyDown(tabEvent);
                    // On first Tab, the suggestion from auto complete menu is applied.
                    assert.strictEqual(cssPropertyPrompt.text(), 'var(--rgb-color)');
                    cssPropertyPrompt.onKeyDown(tabEvent);
                    // On second Tab, the AI suggestion is committed.
                    sinon.assert.calledOnce(section.commitActiveAiSuggestion);
                });
                it('accepts traditional autocomplete suggestion on Tab when suggest box is hidden but inline suggestion is active', async () => {
                    const cssPropertyPrompt = new CSSPropertyPrompt(mockTreeItem, true);
                    cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                    cssPropertyPrompt.setText('flex-g');
                    await cssPropertyPrompt.complete(true);
                    assert.isFalse(cssPropertyPrompt.isSuggestBoxVisible());
                    assert.strictEqual(cssPropertyPrompt.currentSuggestion()?.text, 'flex-grow');
                    const tabEvent = new KeyboardEvent('keydown', { key: 'Tab' });
                    cssPropertyPrompt.onKeyDown(tabEvent);
                    assert.strictEqual(cssPropertyPrompt.text(), 'flex-grow');
                });
            });
            describe('updateAiCodeSuggestion', () => {
                it('clears suggestion if user input does not match', async () => {
                    cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                    assert.exists(cssPropertyPrompt.aiCodeCompletionProvider);
                    cssPropertyPrompt.aiCodeCompletionProvider.setAiAutoCompletion?.({
                        text: 'color: pink;',
                        from: 0,
                        startTime: 0,
                        clearCachedRequest: () => { },
                        onImpression: () => { },
                        citations: [],
                    });
                    assert.exists(section.activeAiSuggestion);
                    cssPropertyPrompt.setText('bac');
                    cssPropertyPrompt.onInput(new Event('input'));
                    assert.isUndefined(section.activeAiSuggestion);
                });
                it('clears suggestion if cursor is moved before trigger point', async () => {
                    cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                    cssPropertyPrompt.setText('pin');
                    cssPropertyPrompt.aiCodeCompletionProvider?.setAiAutoCompletion?.({
                        text: 'color: pink;',
                        from: 3,
                        startTime: 0,
                        clearCachedRequest: () => { },
                        onImpression: () => { },
                        citations: [],
                    });
                    assert.exists(section.activeAiSuggestion);
                    const mockSelection = ({
                        rangeCount: 1,
                        getRangeAt: () => ({
                            endOffset: 2,
                        }),
                    });
                    sinon.stub(cssPropertyPrompt.element(), 'getComponentSelection').returns(mockSelection);
                    cssPropertyPrompt.onInput(new Event('input'));
                    assert.isUndefined(section.activeAiSuggestion);
                });
                it('clears suggestion if suggest box shows inconsistent top suggestion', async () => {
                    cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                    cssPropertyPrompt.setText('var(--rgb');
                    await cssPropertyPrompt.complete(true);
                    cssPropertyPrompt.aiCodeCompletionProvider?.setAiAutoCompletion?.({
                        text: 'color: var(--rgb-background-color);',
                        from: 0,
                        startTime: 0,
                        clearCachedRequest: () => { },
                        onImpression: () => { },
                        citations: [],
                    });
                    assert.exists(section.activeAiSuggestion);
                    await cssPropertyPrompt.onInput(new Event('input'));
                    assert.isUndefined(section.activeAiSuggestion);
                });
                it('keeps suggestion if input matches', async () => {
                    cssPropertyPrompt.attachAndStartEditing(attachedElement, noop);
                    cssPropertyPrompt.setText('p');
                    cssPropertyPrompt.aiCodeCompletionProvider?.setAiAutoCompletion?.({
                        text: 'color: pink;',
                        from: 1,
                        startTime: 0,
                        clearCachedRequest: () => { },
                        onImpression: () => { },
                        citations: [],
                    });
                    assert.exists(section.activeAiSuggestion);
                    cssPropertyPrompt.setText('pi');
                    cssPropertyPrompt.onInput(new Event('input'));
                    assert.exists(section.activeAiSuggestion);
                    assert.deepEqual(section.activeAiSuggestion.properties, [{ name: 'color', value: 'pink' }]);
                });
            });
        });
    });
});
//# sourceMappingURL=StylesSidebarPane.test.js.map