diff options
Diffstat (limited to 'toolkit/components/places/tests/queries/test_containersQueries_sorting.js')
-rw-r--r-- | toolkit/components/places/tests/queries/test_containersQueries_sorting.js | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/toolkit/components/places/tests/queries/test_containersQueries_sorting.js b/toolkit/components/places/tests/queries/test_containersQueries_sorting.js new file mode 100644 index 0000000000..9cdc0f2a52 --- /dev/null +++ b/toolkit/components/places/tests/queries/test_containersQueries_sorting.js @@ -0,0 +1,492 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Testing behavior of bug 473157 + * "Want to sort history in container view without sorting the containers" + * and regression bug 488783 + * Tags list no longer sorted (alphabetized). + * This test is for global testing sorting containers queries. + */ + +// Globals and Constants + +var resultTypes = [ + { + value: Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY, + name: "RESULTS_AS_DATE_QUERY", + }, + { + value: Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY, + name: "RESULTS_AS_SITE_QUERY", + }, + { + value: Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY, + name: "RESULTS_AS_DATE_SITE_QUERY", + }, + { + value: Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAGS_ROOT, + name: "RESULTS_AS_TAGS_ROOT", + }, +]; + +var sortingModes = [ + { + value: Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING, + name: "SORT_BY_TITLE_ASCENDING", + }, + { + value: Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING, + name: "SORT_BY_TITLE_DESCENDING", + }, + { + value: Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING, + name: "SORT_BY_DATE_ASCENDING", + }, + { + value: Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING, + name: "SORT_BY_DATE_DESCENDING", + }, + { + value: Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_ASCENDING, + name: "SORT_BY_DATEADDED_ASCENDING", + }, + { + value: Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING, + name: "SORT_BY_DATEADDED_DESCENDING", + }, +]; + +// These pages will be added from newest to oldest and from less visited to most +// visited. +var pages = [ + "http://www.mozilla.org/c/", + "http://www.mozilla.org/a/", + "http://www.mozilla.org/b/", + "http://www.mozilla.com/c/", + "http://www.mozilla.com/a/", + "http://www.mozilla.com/b/", +]; + +var tags = ["mozilla", "Development", "test"]; + +// Test Runner + +/** + * Enumerates all the sequences of the cartesian product of the arrays contained + * in aSequences. Examples: + * + * cartProd([[1, 2, 3], ["a", "b"]], callback); + * // callback is called 3 * 2 = 6 times with the following arrays: + * // [1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"] + * + * cartProd([["a"], [1, 2, 3], ["X", "Y"]], callback); + * // callback is called 1 * 3 * 2 = 6 times with the following arrays: + * // ["a", 1, "X"], ["a", 1, "Y"], ["a", 2, "X"], ["a", 2, "Y"], + * // ["a", 3, "X"], ["a", 3, "Y"] + * + * cartProd([[1], [2], [3], [4]], callback); + * // callback is called 1 * 1 * 1 * 1 = 1 time with the following array: + * // [1, 2, 3, 4] + * + * cartProd([], callback); + * // callback is 0 times + * + * cartProd([[1, 2, 3, 4]], callback); + * // callback is called 4 times with the following arrays: + * // [1], [2], [3], [4] + * + * @param aSequences + * an array that contains an arbitrary number of arrays + * @param aCallback + * a function that is passed each sequence of the product as it's + * computed + * @return the total number of sequences in the product + */ +function cartProd(aSequences, aCallback) { + if (aSequences.length === 0) { + return 0; + } + + // For each sequence in aSequences, we maintain a pointer (an array index, + // really) to the element we're currently enumerating in that sequence + var seqEltPtrs = aSequences.map(i => 0); + + var numProds = 0; + var done = false; + while (!done) { + numProds++; + + // prod = sequence in product we're currently enumerating + var prod = []; + for (var i = 0; i < aSequences.length; i++) { + prod.push(aSequences[i][seqEltPtrs[i]]); + } + aCallback(prod); + + // The next sequence in the product differs from the current one by just a + // single element. Determine which element that is. We advance the + // "rightmost" element pointer to the "right" by one. If we move past the + // end of that pointer's sequence, reset the pointer to the first element + // in its sequence and then try the sequence to the "left", and so on. + + // seqPtr = index of rightmost input sequence whose element pointer is not + // past the end of the sequence + var seqPtr = aSequences.length - 1; + while (!done) { + // Advance the rightmost element pointer. + seqEltPtrs[seqPtr]++; + + // The rightmost element pointer is past the end of its sequence. + if (seqEltPtrs[seqPtr] >= aSequences[seqPtr].length) { + seqEltPtrs[seqPtr] = 0; + seqPtr--; + + // All element pointers are past the ends of their sequences. + if (seqPtr < 0) { + done = true; + } + } else { + break; + } + } + } + return numProds; +} + +/** + * Test a query based on passed-in options. + * + * @param aSequence + * array of options we will use to query. + */ +function test_query_callback(aSequence) { + Assert.equal(aSequence.length, 2); + var resultType = aSequence[0]; + var sortingMode = aSequence[1]; + print( + "\n\n*** Testing default sorting for resultType (" + + resultType.name + + ") and sortingMode (" + + sortingMode.name + + ")" + ); + + // Skip invalid combinations sorting queries by none. + if ( + resultType.value == Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAGS_ROOT && + (sortingMode.value == Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING || + sortingMode.value == Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING) + ) { + // This is a bookmark query, we can't sort by visit date. + sortingMode.value = Ci.nsINavHistoryQueryOptions.SORT_BY_NONE; + } + if ( + resultType.value == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY || + resultType.value == Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY || + resultType.value == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY + ) { + // This is an history query, we can't sort by date added. + if ( + sortingMode.value == + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_ASCENDING || + sortingMode.value == + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING + ) { + sortingMode.value = Ci.nsINavHistoryQueryOptions.SORT_BY_NONE; + } + } + + // Create a new query with required options. + var query = PlacesUtils.history.getNewQuery(); + var options = PlacesUtils.history.getNewQueryOptions(); + options.resultType = resultType.value; + options.sortingMode = sortingMode.value; + + // Compare resultset with expectedData. + var result = PlacesUtils.history.executeQuery(query, options); + var root = result.root; + root.containerOpen = true; + + if ( + resultType.value == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY || + resultType.value == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY + ) { + // Date containers are always sorted by date descending. + check_children_sorting( + root, + Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING + ); + } else { + check_children_sorting(root, sortingMode.value); + } + + // Now Check sorting of the first child container. + var container = root + .getChild(0) + .QueryInterface(Ci.nsINavHistoryContainerResultNode); + container.containerOpen = true; + + if ( + resultType.value == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY + ) { + // Has more than one level of containers, first we check the sorting of + // the first level (site containers), those won't inherit sorting... + check_children_sorting( + container, + Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING + ); + // ...then we check sorting of the contained urls, we can't inherit sorting + // since the above level does not inherit it, so they will be sorted by + // title ascending. + let innerContainer = container + .getChild(0) + .QueryInterface(Ci.nsINavHistoryContainerResultNode); + innerContainer.containerOpen = true; + check_children_sorting( + innerContainer, + Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING + ); + innerContainer.containerOpen = false; + } else if ( + resultType.value == Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAGS_ROOT + ) { + // Sorting mode for tag contents is hardcoded for now, to allow for faster + // duplicates filtering. + check_children_sorting( + container, + Ci.nsINavHistoryQueryOptions.SORT_BY_NONE + ); + } else { + check_children_sorting(container, sortingMode.value); + } + + container.containerOpen = false; + root.containerOpen = false; + + test_result_sortingMode_change(result, resultType, sortingMode); +} + +/** + * Sets sortingMode on aResult and checks for correct sorting of children. + * Containers should not change their sorting, while contained uri nodes should. + * + * @param aResult + * nsINavHistoryResult generated by our query. + * @param aResultType + * required result type. + * @param aOriginalSortingMode + * the sorting mode from query's options. + */ +function test_result_sortingMode_change( + aResult, + aResultType, + aOriginalSortingMode +) { + var root = aResult.root; + // Now we set sortingMode on the result and check that containers are not + // sorted while children are. + sortingModes.forEach(function sortingModeChecker(aForcedSortingMode) { + print( + "\n* Test setting sortingMode (" + + aForcedSortingMode.name + + ") " + + "on result with resultType (" + + aResultType.name + + ") " + + "currently sorted as (" + + aOriginalSortingMode.name + + ")" + ); + + aResult.sortingMode = aForcedSortingMode.value; + root.containerOpen = true; + + if ( + aResultType.value == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY || + aResultType.value == + Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY + ) { + // Date containers are always sorted by date descending. + check_children_sorting( + root, + Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING + ); + } else if ( + aResultType.value == Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY && + (aOriginalSortingMode.value == + Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING || + aOriginalSortingMode.value == + Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING) + ) { + // Site containers don't have a good time property to sort by. + check_children_sorting(root, Ci.nsINavHistoryQueryOptions.SORT_BY_NONE); + } else { + check_children_sorting(root, aOriginalSortingMode.value); + } + + // Now Check sorting of the first child container. + var container = root + .getChild(0) + .QueryInterface(Ci.nsINavHistoryContainerResultNode); + container.containerOpen = true; + + if ( + aResultType.value == + Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY + ) { + // Has more than one level of containers, first we check the sorting of + // the first level (site containers), those won't be sorted... + check_children_sorting( + container, + Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING + ); + // ...then we check sorting of the second level of containers, result + // will sort them through recursiveSort. + let innerContainer = container + .getChild(0) + .QueryInterface(Ci.nsINavHistoryContainerResultNode); + innerContainer.containerOpen = true; + check_children_sorting(innerContainer, aForcedSortingMode.value); + innerContainer.containerOpen = false; + } else { + if ( + aResultType.value == + Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY || + aResultType.value == + Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY || + aResultType.value == Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY + ) { + // Date containers are always sorted by date descending. + check_children_sorting(root, Ci.nsINavHistoryQueryOptions.SORT_BY_NONE); + } else { + check_children_sorting(root, aOriginalSortingMode.value); + } + + // Children should always be sorted. + check_children_sorting(container, aForcedSortingMode.value); + } + + container.containerOpen = false; + root.containerOpen = false; + }); +} + +/** + * Test if children of aRootNode are correctly sorted. + * @param aRootNode + * already opened root node from our query's result. + * @param aExpectedSortingMode + * The sortingMode we expect results to be. + */ +function check_children_sorting(aRootNode, aExpectedSortingMode) { + var results = []; + print("Found children:"); + for (let i = 0; i < aRootNode.childCount; i++) { + results[i] = aRootNode.getChild(i); + print(i + " " + results[i].title); + } + + // Helper for case insensitive string comparison. + function caseInsensitiveStringComparator(a, b) { + var aLC = a.toLowerCase(); + var bLC = b.toLowerCase(); + if (aLC < bLC) { + return -1; + } + if (aLC > bLC) { + return 1; + } + return 0; + } + + // Get a comparator based on expected sortingMode. + var comparator; + switch (aExpectedSortingMode) { + case Ci.nsINavHistoryQueryOptions.SORT_BY_NONE: + comparator = function (a, b) { + return 0; + }; + break; + case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING: + comparator = function (a, b) { + return caseInsensitiveStringComparator(a.title, b.title); + }; + break; + case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING: + comparator = function (a, b) { + return -caseInsensitiveStringComparator(a.title, b.title); + }; + break; + case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING: + comparator = function (a, b) { + return a.time - b.time; + }; + break; + case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING: + comparator = function (a, b) { + return b.time - a.time; + }; + // fall through - we shouldn't do this, see bug 1572437. + case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_ASCENDING: + comparator = function (a, b) { + return a.dateAdded - b.dateAdded; + }; + break; + case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING: + comparator = function (a, b) { + return b.dateAdded - a.dateAdded; + }; + break; + default: + do_throw("Unknown sorting type: " + aExpectedSortingMode); + } + + // Make an independent copy of the results array and sort it. + var sortedResults = results.slice(); + sortedResults.sort(comparator); + // Actually compare returned children with our sorted array. + for (let i = 0; i < sortedResults.length; i++) { + if (sortedResults[i].title != results[i].title) { + print( + i + + " index wrong, expected " + + sortedResults[i].title + + " found " + + results[i].title + ); + } + Assert.equal(sortedResults[i].title, results[i].title); + } +} + +// Main + +add_task(async function test_containersQueries_sorting() { + // Add visits, bookmarks and tags to our database. + var timeInMilliseconds = Date.now(); + var visitCount = 0; + var dayOffset = 0; + var visits = []; + pages.forEach(aPageUrl => + visits.push({ + isVisit: true, + isBookmark: true, + transType: Ci.nsINavHistoryService.TRANSITION_TYPED, + uri: aPageUrl, + title: aPageUrl, + // subtract 5 hours per iteration, to expose more than one day container. + lastVisit: (timeInMilliseconds - 18000 * 1000 * dayOffset++) * 1000, + visitCount: visitCount++, + isTag: true, + tagArray: tags, + isInQuery: true, + }) + ); + await task_populateDB(visits); + + cartProd([resultTypes, sortingModes], test_query_callback); +}); |