1 |
|
2 |
|
3 |
|
4 |
|
5 | var DomUtils = require("htmlparser2").DomUtils,
|
6 | helper = require("../tools/helper.js"),
|
7 | assert = require("assert"),
|
8 | path = require("path"),
|
9 | document = helper.getDocument(path.join(__dirname, "test.html")),
|
10 | CSSselect = helper.CSSselect;
|
11 |
|
12 |
|
13 | function getById(element){
|
14 | if(arguments.length === 1){
|
15 | if(typeof element === "string"){
|
16 | return DomUtils.getElementById(element, document);
|
17 | }
|
18 | return element;
|
19 | }
|
20 | else return Array.prototype.map.call(arguments, function(elem){
|
21 | return getById(elem);
|
22 | });
|
23 | }
|
24 |
|
25 |
|
26 | var select = function(query, doc){
|
27 | if(arguments.length === 1 || typeof doc === "undefined") doc = document;
|
28 | else if(typeof doc === "string") doc = select(doc);
|
29 | return CSSselect(query, doc);
|
30 | }, match = CSSselect.is;
|
31 |
|
32 | var validators = {
|
33 | assert: assert,
|
34 | assertEqual: assert.equal,
|
35 | assertEquivalent: assert.deepEqual,
|
36 | refute: function refute(a, msg){
|
37 | assert(!a, msg);
|
38 | },
|
39 | assertThrowsException: function(){}
|
40 | };
|
41 |
|
42 | var runner = {
|
43 | __name: "",
|
44 | addGroup: function(name){
|
45 | this.__name = name;
|
46 | return this;
|
47 | },
|
48 | addTests: function(_, tests){
|
49 | if(this.__name){
|
50 | describe(this.__name, run);
|
51 | this.__name = "";
|
52 | } else run();
|
53 |
|
54 | function run(){
|
55 | Object.keys(tests).forEach(function(name){
|
56 | it(name, function(){
|
57 | tests[name].call(validators);
|
58 | });
|
59 | });
|
60 | }
|
61 | }
|
62 | }
|
63 |
|
64 | var RUN_BENCHMARKS = false;
|
65 |
|
66 | (function(runner){
|
67 | runner.addGroup("Basic Selectors").addTests(null, {
|
68 | "*": function(){
|
69 |
|
70 | var results = [], nodes = document.getElementsByTagName("*"), index = 0, length = nodes.length, node;
|
71 |
|
72 | for(; index < length; index++){
|
73 | if((node = nodes[index]).tagName !== "!"){
|
74 | results[results.length] = node;
|
75 | }
|
76 | }
|
77 | this.assertEquivalent(select("*"), results, "Comment nodes should be ignored.");
|
78 | },
|
79 | "E": function(){
|
80 |
|
81 | var results = [], index = 0, nodes = document.getElementsByTagName("li");
|
82 | while((results[index] = nodes[index++])){}
|
83 | results.length--;
|
84 |
|
85 | this.assertEqual(select("strong", getById("fixtures"))[0], getById("strong"));
|
86 | this.assertEquivalent(select("nonexistent"), []);
|
87 | },
|
88 | "#id": function(){
|
89 |
|
90 | this.assertEqual(select("#fixtures")[0], getById("fixtures"));
|
91 | this.assertEquivalent(select("nonexistent"), []);
|
92 | this.assertEqual(select("#troubleForm")[0], getById("troubleForm"));
|
93 | },
|
94 | ".class": function(){
|
95 |
|
96 | this.assertEquivalent(select(".first"), getById('p', 'link_1', 'item_1'));
|
97 | this.assertEquivalent(select(".second"), []);
|
98 | },
|
99 | "E#id": function(){
|
100 | this.assertEqual(select("strong#strong")[0], getById("strong"));
|
101 | this.assertEquivalent(select("p#strong"), []);
|
102 | },
|
103 | "E.class": function(){
|
104 | var secondLink = getById("link_2");
|
105 | this.assertEquivalent(select('a.internal'), getById('link_1', 'link_2'));
|
106 | this.assertEqual(select('a.internal.highlight')[0], secondLink);
|
107 | this.assertEqual(select('a.highlight.internal')[0], secondLink);
|
108 | this.assertEquivalent(select('a.highlight.internal.nonexistent'), []);
|
109 | },
|
110 | "#id.class": function(){
|
111 | var secondLink = getById('link_2');
|
112 | this.assertEqual(select('#link_2.internal')[0], secondLink);
|
113 | this.assertEqual(select('.internal#link_2')[0], secondLink);
|
114 | this.assertEqual(select('#link_2.internal.highlight')[0], secondLink);
|
115 | this.assertEquivalent(select('#link_2.internal.nonexistent'), []);
|
116 | },
|
117 | "E#id.class": function(){
|
118 | var secondLink = getById('link_2');
|
119 | this.assertEqual(select('a#link_2.internal')[0], secondLink);
|
120 | this.assertEqual(select('a.internal#link_2')[0], secondLink);
|
121 | this.assertEqual(select('li#item_1.first')[0], getById("item_1"));
|
122 | this.assertEquivalent(select('li#item_1.nonexistent'), []);
|
123 | this.assertEquivalent(select('li#item_1.first.nonexistent'), []);
|
124 | }
|
125 | });
|
126 |
|
127 | runner.addGroup("Attribute Selectors").addTests(null, {
|
128 | "[foo]": function(){
|
129 | this.assertEquivalent(select('[href]', document.body), select('a[href]', document.body));
|
130 | this.assertEquivalent(select('[class~=internal]'), select('a[class~="internal"]'));
|
131 | this.assertEquivalent(select('[id]'), select('*[id]'));
|
132 | this.assertEquivalent(select('[type=radio]'), getById('checked_radio', 'unchecked_radio'));
|
133 | this.assertEquivalent(select('[type=checkbox]'), select('*[type=checkbox]'));
|
134 | this.assertEquivalent(select('[title]'), getById('with_title', 'commaParent'));
|
135 | this.assertEquivalent(select('#troubleForm [type=radio]'), select('#troubleForm *[type=radio]'));
|
136 | this.assertEquivalent(select('#troubleForm [type]'), select('#troubleForm *[type]'));
|
137 | },
|
138 | "E[foo]": function(){
|
139 | this.assertEquivalent(select('h1[class]'), select('#fixtures h1'), "h1[class]");
|
140 | this.assertEquivalent(select('h1[CLASS]'), select('#fixtures h1'), "h1[CLASS]");
|
141 | this.assertEqual(select('li#item_3[class]')[0], getById('item_3'), "li#item_3[class]");
|
142 | this.assertEquivalent(select('#troubleForm2 input[name="brackets[5][]"]'), getById('chk_1', 'chk_2'));
|
143 |
|
144 | this.assertEqual(select('#troubleForm2 input[name="brackets[5][]"]:checked')[0], getById('chk_1'));
|
145 |
|
146 | this.assertEqual(select('cite[title="hello world!"]')[0], getById('with_title'));
|
147 |
|
148 |
|
149 |
|
150 | },
|
151 | 'E[foo="bar"]': function(){
|
152 | this.assertEquivalent(select('a[href="#"]'), getById('link_1', 'link_2', 'link_3'));
|
153 | this.assertThrowsException(/Error/, function(){
|
154 | select('a[href=#]');
|
155 | });
|
156 | this.assertEqual(select('#troubleForm2 input[name="brackets[5][]"][value="2"]')[0], getById('chk_2'));
|
157 | },
|
158 | 'E[foo~="bar"]': function(){
|
159 | this.assertEquivalent(select('a[class~="internal"]'), getById('link_1', 'link_2'), "a[class~=\"internal\"]");
|
160 | this.assertEquivalent(select('a[class~=internal]'), getById('link_1', 'link_2'), "a[class~=internal]");
|
161 | this.assertEqual(select('a[class~=external][href="#"]')[0], getById('link_3'), 'a[class~=external][href="#"]');
|
162 | }, |
163 |
|
164 |
|
165 |
|
166 |
|
167 | 'E[foo^="bar"]': function(){
|
168 | this.assertEquivalent(select('div[class^=bro]'), getById('father', 'uncle'), 'matching beginning of string');
|
169 | this.assertEquivalent(select('#level1 *[id^="level2_"]'), getById('level2_1', 'level2_2', 'level2_3'));
|
170 | this.assertEquivalent(select('#level1 *[id^=level2_]'), getById('level2_1', 'level2_2', 'level2_3'));
|
171 | if(RUN_BENCHMARKS){
|
172 | this.wait(function(){
|
173 | this.benchmark(function(){
|
174 | select('#level1 *[id^=level2_]');
|
175 | }, 1000);
|
176 | }, 500);
|
177 | }
|
178 | },
|
179 | 'E[foo$="bar"]': function(){
|
180 | this.assertEquivalent(select('div[class$=men]'), getById('father', 'uncle'), 'matching end of string');
|
181 | this.assertEquivalent(select('#level1 *[id$="_1"]'), getById('level2_1', 'level3_1'));
|
182 | this.assertEquivalent(select('#level1 *[id$=_1]'), getById('level2_1', 'level3_1'));
|
183 | if(RUN_BENCHMARKS){
|
184 | this.wait(function(){
|
185 | this.benchmark(function(){
|
186 | select('#level1 *[id$=_1]');
|
187 | }, 1000);
|
188 | }, 500);
|
189 | }
|
190 | },
|
191 | 'E[foo*="bar"]': function(){
|
192 | this.assertEquivalent(select('div[class*="ers m"]'), getById('father', 'uncle'), 'matching substring');
|
193 | this.assertEquivalent(select('#level1 *[id*="2"]'), getById('level2_1', 'level3_2', 'level2_2', 'level2_3'));
|
194 | this.assertThrowsException(/Error/, function(){
|
195 | select('#level1 *[id*=2]');
|
196 | });
|
197 | if(RUN_BENCHMARKS){
|
198 | this.wait(function(){
|
199 | this.benchmark(function(){
|
200 | select('#level1 *[id*=2]');
|
201 | }, 1000);
|
202 | }, 500);
|
203 | }
|
204 | },
|
205 |
|
206 |
|
207 |
|
208 | 'E[id=-1]': function(){
|
209 | this.assertThrowsException(/Error/, function(){
|
210 | select('#level1 *[id=-1]');
|
211 | });
|
212 | if(RUN_BENCHMARKS){
|
213 | this.wait(function(){
|
214 | this.benchmark(function(){
|
215 | select('#level1 *[id=9]');
|
216 | }, 1000);
|
217 | }, 500);
|
218 | }
|
219 | },
|
220 | 'E[class=-45deg]': function(){
|
221 | this.assertThrowsException(/Error/, function(){
|
222 | select('#level1 *[class=-45deg]');
|
223 | });
|
224 | if(RUN_BENCHMARKS){
|
225 | this.wait(function(){
|
226 | this.benchmark(function(){
|
227 | select('#level1 *[class=-45deg]');
|
228 | }, 1000);
|
229 | }, 500);
|
230 | }
|
231 | },
|
232 | 'E[class=8mm]': function(){
|
233 | this.assertThrowsException(/Error/, function(){
|
234 | select('#level1 *[class=8mm]');
|
235 | });
|
236 | if(RUN_BENCHMARKS){
|
237 | this.wait(function(){
|
238 | this.benchmark(function(){
|
239 | select('#level1 *[class=8mm]');
|
240 | }, 1000);
|
241 | }, 500);
|
242 | }
|
243 | }
|
244 |
|
245 | });
|
246 |
|
247 | runner.addGroup("Structural pseudo-classes").addTests(null, {
|
248 | "E:first-child": function(){
|
249 | this.assertEqual(select('#level1>*:first-child')[0], getById('level2_1'));
|
250 | this.assertEquivalent(select('#level1 *:first-child'), getById('level2_1', 'level3_1', 'level_only_child'));
|
251 | this.assertEquivalent(select('#level1>div:first-child'), []);
|
252 | this.assertEquivalent(select('#level1 span:first-child'), getById('level2_1', 'level3_1'));
|
253 | this.assertEquivalent(select('#level1:first-child'), []);
|
254 | if(RUN_BENCHMARKS){
|
255 | this.wait(function(){
|
256 | this.benchmark(function(){
|
257 | select('#level1 *:first-child');
|
258 | }, 1000);
|
259 | }, 500);
|
260 | }
|
261 | },
|
262 | "E:last-child": function(){
|
263 | this.assertEqual(select('#level1>*:last-child')[0], getById('level2_3'));
|
264 | this.assertEquivalent(select('#level1 *:last-child'), getById('level3_2', 'level_only_child', 'level2_3'));
|
265 | this.assertEqual(select('#level1>div:last-child')[0], getById('level2_3'));
|
266 | this.assertEqual(select('#level1 div:last-child')[0], getById('level2_3'));
|
267 | this.assertEquivalent(select('#level1>span:last-child'), []);
|
268 | if(RUN_BENCHMARKS){
|
269 | this.wait(function(){
|
270 | this.benchmark(function(){
|
271 | select('#level1 *:last-child');
|
272 | }, 1000);
|
273 | }, 500);
|
274 | }
|
275 | },
|
276 | "E:nth-child(n)": function(){
|
277 | this.assertEqual(select('#p *:nth-child(3)')[0], getById('link_2'));
|
278 | this.assertEqual(select('#p a:nth-child(3)')[0], getById('link_2'), 'nth-child');
|
279 | this.assertEquivalent(select('#list > li:nth-child(n+2)'), getById('item_2', 'item_3'));
|
280 | this.assertEquivalent(select('#list > li:nth-child(-n+2)'), getById('item_1', 'item_2'));
|
281 | },
|
282 | "E:nth-of-type(n)": function(){
|
283 | this.assertEqual(select('#p a:nth-of-type(2)')[0], getById('link_2'), 'nth-of-type');
|
284 | this.assertEqual(select('#p a:nth-of-type(1)')[0], getById('link_1'), 'nth-of-type');
|
285 | },
|
286 | "E:nth-last-of-type(n)": function(){
|
287 | this.assertEqual(select('#p a:nth-last-of-type(1)')[0], getById('link_2'), 'nth-last-of-type');
|
288 | },
|
289 | "E:first-of-type": function(){
|
290 | this.assertEqual(select('#p a:first-of-type')[0], getById('link_1'), 'first-of-type');
|
291 | },
|
292 | "E:last-of-type": function(){
|
293 | this.assertEqual(select('#p a:last-of-type')[0], getById('link_2'), 'last-of-type');
|
294 | },
|
295 | "E:only-child": function(){
|
296 | this.assertEqual(select('#level1 *:only-child')[0], getById('level_only_child'));
|
297 |
|
298 | this.assertEquivalent(select('#level1>*:only-child'), []);
|
299 | this.assertEquivalent(select('#level1:only-child'), []);
|
300 | this.assertEquivalent(select('#level2_2 :only-child:not(:last-child)'), []);
|
301 | this.assertEquivalent(select('#level2_2 :only-child:not(:first-child)'), []);
|
302 | if(RUN_BENCHMARKS){
|
303 | this.wait(function(){
|
304 | this.benchmark(function(){
|
305 | select('#level1 *:only-child');
|
306 | }, 1000);
|
307 | }, 500);
|
308 | }
|
309 | },
|
310 | "E:empty": function(){
|
311 | getById('level3_1').children = [];
|
312 | if(document.createEvent){
|
313 | this.assertEquivalent(select('#level1 *:empty'), getById('level3_1', 'level3_2', 'level2_3'), '#level1 *:empty');
|
314 | this.assertEquivalent(select('#level_only_child:empty'), [], 'newlines count as content!');
|
315 | }else{
|
316 | this.assertEqual(select('#level3_1:empty')[0], getById('level3_1'), 'IE forced empty content!');
|
317 |
|
318 | }
|
319 |
|
320 | this.assertEquivalent(select('span:empty > *'), []);
|
321 | }
|
322 | });
|
323 |
|
324 | runner.addTests(null, {
|
325 | "E:not(s)": function(){
|
326 |
|
327 | this.assertEquivalent(select('a:not([href="#"])'), []);
|
328 | this.assertEquivalent(select('div.brothers:not(.brothers)'), []);
|
329 | this.assertEquivalent(select('a[class~=external]:not([href="#"])'), [], 'a[class~=external][href!="#"]');
|
330 | this.assertEqual(select('#p a:not(:first-of-type)')[0], getById('link_2'), 'first-of-type');
|
331 | this.assertEqual(select('#p a:not(:last-of-type)')[0], getById('link_1'), 'last-of-type');
|
332 | this.assertEqual(select('#p a:not(:nth-of-type(1))')[0], getById('link_2'), 'nth-of-type');
|
333 | this.assertEqual(select('#p a:not(:nth-last-of-type(1))')[0], getById('link_1'), 'nth-last-of-type');
|
334 | this.assertEqual(select('#p a:not([rel~=nofollow])')[0], getById('link_2'), 'attribute 1');
|
335 | this.assertEqual(select('#p a:not([rel^=external])')[0], getById('link_2'), 'attribute 2');
|
336 | this.assertEqual(select('#p a:not([rel$=nofollow])')[0], getById('link_2'), 'attribute 3');
|
337 | this.assertEqual(select('#p a:not([rel$="nofollow"]) > em')[0], getById('em'), 'attribute 4');
|
338 | this.assertEqual(select('#list li:not(#item_1):not(#item_3)')[0], getById('item_2'), 'adjacent :not clauses');
|
339 | this.assertEqual(select('#grandfather > div:not(#uncle) #son')[0], getById('son'));
|
340 | this.assertEqual(select('#p a:not([rel$="nofollow"]) em')[0], getById('em'), 'attribute 4 + all descendants');
|
341 | this.assertEqual(select('#p a:not([rel$="nofollow"])>em')[0], getById('em'), 'attribute 4 (without whitespace)');
|
342 | }
|
343 | });
|
344 |
|
345 | runner.addGroup("UI element states pseudo-classes").addTests(null, {
|
346 | "E:disabled": function(){
|
347 | this.assertEqual(select('#troubleForm > p > *:disabled')[0], getById('disabled_text_field'));
|
348 | },
|
349 | "E:checked": function(){
|
350 | this.assertEquivalent(select('#troubleForm *:checked'), getById('checked_box', 'checked_radio'));
|
351 | }
|
352 | });
|
353 |
|
354 | runner.addGroup("Combinators").addTests(null, {
|
355 | "E F": function(){
|
356 |
|
357 | this.assertEquivalent(select('#fixtures a *'), getById('em2', 'em', 'span'));
|
358 | this.assertEqual(select('div#fixtures p')[0], getById("p"));
|
359 | },
|
360 | "E + F": function(){
|
361 |
|
362 | this.assertEqual(select('div.brothers + div.brothers')[0], getById("uncle"));
|
363 | this.assertEqual(select('div.brothers + div')[0], getById('uncle'));
|
364 | this.assertEqual(select('#level2_1+span')[0], getById('level2_2'));
|
365 | this.assertEqual(select('#level2_1 + span')[0], getById('level2_2'));
|
366 | this.assertEqual(select('#level2_1 + *')[0], getById('level2_2'));
|
367 | this.assertEquivalent(select('#level2_2 + span'), []);
|
368 | this.assertEqual(select('#level3_1 + span')[0], getById('level3_2'));
|
369 | this.assertEqual(select('#level3_1 + *')[0], getById('level3_2'));
|
370 | this.assertEquivalent(select('#level3_2 + *'), []);
|
371 | this.assertEquivalent(select('#level3_1 + em'), []);
|
372 | if(RUN_BENCHMARKS){
|
373 | this.wait(function(){
|
374 | this.benchmark(function(){
|
375 | select('#level3_1 + span');
|
376 | }, 1000);
|
377 | }, 500);
|
378 | }
|
379 | },
|
380 | "E > F": function(){
|
381 |
|
382 | this.assertEquivalent(select('p.first > a'), getById('link_1', 'link_2'));
|
383 | this.assertEquivalent(select('div#grandfather > div'), getById('father', 'uncle'));
|
384 | this.assertEquivalent(select('#level1>span'), getById('level2_1', 'level2_2'));
|
385 | this.assertEquivalent(select('#level1 > span'), getById('level2_1', 'level2_2'));
|
386 | this.assertEquivalent(select('#level2_1 > *'), getById('level3_1', 'level3_2'));
|
387 | this.assertEquivalent(select('div > #nonexistent'), []);
|
388 | if(RUN_BENCHMARKS){
|
389 | this.wait(function(){
|
390 | this.benchmark(function(){
|
391 | select('#level1 > span');
|
392 | }, 1000);
|
393 | }, 500);
|
394 | }
|
395 | },
|
396 | "E ~ F": function(){
|
397 |
|
398 | this.assertEqual(select('h1 ~ ul')[0], getById('list'));
|
399 | this.assertEquivalent(select('#level2_2 ~ span'), []);
|
400 | this.assertEquivalent(select('#level3_2 ~ *'), []);
|
401 | this.assertEquivalent(select('#level3_1 ~ em'), []);
|
402 | this.assertEquivalent(select('div ~ #level3_2'), []);
|
403 | this.assertEquivalent(select('div ~ #level2_3'), []);
|
404 | this.assertEqual(select('#level2_1 ~ span')[0], getById('level2_2'));
|
405 | this.assertEquivalent(select('#level2_1 ~ *'), getById('level2_2', 'level2_3'));
|
406 | this.assertEqual(select('#level3_1 ~ #level3_2')[0], getById('level3_2'));
|
407 | this.assertEqual(select('span ~ #level3_2')[0], getById('level3_2'));
|
408 | if(RUN_BENCHMARKS){
|
409 | this.wait(function(){
|
410 | this.benchmark(function(){
|
411 | select('#level2_1 ~ span');
|
412 | }, 1000);
|
413 | }, 500);
|
414 | }
|
415 | }
|
416 | });
|
417 |
|
418 | runner.addTests(null, {
|
419 | "NW.Dom.match": function(){
|
420 | var element = getById('dupL1');
|
421 |
|
422 | this.assert(match(element, 'span'));
|
423 | this.assert(match(element, "span#dupL1"));
|
424 | this.assert(match(element, "div > span"), "child combinator");
|
425 | this.assert(match(element, "#dupContainer span"), "descendant combinator");
|
426 | this.assert(match(element, "#dupL1"), "ID only");
|
427 | this.assert(match(element, "span.span_foo"), "class name 1");
|
428 | this.assert(match(element, "span.span_bar"), "class name 2");
|
429 | this.assert(match(element, "span:first-child"), "first-child pseudoclass");
|
430 |
|
431 | this.refute(match(element, "span.span_wtf"), "bogus class name");
|
432 | this.refute(match(element, "#dupL2"), "different ID");
|
433 | this.refute(match(element, "div"), "different tag name");
|
434 | this.refute(match(element, "span span"), "different ancestry");
|
435 | this.refute(match(element, "span > span"), "different parent");
|
436 | this.refute(match(element, "span:nth-child(5)"), "different pseudoclass");
|
437 |
|
438 | this.refute(match(getById('link_2'), 'a[rel^=external]'));
|
439 | this.assert(match(getById('link_1'), 'a[rel^=external]'));
|
440 | this.assert(match(getById('link_1'), 'a[rel^="external"]'));
|
441 | this.assert(match(getById('link_1'), "a[rel^='external']"));
|
442 | },
|
443 | "Equivalent Selectors": function(){
|
444 | this.assertEquivalent(select('div.brothers'), select('div[class~=brothers]'));
|
445 | this.assertEquivalent(select('div.brothers'), select('div[class~=brothers].brothers'));
|
446 | this.assertEquivalent(select('div:not(.brothers)'), select('div:not([class~=brothers])'));
|
447 | this.assertEquivalent(select('li ~ li'), select('li:not(:first-child)'));
|
448 | this.assertEquivalent(select('ul > li'), select('ul > li:nth-child(n)'));
|
449 | this.assertEquivalent(select('ul > li:nth-child(even)'), select('ul > li:nth-child(2n)'));
|
450 | this.assertEquivalent(select('ul > li:nth-child(odd)'), select('ul > li:nth-child(2n+1)'));
|
451 | this.assertEquivalent(select('ul > li:first-child'), select('ul > li:nth-child(1)'));
|
452 | this.assertEquivalent(select('ul > li:last-child'), select('ul > li:nth-last-child(1)'));
|
453 | |
454 |
|
455 | this.assertEquivalent(select('ul > li:nth-child(n-128)'), select('ul > li'));
|
456 | this.assertEquivalent(select('ul>li'), select('ul > li'));
|
457 | this.assertEquivalent(select('#p a:not([rel$="nofollow"])>em'), select('#p a:not([rel$="nofollow"]) > em'));
|
458 | },
|
459 | "Multiple Selectors": function(){
|
460 |
|
461 |
|
462 |
|
463 | this.assertEquivalent(select('form[title*="commas,"], input[value="#commaOne,#commaTwo"]'), getById('commaParent', 'commaChild'));
|
464 | this.assertEquivalent(select('form[title*="commas,"], input[value="#commaOne,#commaTwo"]'), getById('commaParent', 'commaChild'));
|
465 | }
|
466 | });
|
467 | }(runner)); |
\ | No newline at end of file |