content/xul/templates/tests/chrome/templates_shared.js

changeset 0
6474c204b198
     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 +}

mercurial