From 097d97905ed047ab67bf03dca8d95f4b59548e31 Mon Sep 17 00:00:00 2001 From: zipg Date: Fri, 17 Jul 2026 15:30:27 +0800 Subject: [PATCH] fix(grid): clear filter state with empty WHERE clause --- apps/desktop/src/components/grid/DataGrid.vue | 6 -- .../components/grid/DataGridFilterBuilder.vue | 23 ++++---- .../components/grid/DataGridQueryControls.vue | 6 +- .../grid/__tests__/DataGridSurfaces.spec.ts | 59 ++++++++++++++++++- 4 files changed, 75 insertions(+), 19 deletions(-) diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 7cb1cf0c8..71101f042 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -1560,11 +1560,6 @@ function onSearchKeydown(e: KeyboardEvent) { } } -function clearWhereFilterInput() { - whereFilterInput.value = ""; - void applyWhereFilter(); -} - watch(whereFilterInput, () => { emit("update:whereInput", currentWhereInput() ?? ""); persistStructuredFilterState(); @@ -7446,7 +7441,6 @@ const gridContextMenuItems = computed(() => { :mode-options="filterModeOptions" :column-search="filterBuilderColumnSearch" :apply-where="applyWhereFilter" - :clear-where="clearWhereFilterInput" :apply-order-by="applyOrderBySearch" :clear-order-by="clearOrderByInput" @update:column-search="filterBuilderColumnSearch = $event" diff --git a/apps/desktop/src/components/grid/DataGridFilterBuilder.vue b/apps/desktop/src/components/grid/DataGridFilterBuilder.vue index 2a5c5fce9..6d9a7f922 100644 --- a/apps/desktop/src/components/grid/DataGridFilterBuilder.vue +++ b/apps/desktop/src/components/grid/DataGridFilterBuilder.vue @@ -9,16 +9,19 @@ import type { DataGridContextFilterMode } from "@/lib/dataGrid/dataGridSql"; import type { DataGridStructuredFilterRule } from "@/composables/useDataGridFilterBuilder"; const { t } = useI18n(); -const props = defineProps<{ - rules: DataGridStructuredFilterRule[]; - columns: string[]; - filteredColumns: string[]; - modeOptions: Array<{ value: DataGridContextFilterMode; labelKey: string }>; - columnSearch: string; - disabled?: boolean; - showHeader?: boolean; - showFooter?: boolean; -}>(); +const props = withDefaults( + defineProps<{ + rules: DataGridStructuredFilterRule[]; + columns: string[]; + filteredColumns: string[]; + modeOptions: Array<{ value: DataGridContextFilterMode; labelKey: string }>; + columnSearch: string; + disabled?: boolean; + showHeader?: boolean; + showFooter?: boolean; + }>(), + { showHeader: true, showFooter: true }, +); const emit = defineEmits<{ add: []; apply: []; diff --git a/apps/desktop/src/components/grid/DataGridQueryControls.vue b/apps/desktop/src/components/grid/DataGridQueryControls.vue index ae46f610e..66d469fa3 100644 --- a/apps/desktop/src/components/grid/DataGridQueryControls.vue +++ b/apps/desktop/src/components/grid/DataGridQueryControls.vue @@ -39,7 +39,6 @@ const props = defineProps<{ modeOptions: Array<{ value: DataGridContextFilterMode; labelKey: string }>; columnSearch: string; applyWhere: (value?: string) => void | boolean | Promise; - clearWhere: () => void | Promise; applyOrderBy: (value?: string) => void | boolean | Promise; clearOrderBy: () => void | Promise; }>(); @@ -108,6 +107,10 @@ function updateRule(id: string, patch: Partial) { emit("updateRule", id, patch); } +function clearWhere() { + emit("clearFilters"); +} + onUnmounted(onResizeEnd); @@ -162,7 +165,6 @@ onUnmounted(onResizeEnd); :column-search="columnSearch" :disabled="!canUseWhereSearch" :show-header="false" - :show-footer="false" @add="emit('addRule')" @apply="emit('applyFilters')" @reset="emit('resetFilters')" diff --git a/apps/desktop/src/components/grid/__tests__/DataGridSurfaces.spec.ts b/apps/desktop/src/components/grid/__tests__/DataGridSurfaces.spec.ts index 963fad13d..fb0bbc8ba 100644 --- a/apps/desktop/src/components/grid/__tests__/DataGridSurfaces.spec.ts +++ b/apps/desktop/src/components/grid/__tests__/DataGridSurfaces.spec.ts @@ -17,7 +17,7 @@ vi.mock("vue-i18n", () => ({ useI18n: () => ({ t: (key: string) => key }) })); vi.mock("@lucide/vue", async () => { const { createPassthroughStub } = await import("./vueHostHarness"); const icon = createPassthroughStub("Icon", "i"); - return { Check: icon, ChevronLeft: icon, ChevronRight: icon, ChevronsLeft: icon, ChevronsRight: icon, Loader2: icon, Upload: icon, Search: icon, X: icon, Code2: icon, Copy: icon, Eye: icon, EyeOff: icon, Info: icon, Pencil: icon, Plus: icon, Trash2: icon }; + return { Check: icon, ChevronDown: icon, ChevronLeft: icon, ChevronRight: icon, ChevronsLeft: icon, ChevronsRight: icon, Filter: icon, Loader2: icon, Upload: icon, Search: icon, X: icon, Code2: icon, Copy: icon, Eye: icon, EyeOff: icon, Info: icon, Pencil: icon, Plus: icon, Trash2: icon }; }); vi.mock("@/components/ui/button", async () => ({ Button: (await import("./vueHostHarness")).createPassthroughStub("Button", "button") })); @@ -65,6 +65,7 @@ import DataGridCellDetailPanel from "@/components/grid/DataGridCellDetailPanel.v import DataGridColumnHeader from "@/components/grid/DataGridColumnHeader.vue"; import DataGridFilterBuilder from "@/components/grid/DataGridFilterBuilder.vue"; import DataGridPagination from "@/components/grid/DataGridPagination.vue"; +import DataGridQueryControls from "@/components/grid/DataGridQueryControls.vue"; import DataGridSearchBar from "@/components/grid/DataGridSearchBar.vue"; function detail(patch: Partial = {}): DataGridCellDetail { @@ -287,6 +288,62 @@ describe("DataGridFilterBuilder", () => { }); }); +describe("DataGridQueryControls", () => { + it("keeps filter actions available in the popover", () => { + const clearFilters = vi.fn(); + const applyFilters = vi.fn(); + const resetFilters = vi.fn(); + const mounted = mountComponent(DataGridQueryControls, { + whereInput: "id = 1", + orderByInput: "", + columns: ["id"], + conditionColumns: ["id"], + historyScope: {}, + canUseWhereSearch: true, + compact: false, + leadingBorder: false, + filterBuilderOpen: true, + filterButtonActive: true, + filterButtonCount: 1, + hasLocalColumnFilters: false, + localFilterCount: 0, + localFilterSummaries: [], + rules: [{ id: "r1", columnName: "id", mode: "equals", rawValue: "1", rawEndValue: "", conjunction: "AND" }], + filteredColumns: ["id"], + modeOptions: [{ value: "equals", labelKey: "equals" }], + columnSearch: "", + applyWhere: vi.fn(), + applyOrderBy: vi.fn(), + clearOrderBy: vi.fn(), + onClearFilters: clearFilters, + onApplyFilters: applyFilters, + onResetFilters: resetFilters, + }); + + dispatch( + findOne(mounted.root, (node) => node.type === "button" && hostText(node) === "grid.clearFilter"), + "click", + ); + dispatch( + findOne(mounted.root, (node) => node.type === "button" && hostText(node) === "grid.resetFilterBuilder"), + "click", + ); + dispatch( + findOne(mounted.root, (node) => node.type === "button" && hostText(node) === "grid.applyFilter"), + "click", + ); + const whereInput = findOne(mounted.root, (node) => node.type === "textarea" && node.props.placeholder === "WHERE"); + const whereControl = whereInput.parent?.parent; + expect(whereControl).toBeTruthy(); + const whereButtons = findAll(whereControl!, (node) => node.type === "button"); + dispatch(whereButtons[whereButtons.length - 1], "click"); + + expect(clearFilters).toHaveBeenCalledTimes(2); + expect(resetFilters).toHaveBeenCalledOnce(); + expect(applyFilters).toHaveBeenCalledOnce(); + }); +}); + describe("cell detail surfaces", () => { it("copies the presented value, emits edit, closes, and replaces the JSON result", async () => { const copyText = vi.fn();