/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the rule view custom properties (css variables) jump to definition works
// as expected.
const TEST_URI = `
Styled Node
sub
`;
add_task(async function () {
// Make scrolling instant when jumping to a variable
await pushPref("ui.prefersReducedMotion", 1);
await pushPref("devtools.inspector.three-pane-enabled", false);
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
const { inspector, view } = await openRuleView();
await selectNode("h1#testid", inspector);
info("Check that the correct rules are visible");
assertDisplayedRulesCount(view, 7);
let rule = getRuleViewRuleEditorAt(view, 2).rule;
is(rule.selectorText, "#testid", "Second rule is #testid.");
info(
"Check that property not using variable don't have a jump to definition button"
);
let variableButtonEls = getJumpToDefinitionButtonForDeclaration(rule, {
"border-width": "1px",
});
is(
variableButtonEls.length,
0,
"border-width property does not have custom properties and no variable jump to definition is displayed."
);
info(
"Check that property using unset variable don't have a jump to definition button"
);
variableButtonEls = getJumpToDefinitionButtonForDeclaration(rule, {
"outline-width": "var(--undefined)",
});
is(
variableButtonEls.length,
0,
"outline-width property has an undefined custom properties, so no variable jump to definition is displayed."
);
info(
"Check that there's a jump to definition button for `color: var(--my-color-1)`"
);
variableButtonEls = getJumpToDefinitionButtonForDeclaration(rule, {
color: "var(--my-color-1)",
});
is(
variableButtonEls.length,
1,
"color property has custom property and variable jump to definition is displayed."
);
info("Click the --my-color-1 jump to definition button");
await highlightProperty(view, variableButtonEls[0], "--my-color-1", "tomato");
info(
"Check that there's a jump to definition button for `background-color var(--my-registered-color)`"
);
variableButtonEls = getJumpToDefinitionButtonForDeclaration(rule, {
"background-color": "var(--my-registered-color)",
});
is(
variableButtonEls.length,
1,
"background-color property has a registered custom property and variable jump to definition is displayed."
);
info("Click the --my-registered-color jump to definition button");
// Collapse the `@property` section to check that it gets expanded when clicking on the
// jump to definition button.
const registerPropertyToggle = view.styleDocument.querySelector(
`[aria-controls="registered-properties-container"]`
);
registerPropertyToggle.click();
is(
registerPropertyToggle.ariaExpanded,
"false",
"@property section is collapsed"
);
const onHighlightProperty = view.once("element-highlighted");
variableButtonEls[0].click();
const highlightedElement = await onHighlightProperty;
is(
highlightedElement.querySelector(".ruleview-registered-property-name")
.innerText,
"--my-registered-color",
"The expected element was highlighted"
);
is(
registerPropertyToggle.ariaExpanded,
"true",
"@property section is expanded after clicking on the jump to definition button"
);
info(
"Check that there are multiple jump to definition buttons when using multiple variables"
);
rule = getRuleViewRuleEditorAt(view, 4).rule;
is(rule.selectorText, "h1", "Fifth rule is h1.");
variableButtonEls = getJumpToDefinitionButtonForDeclaration(rule, {
"background-color":
"linear-gradient(var(--my-color-1), var(--my-color-2, var(--my-color-3)))",
});
Assert.deepEqual(
[...variableButtonEls].map(el => el.dataset.variableName),
["--my-color-1", "--my-color-2", "--my-color-3"]
);
info(`Click the "--my-color-2" variable jump to definition button`);
await highlightProperty(view, variableButtonEls[1], "--my-color-2", "cyan");
info(`Click the fallback "--my-color-3" variable jump to definition button`);
await highlightProperty(view, variableButtonEls[2], "--my-color-3", "green");
info("Check that we can jump in @starting-style rule`");
rule = getRuleViewRuleEditorAt(view, 1).rule;
ok(rule.isInStartingStyle(), "Got expected starting style rule");
variableButtonEls = getJumpToDefinitionButtonForDeclaration(rule, {
"outline-color": "var(--my-color-1, var(--my-color-2))",
});
Assert.deepEqual(
[...variableButtonEls].map(el => el.dataset.variableName),
["--my-color-1", "--my-color-2"]
);
info(
"Click the --my-color-1 jump to definition button in @starting-style rule"
);
await highlightProperty(
view,
variableButtonEls[0],
"--my-color-1",
"hotpink"
);
info(
"Click the --my-color-2 jump to definition button in @starting-style rule"
);
await highlightProperty(view, variableButtonEls[1], "--my-color-2", "cyan");
info("Check that jump to definition works well with pseudo elements");
await selectNode("h2", inspector);
expandPseudoElementContainer(view);
rule = getRuleViewRuleEditorAt(view, 0).rule;
is(rule.selectorText, "h2::after", "First rule is h2::after");
await highlightProperty(
view,
getJumpToDefinitionButtonForDeclaration(rule, {
color: "var(--my-color-1)",
})[0],
"--my-color-1",
"azure"
);
rule = getRuleViewRuleEditorAt(view, 1).rule;
is(rule.selectorText, "h2::before", "First rule is h2::before");
variableButtonEls = getJumpToDefinitionButtonForDeclaration(rule, {
color: "var(--my-color-1, var(--my-color-2, var(--my-color-3)))",
});
await highlightProperty(
view,
variableButtonEls[0],
"--my-color-1",
// definition in h2::before
"blueviolet"
);
await highlightProperty(
view,
variableButtonEls[1],
"--my-color-2",
// definition in h2
"chartreuse"
);
await highlightProperty(
view,
variableButtonEls[2],
"--my-color-3",
// definition in :root
"green"
);
rule = getRuleViewRuleEditorAt(view, 3).rule;
is(rule.selectorText, "h2", "Got expected h2 rule");
await highlightProperty(
view,
getJumpToDefinitionButtonForDeclaration(rule, {
color: "var(--my-color-1)",
})[0],
"--my-color-1",
// definition in :root
"tomato"
);
});
add_task(async function checkClearSearch() {
const fillerDeclarations = Array.from({ length: 50 }, (_, i) => ({
name: `line-height`,
value: i.toString(),
overridden: i !== 49,
}));
await addTab(
"data:text/html;charset=utf-8," +
encodeURIComponent(`
Filter
`)
);
const { inspector, view } = await openRuleView();
await selectNode("h1", inspector);
info("Check that search is cleared when clicking on the jump button");
await setSearchFilter(view, "--my-unique-var");
// check that the rule view is filtered as expected
await checkRuleViewContent(view, [
{ selector: "element", selectorEditable: false, declarations: [] },
{
selector: "h1",
declarations: [
{
name: "--my-unique-var",
value: "var(--my-color-1)",
},
],
highlighted: ["--my-unique-var: var(--my-color-1);"],
},
]);
const rule = getRuleViewRuleEditorAt(view, 1).rule;
is(rule.selectorText, "h1", "Got expected rule");
await highlightProperty(
view,
getJumpToDefinitionButtonForDeclaration(rule, {
"--my-unique-var": "var(--my-color-1)",
})[0],
"--my-color-1",
// definition in :root
"tomato"
);
is(view.searchField.value, "", "Search input was cleared");
// check that the rule view is no longer filtered
await checkRuleViewContent(view, [
{ selector: "element", selectorEditable: false, declarations: [] },
{ selector: "h1#title", declarations: fillerDeclarations },
{
selector: "h1",
declarations: [{ name: "--my-unique-var", value: "var(--my-color-1)" }],
},
{
header: "Inherited from html",
},
{
selector: ":root",
inherited: true,
declarations: [{ name: "--my-color-1", value: "tomato" }],
},
]);
});
add_task(async function checkJumpToUnusedVariable() {
await addTab(
"data:text/html;charset=utf-8," +
encodeURIComponent(`
for unused variables
`)
);
const { inspector, view } = await openRuleView();
await selectNode("h1", inspector);
info("Check that jump to definition of unused variables do work");
// If you have 2 rules, one with hidden custom properties, and the other one with
// custom properties not being hidden because we're not entering the threshold
// Make sure the clicking the Jump to definition button will reveal the hidden property
await selectNode("h3", inspector);
await checkRuleViewContent(view, [
{
selector: "element",
selectorEditable: false,
declarations: [],
},
{
selector: "h3",
declarations: [{ name: "--another-unused", value: "var(--unused-5)" }],
},
{
// Contains the hidden variables
selector: ":where(h3)",
// All the variables are hidden
declarations: [],
},
]);
is(
getUnusedVariableButton(view, 2)?.textContent,
"Show 15 unused custom CSS properties",
"Show unused variables button has expected text"
);
const rule = getRuleViewRuleEditorAt(view, 1).rule;
is(rule.selectorText, "h3", "Got expected rule");
const variableButtonEls = getJumpToDefinitionButtonForDeclaration(rule, {
"--another-unused": "var(--unused-5)",
});
is(variableButtonEls.length, 1, "There's one jump to variable button");
await highlightProperty(view, variableButtonEls[0], "--unused-5", "5");
await checkRuleViewContent(view, [
{
selector: "element",
selectorEditable: false,
declarations: [],
},
{
selector: "h3",
declarations: [{ name: "--another-unused", value: "var(--unused-5)" }],
},
{
// Contains the hidden variables
selector: ":where(h3)",
declarations: [{ name: "--unused-5", value: "5" }],
},
]);
is(
getUnusedVariableButton(view, 2)?.textContent,
"Show 14 unused custom CSS properties",
"Show unused variables button has expected text"
);
});
function getJumpToDefinitionButtonForDeclaration(rule, declaration) {
const [[name, value]] = Object.entries(declaration);
const textProp = rule.textProps.find(prop => {
return prop.name === name && prop.value === value;
});
if (!textProp) {
throw Error(`Declaration ${name}:${value} not found on rule`);
}
return textProp.editor.element.querySelectorAll(".ruleview-variable-link");
}
/**
* Click on the provided jump to definition button and wait for the element-highlighted
* event to be emitted.
*
* @param {RuleView} view
* @param {Element} jumpToDefinitionButton
* @param {string} expectedPropertyName: The name of the property that should be highlighted
* @param {string} expectedPropertyValue: The value of the property that should be highlighted
*/
async function highlightProperty(
view,
jumpToDefinitionButton,
expectedPropertyName,
expectedPropertyValue
) {
info(`Highlight "${expectedPropertyName}: ${expectedPropertyValue}"`);
const onHighlightProperty = view.once("element-highlighted");
jumpToDefinitionButton.click();
const highlightedElement = await onHighlightProperty;
const propertyNameEl = highlightedElement.querySelector(
".ruleview-propertyname"
);
is(
propertyNameEl.innerText,
expectedPropertyName,
"The expected element was highlighted"
);
is(
highlightedElement.querySelector(".ruleview-propertyvalue").innerText,
expectedPropertyValue,
"The expected element was highlighted"
);
// check that the declaration we jumped to is into view
ok(
isInViewport(highlightedElement, view.styleWindow),
`Highlighted element is in view`
);
is(
view.styleDocument.activeElement,
propertyNameEl,
"Focus is set on the declaration name element"
);
}
function isInViewport(element, win) {
const { top, left, bottom, right } = element.getBoundingClientRect();
return (
top >= 0 &&
bottom <= win.innerHeight &&
left >= 0 &&
right <= win.innerWidth
);
}