blob: 0c91b3549b14064375fa8dea450fc9cb6e5fddad [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
const {assert} = require('chai');
const path = require('node:path');
const stylelint = require('stylelint');
const configBase = {
config: {
plugins: [path.resolve(__dirname, '../lib/check_highlight_scope.mjs')],
rules: {'plugin/check_highlight_scope': [true]},
},
// Remove once we use a ESM test runner
quietDeprecationWarnings: true,
};
async function lintAndGetWarnings(code) {
const {
results: [{warnings}],
} = await stylelint.lint({
code,
...configBase,
});
return warnings;
}
const createExpectedError = selector => `::highlight not be scoped in "${
selector}". Observed performance regressions (https://crbug.com/461462682). (plugin/check_highlight_scope)`;
describe('check_highlight_scope', () => {
it('errors when ::highlight is used at the root level', async () => {
const code = `::highlight(my-search-results) {
background-color: yellow;
}`;
const warnings = await lintAndGetWarnings(code);
assert.deepEqual(warnings, [
{
column: 1,
line: 1,
endColumn: 31,
endLine: 1,
rule: 'plugin/check_highlight_scope',
severity: 'error',
text: createExpectedError('::highlight(my-search-results)'),
url: undefined,
fix: undefined,
},
]);
});
it('errors when ::highlight is used in a comma-separated list without scoping', async () => {
const code = `div, ::highlight(foo) {
color: red;
}`;
const warnings = await lintAndGetWarnings(code);
assert.deepEqual(warnings, [
{
column: 6,
endColumn: 22,
endLine: 1,
line: 1,
rule: 'plugin/check_highlight_scope',
severity: 'error',
text: createExpectedError('::highlight(foo)'),
url: undefined,
fix: undefined,
},
]);
});
it('allows ::highlight when scoped to :host', async () => {
const warnings = await lintAndGetWarnings(
':host::highlight(my-highlight) { background: blue; }',
);
assert.deepEqual(warnings, []);
});
it('allows ::highlight when scoped to a specific class', async () => {
const warnings = await lintAndGetWarnings(
'.search-results::highlight(term) { color: white; }',
);
assert.deepEqual(warnings, []);
});
it('allows ::highlight when scoped to a type selector', async () => {
const warnings = await lintAndGetWarnings(
'p::highlight(spelling-error) { text-decoration: wavy; }',
);
assert.deepEqual(warnings, []);
});
it('ignores other pseudo-elements like ::before', async () => {
const warnings = await lintAndGetWarnings('::before { content: ""; }');
assert.deepEqual(warnings, []);
});
it('allows ::highlight when used with a combinator (child)', async () => {
// This is technically scoped to children of 'main', so it should pass
const warnings = await lintAndGetWarnings(
'main > ::highlight(foo) { color: green; }',
);
assert.deepEqual(warnings, [
{
column: 1,
line: 1,
endColumn: 24,
endLine: 1,
rule: 'plugin/check_highlight_scope',
severity: 'error',
text: createExpectedError('main > ::highlight(foo)'),
url: undefined,
fix: undefined,
},
]);
});
it('errors correctly on multiple unscoped highlights in one rule', async () => {
const code = `::highlight(one), ::highlight(two) {
color: pink;
}`;
const warnings = await lintAndGetWarnings(code);
assert.deepEqual(warnings, [
{
column: 1,
line: 1,
endColumn: 17,
endLine: 1,
rule: 'plugin/check_highlight_scope',
severity: 'error',
text: createExpectedError('::highlight(one)'),
url: undefined,
fix: undefined,
},
{
column: 19,
line: 1,
endColumn: 35,
endLine: 1,
rule: 'plugin/check_highlight_scope',
severity: 'error',
text: createExpectedError('::highlight(two)'),
url: undefined,
fix: undefined,
},
]);
});
});