mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Fixing ACIndex highlightMatches functions
Summary: Highlighting wasn't working correctly for the new normalized search for autocomplate widgets. Test Plan: Existing tests Reviewers: alexmojaki Reviewed By: alexmojaki Differential Revision: https://phab.getgrist.com/D3602
This commit is contained in:
parent
2cb783ea7b
commit
b6f5718ad0
@ -11,6 +11,7 @@ import {localeCompare, nativeCompare, sortedIndex} from 'app/common/gutil';
|
||||
import {DomContents} from 'grainjs';
|
||||
import escapeRegExp = require("lodash/escapeRegExp");
|
||||
import deburr = require("lodash/deburr");
|
||||
import split = require("lodash/split");
|
||||
|
||||
export interface ACItem {
|
||||
// This should be a trimmed lowercase version of the item's text. It may be an accessor.
|
||||
@ -243,11 +244,17 @@ function highlightMatches(searchWords: string[], text: string): string[] {
|
||||
for (let i = 0; i < textParts.length; i += 2) {
|
||||
const word = textParts[i];
|
||||
const separator = textParts[i + 1] || '';
|
||||
const prefixLen = findLongestPrefixLen(word.toLowerCase(), searchWords);
|
||||
// deburr (remove diacritics) was used to produce searchWords, so `word` needs to match that.
|
||||
const prefixLen = findLongestPrefixLen(deburr(word).toLowerCase(), searchWords);
|
||||
if (prefixLen === 0) {
|
||||
outputs[outputs.length - 1] += word + separator;
|
||||
} else {
|
||||
outputs.push(word.slice(0, prefixLen), word.slice(prefixLen) + separator);
|
||||
// Split into unicode 'characters' that keep diacritics combined
|
||||
const chars = split(word, '');
|
||||
outputs.push(
|
||||
chars.slice(0, prefixLen).join(''),
|
||||
chars.slice(prefixLen).join('') + separator
|
||||
);
|
||||
}
|
||||
}
|
||||
return outputs;
|
||||
|
@ -281,9 +281,18 @@ describe('ACIndex', function() {
|
||||
|
||||
it('should highlight multi-byte unicode', function() {
|
||||
const acIndex = new ACIndexImpl(['Lorem ipsum 𝌆 dolor sit ameͨ͆t.', "mañana", "Москва"].map(makeItem), 3);
|
||||
const acResult: ACResults<TestACItem> = acIndex.search("mañ моск am");
|
||||
let acResult: ACResults<TestACItem> = acIndex.search("mañ моск am");
|
||||
assert.deepEqual(acResult.items.map(i => acResult.highlightFunc(i.text)),
|
||||
[["", "Моск", "ва"], ["", "mañ", "ana"], ["Lorem ipsum 𝌆 dolor sit ", "am", "eͨ͆t."]]);
|
||||
|
||||
const original = "ameͨ͆";
|
||||
assert.equal(original.length, 5);
|
||||
for (let end = 3; end <= original.length; end++) {
|
||||
const text = original.slice(0, end); // i.e. test: ame, ameͨ, ameͨ͆ (hard to see the difference in some editors)
|
||||
acResult = acIndex.search(text);
|
||||
assert.deepEqual(acResult.items.map(i => acResult.highlightFunc(i.text))[0],
|
||||
["Lorem ipsum 𝌆 dolor sit ", original, "t."]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should match a brute-force scoring implementation', function() {
|
||||
|
@ -456,32 +456,34 @@ export async function rightClick(cell: WebElement) {
|
||||
* grid view or field name for detail view.
|
||||
*/
|
||||
export async function getCursorPosition() {
|
||||
const section = await driver.findWait('.active_section', 4000);
|
||||
const cursor = await section.findWait('.active_cursor', 1000);
|
||||
// Query assuming the cursor is in a GridView and a DetailView, then use whichever query data
|
||||
// works out.
|
||||
const [colIndex, rowIndex, rowNum, colName] = await Promise.all([
|
||||
catchNoSuchElem(() => cursor.findClosest('.field').index()),
|
||||
catchNoSuchElem(() => cursor.findClosest('.gridview_row').index()),
|
||||
catchNoSuchElem(() => cursor.findClosest('.g_record_detail').find('.detail_row_num').getText()),
|
||||
catchNoSuchElem(() => cursor.findClosest('.g_record_detail_el')
|
||||
.find('.g_record_detail_label').getText())
|
||||
]);
|
||||
if (rowNum && colName) {
|
||||
// This must be a detail view, and we just got the info we need.
|
||||
return {rowNum: parseInt(rowNum, 10), col: colName};
|
||||
} else {
|
||||
// We might be on a single card record
|
||||
const counter = await section.findAll(".grist-single-record__menu__count");
|
||||
if (counter.length) {
|
||||
const cardRow = (await counter[0].getText()).split(' OF ')[0];
|
||||
return { rowNum : parseInt(cardRow), col: colName };
|
||||
return await retryOnStale(async () => {
|
||||
const section = await driver.findWait('.active_section', 4000);
|
||||
const cursor = await section.findWait('.active_cursor', 1000);
|
||||
// Query assuming the cursor is in a GridView and a DetailView, then use whichever query data
|
||||
// works out.
|
||||
const [colIndex, rowIndex, rowNum, colName] = await Promise.all([
|
||||
catchNoSuchElem(() => cursor.findClosest('.field').index()),
|
||||
catchNoSuchElem(() => cursor.findClosest('.gridview_row').index()),
|
||||
catchNoSuchElem(() => cursor.findClosest('.g_record_detail').find('.detail_row_num').getText()),
|
||||
catchNoSuchElem(() => cursor.findClosest('.g_record_detail_el')
|
||||
.find('.g_record_detail_label').getText())
|
||||
]);
|
||||
if (rowNum && colName) {
|
||||
// This must be a detail view, and we just got the info we need.
|
||||
return {rowNum: parseInt(rowNum, 10), col: colName};
|
||||
} else {
|
||||
// We might be on a single card record
|
||||
const counter = await section.findAll(".grist-single-record__menu__count");
|
||||
if (counter.length) {
|
||||
const cardRow = (await counter[0].getText()).split(' OF ')[0];
|
||||
return { rowNum : parseInt(cardRow), col: colName };
|
||||
}
|
||||
// Otherwise, it's a grid view, and we need to use indices to look up the info.
|
||||
const gridRows = await section.findAll('.gridview_data_row_num');
|
||||
const gridRowNum = await gridRows[rowIndex].getText();
|
||||
return { rowNum: parseInt(gridRowNum, 10), col: colIndex };
|
||||
}
|
||||
// Otherwise, it's a grid view, and we need to use indices to look up the info.
|
||||
const gridRows = await section.findAll('.gridview_data_row_num');
|
||||
const gridRowNum = await gridRows[rowIndex].getText();
|
||||
return { rowNum: parseInt(gridRowNum, 10), col: colIndex };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -496,6 +498,15 @@ async function catchNoSuchElem(query: () => any) {
|
||||
}
|
||||
}
|
||||
|
||||
async function retryOnStale<T>(query: () => Promise<T>): Promise<T> {
|
||||
try {
|
||||
return await query();
|
||||
} catch (err) {
|
||||
if (err instanceof error.StaleElementReferenceError) { return await query(); }
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Type keys in the currently active cell, then hit Enter to save, and wait for the server.
|
||||
|
Loading…
Reference in New Issue
Block a user