- add sources.
[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   isAsync: true,
147 };
148
149 /**
150  * Fixture for History WebUI testing which returns some fake history results
151  * to the frontend. Other fixtures that want to stub out calls to the backend
152  * can extend this one.
153  * @extends {BaseHistoryWebUITest}
154  * @constructor
155  */
156 function HistoryWebUIFakeBackendTest() {
157 }
158
159 HistoryWebUIFakeBackendTest.prototype = {
160   __proto__: BaseHistoryWebUITest.prototype,
161
162   /**
163    * Register handlers to stub out calls to the history backend.
164    * @override
165    */
166   preLoad: function() {
167     this.registerMockHandler_(
168         'queryHistory', this.queryHistoryStub_.bind(this));
169   },
170
171   /**
172    * Register a mock handler for a message to the history backend.
173    * @param handlerName The name of the message to mock.
174    * @param handler The mock message handler function.
175    */
176   registerMockHandler_: function(handlerName, handler) {
177     // Mock4JS doesn't pass in the actual arguments to the stub, but it _will_
178     // pass the original args to the matcher object. SaveMockArguments acts as
179     // a proxy for another matcher, but keeps track of all the arguments it was
180     // asked to match.
181     var savedArgs = new SaveMockArguments();
182
183     this.makeAndRegisterMockHandler([handlerName]);
184     this.mockHandler.stubs()[handlerName](savedArgs.match(ANYTHING)).will(
185         callFunctionWithSavedArgs(savedArgs, handler));
186   },
187
188   /**
189    * Default stub for the queryHistory message to the history backend.
190    * Simulates an empty history database. Override this to customize this
191    * behavior for particular tests.
192    * @param {Array} arguments The original arguments to queryHistory.
193    */
194   queryHistoryStub_: function(args) {
195     callFrontendAsync(
196         'historyResult', { term: args[0], finished: true }, []);
197   }
198 };
199
200 function queryHistoryImpl(args, beginTime, history) {
201   var searchText = args[0];
202   var offset = args[1];
203   var range = args[2];
204   var endTime = args[3] || Number.MAX_VALUE;
205   var maxCount = args[4];
206
207   var results = [];
208   if (searchText) {
209     for (var k = 0; k < history.length; k++) {
210       // Search only by title in this stub.
211       if (history[k].title.indexOf(searchText) != -1)
212         results.push(history[k]);
213     }
214   } else {
215     results = history;
216   }
217
218   // Advance past all entries newer than the specified end time.
219   var i = 0;
220   // Finished is set from the history database so this behavior may not be
221   // completely identical.
222   var finished = true;
223   while (i < results.length && results[i].time >= endTime)
224     ++i;
225
226   if (beginTime) {
227     var j = i;
228     while (j < results.length && results[j].time >= beginTime)
229       ++j;
230
231     finished = (j == results.length);
232     results = results.slice(i, j);
233   } else {
234     results = results.slice(i);
235   }
236
237   if (maxCount) {
238     finished = (maxCount >= results.length);
239     results = results.slice(0, maxCount);
240   }
241
242   var queryStartTime = '';
243   var queryEndTime = '';
244   if (results.length) {
245     queryStartTime = results[results.length - 1].dateRelativeDay;
246     queryEndTime = results[0].dateRelativeDay;
247   } else if (beginTime) {
248     queryStartTime = Date(beginTime);
249     queryEndTime = Date(endTime);
250   }
251
252   callFrontendAsync(
253       'historyResult',
254       {
255         term: searchText,
256         finished: finished,
257         queryStartTime: queryStartTime,
258         queryEndTime: queryEndTime
259       },
260       results);
261 }
262
263 /**
264  * Fixture for History WebUI testing which returns some fake history results
265  * to the frontend.
266  * @extends {HistoryWebUIFakeBackendTest}
267  * @constructor
268  */
269 function HistoryWebUITest() {}
270
271 HistoryWebUITest.prototype = {
272   __proto__: HistoryWebUIFakeBackendTest.prototype,
273
274   preLoad: function() {
275     HistoryWebUIFakeBackendTest.prototype.preLoad.call(this);
276
277     this.registerMockHandler_(
278         'removeVisits', this.removeVisitsStub_.bind(this));
279
280     // Prepare a list of fake history results. The entries will begin at
281     // 1:00 AM on Sept 2, 2008, and will be spaced two minutes apart.
282     var timestamp = new Date(2008, 9, 2, 1, 0).getTime();
283     this.fakeHistory_ = [];
284
285     for (var i = 0; i < TOTAL_RESULT_COUNT; i++) {
286       this.fakeHistory_.push(
287           createHistoryEntry(timestamp, 'http://google.com/' + timestamp));
288       timestamp -= 2 * 60 * 1000;  // Next visit is two minutes earlier.
289     }
290   },
291
292   /**
293    * Stub for the 'queryHistory' message to the history backend.
294    * Simulates a history database using the fake history data that is
295    * initialized in preLoad().
296    * @param {Array} arguments The original arguments to queryHistory.
297    */
298   queryHistoryStub_: function(args) {
299     var searchText = args[0];
300     var offset = args[1];
301     var range = args[2];
302     var endTime = args[3] || Number.MAX_VALUE;
303     var maxCount = args[4];
304     if (range == HistoryModel.Range.ALL_TIME) {
305       queryHistoryImpl(args, null, this.fakeHistory_);
306       return;
307     }
308     if (range == HistoryModel.Range.WEEK)
309       var interval = setQueryTimeInWeeks(offset, this.today);
310     else
311       var interval = setQueryTimeInMonths(offset, this.today);
312
313     args[3] = interval.endTime.getTime();
314     queryHistoryImpl(args, interval.beginTime.getTime(), this.fakeHistory_);
315   },
316
317   /**
318    * Stub for the 'removeVisits' message to the history backend.
319    * This will modify the fake history data in the test instance, so that
320    * further 'queryHistory' messages will not contain the deleted entries.
321    * @param {Array} arguments The original arguments to removeVisits.
322    */
323   removeVisitsStub_: function(args) {
324     for (var i = 0; i < args.length; ++i) {
325       var url = args[i].url;
326       var timestamps = args[i].timestamps;
327       assertEquals(timestamps.length, 1);
328       this.removeVisitsToUrl_(url, new Date(timestamps[0]));
329     }
330     callFrontendAsync('deleteComplete');
331   },
332
333   /**
334    * Removes any visits to |url| on the same day as |date| from the fake
335    * history data.
336    * @param {string} url
337    * @param {Date} date
338    */
339   removeVisitsToUrl_: function(url, date) {
340     var day = date.toDateString();
341     var newHistory = [];
342     for (var i = 0, visit; visit = this.fakeHistory_[i]; ++i) {
343       if (url != visit.url || visit.dateRelativeDay != day)
344         newHistory.push(visit);
345     }
346     this.fakeHistory_ = newHistory;
347   }
348 };
349
350 TEST_F('HistoryWebUIFakeBackendTest', 'emptyHistory', function() {
351   expectTrue($('newest-button').hidden);
352   expectTrue($('newer-button').hidden);
353   expectTrue($('older-button').hidden);
354   testDone();
355 });
356
357 TEST_F('HistoryWebUITest', 'basicTest', function() {
358   var resultCount = document.querySelectorAll('.entry').length;
359
360   // Check that there are two days of entries.
361   var dayHeaders = document.querySelectorAll('.day');
362   assertEquals(2, dayHeaders.length);
363   expectNotEquals(dayHeaders[0].textContent, dayHeaders[1].textContent);
364
365   // Check that the entries in each day are time-ordered, and that no
366   // duplicate URLs appear on a given day.
367   var urlsByDay = {};
368   var lastDate = new Date();
369   for (var day = 0; day < dayHeaders.length; ++day) {
370     var dayTitle = dayHeaders[day].textContent;
371     var dayResults = document.querySelectorAll('.day-results')[day];
372     var entries = dayResults.querySelectorAll('.entry');
373     expectGT(entries.length, 0);
374
375     for (var i = 0, entry; entry = entries[i]; ++i) {
376       var time = entry.querySelector('.time').textContent;
377       expectGT(time.length, 0);
378
379       var date = new Date(dayTitle + ' ' + time);
380       expectGT(lastDate, date);
381       lastDate = date;
382
383       // Ensure it's not a duplicate URL for this day.
384       var dayAndUrl = day + entry.querySelector('a').href;
385       expectFalse(urlsByDay.hasOwnProperty(dayAndUrl));
386       urlsByDay[dayAndUrl] = dayAndUrl;
387
388       // Reconstruct the entry date from the title, and ensure that it's
389       // consistent with the date header and with the time.
390       var entryDate = new Date(entry.querySelector('.title').textContent);
391       expectEquals(entryDate.getYear(), date.getYear());
392       expectEquals(entryDate.getMonth(), date.getMonth());
393       expectEquals(entryDate.getDay(), date.getDay());
394       expectEquals(entryDate.getHours(), date.getHours());
395       expectEquals(entryDate.getMinutes(), date.getMinutes());
396     }
397   }
398
399   // Check that there are 3 page navigation links and that only the "Older"
400   // link is visible.
401   expectEquals(3, document.querySelectorAll('.link-button').length);
402   expectTrue($('newest-button').hidden);
403   expectTrue($('newer-button').hidden);
404   expectFalse($('older-button').hidden);
405
406   // Go to the next page.
407   $('older-button').click();
408   waitForCallback('historyResult', function() {
409     resultCount += document.querySelectorAll('.entry').length;
410
411     // Check that the two pages include all of the entries.
412     expectEquals(TOTAL_RESULT_COUNT, resultCount);
413
414     // Check that the day header was properly continued -- the header for the
415     // last day on the first page should be a substring of the header on the
416     // second page. E.g. "Wed, Oct 8, 2008" and "Web, Oct 8, 2008 - cont'd".
417     var newDayHeaders = document.querySelectorAll('.day');
418     expectEquals(1, newDayHeaders.length);
419     expectEquals(0,
420         newDayHeaders[0].textContent.indexOf(dayHeaders[1].textContent));
421
422     // Check that the "Newest" and "Newer" links are now visible, but the
423     // "Older" link is hidden.
424     expectEquals(3, document.querySelectorAll('.link-button').length);
425     expectFalse($('newest-button').hidden);
426     expectFalse($('newer-button').hidden);
427     expectTrue($('older-button').hidden);
428
429     // Go back to the first page, and check that the same day headers are there.
430     $('newest-button').click();
431     var newDayHeaders = document.querySelectorAll('.day');
432     expectEquals(2, newDayHeaders.length);
433
434     expectNotEquals(newDayHeaders[0].textContent,
435                     newDayHeaders[1].textContent);
436     expectEquals(dayHeaders[0].textContent, newDayHeaders[0].textContent);
437     expectEquals(dayHeaders[1].textContent, newDayHeaders[1].textContent);
438
439     testDone();
440   });
441 });
442
443 /**
444  * Test bulk deletion of history entries.
445  */
446 TEST_F('HistoryWebUITest', 'bulkDeletion', function() {
447   var checkboxes = document.querySelectorAll(
448       '#results-display input[type=checkbox]');
449
450   // Immediately confirm the history deletion.
451   confirmDeletion = function(okCallback, cancelCallback) {
452     okCallback();
453   };
454
455   // The "remove" button should be initially disabled.
456   var removeButton = $('remove-selected');
457   expectTrue(removeButton.disabled);
458
459   checkboxes[0].click();
460   expectFalse(removeButton.disabled);
461
462   var firstEntry = document.querySelector('.title a').textContent;
463   removeButton.click();
464
465   // After deletion, expect the results to be reloaded.
466   waitForCallback('historyResult', function() {
467     expectNotEquals(document.querySelector('.title a').textContent, firstEntry);
468     expectTrue(removeButton.disabled);
469
470     // Delete the first 3 entries.
471     checkboxes = document.querySelectorAll(
472         '#results-display input[type=checkbox]');
473     checkboxes[0].click();
474     checkboxes[1].click();
475     checkboxes[2].click();
476     expectFalse(removeButton.disabled);
477
478     var nextEntry = document.querySelectorAll('.title a')[3];
479     removeButton.click();
480     waitForCallback('historyResult', function() {
481       // The next entry after the deleted ones should now be the first.
482       expectEquals(document.querySelector('.title a').textContent,
483                    nextEntry.textContent);
484       testDone();
485     });
486   });
487 });
488
489 /**
490  * Test selecting multiple entries using shift click.
491  */
492 TEST_F('HistoryWebUITest', 'multipleSelect', function() {
493   var checkboxes = document.querySelectorAll(
494       '#results-display input[type=checkbox]');
495
496   var getAllChecked = function() {
497     return Array.prototype.slice.call(document.querySelectorAll(
498         '#results-display input[type=checkbox]:checked'));
499   };
500
501   // Make sure that nothing is checked.
502   expectEquals(0, getAllChecked().length);
503
504   var shiftClick = function(el) {
505     el.dispatchEvent(new MouseEvent('click', { shiftKey: true }));
506   };
507
508   // Check the start.
509   shiftClick($('checkbox-4'));
510   // And the end.
511   shiftClick($('checkbox-9'));
512
513   // See if they are checked.
514   var checked = getAllChecked();
515   expectEquals(6, checked.length);
516   checkInterval(checked, 4, 9);
517
518   // Extend the selection.
519   shiftClick($('checkbox-14'));
520
521   checked = getAllChecked();
522   expectEquals(11, checked.length);
523   checkInterval(checked, 4, 14);
524
525   // Now do a normal click on a higher ID box and a shift click on a lower ID
526   // one (test the other way around).
527   $('checkbox-24').click();
528   shiftClick($('checkbox-19'));
529
530   checked = getAllChecked();
531   expectEquals(17, checked.length);
532   // First set of checkboxes (11).
533   checkInterval(checked, 4, 14);
534   // Second set (6).
535   checkInterval(checked.slice(11), 19, 24);
536
537   // Test deselection.
538   $('checkbox-26').click();
539   shiftClick($('checkbox-20'));
540
541   checked = getAllChecked();
542   // checkbox-20 to checkbox-24 should be deselected now.
543   expectEquals(12, checked.length);
544   // First set of checkboxes (11).
545   checkInterval(checked, 4, 14);
546   // Only checkbox-19 should still be selected.
547   expectEquals('checkbox-19', checked[11].id);
548
549   testDone();
550 });
551
552 TEST_F('HistoryWebUITest', 'searchHistory', function() {
553   var getResultCount = function() {
554     return document.querySelectorAll('.entry').length;
555   };
556   // See that all the elements are there.
557   expectEquals(RESULTS_PER_PAGE, getResultCount());
558
559   // See that the search works.
560   $('search-field').value = 'Thu Oct 02 2008';
561   $('search-button').click();
562
563   waitForCallback('historyResult', function() {
564     expectEquals(31, getResultCount());
565
566     // Clear the search.
567     $('search-field').value = '';
568     $('search-button').click();
569     waitForCallback('historyResult', function() {
570       expectEquals(RESULTS_PER_PAGE, getResultCount());
571       testDone();
572     });
573   });
574 });
575
576 function setPageState(searchText, page, groupByDomain, range, offset) {
577   window.location = '#' + PageState.getHashString(
578       searchText, page, groupByDomain, range, offset);
579 }
580
581 function RangeHistoryWebUITest() {}
582
583 RangeHistoryWebUITest.prototype = {
584   __proto__: HistoryWebUITest.prototype,
585
586   /** @override */
587   preLoad: function() {
588     HistoryWebUITest.prototype.preLoad.call(this);
589     // Repeat the domain visits every 4 days. The nested lists contain the
590     // domain suffixes for the visits in a day.
591     var domainSuffixByDay = [
592       [1, 2, 3, 4],
593       [1, 2, 2, 3],
594       [1, 2, 1, 2],
595       [1, 1, 1, 1]
596     ];
597
598     var buildDomainUrl = function(timestamp) {
599       var d = new Date(timestamp);
600       // Repeat the same setup of domains every 4 days.
601       var day = d.getDate() % 4;
602       // Assign an entry for every 6 hours so that we get 4 entries per day
603       // maximum.
604       var visitInDay = Math.floor(d.getHours() / 6);
605       return 'http://google' + domainSuffixByDay[day][visitInDay] + '.com/' +
606           timestamp;
607     };
608
609     // Prepare a list of fake history results. Start the results on
610     // 11:00 PM on May 2, 2012 and add 4 results every day (one result every 6
611     // hours).
612     var timestamp = new Date(2012, 4, 2, 23, 0).getTime();
613     this.today = new Date(2012, 4, 2);
614     this.fakeHistory_ = [];
615
616     // Put in 2 days for May and 30 days for April so the results span over
617     // the month limit.
618     for (var i = 0; i < 4 * 32; i++) {
619       this.fakeHistory_.push(
620           createHistoryEntry(timestamp, buildDomainUrl(timestamp)));
621       timestamp -= 6 * 60 * 60 * 1000;
622     }
623
624     // Leave March empty.
625     timestamp -= 31 * 24 * 3600 * 1000;
626
627     // Put results in February.
628     for (var i = 0; i < 29 * 4; i++) {
629       this.fakeHistory_.push(
630           createHistoryEntry(timestamp, buildDomainUrl(timestamp)));
631       timestamp -= 6 * 60 * 60 * 1000;
632     }
633   },
634
635   setUp: function() {
636     // Show the filter controls as if the command line switch was active.
637     $('top-container').hidden = true;
638     $('history-page').classList.add('big-topbar-page');
639     $('filter-controls').hidden = false;
640     expectFalse($('filter-controls').hidden);
641   },
642 };
643
644 TEST_F('RangeHistoryWebUITest', 'allView', function() {
645   // Check that we start off in the all time view.
646   expectTrue($('timeframe-filter-all').checked);
647   // See if the correct number of days is shown.
648   var dayHeaders = document.querySelectorAll('.day');
649   assertEquals(Math.ceil(RESULTS_PER_PAGE / 4), dayHeaders.length);
650   testDone();
651 });
652
653 /**
654  * Checks whether the domains in a day are ordered decreasingly.
655  * @param {Element} element Ordered list containing the grouped domains for a
656  *     day.
657  */
658 function checkGroupedVisits(element) {
659   // The history page contains the number of visits next to a domain in
660   // parentheses (e.g. 'google.com (5)'). This function extracts that number
661   // and returns it.
662   var getNumberVisits = function(element) {
663     return parseInt(element.textContent.replace(/\D/g, ''), 10);
664   };
665
666   // Read the number of visits from each domain and make sure that it is lower
667   // than or equal to the number of visits from the previous domain.
668   var domainEntries = element.querySelectorAll('.number-visits');
669   var currentNumberOfVisits = getNumberVisits(domainEntries[0]);
670   for (var j = 1; j < domainEntries.length; j++) {
671     var numberOfVisits = getNumberVisits(domainEntries[j]);
672     assertTrue(currentNumberOfVisits >= numberOfVisits);
673     currentNumberOfVisits = numberOfVisits;
674   }
675 }
676
677 TEST_F('RangeHistoryWebUITest', 'weekViewGrouped', function() {
678   // Change to weekly view.
679   setPageState('', 0, HistoryModel.Range.WEEK, 0);
680   waitForCallback('historyResult', function() {
681     // See if the correct number of days is still shown.
682     var dayResults = document.querySelectorAll('.day-results');
683     assertEquals(7, dayResults.length);
684
685     // Check whether the results are ordered by visits.
686     for (var i = 0; i < dayResults.length; i++)
687       checkGroupedVisits(dayResults[i]);
688
689     testDone();
690   });
691 });
692
693 TEST_F('RangeHistoryWebUITest', 'monthViewGrouped', function() {
694   // Change to monthly view.
695   setPageState('', 0, HistoryModel.Range.MONTH, 0);
696   waitForCallback('historyResult', function() {
697     // See if the correct number of days is shown.
698     var monthResults = document.querySelectorAll('.month-results');
699     assertEquals(1, monthResults.length);
700
701     checkGroupedVisits(monthResults[0]);
702
703     testDone();
704   });
705 });
706
707 TEST_F('RangeHistoryWebUITest', 'monthViewEmptyMonth', function() {
708   // Change to monthly view.
709   setPageState('', 0, HistoryModel.Range.MONTH, 2);
710
711   waitForCallback('historyResult', function() {
712     // See if the correct number of days is shown.
713     var resultsDisplay = $('results-display');
714     assertEquals(0, resultsDisplay.querySelectorAll('.months-results').length);
715     assertEquals(1, resultsDisplay.querySelectorAll('div').length);
716
717     testDone();
718   });
719 });
720
721 /**
722  * Fixture for History WebUI tests using the real history backend.
723  * @extends {BaseHistoryWebUITest}
724  * @constructor
725  */
726 function HistoryWebUIRealBackendTest() {}
727
728 HistoryWebUIRealBackendTest.prototype = {
729   __proto__: BaseHistoryWebUITest.prototype,
730
731   /** @override */
732   testGenPreamble: function() {
733     // Add some visits to the history database.
734     GEN('  AddPageToHistory(0, "http://google.com", "Google");');
735     GEN('  AddPageToHistory(1, "http://example.com", "Example");');
736     GEN('  AddPageToHistory(2, "http://google.com", "Google");');
737
738     // Add a visit on the next day.
739     GEN('  AddPageToHistory(24, "http://google.com", "Google");');
740   },
741 };
742
743 /**
744  * Simple test that verifies that the correct entries are retrieved from the
745  * history database and displayed in the UI.
746  */
747 TEST_F('HistoryWebUIRealBackendTest', 'DISABLED_basic', function() {
748   // Check that there are two days of entries, and three entries in total.
749   assertEquals(2, document.querySelectorAll('.day').length);
750   assertEquals(3, document.querySelectorAll('.entry').length);
751
752   testDone();
753 });
754
755 /**
756  * Test individual deletion of history entries.
757  * Disabled because it fails on all platforms: crbug.com/242293
758  */
759 TEST_F('HistoryWebUIRealBackendTest', 'DISABLED_singleDeletion', function() {
760   // Deletes the history entry represented by |entryElement|, and calls callback
761   // when the deletion is complete.
762   var removeEntry = function(entryElement, callback) {
763     var dropDownButton = entryElement.querySelector('.drop-down');
764     var removeMenuItem = $('remove-visit');
765
766     assertFalse(dropDownButton.disabled);
767     assertFalse(removeMenuItem.disabled);
768
769     waitForCallback('removeNodeWithoutTransition', callback);
770
771     cr.dispatchSimpleEvent(dropDownButton, 'mousedown');
772     cr.dispatchSimpleEvent(removeMenuItem, 'activate');
773   };
774
775   var secondTitle = document.querySelectorAll('.entry a')[1].textContent;
776   var thirdTitle = document.querySelectorAll('.entry a')[2].textContent;
777
778   // historyDeleted() should not be called when deleting individual entries
779   // using the drop down.
780   waitForCallback('historyDeleted', function() {
781     testDone([false, 'historyDeleted() called when deleting single entry']);
782   });
783
784   // Delete the first entry. The previous second entry should now be the first.
785   removeEntry(document.querySelector('.entry'), function() {
786     expectEquals(document.querySelector('.entry a').textContent, secondTitle);
787
788     // Delete another entry. The original third entry should now be the first.
789     removeEntry(document.querySelector('.entry'), function() {
790       expectEquals(document.querySelector('.entry a').textContent, thirdTitle);
791       testDone();
792     });
793   });
794 });
795
796 /**
797  * Fixture for History WebUI testing when deletions are prohibited.
798  * @extends {HistoryWebUIRealBackendTest}
799  * @constructor
800  */
801 function HistoryWebUIDeleteProhibitedTest() {}
802
803 HistoryWebUIDeleteProhibitedTest.prototype = {
804   __proto__: HistoryWebUIRealBackendTest.prototype,
805
806   /** @override */
807   testGenPreamble: function() {
808     HistoryWebUIRealBackendTest.prototype.testGenPreamble.call(this);
809     GEN('  SetDeleteAllowed(false);');
810   },
811 };
812
813 // Test UI when removing entries is prohibited.
814 TEST_F('HistoryWebUIDeleteProhibitedTest', 'deleteProhibited', function() {
815   // No checkboxes should be created.
816   var checkboxes = document.querySelectorAll(
817       '#results-display input[type=checkbox]');
818   expectEquals(0, checkboxes.length);
819
820   // The "remove" button should be disabled.
821   var removeButton = $('remove-selected');
822   expectTrue(removeButton.disabled);
823
824   // The "Remove from history" drop-down item should be disabled.
825   var removeVisit = $('remove-visit');
826   expectTrue(removeVisit.disabled);
827
828   // Attempting to remove items anyway should fail.
829   historyModel.removeVisitsFromHistory(historyModel.visits_, function () {
830     // The callback is only called on success.
831     testDone([false, 'Delete succeeded even though it was prohibited.']);
832   });
833   waitForCallback('deleteFailed', testDone);
834 });
835
836 /**
837  * Fixture for History WebUI testing IDN.
838  * @extends {BaseHistoryWebUITest}
839  * @constructor
840  */
841 function HistoryWebUIIDNTest() {}
842
843 HistoryWebUIIDNTest.prototype = {
844   __proto__: BaseHistoryWebUITest.prototype,
845
846   /** @override */
847   testGenPreamble: function() {
848     // Add some visits to the history database.
849     GEN('  AddPageToHistory(0, "http://xn--d1abbgf6aiiy.xn--p1ai/",' +
850         ' "Some");');
851
852     // Clear AcceptLanguages to get domain in unicode.
853     GEN('  ClearAcceptLanguages();');
854   },
855 };
856
857 /**
858  * Simple test that verifies that the correct entries are retrieved from the
859  * history database and displayed in the UI.
860  */
861 TEST_F('HistoryWebUIIDNTest', 'basic', function() {
862   // Check that there is only one entry and domain is in unicode.
863   assertEquals(1, document.querySelectorAll('.domain').length);
864   assertEquals("\u043f\u0440\u0435\u0437\u0438\u0434\u0435\u043d\u0442." +
865                "\u0440\u0444", document.querySelector('.domain').textContent);
866
867   testDone();
868 });