Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint no-unused-vars: [2, {"vars": "local"}] */
/* global _snapshots */
"use strict";
var { require } = ChromeUtils.importESModule(
"resource://devtools/shared/loader/Loader.sys.mjs"
);
var { Assert } = ChromeUtils.importESModule(
);
var { gDevTools } = require("resource://devtools/client/framework/devtools.js");
var { BrowserLoader } = ChromeUtils.importESModule(
"resource://devtools/shared/loader/browser-loader.sys.mjs"
);
var {
DevToolsServer,
} = require("resource://devtools/server/devtools-server.js");
var {
DevToolsClient,
} = require("resource://devtools/client/devtools-client.js");
var DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
var { Toolbox } = require("resource://devtools/client/framework/toolbox.js");
var { require: browserRequire } = BrowserLoader({
window,
});
const React = browserRequire("devtools/client/shared/vendor/react");
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
const dom = browserRequire("devtools/client/shared/vendor/react-dom-factories");
const TestUtils = browserRequire(
"devtools/client/shared/vendor/react-dom-test-utils"
);
const ShallowRenderer = browserRequire(
"devtools/client/shared/vendor/react-test-renderer-shallow"
);
const TestRenderer = browserRequire(
"devtools/client/shared/vendor/react-test-renderer"
);
SimpleTest.registerCleanupFunction(() => {
window._snapshots = null;
});
function forceRender(comp) {
return setState(comp, {}).then(() => setState(comp, {}));
}
// All tests are asynchronous.
SimpleTest.waitForExplicitFinish();
function onNextAnimationFrame(fn) {
return () => requestAnimationFrame(() => requestAnimationFrame(fn));
}
function setState(component, newState) {
return new Promise(resolve => {
component.setState(newState, onNextAnimationFrame(resolve));
});
}
function dumpn(msg) {
dump(`SHARED-COMPONENTS-TEST: ${msg}\n`);
}
/**
* Tree View
*/
const TEST_TREE_VIEW = {
A: { label: "A", value: "A" },
B: { label: "B", value: "B" },
C: { label: "C", value: "C" },
D: { label: "D", value: "D" },
E: { label: "E", value: "E" },
F: { label: "F", value: "F" },
G: { label: "G", value: "G" },
H: { label: "H", value: "H" },
I: { label: "I", value: "I" },
J: { label: "J", value: "J" },
K: { label: "K", value: "K" },
L: { label: "L", value: "L" },
};
TEST_TREE_VIEW.children = {
A: [TEST_TREE_VIEW.B, TEST_TREE_VIEW.C, TEST_TREE_VIEW.D],
B: [TEST_TREE_VIEW.E, TEST_TREE_VIEW.F, TEST_TREE_VIEW.G],
C: [TEST_TREE_VIEW.H, TEST_TREE_VIEW.I],
D: [TEST_TREE_VIEW.J],
E: [TEST_TREE_VIEW.K, TEST_TREE_VIEW.L],
F: [],
G: [],
H: [],
I: [],
J: [],
K: [],
L: [],
};
const TEST_TREE_VIEW_INTERFACE = {
provider: {
getChildren: x => TEST_TREE_VIEW.children[x.label],
hasChildren: x => !!TEST_TREE_VIEW.children[x.label].length,
getLabel: x => x.label,
getValue: x => x.value,
getKey: x => x.label,
getType: () => "string",
},
object: TEST_TREE_VIEW.A,
columns: [{ id: "default" }, { id: "value" }],
};
/**
* Tree
*/
var TEST_TREE_INTERFACE = {
getParent: x => TEST_TREE.parent[x],
getChildren: x => TEST_TREE.children[x],
renderItem: (x, depth, focused) =>
"-".repeat(depth) + x + ":" + focused + "\n",
getRoots: () => ["A", "M"],
getKey: x => "key-" + x,
itemHeight: 1,
onExpand: x => TEST_TREE.expanded.add(x),
onCollapse: x => TEST_TREE.expanded.delete(x),
isExpanded: x => TEST_TREE.expanded.has(x),
};
function isRenderedTree(actual, expectedDescription, msg) {
const expected = expectedDescription.map(x => x + "\n").join("");
dumpn(`Expected tree:\n${expected}`);
dumpn(`Actual tree:\n${actual}`);
is(actual, expected, msg);
}
function isAccessibleTree(tree, options = {}) {
const treeNode = tree.refs.tree;
is(treeNode.getAttribute("tabindex"), "0", "Tab index is set");
is(treeNode.getAttribute("role"), "tree", "Tree semantics is present");
if (options.hasActiveDescendant) {
ok(
treeNode.hasAttribute("aria-activedescendant"),
"Tree has an active descendant set"
);
}
const treeNodes = [...treeNode.querySelectorAll(".tree-node")];
for (const node of treeNodes) {
ok(node.id, "TreeNode has an id");
is(node.getAttribute("role"), "treeitem", "Tree item semantics is present");
is(
parseInt(node.getAttribute("aria-level"), 10),
parseInt(node.getAttribute("data-depth"), 10) + 1,
"Aria level attribute is set correctly"
);
}
}
// Encoding of the following tree/forest:
//
// A
// |-- B
// | |-- E
// | | |-- K
// | | `-- L
// | |-- F
// | `-- G
// |-- C
// | |-- H
// | `-- I
// `-- D
// `-- J
// M
// `-- N
// `-- O
var TEST_TREE = {
children: {
A: ["B", "C", "D"],
B: ["E", "F", "G"],
C: ["H", "I"],
D: ["J"],
E: ["K", "L"],
F: [],
G: [],
H: [],
I: [],
J: [],
K: [],
L: [],
M: ["N"],
N: ["O"],
O: [],
},
parent: {
A: null,
B: "A",
C: "A",
D: "A",
E: "B",
F: "B",
G: "B",
H: "C",
I: "C",
J: "D",
K: "E",
L: "E",
M: null,
N: "M",
O: "N",
},
expanded: new Set(),
};
/**
* Frame
*/
function checkFrameString({
el,
file,
line,
column,
source,
functionName,
shouldLink,
tooltip,
locationPrefix,
}) {
const $ = selector => el.querySelector(selector);
const $func = $(".frame-link-function-display-name");
const $source = $(".frame-link-source");
const $locationPrefix = $(".frame-link-prefix");
const $filename = $(".frame-link-filename");
const $line = $(".frame-link-line");
is($filename.textContent, file, "Correct filename");
is(
el.getAttribute("data-line"),
line ? `${line}` : null,
"Expected `data-line` found"
);
is(
el.getAttribute("data-column"),
column ? `${column}` : null,
"Expected `data-column` found"
);
is($source.getAttribute("title"), tooltip, "Correct tooltip");
is($source.tagName, shouldLink ? "A" : "SPAN", "Correct linkable status");
if (shouldLink) {
is($source.getAttribute("href"), source, "Correct source");
}
if (line != null) {
let lineText = `:${line}`;
if (column != null) {
lineText += `:${column}`;
}
is($line.textContent, lineText, "Correct line number");
} else {
ok(!$line, "Should not have an element for `line`");
}
if (functionName != null) {
is($func.textContent, functionName, "Correct function name");
} else {
ok(!$func, "Should not have an element for `functionName`");
}
if (locationPrefix != null) {
is($locationPrefix.textContent, locationPrefix, "Correct prefix");
} else {
ok(!$locationPrefix, "Should not have an element for `locationPrefix`");
}
}
function checkSmartFrameString({ el, location, functionName, tooltip }) {
const $ = selector => el.querySelector(selector);
const $func = $(".title");
const $location = $(".location");
is($location.textContent, location, "Correct filename");
is(el.getAttribute("title"), tooltip, "Correct tooltip");
if (functionName != null) {
is($func.textContent, functionName, "Correct function name");
} else {
ok(!$func, "Should not have an element for `functionName`");
}
}
function renderComponent(component, props) {
const el = React.createElement(component, props, {});
// By default, renderIntoDocument() won't work for stateless components, but
// it will work if the stateless component is wrapped in a stateful one.
const wrappedEl = dom.span({}, [el]);
const renderedComponent = TestUtils.renderIntoDocument(wrappedEl);
return ReactDOM.findDOMNode(renderedComponent).children[0];
}
function shallowRenderComponent(component, props) {
const el = React.createElement(component, props);
const renderer = new ShallowRenderer();
renderer.render(el, {});
return renderer.getRenderOutput();
}
/**
* Creates a React Component for testing
*
* @param {string} factory - factory object of the component to be created
* @param {object} props - React props for the component
* @returns {object} - container Node, Object with React component
* and querySelector function with $ as name.
*/
async function createComponentTest(factory, props) {
const container = document.createElement("div");
document.body.appendChild(container);
const component = ReactDOM.render(factory(props), container);
await forceRender(component);
return {
container,
component,
$: s => container.querySelector(s),
};
}
async function waitFor(condition = () => true, delay = 50) {
do {
const res = condition();
if (res) {
return res;
}
await new Promise(resolve => setTimeout(resolve, delay));
} while (true);
}
/**
* Matches a component tree rendererd using TestRenderer to a given expected JSON
* snapshot.
* @param {String} name
* Name of the function derived from a test [step] name.
* @param {Object} el
* React element to be rendered using TestRenderer.
*/
function matchSnapshot(name, el) {
if (!_snapshots) {
is(false, "No snapshots were loaded into test.");
}
const snapshot = _snapshots[name];
if (snapshot === undefined) {
is(false, `Snapshot for "${name}" not found.`);
}
const renderer = TestRenderer.create(el, {});
const tree = renderer.toJSON();
is(
JSON.stringify(tree, (key, value) =>
typeof value === "function" ? value.toString() : value
),
JSON.stringify(snapshot),
name
);
}