Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/o-spreadsheet-engine/src/helpers/text_helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,9 @@ export function getFontSizeMatchingWidth(
return fontSize;
}

/** Transform a string to lower case. If the string is undefined, return an empty string */
export function toLowerCase(str: string | undefined): string {
return str ? str.toLowerCase() : "";
/** Transform a string to lowercase and removes whitespace from both ends of the string*/
export function toTrimmedLowerCase(str: string | undefined): string {
return str ? str.toLowerCase().trim() : "";
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { isMultipleElementMatrix, toScalar } from "../../functions/helper_matric
import { parseLiteral } from "../../helpers/cells/cell_evaluation";
import { toXC } from "../../helpers/coordinates";
import { deepCopy, getUniqueText, range } from "../../helpers/misc";
import { toLowerCase } from "../../helpers/text_helper";
import { toTrimmedLowerCase } from "../../helpers/text_helper";
import { positions, toZone, zoneToDimension } from "../../helpers/zones";
import { criterionEvaluatorRegistry } from "../../registries/criterion_registry";
import { Command, CommandResult, LocalCommand, UpdateFilterCommand } from "../../types/commands";
Expand Down Expand Up @@ -139,6 +139,11 @@ export class FilterEvaluationPlugin extends UIPlugin {
const id = this.getters.getFilterId({ sheetId, col, row });
if (!id) return;
if (!this.filterValues[sheetId]) this.filterValues[sheetId] = {};
if (value.filterType === "values") {
value.hiddenValues = value.hiddenValues.map(toTrimmedLowerCase);
} else if (value.filterType === "criterion") {
value.values = value.values.map(toTrimmedLowerCase);
}
this.filterValues[sheetId][id] = value;
}

Expand All @@ -163,7 +168,7 @@ export class FilterEvaluationPlugin extends UIPlugin {
continue;
}
if (filterValue.filterType === "values") {
const filteredValues = filterValue.hiddenValues?.map(toLowerCase);
const filteredValues = filterValue.hiddenValues;
if (!filteredValues) continue;
const filteredValuesSet = new Set(filteredValues);
for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
Expand Down Expand Up @@ -205,7 +210,7 @@ export class FilterEvaluationPlugin extends UIPlugin {

private getCellValueAsString(sheetId: UID, col: number, row: number): string {
const value = this.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue;
return value.toLowerCase();
return toTrimmedLowerCase(value);
}

exportForExcel(data: ExcelWorkbookData) {
Expand Down Expand Up @@ -234,7 +239,7 @@ export class FilterEvaluationPlugin extends UIPlugin {
if (filteredValues.length) {
const xlsxDisplayedValues = valuesInFilterZone
.filter((val) => val)
.filter((val) => !filteredValues.includes(val));
.filter((val) => !filteredValues.includes(toTrimmedLowerCase(val)));
filters.push({
colId: i,
displayedValues: [...new Set(xlsxDisplayedValues)],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { detectLink } from "../helpers/links";
import { localizeContent } from "../helpers/locale";
import { isNumberBetween } from "../helpers/misc";
import { rangeReference } from "../helpers/references";
import { toTrimmedLowerCase } from "../helpers/text_helper";
import { _t } from "../translation";
import { CellValue } from "../types/cells";
import {
Expand Down Expand Up @@ -115,8 +116,8 @@ criterionEvaluatorRegistry.add("notContainsText", {
criterionEvaluatorRegistry.add("isEqualText", {
type: "isEqualText",
isValueValid: (value: CellValue, criterion: EvaluatedCriterion) => {
const strValue = String(value);
return strValue.toLowerCase() === String(criterion.values[0]).toLowerCase();
const strValue = toTrimmedLowerCase(String(value));
return strValue === String(criterion.values[0]);
},
getErrorString: (criterion: EvaluatedCriterion) => {
return _t('The value must be exactly "%s"', String(criterion.values[0]));
Expand Down Expand Up @@ -663,8 +664,8 @@ criterionEvaluatorRegistry.add("customFormula", {
criterionEvaluatorRegistry.add("beginsWithText", {
type: "beginsWithText",
isValueValid: (value: CellValue, criterion: EvaluatedCriterion) => {
const strValue = String(value);
return strValue.toLowerCase().startsWith(String(criterion.values[0]).toLowerCase());
const strValue = toTrimmedLowerCase(String(value));
return strValue.startsWith(String(criterion.values[0]));
},
getErrorString: (criterion: EvaluatedCriterion) => {
return _t('The value must be a text that begins with "%s"', String(criterion.values[0]));
Expand All @@ -679,8 +680,8 @@ criterionEvaluatorRegistry.add("beginsWithText", {
criterionEvaluatorRegistry.add("endsWithText", {
type: "endsWithText",
isValueValid: (value: CellValue, criterion: EvaluatedCriterion) => {
const strValue = String(value);
return strValue.toLowerCase().endsWith(String(criterion.values[0]).toLowerCase());
const strValue = toTrimmedLowerCase(String(value));
return strValue.endsWith(String(criterion.values[0]));
},
getErrorString: (criterion: EvaluatedCriterion) => {
return _t('The value must be a text that ends with "%s"', String(criterion.values[0]));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SpreadsheetChildEnv } from "@odoo/o-spreadsheet-engine/types/spreadsheet_env";
import { Component, onWillUpdateProps, useRef, useState } from "@odoo/owl";
import { deepEquals, positions, toLowerCase } from "../../../helpers";
import { deepEquals, positions, toTrimmedLowerCase } from "../../../helpers";
import { fuzzyLookup } from "../../../helpers/search";
import { Position } from "../../../types";
import { FilterMenuValueItem } from "../filter_menu_item/filter_menu_value_item";
Expand Down Expand Up @@ -76,12 +76,12 @@ export class FilterMenuValueList extends Component<Props, SpreadsheetChildEnv> {

const cellValues = cells.map((val) => val.cellValue);
const filterValues = filterValue?.filterType === "values" ? filterValue.hiddenValues : [];
const normalizedFilteredValues = new Set(filterValues.map(toLowerCase));
const normalizedFilteredValues = new Set(filterValues);

const set = new Set<string>();
const values: (Value & { normalizedValue: string })[] = [];
const addValue = (value: string) => {
const normalizedValue = toLowerCase(value);
const normalizedValue = toTrimmedLowerCase(value);
if (!set.has(normalizedValue)) {
values.push({
string: value || "",
Expand Down
50 changes: 40 additions & 10 deletions tests/table/filter_evaluation_plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ describe("Simple filter test", () => {
test("Can update a filter", () => {
createTableWithFilter(model, "A1:A5");
updateFilter(model, "A1", ["2", "A"]);
expect(model.getters.getFilterHiddenValues({ sheetId, col: 0, row: 0 })).toEqual(["2", "A"]);
expect(model.getters.getFilterHiddenValues({ sheetId, col: 0, row: 0 })).toEqual(["2", "a"]);
});

test("Can update a filter in readonly mode", () => {
createTableWithFilter(model, "A1:A5");
model.updateMode("readonly");
updateFilter(model, "A1", ["2", "A"]);
expect(model.getters.getFilterHiddenValues({ sheetId, col: 0, row: 0 })).toEqual(["2", "A"]);
expect(model.getters.getFilterHiddenValues({ sheetId, col: 0, row: 0 })).toEqual(["2", "a"]);
});

test("Update filter is correctly rejected when target is not inside a table", () => {
Expand Down Expand Up @@ -95,11 +95,11 @@ describe("Simple filter test", () => {
sheetIdTo: sheet2Id,
sheetNameTo: "Copy of Sheet1",
});
expect(getFilterHiddenValues(model, sheet2Id)).toMatchObject([{ zone: "B1:B3", value: ["C"] }]);
expect(getFilterHiddenValues(model, sheet2Id)).toMatchObject([{ zone: "B1:B3", value: ["c"] }]);
deleteColumns(model, ["A"], sheet2Id);

expect(getFilterHiddenValues(model, sheetId)).toMatchObject([{ zone: "B1:B3", value: ["C"] }]);
expect(getFilterHiddenValues(model, sheet2Id)).toMatchObject([{ zone: "A1:A3", value: ["C"] }]);
expect(getFilterHiddenValues(model, sheetId)).toMatchObject([{ zone: "B1:B3", value: ["c"] }]);
expect(getFilterHiddenValues(model, sheet2Id)).toMatchObject([{ zone: "A1:A3", value: ["c"] }]);
});
});

Expand Down Expand Up @@ -161,6 +161,15 @@ describe("Filter Evaluation", () => {
expect(model.getters.isRowHidden(sheetId, 2)).toEqual(true);
});

test("Filters ignore whitespaces", () => {
setCellContent(model, "A2", "a");
setCellContent(model, "A3", " a");
setCellContent(model, "A4", "a ");
updateFilter(model, "A2", ["a"]);
expect(model.getters.isRowHidden(sheetId, 1)).toEqual(true);
expect(model.getters.isRowHidden(sheetId, 2)).toEqual(true);
expect(model.getters.isRowHidden(sheetId, 3)).toEqual(true);
});
test("Header is not filtered", () => {
updateFilter(model, "A1", ["A1"]);
expect(model.getters.isRowHidden(sheetId, 0)).toEqual(false);
Expand Down Expand Up @@ -195,17 +204,17 @@ describe("Filter Evaluation", () => {

test("Updating a table zone keep the filtered values if the filter header did not move", () => {
updateFilter(model, "A1", ["A2"]);
expect(getFilterHiddenValues(model, sheetId)).toMatchObject([{ zone: "A1:A5", value: ["A2"] }]);
expect(getFilterHiddenValues(model, sheetId)).toMatchObject([{ zone: "A1:A5", value: ["a2"] }]);
expect(model.getters.isRowHidden(sheetId, 1)).toEqual(true);

updateTableZone(model, "A1:A5", "A1:A6");
expect(getFilterHiddenValues(model, sheetId)).toMatchObject([{ zone: "A1:A6", value: ["A2"] }]);
expect(getFilterHiddenValues(model, sheetId)).toMatchObject([{ zone: "A1:A6", value: ["a2"] }]);
expect(model.getters.isRowHidden(sheetId, 1)).toEqual(true);
});

test("Updating a table zone drops the filtered values if the filter header moved", () => {
updateFilter(model, "A1", ["A3"]);
expect(getFilterHiddenValues(model, sheetId)).toMatchObject([{ zone: "A1:A5", value: ["A3"] }]);
expect(getFilterHiddenValues(model, sheetId)).toMatchObject([{ zone: "A1:A5", value: ["a3"] }]);
expect(model.getters.isRowHidden(sheetId, 2)).toEqual(true);

updateTableZone(model, "A1:A5", "A2:A5");
Expand All @@ -215,7 +224,7 @@ describe("Filter Evaluation", () => {

test("Updating a table zone updates the hidden rows", () => {
updateFilter(model, "A1", ["A2"]);
expect(getFilterHiddenValues(model, sheetId)).toMatchObject([{ zone: "A1:A5", value: ["A2"] }]);
expect(getFilterHiddenValues(model, sheetId)).toMatchObject([{ zone: "A1:A5", value: ["a2"] }]);
expect(model.getters.isRowHidden(sheetId, 1)).toEqual(true);

updateTableZone(model, "A1:A5", "E1:E3");
Expand All @@ -225,7 +234,7 @@ describe("Filter Evaluation", () => {

test("Removing the filters from a table updates the hidden rows", () => {
updateFilter(model, "A1", ["A2"]);
expect(getFilterHiddenValues(model, sheetId)).toMatchObject([{ zone: "A1:A5", value: ["A2"] }]);
expect(getFilterHiddenValues(model, sheetId)).toMatchObject([{ zone: "A1:A5", value: ["a2"] }]);
expect(model.getters.isRowHidden(sheetId, 1)).toEqual(true);

updateTableConfig(model, "A1:A5", { hasFilters: false });
Expand Down Expand Up @@ -377,6 +386,27 @@ describe("Filter criterion test", () => {
}
);

test.each(["beginsWithText", "endsWithText", "isEqualText"] as const)(
"Can filter based on a text criterion %s ignoring lowercase/uppercase and whitespaces",
(type) => {
const grid = {
A2: "a",
A3: " a",
A4: "a ",
A5: "A",
A6: "b",
};
setGrid(model, grid);
createTableWithFilter(model, "A1:A6");
updateFilterCriterion(model, "A1", {
type,
values: ["a"],
});

expect(getFilteredRows()).toEqual([5]);
}
);

test.each([
["isEqual", ["1"], [2, 3]],
["isNotEqual", ["1"], [1]],
Expand Down
Loading