Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / test / data / webui / history_browsertest.js
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 GEN('#include "chrome/test/data/webui/history_ui_browsertest.h"');
6
7 /** @const */ var TOTAL_RESULT_COUNT = 160;
8 /** @const */ var WAIT_TIMEOUT = 200;
9
10 /**
11  * Test fixture for history WebUI testing.
12  * @constructor
13  * @extends {testing.Test}
14  */
15 function HistoryUIBrowserTest() {}
16
17 /**
18  * Create a fake history result with the given timestamp.
19  * @param {Number} timestamp Timestamp of the entry, in ms since the epoch.
20  * @param {String} url The URL to set on this entry.
21  * @return {Object} An object representing a history entry.
22  */
23 function createHistoryEntry(timestamp, url) {
24   var d = new Date(timestamp);
25   // Extract domain from url.
26   var domainMatch = url.replace(/^.+?:\/\//, '').match(/[^/]+/);
27   var domain = domainMatch ? domainMatch[0] : '';
28   return {
29     dateTimeOfDay: d.getHours() + ':' + d.getMinutes(),
30     dateRelativeDay: d.toDateString(),
31     allTimestamps: [timestamp],
32     starred: false,
33     time: timestamp,
34     title: d.toString(),  // Use the stringified date as the title.
35     url: url,
36     domain: domain
37   };
38 }
39
40 /**
41  * Wait for the history backend to call the global function named
42  * |callbackName|, and then execute |afterFunction|. This allows tests to
43  * wait on asynchronous backend operations before proceeding.
44  */
45 function waitForCallback(callbackName, afterFunction) {
46   var originalCallback = window[callbackName];
47
48   // Install a wrapper that temporarily replaces the original function.
49   window[callbackName] = function() {
50     window[callbackName] = originalCallback;
51     originalCallback.apply(this, arguments);
52     afterFunction();
53   };
54 }
55
56 /**
57  * Asynchronously execute the global function named |functionName|. This
58  * should be used for all calls from backend stubs to the frontend.
59  */
60 function callFrontendAsync(functionName) {
61   var args = Array.prototype.slice.call(arguments, 1);
62   setTimeout(function() {
63     window[functionName].apply(window, args);
64   }, 1);
65 }
66
67 /**
68  * Checks that all the checkboxes in the [|start|, |end|] interval are checked
69  * and that their IDs are properly set. Does that against the checkboxes in
70  * |checked|, starting from the |startInChecked| position.
71  * @param {Array} checked An array of all the relevant checked checkboxes
72  *    on this page.
73  * @param {Number} start The starting checkbox id.
74  * @param {Number} end The ending checkbox id.
75  */
76 function checkInterval(checked, start, end) {
77   for (var i = start; i <= end; i++)
78     expectEquals('checkbox-' + i, checked[i - start].id);
79 }
80
81 /**
82  * Returns a period of 7 days, |offset| weeks back from |today|. The behavior
83  * of this function should be identical to
84  * BrowsingHistoryHandler::SetQueryTimeInWeeks.
85  * @param {Number} offset Number of weeks to go back.
86  * @param {Date} today Which date to consider as "today" (since we're not using
87  *     the actual current date in this case).
88  * @return {Object} An object containing the begin date and the end date of the
89  *     computed period.
90  */
91 function setQueryTimeInWeeks(offset, today) {
92   // Going back one day at a time starting from midnight will make sure that
93   // the other values get updated properly.
94   var endTime = new Date(today);
95   endTime.setHours(24, 0, 0, 0);
96   for (var i = 0; i < 7 * offset; i++)
97     endTime.setDate(endTime.getDate() - 1);
98   var beginTime = new Date(endTime);
99   for (var i = 0; i < 7; i++)
100     beginTime.setDate(beginTime.getDate() - 1);
101   return {'endTime': endTime, 'beginTime': beginTime};
102 }
103
104 /**
105  * Returns the period of a month, |offset| months back from |today|. The
106  * behavior of this function should be identical to
107  * BrowsingHistoryHandler::SetQueryTimeInMonths.
108  * @param {Number} offset Number of months to go back.
109  * @param {Date} today Which date to consider as "today" (since we're not using
110  *     the actual current date in this case).
111  * @return {Object} An object containing the begin date and the end date of the
112  *     computed period.
113  */
114 function setQueryTimeInMonths(offset, today) {
115   var endTime = new Date(today);
116   var beginTime = new Date(today);
117   // Last day of this month.
118   endTime.setMonth(endTime.getMonth() + 1, 0);
119   // First day of the current month.
120   beginTime.setMonth(beginTime.getMonth(), 1);
121   for (var i = 0; i < offset; i++) {
122     beginTime.setMonth(beginTime.getMonth() - 1);
123     endTime.setMonth(endTime.getMonth() - 1);
124   }
125   return {'endTime': endTime, 'beginTime': beginTime};
126 }
127
128 /**
129  * Base fixture for History WebUI testing.
130  * @extends {testing.Test}
131  * @constructor
132  */
133 function BaseHistoryWebUITest() {}
134
135 BaseHistoryWebUITest.prototype = {
136   __proto__: testing.Test.prototype,
137
138   /**
139    * Browse to the history page & call our preLoad().
140    */
141   browsePreload: 'chrome://history-frame',
142
143   /** @override */
144   typedefCppFixture: 'HistoryUIBrowserTest',
145
146   /** @override */
147   runAccessibilityChecks: true,
148
149   /** @override */
150   accessibilityIssuesAreErrors: true,
151
152   isAsync: true,
153 };
154
155 /**
156  * Fixture for History WebUI testing which returns some fake history results
157  * to the frontend. Other fixtures that want to stub out calls to the backend
158  * can extend this one.
159  * @extends {BaseHistoryWebUITest}
160  * @constructor
161  */
162 function HistoryWebUIFakeBackendTest() {
163 }
164
165 HistoryWebUIFakeBackendTest.prototype = {
166   __proto__: BaseHistoryWebUITest.prototype,
167
168   /**
169    * Register handlers to stub out calls to the history backend.
170    * @override
171    */
172   preLoad: function() {
173     this.registerMockHandler_(
174         'queryHistory', this.queryHistoryStub_.bind(this));
175   },
176
177   /**
178    * Register a mock handler for a message to the history backend.
179    * @param handlerName The name of the message to mock.
180    * @param handler The mock message handler function.
181    */
182   registerMockHandler_: function(handlerName, handler) {
183     // Mock4JS doesn't pass in the actual arguments to the stub, but it _will_
184     // pass the original args to the matcher object. SaveMockArguments acts as
185     // a proxy for another matcher, but keeps track of all the arguments it was
186     // asked to match.
187     var savedArgs = new SaveMockArguments();
188
189     this.makeAndRegisterMockHandler([handlerName]);
190     this.mockHandler.stubs()[handlerName](savedArgs.match(ANYTHING)).will(
191         callFunctionWithSavedArgs(savedArgs, handler));
192   },
193
194   /**
195    * Default stub for the queryHistory message to the history backend.
196    * Simulates an empty history database. Override this to customize this
197    * behavior for particular tests.
198    * @param {Array} arguments The original arguments to queryHistory.
199    */
200   queryHistoryStub_: function(args) {
201     callFrontendAsync(
202         'historyResult', { term: args[0], finished: true }, []);
203   }
204 };
205
206 function queryHistoryImpl(args, beginTime, history) {
207   var searchText = args[0];
208   var offset = args[1];
209   var range = args[2];
210   var endTime = args[3] || Number.MAX_VALUE;
211   var maxCount = args[4];
212
213   var results = [];
214   if (searchText) {
215     for (var k = 0; k < history.length; k++) {
216       // Search only by title in this stub.
217       if (history[k].title.indexOf(searchText) != -1)
218         results.push(history[k]);
219     }
220   } else {
221     results = history;
222   }
223
224   // Advance past all entries newer than the specified end time.
225   var i = 0;
226   // Finished is set from the history database so this behavior may not be
227   // completely identical.
228   var finished = true;
229   while (i < results.length && results[i].time >= endTime)
230     ++i;
231
232   if (beginTime) {
233     var j = i;
234     while (j < results.length && results[j].time >= beginTime)
235       ++j;
236
237     finished = (j == results.length);
238     results = results.slice(i, j);
239   } else {
240     results = results.slice(i);
241   }
242
243   if (maxCount) {
244     finished = (maxCount >= results.length);
245     results = results.slice(0, maxCount);
246   }
247
248   var queryStartTime = '';
249   var queryEndTime = '';
250   if (results.length) {
251     queryStartTime = results[results.length - 1].dateRelativeDay;
252     queryEndTime = results[0].dateRelativeDay;
253   } else if (beginTime) {
254     queryStartTime = Date(beginTime);
255     queryEndTime = Date(endTime);
256   }
257
258   callFrontendAsync(
259       'historyResult',
260       {
261         term: searchText,
262         finished: finished,
263         queryStartTime: queryStartTime,
264         queryEndTime: queryEndTime
265       },
266       results);
267 }
268
269 /**
270  * Fixture for History WebUI testing which returns some fake history results
271  * to the frontend.
272  * @extends {HistoryWebUIFakeBackendTest}
273  * @constructor
274  */
275 function HistoryWebUITest() {}
276
277 HistoryWebUITest.prototype = {
278   __proto__: HistoryWebUIFakeBackendTest.prototype,
279
280   preLoad: function() {
281     HistoryWebUIFakeBackendTest.prototype.preLoad.call(this);
282
283     this.registerMockHandler_(
284         'removeVisits', this.removeVisitsStub_.bind(this));
285
286     // Prepare a list of fake history results. The entries will begin at
287     // 1:00 AM on Sept 2, 2008, and will be spaced two minutes apart.
288     var timestamp = new Date(2008, 9, 2, 1, 0).getTime();
289     this.fakeHistory_ = [];
290
291     for (var i = 0; i < TOTAL_RESULT_COUNT; i++) {
292       this.fakeHistory_.push(
293           createHistoryEntry(timestamp, 'http://google.com/' + timestamp));
294       timestamp -= 2 * 60 * 1000;  // Next visit is two minutes earlier.
295     }
296   },
297
298   /**
299    * Stub for the 'queryHistory' message to the history backend.
300    * Simulates a history database using the fake history data that is
301    * initialized in preLoad().
302    * @param {Array} arguments The original arguments to queryHistory.
303    */
304   queryHistoryStub_: function(args) {
305     var searchText = args[0];
306     var offset = args[1];
307     var range = args[2];
308     var endTime = args[3] || Number.MAX_VALUE;
309     var maxCount = args[4];
310     if (range == HistoryModel.Range.ALL_TIME) {
311       queryHistoryImpl(args, null, this.fakeHistory_);
312       return;
313     }
314     if (range == HistoryModel.Range.WEEK)
315       var interval = setQueryTimeInWeeks(offset, this.today);
316     else
317       var interval = setQueryTimeInMonths(offset, this.today);
318
319     args[3] = interval.endTime.getTime();
320     queryHistoryImpl(args, interval.beginTime.getTime(), this.fakeHistory_);
321   },
322
323   /**
324    * Stub for the 'removeVisits' message to the history backend.
325    * This will modify the fake history data in the test instance, so that
326    * further 'queryHistory' messages will not contain the deleted entries.
327    * @param {Array} arguments The original arguments to removeVisits.
328    */
329   removeVisitsStub_: function(args) {
330     for (var i = 0; i < args.length; ++i) {
331       var url = args[i].url;
332       var timestamps = args[i].timestamps;
333       assertEquals(timestamps.length, 1);
334       this.removeVisitsToUrl_(url, new Date(timestamps[0]));
335     }
336     callFrontendAsync('deleteComplete');
337   },
338
339   /**
340    * Removes any visits to |url| on the same day as |date| from the fake
341    * history data.
342    * @param {string} url
343    * @param {Date} date
344    */
345   removeVisitsToUrl_: function(url, date) {
346     var day = date.toDateString();
347     var newHistory = [];
348     for (var i = 0, visit; visit = this.fakeHistory_[i]; ++i) {
349       if (url != visit.url || visit.dateRelativeDay != day)
350         newHistory.push(visit);
351     }
352     this.fakeHistory_ = newHistory;
353   }
354 };
355
356 /**
357  * Examines the time column of every entry on the page, and ensure that they
358  * are all the same width.
359  */
360 function ensureTimeWidthsEqual() {
361   var times = document.querySelectorAll('.entry .time');
362   var timeWidth = times[0].clientWidth;
363   for (var i = 1; i < times.length; ++i) {
364     assertEquals(timeWidth, times[i].clientWidth);
365   }
366 }
367
368 TEST_F('HistoryWebUIFakeBackendTest', 'emptyHistory', function() {
369   expectTrue($('newest-button').hidden);
370   expectTrue($('newer-button').hidden);
371   expectTrue($('older-button').hidden);
372   testDone();
373 });
374
375 // Times out on Win: http://crbug.com/336845
376 TEST_F('HistoryWebUITest', 'DISABLED_basicTest', function() {
377   var resultCount = document.querySelectorAll('.entry').length;
378
379   // Check that there are two days of entries.
380   var dayHeaders = document.querySelectorAll('.day');
381   assertEquals(2, dayHeaders.length);
382   expectNotEquals(dayHeaders[0].textContent, dayHeaders[1].textContent);
383
384   // Check that the entries in each day are time-ordered, and that no
385   // duplicate URLs appear on a given day.
386   var urlsByDay = {};
387   var lastDate = new Date();
388   for (var day = 0; day < dayHeaders.length; ++day) {
389     var dayTitle = dayHeaders[day].textContent;
390     var dayResults = document.querySelectorAll('.day-results')[day];
391     var entries = dayResults.querySelectorAll('.entry');
392     expectGT(entries.length, 0);
393
394     for (var i = 0, entry; entry = entries[i]; ++i) {
395       var time = entry.querySelector('.time').textContent;
396       expectGT(time.length, 0);
397
398       var date = new Date(dayTitle + ' ' + time);
399       expectGT(lastDate, date);
400       lastDate = date;
401
402       // Ensure it's not a duplicate URL for this day.
403       var dayAndUrl = day + entry.querySelector('a').href;
404       expectFalse(urlsByDay.hasOwnProperty(dayAndUrl));
405       urlsByDay[dayAndUrl] = dayAndUrl;
406
407       // Reconstruct the entry date from the title, and ensure that it's
408       // consistent with the date header and with the time.
409       var entryDate = new Date(entry.querySelector('.title').textContent);
410       expectEquals(entryDate.getYear(), date.getYear());
411       expectEquals(entryDate.getMonth(), date.getMonth());
412       expectEquals(entryDate.getDay(), date.getDay());
413       expectEquals(entryDate.getHours(), date.getHours());
414       expectEquals(entryDate.getMinutes(), date.getMinutes());
415     }
416   }
417
418   // Check that there are 3 page navigation links and that only the "Older"
419   // link is visible.
420   expectEquals(3, document.querySelectorAll('[is="action-link"]').length);
421   expectTrue($('newest-button').hidden);
422   expectTrue($('newer-button').hidden);
423   expectFalse($('older-button').hidden);
424
425   ensureTimeWidthsEqual();
426
427   // Go to the next page.
428   $('older-button').click();
429   waitForCallback('historyResult', function() {
430     resultCount += document.querySelectorAll('.entry').length;
431
432     // Check that the two pages include all of the entries.
433     expectEquals(TOTAL_RESULT_COUNT, resultCount);
434
435     // Check that the day header was properly continued -- the header for the
436     // last day on the first page should be a substring of the header on the
437     // second page. E.g. "Wed, Oct 8, 2008" and "Web, Oct 8, 2008 - cont'd".
438     var newDayHeaders = document.querySelectorAll('.day');
439     expectEquals(1, newDayHeaders.length);
440     expectEquals(0,
441         newDayHeaders[0].textContent.indexOf(dayHeaders[1].textContent));
442
443     // Check that the "Newest" and "Newer" links are now visible, but the
444     // "Older" link is hidden.
445     expectEquals(3, document.querySelectorAll('[is="action-link"]').length);
446     expectFalse($('newest-button').hidden);
447     expectFalse($('newer-button').hidden);
448     expectTrue($('older-button').hidden);
449
450     ensureTimeWidthsEqual();
451
452     // Go back to the first page, and check that the same day headers are there.
453     $('newest-button').click();
454     var newDayHeaders = document.querySelectorAll('.day');
455     expectEquals(2, newDayHeaders.length);
456
457     expectNotEquals(newDayHeaders[0].textContent,
458                     newDayHeaders[1].textContent);
459     expectEquals(dayHeaders[0].textContent, newDayHeaders[0].textContent);
460     expectEquals(dayHeaders[1].textContent, newDayHeaders[1].textContent);
461
462     testDone();
463   });
464 });
465
466 /**
467  * Test bulk deletion of history entries.
468  * Disabled because it is currently very flaky on the Windows XP bot.
469  */
470 TEST_F('HistoryWebUITest', 'DISABLED_bulkDeletion', function() {
471   var checkboxes = document.querySelectorAll(
472       '#results-display input[type=checkbox]');
473
474   // Immediately confirm the history deletion.
475   confirmDeletion = function(okCallback, cancelCallback) {
476     okCallback();
477   };
478
479   // The "remove" button should be initially disabled.
480   var removeButton = $('remove-selected');
481   expectTrue(removeButton.disabled);
482
483   checkboxes[0].click();
484   expectFalse(removeButton.disabled);
485
486   var firstEntry = document.querySelector('.title a').textContent;
487   removeButton.click();
488
489   // After deletion, expect the results to be reloaded.
490   waitForCallback('historyResult', function() {
491     expectNotEquals(document.querySelector('.title a').textContent, firstEntry);
492     expectTrue(removeButton.disabled);
493
494     // Delete the first 3 entries.
495     checkboxes = document.querySelectorAll(
496         '#results-display input[type=checkbox]');
497     checkboxes[0].click();
498     checkboxes[1].click();
499     checkboxes[2].click();
500     expectFalse(removeButton.disabled);
501
502     var nextEntry = document.querySelectorAll('.title a')[3];
503     removeButton.click();
504     waitForCallback('historyResult', function() {
505       // The next entry after the deleted ones should now be the first.
506       expectEquals(document.querySelector('.title a').textContent,
507                    nextEntry.textContent);
508       testDone();
509     });
510   });
511 });
512
513 /**
514  * Test selecting multiple entries using shift click.
515  * Disabled due to time out on all platforms: crbug/375910
516  */
517 TEST_F('HistoryWebUITest', 'DISABLED_multipleSelect', function() {
518   var checkboxes = document.querySelectorAll(
519       '#results-display input[type=checkbox]');
520
521   var getAllChecked = function() {
522     return Array.prototype.slice.call(document.querySelectorAll(
523         '#results-display input[type=checkbox]:checked'));
524   };
525
526   // Make sure that nothing is checked.
527   expectEquals(0, getAllChecked().length);
528
529   var shiftClick = function(el) {
530     el.dispatchEvent(new MouseEvent('click', { shiftKey: true }));
531   };
532
533   // Check the start.
534   shiftClick($('checkbox-4'));
535   // And the end.
536   shiftClick($('checkbox-9'));
537
538   // See if they are checked.
539   var checked = getAllChecked();
540   expectEquals(6, checked.length);
541   checkInterval(checked, 4, 9);
542
543   // Extend the selection.
544   shiftClick($('checkbox-14'));
545
546   checked = getAllChecked();
547   expectEquals(11, checked.length);
548   checkInterval(checked, 4, 14);
549
550   // Now do a normal click on a higher ID box and a shift click on a lower ID
551   // one (test the other way around).
552   $('checkbox-24').click();
553   shiftClick($('checkbox-19'));
554
555   checked = getAllChecked();
556   expectEquals(17, checked.length);
557   // First set of checkboxes (11).
558   checkInterval(checked, 4, 14);
559   // Second set (6).
560   checkInterval(checked.slice(11), 19, 24);
561
562   // Test deselection.
563   $('checkbox-26').click();
564   shiftClick($('checkbox-20'));
565
566   checked = getAllChecked();
567   // checkbox-20 to checkbox-24 should be deselected now.
568   expectEquals(12, checked.length);
569   // First set of checkboxes (11).
570   checkInterval(checked, 4, 14);
571   // Only checkbox-19 should still be selected.
572   expectEquals('checkbox-19', checked[11].id);
573
574   testDone();
575 });
576
577 TEST_F('HistoryWebUITest', 'DISABLED_searchHistory', function() {
578   var getResultCount = function() {
579     return document.querySelectorAll('.entry').length;
580   };
581   // See that all the elements are there.
582   expectEquals(RESULTS_PER_PAGE, getResultCount());
583
584   // See that the search works.
585   $('search-field').value = 'Thu Oct 02 2008';
586   $('search-button').click();
587
588   waitForCallback('historyResult', function() {
589     expectEquals(31, getResultCount());
590
591     // Clear the search.
592     $('search-field').value = '';
593     $('search-button').click();
594     waitForCallback('historyResult', function() {
595       expectEquals(RESULTS_PER_PAGE, getResultCount());
596       testDone();
597     });
598   });
599 });
600
601 function setPageState(searchText, page, groupByDomain, range, offset) {
602   window.location = '#' + PageState.getHashString(
603       searchText, page, groupByDomain, range, offset);
604 }
605
606 function RangeHistoryWebUITest() {}
607
608 RangeHistoryWebUITest.prototype = {
609   __proto__: HistoryWebUITest.prototype,
610
611   /** @override */
612   preLoad: function() {
613     HistoryWebUITest.prototype.preLoad.call(this);
614     // Repeat the domain visits every 4 days. The nested lists contain the
615     // domain suffixes for the visits in a day.
616     var domainSuffixByDay = [
617       [1, 2, 3, 4],
618       [1, 2, 2, 3],
619       [1, 2, 1, 2],
620       [1, 1, 1, 1]
621     ];
622
623     var buildDomainUrl = function(timestamp) {
624       var d = new Date(timestamp);
625       // Repeat the same setup of domains every 4 days.
626       var day = d.getDate() % 4;
627       // Assign an entry for every 6 hours so that we get 4 entries per day
628       // maximum.
629       var visitInDay = Math.floor(d.getHours() / 6);
630       return 'http://google' + domainSuffixByDay[day][visitInDay] + '.com/' +
631           timestamp;
632     };
633
634     // Prepare a list of fake history results. Start the results on
635     // 11:00 PM on May 2, 2012 and add 4 results every day (one result every 6
636     // hours).
637     var timestamp = new Date(2012, 4, 2, 23, 0).getTime();
638     this.today = new Date(2012, 4, 2);
639     this.fakeHistory_ = [];
640
641     // Put in 2 days for May and 30 days for April so the results span over
642     // the month limit.
643     for (var i = 0; i < 4 * 32; i++) {
644       this.fakeHistory_.push(
645           createHistoryEntry(timestamp, buildDomainUrl(timestamp)));
646       timestamp -= 6 * 60 * 60 * 1000;
647     }
648
649     // Leave March empty.
650     timestamp -= 31 * 24 * 3600 * 1000;
651
652     // Put results in February.
653     for (var i = 0; i < 29 * 4; i++) {
654       this.fakeHistory_.push(
655           createHistoryEntry(timestamp, buildDomainUrl(timestamp)));
656       timestamp -= 6 * 60 * 60 * 1000;
657     }
658   },
659
660   setUp: function() {
661     // Show the filter controls as if the command line switch was active.
662     $('top-container').hidden = true;
663     $('history-page').classList.add('big-topbar-page');
664     $('filter-controls').hidden = false;
665     expectFalse($('filter-controls').hidden);
666   },
667 };
668
669 /**
670  * Disabled due intermitent failures on multiple OSes http://crbug.com/377338
671  */
672 TEST_F('RangeHistoryWebUITest', 'DISABLED_allView', function() {
673   // Check that we start off in the all time view.
674   expectTrue($('timeframe-controls').querySelector('input').checked);
675   // See if the correct number of days is shown.
676   var dayHeaders = document.querySelectorAll('.day');
677   assertEquals(Math.ceil(RESULTS_PER_PAGE / 4), dayHeaders.length);
678   testDone();
679 });
680
681 /**
682  * Checks whether the domains in a day are ordered decreasingly.
683  * @param {Element} element Ordered list containing the grouped domains for a
684  *     day.
685  */
686 function checkGroupedVisits(element) {
687   // The history page contains the number of visits next to a domain in
688   // parentheses (e.g. 'google.com (5)'). This function extracts that number
689   // and returns it.
690   var getNumberVisits = function(element) {
691     return parseInt(element.textContent.replace(/\D/g, ''), 10);
692   };
693
694   // Read the number of visits from each domain and make sure that it is lower
695   // than or equal to the number of visits from the previous domain.
696   var domainEntries = element.querySelectorAll('.number-visits');
697   var currentNumberOfVisits = getNumberVisits(domainEntries[0]);
698   for (var j = 1; j < domainEntries.length; j++) {
699     var numberOfVisits = getNumberVisits(domainEntries[j]);
700     assertTrue(currentNumberOfVisits >= numberOfVisits);
701     currentNumberOfVisits = numberOfVisits;
702   }
703 }
704
705 TEST_F('RangeHistoryWebUITest', 'weekViewGrouped', function() {
706   // Change to weekly view.
707   setPageState('', 0, HistoryModel.Range.WEEK, 0);
708   waitForCallback('historyResult', function() {
709     // See if the correct number of days is still shown.
710     var dayResults = document.querySelectorAll('.day-results');
711     assertEquals(7, dayResults.length);
712
713     // Check whether the results are ordered by visits.
714     for (var i = 0; i < dayResults.length; i++)
715       checkGroupedVisits(dayResults[i]);
716
717     ensureTimeWidthsEqual();
718
719     testDone();
720   });
721 });
722
723 TEST_F('RangeHistoryWebUITest', 'monthViewGrouped', function() {
724   // Change to monthly view.
725   setPageState('', 0, HistoryModel.Range.MONTH, 0);
726   waitForCallback('historyResult', function() {
727     // See if the correct number of days is shown.
728     var monthResults = document.querySelectorAll('.month-results');
729     assertEquals(1, monthResults.length);
730
731     checkGroupedVisits(monthResults[0]);
732     ensureTimeWidthsEqual();
733
734     testDone();
735   });
736 });
737
738 TEST_F('RangeHistoryWebUITest', 'monthViewEmptyMonth', function() {
739   // Change to monthly view.
740   setPageState('', 0, HistoryModel.Range.MONTH, 2);
741
742   waitForCallback('historyResult', function() {
743     // See if the correct number of days is shown.
744     var resultsDisplay = $('results-display');
745     assertEquals(0, resultsDisplay.querySelectorAll('.months-results').length);
746     assertEquals(1, resultsDisplay.querySelectorAll('div').length);
747
748     testDone();
749   });
750 });
751
752 /**
753  * Fixture for History WebUI tests using the real history backend.
754  * @extends {BaseHistoryWebUITest}
755  * @constructor
756  */
757 function HistoryWebUIRealBackendTest() {}
758
759 HistoryWebUIRealBackendTest.prototype = {
760   __proto__: BaseHistoryWebUITest.prototype,
761
762   /** @override */
763   testGenPreamble: function() {
764     // Add some visits to the history database.
765     GEN('  AddPageToHistory(0, "http://google.com", "Google");');
766     GEN('  AddPageToHistory(1, "http://example.com", "Example");');
767     GEN('  AddPageToHistory(2, "http://google.com", "Google");');
768
769     // Add a visit on the next day.
770     GEN('  AddPageToHistory(36, "http://google.com", "Google");');
771   },
772 };
773
774 /**
775  * Simple test that verifies that the correct entries are retrieved from the
776  * history database and displayed in the UI.
777  */
778 TEST_F('HistoryWebUIRealBackendTest', 'basic', function() {
779   // Check that there are two days of entries, and three entries in total.
780   assertEquals(2, document.querySelectorAll('.day').length);
781   assertEquals(3, document.querySelectorAll('.entry').length);
782
783   testDone();
784 });
785
786 TEST_F('HistoryWebUIRealBackendTest', 'atLeastOneFocusable', function() {
787   var results = document.querySelectorAll('#results-display [tabindex="0"]');
788   expectEquals(1, results.length);
789   testDone();
790 });
791
792 TEST_F('HistoryWebUIRealBackendTest', 'deleteRemovesEntry', function() {
793   assertTrue(historyModel.deletingHistoryAllowed);
794
795   var visit = document.querySelector('.entry').visit;
796   visit.titleLink.focus();
797   assertEquals(visit.titleLink, document.activeElement);
798
799   var deleteKey = document.createEvent('KeyboardEvent');
800   deleteKey.initKeyboardEvent('keydown', true, true, window, 'U+007F');
801   assertEquals('U+007F', deleteKey.keyIdentifier);
802
803   assertFalse(historyModel.isDeletingVisits());
804   expectFalse(visit.titleLink.dispatchEvent(deleteKey));
805   expectTrue(historyModel.isDeletingVisits());
806
807   expectNotEquals(visit.dropDown, document.activeElement);
808   testDone();
809 });
810
811 /**
812  * Test individual deletion of history entries.
813  */
814 TEST_F('HistoryWebUIRealBackendTest', 'singleDeletion', function() {
815   // Deletes the history entry represented by |entryElement|, and calls callback
816   // when the deletion is complete.
817   var removeEntry = function(entryElement, callback) {
818     var dropDownButton = entryElement.querySelector('.drop-down');
819     var removeMenuItem = $('remove-visit');
820
821     assertFalse(dropDownButton.disabled);
822     assertFalse(removeMenuItem.disabled);
823
824     waitForCallback('onEntryRemoved', callback);
825
826     cr.dispatchSimpleEvent(dropDownButton, 'mousedown');
827     cr.dispatchSimpleEvent(removeMenuItem, 'activate');
828   };
829
830   var secondTitle = document.querySelectorAll('.entry a')[1].textContent;
831   var thirdTitle = document.querySelectorAll('.entry a')[2].textContent;
832
833   // historyDeleted() should not be called when deleting individual entries
834   // using the drop down.
835   waitForCallback('historyDeleted', function() {
836     testDone([false, 'historyDeleted() called when deleting single entry']);
837   });
838
839   expectEquals(2, document.querySelectorAll('.day').length);
840
841   // Delete the first entry. The previous second entry should now be the first.
842   removeEntry(document.querySelector('.entry'), function() {
843     expectEquals(secondTitle, document.querySelector('.entry a').textContent);
844
845     // After removing the first entry, its day header should also be gone.
846     expectEquals(1, document.querySelectorAll('.day').length);
847
848     // Delete another entry. The original third entry should now be the first.
849     removeEntry(document.querySelector('.entry'), function() {
850       expectEquals(thirdTitle, document.querySelector('.entry a').textContent);
851       testDone();
852     });
853   });
854 });
855
856 TEST_F('HistoryWebUIRealBackendTest', 'leftRightChangeFocus', function() {
857   var visit = document.querySelector('.entry').visit;
858   visit.titleLink.focus();
859   assertEquals(visit.titleLink, document.activeElement);
860
861   var right = document.createEvent('KeyboardEvent');
862   right.initKeyboardEvent('keydown', true, true, window, 'Right');
863   assertEquals('Right', right.keyIdentifier);
864   expectFalse(visit.titleLink.dispatchEvent(right));
865
866   assertEquals(visit.dropDown, document.activeElement);
867
868   var left = document.createEvent('KeyboardEvent');
869   left.initKeyboardEvent('keydown', true, true, window, 'Left');
870   assertEquals('Left', left.keyIdentifier);
871   expectFalse(visit.dropDown.dispatchEvent(left));
872
873   expectEquals(visit.titleLink, document.activeElement);
874   testDone();
875 });
876
877 TEST_F('HistoryWebUIRealBackendTest', 'showConfirmDialogAndCancel', function() {
878   waitForCallback('deleteComplete', function() {
879     testDone([false, "history deleted when it shouldn't have been"]);
880   });
881
882   document.querySelector('input[type=checkbox]').click();
883   $('remove-selected').click();
884
885   assertTrue($('alertOverlay').classList.contains('showing'));
886   assertFalse($('history-page').contains(document.activeElement));
887
888   var esc = document.createEvent('KeyboardEvent');
889   esc.initKeyboardEvent('keydown', true, true, window, 'U+001B');
890
891   document.dispatchEvent(esc);
892   assertFalse($('alertOverlay').classList.contains('showing'));
893
894   testDone();
895 });
896
897 TEST_F('HistoryWebUIRealBackendTest', 'showConfirmDialogAndRemove', function() {
898   document.querySelector('input[type=checkbox]').click();
899   $('remove-selected').click();
900
901   assertTrue($('alertOverlay').classList.contains('showing'));
902   assertFalse($('history-page').contains(document.activeElement));
903
904   waitForCallback('deleteComplete', testDone);
905
906   var enter = document.createEvent('KeyboardEvent');
907   enter.initKeyboardEvent('keydown', true, true, window, 'Enter');
908   document.dispatchEvent(enter);
909   assertFalse($('alertOverlay').classList.contains('showing'));
910 });
911
912 TEST_F('HistoryWebUIRealBackendTest', 'menuButtonActivatesOneRow', function() {
913   var entries = document.querySelectorAll('.entry');
914   assertEquals(3, entries.length);
915   assertTrue(entries[0].classList.contains('active'));
916   assertTrue($('action-menu').hidden);
917
918   // Show the menu via mousedown on the menu button.
919   var menuButton = entries[2].querySelector('.menu-button');
920   menuButton.dispatchEvent(new MouseEvent('mousedown'));
921   expectFalse($('action-menu').hidden);
922
923   // Check that the 'active' item hasn't changed.
924   expectTrue(entries[0].classList.contains('active'));
925   expectFalse(entries[2].classList.contains('active'));
926
927   testDone();
928 });
929
930 TEST_F('HistoryWebUIRealBackendTest', 'shiftClickActivatesOneRow', function() {
931   var entries = document.querySelectorAll('.entry');
932   assertEquals(3, entries.length);
933   assertTrue(entries[0].classList.contains('active'));
934
935   entries[0].visit.checkBox.focus();
936   assertEquals(entries[0].visit.checkBox, document.activeElement);
937
938   entries[0].visit.checkBox.click();
939   assertTrue(entries[0].visit.checkBox.checked);
940
941   var entryBox = entries[2].querySelector('.entry-box');
942   entryBox.dispatchEvent(new MouseEvent('click', {shiftKey: true}));
943   assertTrue(entries[1].visit.checkBox.checked);
944
945   // Focus shouldn't have changed, but the checkbox should toggle.
946   expectEquals(entries[0].visit.checkBox, document.activeElement);
947
948   expectTrue(entries[0].classList.contains('active'));
949   expectFalse(entries[2].classList.contains('active'));
950
951   var shiftDown = new MouseEvent('mousedown', {shiftKey: true, bubbles: true});
952   entries[2].visit.checkBox.dispatchEvent(shiftDown);
953   expectEquals(entries[2].visit.checkBox, document.activeElement);
954
955   // 'focusin' events aren't dispatched while tests are run in batch (e.g.
956   // --test-launcher-jobs=2). Simulate this. TODO(dbeam): fix instead.
957   cr.dispatchSimpleEvent(document.activeElement, 'focusin', true, true);
958
959   expectFalse(entries[0].classList.contains('active'));
960   expectTrue(entries[2].classList.contains('active'));
961
962   testDone();
963 });
964
965 /**
966  * Fixture for History WebUI testing when deletions are prohibited.
967  * @extends {HistoryWebUIRealBackendTest}
968  * @constructor
969  */
970 function HistoryWebUIDeleteProhibitedTest() {}
971
972 HistoryWebUIDeleteProhibitedTest.prototype = {
973   __proto__: HistoryWebUIRealBackendTest.prototype,
974
975   /** @override */
976   testGenPreamble: function() {
977     HistoryWebUIRealBackendTest.prototype.testGenPreamble.call(this);
978     GEN('  SetDeleteAllowed(false);');
979   },
980 };
981
982 // Test UI when removing entries is prohibited.
983 TEST_F('HistoryWebUIDeleteProhibitedTest', 'deleteProhibited', function() {
984   // No checkboxes should be created.
985   var checkboxes = document.querySelectorAll(
986       '#results-display input[type=checkbox]');
987   expectEquals(0, checkboxes.length);
988
989   // The "remove" button should be disabled.
990   var removeButton = $('remove-selected');
991   expectTrue(removeButton.disabled);
992
993   // The "Remove from history" drop-down item should be disabled.
994   var removeVisit = $('remove-visit');
995   expectTrue(removeVisit.disabled);
996
997   testDone();
998 });
999
1000 TEST_F('HistoryWebUIDeleteProhibitedTest', 'atLeastOneFocusable', function() {
1001   var results = document.querySelectorAll('#results-display [tabindex="0"]');
1002   expectEquals(1, results.length);
1003   testDone();
1004 });
1005
1006 TEST_F('HistoryWebUIDeleteProhibitedTest', 'leftRightChangeFocus', function() {
1007   var visit = document.querySelector('.entry').visit;
1008   visit.titleLink.focus();
1009   assertEquals(visit.titleLink, document.activeElement);
1010
1011   var right = document.createEvent('KeyboardEvent');
1012   right.initKeyboardEvent('keydown', true, true, window, 'Right');
1013   assertEquals('Right', right.keyIdentifier);
1014   expectFalse(visit.titleLink.dispatchEvent(right));
1015
1016   assertEquals(visit.dropDown, document.activeElement);
1017
1018   var left = document.createEvent('KeyboardEvent');
1019   left.initKeyboardEvent('keydown', true, true, window, 'Left');
1020   assertEquals('Left', left.keyIdentifier);
1021   expectFalse(visit.dropDown.dispatchEvent(left));
1022
1023   expectEquals(visit.titleLink, document.activeElement);
1024   testDone();
1025 });
1026
1027 TEST_F('HistoryWebUIDeleteProhibitedTest', 'deleteIgnored', function() {
1028   assertFalse(historyModel.deletingHistoryAllowed);
1029
1030   var visit = document.querySelector('.entry').visit;
1031   visit.titleLink.focus();
1032   assertEquals(visit.titleLink, document.activeElement);
1033
1034   var deleteKey = document.createEvent('KeyboardEvent');
1035   deleteKey.initKeyboardEvent('keydown', true, true, window, 'U+007F');
1036   assertEquals('U+007F', deleteKey.keyIdentifier);
1037
1038   assertFalse(historyModel.isDeletingVisits());
1039   expectTrue(visit.titleLink.dispatchEvent(deleteKey));
1040   expectFalse(historyModel.isDeletingVisits());
1041
1042   expectEquals(visit.titleLink, document.activeElement);
1043   testDone();
1044 });
1045
1046 /**
1047  * Fixture for History WebUI testing IDN.
1048  * @extends {BaseHistoryWebUITest}
1049  * @constructor
1050  */
1051 function HistoryWebUIIDNTest() {}
1052
1053 HistoryWebUIIDNTest.prototype = {
1054   __proto__: BaseHistoryWebUITest.prototype,
1055
1056   /** @override */
1057   testGenPreamble: function() {
1058     // Add some visits to the history database.
1059     GEN('  AddPageToHistory(0, "http://xn--d1abbgf6aiiy.xn--p1ai/",' +
1060         ' "Some");');
1061
1062     // Clear AcceptLanguages to get domain in unicode.
1063     GEN('  ClearAcceptLanguages();');
1064   },
1065 };
1066
1067 /**
1068  * Simple test that verifies that the correct entries are retrieved from the
1069  * history database and displayed in the UI.
1070  */
1071 TEST_F('HistoryWebUIIDNTest', 'basic', function() {
1072   // Check that there is only one entry and domain is in unicode.
1073   assertEquals(1, document.querySelectorAll('.domain').length);
1074   assertEquals("\u043f\u0440\u0435\u0437\u0438\u0434\u0435\u043d\u0442." +
1075                "\u0440\u0444", document.querySelector('.domain').textContent);
1076
1077   testDone();
1078 });
1079
1080 /**
1081  * Fixture for a test that uses the real backend and tests how the history
1082  * page deals with odd schemes in URLs.
1083  * @extends {HistoryWebUIRealBackendTest}
1084  */
1085 function HistoryWebUIWithSchemesTest() {}
1086
1087 HistoryWebUIWithSchemesTest.prototype = {
1088   __proto__: HistoryWebUIRealBackendTest.prototype,
1089
1090   /** @override */
1091   testGenPreamble: function() {
1092     // Add a bunch of entries on the same day, including some weird schemes.
1093     GEN('  AddPageToHistory(12, "http://google.com", "Google");');
1094     GEN('  AddPageToHistory(13, "file:///tmp/foo", "");');
1095     GEN('  AddPageToHistory(14, "mailto:chromium@chromium.org", "");');
1096     GEN('  AddPageToHistory(15, "tel:555123456", "");');
1097   },
1098
1099   setUp: function() {
1100     // Show the filter controls as if the command line switch was active.
1101     $('top-container').hidden = true;
1102     $('history-page').classList.add('big-topbar-page');
1103     $('filter-controls').hidden = false;
1104     expectFalse($('filter-controls').hidden);
1105   },
1106 };
1107
1108 TEST_F('HistoryWebUIWithSchemesTest', 'groupingWithSchemes', function() {
1109   // Switch to the week view.
1110   $('timeframe-controls').querySelectorAll('input')[1].click();
1111   waitForCallback('historyResult', function() {
1112     // Each URL should be organized under a different "domain".
1113     expectEquals(document.querySelectorAll('.entry').length, 4);
1114     expectEquals(document.querySelectorAll('.site-domain-wrapper').length, 4);
1115     testDone();
1116   });
1117 });