Check if the attribute value does match the definedd attr() attr-type parameter to see if the attribute value or the fallback is being used. In the attr() tooltip, indicate when the attribute value doesn't match the specified type. Differential Revision: https://phabricator.services.mozilla.com/D301490
384 lines
11 KiB
JavaScript
384 lines
11 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
// Test for attr() in rule view.
|
|
|
|
const TEST_URI = `data:text/html,<meta charset=utf8>
|
|
<style>
|
|
div::before {
|
|
content: attr(data-before);
|
|
}
|
|
|
|
div::after {
|
|
content: attr(data-after, "✕");
|
|
display: list-item;
|
|
}
|
|
|
|
div::after::marker {
|
|
content: attr(data-marker, "-");
|
|
}
|
|
|
|
article {
|
|
background-color: attr(unknown, attr(data-x type(<color>), attr(data-y type(<color>), tomato)));
|
|
}
|
|
</style>
|
|
<div id=with-attr data-before="→" data-after="←" data-marker="❥"></div>
|
|
<div id=without-attr></div>
|
|
<article data-x=10 data-y=gold>hello</article>`;
|
|
|
|
add_task(async function () {
|
|
await pushPref("layout.css.attr.enabled", true);
|
|
|
|
await addTab(TEST_URI);
|
|
const { inspector, view } = await openRuleView();
|
|
|
|
const withAttrNodeFront = await getNodeFront("#with-attr", inspector);
|
|
await selectNode(withAttrNodeFront, inspector);
|
|
|
|
expandPseudoElementContainer(view);
|
|
|
|
info(
|
|
"Check that the declarations using `attr()` are properly rendered and that the preview tooltip works as expected"
|
|
);
|
|
await assertAttr({
|
|
view,
|
|
description: `matched "data-before" on #with-attr node`,
|
|
propertyName: "content",
|
|
selector: "div::before",
|
|
expected: {
|
|
text: `attr(data-before)`,
|
|
attributeName: "data-before",
|
|
attributeUnmatched: false,
|
|
tooltipText: `"→"`,
|
|
fallback: null,
|
|
},
|
|
});
|
|
|
|
await assertAttr({
|
|
view,
|
|
description: `unmatched "data-after" fallback on #with-attr node`,
|
|
propertyName: "content",
|
|
selector: "div::after",
|
|
expected: {
|
|
text: `attr(data-after, "✕")`,
|
|
attributeName: "data-after",
|
|
attributeUnmatched: false,
|
|
tooltipText: `"←"`,
|
|
fallback: `"✕"`,
|
|
},
|
|
});
|
|
|
|
// Select #with-attr::after element
|
|
const withAttrChildren =
|
|
await inspector.markup.walker.children(withAttrNodeFront);
|
|
const withAttrAfterNode = withAttrChildren.nodes.at(-1);
|
|
await selectNode(withAttrAfterNode, inspector);
|
|
|
|
await assertAttr({
|
|
view,
|
|
description: `unmatched "data-after" fallback on #with-attr::after node`,
|
|
propertyName: "content",
|
|
selector: "div::after",
|
|
expected: {
|
|
text: `attr(data-after, "✕")`,
|
|
attributeName: "data-after",
|
|
attributeUnmatched: false,
|
|
tooltipText: `"←"`,
|
|
fallback: `"✕"`,
|
|
},
|
|
});
|
|
|
|
// Select ::after::marker element
|
|
const withAttrAfterChildren =
|
|
await inspector.markup.walker.children(withAttrAfterNode);
|
|
const withAttrAfterMarkerNode = withAttrAfterChildren.nodes[0];
|
|
await selectNode(withAttrAfterMarkerNode, inspector);
|
|
// Note that in the page, the fallback is being used, but shouldn't (see Bug 2012042),
|
|
// so we're showing the right thing here
|
|
await assertAttr({
|
|
view,
|
|
description: `unmatched "data-marker" fallback on #with-attr::after::marker node`,
|
|
propertyName: "content",
|
|
selector: "div::after::marker",
|
|
expected: {
|
|
text: `attr(data-marker, "-")`,
|
|
attributeName: "data-marker",
|
|
attributeUnmatched: false,
|
|
tooltipText: `"❥"`,
|
|
fallback: `"-"`,
|
|
},
|
|
});
|
|
|
|
const withoutAttrNodeFront = await getNodeFront("#without-attr", inspector);
|
|
await selectNode(withoutAttrNodeFront, inspector);
|
|
await assertAttr({
|
|
view,
|
|
description: `unmatched "data-before" on #without-attr node`,
|
|
propertyName: "content",
|
|
selector: "div::before",
|
|
expected: {
|
|
text: `attr(data-before)`,
|
|
attributeName: "data-before",
|
|
attributeUnmatched: true,
|
|
tooltipText: `Attribute data-before is not set`,
|
|
fallback: null,
|
|
},
|
|
});
|
|
await assertAttr({
|
|
view,
|
|
description: `unmatched "data-after" on #without-attr node`,
|
|
propertyName: "content",
|
|
selector: "div::after",
|
|
expected: {
|
|
text: `attr(data-after, "✕")`,
|
|
attributeName: "data-after",
|
|
attributeUnmatched: true,
|
|
tooltipText: `Attribute data-after is not set`,
|
|
fallback: `"✕"`,
|
|
},
|
|
});
|
|
|
|
// Select #with-attr::after element
|
|
const withoutAttrChildren =
|
|
await inspector.markup.walker.children(withoutAttrNodeFront);
|
|
const withoutAttrAfterNode = withoutAttrChildren.nodes.at(-1);
|
|
await selectNode(withoutAttrAfterNode, inspector);
|
|
await assertAttr({
|
|
view,
|
|
description: `unmatched "data-after" on #without-attr::after node`,
|
|
propertyName: "content",
|
|
selector: "div::after",
|
|
expected: {
|
|
text: `attr(data-after, "✕")`,
|
|
attributeName: "data-after",
|
|
attributeUnmatched: true,
|
|
tooltipText: `Attribute data-after is not set`,
|
|
fallback: `"✕"`,
|
|
},
|
|
});
|
|
|
|
// Select ::after::marker element
|
|
const withoutAttrAfterChildren =
|
|
await inspector.markup.walker.children(withoutAttrAfterNode);
|
|
const withoutAttrAfterMarkerNode = withoutAttrAfterChildren.nodes[0];
|
|
await selectNode(withoutAttrAfterMarkerNode, inspector);
|
|
await assertAttr({
|
|
view,
|
|
description: `unmatched "data-marker" on #without-attr::after::marker node`,
|
|
propertyName: "content",
|
|
selector: "div::after::marker",
|
|
expected: {
|
|
text: `attr(data-marker, "-")`,
|
|
attributeName: "data-marker",
|
|
attributeUnmatched: true,
|
|
tooltipText: `Attribute data-marker is not set`,
|
|
fallback: `"-"`,
|
|
},
|
|
});
|
|
|
|
info("Check that updating an attribute does update the rendering");
|
|
// Select the #without-attr
|
|
await selectNode(withoutAttrNodeFront, inspector);
|
|
|
|
info("Add the missing attribute");
|
|
let onRuleViewRefreshed = inspector.once("rule-view-refreshed");
|
|
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
|
|
content.document
|
|
.querySelector("#without-attr")
|
|
.setAttribute("data-after", "new-after");
|
|
});
|
|
await onRuleViewRefreshed;
|
|
await assertAttr({
|
|
view,
|
|
description: `newly matched "data-after" on #without-attr::after node`,
|
|
propertyName: "content",
|
|
selector: "div::after",
|
|
expected: {
|
|
text: `attr(data-after, "✕")`,
|
|
attributeName: "data-after",
|
|
attributeUnmatched: false,
|
|
tooltipText: `"new-after"`,
|
|
fallback: `"✕"`,
|
|
},
|
|
});
|
|
|
|
info("Update the attribute");
|
|
onRuleViewRefreshed = inspector.once("rule-view-refreshed");
|
|
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
|
|
content.document
|
|
.querySelector("#without-attr")
|
|
.setAttribute("data-after", "updated-after");
|
|
});
|
|
await onRuleViewRefreshed;
|
|
await assertAttr({
|
|
view,
|
|
description: `updated matched "data-after" on #without-attr::after node`,
|
|
propertyName: "content",
|
|
selector: "div::after",
|
|
expected: {
|
|
text: `attr(data-after, "✕")`,
|
|
attributeName: "data-after",
|
|
attributeUnmatched: false,
|
|
tooltipText: `"updated-after"`,
|
|
fallback: `"✕"`,
|
|
},
|
|
});
|
|
|
|
info("Remove the attribute");
|
|
onRuleViewRefreshed = inspector.once("rule-view-refreshed");
|
|
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
|
|
content.document
|
|
.querySelector("#without-attr")
|
|
.removeAttribute("data-after");
|
|
});
|
|
await onRuleViewRefreshed;
|
|
await assertAttr({
|
|
view,
|
|
description: `updated matched "data-after" on #without-attr::after node`,
|
|
propertyName: "content",
|
|
selector: "div::after",
|
|
expected: {
|
|
text: `attr(data-after, "✕")`,
|
|
attributeName: "data-after",
|
|
attributeUnmatched: true,
|
|
tooltipText: `Attribute data-after is not set`,
|
|
fallback: `"✕"`,
|
|
},
|
|
});
|
|
|
|
await selectNode("article", inspector);
|
|
info(
|
|
"Check that the declarations using nested `attr()` are properly rendered and that the preview tooltip works as expected"
|
|
);
|
|
await assertAttr({
|
|
view,
|
|
description: `with nested attr()`,
|
|
propertyName: "background-color",
|
|
selector: "article",
|
|
attrIndex: 0,
|
|
expected: {
|
|
text: `attr(unknown, attr(data-x type(<color>), attr(data-y type(<color>), tomato)))`,
|
|
attributeName: "unknown",
|
|
attributeUnmatched: true,
|
|
tooltipText: `Attribute unknown is not set`,
|
|
fallback: `attr(data-x type(<color>), attr(data-y type(<color>), tomato))`,
|
|
},
|
|
});
|
|
await assertAttr({
|
|
view,
|
|
description: `nested attr() in fallback`,
|
|
propertyName: "background-color",
|
|
selector: "article",
|
|
attrIndex: 1,
|
|
expected: {
|
|
text: `attr(unknown, attr(data-x type(<color>), attr(data-y type(<color>), tomato)))`,
|
|
attributeName: "data-x",
|
|
attributeUnmatched: true,
|
|
tooltipText: `Attribute value ("10") does not match expected "<color>" syntax`,
|
|
fallback: `attr(data-y type(<color>), tomato)`,
|
|
},
|
|
});
|
|
await assertAttr({
|
|
view,
|
|
description: `2-level deep nested attr()`,
|
|
propertyName: "background-color",
|
|
selector: "article",
|
|
attrIndex: 2,
|
|
expected: {
|
|
text: `attr(unknown, attr(data-x type(<color>), attr(data-y type(<color>), tomato)))`,
|
|
attributeName: "data-y",
|
|
attributeUnmatched: false,
|
|
tooltipText: `"gold"`,
|
|
fallback: "tomato",
|
|
},
|
|
});
|
|
});
|
|
|
|
async function assertAttr({
|
|
view,
|
|
propertyName,
|
|
selector,
|
|
description,
|
|
attrIndex = 0,
|
|
expected,
|
|
}) {
|
|
info(description);
|
|
const { valueSpan } = getRuleViewProperty(view, selector, propertyName);
|
|
is(
|
|
valueSpan.textContent,
|
|
expected.text,
|
|
`Got expected text for the property value`
|
|
);
|
|
const attributeEl =
|
|
valueSpan.querySelectorAll(".inspector-attr-name")[attrIndex] || null;
|
|
const fallbackEl =
|
|
valueSpan.querySelectorAll(".inspector-attr-fallback")[attrIndex] || null;
|
|
if (!attributeEl) {
|
|
ok(
|
|
false,
|
|
`Could not find an .inspector-attr-name element on passed ruleViewPropertyValueSpan`
|
|
);
|
|
return;
|
|
}
|
|
|
|
is(
|
|
attributeEl.textContent,
|
|
expected.attributeName,
|
|
"attribute element is the expected one"
|
|
);
|
|
is(
|
|
attributeEl
|
|
.closest(".inspector-attr-param")
|
|
.classList.contains("inspector-unmatched"),
|
|
expected.attributeUnmatched,
|
|
`attribute element ${expected.attributeUnmatched ? "has " : "doesn't have"} unmatched style`
|
|
);
|
|
|
|
if (!expected.fallback) {
|
|
is(fallbackEl, null, `There is no fallback element`);
|
|
} else {
|
|
is(
|
|
fallbackEl.textContent,
|
|
expected.fallback,
|
|
`Got expected fallback value`
|
|
);
|
|
is(
|
|
fallbackEl.classList.contains("inspector-unmatched"),
|
|
!expected.attributeUnmatched,
|
|
`attribute fallback element ${!expected.attributeUnmatched ? "has" : "doesn't have"} unmatched style`
|
|
);
|
|
}
|
|
|
|
// Ensure that the element can be targetted from EventUtils.
|
|
attributeEl.scrollIntoView();
|
|
|
|
const tooltip = view.tooltips.getTooltip("previewTooltip");
|
|
const onTooltipReady = tooltip.once("shown");
|
|
EventUtils.synthesizeMouseAtCenter(
|
|
attributeEl,
|
|
{ type: "mousemove" },
|
|
attributeEl.ownerDocument.defaultView
|
|
);
|
|
await onTooltipReady;
|
|
|
|
is(
|
|
tooltip.panel.textContent,
|
|
expected.tooltipText,
|
|
"Tooltip has expected text"
|
|
);
|
|
|
|
info("Hide the tooltip");
|
|
const onHidden = tooltip.once("hidden");
|
|
// Move the mouse elsewhere to hide the tooltip
|
|
EventUtils.synthesizeMouse(
|
|
attributeEl.ownerDocument.body,
|
|
1,
|
|
1,
|
|
{ type: "mousemove" },
|
|
attributeEl.ownerDocument.defaultView
|
|
);
|
|
await onHidden;
|
|
}
|