Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {
addFilter,
useDashboardFetchMocksForComponentTests,
} from "@rilldata/web-common/features/dashboards/filters/test/filter-test-utils";
import DimensionFilter from "@rilldata/web-common/features/dashboards/filters/dimension-filters/DimensionFilter.svelte";
import { DimensionFilterMode } from "@rilldata/web-common/features/dashboards/filters/dimension-filters/constants";
import { renderFilterComponent } from "@rilldata/web-common/features/dashboards/filters/test/render-filter-component";
import {
createAndExpression,
Expand All @@ -16,9 +18,19 @@ import {
AD_BIDS_PUBLISHER_DIMENSION,
} from "@rilldata/web-common/features/dashboards/stores/test-data/data";
import { mockAnimationsForComponentTesting } from "@rilldata/web-common/lib/test/mock-animations";
import { act, fireEvent, screen, waitFor } from "@testing-library/svelte";
import {
RUNTIME_CONTEXT_KEY,
RuntimeClient,
} from "@rilldata/web-common/runtime-client/v2";
import {
act,
fireEvent,
render,
screen,
waitFor,
} from "@testing-library/svelte";
import { get } from "svelte/store";
import { beforeAll, describe, expect, it } from "vitest";
import { beforeAll, describe, expect, it, vi } from "vitest";

// bits-ui 2.x Select uses PointerEvent APIs that jsdom doesn't support.
// Polyfill the missing types and methods so pointer-based interactions work in tests.
Expand Down Expand Up @@ -337,55 +349,84 @@ describe("DimensionFilter", () => {
);
});

it("In-List filter mode using search text", async () => {
const { stateManagers } = renderFilterComponent();
it("Select filter mode treats comma search text literally", async () => {
renderFilterComponent();

// Add a filter pill for publisher
await addFilter("publisher");

// Enter a search term with commas
// Enter search text with commas.
await act(() =>
fireEvent.input(screen.getByLabelText("publisher search list"), {
target: { value: "Facebook,Google,Apple" },
}),
);
// Mode is automatically changed to In-List
expect(getModeSelectorText()).toContain("In List");
// 2 of 3 results matched based on mocked response.

// Select mode should not auto-switch to In List.
expect(getModeSelectorText()).toContain("Select");
expect(
screen.queryByLabelText("publisher result count"),
).not.toBeInTheDocument();
await waitFor(() =>
expect(screen.getByLabelText("publisher result count")).toHaveTextContent(
"2 of 3 matched",
expect(screen.getByLabelText("Open publisher filter")).toHaveTextContent(
"publisher",
),
);
expect(getGroupItemTexts("publisher results")).toEqual([
});

it("checks In List URL length using selected and searched values", async () => {
const isUrlTooLongAfterInListFilter = vi.fn(() => false);

render(DimensionFilter, {
props: {
filterData: {
name: AD_BIDS_PUBLISHER_DIMENSION,
label: AD_BIDS_PUBLISHER_DIMENSION,
mode: DimensionFilterMode.InList,
dimensions: new Map([[AD_BIDS_METRICS_NAME, {}]]),
selectedValues: ["Existing"],
},
expressionMap: new Map(),
openOnMount: false,
timeStart: undefined,
timeEnd: undefined,
timeDimension: undefined,
timeControlsReady: false,
removeDimensionFilter: vi.fn(),
applyDimensionInListMode: vi.fn(),
toggleDimensionValueSelections: vi.fn(),
applyDimensionContainsMode: vi.fn(),
toggleDimensionFilterMode: vi.fn(),
isUrlTooLongAfterInListFilter,
},
context: new Map<string | symbol, unknown>([
[
RUNTIME_CONTEXT_KEY,
new RuntimeClient({ host: "http://localhost", instanceId: "test" }),
],
]),
});

await act(() => screen.getByLabelText("Open publisher filter").click());
await act(() =>
fireEvent.input(screen.getByLabelText("publisher search list"), {
target: { value: "Facebook,Google" },
}),
);

expect(isUrlTooLongAfterInListFilter).toHaveBeenLastCalledWith([
"Existing",
"Facebook",
"Google",
"Apple",
]);
// Pill is updated as well.
expect(screen.getByLabelText("Open publisher filter")).toHaveTextContent(
"publisher In list (2 of 3)",
);

// Apply to get the filter to take effect.
await act(() => screen.getByRole("button", { name: "Apply" }).click());

// Filter is added to the dashboard
expect(get(stateManagers.dashboardStore).whereFilter).toEqual(
createAndExpression([
createInExpression(AD_BIDS_PUBLISHER_DIMENSION, [
"Facebook",
"Google",
"Apple",
]),
]),
);
expect(
get(stateManagers.dashboardStore).dimensionsWithInlistFilter,
).toEqual(["publisher"]);
// Filter pill is persisted
expect(screen.getByLabelText("Open publisher filter")).toHaveTextContent(
"publisher In list (2 of 3)",
// Close the dropdown so bits-ui's body-scroll-lock cleanup fires while
// jsdom is still alive (otherwise the deferred cleanup fires after teardown
// and produces an unhandled "document is not defined" error).
await act(() =>
fireEvent.keyDown(screen.getByLabelText("publisher search list"), {
key: "Escape",
}),
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -228,24 +228,21 @@
function checkSearchText(inputText: string) {
inListTooLong = false;

// Do not check search text and possibly switch to InList when mode is Contains
if (curMode === DimensionFilterMode.Contains) return;
// Only InList mode parses bulk values. Other modes treat input as search text.
if (curMode !== DimensionFilterMode.InList) return;

const values = splitDimensionSearchText(inputText);

if (values.length <= 1) {
if (curMode === DimensionFilterMode.InList) {
searchedBulkValues = inputText === "" ? [] : values;
}
searchedBulkValues = inputText === "" ? [] : values;
return;
}

// When switching to InList mode, include both existing selected values and new search values
// This ensures the below-fold query can find existing selected values that might not be in top 250
const allRelevantValues = [...new Set([...selectedValues, ...values])];
searchedBulkValues = allRelevantValues;
curMode = DimensionFilterMode.InList;
inListTooLong = isUrlTooLongAfterInListFilter(values);
// Include both existing selected values and new search values so the
// below-fold query can find existing selected values that might not be
// in the top 250.
searchedBulkValues = [...new Set([...selectedValues, ...values])];
inListTooLong = isUrlTooLongAfterInListFilter(searchedBulkValues);
}

function handleModeChange(newMode: DimensionFilterMode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DimensionFilterMode } from "./constants";
export function getSearchPlaceholder(mode: DimensionFilterMode): string {
switch (mode) {
case DimensionFilterMode.Select:
return "Enter search term or paste list of values";
return "Enter search term";
case DimensionFilterMode.InList:
return "Paste a list separated by commas or \\n";
case DimensionFilterMode.Contains:
Expand Down
Loading