1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/xul/templates/tests/chrome/templates_shared.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,477 @@ 1.4 +/** 1.5 + * This script is used for testing XUL templates. Call test_template within 1.6 + * a load event handler. 1.7 + * 1.8 + * A test should have a root node with the datasources attribute with the 1.9 + * id 'root', and a few global variables defined in the test's XUL file: 1.10 + * 1.11 + * testid: the testid, used when outputting test results 1.12 + * expectedOutput: e4x data containing the expected output. It can optionally 1.13 + * be enclosed in an <output> element as most tests generate 1.14 + * more than one node of output. 1.15 + * isTreeBuilder: true for dont-build-content trees, false otherwise 1.16 + * queryType: 'rdf', 'xml', etc. 1.17 + * needsOpen: true for menu tests where the root menu must be opened before 1.18 + * comparing results 1.19 + * notWorkingYet: true if this test isn't working yet, outputs todo results 1.20 + * notWorkingYetDynamic: true if the dynamic changes portion of the test 1.21 + * isn't working yet, outputs todo results 1.22 + * changes: an array of functions to perform in sequence to test dynamic changes 1.23 + * to the datasource. 1.24 + * 1.25 + * If the <output> element has an unordered attribute set to true, the 1.26 + * children within it must all appear to match, but may appear in any order. 1.27 + * If the unordered attribute is not set, the children must appear in the same 1.28 + * order. 1.29 + * 1.30 + * If the 'changes' array is used, it should be an array of functions. Each 1.31 + * function will be called in order and a comparison of the output will be 1.32 + * performed. This allows changes to be made to the datasource to ensure that 1.33 + * the generated template output has been updated. Within the expected output 1.34 + * XML, the step attribute may be set to a number on an element to indicate 1.35 + * that an element only applies before or after a particular change. If step 1.36 + * is set to a positive number, that element will only exist after that step in 1.37 + * the list of changes made. If step is set to a negative number, that element 1.38 + * will only exist until that step. Steps are numbered starting at 1. For 1.39 + * example: 1.40 + * <label value="Cat"/> 1.41 + * <label step="2" value="Dog"/> 1.42 + * <label step="-5" value="Mouse"/> 1.43 + * The first element will always exist. The second element will only appear 1.44 + * after the second change is made. The third element will only appear until 1.45 + * the fifth change and it will no longer be present at later steps. 1.46 + * 1.47 + * If the anyid attribute is set to true on an element in the expected output, 1.48 + * then the value of the id attribute on that element is not compared for a 1.49 + * match. This is used, for example, for xml datasources, where the ids set on 1.50 + * the generated output are pseudo-random. 1.51 + */ 1.52 + 1.53 +const ZOO_NS = "http://www.some-fictitious-zoo.com/"; 1.54 +const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 1.55 +const debug = false; 1.56 + 1.57 +var expectedConsoleMessages = []; 1.58 +var expectLoggedMessages = null; 1.59 + 1.60 +try { 1.61 + const RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]. 1.62 + getService(Components.interfaces.nsIRDFService); 1.63 + const ContainerUtils = Components.classes["@mozilla.org/rdf/container-utils;1"]. 1.64 + getService(Components.interfaces.nsIRDFContainerUtils); 1.65 +} catch(ex) { } 1.66 + 1.67 +var xmlDoc; 1.68 + 1.69 +function test_template() 1.70 +{ 1.71 + var root = document.getElementById("root"); 1.72 + 1.73 + var ds; 1.74 + if (queryType == "rdf" && RDF) { 1.75 + var ioService = Components.classes["@mozilla.org/network/io-service;1"]. 1.76 + getService(Components.interfaces.nsIIOService); 1.77 + 1.78 + var src = window.location.href.replace(/test_tmpl.*xul/, "animals.rdf"); 1.79 + ds = RDF.GetDataSourceBlocking(src); 1.80 + 1.81 + if (expectLoggedMessages) { 1.82 + Components.classes["@mozilla.org/consoleservice;1"]. 1.83 + getService(Components.interfaces.nsIConsoleService).reset(); 1.84 + } 1.85 + 1.86 + if (root.getAttribute("datasources") == "rdf:null") 1.87 + root.setAttribute("datasources", "animals.rdf"); 1.88 + } 1.89 + else if (queryType == "xml") { 1.90 + var src = window.location.href.replace(/test_tmpl.*xul/, "animals.xml"); 1.91 + xmlDoc = new XMLHttpRequest(); 1.92 + xmlDoc.open("get", src, false); 1.93 + xmlDoc.send(null); 1.94 + } 1.95 + 1.96 + // open menus if necessary 1.97 + if (needsOpen) 1.98 + root.open = true; 1.99 + 1.100 + if (expectLoggedMessages) 1.101 + expectLoggedMessages(); 1.102 + 1.103 + checkResults(root, 0); 1.104 + 1.105 + if (changes.length) { 1.106 + var usedds = ds; 1.107 + // within these chrome tests, RDF datasources won't be modifiable unless 1.108 + // an in-memory datasource is used instead. Call copyRDFDataSource to 1.109 + // copy the datasource. 1.110 + if (queryType == "rdf") 1.111 + usedds = copyRDFDataSource(root, ds); 1.112 + if (needsOpen) 1.113 + root.open = true; 1.114 + setTimeout(iterateChanged, 0, root, usedds); 1.115 + } 1.116 + else { 1.117 + if (needsOpen) 1.118 + root.open = false; 1.119 + if (expectedConsoleMessages.length) 1.120 + compareConsoleMessages(); 1.121 + SimpleTest.finish(); 1.122 + } 1.123 +} 1.124 + 1.125 +function iterateChanged(root, ds) 1.126 +{ 1.127 + Components.classes["@mozilla.org/consoleservice;1"]. 1.128 + getService(Components.interfaces.nsIConsoleService).reset(); 1.129 + 1.130 + for (var c = 0; c < changes.length; c++) { 1.131 + changes[c](ds, root); 1.132 + checkResults(root, c + 1); 1.133 + } 1.134 + 1.135 + if (needsOpen) 1.136 + root.open = false; 1.137 + if (expectedConsoleMessages.length) 1.138 + compareConsoleMessages(); 1.139 + SimpleTest.finish(); 1.140 +} 1.141 + 1.142 +function checkResults(root, step) 1.143 +{ 1.144 + var output = expectedOutput.cloneNode(true); 1.145 + setForCurrentStep(output, step); 1.146 + 1.147 + var error; 1.148 + var actualoutput = root; 1.149 + if (isTreeBuilder) { 1.150 + // convert the tree's view data into the equivalent DOM structure 1.151 + // for easier comparison 1.152 + actualoutput = treeViewToDOM(root); 1.153 + var treechildrenElements = [e for (e of output.children) if (e.localName === "treechildren")]; 1.154 + error = compareOutput(actualoutput, treechildrenElements[0], false); 1.155 + } 1.156 + else { 1.157 + error = compareOutput(actualoutput, output, true); 1.158 + } 1.159 + 1.160 + var adjtestid = testid; 1.161 + if (step > 0) 1.162 + adjtestid += " dynamic step " + step; 1.163 + 1.164 + var stilltodo = ((step == 0 && notWorkingYet) || (step > 0 && notWorkingYetDynamic)); 1.165 + if (stilltodo) 1.166 + todo(false, adjtestid); 1.167 + else 1.168 + ok(!error, adjtestid); 1.169 + 1.170 + if ((!stilltodo && error) || debug) { 1.171 + // for debugging, serialize the XML output 1.172 + var serializedXML = ""; 1.173 + var rootNodes = actualoutput.childNodes; 1.174 + for (var n = 0; n < rootNodes.length; n++) { 1.175 + var node = rootNodes[n]; 1.176 + if (node.localName != "template") 1.177 + serializedXML += ((new XMLSerializer()).serializeToString(node)); 1.178 + } 1.179 + 1.180 + // remove the XUL namespace declarations to make the output more readable 1.181 + const nsrepl = new RegExp("xmlns=\"" + XUL_NS + "\" ", "g"); 1.182 + serializedXML = serializedXML.replace(nsrepl, ""); 1.183 + if (debug) 1.184 + dump("-------- " + adjtestid + " " + error + ":\n" + serializedXML + "\n"); 1.185 + if (!stilltodo && error) 1.186 + is(serializedXML, "Same", "Error is: " + error); 1.187 + } 1.188 +} 1.189 + 1.190 +/** 1.191 + * Adjust the expected output to acccount for any step attributes. 1.192 + */ 1.193 +function setForCurrentStep(content, currentStep) 1.194 +{ 1.195 + var todelete = []; 1.196 + for (var child of content.childNodes) { 1.197 + if (child.nodeType === Node.ELEMENT_NODE) { 1.198 + var stepstr = child.getAttribute("step") || ""; 1.199 + var stepsarr = stepstr.split(","); 1.200 + for (var s = 0; s < stepsarr.length; s++) { 1.201 + var step = parseInt(stepsarr[s]); 1.202 + if ((step > 0 && step > currentStep) || 1.203 + (step < 0 && -step <= currentStep)) { 1.204 + todelete.push(child); 1.205 + } 1.206 + } 1.207 + } else if (child.nodeType === Node.TEXT_NODE) { 1.208 + // Drop empty text nodes. 1.209 + if (child.nodeValue.trim() === "") 1.210 + todelete.push(child); 1.211 + } 1.212 + } 1.213 + 1.214 + for (var e of todelete) 1.215 + content.removeChild(e); 1.216 + 1.217 + for (var child of content.children) { 1.218 + child.removeAttribute("step"); 1.219 + setForCurrentStep(child, currentStep); 1.220 + } 1.221 +} 1.222 + 1.223 +/** 1.224 + * Compares the 'actual' DOM output with the 'expected' output. This function 1.225 + * is called recursively, with isroot true if actual refers to the root of the 1.226 + * template. Returns a null string if they are equal and an error string if 1.227 + * they are not equal. This function is called recursively as it iterates 1.228 + * through each node in the DOM tree. 1.229 + */ 1.230 +function compareOutput(actual, expected, isroot) 1.231 +{ 1.232 + if (isroot && expected.localName != "data") 1.233 + return "expected must be a <data> element"; 1.234 + 1.235 + var t; 1.236 + 1.237 + // compare text nodes 1.238 + if (expected.nodeType == Node.TEXT_NODE) { 1.239 + if (actual.nodeValue !== expected.nodeValue.trim()) 1.240 + return "Text " + actual.nodeValue + " doesn't match " + expected.nodeValue; 1.241 + return ""; 1.242 + } 1.243 + 1.244 + if (!isroot) { 1.245 + var anyid = false; 1.246 + // make sure that the tags match 1.247 + if (actual.localName != expected.localName) 1.248 + return "Tag name " + expected.localName + " not found"; 1.249 + 1.250 + // loop through the attributes in the expected node and compare their 1.251 + // values with the corresponding attribute on the actual node 1.252 + 1.253 + var expectedAttrs = expected.attributes; 1.254 + for (var a = 0; a < expectedAttrs.length; a++) { 1.255 + var attr = expectedAttrs[a]; 1.256 + var expectval = attr.value; 1.257 + // skip checking the id when anyid="true", however make sure to 1.258 + // ensure that the id is actually present. 1.259 + if (attr.name == "anyid" && expectval == "true") { 1.260 + anyid = true; 1.261 + if (!actual.hasAttribute("id")) 1.262 + return "expected id attribute"; 1.263 + } 1.264 + else if (actual.getAttribute(attr.name) != expectval) { 1.265 + return "attribute " + attr.name + " is '" + 1.266 + actual.getAttribute(attr.name) + "' instead of '" + expectval + "'"; 1.267 + } 1.268 + } 1.269 + 1.270 + // now loop through the actual attributes and make sure that there aren't 1.271 + // any extra attributes that weren't expected 1.272 + var length = actual.attributes.length; 1.273 + for (t = 0; t < length; t++) { 1.274 + var aattr = actual.attributes[t]; 1.275 + var expectval = expected.getAttribute(aattr.name); 1.276 + // ignore some attributes that don't matter 1.277 + if (expectval != actual.getAttribute(aattr.name) && 1.278 + aattr.name != "staticHint" && aattr.name != "xmlns" && 1.279 + (aattr.name != "id" || !anyid)) 1.280 + return "extra attribute " + aattr.name; 1.281 + } 1.282 + } 1.283 + 1.284 + // ensure that the node has the right number of children. Subtract one for 1.285 + // the root node to account for the <template> node. 1.286 + length = actual.childNodes.length - (isroot ? 1 : 0); 1.287 + if (length != expected.childNodes.length) 1.288 + return "incorrect child node count of " + actual.localName + " " + length + 1.289 + " expected " + expected.childNodes.length; 1.290 + 1.291 + // if <data unordered="true"> is used, then the child nodes may be in any order 1.292 + var unordered = (expected.localName == "data" && expected.getAttribute("unordered") == "true"); 1.293 + 1.294 + // next, loop over the children and call compareOutput recursively on each one 1.295 + var adj = 0; 1.296 + for (t = 0; t < actual.childNodes.length; t++) { 1.297 + var actualnode = actual.childNodes[t]; 1.298 + // skip the <template> element, and add one to the indices when looking 1.299 + // at the later nodes to account for it 1.300 + if (isroot && actualnode.localName == "template") { 1.301 + adj++; 1.302 + } 1.303 + else { 1.304 + var output = "unexpected"; 1.305 + if (unordered) { 1.306 + var expectedChildren = expected.childNodes; 1.307 + for (var e = 0; e < expectedChildren.length; e++) { 1.308 + output = compareOutput(actualnode, expectedChildren[e], false); 1.309 + if (!output) 1.310 + break; 1.311 + } 1.312 + } 1.313 + else { 1.314 + output = compareOutput(actualnode, expected.childNodes[t - adj], false); 1.315 + } 1.316 + 1.317 + // an error was returned, so return early 1.318 + if (output) 1.319 + return output; 1.320 + } 1.321 + } 1.322 + 1.323 + return ""; 1.324 +} 1.325 + 1.326 +/* 1.327 + * copy the datasource into an in-memory datasource so that it can be modified 1.328 + */ 1.329 +function copyRDFDataSource(root, sourceds) 1.330 +{ 1.331 + var dsourcesArr = []; 1.332 + var composite = root.database; 1.333 + var dsources = composite.GetDataSources(); 1.334 + while (dsources.hasMoreElements()) { 1.335 + sourceds = dsources.getNext().QueryInterface(Components.interfaces.nsIRDFDataSource); 1.336 + dsourcesArr.push(sourceds); 1.337 + } 1.338 + 1.339 + for (var d = 0; d < dsourcesArr.length; d++) 1.340 + composite.RemoveDataSource(dsourcesArr[d]); 1.341 + 1.342 + var newds = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]. 1.343 + createInstance(Components.interfaces.nsIRDFDataSource); 1.344 + 1.345 + var sourcelist = sourceds.GetAllResources(); 1.346 + while (sourcelist.hasMoreElements()) { 1.347 + var source = sourcelist.getNext(); 1.348 + var props = sourceds.ArcLabelsOut(source); 1.349 + while (props.hasMoreElements()) { 1.350 + var prop = props.getNext(); 1.351 + if (prop instanceof Components.interfaces.nsIRDFResource) { 1.352 + var targets = sourceds.GetTargets(source, prop, true); 1.353 + while (targets.hasMoreElements()) 1.354 + newds.Assert(source, prop, targets.getNext(), true); 1.355 + } 1.356 + } 1.357 + } 1.358 + 1.359 + composite.AddDataSource(newds); 1.360 + root.builder.rebuild(); 1.361 + 1.362 + return newds; 1.363 +} 1.364 + 1.365 +/** 1.366 + * Converts a tree view (nsITreeView) into the equivalent DOM tree. 1.367 + * Returns the treechildren 1.368 + */ 1.369 +function treeViewToDOM(tree) 1.370 +{ 1.371 + var treechildren = document.createElement("treechildren"); 1.372 + 1.373 + if (tree.view) 1.374 + treeViewToDOMInner(tree.columns, treechildren, tree.view, tree.builder, 0, 0); 1.375 + 1.376 + return treechildren; 1.377 +} 1.378 + 1.379 +function treeViewToDOMInner(columns, treechildren, view, builder, start, level) 1.380 +{ 1.381 + var end = view.rowCount; 1.382 + 1.383 + for (var i = start; i < end; i++) { 1.384 + if (view.getLevel(i) < level) 1.385 + return i - 1; 1.386 + 1.387 + var id = builder ? builder.getResourceAtIndex(i).Value : "id" + i; 1.388 + var item = document.createElement("treeitem"); 1.389 + item.setAttribute("id", id); 1.390 + treechildren.appendChild(item); 1.391 + 1.392 + var row = document.createElement("treerow"); 1.393 + item.appendChild(row); 1.394 + 1.395 + for (var c = 0; c < columns.length; c++) { 1.396 + var cell = document.createElement("treecell"); 1.397 + var label = view.getCellText(i, columns[c]); 1.398 + if (label) 1.399 + cell.setAttribute("label", label); 1.400 + row.appendChild(cell); 1.401 + } 1.402 + 1.403 + if (view.isContainer(i)) { 1.404 + item.setAttribute("container", "true"); 1.405 + item.setAttribute("empty", view.isContainerEmpty(i) ? "true" : "false"); 1.406 + 1.407 + if (!view.isContainerEmpty(i) && view.isContainerOpen(i)) { 1.408 + item.setAttribute("open", "true"); 1.409 + 1.410 + var innertreechildren = document.createElement("treechildren"); 1.411 + item.appendChild(innertreechildren); 1.412 + 1.413 + i = treeViewToDOMInner(columns, innertreechildren, view, builder, i + 1, level + 1); 1.414 + } 1.415 + } 1.416 + } 1.417 + 1.418 + return i; 1.419 +} 1.420 + 1.421 +function expectConsoleMessage(ref, id, isNew, isActive, extra) 1.422 +{ 1.423 + var message = "In template with id root" + 1.424 + (ref ? " using ref " + ref : "") + "\n " + 1.425 + (isNew ? "New " : "Removed ") + (isActive ? "active" : "inactive") + 1.426 + " result for query " + extra + ": " + id; 1.427 + expectedConsoleMessages.push(message); 1.428 +} 1.429 + 1.430 +function compareConsoleMessages() 1.431 +{ 1.432 + var consoleService = Components.classes["@mozilla.org/consoleservice;1"]. 1.433 + getService(Components.interfaces.nsIConsoleService); 1.434 + var messages = consoleService.getMessageArray() || []; 1.435 + messages = messages.map(function (m) m.message); 1.436 + // Copy to avoid modifying expectedConsoleMessages 1.437 + var expect = expectedConsoleMessages.concat(); 1.438 + for (var m = 0; m < messages.length; m++) { 1.439 + if (messages[m] == expect[0]) { 1.440 + ok(true, "found message " + expect.shift()); 1.441 + } 1.442 + } 1.443 + if (expect.length != 0) { 1.444 + ok(false, "failed to find expected console messages: " + expect); 1.445 + } 1.446 +} 1.447 + 1.448 +function copyToProfile(filename) 1.449 +{ 1.450 + if (Cc === undefined) { 1.451 + var Cc = Components.classes; 1.452 + var Ci = Components.interfaces; 1.453 + } 1.454 + 1.455 + var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] 1.456 + .getService(Ci.mozIJSSubScriptLoader); 1.457 + loader.loadSubScript("chrome://mochikit/content/chrome-harness.js"); 1.458 + 1.459 + var file = Cc["@mozilla.org/file/directory_service;1"] 1.460 + .getService(Ci.nsIProperties) 1.461 + .get("ProfD", Ci.nsIFile); 1.462 + file.append(filename); 1.463 + 1.464 + var parentURI = getResolvedURI(getRootDirectory(window.location.href)); 1.465 + if (parentURI.JARFile) { 1.466 + parentURI = extractJarToTmp(parentURI); 1.467 + } else { 1.468 + var fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"]. 1.469 + getService(Ci.nsIFileProtocolHandler); 1.470 + parentURI = fileHandler.getFileFromURLSpec(parentURI.spec); 1.471 + } 1.472 + 1.473 + parentURI = parentURI.QueryInterface(Ci.nsILocalFile); 1.474 + parentURI.append(filename); 1.475 + try { 1.476 + var retVal = parentURI.copyToFollowingLinks(file.parent, filename); 1.477 + } catch (ex) { 1.478 + //ignore this error as the file could exist already 1.479 + } 1.480 +}