Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
"use strict";
// Tests that various mutations to the dom update the markup view correctly.
const TEST_URL = URL_ROOT + "doc_markup_mutation.html";
// Mutation tests. Each entry in the array has the following properties:
// - desc: for logging only
// - numMutations: how many mutations are expected to come happen due to the
// test case. Defaults to 1 if not set.
// - test: a function supposed to mutate the DOM
// - check: a function supposed to test that the mutation was handled
const TEST_DATA = [
{
desc: "Adding an attribute",
async test() {
await setContentPageElementAttribute("#node1", "newattr", "newattrval");
},
async check(inspector) {
const { editor } = await getContainerForSelector("#node1", inspector);
ok(
[...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
return (
attr.textContent.trim() === 'newattr="newattrval"' &&
attr.dataset.value === "newattrval" &&
attr.dataset.attr === "newattr"
);
}),
"newattr attribute found"
);
},
},
{
desc: "Removing an attribute",
async test() {
await removeContentPageElementAttribute("#node1", "newattr");
},
async check(inspector) {
const { editor } = await getContainerForSelector("#node1", inspector);
ok(
![...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
return attr.textContent.trim() === 'newattr="newattrval"';
}),
"newattr attribute removed"
);
},
},
{
desc: "Re-adding an attribute",
async test() {
await setContentPageElementAttribute("#node1", "newattr", "newattrval");
},
async check(inspector) {
const { editor } = await getContainerForSelector("#node1", inspector);
ok(
[...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
return (
attr.textContent.trim() === 'newattr="newattrval"' &&
attr.dataset.value === "newattrval" &&
attr.dataset.attr === "newattr"
);
}),
"newattr attribute found"
);
},
},
{
desc: "Changing an attribute",
async test() {
await setContentPageElementAttribute(
"#node1",
"newattr",
"newattrchanged"
);
},
async check(inspector) {
const { editor } = await getContainerForSelector("#node1", inspector);
ok(
[...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
return (
attr.textContent.trim() === 'newattr="newattrchanged"' &&
attr.dataset.value === "newattrchanged" &&
attr.dataset.attr === "newattr"
);
}),
"newattr attribute found"
);
},
},
{
desc: "Adding another attribute does not rerender unchanged attributes",
async test(inspector) {
const { editor } = await getContainerForSelector("#node1", inspector);
// This test checks the impact on the markup-view nodes after setting attributes on
// content nodes.
info("Expect attribute-container for 'new-attr' from the previous test");
const attributeContainer = editor.attrList.querySelector(
"[data-attr=newattr]"
);
ok(attributeContainer, "attribute-container for 'newattr' found");
info("Set a flag on the attribute-container to check after the mutation");
attributeContainer.beforeMutationFlag = true;
info(
"Add the attribute 'otherattr' on the content node to trigger the mutation"
);
await setContentPageElementAttribute("#node1", "otherattr", "othervalue");
},
async check(inspector) {
const { editor } = await getContainerForSelector("#node1", inspector);
info(
"Check the attribute-container for the new attribute mutation was created"
);
const otherAttrContainer = editor.attrList.querySelector(
"[data-attr=otherattr]"
);
ok(otherAttrContainer, "attribute-container for 'otherattr' found");
info(
"Check the attribute-container for 'new-attr' is the same node as earlier."
);
const newAttrContainer = editor.attrList.querySelector(
"[data-attr=newattr]"
);
ok(newAttrContainer, "attribute-container for 'newattr' found");
ok(
newAttrContainer.beforeMutationFlag,
"attribute-container same as earlier"
);
},
},
{
desc: "Adding ::after element",
numMutations: 2,
async test() {
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
const node1 = content.document.querySelector("#node1");
node1.classList.add("pseudo");
});
},
async check(inspector) {
const { children } = await getContainerForSelector("#node1", inspector);
is(
children.childNodes.length,
2,
"Node1 now has 2 children (text child and ::after"
);
},
},
{
desc: "Removing ::after element",
numMutations: 2,
async test() {
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
const node1 = content.document.querySelector("#node1");
node1.classList.remove("pseudo");
});
},
async check(inspector) {
const container = await getContainerForSelector("#node1", inspector);
ok(container.inlineTextChild, "Has single text child.");
},
},
{
desc: "Updating the text-content",
async test() {
await setContentPageElementProperty("#node1", "textContent", "newtext");
},
async check(inspector) {
const container = await getContainerForSelector("#node1", inspector);
ok(container.inlineTextChild, "Has single text child.");
ok(!container.canExpand, "Can't expand container with inlineTextChild.");
ok(!container.inlineTextChild.canExpand, "Can't expand inlineTextChild.");
is(
container.editor.elt.querySelector(".text").textContent.trim(),
"newtext",
"Single text child editor updated."
);
},
},
{
desc: "Adding a second text child",
async test() {
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
const node1 = content.document.querySelector("#node1");
const newText = node1.ownerDocument.createTextNode("more");
node1.appendChild(newText);
});
},
async check(inspector) {
const container = await getContainerForSelector("#node1", inspector);
ok(!container.inlineTextChild, "Does not have single text child.");
ok(container.canExpand, "Can expand container with child nodes.");
Assert.equal(
container.editor.elt.querySelector(".text"),
null,
"Single text child editor removed."
);
},
},
{
desc: "Go from 2 to 1 text child",
async test() {
await setContentPageElementProperty("#node1", "textContent", "newtext");
},
async check(inspector) {
const container = await getContainerForSelector("#node1", inspector);
ok(container.inlineTextChild, "Has single text child.");
ok(!container.canExpand, "Can't expand container with inlineTextChild.");
ok(!container.inlineTextChild.canExpand, "Can't expand inlineTextChild.");
is(
container.editor.elt.querySelector(".text").textContent.trim(),
"newtext",
"Single text child editor updated."
);
},
},
{
desc: "Removing an only text child",
async test() {
await setContentPageElementProperty("#node1", "innerHTML", "");
},
async check(inspector) {
const container = await getContainerForSelector("#node1", inspector);
ok(!container.inlineTextChild, "Does not have single text child.");
ok(!container.canExpand, "Can't expand empty container.");
Assert.equal(
container.editor.elt.querySelector(".text"),
null,
"Single text child editor removed."
);
},
},
{
desc: "Go from 0 to 1 text child",
async test() {
await setContentPageElementProperty("#node1", "textContent", "newtext");
},
async check(inspector) {
const container = await getContainerForSelector("#node1", inspector);
ok(container.inlineTextChild, "Has single text child.");
ok(!container.canExpand, "Can't expand container with inlineTextChild.");
ok(!container.inlineTextChild.canExpand, "Can't expand inlineTextChild.");
is(
container.editor.elt.querySelector(".text").textContent.trim(),
"newtext",
"Single text child editor updated."
);
},
},
{
desc: "Updating the innerHTML",
async test() {
await setContentPageElementProperty(
"#node2",
"innerHTML",
"<div><span>foo</span></div>"
);
},
async check(inspector) {
const container = await getContainerForSelector("#node2", inspector);
const openTags = container.children.querySelectorAll(".open .tag");
is(openTags.length, 2, "There are 2 tags in node2");
is(openTags[0].textContent.trim(), "div", "The first tag is a div");
is(openTags[1].textContent.trim(), "span", "The second tag is a span");
is(
container.children.querySelector(".text").textContent.trim(),
"foo",
"The span's textcontent is correct"
);
},
},
{
desc: "Removing child nodes",
async test() {
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
const node4 = content.document.querySelector("#node4");
while (node4.firstChild) {
node4.firstChild.remove();
}
});
},
async check(inspector) {
const { children } = await getContainerForSelector("#node4", inspector);
is(children.innerHTML, "", "Children have been removed");
},
},
{
desc: "Appending a child to a different parent",
async test() {
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
const node17 = content.document.querySelector("#node17");
const node2 = content.document.querySelector("#node2");
node2.appendChild(node17);
});
},
async check(inspector) {
const { children } = await getContainerForSelector("#node16", inspector);
is(
children.innerHTML,
"",
"Node17 has been removed from its node16 parent"
);
const container = await getContainerForSelector("#node2", inspector);
const openTags = container.children.querySelectorAll(".open .tag");
is(openTags.length, 3, "There are now 3 tags in node2");
is(openTags[2].textContent.trim(), "p", "The third tag is node17");
},
},
{
desc: "Swapping a parent and child element, putting them in the same tree",
// body
// node1
// node18
// node19
// node20
// node21
// will become:
// body
// node1
// node20
// node21
// node18
// node19
async test() {
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
const node18 = content.document.querySelector("#node18");
const node20 = content.document.querySelector("#node20");
const node1 = content.document.querySelector("#node1");
node1.appendChild(node20);
node20.appendChild(node18);
});
},
async check(inspector) {
await inspector.markup.expandAll();
const { children } = await getContainerForSelector("#node1", inspector);
is(
children.childNodes.length,
2,
"Node1 now has 2 children (textnode and node20)"
);
const node20 = children.childNodes[1];
const node20Children = node20.container.children;
is(
node20Children.childNodes.length,
2,
"Node20 has 2 children (21 and 18)"
);
const node21 = node20Children.childNodes[0];
is(
node21.container.editor.elt.querySelector(".text").textContent.trim(),
"line21",
"Node21 has a single text child"
);
const node18 = node20Children.childNodes[1];
is(
node18
.querySelector(".open .attreditor .attr-value")
.textContent.trim(),
"node18",
"Node20's second child is indeed node18"
);
},
},
];
add_task(async function () {
const { inspector } = await openInspectorForURL(TEST_URL);
info("Expanding all markup-view nodes");
await inspector.markup.expandAll();
for (let { desc, test, check, numMutations } of TEST_DATA) {
info("Starting test: " + desc);
numMutations = numMutations || 1;
info("Executing the test markup mutation");
// If a test expects more than one mutation it may come through in a single
// event or possibly in multiples.
let seenMutations = 0;
const promise = new Promise(resolve => {
inspector.on("markupmutation", function onmutation(mutations) {
seenMutations += mutations.length;
info(
"Receieved " +
seenMutations +
" mutations, expecting at least " +
numMutations
);
if (seenMutations >= numMutations) {
inspector.off("markupmutation", onmutation);
resolve();
}
});
});
await test(inspector);
await promise;
info("Expanding all markup-view nodes to make sure new nodes are imported");
await inspector.markup.expandAll();
info("Checking the markup-view content");
await check(inspector);
}
});