Merge branch 'main' into dev
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
buildVisibleComboboxOptions,
|
||||
filterAndRankComboboxOptions,
|
||||
getComboboxFallbackIndex,
|
||||
orderVisibleComboboxOptions,
|
||||
} from "./combobox-options";
|
||||
@@ -47,6 +48,48 @@ describe("buildVisibleComboboxOptions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("filterAndRankComboboxOptions", () => {
|
||||
const options = [
|
||||
{ id: "feat/login", label: "feat/login" },
|
||||
{ id: "main", label: "main" },
|
||||
{ id: "feat/main-nav", label: "feat/main-nav" },
|
||||
{ id: "fix/logout", label: "fix/logout", description: "fixes main logout bug" },
|
||||
];
|
||||
|
||||
it("returns all options when search is empty", () => {
|
||||
expect(filterAndRankComboboxOptions(options, "")).toEqual(options);
|
||||
});
|
||||
|
||||
it("filters by label substring", () => {
|
||||
const result = filterAndRankComboboxOptions(options, "login");
|
||||
expect(result.map((o) => o.id)).toEqual(["feat/login"]);
|
||||
});
|
||||
|
||||
it("filters by id substring", () => {
|
||||
const result = filterAndRankComboboxOptions(options, "fix/");
|
||||
expect(result.map((o) => o.id)).toEqual(["fix/logout"]);
|
||||
});
|
||||
|
||||
it("filters by description substring", () => {
|
||||
const result = filterAndRankComboboxOptions(options, "logout bug");
|
||||
expect(result.map((o) => o.id)).toEqual(["fix/logout"]);
|
||||
});
|
||||
|
||||
it("ranks prefix matches above substring matches", () => {
|
||||
const result = filterAndRankComboboxOptions(options, "main");
|
||||
expect(result.map((o) => o.id)).toEqual(["main", "feat/main-nav", "fix/logout"]);
|
||||
});
|
||||
|
||||
it("is case-insensitive", () => {
|
||||
const items = [{ id: "Alpha", label: "Alpha" }];
|
||||
expect(filterAndRankComboboxOptions(items, "alpha")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("returns empty when nothing matches", () => {
|
||||
expect(filterAndRankComboboxOptions(options, "zzz")).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("combobox above-search ordering", () => {
|
||||
const visible = [
|
||||
{ id: "/tmp/new-project", label: "/tmp/new-project", kind: "directory" as const },
|
||||
|
||||
@@ -35,18 +35,33 @@ export function shouldShowCustomComboboxOption(input: {
|
||||
);
|
||||
}
|
||||
|
||||
export function filterAndRankComboboxOptions(
|
||||
options: ComboboxOptionModel[],
|
||||
search: string,
|
||||
): ComboboxOptionModel[] {
|
||||
if (!search) return options;
|
||||
return options
|
||||
.filter(
|
||||
(opt) =>
|
||||
opt.label.toLowerCase().includes(search) ||
|
||||
opt.id.toLowerCase().includes(search) ||
|
||||
opt.description?.toLowerCase().includes(search),
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const aPrefix =
|
||||
a.label.toLowerCase().startsWith(search) || a.id.toLowerCase().startsWith(search);
|
||||
const bPrefix =
|
||||
b.label.toLowerCase().startsWith(search) || b.id.toLowerCase().startsWith(search);
|
||||
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
export function buildVisibleComboboxOptions(
|
||||
input: BuildVisibleComboboxOptionsInput,
|
||||
): ComboboxOptionModel[] {
|
||||
const normalizedSearch = input.searchable ? input.searchQuery.trim().toLowerCase() : "";
|
||||
const filteredOptions = normalizedSearch
|
||||
? input.options.filter(
|
||||
(opt) =>
|
||||
opt.label.toLowerCase().includes(normalizedSearch) ||
|
||||
opt.id.toLowerCase().includes(normalizedSearch) ||
|
||||
opt.description?.toLowerCase().includes(normalizedSearch),
|
||||
)
|
||||
: input.options;
|
||||
const filteredOptions = filterAndRankComboboxOptions(input.options, normalizedSearch);
|
||||
|
||||
const sanitizedSearchValue = input.searchQuery.trim();
|
||||
const showCustomOption = shouldShowCustomComboboxOption({
|
||||
|
||||
Reference in New Issue
Block a user