Files
OmniRoute/tests/unit/sidebar-search-filter.test.ts
Diego Rodrigues de Sa e Souza 2c413f2b75 feat: sidebar search/filter input (#4013) (#6810)
* feat(dashboard): add search/filter input to the dashboard sidebar (#4013)

Adds a search box at the top of the expanded sidebar that filters nav
sections/groups/items client-side by label, so users don't have to
hunt through the growing nav tree. Reuses the existing common.search /
common.noResults i18n keys (no new locale edits needed) and the shared
Input icon="search" pattern. Matching sections auto-expand while
searching and the accordion/pin state is restored once the query is
cleared.

Filtering logic is extracted into a pure filterSidebarSectionsByQuery()
helper (src/shared/utils/sidebarSearch.ts) so it is trivially unit
testable independent of React/next-intl/next-navigation.

* fix(test): move Sidebar.search test to a runner-collected path (test-discovery gate)
2026-07-11 05:04:13 -03:00

93 lines
2.9 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { filterSidebarSectionsByQuery } from "../../src/shared/utils/sidebarSearch.ts";
type Item = { id: string; label: string };
type Group = { type: "group"; id: string; items: Item[] };
type Section = { id: string; children: (Item | Group)[] };
function makeSections(): Section[] {
return [
{
id: "omni-proxy",
children: [
{ id: "combos", label: "Combos" },
{ id: "providers", label: "Providers" },
{
type: "group",
id: "tools",
items: [
{ id: "playground", label: "Playground" },
{ id: "logs", label: "Request Logs" },
],
},
],
},
{
id: "configuration",
children: [
{ id: "settings", label: "Settings" },
{ id: "webhooks", label: "Webhooks" },
],
},
];
}
test("empty/whitespace query returns all sections unchanged (identity-ish)", () => {
const sections = makeSections();
assert.deepEqual(filterSidebarSectionsByQuery(sections, ""), sections);
assert.deepEqual(filterSidebarSectionsByQuery(sections, " "), sections);
});
test("filters flat items by case-insensitive substring match on label", () => {
const result = filterSidebarSectionsByQuery(makeSections(), "combo");
assert.equal(result.length, 1);
assert.equal(result[0].id, "omni-proxy");
assert.deepEqual(
result[0].children.map((c) => ("label" in c ? c.id : c.id)),
["combos"]
);
});
test("matches are case-insensitive", () => {
const result = filterSidebarSectionsByQuery(makeSections(), "SETTINGS");
assert.equal(result.length, 1);
assert.equal(result[0].id, "configuration");
});
test("filters items inside a group, dropping non-matching group members", () => {
const result = filterSidebarSectionsByQuery(makeSections(), "logs");
assert.equal(result.length, 1);
const group = result[0].children.find((c) => "type" in c && c.type === "group") as
| Group
| undefined;
assert.ok(group, "expected the tools group to survive filtering");
assert.deepEqual(
group!.items.map((i) => i.id),
["logs"]
);
});
test("drops a group entirely when none of its items match", () => {
const result = filterSidebarSectionsByQuery(makeSections(), "playground-xyz-no-match");
assert.deepEqual(result, []);
});
test("drops sections that have no matching children at all", () => {
const result = filterSidebarSectionsByQuery(makeSections(), "webhooks");
assert.equal(result.length, 1);
assert.equal(result[0].id, "configuration");
});
test("query matching nothing returns an empty array", () => {
const result = filterSidebarSectionsByQuery(makeSections(), "zzz-nonexistent-zzz");
assert.deepEqual(result, []);
});
test("does not mutate the input sections", () => {
const sections = makeSections();
const snapshot = JSON.parse(JSON.stringify(sections));
filterSidebarSectionsByQuery(sections, "combo");
assert.deepEqual(sections, snapshot);
});