Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / test / data / webui / history_browsertest.js
blobd8792ead582a46b16b57ad31720e96a98359ae20
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.
5 GEN('#include "chrome/test/data/webui/history_ui_browsertest.h"');
7 /** @const */ var TOTAL_RESULT_COUNT = 160;
8 /** @const */ var WAIT_TIMEOUT = 200;
10 /**
11  * Test fixture for history WebUI testing.
12  * @constructor
13  * @extends {testing.Test}
14  */
15 function HistoryUIBrowserTest() {}
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   };
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];
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   };
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);
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);
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};
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};
129  * Base fixture for History WebUI testing.
130  * @extends {testing.Test}
131  * @constructor
132  */
133 function BaseHistoryWebUITest() {}
135 BaseHistoryWebUITest.prototype = {
136   __proto__: testing.Test.prototype,
138   /**
139    * Browse to the history page & call our preLoad().
140    */
141   browsePreload: 'chrome://history-frame',
143   /** @override */
144   typedefCppFixture: 'HistoryUIBrowserTest',
146   /** @override */
147   runAccessibilityChecks: true,
149   /** @override */
150   accessibilityIssuesAreErrors: true,
152   /** @override */
153   isAsync: true,
157  * Fixture for History WebUI testing which returns some fake history results
158  * to the frontend. Other fixtures that want to stub out calls to the backend
159  * can extend this one.
160  * @extends {BaseHistoryWebUITest}
161  * @constructor
162  */
163 function HistoryWebUIFakeBackendTest() {
166 HistoryWebUIFakeBackendTest.prototype = {
167   __proto__: BaseHistoryWebUITest.prototype,
169   /**
170    * Register handlers to stub out calls to the history backend.
171    * @override
172    */
173   preLoad: function() {
174     this.registerMockHandler_(
175         'queryHistory', this.queryHistoryStub_.bind(this));
176   },
178   /**
179    * Register a mock handler for a message to the history backend.
180    * @param handlerName The name of the message to mock.
181    * @param handler The mock message handler function.
182    */
183   registerMockHandler_: function(handlerName, handler) {
184     // Mock4JS doesn't pass in the actual arguments to the stub, but it _will_
185     // pass the original args to the matcher object. SaveMockArguments acts as
186     // a proxy for another matcher, but keeps track of all the arguments it was
187     // asked to match.
188     var savedArgs = new SaveMockArguments();
190     this.makeAndRegisterMockHandler([handlerName]);
191     this.mockHandler.stubs()[handlerName](savedArgs.match(ANYTHING)).will(
192         callFunctionWithSavedArgs(savedArgs, handler));
193   },
195   /**
196    * Default stub for the queryHistory message to the history backend.
197    * Simulates an empty history database. Override this to customize this
198    * behavior for particular tests.
199    * @param {Array} arguments The original arguments to queryHistory.
200    */
201   queryHistoryStub_: function(args) {
202     callFrontendAsync(
203         'historyResult', { term: args[0], finished: true }, []);
204   }
207 function queryHistoryImpl(args, beginTime, history) {
208   var searchText = args[0];
209   var offset = args[1];
210   var range = args[2];
211   var endTime = args[3] || Number.MAX_VALUE;
212   var maxCount = args[4];
214   var results = [];
215   if (searchText) {
216     for (var k = 0; k < history.length; k++) {
217       // Search only by title in this stub.
218       if (history[k].title.indexOf(searchText) != -1)
219         results.push(history[k]);
220     }
221   } else {
222     results = history;
223   }
225   // Advance past all entries newer than the specified end time.
226   var i = 0;
227   // Finished is set from the history database so this behavior may not be
228   // completely identical.
229   var finished = true;
230   while (i < results.length && results[i].time >= endTime)
231     ++i;
233   if (beginTime) {
234     var j = i;
235     while (j < results.length && results[j].time >= beginTime)
236       ++j;
238     finished = (j == results.length);
239     results = results.slice(i, j);
240   } else {
241     results = results.slice(i);
242   }
244   if (maxCount) {
245     finished = (maxCount >= results.length);
246     results = results.slice(0, maxCount);
247   }
249   var queryStartTime = '';
250   var queryEndTime = '';
251   if (results.length) {
252     queryStartTime = results[results.length - 1].dateRelativeDay;
253     queryEndTime = results[0].dateRelativeDay;
254   } else if (beginTime) {
255     queryStartTime = Date(beginTime);
256     queryEndTime = Date(endTime);
257   }
259   callFrontendAsync(
260       'historyResult',
261       {
262         term: searchText,
263         finished: finished,
264         queryStartTime: queryStartTime,
265         queryEndTime: queryEndTime
266       },
267       results);
271  * Fixture for History WebUI testing which returns some fake history results
272  * to the frontend.
273  * @extends {HistoryWebUIFakeBackendTest}
274  * @constructor
275  */
276 function HistoryWebUITest() {}
278 HistoryWebUITest.prototype = {
279   __proto__: HistoryWebUIFakeBackendTest.prototype,
281   preLoad: function() {
282     HistoryWebUIFakeBackendTest.prototype.preLoad.call(this);
284     this.registerMockHandler_(
285         'removeVisits', this.removeVisitsStub_.bind(this));
287     // Prepare a list of fake history results. The entries will begin at
288     // 1:00 AM on Sept 2, 2008, and will be spaced two minutes apart.
289     var timestamp = new Date(2008, 9, 2, 1, 0).getTime();
290     this.fakeHistory_ = [];
292     for (var i = 0; i < TOTAL_RESULT_COUNT; i++) {
293       this.fakeHistory_.push(
294           createHistoryEntry(timestamp, 'http://google.com/' + timestamp));
295       timestamp -= 2 * 60 * 1000;  // Next visit is two minutes earlier.
296     }
297   },
299   /**
300    * Stub for the 'queryHistory' message to the history backend.
301    * Simulates a history database using the fake history data that is
302    * initialized in preLoad().
303    * @param {Array} arguments The original arguments to queryHistory.
304    */
305   queryHistoryStub_: function(args) {
306     var searchText = args[0];
307     var offset = args[1];
308     var range = args[2];
309     var endTime = args[3] || Number.MAX_VALUE;
310     var maxCount = args[4];
311     if (range == HistoryModel.Range.ALL_TIME) {
312       queryHistoryImpl(args, null, this.fakeHistory_);
313       return;
314     }
315     if (range == HistoryModel.Range.WEEK)
316       var interval = setQueryTimeInWeeks(offset, this.today);
317     else
318       var interval = setQueryTimeInMonths(offset, this.today);
320     args[3] = interval.endTime.getTime();
321     queryHistoryImpl(args, interval.beginTime.getTime(), this.fakeHistory_);
322   },
324   /**
325    * Stub for the 'removeVisits' message to the history backend.
326    * This will modify the fake history data in the test instance, so that
327    * further 'queryHistory' messages will not contain the deleted entries.
328    * @param {Array} arguments The original arguments to removeVisits.
329    */
330   removeVisitsStub_: function(args) {
331     for (var i = 0; i < args.length; ++i) {
332       var url = args[i].url;
333       var timestamps = args[i].timestamps;
334       assertEquals(timestamps.length, 1);
335       this.removeVisitsToUrl_(url, new Date(timestamps[0]));
336     }
337     callFrontendAsync('deleteComplete');
338   },
340   /**
341    * Removes any visits to |url| on the same day as |date| from the fake
342    * history data.
343    * @param {string} url
344    * @param {Date} date
345    */
346   removeVisitsToUrl_: function(url, date) {
347     var day = date.toDateString();
348     var newHistory = [];
349     for (var i = 0, visit; visit = this.fakeHistory_[i]; ++i) {
350       if (url != visit.url || visit.dateRelativeDay != day)
351         newHistory.push(visit);
352     }
353     this.fakeHistory_ = newHistory;
354   }
358  * Examines the time column of every entry on the page, and ensure that they
359  * are all the same width.
360  */
361 function ensureTimeWidthsEqual() {
362   var times = document.querySelectorAll('.entry .time');
363   var timeWidth = times[0].clientWidth;
364   for (var i = 1; i < times.length; ++i) {
365     assertEquals(timeWidth, times[i].clientWidth);
366   }
369 // Times out on Mac: http://crbug.com/336845
370 TEST_F('HistoryWebUIFakeBackendTest', 'DISABLED_emptyHistory', function() {
371   expectTrue($('newest-button').hidden);
372   expectTrue($('newer-button').hidden);
373   expectTrue($('older-button').hidden);
374   testDone();
377 // Times out on Win: http://crbug.com/336845
378 TEST_F('HistoryWebUITest', 'DISABLED_basicTest', function() {
379   var resultCount = document.querySelectorAll('.entry').length;
381   // Check that there are two days of entries.
382   var dayHeaders = document.querySelectorAll('.day');
383   assertEquals(2, dayHeaders.length);
384   expectNotEquals(dayHeaders[0].textContent, dayHeaders[1].textContent);
386   // Check that the entries in each day are time-ordered, and that no
387   // duplicate URLs appear on a given day.
388   var urlsByDay = {};
389   var lastDate = new Date();
390   for (var day = 0; day < dayHeaders.length; ++day) {
391     var dayTitle = dayHeaders[day].textContent;
392     var dayResults = document.querySelectorAll('.day-results')[day];
393     var entries = dayResults.querySelectorAll('.entry');
394     expectGT(entries.length, 0);
396     for (var i = 0, entry; entry = entries[i]; ++i) {
397       var time = entry.querySelector('.time').textContent;
398       expectGT(time.length, 0);
400       var date = new Date(dayTitle + ' ' + time);
401       expectGT(lastDate, date);
402       lastDate = date;
404       // Ensure it's not a duplicate URL for this day.
405       var dayAndUrl = day + entry.querySelector('a').href;
406       expectFalse(urlsByDay.hasOwnProperty(dayAndUrl));
407       urlsByDay[dayAndUrl] = dayAndUrl;
409       // Reconstruct the entry date from the title, and ensure that it's
410       // consistent with the date header and with the time.
411       var entryDate = new Date(entry.querySelector('.title').textContent);
412       expectEquals(entryDate.getYear(), date.getYear());
413       expectEquals(entryDate.getMonth(), date.getMonth());
414       expectEquals(entryDate.getDay(), date.getDay());
415       expectEquals(entryDate.getHours(), date.getHours());
416       expectEquals(entryDate.getMinutes(), date.getMinutes());
417     }
418   }
420   // Check that there are 3 page navigation links and that only the "Older"
421   // link is visible.
422   expectEquals(3, document.querySelectorAll('[is="action-link"]').length);
423   expectTrue($('newest-button').hidden);
424   expectTrue($('newer-button').hidden);
425   expectFalse($('older-button').hidden);
427   ensureTimeWidthsEqual();
429   // Go to the next page.
430   $('older-button').click();
431   waitForCallback('historyResult', function() {
432     resultCount += document.querySelectorAll('.entry').length;
434     // Check that the two pages include all of the entries.
435     expectEquals(TOTAL_RESULT_COUNT, resultCount);
437     // Check that the day header was properly continued -- the header for the
438     // last day on the first page should be a substring of the header on the
439     // second page. E.g. "Wed, Oct 8, 2008" and "Web, Oct 8, 2008 - cont'd".
440     var newDayHeaders = document.querySelectorAll('.day');
441     expectEquals(1, newDayHeaders.length);
442     expectEquals(0,
443         newDayHeaders[0].textContent.indexOf(dayHeaders[1].textContent));
445     // Check that the "Newest" and "Newer" links are now visible, but the
446     // "Older" link is hidden.
447     expectEquals(3, document.querySelectorAll('[is="action-link"]').length);
448     expectFalse($('newest-button').hidden);
449     expectFalse($('newer-button').hidden);
450     expectTrue($('older-button').hidden);
452     ensureTimeWidthsEqual();
454     // Go back to the first page, and check that the same day headers are there.
455     $('newest-button').click();
456     var newDayHeaders = document.querySelectorAll('.day');
457     expectEquals(2, newDayHeaders.length);
459     expectNotEquals(newDayHeaders[0].textContent,
460                     newDayHeaders[1].textContent);
461     expectEquals(dayHeaders[0].textContent, newDayHeaders[0].textContent);
462     expectEquals(dayHeaders[1].textContent, newDayHeaders[1].textContent);
464     testDone();
465   });
469  * Test bulk deletion of history entries.
470  * Disabled because it is currently very flaky on the Windows XP bot.
471  */
472 TEST_F('HistoryWebUITest', 'DISABLED_bulkDeletion', function() {
473   var checkboxes = document.querySelectorAll(
474       '#results-display input[type=checkbox]');
476   // Immediately confirm the history deletion.
477   confirmDeletion = function(okCallback, cancelCallback) {
478     okCallback();
479   };
481   // The "remove" button should be initially disabled.
482   var removeButton = $('remove-selected');
483   expectTrue(removeButton.disabled);
485   checkboxes[0].click();
486   expectFalse(removeButton.disabled);
488   var firstEntry = document.querySelector('.title a').textContent;
489   removeButton.click();
491   // After deletion, expect the results to be reloaded.
492   waitForCallback('historyResult', function() {
493     expectNotEquals(document.querySelector('.title a').textContent, firstEntry);
494     expectTrue(removeButton.disabled);
496     // Delete the first 3 entries.
497     checkboxes = document.querySelectorAll(
498         '#results-display input[type=checkbox]');
499     checkboxes[0].click();
500     checkboxes[1].click();
501     checkboxes[2].click();
502     expectFalse(removeButton.disabled);
504     var nextEntry = document.querySelectorAll('.title a')[3];
505     removeButton.click();
506     waitForCallback('historyResult', function() {
507       // The next entry after the deleted ones should now be the first.
508       expectEquals(document.querySelector('.title a').textContent,
509                    nextEntry.textContent);
510       testDone();
511     });
512   });
516  * Test selecting multiple entries using shift click.
517  * Disabled due to time out on all platforms: crbug/375910
518  */
519 TEST_F('HistoryWebUITest', 'DISABLED_multipleSelect', function() {
520   var checkboxes = document.querySelectorAll(
521       '#results-display input[type=checkbox]');
523   var getAllChecked = function() {
524     return Array.prototype.slice.call(document.querySelectorAll(
525         '#results-display input[type=checkbox]:checked'));
526   };
528   // Make sure that nothing is checked.
529   expectEquals(0, getAllChecked().length);
531   var shiftClick = function(el) {
532     el.dispatchEvent(new MouseEvent('click', { shiftKey: true }));
533   };
535   // Check the start.
536   shiftClick($('checkbox-4'));
537   // And the end.
538   shiftClick($('checkbox-9'));
540   // See if they are checked.
541   var checked = getAllChecked();
542   expectEquals(6, checked.length);
543   checkInterval(checked, 4, 9);
545   // Extend the selection.
546   shiftClick($('checkbox-14'));
548   checked = getAllChecked();
549   expectEquals(11, checked.length);
550   checkInterval(checked, 4, 14);
552   // Now do a normal click on a higher ID box and a shift click on a lower ID
553   // one (test the other way around).
554   $('checkbox-24').click();
555   shiftClick($('checkbox-19'));
557   checked = getAllChecked();
558   expectEquals(17, checked.length);
559   // First set of checkboxes (11).
560   checkInterval(checked, 4, 14);
561   // Second set (6).
562   checkInterval(checked.slice(11), 19, 24);
564   // Test deselection.
565   $('checkbox-26').click();
566   shiftClick($('checkbox-20'));
568   checked = getAllChecked();
569   // checkbox-20 to checkbox-24 should be deselected now.
570   expectEquals(12, checked.length);
571   // First set of checkboxes (11).
572   checkInterval(checked, 4, 14);
573   // Only checkbox-19 should still be selected.
574   expectEquals('checkbox-19', checked[11].id);
576   testDone();
579 TEST_F('HistoryWebUITest', 'DISABLED_searchHistory', function() {
580   var getResultCount = function() {
581     return document.querySelectorAll('.entry').length;
582   };
583   // See that all the elements are there.
584   expectEquals(RESULTS_PER_PAGE, getResultCount());
586   // See that the search works.
587   $('search-field').value = 'Thu Oct 02 2008';
588   $('search-button').click();
590   waitForCallback('historyResult', function() {
591     expectEquals(31, getResultCount());
593     // Clear the search.
594     $('search-field').value = '';
595     $('search-button').click();
596     waitForCallback('historyResult', function() {
597       expectEquals(RESULTS_PER_PAGE, getResultCount());
598       testDone();
599     });
600   });
603 function setPageState(searchText, page, groupByDomain, range, offset) {
604   window.location = '#' + PageState.getHashString(
605       searchText, page, groupByDomain, range, offset);
608 function RangeHistoryWebUITest() {}
610 RangeHistoryWebUITest.prototype = {
611   __proto__: HistoryWebUITest.prototype,
613   /** @override */
614   preLoad: function() {
615     HistoryWebUITest.prototype.preLoad.call(this);
616     // Repeat the domain visits every 4 days. The nested lists contain the
617     // domain suffixes for the visits in a day.
618     var domainSuffixByDay = [
619       [1, 2, 3, 4],
620       [1, 2, 2, 3],
621       [1, 2, 1, 2],
622       [1, 1, 1, 1]
623     ];
625     var buildDomainUrl = function(timestamp) {
626       var d = new Date(timestamp);
627       // Repeat the same setup of domains every 4 days.
628       var day = d.getDate() % 4;
629       // Assign an entry for every 6 hours so that we get 4 entries per day
630       // maximum.
631       var visitInDay = Math.floor(d.getHours() / 6);
632       return 'http://google' + domainSuffixByDay[day][visitInDay] + '.com/' +
633           timestamp;
634     };
636     // Prepare a list of fake history results. Start the results on
637     // 11:00 PM on May 2, 2012 and add 4 results every day (one result every 6
638     // hours).
639     var timestamp = new Date(2012, 4, 2, 23, 0).getTime();
640     this.today = new Date(2012, 4, 2);
641     this.fakeHistory_ = [];
643     // Put in 2 days for May and 30 days for April so the results span over
644     // the month limit.
645     for (var i = 0; i < 4 * 32; i++) {
646       this.fakeHistory_.push(
647           createHistoryEntry(timestamp, buildDomainUrl(timestamp)));
648       timestamp -= 6 * 60 * 60 * 1000;
649     }
651     // Leave March empty.
652     timestamp -= 31 * 24 * 3600 * 1000;
654     // Put results in February.
655     for (var i = 0; i < 29 * 4; i++) {
656       this.fakeHistory_.push(
657           createHistoryEntry(timestamp, buildDomainUrl(timestamp)));
658       timestamp -= 6 * 60 * 60 * 1000;
659     }
660   },
662   setUp: function() {
663     // Show the filter controls as if the command line switch was active.
664     $('top-container').hidden = true;
665     $('history-page').classList.add('big-topbar-page');
666     $('filter-controls').hidden = false;
667     expectFalse($('filter-controls').hidden);
668   },
672  * Disabled due intermitent failures on multiple OSes http://crbug.com/377338
673  */
674 TEST_F('RangeHistoryWebUITest', 'DISABLED_allView', function() {
675   // Check that we start off in the all time view.
676   expectTrue($('timeframe-controls').querySelector('input').checked);
677   // See if the correct number of days is shown.
678   var dayHeaders = document.querySelectorAll('.day');
679   assertEquals(Math.ceil(RESULTS_PER_PAGE / 4), dayHeaders.length);
680   testDone();
684  * Checks whether the domains in a day are ordered decreasingly.
685  * @param {Element} element Ordered list containing the grouped domains for a
686  *     day.
687  */
688 function checkGroupedVisits(element) {
689   // The history page contains the number of visits next to a domain in
690   // parentheses (e.g. 'google.com (5)'). This function extracts that number
691   // and returns it.
692   var getNumberVisits = function(element) {
693     return parseInt(element.textContent.replace(/\D/g, ''), 10);
694   };
696   // Read the number of visits from each domain and make sure that it is lower
697   // than or equal to the number of visits from the previous domain.
698   var domainEntries = element.querySelectorAll('.number-visits');
699   var currentNumberOfVisits = getNumberVisits(domainEntries[0]);
700   for (var j = 1; j < domainEntries.length; j++) {
701     var numberOfVisits = getNumberVisits(domainEntries[j]);
702     assertTrue(currentNumberOfVisits >= numberOfVisits);
703     currentNumberOfVisits = numberOfVisits;
704   }
707 // Times out on Mac and Win: http://crbug.com/336845
708 TEST_F('RangeHistoryWebUITest', 'DISABLED_weekViewGrouped', function() {
709   // Change to weekly view.
710   setPageState('', 0, HistoryModel.Range.WEEK, 0);
711   waitForCallback('historyResult', function() {
712     // See if the correct number of days is still shown.
713     var dayResults = document.querySelectorAll('.day-results');
714     assertEquals(7, dayResults.length);
716     // Check whether the results are ordered by visits.
717     for (var i = 0; i < dayResults.length; i++)
718       checkGroupedVisits(dayResults[i]);
720     ensureTimeWidthsEqual();
722     testDone();
723   });
726 // Times out on Mac and Win: http://crbug.com/336845
727 TEST_F('RangeHistoryWebUITest', 'DISABLED_monthViewGrouped', function() {
728   // Change to monthly view.
729   setPageState('', 0, HistoryModel.Range.MONTH, 0);
730   waitForCallback('historyResult', function() {
731     // See if the correct number of days is shown.
732     var monthResults = document.querySelectorAll('.month-results');
733     assertEquals(1, monthResults.length);
735     checkGroupedVisits(monthResults[0]);
736     ensureTimeWidthsEqual();
738     testDone();
739   });
742 // Times out on Mac: http://crbug.com/336845
743 TEST_F('RangeHistoryWebUITest', 'DISABLED_monthViewEmptyMonth', function() {
744   // Change to monthly view.
745   setPageState('', 0, HistoryModel.Range.MONTH, 2);
747   waitForCallback('historyResult', function() {
748     // See if the correct number of days is shown.
749     var resultsDisplay = $('results-display');
750     assertEquals(0, resultsDisplay.querySelectorAll('.months-results').length);
751     var noResults = loadTimeData.getString('noResults');
752     assertNotEquals(-1, $('results-header').textContent.indexOf(noResults));
754     testDone();
755   });
759  * Fixture for History WebUI tests using the real history backend.
760  * @extends {BaseHistoryWebUITest}
761  * @constructor
762  */
763 function HistoryWebUIRealBackendTest() {}
765 HistoryWebUIRealBackendTest.prototype = {
766   __proto__: BaseHistoryWebUITest.prototype,
768   /** @override */
769   testGenPreamble: function() {
770     // Add some visits to the history database.
771     GEN('  AddPageToHistory(0, "http://google.com", "Google");');
772     GEN('  AddPageToHistory(1, "http://example.com", "Example");');
773     GEN('  AddPageToHistory(2, "http://google.com", "Google");');
775     // Add a visit on the next day.
776     GEN('  AddPageToHistory(36, "http://google.com", "Google");');
777   },
781  * Simple test that verifies that the correct entries are retrieved from the
782  * history database and displayed in the UI.
783  */
784 // Times out on Mac and Win: http://crbug.com/336845
785 TEST_F('HistoryWebUIRealBackendTest', 'DISABLED_basic', function() {
786   // Check that there are two days of entries, and three entries in total.
787   assertEquals(2, document.querySelectorAll('.day').length);
788   assertEquals(3, document.querySelectorAll('.entry').length);
790   testDone();
793 // Times out on Mac: http://crbug.com/336845
794 TEST_F('HistoryWebUIRealBackendTest',
795     'DISABLED_atLeastOneFocusable', function() {
796   var results = document.querySelectorAll('#results-display [tabindex="0"]');
797   expectGE(results.length, 1);
798   testDone();
801 // Times out on Mac: http://crbug.com/336845
802 TEST_F('HistoryWebUIRealBackendTest',
803     'DISABLED_deleteRemovesEntry', function() {
804   assertTrue(historyModel.deletingHistoryAllowed);
806   var visit = document.querySelector('.entry').visit;
807   visit.titleLink.focus();
808   assertEquals(visit.titleLink, document.activeElement);
810   var deleteKey = document.createEvent('KeyboardEvent');
811   deleteKey.initKeyboardEvent('keydown', true, true, window, 'U+007F');
812   assertEquals('U+007F', deleteKey.keyIdentifier);
814   assertFalse(historyModel.isDeletingVisits());
815   expectFalse(visit.titleLink.dispatchEvent(deleteKey));
816   expectTrue(historyModel.isDeletingVisits());
818   expectNotEquals(visit.dropDown, document.activeElement);
819   testDone();
823  * Test individual deletion of history entries.
824  */
825 TEST_F('HistoryWebUIRealBackendTest', 'singleDeletion', function() {
826   // Deletes the history entry represented by |entryElement|, and calls callback
827   // when the deletion is complete.
828   var removeEntry = function(entryElement, callback) {
829     var dropDownButton = entryElement.querySelector('.drop-down');
830     var removeMenuItem = $('remove-visit');
832     assertFalse(dropDownButton.disabled);
833     assertFalse(removeMenuItem.disabled);
835     waitForCallback('onEntryRemoved', callback);
837     cr.dispatchSimpleEvent(dropDownButton, 'mousedown');
839     var e = new Event('command', {bubbles: true});
840     e.command = removeMenuItem.command;
841     removeMenuItem.dispatchEvent(e);
842   };
844   var secondTitle = document.querySelectorAll('.entry a')[1].textContent;
845   var thirdTitle = document.querySelectorAll('.entry a')[2].textContent;
847   // historyDeleted() should not be called when deleting individual entries
848   // using the drop down.
849   waitForCallback('historyDeleted', function() {
850     testDone([false, 'historyDeleted() called when deleting single entry']);
851   });
853   expectEquals(2, document.querySelectorAll('.day').length);
855   // Delete the first entry. The previous second entry should now be the first.
856   removeEntry(document.querySelector('.entry'), function() {
857     expectEquals(secondTitle, document.querySelector('.entry a').textContent);
859     // After removing the first entry, its day header should also be gone.
860     expectEquals(1, document.querySelectorAll('.day').length);
862     // Delete another entry. The original third entry should now be the first.
863     removeEntry(document.querySelector('.entry'), function() {
864       expectEquals(thirdTitle, document.querySelector('.entry a').textContent);
865       testDone();
866     });
867   });
870 TEST_F('HistoryWebUIRealBackendTest', 'leftRightChangeFocus', function() {
871   var visit = document.querySelector('.entry').visit;
872   visit.titleLink.focus();
873   assertEquals(visit.titleLink, document.activeElement);
875   var right = document.createEvent('KeyboardEvent');
876   right.initKeyboardEvent('keydown', true, true, window, 'Right');
877   assertEquals('Right', right.keyIdentifier);
878   expectFalse(visit.titleLink.dispatchEvent(right));
880   assertEquals(visit.dropDown, document.activeElement);
882   var left = document.createEvent('KeyboardEvent');
883   left.initKeyboardEvent('keydown', true, true, window, 'Left');
884   assertEquals('Left', left.keyIdentifier);
885   expectFalse(visit.dropDown.dispatchEvent(left));
887   expectEquals(visit.titleLink, document.activeElement);
888   testDone();
891 TEST_F('HistoryWebUIRealBackendTest', 'showConfirmDialogAndCancel', function() {
892   waitForCallback('deleteComplete', function() {
893     testDone([false, "history deleted when it shouldn't have been"]);
894   });
896   document.querySelector('input[type=checkbox]').click();
897   $('remove-selected').click();
899   assertTrue($('alertOverlay').classList.contains('showing'));
900   assertFalse($('history-page').contains(document.activeElement));
902   var esc = document.createEvent('KeyboardEvent');
903   esc.initKeyboardEvent('keydown', true, true, window, 'U+001B');
905   document.documentElement.dispatchEvent(esc);
906   assertFalse($('alertOverlay').classList.contains('showing'));
908   testDone();
911 TEST_F('HistoryWebUIRealBackendTest', 'showConfirmDialogAndRemove', function() {
912   document.querySelector('input[type=checkbox]').click();
913   $('remove-selected').click();
915   assertTrue($('alertOverlay').classList.contains('showing'));
916   assertFalse($('history-page').contains(document.activeElement));
918   waitForCallback('deleteComplete', testDone);
920   var enter = document.createEvent('KeyboardEvent');
921   enter.initKeyboardEvent('keydown', true, true, window, 'Enter');
922   document.documentElement.dispatchEvent(enter);
923   assertFalse($('alertOverlay').classList.contains('showing'));
926 // Times out on Mac: http://crbug.com/336845
927 TEST_F('HistoryWebUIRealBackendTest',
928     'DISABLED_menuButtonActivatesOneRow', function() {
929   var entries = document.querySelectorAll('.entry');
930   assertEquals(3, entries.length);
931   assertTrue(entries[0].classList.contains(cr.ui.FocusRow.ACTIVE_CLASS));
932   assertTrue($('action-menu').hidden);
934   // Show the menu via mousedown on the menu button.
935   var menuButton = entries[2].querySelector('.menu-button');
936   menuButton.dispatchEvent(new MouseEvent('mousedown'));
937   expectFalse($('action-menu').hidden);
939   // Check that the active item has changed.
940   expectTrue(entries[2].classList.contains(cr.ui.FocusRow.ACTIVE_CLASS));
941   expectFalse(entries[0].classList.contains(cr.ui.FocusRow.ACTIVE_CLASS));
943   testDone();
946 // Flaky: http://crbug.com/527434
947 TEST_F('HistoryWebUIRealBackendTest',
948     'DISABLED_shiftClickActivatesOneRow', function () {
949   var entries = document.querySelectorAll('.entry');
950   assertEquals(3, entries.length);
951   assertTrue(entries[0].classList.contains(cr.ui.FocusRow.ACTIVE_CLASS));
953   entries[0].visit.checkBox.focus();
954   assertEquals(entries[0].visit.checkBox, document.activeElement);
956   entries[0].visit.checkBox.click();
957   assertTrue(entries[0].visit.checkBox.checked);
959   var entryBox = entries[2].querySelector('.entry-box');
960   entryBox.dispatchEvent(new MouseEvent('click', {shiftKey: true}));
961   assertTrue(entries[1].visit.checkBox.checked);
963   // Focus shouldn't have changed, but the checkbox should toggle.
964   expectEquals(entries[0].visit.checkBox, document.activeElement);
966   expectTrue(entries[0].classList.contains(cr.ui.FocusRow.ACTIVE_CLASS));
967   expectFalse(entries[2].classList.contains(cr.ui.FocusRow.ACTIVE_CLASS));
969   var shiftDown = new MouseEvent('mousedown', {shiftKey: true, bubbles: true});
970   entries[2].visit.checkBox.dispatchEvent(shiftDown);
971   expectEquals(entries[2].visit.checkBox, document.activeElement);
973   // 'focusin' events aren't dispatched while tests are run in batch (e.g.
974   // --test-launcher-jobs=2). Simulate this. TODO(dbeam): fix instead.
975   cr.dispatchSimpleEvent(document.activeElement, 'focusin', true, true);
977   expectFalse(entries[0].classList.contains(cr.ui.FocusRow.ACTIVE_CLASS));
978   expectTrue(entries[2].classList.contains(cr.ui.FocusRow.ACTIVE_CLASS));
980   testDone();
984  * Fixture for History WebUI testing when deletions are prohibited.
985  * @extends {HistoryWebUIRealBackendTest}
986  * @constructor
987  */
988 function HistoryWebUIDeleteProhibitedTest() {}
990 HistoryWebUIDeleteProhibitedTest.prototype = {
991   __proto__: HistoryWebUIRealBackendTest.prototype,
993   /** @override */
994   testGenPreamble: function() {
995     HistoryWebUIRealBackendTest.prototype.testGenPreamble.call(this);
996     GEN('  SetDeleteAllowed(false);');
997   },
1000 // Test UI when removing entries is prohibited.
1001 // Times out on Mac: http://crbug.com/336845
1002 TEST_F('HistoryWebUIDeleteProhibitedTest',
1003     'DISABLED_deleteProhibited', function() {
1004   // No checkboxes should be created.
1005   var checkboxes = document.querySelectorAll(
1006       '#results-display input[type=checkbox]');
1007   expectEquals(0, checkboxes.length);
1009   // The "remove" button should be disabled.
1010   var removeButton = $('remove-selected');
1011   expectTrue(removeButton.disabled);
1013   // The "Remove from history" drop-down item should be disabled.
1014   var removeVisit = $('remove-visit');
1015   expectTrue(removeVisit.disabled);
1017   testDone();
1020 TEST_F('HistoryWebUIDeleteProhibitedTest', 'atLeastOneFocusable', function() {
1021   var results = document.querySelectorAll('#results-display [tabindex="0"]');
1022   expectGE(results.length, 1);
1023   testDone();
1026 TEST_F('HistoryWebUIDeleteProhibitedTest', 'leftRightChangeFocus', function() {
1027   var visit = document.querySelector('.entry').visit;
1028   visit.titleLink.focus();
1029   assertEquals(visit.titleLink, document.activeElement);
1031   var right = document.createEvent('KeyboardEvent');
1032   right.initKeyboardEvent('keydown', true, true, window, 'Right');
1033   assertEquals('Right', right.keyIdentifier);
1034   expectFalse(visit.titleLink.dispatchEvent(right));
1036   assertEquals(visit.dropDown, document.activeElement);
1038   var left = document.createEvent('KeyboardEvent');
1039   left.initKeyboardEvent('keydown', true, true, window, 'Left');
1040   assertEquals('Left', left.keyIdentifier);
1041   expectFalse(visit.dropDown.dispatchEvent(left));
1043   expectEquals(visit.titleLink, document.activeElement);
1044   testDone();
1047 TEST_F('HistoryWebUIDeleteProhibitedTest', 'deleteIgnored', function() {
1048   assertFalse(historyModel.deletingHistoryAllowed);
1050   var visit = document.querySelector('.entry').visit;
1051   visit.titleLink.focus();
1052   assertEquals(visit.titleLink, document.activeElement);
1054   var deleteKey = document.createEvent('KeyboardEvent');
1055   deleteKey.initKeyboardEvent('keydown', true, true, window, 'U+007F');
1056   assertEquals('U+007F', deleteKey.keyIdentifier);
1058   assertFalse(historyModel.isDeletingVisits());
1059   expectTrue(visit.titleLink.dispatchEvent(deleteKey));
1060   expectFalse(historyModel.isDeletingVisits());
1062   expectEquals(visit.titleLink, document.activeElement);
1063   testDone();
1067  * Fixture for History WebUI testing IDN.
1068  * @extends {BaseHistoryWebUITest}
1069  * @constructor
1070  */
1071 function HistoryWebUIIDNTest() {}
1073 HistoryWebUIIDNTest.prototype = {
1074   __proto__: BaseHistoryWebUITest.prototype,
1076   /** @override */
1077   testGenPreamble: function() {
1078     // Add some visits to the history database.
1079     GEN('  AddPageToHistory(0, "http://xn--d1abbgf6aiiy.xn--p1ai/",' +
1080         ' "Some");');
1082     // Clear AcceptLanguages to get domain in unicode.
1083     GEN('  ClearAcceptLanguages();');
1084   },
1088  * Simple test that verifies that the correct entries are retrieved from the
1089  * history database and displayed in the UI.
1090  */
1091 // Times out on Mac: http://crbug.com/336845
1092 TEST_F('HistoryWebUIIDNTest', 'DISABLED_basic', function() {
1093   // Check that there is only one entry and domain is in unicode.
1094   assertEquals(1, document.querySelectorAll('.domain').length);
1095   assertEquals("\u043f\u0440\u0435\u0437\u0438\u0434\u0435\u043d\u0442." +
1096                "\u0440\u0444", document.querySelector('.domain').textContent);
1098   testDone();
1102  * Fixture for a test that uses the real backend and tests how the history
1103  * page deals with odd schemes in URLs.
1104  * @extends {HistoryWebUIRealBackendTest}
1105  */
1106 function HistoryWebUIWithSchemesTest() {}
1108 HistoryWebUIWithSchemesTest.prototype = {
1109   __proto__: HistoryWebUIRealBackendTest.prototype,
1111   /** @override */
1112   testGenPreamble: function() {
1113     // Add a bunch of entries on the same day, including some weird schemes.
1114     GEN('  AddPageToHistory(12, "http://google.com", "Google");');
1115     GEN('  AddPageToHistory(13, "file:///tmp/foo", "");');
1116     GEN('  AddPageToHistory(14, "mailto:chromium@chromium.org", "");');
1117     GEN('  AddPageToHistory(15, "tel:555123456", "");');
1118   },
1120   setUp: function() {
1121     // Show the filter controls as if the command line switch was active.
1122     $('top-container').hidden = true;
1123     $('history-page').classList.add('big-topbar-page');
1124     $('filter-controls').hidden = false;
1125     expectFalse($('filter-controls').hidden);
1126   },
1129 TEST_F('HistoryWebUIWithSchemesTest', 'groupingWithSchemes', function() {
1130   // Switch to the week view.
1131   $('timeframe-controls').querySelectorAll('input')[1].click();
1132   waitForCallback('historyResult', function() {
1133     // Each URL should be organized under a different "domain".
1134     expectEquals(document.querySelectorAll('.entry').length, 4);
1135     expectEquals(document.querySelectorAll('.site-domain-wrapper').length, 4);
1136     testDone();
1137   });