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

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /**
     2  * This script is used for testing XUL templates. Call test_template within
     3  * a load event handler.
     4  *
     5  * A test should have a root node with the datasources attribute with the
     6  * id 'root', and a few global variables defined in the test's XUL file:
     7  *
     8  *  testid: the testid, used when outputting test results
     9  *  expectedOutput: e4x data containing the expected output. It can optionally
    10  *                  be enclosed in an <output> element as most tests generate
    11  *                  more than one node of output.
    12  *  isTreeBuilder: true for dont-build-content trees, false otherwise
    13  *  queryType: 'rdf', 'xml', etc.
    14  *  needsOpen: true for menu tests where the root menu must be opened before
    15  *             comparing results
    16  *  notWorkingYet: true if this test isn't working yet, outputs todo results
    17  *  notWorkingYetDynamic: true if the dynamic changes portion of the test
    18  *                        isn't working yet, outputs todo results
    19  *  changes: an array of functions to perform in sequence to test dynamic changes
    20  *           to the datasource.
    21  *
    22  * If the <output> element has an unordered attribute set to true, the
    23  * children within it must all appear to match, but may appear in any order.
    24  * If the unordered attribute is not set, the children must appear in the same
    25  * order.
    26  *
    27  * If the 'changes' array is used, it should be an array of functions. Each
    28  * function will be called in order and a comparison of the output will be
    29  * performed. This allows changes to be made to the datasource to ensure that
    30  * the generated template output has been updated. Within the expected output
    31  * XML, the step attribute may be set to a number on an element to indicate
    32  * that an element only applies before or after a particular change. If step
    33  * is set to a positive number, that element will only exist after that step in
    34  * the list of changes made. If step is set to a negative number, that element
    35  * will only exist until that step. Steps are numbered starting at 1. For
    36  * example:
    37  *   <label value="Cat"/>
    38  *   <label step="2" value="Dog"/>
    39  *   <label step="-5" value="Mouse"/>
    40  * The first element will always exist. The second element will only appear
    41  * after the second change is made. The third element will only appear until
    42  * the fifth change and it will no longer be present at later steps.
    43  *
    44  * If the anyid attribute is set to true on an element in the expected output,
    45  * then the value of the id attribute on that element is not compared for a
    46  * match. This is used, for example, for xml datasources, where the ids set on
    47  * the generated output are pseudo-random.
    48  */
    50 const ZOO_NS = "http://www.some-fictitious-zoo.com/";
    51 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
    52 const debug = false;
    54 var expectedConsoleMessages = [];
    55 var expectLoggedMessages = null;
    57 try {
    58   const RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"].
    59                 getService(Components.interfaces.nsIRDFService);
    60   const ContainerUtils = Components.classes["@mozilla.org/rdf/container-utils;1"].
    61                            getService(Components.interfaces.nsIRDFContainerUtils);
    62 } catch(ex) { }
    64 var xmlDoc;
    66 function test_template()
    67 {
    68   var root = document.getElementById("root");
    70   var ds;
    71   if (queryType == "rdf" && RDF) {
    72     var ioService = Components.classes["@mozilla.org/network/io-service;1"].
    73                       getService(Components.interfaces.nsIIOService);
    75     var src = window.location.href.replace(/test_tmpl.*xul/, "animals.rdf");
    76     ds = RDF.GetDataSourceBlocking(src);
    78     if (expectLoggedMessages) {
    79       Components.classes["@mozilla.org/consoleservice;1"].
    80                  getService(Components.interfaces.nsIConsoleService).reset();
    81     }
    83     if (root.getAttribute("datasources") == "rdf:null")
    84       root.setAttribute("datasources", "animals.rdf");
    85   }
    86   else if (queryType == "xml") {
    87     var src = window.location.href.replace(/test_tmpl.*xul/, "animals.xml");
    88     xmlDoc = new XMLHttpRequest();
    89     xmlDoc.open("get", src, false);
    90     xmlDoc.send(null);
    91   }
    93   // open menus if necessary
    94   if (needsOpen)
    95     root.open = true;
    97   if (expectLoggedMessages)
    98     expectLoggedMessages();
   100   checkResults(root, 0);
   102   if (changes.length) {
   103     var usedds = ds;
   104     // within these chrome tests, RDF datasources won't be modifiable unless
   105     // an in-memory datasource is used instead. Call copyRDFDataSource to
   106     // copy the datasource.
   107     if (queryType == "rdf")
   108       usedds = copyRDFDataSource(root, ds);
   109     if (needsOpen)
   110       root.open = true;
   111     setTimeout(iterateChanged, 0, root, usedds);
   112   }
   113   else {
   114     if (needsOpen)
   115       root.open = false;
   116     if (expectedConsoleMessages.length)
   117       compareConsoleMessages();
   118     SimpleTest.finish();
   119   }
   120 }
   122 function iterateChanged(root, ds)
   123 {
   124   Components.classes["@mozilla.org/consoleservice;1"].
   125              getService(Components.interfaces.nsIConsoleService).reset();
   127   for (var c = 0; c < changes.length; c++) {
   128     changes[c](ds, root);
   129     checkResults(root, c + 1);
   130   }
   132   if (needsOpen)
   133     root.open = false;
   134   if (expectedConsoleMessages.length)
   135     compareConsoleMessages();
   136   SimpleTest.finish();
   137 }
   139 function checkResults(root, step)
   140 {
   141   var output = expectedOutput.cloneNode(true);
   142   setForCurrentStep(output, step);
   144   var error;
   145   var actualoutput = root;
   146   if (isTreeBuilder) {
   147     // convert the tree's view data into the equivalent DOM structure
   148     // for easier comparison
   149     actualoutput = treeViewToDOM(root);
   150     var treechildrenElements = [e for (e of output.children) if (e.localName === "treechildren")];
   151     error = compareOutput(actualoutput, treechildrenElements[0], false);
   152   }
   153   else {
   154     error = compareOutput(actualoutput, output, true);
   155   }
   157   var adjtestid = testid;
   158   if (step > 0)
   159     adjtestid += " dynamic step " + step;
   161   var stilltodo = ((step == 0 && notWorkingYet) || (step > 0 && notWorkingYetDynamic));
   162   if (stilltodo)
   163     todo(false, adjtestid);
   164   else
   165     ok(!error, adjtestid);
   167   if ((!stilltodo && error) || debug) {
   168     // for debugging, serialize the XML output
   169     var serializedXML = "";
   170     var rootNodes = actualoutput.childNodes;
   171     for (var n = 0; n < rootNodes.length; n++) {
   172       var node = rootNodes[n];
   173       if (node.localName != "template")
   174         serializedXML += ((new XMLSerializer()).serializeToString(node));
   175     }
   177     // remove the XUL namespace declarations to make the output more readable
   178     const nsrepl = new RegExp("xmlns=\"" + XUL_NS + "\" ", "g");
   179     serializedXML = serializedXML.replace(nsrepl, "");
   180     if (debug)
   181       dump("-------- " + adjtestid + "  " + error + ":\n" + serializedXML + "\n");
   182     if (!stilltodo && error)
   183       is(serializedXML, "Same", "Error is: " + error);
   184   }
   185 }
   187 /**
   188  * Adjust the expected output to acccount for any step attributes.
   189  */
   190 function setForCurrentStep(content, currentStep)
   191 {
   192   var todelete = [];
   193   for (var child of content.childNodes) {
   194     if (child.nodeType === Node.ELEMENT_NODE) {
   195       var stepstr = child.getAttribute("step") || "";
   196       var stepsarr = stepstr.split(",");
   197       for (var s = 0; s < stepsarr.length; s++) {
   198         var step = parseInt(stepsarr[s]);
   199         if ((step > 0 && step > currentStep) ||
   200             (step < 0 && -step <= currentStep)) {
   201           todelete.push(child);
   202         }
   203       }
   204     } else if (child.nodeType === Node.TEXT_NODE) {
   205       // Drop empty text nodes.
   206       if (child.nodeValue.trim() === "")
   207         todelete.push(child);
   208     }
   209   }
   211   for (var e of todelete)
   212     content.removeChild(e);
   214   for (var child of content.children) {
   215     child.removeAttribute("step");
   216     setForCurrentStep(child, currentStep);
   217   }
   218 }
   220 /**
   221  * Compares the 'actual' DOM output with the 'expected' output. This function
   222  * is called recursively, with isroot true if actual refers to the root of the
   223  * template. Returns a null string if they are equal and an error string if
   224  * they are not equal. This function is called recursively as it iterates
   225  * through each node in the DOM tree.
   226  */
   227 function compareOutput(actual, expected, isroot)
   228 {
   229   if (isroot && expected.localName != "data")
   230     return "expected must be a <data> element";
   232   var t;
   234   // compare text nodes
   235   if (expected.nodeType == Node.TEXT_NODE) {
   236     if (actual.nodeValue !== expected.nodeValue.trim())
   237       return "Text " + actual.nodeValue + " doesn't match " + expected.nodeValue;
   238     return "";
   239   }
   241   if (!isroot) {
   242     var anyid = false;
   243     // make sure that the tags match
   244     if (actual.localName != expected.localName)
   245       return "Tag name " + expected.localName + " not found";
   247     // loop through the attributes in the expected node and compare their
   248     // values with the corresponding attribute on the actual node
   250     var expectedAttrs = expected.attributes;
   251     for (var a = 0; a < expectedAttrs.length; a++) {
   252       var attr = expectedAttrs[a];
   253       var expectval = attr.value;
   254       // skip checking the id when anyid="true", however make sure to
   255       // ensure that the id is actually present.
   256       if (attr.name == "anyid" && expectval == "true") {
   257         anyid = true;
   258         if (!actual.hasAttribute("id"))
   259           return "expected id attribute";
   260       }
   261       else if (actual.getAttribute(attr.name) != expectval) {
   262         return "attribute " + attr.name + " is '" +
   263                actual.getAttribute(attr.name) + "' instead of '" + expectval + "'";
   264       }
   265     }
   267     // now loop through the actual attributes and make sure that there aren't
   268     // any extra attributes that weren't expected
   269     var length = actual.attributes.length;
   270     for (t = 0; t < length; t++) {
   271       var aattr = actual.attributes[t];
   272       var expectval = expected.getAttribute(aattr.name);
   273       // ignore some attributes that don't matter
   274       if (expectval != actual.getAttribute(aattr.name) &&
   275           aattr.name != "staticHint" && aattr.name != "xmlns" &&
   276           (aattr.name != "id" || !anyid))
   277         return "extra attribute " + aattr.name;
   278     }
   279   }
   281   // ensure that the node has the right number of children. Subtract one for
   282   // the root node to account for the <template> node.
   283   length = actual.childNodes.length - (isroot ? 1 : 0);
   284   if (length != expected.childNodes.length)
   285     return "incorrect child node count of " + actual.localName + " " + length +
   286            " expected " + expected.childNodes.length;
   288   // if <data unordered="true"> is used, then the child nodes may be in any order
   289   var unordered = (expected.localName == "data" && expected.getAttribute("unordered") == "true");
   291   // next, loop over the children and call compareOutput recursively on each one
   292   var adj = 0;
   293   for (t = 0; t < actual.childNodes.length; t++) {
   294     var actualnode = actual.childNodes[t];
   295     // skip the <template> element, and add one to the indices when looking
   296     // at the later nodes to account for it
   297     if (isroot && actualnode.localName == "template") {
   298       adj++;
   299     }
   300     else {
   301       var output = "unexpected";
   302       if (unordered) {
   303         var expectedChildren = expected.childNodes;
   304         for (var e = 0; e < expectedChildren.length; e++) {
   305           output = compareOutput(actualnode, expectedChildren[e], false);
   306           if (!output)
   307             break;
   308         }
   309       }
   310       else {
   311         output = compareOutput(actualnode, expected.childNodes[t - adj], false);
   312       }
   314       // an error was returned, so return early
   315       if (output)
   316         return output;
   317     }
   318   }
   320   return "";
   321 }
   323 /*
   324  * copy the datasource into an in-memory datasource so that it can be modified
   325  */
   326 function copyRDFDataSource(root, sourceds)
   327 {
   328   var dsourcesArr = [];
   329   var composite = root.database;
   330   var dsources = composite.GetDataSources();
   331   while (dsources.hasMoreElements()) {
   332     sourceds = dsources.getNext().QueryInterface(Components.interfaces.nsIRDFDataSource);
   333     dsourcesArr.push(sourceds);
   334   }
   336   for (var d = 0; d < dsourcesArr.length; d++)
   337     composite.RemoveDataSource(dsourcesArr[d]);
   339   var newds = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
   340                 createInstance(Components.interfaces.nsIRDFDataSource);
   342   var sourcelist = sourceds.GetAllResources();
   343   while (sourcelist.hasMoreElements()) {
   344     var source = sourcelist.getNext();
   345     var props = sourceds.ArcLabelsOut(source);
   346     while (props.hasMoreElements()) {
   347       var prop = props.getNext();
   348       if (prop instanceof Components.interfaces.nsIRDFResource) {
   349         var targets = sourceds.GetTargets(source, prop, true);
   350         while (targets.hasMoreElements())
   351           newds.Assert(source, prop, targets.getNext(), true);
   352       }
   353     }
   354   }
   356   composite.AddDataSource(newds);
   357   root.builder.rebuild();
   359   return newds;
   360 }
   362 /**
   363  * Converts a tree view (nsITreeView) into the equivalent DOM tree.
   364  * Returns the treechildren
   365  */
   366 function treeViewToDOM(tree)
   367 {
   368   var treechildren = document.createElement("treechildren");
   370   if (tree.view)
   371     treeViewToDOMInner(tree.columns, treechildren, tree.view, tree.builder, 0, 0);
   373   return treechildren;
   374 }
   376 function treeViewToDOMInner(columns, treechildren, view, builder, start, level)
   377 {
   378   var end = view.rowCount;
   380   for (var i = start; i < end; i++) {
   381     if (view.getLevel(i) < level)
   382       return i - 1;
   384     var id = builder ? builder.getResourceAtIndex(i).Value : "id" + i;
   385     var item = document.createElement("treeitem");
   386     item.setAttribute("id", id);
   387     treechildren.appendChild(item);
   389     var row = document.createElement("treerow");
   390     item.appendChild(row);
   392     for (var c = 0; c < columns.length; c++) {
   393       var cell = document.createElement("treecell");
   394       var label = view.getCellText(i, columns[c]);
   395       if (label)
   396         cell.setAttribute("label", label);
   397       row.appendChild(cell);
   398     }
   400     if (view.isContainer(i)) {
   401       item.setAttribute("container", "true");
   402       item.setAttribute("empty", view.isContainerEmpty(i) ? "true" : "false");
   404       if (!view.isContainerEmpty(i) && view.isContainerOpen(i)) {
   405         item.setAttribute("open", "true");
   407         var innertreechildren = document.createElement("treechildren");
   408         item.appendChild(innertreechildren);
   410         i = treeViewToDOMInner(columns, innertreechildren, view, builder, i + 1, level + 1);
   411       }
   412     }
   413   }
   415   return i;
   416 }
   418 function expectConsoleMessage(ref, id, isNew, isActive, extra)
   419 {
   420   var message = "In template with id root" +
   421                 (ref ? " using ref " + ref : "") + "\n    " +
   422                 (isNew ? "New " : "Removed ") + (isActive ? "active" : "inactive") +
   423                 " result for query " + extra + ": " + id;
   424   expectedConsoleMessages.push(message);
   425 }
   427 function compareConsoleMessages()
   428 {
   429   var consoleService = Components.classes["@mozilla.org/consoleservice;1"].
   430                          getService(Components.interfaces.nsIConsoleService);
   431   var messages = consoleService.getMessageArray() || [];
   432   messages = messages.map(function (m) m.message);
   433   // Copy to avoid modifying expectedConsoleMessages
   434   var expect = expectedConsoleMessages.concat();
   435   for (var m = 0; m < messages.length; m++) {
   436     if (messages[m] == expect[0]) {
   437       ok(true, "found message " + expect.shift());
   438     }
   439   }
   440   if (expect.length != 0) {
   441     ok(false, "failed to find expected console messages: " + expect);
   442   }
   443 }
   445 function copyToProfile(filename)
   446 {
   447   if (Cc === undefined) {
   448     var Cc = Components.classes;
   449     var Ci = Components.interfaces;
   450   }
   452   var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
   453                          .getService(Ci.mozIJSSubScriptLoader);
   454   loader.loadSubScript("chrome://mochikit/content/chrome-harness.js");
   456   var file = Cc["@mozilla.org/file/directory_service;1"]
   457                        .getService(Ci.nsIProperties)
   458                        .get("ProfD", Ci.nsIFile);
   459   file.append(filename);
   461   var parentURI = getResolvedURI(getRootDirectory(window.location.href));
   462   if (parentURI.JARFile) {
   463     parentURI = extractJarToTmp(parentURI);
   464   } else {
   465     var fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"].
   466                       getService(Ci.nsIFileProtocolHandler);
   467     parentURI = fileHandler.getFileFromURLSpec(parentURI.spec);
   468   }
   470   parentURI = parentURI.QueryInterface(Ci.nsILocalFile);
   471   parentURI.append(filename);
   472   try {
   473     var retVal = parentURI.copyToFollowingLinks(file.parent, filename);
   474   } catch (ex) {
   475     //ignore this error as the file could exist already
   476   }
   477 }

mercurial