Skip to content

Commit 99fcbd8

Browse files
Lightning00BladeDevtools-frontend LUCI CQ
authored andcommitted
[AI Assistance] Compute most specific selector
Bug: 393267006 Change-Id: I9aec16c084aa1af24377f66afd3bea4ea45e5d72 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6291421 Reviewed-by: Alex Rudenko <[email protected]> Commit-Queue: Nikolay Vitkov <[email protected]> Reviewed-by: Philip Pfaffe <[email protected]>
1 parent 9b4d93e commit 99fcbd8

File tree

8 files changed

+437
-49
lines changed

8 files changed

+437
-49
lines changed

front_end/core/sdk/CSSMatchedStyles.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,11 @@ describe('CSSMatchedStyles', () => {
343343
node.id = 1 as Protocol.DOM.NodeId;
344344
const startColumn = 0, endColumn = 1;
345345
const matchedPayload = [
346-
ruleMatch('body', [{name: '--var', value: 'blue'}], {startLine: 0, startColumn, endLine: 0, endColumn}),
347-
ruleMatch('*', [{name: 'color', value: 'var(--var)'}], {startLine: 1, startColumn, endLine: 1, endColumn}),
348-
ruleMatch('*', [{name: '--var', value: 'red'}], {startLine: 2, startColumn, endLine: 2, endColumn}),
346+
ruleMatch(
347+
'body', [{name: '--var', value: 'blue'}], {range: {startLine: 0, startColumn, endLine: 0, endColumn}}),
348+
ruleMatch(
349+
'*', [{name: 'color', value: 'var(--var)'}], {range: {startLine: 1, startColumn, endLine: 1, endColumn}}),
350+
ruleMatch('*', [{name: '--var', value: 'red'}], {range: {startLine: 2, startColumn, endLine: 2, endColumn}}),
349351
];
350352
const inheritedPayload = [{matchedCSSRules: matchedPayload.slice(1)}];
351353
const matchedStyles = await getMatchedStyles({

front_end/panels/ai_assistance/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ ts_library("unittests") {
9797
"AiHistoryStorage.test.ts",
9898
"ChangeManager.test.ts",
9999
"EvaluateAction.test.ts",
100+
"ExtensionScope.test.ts",
100101
"agents/AiAgent.test.ts",
101102
"agents/FileAgent.test.ts",
102103
"agents/NetworkAgent.test.ts",
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// Copyright 2024 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as SDK from '../../core/sdk/sdk.js';
6+
import type * as Protocol from '../../generated/protocol.js';
7+
import {createCSSStyle, getMatchedStyles, ruleMatch} from '../../testing/StyleHelpers.js';
8+
9+
import * as ChangeManager from './ChangeManager.js';
10+
import * as ExtensionScope from './ExtensionScope.js';
11+
12+
async function getSelector(payload: Partial<SDK.CSSMatchedStyles.CSSMatchedStylesPayload>) {
13+
const node = sinon.createStubInstance(SDK.DOMModel.DOMNode);
14+
node.id = 1 as Protocol.DOM.NodeId;
15+
// Needed to process the inline styles
16+
node.nodeType.returns(Node.ELEMENT_NODE);
17+
18+
const matchedStyles = await getMatchedStyles({
19+
node,
20+
...payload,
21+
});
22+
23+
return ExtensionScope.ExtensionScope.getSelectorForRule(matchedStyles);
24+
}
25+
26+
describe('ExtensionScope', () => {
27+
const MOCK_STYLE = [
28+
{
29+
name: 'color',
30+
value: 'red',
31+
},
32+
];
33+
34+
describe('getSelector', () => {
35+
it('should work with empty styles', async () => {
36+
const selector = await getSelector({});
37+
assert.strictEqual(selector, '');
38+
});
39+
40+
it('should omit inline selectors', async () => {
41+
const inlinePayload = createCSSStyle(MOCK_STYLE);
42+
const selector = await getSelector({
43+
inlinePayload,
44+
});
45+
assert.strictEqual(selector, '');
46+
});
47+
48+
it('should work with id selector', async () => {
49+
const matchedPayload = [
50+
ruleMatch('#test', MOCK_STYLE),
51+
];
52+
const selector = await getSelector({matchedPayload});
53+
assert.strictEqual(selector, '#test');
54+
});
55+
56+
it('should work with class selector', async () => {
57+
const matchedPayload = [
58+
ruleMatch('.test', MOCK_STYLE),
59+
];
60+
const selector = await getSelector({matchedPayload});
61+
assert.strictEqual(selector, '.test');
62+
});
63+
64+
it('should work with tag selector', async () => {
65+
const matchedPayload = [
66+
ruleMatch('div', MOCK_STYLE),
67+
];
68+
const selector = await getSelector({matchedPayload});
69+
assert.strictEqual(selector, 'div');
70+
});
71+
72+
it('should prefer id selectors', async () => {
73+
const matchedPayload = [
74+
ruleMatch(
75+
{
76+
selectors: [
77+
{
78+
text: '#my-id',
79+
specificity: {a: 1, b: 0, c: 0},
80+
},
81+
{
82+
text: '.my-class',
83+
specificity: {a: 0, b: 1, c: 0},
84+
},
85+
{
86+
text: 'div',
87+
specificity: {a: 0, b: 0, c: 1},
88+
},
89+
],
90+
text: '#my-id, .my-class, div'
91+
},
92+
MOCK_STYLE,
93+
),
94+
];
95+
const selector = await getSelector({matchedPayload});
96+
assert.strictEqual(selector, '#my-id');
97+
});
98+
99+
it('should prefer class selectors over tags', async () => {
100+
const matchedPayload = [
101+
ruleMatch(
102+
{
103+
selectors: [
104+
{
105+
text: '.my-class',
106+
specificity: {a: 0, b: 1, c: 0},
107+
},
108+
{
109+
text: 'div',
110+
specificity: {a: 0, b: 0, c: 1},
111+
},
112+
],
113+
text: '.my-class, div'
114+
},
115+
MOCK_STYLE,
116+
),
117+
];
118+
const selector = await getSelector({matchedPayload});
119+
assert.strictEqual(selector, '.my-class');
120+
});
121+
122+
it('should pick first rule from the cascade', async () => {
123+
// Order is reversed we know that specificity order will
124+
// be returned correctly
125+
// front_end/core/sdk/CSSMatchedStyles.ts:373
126+
const matchedPayload = [
127+
ruleMatch('.test', MOCK_STYLE),
128+
ruleMatch('.test-2', MOCK_STYLE),
129+
];
130+
const selector = await getSelector({matchedPayload});
131+
assert.strictEqual(selector, '.test-2');
132+
});
133+
134+
it('should work with complex selector', async () => {
135+
const matchedPayload = [
136+
ruleMatch('div.container > .header', MOCK_STYLE),
137+
];
138+
const selector = await getSelector({matchedPayload});
139+
assert.strictEqual(selector, 'div.container > .header');
140+
});
141+
142+
it('should return nested selector with ai assistance prefix', async () => {
143+
// Order is reversed we know that specificity order will
144+
// be returned correctly
145+
// front_end/core/sdk/CSSMatchedStyles.ts:373
146+
const matchedPayload = [
147+
ruleMatch('.test', MOCK_STYLE),
148+
ruleMatch(
149+
150+
{
151+
selectors: [{text: 'div&'}],
152+
text: 'div&',
153+
},
154+
MOCK_STYLE,
155+
{
156+
nestingSelectors: [`.${ChangeManager.AI_ASSISTANCE_CSS_CLASS_NAME}-1`],
157+
},
158+
),
159+
];
160+
const selector = await getSelector({matchedPayload});
161+
assert.strictEqual(selector, 'div');
162+
});
163+
});
164+
});

0 commit comments

Comments
 (0)