diff --git a/apps/desktop/src/components/sidebar/VisibleSchemasDialog.vue b/apps/desktop/src/components/sidebar/VisibleSchemasDialog.vue index b6efe71b56..840ebe4bb0 100644 --- a/apps/desktop/src/components/sidebar/VisibleSchemasDialog.vue +++ b/apps/desktop/src/components/sidebar/VisibleSchemasDialog.vue @@ -1,13 +1,15 @@ @@ -182,16 +193,16 @@ async function saveSelection() {
{{ t("visibleSchemas.selectedCount", { selected: selectedCount, total: totalCount }) }}
- - - -
@@ -200,7 +211,7 @@ async function saveSelection() { {{ t("visibleSchemas.emptySelection") }}

-
+
{{ t("common.loading") }} @@ -211,24 +222,18 @@ async function saveSelection() {
{{ t("grid.noSearchResults") }}
-
+ + + - - + diff --git a/apps/desktop/src/components/sidebar/__tests__/visibleSchemasDialogState.spec.ts b/apps/desktop/src/components/sidebar/__tests__/visibleSchemasDialogState.spec.ts new file mode 100644 index 0000000000..7f4fd9e62c --- /dev/null +++ b/apps/desktop/src/components/sidebar/__tests__/visibleSchemasDialogState.spec.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from "vitest"; +import { addVisibleSchemas, initialVisibleSchemaSelection, resolveVisibleSchemaSaveAction, selectVisibleSchemas } from "../visibleSchemasDialogState"; + +describe("visibleSchemasDialogState", () => { + const schemaNames = ["public", "sales", "audit"]; + + it("supports select all, clear, and filtered selection without mutating the previous set", () => { + const selected = selectVisibleSchemas(schemaNames); + const cleared = new Set(); + const filtered = addVisibleSchemas(cleared, ["sales", "audit"]); + + expect([...selected]).toEqual(schemaNames); + expect([...cleared]).toEqual([]); + expect([...filtered]).toEqual(["sales", "audit"]); + expect([...addVisibleSchemas(filtered, ["public", "sales"])]).toEqual(["sales", "audit", "public"]); + expect([...filtered]).toEqual(["sales", "audit"]); + }); + + it("keeps an explicitly empty normal-mode filter empty", () => { + expect([...initialVisibleSchemaSelection(schemaNames, undefined)]).toEqual(schemaNames); + expect([...initialVisibleSchemaSelection(schemaNames, [])]).toEqual([]); + }); + + it("keeps draft-mode empty or absent selections as all selected", () => { + expect([...initialVisibleSchemaSelection(schemaNames, undefined, true)]).toEqual(schemaNames); + expect([...initialVisibleSchemaSelection(schemaNames, [], true)]).toEqual(schemaNames); + }); + + it("normalizes configured schemas against the available list", () => { + expect([...initialVisibleSchemaSelection(schemaNames, ["missing", "audit", "audit"])]).toEqual(["audit"]); + }); + + it("skips persistence when an unchanged filter is saved", () => { + expect(resolveVisibleSchemaSaveAction(new Set(["audit", "public"]), schemaNames, ["public", "audit"])).toEqual({ type: "none" }); + }); + + it("stores changed partial selections in stable schema order", () => { + expect(resolveVisibleSchemaSaveAction(new Set(["audit", "public"]), schemaNames, ["sales"])).toEqual({ + type: "set", + schemaNames: ["public", "audit"], + }); + }); + + it("represents a full selection as show-all and avoids redundant tree refreshes", () => { + const selected = new Set(schemaNames); + expect(resolveVisibleSchemaSaveAction(selected, schemaNames, undefined)).toEqual({ type: "none" }); + expect(resolveVisibleSchemaSaveAction(selected, schemaNames, ["public"])).toEqual({ type: "clear" }); + }); +}); diff --git a/apps/desktop/src/components/sidebar/visibleSchemasDialogState.ts b/apps/desktop/src/components/sidebar/visibleSchemasDialogState.ts new file mode 100644 index 0000000000..9d52a540cc --- /dev/null +++ b/apps/desktop/src/components/sidebar/visibleSchemasDialogState.ts @@ -0,0 +1,44 @@ +import { normalizeVisibleSchemaSelection } from "@/lib/database/visibleDatabases"; + +export type VisibleSchemaSaveAction = { type: "none" } | { type: "clear" } | { type: "set"; schemaNames: string[] }; + +export function initialVisibleSchemaSelection(schemaNames: string[], configured: string[] | undefined, emptySelectionMeansAll = false): Set { + if (configured === undefined || (emptySelectionMeansAll && configured.length === 0)) return new Set(schemaNames); + return new Set(normalizeVisibleSchemaSelection(configured, schemaNames)); +} + +export function selectVisibleSchemas(schemaNames: string[]): Set { + return new Set(schemaNames); +} + +export function addVisibleSchemas(selection: Set, schemaNames: string[]): Set { + const next = new Set(selection); + for (const name of schemaNames) next.add(name); + return next; +} + +export function resolveVisibleSchemaSaveAction(selection: Set, schemaNames: string[], configured: string[] | undefined): VisibleSchemaSaveAction { + const normalized = normalizeSelectionInSchemaOrder(selection, schemaNames); + const availableCount = new Set(schemaNames).size; + + if (normalized.length === availableCount) { + return configured ? { type: "clear" } : { type: "none" }; + } + if (configured && sameSelection(normalized, normalizeVisibleSchemaSelection(configured, schemaNames))) { + return { type: "none" }; + } + return { type: "set", schemaNames: normalized }; +} + +function normalizeSelectionInSchemaOrder(selection: Set, schemaNames: string[]): string[] { + return normalizeVisibleSchemaSelection( + schemaNames.filter((name) => selection.has(name)), + schemaNames, + ); +} + +function sameSelection(left: string[], right: string[]): boolean { + if (left.length !== right.length) return false; + const rightSet = new Set(right); + return left.every((name) => rightSet.has(name)); +}