more tests from DOMTokenList
[microdatajs:microdatajs.git] / test / api.js
1 module("DOM API");
2
3 function testBoolReflection(tag, attr, prop) {
4   var elm = document.createElement(tag);
5
6   equals(typeof elm[prop], 'boolean', 'typeof .'+prop);
7
8   // reflect content attribute -> DOM property
9   equals(elm[prop], false, 'no @'+attr);
10   elm.setAttribute(attr, '');
11   equals(elm[prop], true, '@'+attr+'=""');
12   elm.setAttribute(attr, attr);
13   equals(elm[prop], true, '@'+attr+'="'+attr+'"');
14   elm.removeAttribute(attr);
15   equals(elm[prop], false, 'removing @'+attr);
16
17   // reflect DOM property -> content attribute
18   elm[prop] = true;
19   ok(elm.hasAttribute(attr), '.'+prop+'=true');
20   elm[prop] = false;
21   ok(!elm.hasAttribute(attr), '.'+prop+'=false');
22 }
23
24 function testStringReflection(elm, attr, prop, str) {
25   if (typeof elm == 'string')
26     elm = document.createElement(elm);
27
28   equals(typeof elm[prop], 'string', 'typeof .'+prop);
29
30   // reflect content attribute -> DOM property
31   equals(elm[prop], '', 'no @'+attr);
32   elm.setAttribute(attr, str);
33   equals(elm[prop], str, 'setting @'+attr);
34   elm.removeAttribute(attr);
35   equals(elm[prop], '', 'removing @'+attr);
36
37   // reflect DOM property -> content attribute
38   elm[prop] = '';
39   equals(elm.getAttribute(attr), '', 'setting .'+prop+'=""');
40   elm[prop] = str;
41   equals(elm.getAttribute(attr), str, 'setting .'+prop+'="'+str+'"');
42   //delete elm[prop];
43   //ok(!elm.hasAttribute(attr), 'deleting .'+prop);
44 }
45
46 function testItemValueReflection(tag, attr, str) {
47   var elm = document.createElement(tag);
48   elm.setAttribute('itemprop', '');
49   testStringReflection(elm, attr, 'itemValue', str);
50 }
51
52 test(".itemScope reflects @itemscope", function() {
53   testBoolReflection('span', 'itemscope', 'itemScope');
54 });
55
56 test(".itemType reflects @itemtype", function() {
57   testStringReflection('span', 'itemtype', 'itemType', 'http://example.com/vocab#thing');
58 });
59
60 test(".itemId reflects @itemid", function() {
61   testStringReflection('span', 'itemid', 'itemId', 'http://example.com/item');
62 });
63
64 function verifyTokenList(list, value, allItems, notContainItems) {
65   equals(list.value, value, 'token list .value'+value);
66   equals(list.length, allItems.length, 'token list length');
67   for (var i=0; i < list.length && i < allItems.length; i++) {
68     equals(list.item(i), allItems[i], 'token list .item() getter');
69     equals(list[i], allItems[i], 'token list [] getter');
70     ok(list.contains(allItems[i]), 'token list contains '+allItems[i]);
71   }
72   for (i=0; i<notContainItems.length; i++) {
73     ok(!list.contains(notContainItems[i]), 'token list does not contain '+notContainItems[i]);
74   }
75 }
76
77 test(".itemProp reflects @itemprop (DOMSettableTokenList)", 
78 function() {
79   // content attribute -> DOMSettableTokenList
80   var elm = document.createElement('div');
81   var list = elm.itemProp;
82   verifyTokenList(list, '', [], ['a', 'b']);
83
84   elm.setAttribute('itemprop', ' ');
85   verifyTokenList(list, ' ', [], ['a', 'b']);
86
87   elm.setAttribute('itemprop', ' a');
88   verifyTokenList(list, ' a', ['a'], ['b']);
89
90   elm.setAttribute('itemprop', 'a  b ');
91   verifyTokenList(list, 'a  b ', ['a', 'b'], []);
92
93   elm.setAttribute('itemprop', 'a  b b');
94   verifyTokenList(list, 'a  b b', ['a', 'b', 'b'], []);
95
96   elm.removeAttribute('itemprop');
97   verifyTokenList(list, '', [], ['a', 'b']);
98
99   // DOMSettableTokenList.add()
100   function testAdd(before, token, after) {
101       list.value = before;
102       list.add(token);
103       equals(list.value, after, '"'+before+'" add("'+token+'") -> "'+after+'"');
104   }
105   testAdd('', 'a', 'a');
106   testAdd('a', 'a', 'a');
107   testAdd(' a', 'a', ' a');
108   testAdd('a ', 'a', 'a ');
109   testAdd(' a ', 'a', ' a ');
110   testAdd('a', 'b', 'a b');
111   testAdd(' a', 'b', ' a b');
112   testAdd('  a', 'b', '  a b');
113   testAdd('a ', 'b', 'a b');
114   testAdd('a  ', 'b', 'a  b');
115   testAdd(' a ', 'b', ' a b');
116   testAdd('  a  ', 'b', '  a  b');
117   testAdd('a a', 'b', 'a a b');
118   testAdd(' a a', 'b', ' a a b');
119   testAdd('a a ', 'b', 'a a b');
120   testAdd(' a a ', 'b', ' a a b');
121   testAdd('a  a', 'b', 'a  a b');
122   testAdd('  a  a', 'b', '  a  a b');
123   testAdd('a  a  ', 'b', 'a  a  b');
124   testAdd('  a  a  ', 'b', '  a  a  b');
125
126   // DOMSettableTokenList.remove()
127   function testRemove(before, token, after) {
128       list.value = before;
129       list.remove(token);
130       equals(list.value, after, '"'+before+'" remove("'+token+'") -> "'+after+'"');
131   }
132   testRemove('a', 'a', '');
133   testRemove('a ', 'a', '');
134   testRemove(' a', 'a', '');
135   testRemove(' a ', 'a', '');
136   testRemove('a a ', 'a', '');
137   testRemove(' a a ', 'a', '');
138   testRemove('a a  ', 'a', '');
139   testRemove(' a a  ', 'a', '');
140   testRemove('a b', 'a', 'b');
141   testRemove(' a b ', 'a', 'b ');
142   testRemove('b a', 'a', 'b');
143   testRemove(' b a', 'a', ' b');
144   testRemove('a b a', 'a', 'b');
145   testRemove(' a b a ', 'a', 'b');
146   testRemove('a b a', 'b', 'a a');
147   testRemove(' a b a ', 'b', ' a a ');
148   testRemove('a b  b a', 'a', 'b  b');
149   testRemove(' a b  b a ', 'a', 'b  b');
150   testRemove('a b  b a', 'b', 'a a');
151   testRemove(' a b  b a ', 'b', ' a a ');
152   testRemove('a b a b', 'a', 'b b');
153   testRemove(' a b a b ', 'a', 'b b ');
154   testRemove('a b a b', 'b', 'a a');
155   testRemove(' a b a b ', 'b', ' a a');
156
157   // DOMSettableTokenList.toggle
158   function testToggle(before, token, after, retval) {
159       list.value = before;
160       var ret = list.toggle(token);
161       equals(list.value, after, '"'+before+'" toggle("'+token+'") -> "'+after+'"');
162       equals(ret, retval);
163   }
164   testToggle('', 'a', 'a', true);
165   testToggle('a', 'a', '', false);
166 });
167
168 var throwMethods = ['contains', 'add', 'remove', 'toggle'];
169
170 test("DOMSettableTokenList access with empty token throws SYNTAX_ERR", function() {
171   for (var i=0; i<throwMethods.length; i++) {
172     var elm = document.createElement('div');
173     try {
174       elm.itemProp[throwMethods[i]]('');
175     } catch (e) {
176       equals(e.code, 12, throwMethods[i]);
177     }
178   }
179   expect(throwMethods.length);
180 });
181
182 test("DOMSettableTokenList access with whitespace token throws INVALID_CHARACTER_ERR", function() {
183   for (var i=0; i<throwMethods.length; i++) {
184     var elm = document.createElement('div');
185     try {
186       elm.itemProp[throwMethods[i]](' ');
187     } catch (e) {
188       equals(e.code, 5, throwMethods[i]);
189     }
190   }
191   expect(throwMethods.length);
192 });
193
194 test(".itemRef reflects @itemref", function() {
195   testStringReflection('span', 'itemref', 'itemRef', 'id1 id2');
196 });
197
198 test(".itemValue without @itemprop", function() {
199   var elm = document.createElement('div');
200   ok(elm.itemValue === null, ".itemValue is null");
201   try {
202       elm.itemValue = '';
203   } catch (e) {
204       equals(e.code, 15, 'setting .itemValue throws INVALID_ACCESS_ERR');
205   }
206   expect(2);
207 });
208
209 test(".itemValue with @itemprop and @itemscope", function() {
210   var elm = document.createElement('div');
211   elm.setAttribute('itemprop', 'foo');
212   elm.setAttribute('itemscope', '');
213   equals(elm.itemValue, elm, ".itemValue is the element itself");
214   try {
215       elm.itemValue = '';
216   } catch (e) {
217       equals(e.code, 15, 'setting .itemValue throws INVALID_ACCESS_ERR');
218   }
219   expect(2);
220 });
221
222 test("meta .itemValue reflects @content", function() {
223   testItemValueReflection('meta', 'content', 'Semantic Thing');
224 });
225
226 test("audio .itemValue reflects @src", function() {
227   testItemValueReflection('audio', 'src', 'http://example.com/');
228 });
229
230 test("embed .itemValue reflects @src", function() {
231   testItemValueReflection('embed', 'src', 'http://example.com/');
232 });
233
234 test("iframe .itemValue reflects @src", function() {
235   testItemValueReflection('iframe', 'src', 'http://example.com/');
236 });
237
238 test("img .itemValue reflects @src", function() {
239   testItemValueReflection('img', 'src', 'http://example.com/');
240 });
241
242 test("source .itemValue reflects @src", function() {
243   testItemValueReflection('source', 'src', 'http://example.com/');
244 });
245
246 test("video .itemValue reflects @src", function() {
247   testItemValueReflection('video', 'src', 'http://example.com/');
248 });
249
250 test("a .itemValue reflects @href", function() {
251   testItemValueReflection('a', 'href', 'http://example.com/');
252 });
253
254 test("area .itemValue reflects @href", function() {
255   testItemValueReflection('area', 'href', 'http://example.com/');
256 });
257
258 test("link .itemValue reflects @href", function() {
259   testItemValueReflection('link', 'href', 'http://example.com/');
260 });
261
262 test("object .itemValue reflects @data", function() {
263   testItemValueReflection('object', 'data', 'http://example.com/');
264 });
265
266 test("time .itemValue reflection depends on @datetime", function() {
267   var elm = document.createElement('time');
268   elm.itemProp = 'testDate';
269   elm.itemValue = 'January 1970';
270   equals(elm.textContent, 'January 1970', '.itemValue -> textContent');
271   elm.textContent = 'September 1984';
272   equals(elm.itemValue, 'September 1984', 'textContent -> .itemValue');
273   elm.setAttribute('datetime', '1970-01-01');
274   equals(elm.itemValue, '1970-01-01', '@datetime -> .itemValue');
275   elm.itemValue = '1984-09-03';
276   equals(elm.getAttribute('datetime'), '1984-09-03', '.itemValue -> @datetime');
277 });
278
279 test("div .itemValue acts as .textContent", function() {
280   var elm = document.createElement('div');
281   elm.setAttribute('itemprop', '');
282   equals(elm.itemValue, '', 'no child nodes');
283   elm.appendChild(document.createTextNode('Semantic Thing'));
284   equals(elm.itemValue, 'Semantic Thing', 'text node');
285   var b = document.createElement('b');
286   b.appendChild(document.createTextNode(' 2'));
287   elm.appendChild(b);
288   equals(elm.itemValue, 'Semantic Thing 2', 'text node + element node');
289   elm.itemValue = 'Thing Semantic';
290   equals(elm.childNodes.length, 1, 'setting .itemValue');
291   equals(elm.firstChild.nodeValue, 'Thing Semantic', 'setting .itemValue');
292 });
293
294 function verifyItems(actual, expected, message) {
295   equals(actual.length, expected.length, message+'.length');
296   for (var i=0; i < actual.length && i < expected.length; i++) {
297     equals(actual.item(i), expected[i], message+'.item('+i+')');
298     //equals(actual(i), expected[i], message+'('+i+')');
299     equals(actual[i], expected[i], message+'['+i+']');
300   }
301 }
302
303 test("document.getItems()", function() {
304   var items = document.getItems();
305
306   //ok(items instanceof NodeList, "returns NodeList");
307   verifyItems(items, [], "items");
308
309   // add an item
310   var parent = document.getElementById("parent");
311   var item = document.createElement('div');
312   item.setAttribute('itemscope', '');
313   parent.appendChild(item);
314   verifyItems(items, [item], "items");
315
316   // element with @itemprop is not top-level item
317   item.setAttribute('itemprop', '');
318   verifyItems(items, [], "items");
319
320   // removing @itemprop makes if top-level again
321   item.removeAttribute('itemprop');
322   verifyItems(items, [item], "items");
323
324   // @itemtype is ignored
325   item.setAttribute('itemtype', 'http://example.com/#type');
326   verifyItems(items, [item], "items");
327
328   // nested top-level items work
329   var item2 = document.createElement('div');
330   item2.setAttribute('itemscope', '');
331   item.appendChild(item2);
332   verifyItems(items, [item, item2], "items");
333
334   // sibling top-level items
335   parent.appendChild(item2);
336   verifyItems(items, [item, item2], "items");
337
338   // cleanup
339   parent.innerHTML = "";
340   verifyItems(items, [], "items");
341 });
342
343 test("document.getItems(types)", function() {
344   var catType = "http://example.org/animals#cat";
345   var dogType = "http://example.org/animals#dog";
346
347   var cats = document.getItems(catType);
348   var dogs = document.getItems(dogType);
349   var catsAndDogs = document.getItems(catType+' '+dogType);
350   var all = document.getItems(' ');
351   verifyItems(cats, [], "cats");
352   verifyItems(dogs, [], "dogs");
353   verifyItems(catsAndDogs, [], "catsAndDogs");
354   verifyItems(all, [], "all");
355
356   // item without type is not matched
357   var parent = document.getElementById("parent");
358   var item = document.createElement("div");
359   item.setAttribute('itemscope', '');
360   parent.appendChild(item);
361   verifyItems(cats, [], "cats");
362   verifyItems(dogs, [], "dogs");
363   verifyItems(catsAndDogs, [], "catsAndDogs");
364   verifyItems(all, [item], "all");
365
366   // adding cat item
367   var catItem = document.createElement('div');
368   catItem.setAttribute('itemscope', '');
369   catItem.setAttribute('itemtype', catType);
370   parent.appendChild(catItem);
371   verifyItems(cats, [catItem], "cats");
372   verifyItems(dogs, [], "dogs");
373   verifyItems(catsAndDogs, [catItem], "catsAndDogs");
374   verifyItems(all, [item, catItem], "all");
375
376   // adding dog item
377   var dogItem = document.createElement('div');
378   dogItem.setAttribute('itemscope', '');
379   dogItem.setAttribute('itemtype', dogType);
380   parent.appendChild(dogItem);
381   verifyItems(cats, [catItem], "cats");
382   verifyItems(dogs, [dogItem], "dogs");
383   verifyItems(catsAndDogs, [catItem, dogItem], "catsAndDogs");
384   verifyItems(all, [item, catItem, dogItem], "all");
385
386   // removing cat item
387   parent.removeChild(catItem);
388   verifyItems(cats, [], "cats");
389   verifyItems(dogs, [dogItem], "dogs");
390   verifyItems(catsAndDogs, [dogItem], "catsAndDogs");
391   verifyItems(all, [item, dogItem], "all");
392
393   // whitespace in itemtype is respected
394   dogItem.setAttribute('itemtype', dogType + ' ');
395   verifyItems(cats, [], "cats");
396   verifyItems(dogs, [], "dogs");
397   verifyItems(catsAndDogs, [], "catsAndDogs");
398   verifyItems(all, [item, dogItem], "all");
399
400   // case of itemtype is respected
401   dogItem.setAttribute('itemtype', dogType.toUpperCase());
402   verifyItems(cats, [], "cats");
403   verifyItems(dogs, [], "dogs");
404   verifyItems(catsAndDogs, [], "catsAndDogs");
405   verifyItems(all, [item, dogItem], "all");
406
407   parent.innerHTML = '';
408 });
409
410 function verifyValues(actual, expected, message) {
411   equals(actual.length, expected.length, message+'.length');
412   for (var i=0; i < actual.length && i < expected.length; i++) {
413     equals(actual[i], expected[i], message+'['+i+']');
414   }
415 }
416
417 function verifyNames(actual, expected, message) {
418   verifyValues(actual, expected, message);
419 }
420
421 function verifyNamedItems(actual, props, values, message) {
422   verifyItems(actual, props, message);
423   verifyValues(actual.values, values, message+'.values');
424 }
425
426 test("HTMLElement.properties", function() {
427   var parent = document.getElementById("parent");
428   var item = document.createElement('div');
429   parent.appendChild(item);
430
431   var props = item.properties;
432   var names = props.names;
433   var propsA = props.namedItem('propA');
434   var propsB = props.namedItem('propB');
435   var propsX = props.namedItem('propX');
436
437   equals(typeof props, 'object', '.properties is an object');
438   //ok(props instanceof HTMLPropertiesCollection, '.properties instanceof HTMLPropertiesCollection');
439   equals(typeof props.length, 'number', '.properties.length is a number');
440   equals(typeof props.names, 'object', '.properties.names is an object');
441   //ok(props instanceof DOMStringList, '.properties.names instanceof DOMStringList');
442   equals(typeof props.item, 'function', '.properties.item is a function');
443   equals(typeof props.namedItem, 'function', '.properties.namedItem is a function');
444
445   // non-item matches nothing
446   verifyItems(props, [], "props");
447   verifyNames(names, [], "names");
448   verifyNamedItems(propsA, [], [], "propsA");
449   verifyNamedItems(propsB, [], [], "propsB");
450   verifyNamedItems(propsX, [], [], "propsX");
451
452   // item without any properties
453   item.itemScope = true;
454   verifyItems(props, [], "props");
455   verifyNames(names, [], "names");
456   verifyNamedItems(propsA, [], [], "propsA");
457   verifyNamedItems(propsB, [], [], "propsB");
458   verifyNamedItems(propsX, [], [], "propsX");
459
460   var prop1 = document.createElement('div');
461   prop1.textContent = 'foo';
462   prop1.itemProp = 'propA';
463   item.appendChild(prop1);
464   verifyItems(props, [prop1], "props");
465   verifyNames(names, ['propA'], "names");
466   verifyNamedItems(propsA, [prop1], ['foo'], "propsA");
467   verifyNamedItems(propsB, [], [], "propsB");
468   verifyNamedItems(propsX, [], [], "propsX");
469
470   // prop2 not descendent of item
471   var prop2 = document.createElement('div');
472   prop2.textContent = 'bar';
473   prop2.itemProp = 'propB';
474   parent.appendChild(prop2);
475   verifyItems(props, [prop1], "props");
476   verifyNames(names, ['propA'], "names");
477   verifyNamedItems(propsA, [prop1], ['foo'], "propsA");
478   verifyNamedItems(propsB, [], [], "propsB");
479   verifyNamedItems(propsX, [], [], "propsX");
480
481   // include prop2 via itemref
482   prop2.id = 'id2';
483   item.itemRef = 'id2';
484   verifyItems(props, [prop1, prop2], "props");
485   verifyNames(names, ['propA', 'propB'], "names");
486   verifyNamedItems(propsA, [prop1], ['foo'], "propsA");
487   verifyNamedItems(propsB, [prop2], ['bar'], "propsB");
488   verifyNamedItems(propsX, [], [], "propsX");
489
490   // redundant itemref
491   item.itemRef += ' id2';
492   verifyItems(props, [prop1, prop2], "props");
493   verifyNames(names, ['propA', 'propB'], "names");
494   verifyNamedItems(propsA, [prop1], ['foo'], "propsA");
495   verifyNamedItems(propsB, [prop2], ['bar'], "propsB");
496   verifyNamedItems(propsX, [], [], "propsX");
497
498   // nested property in itemref'd tree
499   var prop3 = document.createElement('div');
500   prop3.textContent = 'baz';
501   prop3.id ='id3';
502   prop3.itemProp = 'propC';
503   prop2.appendChild(prop3);
504   item.itemRef += ' id3';
505   verifyItems(props, [prop1, prop2, prop3], "props");
506   verifyNames(names, ['propA', 'propB', 'propC'], "names");
507   verifyNamedItems(propsA, [prop1], ['foo'], "propsA");
508   verifyNamedItems(propsB, [prop2], ['barbaz'], "propsB");
509   verifyNamedItems(propsX, [], [], "propsX");
510
511   // children of itemscope'd candidate element not included even if
512   // itemref'd
513   var prop4 = document.createElement('div');
514   prop4.id = 'id4';
515   prop4.itemProp = 'propA';
516   prop4.itemScope = true;
517   item.appendChild(prop4);
518   var prop5 = document.createElement('div');
519   prop5.id = 'id5';
520   prop5.itemProp = 'propB';
521   prop4.appendChild(prop5);
522   item.itemRef += ' id5';
523   verifyItems(props, [prop1, prop4, prop2, prop3], "props");
524   verifyNames(names, ['propA', 'propB', 'propC'], "names");
525   verifyNamedItems(propsA, [prop1, prop4], ['foo', prop4], "propsA");
526   verifyNamedItems(propsB, [prop2], ['barbaz'], "propsB");
527   verifyNamedItems(propsX, [], [], "propsX");
528
529   // destroy
530   parent.innerHTML = '';
531   verifyItems(props, [], "props");
532   verifyNames(names, [], "names");
533   verifyNamedItems(propsA, [], [], "propsA");
534   verifyNamedItems(propsB, [], [], "propsB");
535   verifyNamedItems(propsX, [], [], "propsX");
536 });