import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,6 @@
"use strict";
module.exports = {
// Extend from the common devtools xpcshell eslintrc config.
"extends": "../../../../.eslintrc.xpcshell.js"
};

View file

@ -0,0 +1,36 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that VariablesView._doSearch() works even without an attached
// VariablesViewController (bug 1196341).
var Cu = Components.utils;
var Cc = Components.classes;
var Ci = Components.interfaces;
const DOMParser = Cc["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Ci.nsIDOMParser);
const { VariablesView } =
Cu.import("resource://devtools/client/shared/widgets/VariablesView.jsm", {});
function run_test() {
let doc = DOMParser.parseFromString("<div>", "text/html");
let container = doc.body.firstChild;
ok(container, "Got a container.");
let vv = new VariablesView(container, { searchEnabled: true });
let scope = vv.addScope("Test scope");
let item1 = scope.addItem("a", { value: "1" });
let item2 = scope.addItem("b", { value: "2" });
do_print("Performing a search without a controller.");
vv._doSearch("a");
equal(item1.target.hasAttribute("unmatched"), false,
"First item that matched the filter is visible.");
equal(item2.target.hasAttribute("unmatched"), true,
"The second item that did not match the filter is hidden.");
}

View file

@ -0,0 +1,76 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { VariablesView } = Components.utils.import("resource://devtools/client/shared/widgets/VariablesView.jsm", {});
const PENDING = {
"type": "object",
"class": "Promise",
"actor": "conn0.pausedobj35",
"extensible": true,
"frozen": false,
"sealed": false,
"promiseState": {
"state": "pending"
},
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {}
}
};
const FULFILLED = {
"type": "object",
"class": "Promise",
"actor": "conn0.pausedobj35",
"extensible": true,
"frozen": false,
"sealed": false,
"promiseState": {
"state": "fulfilled",
"value": 10
},
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {}
}
};
const REJECTED = {
"type": "object",
"class": "Promise",
"actor": "conn0.pausedobj35",
"extensible": true,
"frozen": false,
"sealed": false,
"promiseState": {
"state": "rejected",
"reason": 10
},
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {}
}
};
function run_test() {
equal(VariablesView.getString(PENDING, { concise: true }), "Promise");
equal(VariablesView.getString(PENDING), 'Promise {<state>: "pending"}');
equal(VariablesView.getString(FULFILLED, { concise: true }), "Promise");
equal(VariablesView.getString(FULFILLED),
'Promise {<state>: "fulfilled", <value>: 10}');
equal(VariablesView.getString(REJECTED, { concise: true }), "Promise");
equal(VariablesView.getString(REJECTED), 'Promise {<state>: "rejected", <reason>: 10}');
}

View file

@ -0,0 +1,31 @@
/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the advanceValidate function from rule-view.js.
const {utils: Cu, interfaces: Ci} = Components;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {advanceValidate} = require("devtools/client/inspector/shared/utils");
// 1 2 3
// 0123456789012345678901234567890
const sampleInput = '\\symbol "string" url(somewhere)';
function testInsertion(where, result, testName) {
do_print(testName);
equal(advanceValidate(Ci.nsIDOMKeyEvent.DOM_VK_SEMICOLON, sampleInput, where),
result, "testing advanceValidate at " + where);
}
function run_test() {
testInsertion(4, true, "inside a symbol");
testInsertion(1, false, "after a backslash");
testInsertion(8, true, "after whitespace");
testInsertion(11, false, "inside a string");
testInsertion(24, false, "inside a URL");
testInsertion(31, true, "at the end");
}

View file

@ -0,0 +1,73 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test splitBy from node-attribute-parser.js
const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
const {splitBy} = require("devtools/client/shared/node-attribute-parser");
const TEST_DATA = [{
value: "this is a test",
splitChar: " ",
expected: [
{value: "this"},
{value: " ", type: "string"},
{value: "is"},
{value: " ", type: "string"},
{value: "a"},
{value: " ", type: "string"},
{value: "test"}
]
}, {
value: "/path/to/handler",
splitChar: " ",
expected: [
{value: "/path/to/handler"}
]
}, {
value: "test",
splitChar: " ",
expected: [
{value: "test"}
]
}, {
value: " test ",
splitChar: " ",
expected: [
{value: " ", type: "string"},
{value: "test"},
{value: " ", type: "string"}
]
}, {
value: "",
splitChar: " ",
expected: []
}, {
value: " ",
splitChar: " ",
expected: [
{value: " ", type: "string"},
{value: " ", type: "string"},
{value: " ", type: "string"}
]
}];
function run_test() {
for (let {value, splitChar, expected} of TEST_DATA) {
do_print("Splitting string: " + value);
let tokens = splitBy(value, splitChar);
do_print("Checking that the number of parsed tokens is correct");
do_check_eq(tokens.length, expected.length);
for (let i = 0; i < tokens.length; i++) {
do_print("Checking the data in token " + i);
do_check_eq(tokens[i].value, expected[i].value);
if (expected[i].type) {
do_check_eq(tokens[i].type, expected[i].type);
}
}
}
}

View file

@ -0,0 +1,134 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test parseAttribute from node-attribute-parser.js
const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
const {parseAttribute} = require("devtools/client/shared/node-attribute-parser");
const TEST_DATA = [{
tagName: "body",
namespaceURI: "http://www.w3.org/1999/xhtml",
attributeName: "class",
attributeValue: "some css class names",
expected: [
{value: "some css class names", type: "string"}
]
}, {
tagName: "box",
namespaceURI: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
attributeName: "datasources",
attributeValue: "/url/1?test=1#test http://mozilla.org/wow",
expected: [
{value: "/url/1?test=1#test", type: "uri"},
{value: " ", type: "string"},
{value: "http://mozilla.org/wow", type: "uri"}
]
}, {
tagName: "form",
namespaceURI: "http://www.w3.org/1999/xhtml",
attributeName: "action",
attributeValue: "/path/to/handler",
expected: [
{value: "/path/to/handler", type: "uri"}
]
}, {
tagName: "a",
namespaceURI: "http://www.w3.org/1999/xhtml",
attributeName: "ping",
attributeValue: "http://analytics.com/track?id=54 http://analytics.com/track?id=55",
expected: [
{value: "http://analytics.com/track?id=54", type: "uri"},
{value: " ", type: "string"},
{value: "http://analytics.com/track?id=55", type: "uri"}
]
}, {
tagName: "link",
namespaceURI: "http://www.w3.org/1999/xhtml",
attributeName: "href",
attributeValue: "styles.css",
otherAttributes: [{name: "rel", value: "stylesheet"}],
expected: [
{value: "styles.css", type: "cssresource"}
]
}, {
tagName: "link",
namespaceURI: "http://www.w3.org/1999/xhtml",
attributeName: "href",
attributeValue: "styles.css",
expected: [
{value: "styles.css", type: "uri"}
]
}, {
tagName: "output",
namespaceURI: "http://www.w3.org/1999/xhtml",
attributeName: "for",
attributeValue: "element-id something id",
expected: [
{value: "element-id", type: "idref"},
{value: " ", type: "string"},
{value: "something", type: "idref"},
{value: " ", type: "string"},
{value: "id", type: "idref"}
]
}, {
tagName: "img",
namespaceURI: "http://www.w3.org/1999/xhtml",
attributeName: "contextmenu",
attributeValue: "id-of-menu",
expected: [
{value: "id-of-menu", type: "idref"}
]
}, {
tagName: "img",
namespaceURI: "http://www.w3.org/1999/xhtml",
attributeName: "src",
attributeValue: "omg-thats-so-funny.gif",
expected: [
{value: "omg-thats-so-funny.gif", type: "uri"}
]
}, {
tagName: "key",
namespaceURI: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
attributeName: "command",
attributeValue: "some_command_id",
expected: [
{value: "some_command_id", type: "idref"}
]
}, {
tagName: "script",
namespaceURI: "whatever",
attributeName: "src",
attributeValue: "script.js",
expected: [
{value: "script.js", type: "jsresource"}
]
}];
function run_test() {
for (let {tagName, namespaceURI, attributeName,
otherAttributes, attributeValue, expected} of TEST_DATA) {
do_print("Testing <" + tagName + " " + attributeName + "='" + attributeValue + "'>");
let attributes = [
...otherAttributes || [],
{ name: attributeName, value: attributeValue }
];
let tokens = parseAttribute(namespaceURI, tagName, attributes, attributeName);
if (!expected) {
do_check_true(!tokens);
continue;
}
do_print("Checking that the number of parsed tokens is correct");
do_check_eq(tokens.length, expected.length);
for (let i = 0; i < tokens.length; i++) {
do_print("Checking the data in token " + i);
do_check_eq(tokens[i].value, expected[i].value);
do_check_eq(tokens[i].type, expected[i].type);
}
}
}

View file

@ -0,0 +1,117 @@
/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the BezierCanvas API in the CubicBezierWidget module
var Cu = Components.utils;
var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
var {CubicBezier, BezierCanvas} = require("devtools/client/shared/widgets/CubicBezierWidget");
function run_test() {
offsetsGetterReturnsData();
convertsOffsetsToCoordinates();
plotsCanvas();
}
function offsetsGetterReturnsData() {
do_print("offsets getter returns an array of 2 offset objects");
let b = new BezierCanvas(getCanvasMock(), getCubicBezier(), [.25, 0]);
let offsets = b.offsets;
do_check_eq(offsets.length, 2);
do_check_true("top" in offsets[0]);
do_check_true("left" in offsets[0]);
do_check_true("top" in offsets[1]);
do_check_true("left" in offsets[1]);
do_check_eq(offsets[0].top, "300px");
do_check_eq(offsets[0].left, "0px");
do_check_eq(offsets[1].top, "100px");
do_check_eq(offsets[1].left, "200px");
do_print("offsets getter returns data according to current padding");
b = new BezierCanvas(getCanvasMock(), getCubicBezier(), [0, 0]);
offsets = b.offsets;
do_check_eq(offsets[0].top, "400px");
do_check_eq(offsets[0].left, "0px");
do_check_eq(offsets[1].top, "0px");
do_check_eq(offsets[1].left, "200px");
}
function convertsOffsetsToCoordinates() {
do_print("Converts offsets to coordinates");
let b = new BezierCanvas(getCanvasMock(), getCubicBezier(), [.25, 0]);
let coordinates = b.offsetsToCoordinates({style: {
left: "0px",
top: "0px"
}});
do_check_eq(coordinates.length, 2);
do_check_eq(coordinates[0], 0);
do_check_eq(coordinates[1], 1.5);
coordinates = b.offsetsToCoordinates({style: {
left: "0px",
top: "300px"
}});
do_check_eq(coordinates[0], 0);
do_check_eq(coordinates[1], 0);
coordinates = b.offsetsToCoordinates({style: {
left: "200px",
top: "100px"
}});
do_check_eq(coordinates[0], 1);
do_check_eq(coordinates[1], 1);
}
function plotsCanvas() {
do_print("Plots the curve to the canvas");
let hasDrawnCurve = false;
let b = new BezierCanvas(getCanvasMock(), getCubicBezier(), [.25, 0]);
b.ctx.bezierCurveTo = () => {
hasDrawnCurve = true;
};
b.plot();
do_check_true(hasDrawnCurve);
}
function getCubicBezier() {
return new CubicBezier([0, 0, 1, 1]);
}
function getCanvasMock(w = 200, h = 400) {
return {
getContext: function () {
return {
scale: () => {},
translate: () => {},
clearRect: () => {},
beginPath: () => {},
closePath: () => {},
moveTo: () => {},
lineTo: () => {},
stroke: () => {},
arc: () => {},
fill: () => {},
bezierCurveTo: () => {},
save: () => {},
restore: () => {},
setTransform: () => {}
};
},
width: w,
height: h
};
}

View file

@ -0,0 +1,33 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test classifyAngle.
"use strict";
var Cu = Components.utils;
var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {angleUtils} = require("devtools/client/shared/css-angle");
const CLASSIFY_TESTS = [
{ input: "180deg", output: "deg" },
{ input: "-180deg", output: "deg" },
{ input: "180DEG", output: "deg" },
{ input: "200rad", output: "rad" },
{ input: "-200rad", output: "rad" },
{ input: "200RAD", output: "rad" },
{ input: "0.5grad", output: "grad" },
{ input: "-0.5grad", output: "grad" },
{ input: "0.5GRAD", output: "grad" },
{ input: "0.33turn", output: "turn" },
{ input: "0.33TURN", output: "turn" },
{ input: "-0.33turn", output: "turn" }
];
function run_test() {
for (let test of CLASSIFY_TESTS) {
let result = angleUtils.classifyAngle(test.input);
equal(result, test.output, "test classifyAngle(" + test.input + ")");
}
}

View file

@ -0,0 +1,76 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test classifyColor.
"use strict";
var Cu = Components.utils;
var Ci = Components.interfaces;
var Cc = Components.classes;
var {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {colorUtils} = require("devtools/shared/css/color");
loader.lazyGetter(this, "DOMUtils", function () {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});
const CLASSIFY_TESTS = [
{ input: "rgb(255,0,192)", output: "rgb" },
{ input: "RGB(255,0,192)", output: "rgb" },
{ input: "RGB(100%,0%,83%)", output: "rgb" },
{ input: "rgba(255,0,192, 0.25)", output: "rgb" },
{ input: "hsl(5, 5%, 5%)", output: "hsl" },
{ input: "hsla(5, 5%, 5%, 0.25)", output: "hsl" },
{ input: "hSlA(5, 5%, 5%, 0.25)", output: "hsl" },
{ input: "#f0c", output: "hex" },
{ input: "#f0c0", output: "hex" },
{ input: "#fe01cb", output: "hex" },
{ input: "#fe01cb80", output: "hex" },
{ input: "#FE01CB", output: "hex" },
{ input: "#FE01CB80", output: "hex" },
{ input: "blue", output: "name" },
{ input: "orange", output: "name" }
];
function compareWithDomutils(input, isColor) {
let ours = colorUtils.colorToRGBA(input);
let platform = DOMUtils.colorToRGBA(input);
deepEqual(ours, platform, "color " + input + " matches DOMUtils");
if (isColor) {
ok(ours !== null, "'" + input + "' is a color");
} else {
ok(ours === null, "'" + input + "' is not a color");
}
}
function run_test() {
for (let test of CLASSIFY_TESTS) {
let result = colorUtils.classifyColor(test.input);
equal(result, test.output, "test classifyColor(" + test.input + ")");
let obj = new colorUtils.CssColor("purple");
obj.setAuthoredUnitFromColor(test.input);
equal(obj.colorUnit, test.output,
"test setAuthoredUnitFromColor(" + test.input + ")");
// Check that our implementation matches DOMUtils.
compareWithDomutils(test.input, true);
// And check some obvious errors.
compareWithDomutils("mumble" + test.input, false);
compareWithDomutils(test.input + "trailingstuff", false);
}
// Regression test for bug 1303826.
let black = new colorUtils.CssColor("#000");
black.colorUnit = "name";
equal(black.toString(), "black", "test non-upper-case color cycling");
let upper = new colorUtils.CssColor("BLACK");
upper.colorUnit = "hex";
equal(upper.toString(), "#000", "test upper-case color cycling");
upper.colorUnit = "name";
equal(upper.toString(), "BLACK", "test upper-case color preservation");
}

View file

@ -0,0 +1,45 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Test color cycling regression - Bug 1303748.
*
* Values should cycle from a starting value, back to their original values. This can
* potentially be a little flaky due to the precision of different color representations.
*/
const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
const {colorUtils} = require("devtools/shared/css/color");
const getFixtureColorData = require("resource://test/helper_color_data.js");
function run_test() {
getFixtureColorData().forEach(({authored, name, hex, hsl, rgb, cycle}) => {
if (cycle) {
const nameCycled = runCycle(name, cycle);
const hexCycled = runCycle(hex, cycle);
const hslCycled = runCycle(hsl, cycle);
const rgbCycled = runCycle(rgb, cycle);
// Cut down on log output by only reporting a single pass/fail for the color.
ok(nameCycled && hexCycled && hslCycled && rgbCycled,
`${authored} was able to cycle back to the original value`);
}
});
}
/**
* Test a color cycle to see if a color cycles back to its original value in a fixed
* number of steps.
*
* @param {string} value - The color value, e.g. "#000".
* @param {integer) times - The number of times it takes to cycle back to the
* original color.
*/
function runCycle(value, times) {
let color = new colorUtils.CssColor(value);
for (let i = 0; i < times; i++) {
color.nextColorUnit();
color = new colorUtils.CssColor(color.toString());
}
return color.toString() === value;
}

View file

@ -0,0 +1,61 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test css-color-4 color function syntax and old-style syntax.
"use strict";
var Cu = Components.utils;
var Ci = Components.interfaces;
var Cc = Components.classes;
var {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {colorUtils} = require("devtools/shared/css/color");
loader.lazyGetter(this, "DOMUtils", function () {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});
const OLD_STYLE_TESTS = [
"rgb(255,0,192)",
"RGB(255,0,192)",
"RGB(100%,0%,83%)",
"rgba(255,0,192,0.25)",
"hsl(120, 100%, 40%)",
"hsla(120, 100%, 40%, 0.25)",
"hSlA(240, 100%, 50%, 0.25)",
];
const CSS_COLOR_4_TESTS = [
"rgb(255.0,0.0,192.0)",
"RGB(255 0 192)",
"RGB(100% 0% 83% / 0.5)",
"RGB(100%,0%,83%,0.5)",
"RGB(100%,0%,83%,50%)",
"rgba(255,0,192)",
"hsl(50deg,15%,25%)",
"hsl(240 25% 33%)",
"hsl(50deg 25% 33% / 0.25)",
"hsl(60 120% 60% / 0.25)",
"hSlA(5turn 40% 4%)",
];
function run_test() {
for (let test of OLD_STYLE_TESTS) {
let ours = colorUtils.colorToRGBA(test, true);
let platform = DOMUtils.colorToRGBA(test);
deepEqual(ours, platform, "color " + test + " matches DOMUtils");
ok(ours !== null, "'" + test + "' is a color");
}
for (let test of CSS_COLOR_4_TESTS) {
let oursOld = colorUtils.colorToRGBA(test, true);
let oursNew = colorUtils.colorToRGBA(test, false);
let platform = DOMUtils.colorToRGBA(test);
notEqual(oursOld, platform, "old style parser for color " + test +
" should not match DOMUtils");
ok(oursOld === null, "'" + test + "' is not a color with old parser");
deepEqual(oursNew, platform, `css-color-4 parser for color ${test} matches DOMUtils`);
ok(oursNew !== null, "'" + test + "' is a color with css-color-4 parser");
}
}

View file

@ -0,0 +1,63 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that css-color-db matches platform.
"use strict";
var Cu = Components.utils;
var Ci = Components.interfaces;
var Cc = Components.classes;
var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
const {colorUtils} = require("devtools/shared/css/color");
const {cssColors} = require("devtools/shared/css/color-db");
function isValid(colorName) {
ok(colorUtils.isValidCSSColor(colorName),
colorName + " is valid in database");
ok(DOMUtils.isValidCSSColor(colorName),
colorName + " is valid in DOMUtils");
}
function checkOne(colorName, checkName) {
let ours = colorUtils.colorToRGBA(colorName);
let fromDom = DOMUtils.colorToRGBA(colorName);
deepEqual(ours, fromDom, colorName + " agrees with DOMUtils");
isValid(colorName);
if (checkName) {
let {r, g, b} = ours;
// The color we got might not map back to the same name; but our
// implementation should agree with DOMUtils about which name is
// canonical.
let ourName = colorUtils.rgbToColorName(r, g, b);
let domName = DOMUtils.rgbToColorName(r, g, b);
equal(ourName, domName,
colorName + " canonical name agrees with DOMUtils");
}
}
function run_test() {
for (let name in cssColors) {
checkOne(name, true);
}
checkOne("transparent", false);
// Now check that platform didn't add a new name when we weren't
// looking.
let names = DOMUtils.getCSSValuesForProperty("background-color");
for (let name of names) {
if (name !== "hsl" && name !== "hsla" &&
name !== "rgb" && name !== "rgba" &&
name !== "inherit" && name !== "initial" && name !== "unset") {
checkOne(name, true);
}
}
}

View file

@ -0,0 +1,146 @@
/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the CubicBezier API in the CubicBezierWidget module
var Cu = Components.utils;
var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
var {CubicBezier, _parseTimingFunction} = require("devtools/client/shared/widgets/CubicBezierWidget");
function run_test() {
throwsWhenMissingCoordinates();
throwsWhenIncorrectCoordinates();
convertsStringCoordinates();
coordinatesToStringOutputsAString();
pointGettersReturnPointCoordinatesArrays();
toStringOutputsCubicBezierValue();
toStringOutputsCssPresetValues();
testParseTimingFunction();
}
function throwsWhenMissingCoordinates() {
do_check_throws(() => {
new CubicBezier();
}, "Throws an exception when coordinates are missing");
}
function throwsWhenIncorrectCoordinates() {
do_check_throws(() => {
new CubicBezier([]);
}, "Throws an exception when coordinates are incorrect (empty array)");
do_check_throws(() => {
new CubicBezier([0, 0]);
}, "Throws an exception when coordinates are incorrect (incomplete array)");
do_check_throws(() => {
new CubicBezier(["a", "b", "c", "d"]);
}, "Throws an exception when coordinates are incorrect (invalid type)");
do_check_throws(() => {
new CubicBezier([1.5, 0, 1.5, 0]);
}, "Throws an exception when coordinates are incorrect (time range invalid)");
do_check_throws(() => {
new CubicBezier([-0.5, 0, -0.5, 0]);
}, "Throws an exception when coordinates are incorrect (time range invalid)");
}
function convertsStringCoordinates() {
do_print("Converts string coordinates to numbers");
let c = new CubicBezier(["0", "1", ".5", "-2"]);
do_check_eq(c.coordinates[0], 0);
do_check_eq(c.coordinates[1], 1);
do_check_eq(c.coordinates[2], .5);
do_check_eq(c.coordinates[3], -2);
}
function coordinatesToStringOutputsAString() {
do_print("coordinates.toString() outputs a string representation");
let c = new CubicBezier(["0", "1", "0.5", "-2"]);
let string = c.coordinates.toString();
do_check_eq(string, "0,1,.5,-2");
c = new CubicBezier([1, 1, 1, 1]);
string = c.coordinates.toString();
do_check_eq(string, "1,1,1,1");
}
function pointGettersReturnPointCoordinatesArrays() {
do_print("Points getters return arrays of coordinates");
let c = new CubicBezier([0, .2, .5, 1]);
do_check_eq(c.P1[0], 0);
do_check_eq(c.P1[1], .2);
do_check_eq(c.P2[0], .5);
do_check_eq(c.P2[1], 1);
}
function toStringOutputsCubicBezierValue() {
do_print("toString() outputs the cubic-bezier() value");
let c = new CubicBezier([0, 1, 1, 0]);
do_check_eq(c.toString(), "cubic-bezier(0,1,1,0)");
}
function toStringOutputsCssPresetValues() {
do_print("toString() outputs the css predefined values");
let c = new CubicBezier([0, 0, 1, 1]);
do_check_eq(c.toString(), "linear");
c = new CubicBezier([0.25, 0.1, 0.25, 1]);
do_check_eq(c.toString(), "ease");
c = new CubicBezier([0.42, 0, 1, 1]);
do_check_eq(c.toString(), "ease-in");
c = new CubicBezier([0, 0, 0.58, 1]);
do_check_eq(c.toString(), "ease-out");
c = new CubicBezier([0.42, 0, 0.58, 1]);
do_check_eq(c.toString(), "ease-in-out");
}
function testParseTimingFunction() {
do_print("test parseTimingFunction");
for (let test of ["ease", "linear", "ease-in", "ease-out", "ease-in-out"]) {
ok(_parseTimingFunction(test), test);
}
ok(!_parseTimingFunction("something"), "non-function token");
ok(!_parseTimingFunction("something()"), "non-cubic-bezier function");
ok(!_parseTimingFunction("cubic-bezier(something)",
"cubic-bezier with non-numeric argument"));
ok(!_parseTimingFunction("cubic-bezier(1,2,3:7)",
"did not see comma"));
ok(!_parseTimingFunction("cubic-bezier(1,2,3,7:",
"did not see close paren"));
ok(!_parseTimingFunction("cubic-bezier(1,2", "early EOF after number"));
ok(!_parseTimingFunction("cubic-bezier(1,2,", "early EOF after comma"));
deepEqual(_parseTimingFunction("cubic-bezier(1,2,3,7)"), [1, 2, 3, 7],
"correct invocation");
deepEqual(_parseTimingFunction("cubic-bezier(1, /* */ 2,3, 7 )"),
[1, 2, 3, 7],
"correct with comments and whitespace");
}
function do_check_throws(cb, info) {
do_print(info);
let hasThrown = false;
try {
cb();
} catch (e) {
hasThrown = true;
}
do_check_true(hasThrown);
}

View file

@ -0,0 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {escapeCSSComment, _unescapeCSSComment} = require("devtools/shared/css/parsing-utils");
const TEST_DATA = [
{
input: "simple",
expected: "simple"
},
{
input: "/* comment */",
expected: "/\\* comment *\\/"
},
{
input: "/* two *//* comments */",
expected: "/\\* two *\\//\\* comments *\\/"
},
{
input: "/* nested /\\* comment *\\/ */",
expected: "/\\* nested /\\\\* comment *\\\\/ *\\/",
}
];
function run_test() {
let i = 0;
for (let test of TEST_DATA) {
++i;
do_print("Test #" + i);
let escaped = escapeCSSComment(test.input);
equal(escaped, test.expected);
let unescaped = _unescapeCSSComment(escaped);
equal(unescaped, test.input);
}
}

View file

@ -0,0 +1,439 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
const {parseDeclarations, _parseCommentDeclarations} = require("devtools/shared/css/parsing-utils");
const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
const TEST_DATA = [
// Simple test
{
input: "p:v;",
expected: [{name: "p", value: "v", priority: "", offsets: [0, 4]}]
},
// Simple test
{
input: "this:is;a:test;",
expected: [
{name: "this", value: "is", priority: "", offsets: [0, 8]},
{name: "a", value: "test", priority: "", offsets: [8, 15]}
]
},
// Test a single declaration with semi-colon
{
input: "name:value;",
expected: [{name: "name", value: "value", priority: "", offsets: [0, 11]}]
},
// Test a single declaration without semi-colon
{
input: "name:value",
expected: [{name: "name", value: "value", priority: "", offsets: [0, 10]}]
},
// Test multiple declarations separated by whitespaces and carriage
// returns and tabs
{
input: "p1 : v1 ; \t\t \n p2:v2; \n\n\n\n\t p3 : v3;",
expected: [
{name: "p1", value: "v1", priority: "", offsets: [0, 9]},
{name: "p2", value: "v2", priority: "", offsets: [16, 22]},
{name: "p3", value: "v3", priority: "", offsets: [32, 45]},
]
},
// Test simple priority
{
input: "p1: v1; p2: v2 !important;",
expected: [
{name: "p1", value: "v1", priority: "", offsets: [0, 7]},
{name: "p2", value: "v2", priority: "important", offsets: [8, 26]}
]
},
// Test simple priority
{
input: "p1: v1 !important; p2: v2",
expected: [
{name: "p1", value: "v1", priority: "important", offsets: [0, 18]},
{name: "p2", value: "v2", priority: "", offsets: [19, 25]}
]
},
// Test simple priority
{
input: "p1: v1 ! important; p2: v2 ! important;",
expected: [
{name: "p1", value: "v1", priority: "important", offsets: [0, 20]},
{name: "p2", value: "v2", priority: "important", offsets: [21, 40]}
]
},
// Test invalid priority
{
input: "p1: v1 important;",
expected: [
{name: "p1", value: "v1 important", priority: "", offsets: [0, 17]}
]
},
// Test various types of background-image urls
{
input: "background-image: url(../../relative/image.png)",
expected: [{
name: "background-image",
value: "url(../../relative/image.png)",
priority: "",
offsets: [0, 47]
}]
},
{
input: "background-image: url(http://site.com/test.png)",
expected: [{
name: "background-image",
value: "url(http://site.com/test.png)",
priority: "",
offsets: [0, 47]
}]
},
{
input: "background-image: url(wow.gif)",
expected: [{
name: "background-image",
value: "url(wow.gif)",
priority: "",
offsets: [0, 30]
}]
},
// Test that urls with :;{} characters in them are parsed correctly
{
input: "background: red url(\"http://site.com/image{}:;.png?id=4#wat\") "
+ "repeat top right",
expected: [{
name: "background",
value: "red url(\"http://site.com/image{}:;.png?id=4#wat\") " +
"repeat top right",
priority: "",
offsets: [0, 78]
}]
},
// Test that an empty string results in an empty array
{input: "", expected: []},
// Test that a string comprised only of whitespaces results in an empty array
{input: " \n \n \n \n \t \t\t\t ", expected: []},
// Test that a null input throws an exception
{input: null, throws: true},
// Test that a undefined input throws an exception
{input: undefined, throws: true},
// Test that :;{} characters in quoted content are not parsed as multiple
// declarations
{
input: "content: \";color:red;}selector{color:yellow;\"",
expected: [{
name: "content",
value: "\";color:red;}selector{color:yellow;\"",
priority: "",
offsets: [0, 45]
}]
},
// Test that rules aren't parsed, just declarations. So { and } found after a
// property name should be part of the property name, same for values.
{
input: "body {color:red;} p {color: blue;}",
expected: [
{name: "body {color", value: "red", priority: "", offsets: [0, 16]},
{name: "} p {color", value: "blue", priority: "", offsets: [16, 33]},
{name: "}", value: "", priority: "", offsets: [33, 34]}
]
},
// Test unbalanced : and ;
{
input: "color :red : font : arial;",
expected: [
{name: "color", value: "red : font : arial", priority: "",
offsets: [0, 26]}
]
},
{
input: "background: red;;;;;",
expected: [{name: "background", value: "red", priority: "",
offsets: [0, 16]}]
},
{
input: "background:;",
expected: [{name: "background", value: "", priority: "",
offsets: [0, 12]}]
},
{input: ";;;;;", expected: []},
{input: ":;:;", expected: []},
// Test name only
{input: "color", expected: [
{name: "color", value: "", priority: "", offsets: [0, 5]}
]},
// Test trailing name without :
{input: "color:blue;font", expected: [
{name: "color", value: "blue", priority: "", offsets: [0, 11]},
{name: "font", value: "", priority: "", offsets: [11, 15]}
]},
// Test trailing name with :
{input: "color:blue;font:", expected: [
{name: "color", value: "blue", priority: "", offsets: [0, 11]},
{name: "font", value: "", priority: "", offsets: [11, 16]}
]},
// Test leading value
{input: "Arial;color:blue;", expected: [
{name: "", value: "Arial", priority: "", offsets: [0, 6]},
{name: "color", value: "blue", priority: "", offsets: [6, 17]}
]},
// Test hex colors
{
input: "color: #333",
expected: [{name: "color", value: "#333", priority: "", offsets: [0, 11]}]
},
{
input: "color: #456789",
expected: [{name: "color", value: "#456789", priority: "",
offsets: [0, 14]}]
},
{
input: "wat: #XYZ",
expected: [{name: "wat", value: "#XYZ", priority: "", offsets: [0, 9]}]
},
// Test string/url quotes escaping
{
input: "content: \"this is a 'string'\"",
expected: [{name: "content", value: "\"this is a 'string'\"", priority: "",
offsets: [0, 29]}]
},
{
input: 'content: "this is a \\"string\\""',
expected: [{
name: "content",
value: '"this is a \\"string\\""',
priority: "",
offsets: [0, 31]}]
},
{
input: "content: 'this is a \"string\"'",
expected: [{
name: "content",
value: '\'this is a "string"\'',
priority: "",
offsets: [0, 29]
}]
},
{
input: "content: 'this is a \\'string\\''",
expected: [{
name: "content",
value: "'this is a \\'string\\''",
priority: "",
offsets: [0, 31],
}]
},
{
input: "content: 'this \\' is a \" really strange string'",
expected: [{
name: "content",
value: "'this \\' is a \" really strange string'",
priority: "",
offsets: [0, 47]
}]
},
{
input: "content: \"a not s\\ o very long title\"",
expected: [{
name: "content",
value: '"a not s\\ o very long title"',
priority: "",
offsets: [0, 46]
}]
},
// Test calc with nested parentheses
{
input: "width: calc((100% - 3em) / 2)",
expected: [{name: "width", value: "calc((100% - 3em) / 2)", priority: "",
offsets: [0, 29]}]
},
// Simple embedded comment test.
{
parseComments: true,
input: "width: 5; /* background: green; */ background: red;",
expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
{name: "background", value: "green", priority: "",
offsets: [13, 31], commentOffsets: [10, 34]},
{name: "background", value: "red", priority: "",
offsets: [35, 51]}]
},
// Embedded comment where the parsing heuristic fails.
{
parseComments: true,
input: "width: 5; /* background something: green; */ background: red;",
expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
{name: "background", value: "red", priority: "",
offsets: [45, 61]}]
},
// Embedded comment where the parsing heuristic is a bit funny.
{
parseComments: true,
input: "width: 5; /* background: */ background: red;",
expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
{name: "background", value: "", priority: "",
offsets: [13, 24], commentOffsets: [10, 27]},
{name: "background", value: "red", priority: "",
offsets: [28, 44]}]
},
// Another case where the parsing heuristic says not to bother; note
// that there is no ";" in the comment.
{
parseComments: true,
input: "width: 5; /* background: yellow */ background: red;",
expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
{name: "background", value: "yellow", priority: "",
offsets: [13, 31], commentOffsets: [10, 34]},
{name: "background", value: "red", priority: "",
offsets: [35, 51]}]
},
// Parsing a comment should yield text that has been unescaped, and
// the offsets should refer to the original text.
{
parseComments: true,
input: "/* content: '*\\/'; */",
expected: [{name: "content", value: "'*/'", priority: "",
offsets: [3, 18], commentOffsets: [0, 21]}]
},
// Parsing a comment should yield text that has been unescaped, and
// the offsets should refer to the original text. This variant
// tests the no-semicolon path.
{
parseComments: true,
input: "/* content: '*\\/' */",
expected: [{name: "content", value: "'*/'", priority: "",
offsets: [3, 17], commentOffsets: [0, 20]}]
},
// A comment-in-a-comment should yield the correct offsets.
{
parseComments: true,
input: "/* color: /\\* comment *\\/ red; */",
expected: [{name: "color", value: "red", priority: "",
offsets: [3, 30], commentOffsets: [0, 33]}]
},
// HTML comments are ignored.
{
parseComments: true,
input: "<!-- color: red; --> color: blue;",
expected: [{name: "color", value: "red", priority: "",
offsets: [5, 16]},
{name: "color", value: "blue", priority: "",
offsets: [21, 33]}]
},
// Don't error on an empty comment.
{
parseComments: true,
input: "/**/",
expected: []
},
// Parsing our special comments skips the name-check heuristic.
{
parseComments: true,
input: "/*! walrus: zebra; */",
expected: [{name: "walrus", value: "zebra", priority: "",
offsets: [4, 18], commentOffsets: [0, 21]}]
},
// Regression test for bug 1287620.
{
input: "color: blue \\9 no\\_need",
expected: [{name: "color", value: "blue \\9 no_need", priority: "", offsets: [0, 23]}]
},
// Regression test for bug 1297890 - don't paste tokens.
{
parseComments: true,
input: "stroke-dasharray: 1/*ThisIsAComment*/2;",
expected: [{name: "stroke-dasharray", value: "1 2", priority: "", offsets: [0, 39]}]
},
];
function run_test() {
run_basic_tests();
run_comment_tests();
}
// Test parseDeclarations.
function run_basic_tests() {
for (let test of TEST_DATA) {
do_print("Test input string " + test.input);
let output;
try {
output = parseDeclarations(isCssPropertyKnown, test.input,
test.parseComments);
} catch (e) {
do_print("parseDeclarations threw an exception with the given input " +
"string");
if (test.throws) {
do_print("Exception expected");
do_check_true(true);
} else {
do_print("Exception unexpected\n" + e);
do_check_true(false);
}
}
if (output) {
assertOutput(output, test.expected);
}
}
}
const COMMENT_DATA = [
{
input: "content: 'hi",
expected: [{name: "content", value: "'hi", priority: "", terminator: "';",
offsets: [2, 14], colonOffsets: [9, 11],
commentOffsets: [0, 16]}],
},
{
input: "text that once confounded the parser;",
expected: []
},
];
// Test parseCommentDeclarations.
function run_comment_tests() {
for (let test of COMMENT_DATA) {
do_print("Test input string " + test.input);
let output = _parseCommentDeclarations(isCssPropertyKnown, test.input, 0,
test.input.length + 4);
deepEqual(output, test.expected);
}
}
function assertOutput(actual, expected) {
if (actual.length === expected.length) {
for (let i = 0; i < expected.length; i++) {
do_check_true(!!actual[i]);
do_print("Check that the output item has the expected name, " +
"value and priority");
do_check_eq(expected[i].name, actual[i].name);
do_check_eq(expected[i].value, actual[i].value);
do_check_eq(expected[i].priority, actual[i].priority);
deepEqual(expected[i].offsets, actual[i].offsets);
if ("commentOffsets" in expected[i]) {
deepEqual(expected[i].commentOffsets, actual[i].commentOffsets);
}
}
} else {
for (let prop of actual) {
do_print("Actual output contained: {name: " + prop.name + ", value: " +
prop.value + ", priority: " + prop.priority + "}");
}
do_check_eq(actual.length, expected.length);
}
}

View file

@ -0,0 +1,213 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {
parsePseudoClassesAndAttributes,
SELECTOR_ATTRIBUTE,
SELECTOR_ELEMENT,
SELECTOR_PSEUDO_CLASS
} = require("devtools/shared/css/parsing-utils");
const TEST_DATA = [
// Test that a null input throws an exception
{
input: null,
throws: true
},
// Test that a undefined input throws an exception
{
input: undefined,
throws: true
},
{
input: ":root",
expected: [
{ value: ":root", type: SELECTOR_PSEUDO_CLASS }
]
},
{
input: ".testclass",
expected: [
{ value: ".testclass", type: SELECTOR_ELEMENT }
]
},
{
input: "div p",
expected: [
{ value: "div p", type: SELECTOR_ELEMENT }
]
},
{
input: "div > p",
expected: [
{ value: "div > p", type: SELECTOR_ELEMENT }
]
},
{
input: "a[hidden]",
expected: [
{ value: "a", type: SELECTOR_ELEMENT },
{ value: "[hidden]", type: SELECTOR_ATTRIBUTE }
]
},
{
input: "a[hidden=true]",
expected: [
{ value: "a", type: SELECTOR_ELEMENT },
{ value: "[hidden=true]", type: SELECTOR_ATTRIBUTE }
]
},
{
input: "a[hidden=true] p:hover",
expected: [
{ value: "a", type: SELECTOR_ELEMENT },
{ value: "[hidden=true]", type: SELECTOR_ATTRIBUTE },
{ value: " p", type: SELECTOR_ELEMENT },
{ value: ":hover", type: SELECTOR_PSEUDO_CLASS }
]
},
{
input: "a[checked=\"true\"]",
expected: [
{ value: "a", type: SELECTOR_ELEMENT },
{ value: "[checked=\"true\"]", type: SELECTOR_ATTRIBUTE }
]
},
{
input: "a[title~=test]",
expected: [
{ value: "a", type: SELECTOR_ELEMENT },
{ value: "[title~=test]", type: SELECTOR_ATTRIBUTE }
]
},
{
input: "h1[hidden=\"true\"][title^=\"Important\"]",
expected: [
{ value: "h1", type: SELECTOR_ELEMENT },
{ value: "[hidden=\"true\"]", type: SELECTOR_ATTRIBUTE },
{ value: "[title^=\"Important\"]", type: SELECTOR_ATTRIBUTE}
]
},
{
input: "p:hover",
expected: [
{ value: "p", type: SELECTOR_ELEMENT },
{ value: ":hover", type: SELECTOR_PSEUDO_CLASS }
]
},
{
input: "p + .testclass:hover",
expected: [
{ value: "p + .testclass", type: SELECTOR_ELEMENT },
{ value: ":hover", type: SELECTOR_PSEUDO_CLASS }
]
},
{
input: "p::before",
expected: [
{ value: "p", type: SELECTOR_ELEMENT },
{ value: "::before", type: SELECTOR_PSEUDO_CLASS }
]
},
{
input: "p:nth-child(2)",
expected: [
{ value: "p", type: SELECTOR_ELEMENT },
{ value: ":nth-child(2)", type: SELECTOR_PSEUDO_CLASS }
]
},
{
input: "p:not([title=\"test\"]) .testclass",
expected: [
{ value: "p", type: SELECTOR_ELEMENT },
{ value: ":not([title=\"test\"])", type: SELECTOR_PSEUDO_CLASS },
{ value: " .testclass", type: SELECTOR_ELEMENT }
]
},
{
input: "a\\:hover",
expected: [
{ value: "a\\:hover", type: SELECTOR_ELEMENT }
]
},
{
input: ":not(:lang(it))",
expected: [
{ value: ":not(:lang(it))", type: SELECTOR_PSEUDO_CLASS }
]
},
{
input: "p:not(:lang(it))",
expected: [
{ value: "p", type: SELECTOR_ELEMENT },
{ value: ":not(:lang(it))", type: SELECTOR_PSEUDO_CLASS }
]
},
{
input: "p:not(p:lang(it))",
expected: [
{ value: "p", type: SELECTOR_ELEMENT },
{ value: ":not(p:lang(it))", type: SELECTOR_PSEUDO_CLASS }
]
},
{
input: ":not(:lang(it)",
expected: [
{ value: ":not(:lang(it)", type: SELECTOR_ELEMENT }
]
},
{
input: ":not(:lang(it)))",
expected: [
{ value: ":not(:lang(it))", type: SELECTOR_PSEUDO_CLASS },
{ value: ")", type: SELECTOR_ELEMENT }
]
}
];
function run_test() {
for (let test of TEST_DATA) {
dump("Test input string " + test.input + "\n");
let output;
try {
output = parsePseudoClassesAndAttributes(test.input);
} catch (e) {
dump("parsePseudoClassesAndAttributes threw an exception with the " +
"given input string\n");
if (test.throws) {
ok(true, "Exception expected");
} else {
dump();
ok(false, "Exception unexpected\n" + e);
}
}
if (output) {
assertOutput(output, test.expected);
}
}
}
function assertOutput(actual, expected) {
if (actual.length === expected.length) {
for (let i = 0; i < expected.length; i++) {
dump("Check that the output item has the expected value and type\n");
ok(!!actual[i]);
equal(expected[i].value, actual[i].value);
equal(expected[i].type, actual[i].type);
}
} else {
for (let prop of actual) {
dump("Actual output contained: {value: " + prop.value + ", type: " +
prop.type + "}\n");
}
equal(actual.length, expected.length);
}
}

View file

@ -0,0 +1,93 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {parseSingleValue} = require("devtools/shared/css/parsing-utils");
const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
const TEST_DATA = [
{input: null, throws: true},
{input: undefined, throws: true},
{input: "", expected: {value: "", priority: ""}},
{input: " \t \t \n\n ", expected: {value: "", priority: ""}},
{input: "blue", expected: {value: "blue", priority: ""}},
{input: "blue !important", expected: {value: "blue", priority: "important"}},
{input: "blue!important", expected: {value: "blue", priority: "important"}},
{input: "blue ! important", expected: {value: "blue", priority: "important"}},
{
input: "blue ! important",
expected: {value: "blue", priority: "important"}
},
{input: "blue !", expected: {value: "blue", priority: ""}},
{input: "blue !mportant", expected: {value: "blue !mportant", priority: ""}},
{
input: " blue !important ",
expected: {value: "blue", priority: "important"}
},
{
input: "url(\"http://url.com/whyWouldYouDoThat!important.png\") !important",
expected: {
value: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
priority: "important"
}
},
{
input: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
expected: {
value: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
priority: ""
}
},
{
input: "\"content!important\" !important",
expected: {
value: "\"content!important\"",
priority: "important"
}
},
{
input: "\"content!important\"",
expected: {
value: "\"content!important\"",
priority: ""
}
},
{
input: "\"all the \\\"'\\\\ special characters\"",
expected: {
value: "\"all the \\\"'\\\\ special characters\"",
priority: ""
}
}
];
function run_test() {
for (let test of TEST_DATA) {
do_print("Test input value " + test.input);
try {
let output = parseSingleValue(isCssPropertyKnown, test.input);
assertOutput(output, test.expected);
} catch (e) {
do_print("parseSingleValue threw an exception with the given input " +
"value");
if (test.throws) {
do_print("Exception expected");
do_check_true(true);
} else {
do_print("Exception unexpected\n" + e);
do_check_true(false);
}
}
}
}
function assertOutput(actual, expected) {
do_print("Check that the output has the expected value and priority");
do_check_eq(expected.value, actual.value);
do_check_eq(expected.priority, actual.priority);
}

View file

@ -0,0 +1,529 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {RuleRewriter} = require("devtools/shared/css/parsing-utils");
const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
const TEST_DATA = [
{
desc: "simple set",
input: "p:v;",
instruction: {type: "set", name: "p", value: "N", priority: "",
index: 0},
expected: "p:N;"
},
{
desc: "simple set clearing !important",
input: "p:v !important;",
instruction: {type: "set", name: "p", value: "N", priority: "",
index: 0},
expected: "p:N;"
},
{
desc: "simple set adding !important",
input: "p:v;",
instruction: {type: "set", name: "p", value: "N", priority: "important",
index: 0},
expected: "p:N !important;"
},
{
desc: "simple set between comments",
input: "/*color:red;*/ p:v; /*color:green;*/",
instruction: {type: "set", name: "p", value: "N", priority: "",
index: 1},
expected: "/*color:red;*/ p:N; /*color:green;*/"
},
// The rule view can generate a "set" with a previously unknown
// property index; which should work like "create".
{
desc: "set at unknown index",
input: "a:b; e: f;",
instruction: {type: "set", name: "c", value: "d", priority: "",
index: 2},
expected: "a:b; e: f;c: d;"
},
{
desc: "simple rename",
input: "p:v;",
instruction: {type: "rename", name: "p", newName: "q", index: 0},
expected: "q:v;"
},
// "rename" is passed the name that the user entered, and must do
// any escaping necessary to ensure that this is an identifier.
{
desc: "rename requiring escape",
input: "p:v;",
instruction: {type: "rename", name: "p", newName: "a b", index: 0},
expected: "a\\ b:v;"
},
{
desc: "simple create",
input: "",
instruction: {type: "create", name: "p", value: "v", priority: "important",
index: 0, enabled: true},
expected: "p: v !important;"
},
{
desc: "create between two properties",
input: "a:b; e: f;",
instruction: {type: "create", name: "c", value: "d", priority: "",
index: 1, enabled: true},
expected: "a:b; c: d;e: f;"
},
// "create" is passed the name that the user entered, and must do
// any escaping necessary to ensure that this is an identifier.
{
desc: "create requiring escape",
input: "",
instruction: {type: "create", name: "a b", value: "d", priority: "",
index: 1, enabled: true},
expected: "a\\ b: d;"
},
{
desc: "simple disable",
input: "p:v;",
instruction: {type: "enable", name: "p", value: false, index: 0},
expected: "/*! p:v; */"
},
{
desc: "simple enable",
input: "/* color:v; */",
instruction: {type: "enable", name: "color", value: true, index: 0},
expected: "color:v;"
},
{
desc: "enable with following property in comment",
input: "/* color:red; color: blue; */",
instruction: {type: "enable", name: "color", value: true, index: 0},
expected: "color:red; /* color: blue; */"
},
{
desc: "enable with preceding property in comment",
input: "/* color:red; color: blue; */",
instruction: {type: "enable", name: "color", value: true, index: 1},
expected: "/* color:red; */ color: blue;"
},
{
desc: "simple remove",
input: "a:b;c:d;e:f;",
instruction: {type: "remove", name: "c", index: 1},
expected: "a:b;e:f;"
},
{
desc: "disable with comment ender in string",
input: "content: '*/';",
instruction: {type: "enable", name: "content", value: false, index: 0},
expected: "/*! content: '*\\/'; */"
},
{
desc: "enable with comment ender in string",
input: "/* content: '*\\/'; */",
instruction: {type: "enable", name: "content", value: true, index: 0},
expected: "content: '*/';"
},
{
desc: "enable requiring semicolon insertion",
// Note the lack of a trailing semicolon in the comment.
input: "/* color:red */ color: blue;",
instruction: {type: "enable", name: "color", value: true, index: 0},
expected: "color:red; color: blue;"
},
{
desc: "create requiring semicolon insertion",
// Note the lack of a trailing semicolon.
input: "color: red",
instruction: {type: "create", name: "a", value: "b", priority: "",
index: 1, enabled: true},
expected: "color: red;a: b;"
},
// Newline insertion.
{
desc: "simple newline insertion",
input: "\ncolor: red;\n",
instruction: {type: "create", name: "a", value: "b", priority: "",
index: 1, enabled: true},
expected: "\ncolor: red;\na: b;\n"
},
// Newline insertion.
{
desc: "semicolon insertion before newline",
// Note the lack of a trailing semicolon.
input: "\ncolor: red\n",
instruction: {type: "create", name: "a", value: "b", priority: "",
index: 1, enabled: true},
expected: "\ncolor: red;\na: b;\n"
},
// Newline insertion.
{
desc: "newline and semicolon insertion",
// Note the lack of a trailing semicolon and newline.
input: "\ncolor: red",
instruction: {type: "create", name: "a", value: "b", priority: "",
index: 1, enabled: true},
expected: "\ncolor: red;\na: b;\n"
},
// Newline insertion and indentation.
{
desc: "indentation with create",
input: "\n color: red;\n",
instruction: {type: "create", name: "a", value: "b", priority: "",
index: 1, enabled: true},
expected: "\n color: red;\n a: b;\n"
},
// Newline insertion and indentation.
{
desc: "indentation plus semicolon insertion before newline",
// Note the lack of a trailing semicolon.
input: "\n color: red\n",
instruction: {type: "create", name: "a", value: "b", priority: "",
index: 1, enabled: true},
expected: "\n color: red;\n a: b;\n"
},
{
desc: "indentation inserted before trailing whitespace",
// Note the trailing whitespace. This could come from a rule
// like:
// @supports (mumble) {
// body {
// color: red;
// }
// }
// Here if we create a rule we don't want it to follow
// the indentation of the "}".
input: "\n color: red;\n ",
instruction: {type: "create", name: "a", value: "b", priority: "",
index: 1, enabled: true},
expected: "\n color: red;\n a: b;\n "
},
// Newline insertion and indentation.
{
desc: "indentation comes from preceding comment",
// Note how the comment comes before the declaration.
input: "\n /* comment */ color: red\n",
instruction: {type: "create", name: "a", value: "b", priority: "",
index: 1, enabled: true},
expected: "\n /* comment */ color: red;\n a: b;\n"
},
// Default indentation.
{
desc: "use of default indentation",
input: "\n",
instruction: {type: "create", name: "a", value: "b", priority: "",
index: 0, enabled: true},
expected: "\n\ta: b;\n"
},
// Deletion handles newlines properly.
{
desc: "deletion removes newline",
input: "a:b;\nc:d;\ne:f;",
instruction: {type: "remove", name: "c", index: 1},
expected: "a:b;\ne:f;"
},
// Deletion handles newlines properly.
{
desc: "deletion remove blank line",
input: "\n a:b;\n c:d; \ne:f;",
instruction: {type: "remove", name: "c", index: 1},
expected: "\n a:b;\ne:f;"
},
// Deletion handles newlines properly.
{
desc: "deletion leaves comment",
input: "\n a:b;\n /* something */ c:d; \ne:f;",
instruction: {type: "remove", name: "c", index: 1},
expected: "\n a:b;\n /* something */ \ne:f;"
},
// Deletion handles newlines properly.
{
desc: "deletion leaves previous newline",
input: "\n a:b;\n c:d; \ne:f;",
instruction: {type: "remove", name: "e", index: 2},
expected: "\n a:b;\n c:d; \n"
},
// Deletion handles newlines properly.
{
desc: "deletion removes trailing whitespace",
input: "\n a:b;\n c:d; \n e:f;",
instruction: {type: "remove", name: "e", index: 2},
expected: "\n a:b;\n c:d; \n"
},
// Deletion handles newlines properly.
{
desc: "deletion preserves indentation",
input: " a:b;\n c:d; \n e:f;",
instruction: {type: "remove", name: "a", index: 0},
expected: " c:d; \n e:f;"
},
// Termination insertion corner case.
{
desc: "enable single quote termination",
input: "/* content: 'hi */ color: red;",
instruction: {type: "enable", name: "content", value: true, index: 0},
expected: "content: 'hi'; color: red;",
changed: {0: "'hi'"}
},
// Termination insertion corner case.
{
desc: "create single quote termination",
input: "content: 'hi",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1, enabled: true},
expected: "content: 'hi';color: red;",
changed: {0: "'hi'"}
},
// Termination insertion corner case.
{
desc: "enable double quote termination",
input: "/* content: \"hi */ color: red;",
instruction: {type: "enable", name: "content", value: true, index: 0},
expected: "content: \"hi\"; color: red;",
changed: {0: "\"hi\""}
},
// Termination insertion corner case.
{
desc: "create double quote termination",
input: "content: \"hi",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1, enabled: true},
expected: "content: \"hi\";color: red;",
changed: {0: "\"hi\""}
},
// Termination insertion corner case.
{
desc: "enable url termination",
input: "/* background-image: url(something.jpg */ color: red;",
instruction: {type: "enable", name: "background-image", value: true,
index: 0},
expected: "background-image: url(something.jpg); color: red;",
changed: {0: "url(something.jpg)"}
},
// Termination insertion corner case.
{
desc: "create url termination",
input: "background-image: url(something.jpg",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1, enabled: true},
expected: "background-image: url(something.jpg);color: red;",
changed: {0: "url(something.jpg)"}
},
// Termination insertion corner case.
{
desc: "enable url single quote termination",
input: "/* background-image: url('something.jpg */ color: red;",
instruction: {type: "enable", name: "background-image", value: true,
index: 0},
expected: "background-image: url('something.jpg'); color: red;",
changed: {0: "url('something.jpg')"}
},
// Termination insertion corner case.
{
desc: "create url single quote termination",
input: "background-image: url('something.jpg",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1, enabled: true},
expected: "background-image: url('something.jpg');color: red;",
changed: {0: "url('something.jpg')"}
},
// Termination insertion corner case.
{
desc: "create url double quote termination",
input: "/* background-image: url(\"something.jpg */ color: red;",
instruction: {type: "enable", name: "background-image", value: true,
index: 0},
expected: "background-image: url(\"something.jpg\"); color: red;",
changed: {0: "url(\"something.jpg\")"}
},
// Termination insertion corner case.
{
desc: "enable url double quote termination",
input: "background-image: url(\"something.jpg",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1, enabled: true},
expected: "background-image: url(\"something.jpg\");color: red;",
changed: {0: "url(\"something.jpg\")"}
},
// Termination insertion corner case.
{
desc: "create backslash termination",
input: "something: \\",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1, enabled: true},
expected: "something: \\\\;color: red;",
// The lexer rewrites the token before we see it. However this is
// so obscure as to be inconsequential.
changed: {0: "\uFFFD\\"}
},
// Termination insertion corner case.
{
desc: "enable backslash single quote termination",
input: "something: '\\",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1, enabled: true},
expected: "something: '\\\\';color: red;",
changed: {0: "'\\\\'"}
},
{
desc: "enable backslash double quote termination",
input: "something: \"\\",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1, enabled: true},
expected: "something: \"\\\\\";color: red;",
changed: {0: "\"\\\\\""}
},
// Termination insertion corner case.
{
desc: "enable comment termination",
input: "something: blah /* comment ",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1, enabled: true},
expected: "something: blah /* comment*/; color: red;"
},
// Rewrite a "heuristic override" comment.
{
desc: "enable with heuristic override comment",
input: "/*! walrus: zebra; */",
instruction: {type: "enable", name: "walrus", value: true, index: 0},
expected: "walrus: zebra;"
},
// Sanitize a bad value.
{
desc: "create sanitize unpaired brace",
input: "",
instruction: {type: "create", name: "p", value: "}", priority: "",
index: 0, enabled: true},
expected: "p: \\};",
changed: {0: "\\}"}
},
// Sanitize a bad value.
{
desc: "set sanitize unpaired brace",
input: "walrus: zebra;",
instruction: {type: "set", name: "walrus", value: "{{}}}", priority: "",
index: 0},
expected: "walrus: {{}}\\};",
changed: {0: "{{}}\\}"}
},
// Sanitize a bad value.
{
desc: "enable sanitize unpaired brace",
input: "/*! walrus: }*/",
instruction: {type: "enable", name: "walrus", value: true, index: 0},
expected: "walrus: \\};",
changed: {0: "\\}"}
},
// Creating a new declaration does not require an attempt to
// terminate a previous commented declaration.
{
desc: "disabled declaration does not need semicolon insertion",
input: "/*! no: semicolon */\n",
instruction: {type: "create", name: "walrus", value: "zebra", priority: "",
index: 1, enabled: true},
expected: "/*! no: semicolon */\nwalrus: zebra;\n",
changed: {}
},
{
desc: "create commented-out property",
input: "p: v",
instruction: {type: "create", name: "shoveler", value: "duck", priority: "",
index: 1, enabled: false},
expected: "p: v;/*! shoveler: duck; */",
},
{
desc: "disabled create with comment ender in string",
input: "",
instruction: {type: "create", name: "content", value: "'*/'", priority: "",
index: 0, enabled: false},
expected: "/*! content: '*\\/'; */"
},
{
desc: "delete disabled property",
input: "\n a:b;\n /* color:#f0c; */\n e:f;",
instruction: {type: "remove", name: "color", index: 1},
expected: "\n a:b;\n e:f;",
},
{
desc: "delete heuristic-disabled property",
input: "\n a:b;\n /*! c:d; */\n e:f;",
instruction: {type: "remove", name: "c", index: 1},
expected: "\n a:b;\n e:f;",
},
{
desc: "delete disabled property leaving other disabled property",
input: "\n a:b;\n /* color:#f0c; background-color: seagreen; */\n e:f;",
instruction: {type: "remove", name: "color", index: 1},
expected: "\n a:b;\n /* background-color: seagreen; */\n e:f;",
},
];
function rewriteDeclarations(inputString, instruction, defaultIndentation) {
let rewriter = new RuleRewriter(isCssPropertyKnown, null, inputString);
rewriter.defaultIndentation = defaultIndentation;
switch (instruction.type) {
case "rename":
rewriter.renameProperty(instruction.index, instruction.name,
instruction.newName);
break;
case "enable":
rewriter.setPropertyEnabled(instruction.index, instruction.name,
instruction.value);
break;
case "create":
rewriter.createProperty(instruction.index, instruction.name,
instruction.value, instruction.priority,
instruction.enabled);
break;
case "set":
rewriter.setProperty(instruction.index, instruction.name,
instruction.value, instruction.priority);
break;
case "remove":
rewriter.removeProperty(instruction.index, instruction.name);
break;
default:
throw new Error("unrecognized instruction");
}
return rewriter.getResult();
}
function run_test() {
for (let test of TEST_DATA) {
let {changed, text} = rewriteDeclarations(test.input, test.instruction,
"\t");
equal(text, test.expected, "output for " + test.desc);
let expectChanged;
if ("changed" in test) {
expectChanged = test.changed;
} else {
expectChanged = {};
}
deepEqual(changed, expectChanged, "changed result for " + test.desc);
}
}

View file

@ -0,0 +1,181 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests utility functions contained in `source-utils.js`
*/
const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
const sourceUtils = require("devtools/client/shared/source-utils");
function run_test() {
run_next_test();
}
const CHROME_URLS = [
"chrome://foo", "resource://baz", "jar:file:///Users/root"
];
const CONTENT_URLS = [
"http://mozilla.org", "https://mozilla.org", "file:///Users/root", "app://fxosapp"
];
// Test `sourceUtils.parseURL`
add_task(function* () {
let parsed = sourceUtils.parseURL("https://foo.com:8888/boo/bar.js?q=query");
equal(parsed.fileName, "bar.js", "parseURL parsed valid fileName");
equal(parsed.host, "foo.com:8888", "parseURL parsed valid host");
equal(parsed.hostname, "foo.com", "parseURL parsed valid hostname");
equal(parsed.port, "8888", "parseURL parsed valid port");
equal(parsed.href, "https://foo.com:8888/boo/bar.js?q=query", "parseURL parsed valid href");
parsed = sourceUtils.parseURL("https://foo.com");
equal(parsed.host, "foo.com", "parseURL parsed valid host when no port given");
equal(parsed.hostname, "foo.com", "parseURL parsed valid hostname when no port given");
equal(sourceUtils.parseURL("self-hosted"), null,
"parseURL returns `null` for invalid URLs");
});
// Test `sourceUtils.isContentScheme`.
add_task(function* () {
for (let url of CHROME_URLS) {
ok(!sourceUtils.isContentScheme(url),
`${url} correctly identified as not content scheme`);
}
for (let url of CONTENT_URLS) {
ok(sourceUtils.isContentScheme(url), `${url} correctly identified as content scheme`);
}
});
// Test `sourceUtils.isChromeScheme`.
add_task(function* () {
for (let url of CHROME_URLS) {
ok(sourceUtils.isChromeScheme(url), `${url} correctly identified as chrome scheme`);
}
for (let url of CONTENT_URLS) {
ok(!sourceUtils.isChromeScheme(url),
`${url} correctly identified as not chrome scheme`);
}
});
// Test `sourceUtils.isDataScheme`.
add_task(function* () {
let dataURI = "data:text/html;charset=utf-8,<!DOCTYPE html></html>";
ok(sourceUtils.isDataScheme(dataURI), `${dataURI} correctly identified as data scheme`);
for (let url of CHROME_URLS) {
ok(!sourceUtils.isDataScheme(url), `${url} correctly identified as not data scheme`);
}
for (let url of CONTENT_URLS) {
ok(!sourceUtils.isDataScheme(url), `${url} correctly identified as not data scheme`);
}
});
// Test `sourceUtils.getSourceNames`.
add_task(function* () {
testAbbreviation("http://example.com/foo/bar/baz/boo.js",
"boo.js",
"http://example.com/foo/bar/baz/boo.js",
"example.com");
});
// Test `sourceUtils.isScratchpadTheme`
add_task(function* () {
ok(sourceUtils.isScratchpadScheme("Scratchpad/1"),
"Scratchpad/1 identified as scratchpad");
ok(sourceUtils.isScratchpadScheme("Scratchpad/20"),
"Scratchpad/20 identified as scratchpad");
ok(!sourceUtils.isScratchpadScheme("http://www.mozilla.org"), "http://www.mozilla.org not identified as scratchpad");
});
// Test `sourceUtils.getSourceNames`.
add_task(function* () {
// Check length
let longMalformedURL = `example.com${new Array(100).fill("/a").join("")}/file.js`;
ok(sourceUtils.getSourceNames(longMalformedURL).short.length <= 100,
"`short` names are capped at 100 characters");
testAbbreviation("self-hosted", "self-hosted", "self-hosted");
testAbbreviation("", "(unknown)", "(unknown)");
// Test shortening data URIs, stripping mime/charset
testAbbreviation("data:text/html;charset=utf-8,<!DOCTYPE html></html>",
"data:<!DOCTYPE html></html>",
"data:text/html;charset=utf-8,<!DOCTYPE html></html>");
let longDataURI = `data:image/png;base64,${new Array(100).fill("a").join("")}`;
let longDataURIShort = sourceUtils.getSourceNames(longDataURI).short;
// Test shortening data URIs and that the `short` result is capped
ok(longDataURIShort.length <= 100,
"`short` names are capped at 100 characters for data URIs");
equal(longDataURIShort.substr(0, 10), "data:aaaaa",
"truncated data URI short names still have `data:...`");
// Test simple URL and cache retrieval by calling the same input multiple times.
let testUrl = "http://example.com/foo/bar/baz/boo.js";
testAbbreviation(testUrl, "boo.js", testUrl, "example.com");
testAbbreviation(testUrl, "boo.js", testUrl, "example.com");
// Check query and hash and port
testAbbreviation("http://example.com:8888/foo/bar/baz.js?q=query#go",
"baz.js",
"http://example.com:8888/foo/bar/baz.js",
"example.com:8888");
// Trailing "/" with nothing beyond host
testAbbreviation("http://example.com/",
"/",
"http://example.com/",
"example.com");
// Trailing "/"
testAbbreviation("http://example.com/foo/bar/",
"bar",
"http://example.com/foo/bar/",
"example.com");
// Non-extension ending
testAbbreviation("http://example.com/bar",
"bar",
"http://example.com/bar",
"example.com");
// Check query
testAbbreviation("http://example.com/foo.js?bar=1&baz=2",
"foo.js",
"http://example.com/foo.js",
"example.com");
// Check query with trailing slash
testAbbreviation("http://example.com/foo/?bar=1&baz=2",
"foo",
"http://example.com/foo/",
"example.com");
});
// Test for source mapped file name
add_task(function* () {
const { getSourceMappedFile } = sourceUtils;
const source = "baz.js";
const output = getSourceMappedFile(source);
equal(output, "baz.js", "correctly formats file name");
// Test for OSX file path
const source1 = "/foo/bar/baz.js";
const output1 = getSourceMappedFile(source1);
equal(output1, "baz.js", "correctly formats Linux file path");
// Test for Windows file path
const source2 = "Z:\\foo\\bar\\baz.js";
const output2 = getSourceMappedFile(source2);
equal(output2, "baz.js", "correctly formats Windows file path");
});
function testAbbreviation(source, short, long, host) {
let results = sourceUtils.getSourceNames(source);
equal(results.short, short, `${source} has correct "short" name`);
equal(results.long, long, `${source} has correct "long" name`);
equal(results.host, host, `${source} has correct "host" name`);
}

View file

@ -0,0 +1,149 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Test the suggestion-picker helper methods.
*/
const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
const {
findMostRelevantIndex,
findMostRelevantCssPropertyIndex
} = require("devtools/client/shared/suggestion-picker");
/**
* Run all tests defined below.
*/
function run_test() {
ensureMostRelevantIndexProvidedByHelperFunction();
ensureMostRelevantIndexProvidedByClassMethod();
ensureErrorThrownWithInvalidArguments();
}
/**
* Generic test data.
*/
const TEST_DATA = [
{
// Match in sortedItems array.
items: ["chrome", "edge", "firefox"],
sortedItems: ["firefox", "chrome", "edge"],
expectedIndex: 2
}, {
// No match in sortedItems array.
items: ["apple", "oranges", "banana"],
sortedItems: ["kiwi", "pear", "peach"],
expectedIndex: 0
}, {
// Empty items array.
items: [],
sortedItems: ["empty", "arrays", "can't", "have", "relevant", "indexes"],
expectedIndex: -1
}
];
function ensureMostRelevantIndexProvidedByHelperFunction() {
do_print("Running ensureMostRelevantIndexProvidedByHelperFunction()");
for (let testData of TEST_DATA) {
let { items, sortedItems, expectedIndex } = testData;
let mostRelevantIndex = findMostRelevantIndex(items, sortedItems);
strictEqual(mostRelevantIndex, expectedIndex);
}
}
/**
* CSS properties test data.
*/
const CSS_TEST_DATA = [
{
items: [
"backface-visibility",
"background",
"background-attachment",
"background-blend-mode",
"background-clip",
"background-color",
"background-image",
"background-origin",
"background-position",
"background-repeat"
],
expectedIndex: 1
},
{
items: [
"caption-side",
"clear",
"clip",
"clip-path",
"clip-rule",
"color",
"color-interpolation",
"color-interpolation-filters",
"content",
"counter-increment"
],
expectedIndex: 5
},
{
items: [
"direction",
"display",
"dominant-baseline"
],
expectedIndex: 1
},
{
items: [
"object-fit",
"object-position",
"offset-block-end",
"offset-block-start",
"offset-inline-end",
"offset-inline-start",
"opacity",
"order",
"orphans",
"outline"
],
expectedIndex: 6
},
{
items: [
"white-space",
"widows",
"width",
"will-change",
"word-break",
"word-spacing",
"word-wrap",
"writing-mode"
],
expectedIndex: 2
}
];
function ensureMostRelevantIndexProvidedByClassMethod() {
do_print("Running ensureMostRelevantIndexProvidedByClassMethod()");
for (let testData of CSS_TEST_DATA) {
let { items, expectedIndex } = testData;
let mostRelevantIndex = findMostRelevantCssPropertyIndex(items);
strictEqual(mostRelevantIndex, expectedIndex);
}
}
function ensureErrorThrownWithInvalidArguments() {
do_print("Running ensureErrorThrownWithInvalidTypeArgument()");
let expectedError = "Please provide valid items and sortedItems arrays.";
// No arguments passed.
throws(() => findMostRelevantIndex(), expectedError);
// Invalid arguments passed.
throws(() => findMostRelevantIndex([]), expectedError);
throws(() => findMostRelevantIndex(null, []), expectedError);
throws(() => findMostRelevantIndex([], "string"), expectedError);
throws(() => findMostRelevantIndex("string", []), expectedError);
}

View file

@ -0,0 +1,98 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {Loader} = Components.utils.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
const loader = new Loader.Loader({
paths: {
"": "resource://gre/modules/commonjs/",
"devtools": "resource://devtools",
},
globals: {},
});
const require = Loader.Require(loader, { id: "undo-test" });
const {UndoStack} = require("devtools/client/shared/undo");
const MAX_SIZE = 5;
function run_test() {
let str = "";
let stack = new UndoStack(MAX_SIZE);
function add(ch) {
stack.do(function () {
str += ch;
}, function () {
str = str.slice(0, -1);
});
}
do_check_false(stack.canUndo());
do_check_false(stack.canRedo());
// Check adding up to the limit of the size
add("a");
do_check_true(stack.canUndo());
do_check_false(stack.canRedo());
add("b");
add("c");
add("d");
add("e");
do_check_eq(str, "abcde");
// Check a simple undo+redo
stack.undo();
do_check_eq(str, "abcd");
do_check_true(stack.canRedo());
stack.redo();
do_check_eq(str, "abcde");
do_check_false(stack.canRedo());
// Check an undo followed by a new action
stack.undo();
do_check_eq(str, "abcd");
add("q");
do_check_eq(str, "abcdq");
do_check_false(stack.canRedo());
stack.undo();
do_check_eq(str, "abcd");
stack.redo();
do_check_eq(str, "abcdq");
// Revert back to the beginning of the queue...
while (stack.canUndo()) {
stack.undo();
}
do_check_eq(str, "");
// Now put it all back....
while (stack.canRedo()) {
stack.redo();
}
do_check_eq(str, "abcdq");
// Now go over the undo limit...
add("1");
add("2");
add("3");
do_check_eq(str, "abcdq123");
// And now undoing the whole stack should only undo 5 actions.
while (stack.canUndo()) {
stack.undo();
}
do_check_eq(str, "abc");
}

View file

@ -0,0 +1,30 @@
[DEFAULT]
tags = devtools
head =
tail =
firefox-appdir = browser
skip-if = toolkit == 'android'
support-files =
../helper_color_data.js
[test_advanceValidate.js]
[test_attribute-parsing-01.js]
[test_attribute-parsing-02.js]
[test_bezierCanvas.js]
[test_cssAngle.js]
[test_cssColor-01.js]
[test_cssColor-02.js]
[test_cssColor-03.js]
[test_cssColorDatabase.js]
[test_cubicBezier.js]
[test_escapeCSSComment.js]
[test_parseDeclarations.js]
[test_parsePseudoClassesAndAttributes.js]
[test_parseSingleValue.js]
[test_rewriteDeclarations.js]
[test_source-utils.js]
[test_suggestion-picker.js]
[test_undoStack.js]
[test_VariablesView_filtering-without-controller.js]
[test_VariablesView_getString_promise.js]