d2469ba60a7d1323ab685d56c5bf70337f376a45
[WebKit-https.git] / Websites / bugs.webkit.org / code-review-test.html
1 <div>Tests for some of the easily unittestable parts of code-review.js. You should see a series of "PASS" lines.</div>
2 <div>FIXME: Run these as part of the layout test suite?</div>
3
4 <script>
5 CODE_REVIEW_UNITTEST = true;
6
7 window.onerror = function(errorMsg, url, lineNumber) {
8   log('FAIL: Received an error at line ' + lineNumber);
9   return false;
10 }
11 </script>
12 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> 
13 <script src="code-review.js"></script>
14 <pre id="output"></pre>
15 <div id="diff-content"></div>
16 <script>
17 function inherits(childConstructor, parentConstructor) {
18   function tempConstructor() {};
19   tempConstructor.prototype = parentConstructor.prototype;
20   childConstructor.prototype = new tempConstructor();
21   childConstructor.prototype.constructor = childConstructor;
22 }
23
24 function log(msg) {
25     document.getElementById('output').textContent += msg + '\n\n';
26 }
27
28 function ASSERT(msg, assertion) {
29     if (assertion)
30         log('PASS');
31     else
32         log('FAIL: ' + msg);
33 }
34
35 function ASSERT_EQUAL(actual, expected) {
36     if (actual == expected)
37         log('PASS');
38     else
39         log('FAIL:\ngot:\n' + actual + '\nexpected:\n' + expected + '');
40 }
41
42 function testTracLinks() {
43   var links = tracLinks('foo/bar/baz.cpp', '');
44   ASSERT_EQUAL($('<div>').append(links).html(),
45     '<a target="_blank" href="http://trac.webkit.org/log/trunk/foo/bar/baz.h">header</a>' +
46     '<a target="_blank" href="http://trac.webkit.org/browser/trunk/foo/bar/baz.cpp?annotate=blame">annotate</a>' +
47     '<a target="_blank" href="http://trac.webkit.org/log/trunk/foo/bar/baz.cpp">revision log</a>');
48
49   var links = tracLinks('foo/bar.cpp/qux.mm', '');
50   ASSERT_EQUAL($('<div>').append(links).html(),
51     '<a target="_blank" href="http://trac.webkit.org/log/trunk/foo/bar.cpp/qux.h">header</a>' +
52     '<a target="_blank" href="http://trac.webkit.org/browser/trunk/foo/bar.cpp/qux.mm?annotate=blame">annotate</a>' +
53     '<a target="_blank" href="http://trac.webkit.org/log/trunk/foo/bar.cpp/qux.mm">revision log</a>');
54
55   var links = tracLinks('foo/bar.h', '');
56   ASSERT_EQUAL($('<div>').append(links).html(),
57     '<a target="_blank" href="http://trac.webkit.org/browser/trunk/foo/bar.h?annotate=blame">annotate</a>' +
58     '<a target="_blank" href="http://trac.webkit.org/log/trunk/foo/bar.h">revision log</a>');
59
60   var links = tracLinks('foo/bar.gypi', '');
61   ASSERT_EQUAL($('<div>').append(links).html(),
62     '<a target="_blank" href="http://trac.webkit.org/browser/trunk/foo/bar.gypi?annotate=blame">annotate</a>' +
63     '<a target="_blank" href="http://trac.webkit.org/log/trunk/foo/bar.gypi">revision log</a>');
64
65   var links = tracLinks('foo/ChangeLog', '');
66   ASSERT_EQUAL($('<div>').append(links).html(),
67     '<a target="_blank" href="http://trac.webkit.org/browser/trunk/foo/ChangeLog?annotate=blame">annotate</a>' +
68     '<a target="_blank" href="http://trac.webkit.org/log/trunk/foo/ChangeLog">revision log</a>');
69 }
70
71 function testDraftCommentSaver() {
72   function MockLocalStorage() {
73       this.localStorageStore = {};
74       this.log = [];
75
76       this.getItem = function(key) {
77           this.log.push('getItem:' + key);
78           return this.localStorageStore[key];
79       };
80
81       this.setItem = function(key, value) {
82           // For testing sake, consider having more than 2 items to exceed the storage quota.
83           if (Object.keys(this.localStorageStore).length > 2) {
84               this.log.push('QuotaExceeded on setItem:' + key + ',' + value);
85               throw "QuotaExceeded";
86           }
87           this.log.push('setItem:' + key + ',' + value);
88           this.localStorageStore[key] = value;
89       };
90
91       this.removeItem = function(key) {
92           this.log.push('removeItem:' + key);
93           delete this.localStorageStore[key];
94       };
95
96       this.log_string = function() {
97           return this.log.join('\n');
98       };
99
100       this.key = function(i) {
101           return Object.keys(this.localStorageStore)[i];
102       };
103
104       this.__defineGetter__('length', function() {
105           return Object.keys(this.localStorageStore).length;
106       });
107   }
108
109   function MockDraftCommentSaver(attachment_id, opt_localStorage) {
110       DraftCommentSaver.call(this, attachment_id, opt_localStorage);
111   }
112
113   inherits(MockDraftCommentSaver, DraftCommentSaver)
114
115   MockDraftCommentSaver.prototype._json = function() {
116       return "{MOCK JSON}";
117   }
118
119   MockDraftCommentSaver.prototype._should_remove_comments = function(message) {
120       return false;
121   }
122
123   // Basic setItem.
124   var ls = new MockLocalStorage();
125   new MockDraftCommentSaver('1234', ls).save();
126   ASSERT_EQUAL(ls.log_string(), 'setItem:draft-comments-for-attachment-1234,{MOCK JSON}');
127
128   // Exceed quota, but succeed after erasing old reviews.
129   var ls = new MockLocalStorage();
130   ls.localStorageStore = {
131       'draft-comments-for-attachment-1': '{"born-on": 100, "comments":[]}',
132       'draft-comments-for-attachment-2': '{"born-on": 100, "comments":[]}',
133       'draft-comments-for-attachment-3': '{"born-on": 100, "comments":[]}',
134       'draft-comments-for-attachment-4': '{"born-on": ' + (Date.now() - 100) + ', "comments":[]}'
135   };
136   new MockDraftCommentSaver('1234', ls).save();
137   ASSERT_EQUAL(ls.log_string(), 'QuotaExceeded on setItem:draft-comments-for-attachment-1234,{MOCK JSON}\ngetItem:draft-comments-for-attachment-1\ngetItem:draft-comments-for-attachment-2\ngetItem:draft-comments-for-attachment-3\ngetItem:draft-comments-for-attachment-4\nremoveItem:draft-comments-for-attachment-1\nremoveItem:draft-comments-for-attachment-2\nremoveItem:draft-comments-for-attachment-3\nsetItem:draft-comments-for-attachment-1234,{MOCK JSON}');
138
139   // Exceed quota after erasing old reviews and fail after prompt.
140   var ls = new MockLocalStorage();
141   ls.localStorageStore = {
142       'draft-comments-for-attachment-1': '{"born-on": 100, "comments":[]}',
143       'draft-comments-for-attachment-2': '{"born-on": ' + (Date.now() - 100) + ', "comments":[]}',
144       'draft-comments-for-attachment-3': '{"born-on": ' + (Date.now() - 100) + ', "comments":[]}',
145       'draft-comments-for-attachment-4': '{"born-on": ' + (Date.now() - 100) + ', "comments":[]}'
146   };
147   var mockDraftSaver = new MockDraftCommentSaver('1234', ls);
148   mockDraftSaver.save();
149   // Second save to ensure that we stop trying to save when we fail the prompt.
150   mockDraftSaver.save();
151   ASSERT_EQUAL(ls.log_string(), 'QuotaExceeded on setItem:draft-comments-for-attachment-1234,{MOCK JSON}\ngetItem:draft-comments-for-attachment-1\ngetItem:draft-comments-for-attachment-2\ngetItem:draft-comments-for-attachment-3\ngetItem:draft-comments-for-attachment-4\nremoveItem:draft-comments-for-attachment-1\nQuotaExceeded on setItem:draft-comments-for-attachment-1234,{MOCK JSON}');
152
153   // Exceed quota after erasing old reviews, but succeed after prompt.
154   var ls = new MockLocalStorage();
155   ls.localStorageStore = {
156       'draft-comments-for-attachment-1': '{"born-on": 100, "comments":[]}',
157       'draft-comments-for-attachment-2': '{"born-on": ' + (Date.now() - 100) + ', "comments":[]}',
158       'draft-comments-for-attachment-3': '{"born-on": ' + (Date.now() - 100) + ', "comments":[]}',
159       'draft-comments-for-attachment-4': '{"born-on": ' + (Date.now() - 100) + ', "comments":[]}'
160   };
161   var mockDraftSaver = new MockDraftCommentSaver('1234', ls);
162   mockDraftSaver._should_remove_comments = function() { return true; };
163   mockDraftSaver.save();
164   ASSERT_EQUAL(ls.log_string(), 'QuotaExceeded on setItem:draft-comments-for-attachment-1234,{MOCK JSON}\ngetItem:draft-comments-for-attachment-1\ngetItem:draft-comments-for-attachment-2\ngetItem:draft-comments-for-attachment-3\ngetItem:draft-comments-for-attachment-4\nremoveItem:draft-comments-for-attachment-1\nQuotaExceeded on setItem:draft-comments-for-attachment-1234,{MOCK JSON}\nremoveItem:draft-comments-for-attachment-2\nremoveItem:draft-comments-for-attachment-3\nremoveItem:draft-comments-for-attachment-4\nsetItem:draft-comments-for-attachment-1234,{MOCK JSON}');
165
166   // Always exceeds quota, even after erasing all review comments. There should be no setItem calls.
167   var ls = new MockLocalStorage();
168   ls.setItem = function() {
169       this.log.push('QuotaExceeded on setItem:' + key + ',' + value);
170       throw "QuotaExceeded"; 
171   }
172   ls.localStorageStore = {
173       'draft-comments-for-attachment-1': '{"born-on": 100, "comments":[]}',
174       'draft-comments-for-attachment-2': '{"born-on": ' + (Date.now() - 100) + ', "comments":[]}',
175       'draft-comments-for-attachment-3': '{"born-on": ' + (Date.now() - 100) + ', "comments":[]}',
176       'draft-comments-for-attachment-4': '{"born-on": ' + (Date.now() - 100) + ', "comments":[]}'
177   };
178   var mockDraftSaver = new MockDraftCommentSaver('1234', ls);
179   mockDraftSaver._should_remove_comments = function() { return true; };
180   mockDraftSaver.save();
181   // Second save to ensure that we stop trying to save when we fail the prompt.
182   mockDraftSaver.save();
183   ASSERT_EQUAL(ls.log_string(), 'getItem:draft-comments-for-attachment-1\ngetItem:draft-comments-for-attachment-2\ngetItem:draft-comments-for-attachment-3\ngetItem:draft-comments-for-attachment-4\nremoveItem:draft-comments-for-attachment-1\nremoveItem:draft-comments-for-attachment-2\nremoveItem:draft-comments-for-attachment-3\nremoveItem:draft-comments-for-attachment-4');
184
185   var ls = new MockLocalStorage();
186   ls.localStorageStore = {
187       'draft-comments-for-attachment-1': '{"born-on": 100, "comments":[]}',
188       'draft-comments-for-attachment-2': '{"born-on": 100, "comments":[{"start_line_id": 1, "end_line_id": 2, "contents": "DUMMY CONTENTS"}, {"start_line_id": 3, "end_line_id": 4, "contents": "DUMMY CONTENTS 2"}]}'
189   };
190   var comments = new MockDraftCommentSaver('2', ls).saved_comments().comments;
191   ASSERT_EQUAL(comments.length, 2);
192   ASSERT_EQUAL(ls.log_string(), 'getItem:draft-comments-for-attachment-2');
193
194   var ls = new MockLocalStorage();
195   ls.localStorageStore = {
196       'draft-comments-for-attachment-1': 'corrupt comments'
197   };
198   var comments = new MockDraftCommentSaver('1', ls).saved_comments().comments;
199   ASSERT_EQUAL(ls.log_string(), 'getItem:draft-comments-for-attachment-1\nremoveItem:draft-comments-for-attachment-1');
200
201   var ls = new MockLocalStorage();
202   ls.localStorageStore = {
203       'draft-comments-for-attachment-1': '["also corrupt comments"]'
204   };
205   var comments = new MockDraftCommentSaver('1', ls).saved_comments().comments;
206   ASSERT_EQUAL(ls.log_string(), 'getItem:draft-comments-for-attachment-1\nremoveItem:draft-comments-for-attachment-1');
207 }
208
209 function stripBornOn(json) {
210   return json.replace(/\"born\-on\"\:\d+,/, "");
211 }
212
213 function strippedSavedComments() {
214   return stripBornOn(localStorage[g_draftCommentSaver.localStorageKey()]);
215 }
216
217 function syncSlideUp(type, handler) {
218   handler.call(this);
219 }
220
221 function testReaddDiscardedCommentWithPreviousComment() {
222   document.getElementById('diff-content').innerHTML =
223       '<div class="FileDiff">' +
224         '<h1><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/ChangeLog">Source/WebCore/ChangeLog</a></h1>' +
225         '<div class="DiffSection">' +
226           '<div class="DiffBlock">' +
227             '<div class="DiffBlockPart add">' +
228               '<div class="Line LineContainer add">' +
229                 '<span class="from lineNumber">&nbsp;</span><span class="to lineNumber">1</span><span class="text">first diff block first line</span>' +
230               '</div>' +
231               '<div class="Line LineContainer add">' +
232                 '<span class="from lineNumber">&nbsp;</span><span class="to lineNumber">2</span><span class="text"></span>' +
233               '</div>' +
234             '</div>' +
235             '<div class="clear_float"></div>' +
236           '</div>' +
237           '<div class="DiffBlock">' +
238             '<div class="DiffBlockPart shared">' +
239               '<div class="Line LineContainer">' +
240                 '<span class="from lineNumber">1</span><span class="to lineNumber">12</span><span class="text">second diff block first line</span>' +
241               '</div>' +
242               '</div><div class="clear_float">' +
243             '</div>' +
244           '</div>' +
245         '</div>' +
246       '</div>';
247
248   eraseDraftComments();
249   crawlDiff();
250   appendToolbar();
251
252   var line = document.getElementById('line0');
253   var author = "ojan@chromium.org";
254   var comment_text = "This change sux.";
255   addPreviousComment(line, author, comment_text);
256   var previous_comment = document.querySelector('.previousComment');
257   ASSERT("Line with only a previous comment should not have 'data-has-comment' attribute.", !line.getAttribute('data-has-comment'));
258
259   var new_comment = addCommentField(previous_comment);
260   new_comment.find('textarea').val("dummy content");
261   var frozen_comment = acceptComment(new_comment);
262
263   ASSERT_EQUAL(document.querySelectorAll('.comment').length, 1);
264   ASSERT_EQUAL(strippedSavedComments(), '{"comments":[{"start_line_id":"line0","end_line_id":"line0","contents":"dummy content"}],"overall-comments":""}');
265
266   unfreezeComment(frozen_comment);
267   // This is a hack to make slideUp synchronous so that we can keep this test from needing to be async.
268   jQuery.fn.slideUp = syncSlideUp;
269   discardComment(new_comment);
270
271   ASSERT('There should be no draft comments.', !document.querySelector('.comment'));
272   ASSERT_EQUAL(strippedSavedComments(), '{"comments":[],"overall-comments":""}');
273   ASSERT("Line with only a previous comment should not have 'data-has-comment' attribute.", !line.getAttribute('data-has-comment'));
274
275   new_comment = addCommentField(previous_comment);
276   new_comment.find('textarea').val("dummy content");
277   acceptComment(new_comment);
278
279   ASSERT_EQUAL(document.querySelectorAll('.comment').length, 1);
280   ASSERT_EQUAL(strippedSavedComments(), '{"comments":[{"start_line_id":"line0","end_line_id":"line0","contents":"dummy content"}],"overall-comments":""}');
281
282   document.getElementById('diff-content').innerHTML = '';
283 }
284
285 function testSideBySideDiffWithPreviousCommentsOnSharedLine() {
286   document.getElementById('diff-content').innerHTML =
287       '<div class="FileDiff">' +
288         '<h1><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/ChangeLog">Source/WebCore/ChangeLog</a></h1>' +
289         '<div class="DiffSection">' +
290           '<div class="DiffBlock">' +
291             '<div class="DiffBlockPart shared">' +
292               '<div class="Line LineContainer">' +
293               '<span class="from lineNumber">336</span><span class="to lineNumber">338</span><span class="text">    layoutFlexItems(*m_orderIterator, lineContexts);</span>' +
294               '</div>' +
295               '<div class="Line LineContainer">' +
296               '<span class="from lineNumber">337</span><span class="to lineNumber">339</span><span class="text"></span>' +
297               '</div>' +
298               '<div class="Line LineContainer">' +
299               '<span class="from lineNumber">338</span><span class="to lineNumber">340</span><span class="text">    LayoutUnit oldClientAfterEdge = clientLogicalBottom();</span>' +
300               '</div>' +
301             '</div><div class="clear_float">' +
302           '</div>' +
303         '</div>' +
304       '</div>';
305
306   eraseDraftComments();
307   crawlDiff();
308
309   convertAllFileDiffs('sidebyside', $('.FileDiff'));
310
311   displayPreviousComments([{
312     author: 'ojan@chromium.org',
313     file_name: 'Source/WebCore/ChangeLog',
314     line_number: 338,
315     comment_text: 'This change sux.'
316   }]);
317
318   var previous_comment = document.querySelector('.previousComment');
319   ASSERT_EQUAL(previous_comment.getAttribute('data-comment-for'), 'line0');
320
321   var new_comment = addCommentField(previous_comment);
322   ASSERT("New comment should exist and contain a textarea.", new_comment.find('textarea'));
323
324   document.getElementById('diff-content').innerHTML = '';
325 }
326
327 function testSanitizeFragmentForCopy() {
328   var fragment = document.createElement('div');
329   fragment.innerHTML = '<div class="FileDiff">' +
330       '<h1><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/ChangeLog">Source/WebCore/ChangeLog</a></h1>' +
331       '<div class="FileDiffLinkContainer LinkContainer" style="opacity: 0;"><a href="javascript:" class="unify-link cremed" style="display: inline;">unified</a></div>' +
332       '<div class="DiffSection">' +
333         '<div class="DiffBlock">' +
334           '<div class="DiffBlockPart shared">' +
335             '<div class="Line LineContainer">' +
336               '<span class="from lineNumber">336</span><span class="to lineNumber">338</span><span class="text">    layoutFlexItems(*m_orderIterator, lineContexts);</span>' +
337             '</div>' +
338             '<div class="Line LineContainer">' +
339               '<span class="from lineNumber">337</span><span class="to lineNumber">339</span><span class="text"></span>' +
340             '</div>' +
341             '<div class="Line LineContainer">' +
342               '<span class="from lineNumber">338</span><span class="to lineNumber">340</span><span class="text">    LayoutUnit oldClientAfterEdge = clientLogicalBottom();</span>' +
343             '</div>' +
344           '</div><div class="clear_float"></div>' +
345         '</div>' +
346       '</div>' +
347     '</div>';
348
349   var expectedWithLineNumbers = '<div class="FileDiff">' +
350       '<h1><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/ChangeLog">Source/WebCore/ChangeLog</a></h1>' +
351       '<div class="DiffSection">' +
352         '<div class="DiffBlock">' +
353           '<div class="DiffBlockPart shared">' +
354             '<div class="Line LineContainer">' +
355               '<span class="from lineNumber">336</span><span class="to lineNumber">338</span><span class="text">    layoutFlexItems(*m_orderIterator, lineContexts);</span>' +
356             '</div>' +
357             '<div class="Line LineContainer">' +
358               '<span class="from lineNumber">337</span><span class="to lineNumber">339</span><span class="text"><br></span>' +
359             '</div>' +
360             '<div class="Line LineContainer">' +
361               '<span class="from lineNumber">338</span><span class="to lineNumber">340</span><span class="text">    LayoutUnit oldClientAfterEdge = clientLogicalBottom();</span>' +
362             '</div>' +
363           '</div><div class="clear_float"></div>' +
364         '</div>' +
365       '</div>' +
366     '</div>';
367
368   var fragmentCopy = fragment.cloneNode(true);
369   sanitizeFragmentForCopy(fragmentCopy, false);
370   ASSERT_EQUAL(fragmentCopy.innerHTML, expectedWithLineNumbers);
371
372   var expectedWithOutLineNumbers = '<div class="FileDiff">' +
373       '<h1><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/ChangeLog">Source/WebCore/ChangeLog</a></h1>' +
374       '<div class="DiffSection">' +
375         '<div class="DiffBlock">' +
376           '<div class="DiffBlockPart shared">' +
377             '<div class="Line LineContainer">' +
378               '<span class="text">    layoutFlexItems(*m_orderIterator, lineContexts);</span>' +
379             '</div>' +
380             '<div class="Line LineContainer">' +
381               '<span class="text"><br></span>' +
382             '</div>' +
383             '<div class="Line LineContainer">' +
384               '<span class="text">    LayoutUnit oldClientAfterEdge = clientLogicalBottom();</span>' +
385             '</div>' +
386           '</div><div class="clear_float"></div>' +
387         '</div>' +
388       '</div>' +
389     '</div>';
390
391   var fragmentCopy = fragment.cloneNode(true);
392   sanitizeFragmentForCopy(fragmentCopy, true);
393   ASSERT_EQUAL(fragmentCopy.innerHTML, expectedWithOutLineNumbers);
394 }
395
396 function testIsChangeLog() {
397   ASSERT("Top-level ChangeLog file is a ChangeLog file", isChangeLog("ChangeLog"));
398   ASSERT("Second-level ChangeLog file is a ChangeLog file", isChangeLog("Tools/ChangeLog"));
399   ASSERT("prepare-ChangeLog is not a ChangeLog file", !isChangeLog("Tools/Scripts/prepare-ChangeLog"));
400   ASSERT("ChangeLog-script is not a ChangeLog file", !isChangeLog("Tools/Scripts/ChangeLog-script"));
401 }
402
403 function testSaveCommentsWithMissingLineIds() {
404   document.getElementById('diff-content').innerHTML =
405       '<div class="FileDiff">' +
406         '<h1><a href="http://trac.webkit.org/browser/trunk/Source/WebCore/ChangeLog">Source/WebCore/dummy.txt</a></h1>' +
407         '<div class="DiffSection">' +
408           '<div class="DiffBlock">' +
409             '<div class="DiffBlockPart shared">' +
410               '<div class="Line LineContainer">' +
411               '<span class="from lineNumber">6</span><span class="to lineNumber">8</span><span class="text">a</span>' +
412               '</div>' +
413               '<div class="Line LineContainer">' +
414               '<span class="from lineNumber">7</span><span class="to lineNumber">9</span><span class="text">b</span>' +
415               '</div>' +
416               '<div class="Line LineContainer">' +
417               '<span class="from lineNumber">8</span><span class="to lineNumber">10</span><span class="text">c</span>' +
418               '</div>' +
419             '</div><div class="clear_float">' +
420           '</div>' +
421         '</div>' +
422         '<div class="DiffSection">' +
423           '<div class="Line LineContainer context">' +
424             '<span class="from lineNumber">@</span><span class="to lineNumber">@</span><span class="text">static void willRemoveChildren(ContainerNode* container)</span>' +
425           '</div>' +
426           '<div class="DiffBlock">' +
427             '<div class="DiffBlockPart shared">' +
428             '<div class="Line LineContainer">' +
429               '<span class="from lineNumber">15</span><span class="to lineNumber">17</span><span class="text">d</span>' +
430             '</div>' +
431           '</div><div class="clear_float"></div></div>' +
432         '</div>' +
433       '</div>';
434
435   var file_name = "Source/WebCore/dummy.txt";
436   var file_contents = [];
437   for (var i = 0; i < 20; i++)
438     file_contents[i] = i;
439   setFileContents(file_name, file_contents, file_contents);
440
441   eraseDraftComments();
442   crawlDiff();
443
444   var new_comment = addCommentFor($('#line4'));
445   new_comment.find('textarea').val("dummy content");
446   acceptComment(new_comment);
447
448   $('.ExpandLink')[0].click();
449
450   // Strip the link to the context since that points to window.location.
451   ASSERT_EQUAL(serializedComments().replace(/View in context.*code-review-test.html/, ''),
452     '\n\n' +
453     '> Source/WebCore/dummy.txt:17\n\n\n' +
454     'dummy content');
455   document.getElementById('diff-content').innerHTML = '';
456 }
457
458
459 for (var property in window) {
460   if (property.indexOf('test') == 0) {
461     window[property]();
462   }
463 }
464 </script>