chrome-devtools-frontend
Version:
Chrome DevTools UI
651 lines (582 loc) • 29.2 kB
text/typescript
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Common from '../../core/common/common.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';
import {renderElementIntoDOM} from '../../testing/DOMHelpers.js';
import {
createTarget,
describeWithEnvironment,
describeWithLocale,
getGetHostConfigStub,
} from '../../testing/EnvironmentHelpers.js';
import {expectCall} from '../../testing/ExpectStubCall.js';
import {describeWithMockConnection, setMockConnectionResponseHandler} from '../../testing/MockConnection.js';
import {getMatchedStyles} from '../../testing/StyleHelpers.js';
import * as InlineEditor from '../../ui/legacy/components/inline_editor/inline_editor.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as Elements from './elements.js';
describe('StylesSidebarPane', () => {
let node: SDK.DOMModel.DOMNode;
beforeEach(() => {
node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
node.id = 1 as Protocol.DOM.NodeId;
UI.Context.Context.instance().setFlavor(SDK.DOMModel.DOMNode, node);
});
describeWithMockConnection('StylesSidebarPane', () => {
beforeEach(() => {
const target = createTarget();
const cssModel = target.model(SDK.CSSModel.CSSModel);
sinon.stub(Elements.ComputedStyleModel.ComputedStyleModel.prototype, 'cssModel').returns(cssModel);
});
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 Elements.ComputedStyleModel.ComputedStyleModel());
const matchedStyles = await getMatchedStyles({
cssModel: stylesSidebarPane.cssModel() as SDK.CSSModel.CSSModel,
node: sinon.createStubInstance(SDK.DOMModel.DOMNode),
positionTryRules: [{
name: {text: '--try-one'},
origin: 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());
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('should add @font-palette-values section to the end', async () => {
const stylesSidebarPane =
new Elements.StylesSidebarPane.StylesSidebarPane(new Elements.ComputedStyleModel.ComputedStyleModel());
const matchedStyles = await getMatchedStyles({
cssModel: stylesSidebarPane.cssModel() as SDK.CSSModel.CSSModel,
node: sinon.createStubInstance(SDK.DOMModel.DOMNode),
fontPaletteValuesRule: {
fontPaletteName: {text: '--palette'},
origin: Protocol.CSS.StyleSheetOrigin.Regular,
style: {
cssProperties: [{name: 'font-family', value: 'Bixa'}, {name: 'override-colors', value: '0 red'}],
shorthandEntries: [],
},
},
});
const sectionBlocks =
await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map());
assert.lengthOf(sectionBlocks, 2);
assert.strictEqual(sectionBlocks[1].titleElement()?.textContent, '@font-palette-values --palette');
assert.lengthOf(sectionBlocks[1].sections, 1);
assert.instanceOf(sectionBlocks[1].sections[0], Elements.StylePropertiesSection.FontPaletteValuesRuleSection);
});
describe('Animation styles', () => {
function mockGetAnimatedComputedStyles(response: Partial<Protocol.CSS.GetAnimatedStylesForNodeResponse>) {
setMockConnectionResponseHandler('CSS.getAnimatedStylesForNode', () => response);
}
let hostConfigStub: sinon.SinonStub;
beforeEach(() => {
sinon.stub(Common.Linkifier.Linkifier, 'linkify').returns(Promise.resolve(document.createTextNode('link')));
hostConfigStub = getGetHostConfigStub({
devToolsAnimationStylesInStylesTab: {
enabled: true,
},
});
});
afterEach(() => {
hostConfigStub.restore();
});
it('should render transition & animation styles in the styles tab', async () => {
const stylesSidebarPane =
new Elements.StylesSidebarPane.StylesSidebarPane(new Elements.ComputedStyleModel.ComputedStyleModel());
const matchedStyles = await getMatchedStyles({
cssModel: stylesSidebarPane.cssModel() as SDK.CSSModel.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());
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', () => {
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 = sinon.createStubInstance(SDK.DOMModel.DOMNode);
node.id = 1 as Protocol.DOM.NodeId;
const stylesSidebarPane =
new Elements.StylesSidebarPane.StylesSidebarPane(new Elements.ComputedStyleModel.ComputedStyleModel());
const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
const matchedStyles = await getMatchedStyles({
cssModel: stylesSidebarPane.cssModel() as SDK.CSSModel.CSSModel,
node,
transitionsStylePayload: null,
});
stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
const sectionBlocks =
await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map());
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 = sinon.createStubInstance(SDK.DOMModel.DOMNode);
node.id = 1 as Protocol.DOM.NodeId;
const stylesSidebarPane =
new Elements.StylesSidebarPane.StylesSidebarPane(new Elements.ComputedStyleModel.ComputedStyleModel());
const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
const matchedStyles = await getMatchedStyles({
cssModel: stylesSidebarPane.cssModel() as SDK.CSSModel.CSSModel,
node,
transitionsStylePayload: {
cssProperties: [{
name: 'color',
value: 'blue',
}],
shorthandEntries: [],
},
});
stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
const sectionBlocks =
await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map());
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 = sinon.createStubInstance(SDK.DOMModel.DOMNode);
node.id = 1 as Protocol.DOM.NodeId;
const stylesSidebarPane =
new Elements.StylesSidebarPane.StylesSidebarPane(new Elements.ComputedStyleModel.ComputedStyleModel());
const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
const matchedStyles = await getMatchedStyles({
cssModel: stylesSidebarPane.cssModel() as SDK.CSSModel.CSSModel,
node,
animationStylesPayload: [],
});
stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
const sectionBlocks =
await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map());
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 = sinon.createStubInstance(SDK.DOMModel.DOMNode);
node.id = 1 as Protocol.DOM.NodeId;
const stylesSidebarPane =
new Elements.StylesSidebarPane.StylesSidebarPane(new Elements.ComputedStyleModel.ComputedStyleModel());
const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
const matchedStyles = await getMatchedStyles({
cssModel: stylesSidebarPane.cssModel() as SDK.CSSModel.CSSModel,
node,
animationStylesPayload: [{
style: {
cssProperties: [{
name: 'color',
value: 'blue',
}],
shorthandEntries: [],
},
}],
});
stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
const sectionBlocks =
await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map());
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 = sinon.createStubInstance(SDK.DOMModel.DOMNode);
node.id = 1 as Protocol.DOM.NodeId;
const stylesSidebarPane =
new Elements.StylesSidebarPane.StylesSidebarPane(new Elements.ComputedStyleModel.ComputedStyleModel());
const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
const matchedStyles = await getMatchedStyles({
cssModel: stylesSidebarPane.cssModel() as SDK.CSSModel.CSSModel,
node,
animationStylesPayload: [{
style: {
cssProperties: [{
name: 'color',
value: 'blue',
}],
shorthandEntries: [],
},
}],
});
stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
const sectionBlocks =
await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(matchedStyles, new Map(), new Map());
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 = sinon.createStubInstance(SDK.DOMModel.DOMNode);
node.id = 1 as Protocol.DOM.NodeId;
node.parentNode = sinon.createStubInstance(SDK.DOMModel.DOMNode);
const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(
new Elements.ComputedStyleModel.ComputedStyleModel());
const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
const matchedStyles = await getMatchedStyles({
cssModel: stylesSidebarPane.cssModel() as SDK.CSSModel.CSSModel,
node,
});
stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(
matchedStyles, new Map(), new Map());
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 = sinon.createStubInstance(SDK.DOMModel.DOMNode);
node.id = 1 as Protocol.DOM.NodeId;
node.parentNode = sinon.createStubInstance(SDK.DOMModel.DOMNode);
const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(
new Elements.ComputedStyleModel.ComputedStyleModel());
const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
const matchedStyles = await getMatchedStyles({
cssModel: stylesSidebarPane.cssModel() as SDK.CSSModel.CSSModel,
node,
});
stylesSidebarPane.setMatchedStylesForTest(matchedStyles);
const sectionBlocks = await stylesSidebarPane.rebuildSectionsForMatchedStyleRulesForTest(
matchedStyles, new Map(), new Map());
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 = sinon.createStubInstance(SDK.DOMModel.DOMNode);
node.id = 1 as Protocol.DOM.NodeId;
node.parentNode = sinon.createStubInstance(SDK.DOMModel.DOMNode);
const stylesSidebarPane = new Elements.StylesSidebarPane.StylesSidebarPane(
new Elements.ComputedStyleModel.ComputedStyleModel());
const resetUpdateSpy = sinon.spy(stylesSidebarPane, 'scheduleResetUpdateIfNotEditingCalledForTest');
const matchedStyles = await getMatchedStyles({
cssModel: stylesSidebarPane.cssModel() as SDK.CSSModel.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());
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('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 {
protected override scheduleIdleCallback(_: number): void {
super.scheduleIdleCallback(1);
}
}
const timeout = (time: number) => new Promise<void>(resolve => setTimeout(resolve, time));
const elements: number[] = [];
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]);
});
});
describeWithLocale('CSSPropertyPrompt', () => {
const CSSPropertyPrompt = Elements.StylesSidebarPane.CSSPropertyPrompt;
const CSS_VARIABLES_FOR_TEST: Record<string, string> = {
'--rgb-color': 'rgb(0 0 0)',
'--wide-gamut-color': 'lch(0 0 0)',
};
const mockTreeItem = {
property: {
name: 'color',
},
node() {
return {
isSVGNode() {
return false;
},
domModel() {
return {
cssModel() {
return {
getComputedStyle() {
return new Map<string, string>();
},
};
},
};
},
};
},
matchedStyles() {
return {
availableCSSVariables(): string[] {
return ['--rgb-color', '--wide-gamut-color'];
},
computeCSSVariable(_: unknown, completion: string): {value: string, declaration: null} |
undefined {
return {value: CSS_VARIABLES_FOR_TEST[completion], declaration: null};
},
};
},
} as unknown as Elements.StylePropertyTreeElement.StylePropertyTreeElement;
const noop = () => {};
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');
});
});
});
});