diff --git a/tests/DataTable/DataTableSearch.test.svelte b/tests/DataTable/DataTableSearch.test.svelte
new file mode 100644
index 00000000..f333b343
--- /dev/null
+++ b/tests/DataTable/DataTableSearch.test.svelte
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/DataTable/DataTableSearch.test.ts b/tests/DataTable/DataTableSearch.test.ts
new file mode 100644
index 00000000..71758718
--- /dev/null
+++ b/tests/DataTable/DataTableSearch.test.ts
@@ -0,0 +1,232 @@
+import { render, screen } from "@testing-library/svelte";
+import { user } from "../setup-tests";
+import DataTableSearch from "./DataTableSearch.test.svelte";
+
+describe("DataTableSearch", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ // Remove first row since it's the header
+ const getTableRows = () => screen.getAllByRole("row").slice(1);
+ const getNextPageButton = () =>
+ screen.getByRole("button", { name: "Next page" });
+ const getPrevPageButton = () =>
+ screen.getByRole("button", { name: "Previous page" });
+ const allRowsRendered = () => {
+ const tableRows = getTableRows();
+ expect(tableRows).toHaveLength(5);
+ tableRows.forEach((row) => {
+ expect(row).toHaveTextContent(/Round robin|DNS delegation/);
+ });
+
+ expect(screen.getByText("1–5 of 10 items")).toBeInTheDocument();
+ expect(screen.getByText("of 2 pages")).toBeInTheDocument();
+ expect(getNextPageButton()).toBeEnabled();
+ expect(getPrevPageButton()).toBeDisabled();
+ };
+
+ it("renders non-persistent search input", async () => {
+ render(DataTableSearch);
+
+ const searchBar = screen.getByRole("search");
+ expect(searchBar).not.toHaveClass(
+ "bx--toolbar-search-container-persistent",
+ );
+ expect(searchBar).not.toHaveClass("bx--toolbar-search-container-active");
+
+ const searchInput = screen.getByRole("searchbox");
+ expect(searchInput).toHaveValue("");
+ expect(searchInput).not.toHaveFocus();
+ allRowsRendered();
+
+ await user.type(searchInput, "dns");
+ expect(searchInput).toHaveValue("dns");
+ expect(searchInput).toHaveFocus();
+ expect(searchBar).toHaveClass("bx--toolbar-search-container-active");
+ expect(screen.getByText("1–5 of 5 items")).toBeInTheDocument();
+ expect(screen.getByText("of 1 page")).toBeInTheDocument();
+ expect(getNextPageButton()).toBeDisabled();
+ expect(getPrevPageButton()).toBeDisabled();
+
+ let tableRows = getTableRows();
+ expect(tableRows).toHaveLength(5);
+ tableRows.forEach((row) => {
+ expect(row).toHaveTextContent("DNS");
+ });
+
+ await user.keyboard("{Tab}{Enter}");
+ expect(searchInput).toHaveValue("");
+ expect(searchInput).toHaveFocus();
+ allRowsRendered();
+
+ await user.keyboard("{Tab}");
+ expect(searchBar).not.toHaveClass("bx--toolbar-search-container-active");
+ });
+
+ it("renders persistent search input", async () => {
+ render(DataTableSearch, {
+ props: {
+ persistent: true,
+ },
+ });
+
+ const searchBar = screen.getByRole("search");
+ expect(searchBar).toHaveClass("bx--toolbar-search-container-persistent");
+
+ const searchInput = screen.getByRole("searchbox");
+ expect(searchInput).toHaveValue("");
+ expect(searchInput).not.toHaveFocus();
+ allRowsRendered();
+
+ await user.type(searchInput, "dns");
+ expect(searchInput).toHaveValue("dns");
+ expect(searchInput).toHaveFocus();
+ expect(screen.getByText("1–5 of 5 items")).toBeInTheDocument();
+ expect(screen.getByText("of 1 page")).toBeInTheDocument();
+ expect(getNextPageButton()).toBeDisabled();
+ expect(getPrevPageButton()).toBeDisabled();
+
+ let tableRows = getTableRows();
+ expect(tableRows).toHaveLength(5);
+ tableRows.forEach((row) => {
+ expect(row).toHaveTextContent("DNS");
+ });
+
+ await user.keyboard("{Tab}{Enter}");
+ expect(searchInput).toHaveValue("");
+ expect(searchInput).toHaveFocus();
+ allRowsRendered();
+ });
+
+ it("renders with initial search value in non-persistent search input", async () => {
+ render(DataTableSearch, {
+ props: {
+ value: "round",
+ },
+ });
+
+ const searchInput = screen.getByRole("searchbox");
+ expect(searchInput).toHaveValue("round");
+ expect(searchInput).not.toHaveFocus();
+
+ // Search bar should be active.
+ const searchBar = screen.getByRole("search");
+ expect(searchBar).toHaveClass("bx--toolbar-search-container-active");
+
+ expect(screen.getByText("1–5 of 5 items")).toBeInTheDocument();
+ expect(screen.getByText("of 1 page")).toBeInTheDocument();
+ expect(getNextPageButton()).toBeDisabled();
+ expect(getPrevPageButton()).toBeDisabled();
+
+ let tableRows = getTableRows();
+ expect(tableRows).toHaveLength(5);
+ tableRows.forEach((row) => {
+ expect(row).toHaveTextContent("Round");
+ });
+
+ await user.click(
+ screen.getByRole("button", { name: "Clear search input" }),
+ );
+ expect(searchInput).toHaveValue("");
+ expect(searchInput).toHaveFocus();
+ allRowsRendered();
+
+ await user.type(searchInput, "rr");
+
+ tableRows = getTableRows();
+ expect(tableRows).toHaveLength(0);
+
+ expect(screen.getByText("0–0 of 0 items")).toBeInTheDocument();
+ expect(screen.getByText("of 1 page")).toBeInTheDocument();
+ expect(getNextPageButton()).toBeDisabled();
+ expect(getPrevPageButton()).toBeDisabled();
+
+ await user.keyboard("{Escape}");
+ expect(searchInput).toHaveValue("");
+ expect(searchInput).toHaveFocus();
+ allRowsRendered();
+
+ await user.keyboard("{Tab}");
+ expect(searchBar).not.toHaveClass("bx--toolbar-search-container-active");
+ });
+
+ it("can filter with a custom filter function", async () => {
+ render(DataTableSearch, {
+ props: {
+ shouldFilterRows: (row, value) => {
+ return (
+ /(6|8)$/.test(row.name) &&
+ row.rule.toLowerCase().includes((value + "").toLowerCase())
+ );
+ },
+ },
+ });
+
+ allRowsRendered();
+
+ const searchInput = screen.getByRole("searchbox");
+ await user.type(searchInput, "round");
+ expect(searchInput).toHaveValue("round");
+ expect(searchInput).toHaveFocus();
+ expect(screen.getByText("1–2 of 2 items")).toBeInTheDocument();
+ expect(screen.getByText("of 1 page")).toBeInTheDocument();
+ expect(getNextPageButton()).toBeDisabled();
+ expect(getPrevPageButton()).toBeDisabled();
+
+ let tableRows = getTableRows();
+ expect(tableRows).toHaveLength(2);
+ tableRows.forEach((row) => {
+ expect(row).toHaveTextContent("Round");
+ expect(row).toHaveTextContent(/Load Balancer 6|Load Balancer 8/);
+ });
+ });
+
+ // TODO: fix reactivity
+ it.skip("re-filters rows when toggled", async () => {
+ render(DataTableSearch);
+
+ allRowsRendered();
+
+ const searchInput = screen.getByRole("searchbox");
+ await user.type(searchInput, "round");
+ expect(searchInput).toHaveValue("round");
+ expect(searchInput).toHaveFocus();
+ expect(screen.getByText("1–5 of 5 items")).toBeInTheDocument();
+ expect(screen.getByText("of 1 page")).toBeInTheDocument();
+ expect(getNextPageButton()).toBeDisabled();
+ expect(getPrevPageButton()).toBeDisabled();
+
+ const toggleButton = screen.getByRole("button", { name: "Toggle rows" });
+ await user.click(toggleButton);
+
+ expect(searchInput).toHaveValue("round");
+ expect(searchInput).not.toHaveFocus();
+
+ expect(screen.getByText("1–2 of 2 items")).toBeInTheDocument();
+ expect(screen.getByText("of 1 page")).toBeInTheDocument();
+ expect(getNextPageButton()).toBeDisabled();
+ expect(getPrevPageButton()).toBeDisabled();
+
+ let tableRows = getTableRows();
+ expect(tableRows).toHaveLength(2);
+ tableRows.forEach((row) => {
+ expect(row).toHaveTextContent("Round!");
+ });
+
+ await user.click(toggleButton);
+ expect(searchInput).toHaveValue("round");
+ expect(searchInput).not.toHaveFocus();
+ expect(screen.getByText("1–5 of 5 items")).toBeInTheDocument();
+ expect(screen.getByText("of 1 page")).toBeInTheDocument();
+ expect(getNextPageButton()).toBeDisabled();
+ expect(getPrevPageButton()).toBeDisabled();
+
+ await user.click(
+ screen.getByRole("button", { name: "Clear search input" }),
+ );
+ expect(searchInput).toHaveValue("");
+ expect(searchInput).toHaveFocus();
+ allRowsRendered();
+ });
+});