mirror of
https://github.com/home-assistant/frontend.git
synced 2026-07-02 03:55:52 +01:00
159 lines
4.5 KiB
TypeScript
159 lines
4.5 KiB
TypeScript
import Fuse from "fuse.js";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
multiTermSortedSearch,
|
|
type FuseWeightedKey,
|
|
} from "../../src/resources/fuseMultiTerm";
|
|
|
|
interface Item {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
}
|
|
|
|
const keys: FuseWeightedKey[] = [
|
|
{ name: "name", weight: 10 },
|
|
{ name: "description", weight: 1 },
|
|
];
|
|
|
|
const getId = (i: Item) => i.id;
|
|
|
|
const search = (items: Item[], query: string) => {
|
|
const index = Fuse.createIndex(keys, items);
|
|
return multiTermSortedSearch(items, query, getId, index);
|
|
};
|
|
|
|
const ids = (items: Item[], query: string) => search(items, query).map(getId);
|
|
|
|
const item = (id: string, name: string, description = ""): Item => ({
|
|
id,
|
|
name,
|
|
description,
|
|
});
|
|
|
|
describe("multiTermSortedSearch", () => {
|
|
describe("filtering", () => {
|
|
it("returns all items for a blank search", () => {
|
|
const items = [item("1", "Alpha"), item("2", "Beta")];
|
|
|
|
expect(search(items, " ")).toEqual(items);
|
|
});
|
|
|
|
it("returns an empty array when there are no items", () => {
|
|
expect(search([], "alpha")).toEqual([]);
|
|
});
|
|
|
|
it("filters to matching items for a single term", () => {
|
|
const items = [item("match", "Apple pie"), item("miss", "Banana bread")];
|
|
|
|
expect(ids(items, "apple")).toEqual(["match"]);
|
|
});
|
|
|
|
it("keeps only items that match every term", () => {
|
|
const items = [item("both", "Red apple"), item("one", "Red onion")];
|
|
|
|
expect(ids(items, "red apple")).toEqual(["both"]);
|
|
});
|
|
|
|
it("returns nothing when a term matches no item", () => {
|
|
const items = [item("1", "Red apple"), item("2", "Green apple")];
|
|
|
|
expect(search(items, "apple zzzzz")).toEqual([]);
|
|
});
|
|
|
|
it("matches case-insensitively", () => {
|
|
const items = [item("1", "Red Apple")];
|
|
|
|
expect(ids(items, "RED apple")).toEqual(["1"]);
|
|
});
|
|
|
|
it("ignores diacritics", () => {
|
|
const items = [item("1", "Crème brûlée")];
|
|
|
|
expect(ids(items, "creme brulee")).toEqual(["1"]);
|
|
});
|
|
|
|
it("ignores extra whitespace between terms", () => {
|
|
const items = [item("both", "Red apple"), item("one", "Red onion")];
|
|
|
|
expect(ids(items, "red apple")).toEqual(["both"]);
|
|
});
|
|
|
|
it("tolerates a small typo", () => {
|
|
const items = [item("1", "Strawberry"), item("2", "Blueberry")];
|
|
|
|
expect(ids(items, "strawbery")).toEqual(["1"]);
|
|
});
|
|
});
|
|
|
|
describe("ranking", () => {
|
|
it("ranks a match in a higher-weighted field first", () => {
|
|
const inName = item("name", "ripe mango from the orchard");
|
|
const inDescription = item("description", "Smoothie", "ripe mango");
|
|
|
|
expect(ids([inDescription, inName], "ripe mango")[0]).toBe("name");
|
|
});
|
|
|
|
it("ranks the shorter match first when weights are equal", () => {
|
|
const short = item("short", "ripe mango");
|
|
const long = item("long", "ripe mango from the orchard");
|
|
|
|
expect(ids([long, short], "ripe mango")[0]).toBe("short");
|
|
});
|
|
|
|
it("ranks an exact match above a fuzzy one", () => {
|
|
const exact = item("exact", "Red apple");
|
|
const fuzzy = item("fuzzy", "Red aple");
|
|
|
|
expect(ids([fuzzy, exact], "red apple")[0]).toBe("exact");
|
|
});
|
|
});
|
|
|
|
describe("parameters", () => {
|
|
it("uses getItemId to aggregate an item across terms", () => {
|
|
const items = [item("both", "Red apple"), item("one", "Red onion")];
|
|
|
|
const result = multiTermSortedSearch(
|
|
items,
|
|
"red apple",
|
|
getId,
|
|
Fuse.createIndex(keys, items)
|
|
);
|
|
|
|
expect(result.map((i) => i.id)).toEqual(["both"]);
|
|
});
|
|
|
|
it("builds the index from keys passed in options when none is provided", () => {
|
|
const items = [item("1", "Red apple"), item("2", "Green apple")];
|
|
|
|
const result = multiTermSortedSearch(
|
|
items,
|
|
"red apple",
|
|
getId,
|
|
undefined,
|
|
{ keys }
|
|
);
|
|
|
|
expect(result.map((i) => i.id)).toEqual(["1"]);
|
|
});
|
|
|
|
it("returns nothing without an index or keys", () => {
|
|
const items = [item("1", "Red apple")];
|
|
|
|
expect(multiTermSortedSearch(items, "red apple", getId)).toEqual([]);
|
|
});
|
|
|
|
it("forwards Fuse options, like a stricter threshold", () => {
|
|
const items = [item("1", "Strawberry")];
|
|
const index = Fuse.createIndex(keys, items);
|
|
|
|
// The default threshold accepts this typo; a threshold of 0 rejects it.
|
|
expect(
|
|
multiTermSortedSearch(items, "strawbery", getId, index, {
|
|
threshold: 0,
|
|
})
|
|
).toEqual([]);
|
|
});
|
|
});
|
|
});
|