Bug 1699227 - Support negative suggestedIndex values to allow indexes to be specified from the end of the list of urlbar results. r=mak

Given the result span problem described in bug 1699211 and bug 1699607,
currently there's no way for quick suggest to accurately specify the last index
as its suggested index.

Differential Revision: https://phabricator.services.mozilla.com/D109571
This commit is contained in:
Drew Willcoxon 2021-03-25 20:32:52 +00:00
parent 9624bcba17
commit bc2e6501df
9 changed files with 689 additions and 49 deletions

View File

@ -366,7 +366,7 @@ add_task(async function test_onProviderResultsRequested() {
},
];
Assert.ok(context.results.every(r => r.suggestedIndex == -1));
Assert.ok(context.results.every(r => !r.hasSuggestedIndex));
let actualResults = context.results.map(r => ({
type: r.type,
source: r.source,

View File

@ -99,33 +99,9 @@ class MuxerUnifiedComplete extends UrlbarMuxer {
state
);
// Finally, insert results that have a suggested index.
let resultsWithSuggestedIndex = state.resultsByGroup.get(
UrlbarUtils.RESULT_GROUP.SUGGESTED_INDEX
);
if (resultsWithSuggestedIndex) {
// Sort them by index in descending order so that earlier insertions don't
// disrupt later ones.
resultsWithSuggestedIndex.sort(
(a, b) => a.suggestedIndex - b.suggestedIndex
);
// Do a first pass to update sort state for each result.
for (let result of resultsWithSuggestedIndex) {
this._updateStatePreAdd(result, state);
}
// Now insert them.
for (let result of resultsWithSuggestedIndex) {
if (this._canAddResult(result, state)) {
let index =
result.suggestedIndex <= sortedResults.length
? result.suggestedIndex
: sortedResults.length;
sortedResults.splice(index, 0, result);
this._updateStatePostAdd(result, state);
}
}
}
this._addSuggestedIndexResults(sortedResults, state);
this._truncateResults(sortedResults, context.maxResults);
context.results = sortedResults;
}
@ -645,6 +621,97 @@ class MuxerUnifiedComplete extends UrlbarMuxer {
}
}
}
/**
* Inserts results with suggested indexes. This should be called at the end
* of the sort, after all buckets have been filled.
*
* @param {array} sortedResults
* The sorted results produced by the muxer so far. Updated in place.
* @param {object} state
* Global state that we use to make decisions during this sort.
*/
_addSuggestedIndexResults(sortedResults, state) {
let suggestedIndexResults = state.resultsByGroup.get(
UrlbarUtils.RESULT_GROUP.SUGGESTED_INDEX
);
if (!suggestedIndexResults) {
return;
}
// First, sort the results by index in ascending order so that insertions of
// results with both positive and negative indexes are in ascending order.
suggestedIndexResults.sort((a, b) => a.suggestedIndex - b.suggestedIndex);
// Insert results with positive indexes. Insertions should happen in
// ascending order so that higher-index results are inserted at their
// suggested indexes and aren't offset by later lower-index insertions.
let negativeIndexSpanCount = 0;
for (let result of suggestedIndexResults) {
if (result.suggestedIndex < 0) {
negativeIndexSpanCount += UrlbarUtils.getSpanForResult(result);
} else {
this._updateStatePreAdd(result, state);
if (this._canAddResult(result, state)) {
let index =
result.suggestedIndex <= sortedResults.length
? result.suggestedIndex
: sortedResults.length;
sortedResults.splice(index, 0, result);
this._updateStatePostAdd(result, state);
}
}
}
// Before inserting results with negative indexes, truncate the sorted
// results so that their total span count is no larger than maxResults minus
// the span count of the negative-index results themselves. If we didn't do
// that, the negative-index results could end up getting removed when the
// muxer truncates the final results array, which would effectively mean
// that we inserted them at the wrong indexes.
this._truncateResults(
sortedResults,
state.context.maxResults - negativeIndexSpanCount
);
// Insert results with negative indexes.
if (negativeIndexSpanCount) {
for (let result of suggestedIndexResults) {
if (result.suggestedIndex >= 0) {
break;
}
this._updateStatePreAdd(result, state);
if (this._canAddResult(result, state)) {
let index = Math.max(
result.suggestedIndex + sortedResults.length + 1,
0
);
sortedResults.splice(index, 0, result);
this._updateStatePostAdd(result, state);
}
}
}
}
/**
* Truncates the array of results so that their total span count is no larger
* than a given number.
*
* @param {array} sortedResults
* The sorted results produced by the muxer so far. Updated in place.
* @param {number} maxSpanCount
* The max span count.
*/
_truncateResults(sortedResults, maxSpanCount) {
let remainingSpanCount = maxSpanCount;
for (let i = 0; i < sortedResults.length; i++) {
remainingSpanCount -= UrlbarUtils.getSpanForResult(sortedResults[i]);
if (remainingSpanCount < 0) {
sortedResults.splice(i, sortedResults.length - i);
break;
}
}
}
}
var UrlbarMuxerUnifiedComplete = new MuxerUnifiedComplete();

View File

@ -617,20 +617,6 @@ class Query {
return;
}
// Crop results to the requested number, taking their result spans into
// account.
let resultCount = this.context.maxResults;
for (let i = 0; i < this.context.results.length; i++) {
resultCount -= UrlbarUtils.getSpanForResult(this.context.results[i]);
if (resultCount < 0) {
logger.debug(
`Splicing results from ${i} to crop results to ${this.context.maxResults}`
);
this.context.results.splice(i, this.context.results.length - i);
break;
}
}
this.context.firstResultChanged = !ObjectUtils.deepEqual(
this.context.firstResult,
this.context.results[0]

View File

@ -61,10 +61,6 @@ class UrlbarResult {
// UrlbarView is responsible for updating this.
this.rowIndex = -1;
// This is an optional hint to the Muxer that can be set by a provider to
// suggest a specific position among the results.
this.suggestedIndex = -1;
// May be used to indicate an heuristic result. Heuristic results can bypass
// source filters in the ProvidersManager, that otherwise may skip them.
this.heuristic = false;
@ -152,6 +148,16 @@ class UrlbarResult {
return this.payload.icon;
}
/**
* Returns whether the result's `suggestedIndex` property is defined.
* `suggestedIndex` is an optional hint to the muxer that can be set to
* suggest a specific position among the results.
* @returns {boolean} Whether `suggestedIndex` is defined.
*/
get hasSuggestedIndex() {
return typeof this.suggestedIndex == "number";
}
/**
* Returns the given payload if it's valid or throws an error if it's not.
* The schemas in UrlbarUtils.RESULT_PAYLOAD_SCHEMA are used for validation.

View File

@ -496,7 +496,7 @@ var UrlbarUtils = {
* The reuslt's group.
*/
getResultGroup(result) {
if (result.suggestedIndex >= 0) {
if (result.hasSuggestedIndex) {
return UrlbarUtils.RESULT_GROUP.SUGGESTED_INDEX;
}
if (result.heuristic) {

View File

@ -909,7 +909,7 @@ class UrlbarView {
return true;
}
let row = this._rows.children[rowIndex];
if (result.suggestedIndex >= 0) {
if (result.hasSuggestedIndex) {
// Always allow a result with a suggested index to replace any other
// result. Otherwise it can briefly end up at some larger index due to
// the presence of visible stale rows. Then, if the user makes a
@ -917,7 +917,7 @@ class UrlbarView {
// the suggested-index row in the wrong spot.
return true;
}
if (row.result.suggestedIndex >= 0) {
if (row.result.hasSuggestedIndex) {
// Never allow a result without a suggested index to replace a result with
// a suggested index. If the suggested-index row is not stale, then it
// needs to remain in the same spot to avoid flicker.

View File

@ -377,7 +377,7 @@ properties, supported by all of the results.
selection.
autofill.selectionEnd: {integer} The last index in the autofill selection.
suggestedIndex: {integer} Suggest a preferred position for this result
within the result set.
within the result set. Undefined if none.
}
The following RESULT_TYPEs are supported:

View File

@ -0,0 +1,580 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests results with suggestedIndex and resultSpan.
"use strict";
const MAX_RESULTS = 10;
add_task(async function suggestedIndex() {
// Initialize maxRichResults for sanity.
UrlbarPrefs.set("maxRichResults", MAX_RESULTS);
let tests = [
// no result spans > 1
{
desc: "{ suggestedIndex: 0 }",
suggestedIndexes: [0],
expected: indexes([10, 1], [0, 9]),
},
{
desc: "{ suggestedIndex: 1 }",
suggestedIndexes: [1],
expected: indexes([0, 1], [10, 1], [1, 8]),
},
{
desc: "{ suggestedIndex: -1 }",
suggestedIndexes: [-1],
expected: indexes([0, 9], [10, 1]),
},
{
desc: "{ suggestedIndex: -2 }",
suggestedIndexes: [-2],
expected: indexes([0, 8], [10, 1], [8, 1]),
},
{
desc: "{ suggestedIndex: 0 }, { suggestedIndex: -1 }",
suggestedIndexes: [0, -1],
expected: indexes([10, 1], [0, 8], [11, 1]),
},
{
desc: "{ suggestedIndex: 1 }, { suggestedIndex: -1 }",
suggestedIndexes: [1, -1],
expected: indexes([0, 1], [10, 1], [1, 7], [11, 1]),
},
{
desc: "{ suggestedIndex: 1 }, { suggestedIndex: -2 }",
suggestedIndexes: [1, -2],
expected: indexes([0, 1], [10, 1], [1, 6], [11, 1], [7, 1]),
},
{
desc: "{ suggestedIndex: 0 }, resultCount < max",
suggestedIndexes: [0],
resultCount: 5,
expected: indexes([5, 1], [0, 5]),
},
{
desc: "{ suggestedIndex: 1 }, resultCount < max",
suggestedIndexes: [1],
resultCount: 5,
expected: indexes([0, 1], [5, 1], [1, 4]),
},
{
desc: "{ suggestedIndex: -1 }, resultCount < max",
suggestedIndexes: [-1],
resultCount: 5,
expected: indexes([0, 5], [5, 1]),
},
{
desc: "{ suggestedIndex: -2 }, resultCount < max",
suggestedIndexes: [-2],
resultCount: 5,
expected: indexes([0, 4], [5, 1], [4, 1]),
},
{
desc: "{ suggestedIndex: 0 }, { suggestedIndex: -1 }, resultCount < max",
suggestedIndexes: [0, -1],
resultCount: 5,
expected: indexes([5, 1], [0, 5], [6, 1]),
},
{
desc: "{ suggestedIndex: 1 }, { suggestedIndex: -1 }, resultCount < max",
suggestedIndexes: [1, -1],
resultCount: 5,
expected: indexes([0, 1], [5, 1], [1, 4], [6, 1]),
},
{
desc: "{ suggestedIndex: 0 }, { suggestedIndex: -2 }, resultCount < max",
suggestedIndexes: [0, -2],
resultCount: 5,
expected: indexes([5, 1], [0, 4], [6, 1], [4, 1]),
},
{
desc: "{ suggestedIndex: 1 }, { suggestedIndex: -2 }, resultCount < max",
suggestedIndexes: [1, -2],
resultCount: 5,
expected: indexes([0, 1], [5, 1], [1, 3], [6, 1], [4, 1]),
},
// one suggestedIndex with result span > 1
{
desc: "{ suggestedIndex: 0, resultSpan: 2 }",
suggestedIndexes: [0],
spansByIndex: { 10: 2 },
expected: indexes([10, 1], [0, 8]),
},
{
desc: "{ suggestedIndex: 0, resultSpan: 3 }",
suggestedIndexes: [0],
spansByIndex: { 10: 3 },
expected: indexes([10, 1], [0, 7]),
},
{
desc: "{ suggestedIndex: 1, resultSpan: 2 }",
suggestedIndexes: [1],
spansByIndex: { 10: 2 },
expected: indexes([0, 1], [10, 1], [1, 7]),
},
{
desc: "suggestedIndex: 1, resultSpan:: 3 }",
suggestedIndexes: [1],
spansByIndex: { 10: 3 },
expected: indexes([0, 1], [10, 1], [1, 6]),
},
{
desc: "{ suggestedIndex: -1, resultSpan 2 }",
suggestedIndexes: [-1],
spansByIndex: { 10: 2 },
expected: indexes([0, 8], [10, 1]),
},
{
desc: "{ suggestedIndex: -1, resultSpan: 3 }",
suggestedIndexes: [-1],
spansByIndex: { 10: 3 },
expected: indexes([0, 7], [10, 1]),
},
{
desc: "{ suggestedIndex: 0, resultSpan: 2 }, { suggestedIndex: -1 }",
suggestedIndexes: [0, -1],
spansByIndex: { 10: 2 },
expected: indexes([10, 1], [0, 7], [11, 1]),
},
{
desc: "{ suggestedIndex: 0, resultSpan: 3 }, { suggestedIndex: -1 }",
suggestedIndexes: [0, -1],
spansByIndex: { 10: 3 },
expected: indexes([10, 1], [0, 6], [11, 1]),
},
{
desc: "{ suggestedIndex: 0 }, { suggestedIndex: -1, resultSpan: 2 }",
suggestedIndexes: [0, -1],
spansByIndex: { 11: 2 },
expected: indexes([10, 1], [0, 7], [11, 1]),
},
{
desc: "{ suggestedIndex: 0 }, { suggestedIndex: -1, resultSpan: 3 }",
suggestedIndexes: [0, -1],
spansByIndex: { 11: 3 },
expected: indexes([10, 1], [0, 6], [11, 1]),
},
{
desc: "{ suggestedIndex: 1, resultSpan: 2 }, { suggestedIndex: -1 }",
suggestedIndexes: [1, -1],
spansByIndex: { 10: 2 },
expected: indexes([0, 1], [10, 1], [1, 6], [11, 1]),
},
{
desc: "{ suggestedIndex: 1, resultSpan: 3 }, { suggestedIndex: -1 }",
suggestedIndexes: [1, -1],
spansByIndex: { 10: 3 },
expected: indexes([0, 1], [10, 1], [1, 5], [11, 1]),
},
{
desc: "{ suggestedIndex: 1 }, { suggestedIndex: -1, resultSpan: 2 }",
suggestedIndexes: [1, -1],
spansByIndex: { 11: 2 },
expected: indexes([0, 1], [10, 1], [1, 6], [11, 1]),
},
{
desc: "{ suggestedIndex: 1 }, { suggestedIndex: -1, resultSpan: 3 }",
suggestedIndexes: [1, -1],
spansByIndex: { 11: 3 },
expected: indexes([0, 1], [10, 1], [1, 5], [11, 1]),
},
{
desc: "{ suggestedIndex: 0, resultSpan: 2 }, { suggestedIndex: -2 }",
suggestedIndexes: [0, -2],
spansByIndex: { 10: 2 },
expected: indexes([10, 1], [0, 6], [11, 1], [6, 1]),
},
{
desc: "{ suggestedIndex: 0, resultSpan: 3 }, { suggestedIndex: -2 }",
suggestedIndexes: [0, -2],
spansByIndex: { 10: 3 },
expected: indexes([10, 1], [0, 5], [11, 1], [5, 1]),
},
{
desc: "{ suggestedIndex: 1, resultSpan: 2 }, { suggestedIndex: -2 }",
suggestedIndexes: [1, -2],
spansByIndex: { 10: 2 },
expected: indexes([0, 1], [10, 1], [1, 5], [11, 1], [6, 1]),
},
{
desc: "{ suggestedIndex: 1, resultSpan: 3 }, { suggestedIndex: -2 }",
suggestedIndexes: [1, -2],
spansByIndex: { 10: 3 },
expected: indexes([0, 1], [10, 1], [1, 4], [11, 1], [5, 1]),
},
{
desc: "{ suggestedIndex: 0 }, { suggestedIndex: -2, resultSpan: 2 }",
suggestedIndexes: [0, -2],
spansByIndex: { 11: 2 },
expected: indexes([10, 1], [0, 6], [11, 1], [6, 1]),
},
{
desc: "{ suggestedIndex: 0 }, { suggestedIndex: -2, resultSpan: 3 }",
suggestedIndexes: [0, -2],
spansByIndex: { 11: 3 },
expected: indexes([10, 1], [0, 5], [11, 1], [5, 1]),
},
{
desc: "{ suggestedIndex: 1 }, { suggestedIndex: -2, resultSpan: 2 }",
suggestedIndexes: [1, -2],
spansByIndex: { 11: 2 },
expected: indexes([0, 1], [10, 1], [1, 5], [11, 1], [6, 1]),
},
{
desc: "{ suggestedIndex: 1 }, { suggestedIndex: -2, resultSpan: 3 }",
suggestedIndexes: [1, -2],
spansByIndex: { 11: 3 },
expected: indexes([0, 1], [10, 1], [1, 4], [11, 1], [5, 1]),
},
{
desc: "{ suggestedIndex: 0, resultSpan: 2 }, resultCount < max",
suggestedIndexes: [0],
spansByIndex: { 5: 2 },
resultCount: 5,
expected: indexes([5, 1], [0, 5]),
},
{
desc: "{ suggestedIndex: 1, resultSpan: 2 }, resultCount < max",
suggestedIndexes: [1],
spansByIndex: { 5: 2 },
resultCount: 5,
expected: indexes([0, 1], [5, 1], [1, 4]),
},
{
desc: "{ suggestedIndex: -1, resultSpan: 2 }, resultCount < max",
suggestedIndexes: [-1],
spansByIndex: { 5: 2 },
resultCount: 5,
expected: indexes([0, 5], [5, 1]),
},
{
desc: "{ suggestedIndex: -2, resultSpan: 2 }, resultCount < max",
suggestedIndexes: [-2],
spansByIndex: { 5: 2 },
resultCount: 5,
expected: indexes([0, 4], [5, 1], [4, 1]),
},
{
desc:
"{ suggestedIndex: 0, resultSpan: 2 }, { suggestedIndex: -1 }, resultCount < max",
suggestedIndexes: [0, -1],
spansByIndex: { 5: 2 },
resultCount: 5,
expected: indexes([5, 1], [0, 5], [6, 1]),
},
{
desc:
"{ suggestedIndex: 1, resultSpan: 2 }, { suggestedIndex: -1 }, resultCount < max",
suggestedIndexes: [1, -1],
spansByIndex: { 5: 2 },
resultCount: 5,
expected: indexes([0, 1], [5, 1], [1, 4], [6, 1]),
},
{
desc:
"{ suggestedIndex: 0, resultSpan: 2 }, { suggestedIndex: -2 }, resultCount < max",
suggestedIndexes: [0, -2],
spansByIndex: { 5: 2 },
resultCount: 5,
expected: indexes([5, 1], [0, 4], [6, 1], [4, 1]),
},
{
desc:
"{ suggestedIndex: 0 }, { suggestedIndex: -1, resultSpan: 2 }, resultCount < max",
suggestedIndexes: [0, -1],
spansByIndex: { 6: 2 },
resultCount: 5,
expected: indexes([5, 1], [0, 5], [6, 1]),
},
{
desc:
"{ suggestedIndex: 0 }, { suggestedIndex: -2, resultSpan: 2 }, resultCount < max",
suggestedIndexes: [0, -2],
spansByIndex: { 6: 2 },
resultCount: 5,
expected: indexes([5, 1], [0, 4], [6, 1], [4, 1]),
},
// two suggestedIndexes with result span > 1
{
desc:
"{ suggestedIndex: 0, resultSpan: 2 }, { suggestedIndex: -1, resultSpan: 2 }",
suggestedIndexes: [0, -1],
spansByIndex: { 10: 2, 11: 2 },
expected: indexes([10, 1], [0, 6], [11, 1]),
},
{
desc:
"{ suggestedIndex: 0, resultSpan: 3 }, { suggestedIndex: -1, resultSpan: 2 }",
suggestedIndexes: [0, -1],
spansByIndex: { 10: 3, 11: 2 },
expected: indexes([10, 1], [0, 5], [11, 1]),
},
{
desc:
"{ suggestedIndex: 0, resultSpan: 2 }, { suggestedIndex: -1, resultSpan: 3 }",
suggestedIndexes: [0, -1],
spansByIndex: { 10: 2, 11: 3 },
expected: indexes([10, 1], [0, 5], [11, 1]),
},
{
desc:
"{ suggestedIndex: 1, resultSpan: 2 }, { suggestedIndex: -1, resultSpan: 2 }",
suggestedIndexes: [1, -1],
spansByIndex: { 10: 2, 11: 2 },
expected: indexes([0, 1], [10, 1], [1, 5], [11, 1]),
},
{
desc:
"{ suggestedIndex: 1, resultSpan: 3 }, { suggestedIndex: -1, resultSpan: 2 }",
suggestedIndexes: [1, -1],
spansByIndex: { 10: 3, 11: 2 },
expected: indexes([0, 1], [10, 1], [1, 4], [11, 1]),
},
{
desc:
"{ suggestedIndex: 1, resultSpan: 2 }, { suggestedIndex: -1, resultSpan: 3 }",
suggestedIndexes: [1, -1],
spansByIndex: { 10: 2, 11: 3 },
expected: indexes([0, 1], [10, 1], [1, 4], [11, 1]),
},
{
desc:
"{ suggestedIndex: 0, resultSpan: 2 }, { suggestedIndex: -2, resultSpan: 2 }",
suggestedIndexes: [0, -2],
spansByIndex: { 10: 2, 11: 2 },
expected: indexes([10, 1], [0, 5], [11, 1], [5, 1]),
},
{
desc:
"{ suggestedIndex: 0, resultSpan: 3 }, { suggestedIndex: -2, resultSpan: 2 }",
suggestedIndexes: [0, -2],
spansByIndex: { 10: 3, 11: 2 },
expected: indexes([10, 1], [0, 4], [11, 1], [4, 1]),
},
{
desc:
"{ suggestedIndex: 0, resultSpan: 2 }, { suggestedIndex: -2, resultSpan: 3 }",
suggestedIndexes: [0, -2],
spansByIndex: { 10: 2, 11: 3 },
expected: indexes([10, 1], [0, 4], [11, 1], [4, 1]),
},
{
desc:
"{ suggestedIndex: 1, resultSpan: 2 }, { suggestedIndex: -2, resultSpan: 2 }",
suggestedIndexes: [1, -2],
spansByIndex: { 10: 2, 11: 2 },
expected: indexes([0, 1], [10, 1], [1, 4], [11, 1], [5, 1]),
},
{
desc:
"{ suggestedIndex: 1, resultSpan: 3 }, { suggestedIndex: -2, resultSpan: 2 }",
suggestedIndexes: [1, -2],
spansByIndex: { 10: 3, 11: 2 },
expected: indexes([0, 1], [10, 1], [1, 3], [11, 1], [4, 1]),
},
{
desc:
"{ suggestedIndex: 1, resultSpan: 2 }, { suggestedIndex: -2, resultSpan: 3 }",
suggestedIndexes: [1, -2],
spansByIndex: { 10: 2, 11: 3 },
expected: indexes([0, 1], [10, 1], [1, 3], [11, 1], [4, 1]),
},
{
desc:
"{ suggestedIndex: 0, resultSpan: 2 }, { suggestedIndex: -1, resultSpan: 2 }, resultCount < max",
suggestedIndexes: [0, -1],
spansByIndex: { 5: 2, 6: 2 },
resultCount: 5,
expected: indexes([5, 1], [0, 5], [6, 1]),
},
{
desc:
"{ suggestedIndex: 1, resultSpan: 2 }, { suggestedIndex: -1, resultSpan: 2 }, resultCount < max",
suggestedIndexes: [1, -1],
spansByIndex: { 5: 2, 6: 2 },
resultCount: 5,
expected: indexes([0, 1], [5, 1], [1, 4], [6, 1]),
},
{
desc:
"{ suggestedIndex: 0, resultSpan: 2 }, { suggestedIndex: -2, resultSpan: 2 }, resultCount < max",
suggestedIndexes: [0, -2],
spansByIndex: { 5: 2, 6: 2 },
resultCount: 5,
expected: indexes([5, 1], [0, 4], [6, 1], [4, 1]),
},
// one suggestedIndex plus other result with resultSpan > 1
{
desc: "{ suggestedIndex: 0 }, { resultSpan: 2 } A",
suggestedIndexes: [0],
spansByIndex: { 0: 2 },
expected: indexes([10, 1], [0, 8]),
},
{
desc: "{ suggestedIndex: 0 }, { resultSpan: 2 } B",
suggestedIndexes: [0],
spansByIndex: { 8: 2 },
expected: indexes([10, 1], [0, 8]),
},
{
desc: "{ suggestedIndex: 0 }, { resultSpan: 2 } C",
suggestedIndexes: [0],
spansByIndex: { 9: 2 },
expected: indexes([10, 1], [0, 9]),
},
{
desc: "{ suggestedIndex: 1 }, { resultSpan: 2 } A",
suggestedIndexes: [1],
spansByIndex: { 0: 2 },
expected: indexes([0, 1], [10, 1], [1, 7]),
},
{
desc: "{ suggestedIndex: 1 }, { resultSpan: 2 } B",
suggestedIndexes: [1],
spansByIndex: { 8: 2 },
expected: indexes([0, 1], [10, 1], [1, 7]),
},
{
desc: "{ suggestedIndex: -1 }, { resultSpan: 2 }",
suggestedIndexes: [-1],
spansByIndex: { 0: 2 },
expected: indexes([0, 8], [10, 1]),
},
{
desc: "{ suggestedIndex: -2 }, { resultSpan: 2 }",
suggestedIndexes: [-2],
spansByIndex: { 0: 2 },
expected: indexes([0, 7], [10, 1], [7, 1]),
},
// miscellaneous
{
desc: "no suggestedIndex, last result has resultSpan = 2",
suggestedIndexes: [],
spansByIndex: { 9: 2 },
expected: indexes([0, 9]),
},
{
desc: "{ suggestedIndex: -1 }, last result has resultSpan = 2",
suggestedIndexes: [-1],
spansByIndex: { 9: 2 },
expected: indexes([0, 9], [10, 1]),
},
{
desc: "no suggestedIndex, index 8 result has resultSpan = 2",
suggestedIndexes: [],
spansByIndex: { 8: 2 },
expected: indexes([0, 9]),
},
{
desc: "{ suggestedIndex: -1 }, index 8 result has resultSpan = 2",
suggestedIndexes: [-1],
spansByIndex: { 8: 2 },
expected: indexes([0, 8], [10, 1]),
},
];
for (let test of tests) {
info("Running test: " + JSON.stringify(test));
await doSuggestedIndexTest(test);
}
});
/**
* Sets up a provider with some results with suggested indexes and result spans,
* performs a search, and then checks the results.
*
* @param {array} suggestedIndexes
* For each of the indexes in this array, a new result with the given
* suggestedIndex will be returned by the provider.
* @param {array} expected
* The indexes of the expected results within the array of results returned by
* the provider.
* @param {object} [spansByIndex]
* Maps indexes within the array of results returned by the provider to result
* spans to set on those results.
* @param {number} [resultCount]
* Aside from the results with suggested indexes, this is the number of
* results that the provider will return.
*/
async function doSuggestedIndexTest({
suggestedIndexes,
expected,
spansByIndex = {},
resultCount = MAX_RESULTS,
}) {
// Make resultCount history results.
let results = [];
for (let i = 0; i < resultCount; i++) {
results.push(
new UrlbarResult(
UrlbarUtils.RESULT_TYPE.URL,
UrlbarUtils.RESULT_SOURCE.HISTORY,
{
url: "http://example.com/" + i,
}
)
);
}
// Make the suggested-index results.
for (let suggestedIndex of suggestedIndexes) {
results.push(
Object.assign(
new UrlbarResult(
UrlbarUtils.RESULT_TYPE.URL,
UrlbarUtils.RESULT_SOURCE.HISTORY,
{
url: "http://example.com/si " + suggestedIndex,
}
),
{ suggestedIndex }
)
);
}
// Set resultSpan on each result as indicated by spansByIndex.
for (let [index, span] of Object.entries(spansByIndex)) {
results[index].resultSpan = span;
}
// Set up the provider, etc.
let provider = registerBasicTestProvider(results);
let context = createContext(undefined, { providers: [provider.name] });
let controller = UrlbarTestUtils.newMockController();
// Finally, search and check the results.
let expectedResults = expected.map(i => results[i]);
await UrlbarProvidersManager.startQuery(context, controller);
Assert.deepEqual(context.results, expectedResults);
}
/**
* Helper that generates an array of indexes. Pass in [index, length] tuples.
* Each tuple will produce the indexes starting from `index` to `index + length`
* (not including the index at `index + length`).
*
* Examples:
*
* indexes([0, 5]) => [0, 1, 2, 3, 4]
* indexes([0, 1], [4, 3], [8, 2]) => [0, 4, 5, 6, 8, 9]
*
* @param {array} pairs
* [index, length] tuples as described above.
* @returns {array}
* An array of indexes.
*/
function indexes(...pairs) {
return pairs.reduce((indexesArray, [start, len]) => {
for (let i = start; i < start + len; i++) {
indexesArray.push(i);
}
return indexesArray;
}, []);
}

View File

@ -42,6 +42,7 @@ skip-if = os == 'linux' # bug 1474616
[test_search_suggestions.js]
[test_search_suggestions_aliases.js]
[test_search_suggestions_tail.js]
[test_suggestedIndex.js]
[test_quicksuggest_keywordtree.js]
[test_tokenizer.js]
[test_trimming.js]