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

branch
TOR_BUG_3246
changeset 7
129ffea94266
equal deleted inserted replaced
-1:000000000000 0:94221c605f4c
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 */
49
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;
53
54 var expectedConsoleMessages = [];
55 var expectLoggedMessages = null;
56
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) { }
63
64 var xmlDoc;
65
66 function test_template()
67 {
68 var root = document.getElementById("root");
69
70 var ds;
71 if (queryType == "rdf" && RDF) {
72 var ioService = Components.classes["@mozilla.org/network/io-service;1"].
73 getService(Components.interfaces.nsIIOService);
74
75 var src = window.location.href.replace(/test_tmpl.*xul/, "animals.rdf");
76 ds = RDF.GetDataSourceBlocking(src);
77
78 if (expectLoggedMessages) {
79 Components.classes["@mozilla.org/consoleservice;1"].
80 getService(Components.interfaces.nsIConsoleService).reset();
81 }
82
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 }
92
93 // open menus if necessary
94 if (needsOpen)
95 root.open = true;
96
97 if (expectLoggedMessages)
98 expectLoggedMessages();
99
100 checkResults(root, 0);
101
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 }
121
122 function iterateChanged(root, ds)
123 {
124 Components.classes["@mozilla.org/consoleservice;1"].
125 getService(Components.interfaces.nsIConsoleService).reset();
126
127 for (var c = 0; c < changes.length; c++) {
128 changes[c](ds, root);
129 checkResults(root, c + 1);
130 }
131
132 if (needsOpen)
133 root.open = false;
134 if (expectedConsoleMessages.length)
135 compareConsoleMessages();
136 SimpleTest.finish();
137 }
138
139 function checkResults(root, step)
140 {
141 var output = expectedOutput.cloneNode(true);
142 setForCurrentStep(output, step);
143
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 }
156
157 var adjtestid = testid;
158 if (step > 0)
159 adjtestid += " dynamic step " + step;
160
161 var stilltodo = ((step == 0 && notWorkingYet) || (step > 0 && notWorkingYetDynamic));
162 if (stilltodo)
163 todo(false, adjtestid);
164 else
165 ok(!error, adjtestid);
166
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 }
176
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 }
186
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 }
210
211 for (var e of todelete)
212 content.removeChild(e);
213
214 for (var child of content.children) {
215 child.removeAttribute("step");
216 setForCurrentStep(child, currentStep);
217 }
218 }
219
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";
231
232 var t;
233
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 }
240
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";
246
247 // loop through the attributes in the expected node and compare their
248 // values with the corresponding attribute on the actual node
249
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 }
266
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 }
280
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;
287
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");
290
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 }
313
314 // an error was returned, so return early
315 if (output)
316 return output;
317 }
318 }
319
320 return "";
321 }
322
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 }
335
336 for (var d = 0; d < dsourcesArr.length; d++)
337 composite.RemoveDataSource(dsourcesArr[d]);
338
339 var newds = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
340 createInstance(Components.interfaces.nsIRDFDataSource);
341
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 }
355
356 composite.AddDataSource(newds);
357 root.builder.rebuild();
358
359 return newds;
360 }
361
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");
369
370 if (tree.view)
371 treeViewToDOMInner(tree.columns, treechildren, tree.view, tree.builder, 0, 0);
372
373 return treechildren;
374 }
375
376 function treeViewToDOMInner(columns, treechildren, view, builder, start, level)
377 {
378 var end = view.rowCount;
379
380 for (var i = start; i < end; i++) {
381 if (view.getLevel(i) < level)
382 return i - 1;
383
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);
388
389 var row = document.createElement("treerow");
390 item.appendChild(row);
391
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 }
399
400 if (view.isContainer(i)) {
401 item.setAttribute("container", "true");
402 item.setAttribute("empty", view.isContainerEmpty(i) ? "true" : "false");
403
404 if (!view.isContainerEmpty(i) && view.isContainerOpen(i)) {
405 item.setAttribute("open", "true");
406
407 var innertreechildren = document.createElement("treechildren");
408 item.appendChild(innertreechildren);
409
410 i = treeViewToDOMInner(columns, innertreechildren, view, builder, i + 1, level + 1);
411 }
412 }
413 }
414
415 return i;
416 }
417
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 }
426
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 }
444
445 function copyToProfile(filename)
446 {
447 if (Cc === undefined) {
448 var Cc = Components.classes;
449 var Ci = Components.interfaces;
450 }
451
452 var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
453 .getService(Ci.mozIJSSubScriptLoader);
454 loader.loadSubScript("chrome://mochikit/content/chrome-harness.js");
455
456 var file = Cc["@mozilla.org/file/directory_service;1"]
457 .getService(Ci.nsIProperties)
458 .get("ProfD", Ci.nsIFile);
459 file.append(filename);
460
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 }
469
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