Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
"use strict";
// https rather than chrome to improve coverage
const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html";
const SIDEBAR_PREF = "devtools.styleeditor.showAtRulesSidebar";
const RESIZE_W = 300;
const RESIZE_H = 450;
const LABELS = [
"not all",
"all",
"(max-width: 550px)",
"(min-height: 300px) and (max-height: 320px)",
"(max-width: 750px)",
"",
"print",
];
const LINE_NOS = [1, 7, 19, 25, 31, 34, 39];
const NEW_RULE = `
@media (max-width: 750px) {
div {
color: blue;
@layer {
border-color: tomato;
}
}
@media print {
body {
filter: grayscale(100%);
}
}
}`;
waitForExplicitFinish();
add_task(async function () {
await pushPref("layout.css.container-queries.enabled", true);
// Enable @property rules
await pushPref("layout.css.properties-and-values.enabled", true);
const { ui } = await openStyleEditorForURL(TESTCASE_URI);
is(ui.editors.length, 4, "correct number of editors");
info("Test first plain css editor");
const plainEditor = ui.editors[0];
await openEditor(plainEditor);
testPlainEditor(plainEditor);
info("Test editor for inline sheet with @media rules");
const inlineMediaEditor = ui.editors[3];
await openEditor(inlineMediaEditor);
await testInlineMediaEditor(ui, inlineMediaEditor);
info("Test editor with @media rules");
const mediaEditor = ui.editors[1];
await openEditor(mediaEditor);
await testMediaEditor(ui, mediaEditor);
info("Test that sidebar hides when flipping pref");
await testShowHide(ui, mediaEditor);
info("Test adding a rule updates the list");
await testMediaRuleAdded(ui, mediaEditor);
info("Test resizing and seeing @media matching state change");
const originalWidth = window.outerWidth;
const originalHeight = window.outerHeight;
const onMatchesChange = ui.once("at-rules-list-changed");
window.resizeTo(RESIZE_W, RESIZE_H);
await onMatchesChange;
testMediaMatchChanged(mediaEditor);
window.resizeTo(originalWidth, originalHeight);
});
function testPlainEditor(editor) {
const sidebar = editor.details.querySelector(".stylesheet-sidebar");
is(sidebar.hidden, true, "sidebar is hidden on editor without @media");
}
async function testInlineMediaEditor(ui, editor) {
const sidebar = editor.details.querySelector(".stylesheet-sidebar");
is(sidebar.hidden, false, "sidebar is showing on editor with @media");
const entries = sidebar.querySelectorAll(".at-rule-label");
is(entries.length, 7, "7 at-rules displayed in sidebar");
await testRule({
ui,
editor,
rule: entries[0],
conditionText: "screen",
matches: true,
line: 2,
type: "media",
});
await testRule({
ui,
editor,
rule: entries[1],
conditionText: "(display: flex)",
line: 7,
type: "support",
});
await testRule({
ui,
editor,
rule: entries[2],
conditionText: "(1px < height < 10000px)",
matches: true,
line: 8,
type: "media",
});
await testRule({
ui,
editor,
rule: entries[3],
line: 16,
type: "layer",
layerName: "myLayer",
});
await testRule({
ui,
editor,
rule: entries[4],
conditionText: "(min-width: 1px)",
line: 17,
type: "container",
});
await testRule({
ui,
editor,
rule: entries[5],
conditionText: "selector(&)",
line: 21,
type: "support",
});
await testRule({
ui,
editor,
rule: entries[6],
line: 30,
type: "property",
propertyName: "--my-property",
});
}
async function testMediaEditor(ui, editor) {
const sidebar = editor.details.querySelector(".stylesheet-sidebar");
is(sidebar.hidden, false, "sidebar is showing on editor with @media");
const entries = [...sidebar.querySelectorAll(".at-rule-label")];
is(entries.length, 4, "four @media rules displayed in sidebar");
await testRule({
ui,
editor,
rule: entries[0],
conditionText: LABELS[0],
matches: false,
line: LINE_NOS[0],
});
await testRule({
ui,
editor,
rule: entries[1],
conditionText: LABELS[1],
matches: true,
line: LINE_NOS[1],
});
await testRule({
ui,
editor,
rule: entries[2],
conditionText: LABELS[2],
matches: false,
line: LINE_NOS[2],
});
await testRule({
ui,
editor,
rule: entries[3],
conditionText: LABELS[3],
matches: false,
line: LINE_NOS[3],
});
}
function testMediaMatchChanged(editor) {
const sidebar = editor.details.querySelector(".stylesheet-sidebar");
const cond = sidebar.querySelectorAll(".at-rule-condition")[2];
is(
cond.textContent,
"(max-width: 550px)",
"third rule condition text is correct"
);
ok(
!cond.classList.contains("media-condition-unmatched"),
"media rule is now matched after resizing"
);
}
async function testShowHide(ui, editor) {
let sidebarChange = ui.once("at-rules-list-changed");
Services.prefs.setBoolPref(SIDEBAR_PREF, false);
await sidebarChange;
const sidebar = editor.details.querySelector(".stylesheet-sidebar");
is(sidebar.hidden, true, "sidebar is hidden after flipping pref");
sidebarChange = ui.once("at-rules-list-changed");
Services.prefs.clearUserPref(SIDEBAR_PREF);
await sidebarChange;
is(sidebar.hidden, false, "sidebar is showing after flipping pref back");
}
async function testMediaRuleAdded(ui, editor) {
await editor.getSourceEditor();
const sidebar = editor.details.querySelector(".stylesheet-sidebar");
is(
sidebar.querySelectorAll(".at-rule-label").length,
4,
"4 @media rules after changing text"
);
let text = editor.sourceEditor.getText();
text += NEW_RULE;
const listChange = ui.once("at-rules-list-changed");
editor.sourceEditor.setText(text);
await listChange;
const entries = [...sidebar.querySelectorAll(".at-rule-label")];
is(entries.length, 7, "7 @media rules after changing text");
await testRule({
ui,
editor,
rule: entries[4],
conditionText: LABELS[4],
matches: false,
line: LINE_NOS[4],
});
await testRule({
ui,
editor,
rule: entries[5],
type: "layer",
conditionText: LABELS[5],
line: LINE_NOS[5],
});
await testRule({
ui,
editor,
rule: entries[6],
conditionText: LABELS[6],
matches: false,
line: LINE_NOS[6],
});
}
/**
* Run assertion on given rule
*
* @param {Object} options
* @param {StyleEditorUI} options.ui
* @param {StyleSheetEditor} options.editor: The editor the rule is displayed in
* @param {Element} options.rule: The rule element in the media sidebar
* @param {String} options.conditionText: at-rule condition text (for @media, @container, @support)
* @param {Boolean} options.matches: Whether or not the document matches the rule
* @param {String} options.layerName: Optional name of the @layer
* @param {String} options.propertyName: Name of the @property if type is "property"
* @param {Number} options.line: Line of the rule
* @param {String} options.type: The type of the rule (container, layer, media, support, property ).
* Defaults to "media".
*/
async function testRule({
ui,
editor,
rule,
conditionText = "",
matches,
layerName,
propertyName,
line,
type = "media",
}) {
const atTypeEl = rule.querySelector(".at-rule-type");
let name;
if (type === "layer") {
name = layerName;
} else if (type === "property") {
name = propertyName;
}
is(
atTypeEl.textContent,
`@${type}\u00A0${name ? `${name}\u00A0` : ""}`,
"label for at-rule type is correct"
);
const cond = rule.querySelector(".at-rule-condition");
is(
cond.textContent,
conditionText,
"condition label is correct for " + conditionText
);
if (type == "media") {
const matched = !cond.classList.contains("media-condition-unmatched");
ok(
matches ? matched : !matched,
"media rule is " + (matches ? "matched" : "unmatched")
);
}
const ruleLine = rule.querySelector(".at-rule-line");
is(ruleLine.textContent, ":" + line, "correct line number shown");
info(
"Check that clicking on the rule jumps to the expected position in the stylesheet"
);
rule.click();
await waitFor(
() =>
ui.selectedEditor == editor &&
editor.sourceEditor.getCursor().line == line - 1
);
ok(true, "Jumped to the expected location");
}
/* Helpers */
function openEditor(editor) {
getLinkFor(editor).click();
return editor.getSourceEditor();
}
function getLinkFor(editor) {
return editor.summary.querySelector(".stylesheet-name");
}