From 3201eb5953a8e8b6c5cf4d2b3b6e357ae6062f38 Mon Sep 17 00:00:00 2001 From: mikesealey Date: Thu, 11 Apr 2024 12:53:32 +0100 Subject: [PATCH 1/9] adds sidepanel open and close actions, and gives the user the option to disable click-outside closure of sidepanel --- packages/client/manifest.json | 22 +++++++++++++- .../client/src/components/app/Layout.svelte | 6 +++- .../src/components/app/SidePanel.svelte | 29 +++++++++++++++++++ packages/client/src/stores/sidePanel.js | 9 ++++++ 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 08d614391b..2f52085e38 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -6487,7 +6487,27 @@ "illegalChildren": ["section", "sidepanel"], "showEmptyState": false, "draggable": false, - "info": "Side panels are hidden by default. They will only be revealed when triggered by the 'Open Side Panel' action." + "info": "Side panels are hidden by default. They will only be revealed when triggered by the 'Open Side Panel' action.", + "sendEvents": true, + "settings": [ + { + "type": "boolean", + "key": "clickOutsideToClose", + "label": "Click away to close", + "defaultValue": true + }, + { + "type": "event", + "key": "sidePanelOpen", + "label": "Side Panel Open" + }, + { + "type": "event", + "key": "sidePanelClose", + "label": "Side Panel Close", + "info": "Side panel actions configured here will run after the 'Open side panel' action runs, and are not capable of preventing or stopping it. Any form validation should therefore be done before that action if invoked, and not here." + } + ] }, "rowexplorer": { "block": true, diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte index 992a166143..5b68171539 100644 --- a/packages/client/src/components/app/Layout.svelte +++ b/packages/client/src/components/app/Layout.svelte @@ -292,7 +292,11 @@ id="side-panel-container" class:open={$sidePanelStore.open} use:clickOutside={{ - callback: autoCloseSidePanel ? sidePanelStore.actions.close : null, + callback: + $sidePanelStore.clickOutsideToClose && autoCloseSidePanel + ? sidePanelStore.actions.close + : null, + allowedType: "mousedown", }} class:builder={$builderStore.inBuilder} diff --git a/packages/client/src/components/app/SidePanel.svelte b/packages/client/src/components/app/SidePanel.svelte index 825b401bb8..48c84828b0 100644 --- a/packages/client/src/components/app/SidePanel.svelte +++ b/packages/client/src/components/app/SidePanel.svelte @@ -5,6 +5,13 @@ const { styleable, sidePanelStore, builderStore, dndIsDragging } = getContext("sdk") + let handlingSidePanelOpen + let handlingSidePanelClose + + export let sidePanelOpen + export let sidePanelClose + export let clickOutsideToClose + // Automatically show and hide the side panel when inside the builder. // For some unknown reason, svelte reactivity breaks if we reference the // reactive variable "open" inside the following expression, or if we define @@ -26,6 +33,10 @@ } } + $: { + sidePanelStore.actions.setSidepanelState(clickOutsideToClose) + } + // Derive visibility $: open = $sidePanelStore.contentId === $component.id @@ -40,6 +51,22 @@ } } + const handleSidePanelOpen = async () => { + handlingSidePanelOpen = true + if (sidePanelOpen) { + await sidePanelOpen() + } + handlingSidePanelOpen = false + } + + const handleSidePanelClose = async () => { + handlingSidePanelClose = true + if (sidePanelClose) { + await sidePanelClose() + } + handlingSidePanelOpen = false + } + const showInSidePanel = (el, visible) => { const update = visible => { const target = document.getElementById("side-panel-container") @@ -47,10 +74,12 @@ if (visible) { if (!target.contains(node)) { target.appendChild(node) + handleSidePanelOpen() } } else { if (target.contains(node)) { target.removeChild(node) + handleSidePanelClose() } } } diff --git a/packages/client/src/stores/sidePanel.js b/packages/client/src/stores/sidePanel.js index 3b3b9f5f4d..df66eca01c 100644 --- a/packages/client/src/stores/sidePanel.js +++ b/packages/client/src/stores/sidePanel.js @@ -3,6 +3,7 @@ import { writable, derived } from "svelte/store" export const createSidePanelStore = () => { const initialState = { contentId: null, + clickOutsideToClose: true, } const store = writable(initialState) const derivedStore = derived(store, $store => { @@ -32,11 +33,19 @@ export const createSidePanelStore = () => { }, 50) } + const setSidepanelState = bool => { + clearTimeout(timeout) + store.update(state => { + state.clickOutsideToClose = bool + return state + }) + } return { subscribe: derivedStore.subscribe, actions: { open, close, + setSidepanelState, }, } } From a7ec49613c635f95dbf650217efdb515f6e92cfb Mon Sep 17 00:00:00 2001 From: mikesealey Date: Thu, 11 Apr 2024 15:42:19 +0100 Subject: [PATCH 2/9] fixes typo, removes unused variables --- packages/bbui/src/Layout/Page.svelte | 10 +++++----- packages/client/manifest.json | 2 +- packages/client/src/components/app/SidePanel.svelte | 7 ------- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/bbui/src/Layout/Page.svelte b/packages/bbui/src/Layout/Page.svelte index 2169a12459..62dd9cc909 100644 --- a/packages/bbui/src/Layout/Page.svelte +++ b/packages/bbui/src/Layout/Page.svelte @@ -7,11 +7,11 @@ export let narrower = false export let noPadding = false - let sidePanelVisble = false + let sidePanelVisible = false setContext("side-panel", { - open: () => (sidePanelVisble = true), - close: () => (sidePanelVisble = false), + open: () => (sidePanelVisible = true), + close: () => (sidePanelVisible = false), }) @@ -24,9 +24,9 @@
{ - sidePanelVisble = false + sidePanelVisible = false }} > diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 2f52085e38..f7d437a4fd 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -6505,7 +6505,7 @@ "type": "event", "key": "sidePanelClose", "label": "Side Panel Close", - "info": "Side panel actions configured here will run after the 'Open side panel' action runs, and are not capable of preventing or stopping it. Any form validation should therefore be done before that action if invoked, and not here." + "info": "Side panel actions configured here will run after the 'Open side panel' action runs, and are not capable of preventing or stopping it. Any form validation should therefore be done before that action is invoked, and not here." } ] }, diff --git a/packages/client/src/components/app/SidePanel.svelte b/packages/client/src/components/app/SidePanel.svelte index 48c84828b0..98398c4671 100644 --- a/packages/client/src/components/app/SidePanel.svelte +++ b/packages/client/src/components/app/SidePanel.svelte @@ -5,9 +5,6 @@ const { styleable, sidePanelStore, builderStore, dndIsDragging } = getContext("sdk") - let handlingSidePanelOpen - let handlingSidePanelClose - export let sidePanelOpen export let sidePanelClose export let clickOutsideToClose @@ -52,19 +49,15 @@ } const handleSidePanelOpen = async () => { - handlingSidePanelOpen = true if (sidePanelOpen) { await sidePanelOpen() } - handlingSidePanelOpen = false } const handleSidePanelClose = async () => { - handlingSidePanelClose = true if (sidePanelClose) { await sidePanelClose() } - handlingSidePanelOpen = false } const showInSidePanel = (el, visible) => { From 12fdaefe4c57fcc1ff057d67e22dd9ea9e00d80a Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 11 Apr 2024 17:12:15 +0100 Subject: [PATCH 3/9] Add tests for sorting to search.spec.ts --- .../src/api/routes/tests/search.spec.ts | 207 ++++++++++++++++-- .../server/src/sdk/app/rows/search/sqs.ts | 2 +- 2 files changed, 184 insertions(+), 25 deletions(-) diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index fdf1ed7603..028b970a42 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -8,6 +8,8 @@ import { FieldType, RowSearchParams, SearchFilters, + SortOrder, + SortType, Table, TableSchema, } from "@budibase/types" @@ -62,7 +64,25 @@ describe.each([ class SearchAssertion { constructor(private readonly query: RowSearchParams) {} - async toFind(expectedRows: any[]) { + async toMatch(expectedRows: any[]) { + const { rows: foundRows } = await config.api.row.search(table._id!, { + ...this.query, + tableId: table._id!, + }) + + // eslint-disable-next-line jest/no-standalone-expect + expect(foundRows).toHaveLength(expectedRows.length) + // eslint-disable-next-line jest/no-standalone-expect + expect(foundRows).toEqual( + expectedRows.map((expectedRow: any) => + expect.objectContaining( + foundRows.find(foundRow => _.isMatch(foundRow, expectedRow)) + ) + ) + ) + } + + async toContain(expectedRows: any[]) { const { rows: foundRows } = await config.api.row.search(table._id!, { ...this.query, tableId: table._id!, @@ -83,7 +103,17 @@ describe.each([ } async toFindNothing() { - await this.toFind([]) + await this.toContain([]) + } + + async toHaveLength(length: number) { + const { rows: foundRows } = await config.api.row.search(table._id!, { + ...this.query, + tableId: table._id!, + }) + + // eslint-disable-next-line jest/no-standalone-expect + expect(foundRows).toHaveLength(length) } } @@ -105,28 +135,31 @@ describe.each([ describe("misc", () => { it("should return all if no query is passed", () => - expectSearch({} as RowSearchParams).toFind([ + expectSearch({} as RowSearchParams).toContain([ { name: "foo" }, { name: "bar" }, ])) it("should return all if empty query is passed", () => - expectQuery({}).toFind([{ name: "foo" }, { name: "bar" }])) + expectQuery({}).toContain([{ name: "foo" }, { name: "bar" }])) it("should return all if onEmptyFilter is RETURN_ALL", () => expectQuery({ onEmptyFilter: EmptyFilterOption.RETURN_ALL, - }).toFind([{ name: "foo" }, { name: "bar" }])) + }).toContain([{ name: "foo" }, { name: "bar" }])) it("should return nothing if onEmptyFilter is RETURN_NONE", () => expectQuery({ onEmptyFilter: EmptyFilterOption.RETURN_NONE, }).toFindNothing()) + + it("should respect limit", () => + expectSearch({ limit: 1, paginate: true, query: {} }).toHaveLength(1)) }) describe("equal", () => { it("successfully finds a row", () => - expectQuery({ equal: { name: "foo" } }).toFind([{ name: "foo" }])) + expectQuery({ equal: { name: "foo" } }).toContain([{ name: "foo" }])) it("fails to find nonexistent row", () => expectQuery({ equal: { name: "none" } }).toFindNothing()) @@ -134,15 +167,15 @@ describe.each([ describe("notEqual", () => { it("successfully finds a row", () => - expectQuery({ notEqual: { name: "foo" } }).toFind([{ name: "bar" }])) + expectQuery({ notEqual: { name: "foo" } }).toContain([{ name: "bar" }])) it("fails to find nonexistent row", () => - expectQuery({ notEqual: { name: "bar" } }).toFind([{ name: "foo" }])) + expectQuery({ notEqual: { name: "bar" } }).toContain([{ name: "foo" }])) }) describe("oneOf", () => { it("successfully finds a row", () => - expectQuery({ oneOf: { name: ["foo"] } }).toFind([{ name: "foo" }])) + expectQuery({ oneOf: { name: ["foo"] } }).toContain([{ name: "foo" }])) it("fails to find nonexistent row", () => expectQuery({ oneOf: { name: ["none"] } }).toFindNothing()) @@ -150,11 +183,45 @@ describe.each([ describe("fuzzy", () => { it("successfully finds a row", () => - expectQuery({ fuzzy: { name: "oo" } }).toFind([{ name: "foo" }])) + expectQuery({ fuzzy: { name: "oo" } }).toContain([{ name: "foo" }])) it("fails to find nonexistent row", () => expectQuery({ fuzzy: { name: "none" } }).toFindNothing()) }) + + describe("sort", () => { + it("sorts ascending", () => + expectSearch({ + query: {}, + sort: "name", + sortOrder: SortOrder.ASCENDING, + }).toMatch([{ name: "bar" }, { name: "foo" }])) + + it("sorts descending", () => + expectSearch({ + query: {}, + sort: "name", + sortOrder: SortOrder.DESCENDING, + }).toMatch([{ name: "foo" }, { name: "bar" }])) + + describe("sortType STRING", () => { + it("sorts ascending", () => + expectSearch({ + query: {}, + sort: "name", + sortType: SortType.STRING, + sortOrder: SortOrder.ASCENDING, + }).toMatch([{ name: "bar" }, { name: "foo" }])) + + it("sorts descending", () => + expectSearch({ + query: {}, + sort: "name", + sortType: SortType.STRING, + sortOrder: SortOrder.DESCENDING, + }).toMatch([{ name: "foo" }, { name: "bar" }])) + }) + }) }) describe("numbers", () => { @@ -167,7 +234,7 @@ describe.each([ describe("equal", () => { it("successfully finds a row", () => - expectQuery({ equal: { age: 1 } }).toFind([{ age: 1 }])) + expectQuery({ equal: { age: 1 } }).toContain([{ age: 1 }])) it("fails to find nonexistent row", () => expectQuery({ equal: { age: 2 } }).toFindNothing()) @@ -175,15 +242,15 @@ describe.each([ describe("notEqual", () => { it("successfully finds a row", () => - expectQuery({ notEqual: { age: 1 } }).toFind([{ age: 10 }])) + expectQuery({ notEqual: { age: 1 } }).toContain([{ age: 10 }])) it("fails to find nonexistent row", () => - expectQuery({ notEqual: { age: 10 } }).toFind([{ age: 1 }])) + expectQuery({ notEqual: { age: 10 } }).toContain([{ age: 1 }])) }) describe("oneOf", () => { it("successfully finds a row", () => - expectQuery({ oneOf: { age: [1] } }).toFind([{ age: 1 }])) + expectQuery({ oneOf: { age: [1] } }).toContain([{ age: 1 }])) it("fails to find nonexistent row", () => expectQuery({ oneOf: { age: [2] } }).toFindNothing()) @@ -193,17 +260,69 @@ describe.each([ it("successfully finds a row", () => expectQuery({ range: { age: { low: 1, high: 5 } }, - }).toFind([{ age: 1 }])) + }).toContain([{ age: 1 }])) it("successfully finds multiple rows", () => expectQuery({ range: { age: { low: 1, high: 10 } }, - }).toFind([{ age: 1 }, { age: 10 }])) + }).toContain([{ age: 1 }, { age: 10 }])) it("successfully finds a row with a high bound", () => expectQuery({ range: { age: { low: 5, high: 10 } }, - }).toFind([{ age: 10 }])) + }).toContain([{ age: 10 }])) + + describe("sort", () => { + it("sorts ascending", () => + expectSearch({ + query: {}, + sort: "age", + sortOrder: SortOrder.ASCENDING, + }).toMatch([{ age: 1 }, { age: 10 }])) + + it("sorts descending", () => + expectSearch({ + query: {}, + sort: "age", + sortOrder: SortOrder.DESCENDING, + }).toMatch([{ age: 10 }, { age: 1 }])) + }) + + describe("sortType NUMBER", () => { + it("sorts ascending", () => + expectSearch({ + query: {}, + sort: "age", + sortType: SortType.NUMBER, + sortOrder: SortOrder.ASCENDING, + }).toMatch([{ age: 1 }, { age: 10 }])) + + it("sorts descending", () => + expectSearch({ + query: {}, + sort: "age", + sortType: SortType.NUMBER, + sortOrder: SortOrder.DESCENDING, + }).toMatch([{ age: 10 }, { age: 1 }])) + }) + + describe("sortType STRING", () => { + it("sorts ascending", () => + expectSearch({ + query: {}, + sort: "age", + sortType: SortType.STRING, + sortOrder: SortOrder.ASCENDING, + }).toMatch([{ age: 1 }, { age: 10 }])) + + it("sorts descending", () => + expectSearch({ + query: {}, + sort: "age", + sortType: SortType.STRING, + sortOrder: SortOrder.DESCENDING, + }).toMatch([{ age: 10 }, { age: 1 }])) + }) }) }) @@ -223,7 +342,7 @@ describe.each([ describe("equal", () => { it("successfully finds a row", () => - expectQuery({ equal: { dob: JAN_1ST } }).toFind([{ dob: JAN_1ST }])) + expectQuery({ equal: { dob: JAN_1ST } }).toContain([{ dob: JAN_1ST }])) it("fails to find nonexistent row", () => expectQuery({ equal: { dob: JAN_2ND } }).toFindNothing()) @@ -231,15 +350,21 @@ describe.each([ describe("notEqual", () => { it("successfully finds a row", () => - expectQuery({ notEqual: { dob: JAN_1ST } }).toFind([{ dob: JAN_10TH }])) + expectQuery({ notEqual: { dob: JAN_1ST } }).toContain([ + { dob: JAN_10TH }, + ])) it("fails to find nonexistent row", () => - expectQuery({ notEqual: { dob: JAN_10TH } }).toFind([{ dob: JAN_1ST }])) + expectQuery({ notEqual: { dob: JAN_10TH } }).toContain([ + { dob: JAN_1ST }, + ])) }) describe("oneOf", () => { it("successfully finds a row", () => - expectQuery({ oneOf: { dob: [JAN_1ST] } }).toFind([{ dob: JAN_1ST }])) + expectQuery({ oneOf: { dob: [JAN_1ST] } }).toContain([ + { dob: JAN_1ST }, + ])) it("fails to find nonexistent row", () => expectQuery({ oneOf: { dob: [JAN_2ND] } }).toFindNothing()) @@ -249,17 +374,51 @@ describe.each([ it("successfully finds a row", () => expectQuery({ range: { dob: { low: JAN_1ST, high: JAN_5TH } }, - }).toFind([{ dob: JAN_1ST }])) + }).toContain([{ dob: JAN_1ST }])) it("successfully finds multiple rows", () => expectQuery({ range: { dob: { low: JAN_1ST, high: JAN_10TH } }, - }).toFind([{ dob: JAN_1ST }, { dob: JAN_10TH }])) + }).toContain([{ dob: JAN_1ST }, { dob: JAN_10TH }])) it("successfully finds a row with a high bound", () => expectQuery({ range: { dob: { low: JAN_5TH, high: JAN_10TH } }, - }).toFind([{ dob: JAN_10TH }])) + }).toContain([{ dob: JAN_10TH }])) + }) + + describe("sort", () => { + it("sorts ascending", () => + expectSearch({ + query: {}, + sort: "dob", + sortOrder: SortOrder.ASCENDING, + }).toMatch([{ dob: JAN_1ST }, { dob: JAN_10TH }])) + + it("sorts descending", () => + expectSearch({ + query: {}, + sort: "dob", + sortOrder: SortOrder.DESCENDING, + }).toMatch([{ dob: JAN_10TH }, { dob: JAN_1ST }])) + + describe("sortType STRING", () => { + it("sorts ascending", () => + expectSearch({ + query: {}, + sort: "dob", + sortType: SortType.STRING, + sortOrder: SortOrder.ASCENDING, + }).toMatch([{ dob: JAN_1ST }, { dob: JAN_10TH }])) + + it("sorts descending", () => + expectSearch({ + query: {}, + sort: "dob", + sortType: SortType.STRING, + sortOrder: SortOrder.DESCENDING, + }).toMatch([{ dob: JAN_10TH }, { dob: JAN_1ST }])) + }) }) }) }) diff --git a/packages/server/src/sdk/app/rows/search/sqs.ts b/packages/server/src/sdk/app/rows/search/sqs.ts index 5b0b6e3bc7..7abd7d9e72 100644 --- a/packages/server/src/sdk/app/rows/search/sqs.ts +++ b/packages/server/src/sdk/app/rows/search/sqs.ts @@ -132,7 +132,7 @@ export async function search( type: "row", } - if (params.sort && !params.sortType) { + if (params.sort) { const sortField = table.schema[params.sort] const sortType = sortField.type === FieldType.NUMBER ? SortType.NUMBER : SortType.STRING From a024a28de1e3430b42b6a9cb2a03f76d38c303a2 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 11 Apr 2024 17:16:32 +0100 Subject: [PATCH 4/9] Fix tests. --- .../src/api/routes/tests/search.spec.ts | 78 +++++++------------ 1 file changed, 30 insertions(+), 48 deletions(-) diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index 028b970a42..4db47be216 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -271,58 +271,40 @@ describe.each([ expectQuery({ range: { age: { low: 5, high: 10 } }, }).toContain([{ age: 10 }])) + }) - describe("sort", () => { - it("sorts ascending", () => - expectSearch({ - query: {}, - sort: "age", - sortOrder: SortOrder.ASCENDING, - }).toMatch([{ age: 1 }, { age: 10 }])) + describe("sort", () => { + it("sorts ascending", () => + expectSearch({ + query: {}, + sort: "age", + sortOrder: SortOrder.ASCENDING, + }).toMatch([{ age: 1 }, { age: 10 }])) - it("sorts descending", () => - expectSearch({ - query: {}, - sort: "age", - sortOrder: SortOrder.DESCENDING, - }).toMatch([{ age: 10 }, { age: 1 }])) - }) + it("sorts descending", () => + expectSearch({ + query: {}, + sort: "age", + sortOrder: SortOrder.DESCENDING, + }).toMatch([{ age: 10 }, { age: 1 }])) + }) - describe("sortType NUMBER", () => { - it("sorts ascending", () => - expectSearch({ - query: {}, - sort: "age", - sortType: SortType.NUMBER, - sortOrder: SortOrder.ASCENDING, - }).toMatch([{ age: 1 }, { age: 10 }])) + describe("sortType NUMBER", () => { + it("sorts ascending", () => + expectSearch({ + query: {}, + sort: "age", + sortType: SortType.NUMBER, + sortOrder: SortOrder.ASCENDING, + }).toMatch([{ age: 1 }, { age: 10 }])) - it("sorts descending", () => - expectSearch({ - query: {}, - sort: "age", - sortType: SortType.NUMBER, - sortOrder: SortOrder.DESCENDING, - }).toMatch([{ age: 10 }, { age: 1 }])) - }) - - describe("sortType STRING", () => { - it("sorts ascending", () => - expectSearch({ - query: {}, - sort: "age", - sortType: SortType.STRING, - sortOrder: SortOrder.ASCENDING, - }).toMatch([{ age: 1 }, { age: 10 }])) - - it("sorts descending", () => - expectSearch({ - query: {}, - sort: "age", - sortType: SortType.STRING, - sortOrder: SortOrder.DESCENDING, - }).toMatch([{ age: 10 }, { age: 1 }])) - }) + it("sorts descending", () => + expectSearch({ + query: {}, + sort: "age", + sortType: SortType.NUMBER, + sortOrder: SortOrder.DESCENDING, + }).toMatch([{ age: 10 }, { age: 1 }])) }) }) From 859bda0a5128d74e4ddddd239129bfa7717af97b Mon Sep 17 00:00:00 2001 From: mikesealey Date: Fri, 12 Apr 2024 09:24:12 +0100 Subject: [PATCH 5/9] removes unecessary side-panel-open actions --- packages/client/manifest.json | 5 ----- packages/client/src/components/app/SidePanel.svelte | 2 -- 2 files changed, 7 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index bdcbdecb7c..622381847c 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -6732,11 +6732,6 @@ "label": "Click away to close", "defaultValue": true }, - { - "type": "event", - "key": "sidePanelOpen", - "label": "Side Panel Open" - }, { "type": "event", "key": "sidePanelClose", diff --git a/packages/client/src/components/app/SidePanel.svelte b/packages/client/src/components/app/SidePanel.svelte index 98398c4671..8e0dcd99e4 100644 --- a/packages/client/src/components/app/SidePanel.svelte +++ b/packages/client/src/components/app/SidePanel.svelte @@ -5,7 +5,6 @@ const { styleable, sidePanelStore, builderStore, dndIsDragging } = getContext("sdk") - export let sidePanelOpen export let sidePanelClose export let clickOutsideToClose @@ -67,7 +66,6 @@ if (visible) { if (!target.contains(node)) { target.appendChild(node) - handleSidePanelOpen() } } else { if (target.contains(node)) { From 0514641f049285e6139b59a54744d1e79d103105 Mon Sep 17 00:00:00 2001 From: mikesealey Date: Fri, 12 Apr 2024 13:24:21 +0100 Subject: [PATCH 6/9] removes unecessary on-sidepanel-open actions feature. --- packages/client/manifest.json | 2 +- packages/client/src/components/app/Layout.svelte | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 622381847c..f370b67670 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -6729,7 +6729,7 @@ { "type": "boolean", "key": "clickOutsideToClose", - "label": "Click away to close", + "label": "Click outside to close", "defaultValue": true }, { diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte index acef8001a8..bae2bd0faf 100644 --- a/packages/client/src/components/app/Layout.svelte +++ b/packages/client/src/components/app/Layout.svelte @@ -73,7 +73,10 @@ $context.device.width, $context.device.height ) - $: autoCloseSidePanel = !$builderStore.inBuilder && $sidePanelStore.open + $: autoCloseSidePanel = + !$builderStore.inBuilder && + $sidePanelStore.open && + $sidePanelStore.clickOutsideToClose $: screenId = $builderStore.inBuilder ? `${$builderStore.screen?._id}-screen` : "screen" @@ -317,11 +320,7 @@ id="side-panel-container" class:open={$sidePanelStore.open} use:clickOutside={{ - callback: - $sidePanelStore.clickOutsideToClose && autoCloseSidePanel - ? sidePanelStore.actions.close - : null, - + callback: autoCloseSidePanel ? sidePanelStore.actions.close : null, allowedType: "mousedown", }} class:builder={$builderStore.inBuilder} From 1632c9d7a84f74e94fa07a0615517e49fd3cc992 Mon Sep 17 00:00:00 2001 From: mikesealey Date: Fri, 12 Apr 2024 14:33:46 +0100 Subject: [PATCH 7/9] removes unused function --- packages/client/src/components/app/SidePanel.svelte | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/client/src/components/app/SidePanel.svelte b/packages/client/src/components/app/SidePanel.svelte index 8e0dcd99e4..624617ad69 100644 --- a/packages/client/src/components/app/SidePanel.svelte +++ b/packages/client/src/components/app/SidePanel.svelte @@ -47,12 +47,6 @@ } } - const handleSidePanelOpen = async () => { - if (sidePanelOpen) { - await sidePanelOpen() - } - } - const handleSidePanelClose = async () => { if (sidePanelClose) { await sidePanelClose() From 565ee5f7dac0edee614ba841eeef49adf152dfe3 Mon Sep 17 00:00:00 2001 From: mikesealey Date: Fri, 12 Apr 2024 15:23:24 +0100 Subject: [PATCH 8/9] brings key and label into line with standard practices. Removes unecessary info. --- packages/client/manifest.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index f370b67670..c9e28e202b 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -6734,9 +6734,8 @@ }, { "type": "event", - "key": "sidePanelClose", - "label": "Side Panel Close", - "info": "Side panel actions configured here will run after the 'Open side panel' action runs, and are not capable of preventing or stopping it. Any form validation should therefore be done before that action is invoked, and not here." + "key": "onSidePanelClose", + "label": "On side panel close" } ] }, From fbff5c0a316c0acf732fe97b61ee8f6c8edbdf6e Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 12 Apr 2024 16:44:34 +0100 Subject: [PATCH 9/9] Rename toContains to toContainsExactly to better reflect what it does. --- .../src/api/routes/tests/search.spec.ts | 114 ++++++++++++------ 1 file changed, 77 insertions(+), 37 deletions(-) diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index 4db47be216..a473cb77b4 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -64,7 +64,11 @@ describe.each([ class SearchAssertion { constructor(private readonly query: RowSearchParams) {} - async toMatch(expectedRows: any[]) { + // Asserts that the query returns rows matching exactly the set of rows + // passed in. The order of the rows matters. Rows returned in an order + // different to the one passed in will cause the assertion to fail. Extra + // rows returned by the query will also cause the assertion to fail. + async toMatchExactly(expectedRows: any[]) { const { rows: foundRows } = await config.api.row.search(table._id!, { ...this.query, tableId: table._id!, @@ -82,7 +86,10 @@ describe.each([ ) } - async toContain(expectedRows: any[]) { + // Asserts that the query returns rows matching exactly the set of rows + // passed in. The order of the rows is not important, but extra rows will + // cause the assertion to fail. + async toContainExactly(expectedRows: any[]) { const { rows: foundRows } = await config.api.row.search(table._id!, { ...this.query, tableId: table._id!, @@ -102,8 +109,29 @@ describe.each([ ) } + // Asserts that the query returns rows matching the set of rows passed in. + // The order of the rows is not important. Extra rows will not cause the + // assertion to fail. + async toContain(expectedRows: any[]) { + const { rows: foundRows } = await config.api.row.search(table._id!, { + ...this.query, + tableId: table._id!, + }) + + // eslint-disable-next-line jest/no-standalone-expect + expect(foundRows).toEqual( + expect.arrayContaining( + expectedRows.map((expectedRow: any) => + expect.objectContaining( + foundRows.find(foundRow => _.isMatch(foundRow, expectedRow)) + ) + ) + ) + ) + } + async toFindNothing() { - await this.toContain([]) + await this.toContainExactly([]) } async toHaveLength(length: number) { @@ -135,18 +163,18 @@ describe.each([ describe("misc", () => { it("should return all if no query is passed", () => - expectSearch({} as RowSearchParams).toContain([ + expectSearch({} as RowSearchParams).toContainExactly([ { name: "foo" }, { name: "bar" }, ])) it("should return all if empty query is passed", () => - expectQuery({}).toContain([{ name: "foo" }, { name: "bar" }])) + expectQuery({}).toContainExactly([{ name: "foo" }, { name: "bar" }])) it("should return all if onEmptyFilter is RETURN_ALL", () => expectQuery({ onEmptyFilter: EmptyFilterOption.RETURN_ALL, - }).toContain([{ name: "foo" }, { name: "bar" }])) + }).toContainExactly([{ name: "foo" }, { name: "bar" }])) it("should return nothing if onEmptyFilter is RETURN_NONE", () => expectQuery({ @@ -159,7 +187,9 @@ describe.each([ describe("equal", () => { it("successfully finds a row", () => - expectQuery({ equal: { name: "foo" } }).toContain([{ name: "foo" }])) + expectQuery({ equal: { name: "foo" } }).toContainExactly([ + { name: "foo" }, + ])) it("fails to find nonexistent row", () => expectQuery({ equal: { name: "none" } }).toFindNothing()) @@ -167,15 +197,21 @@ describe.each([ describe("notEqual", () => { it("successfully finds a row", () => - expectQuery({ notEqual: { name: "foo" } }).toContain([{ name: "bar" }])) + expectQuery({ notEqual: { name: "foo" } }).toContainExactly([ + { name: "bar" }, + ])) it("fails to find nonexistent row", () => - expectQuery({ notEqual: { name: "bar" } }).toContain([{ name: "foo" }])) + expectQuery({ notEqual: { name: "bar" } }).toContainExactly([ + { name: "foo" }, + ])) }) describe("oneOf", () => { it("successfully finds a row", () => - expectQuery({ oneOf: { name: ["foo"] } }).toContain([{ name: "foo" }])) + expectQuery({ oneOf: { name: ["foo"] } }).toContainExactly([ + { name: "foo" }, + ])) it("fails to find nonexistent row", () => expectQuery({ oneOf: { name: ["none"] } }).toFindNothing()) @@ -183,7 +219,9 @@ describe.each([ describe("fuzzy", () => { it("successfully finds a row", () => - expectQuery({ fuzzy: { name: "oo" } }).toContain([{ name: "foo" }])) + expectQuery({ fuzzy: { name: "oo" } }).toContainExactly([ + { name: "foo" }, + ])) it("fails to find nonexistent row", () => expectQuery({ fuzzy: { name: "none" } }).toFindNothing()) @@ -195,14 +233,14 @@ describe.each([ query: {}, sort: "name", sortOrder: SortOrder.ASCENDING, - }).toMatch([{ name: "bar" }, { name: "foo" }])) + }).toMatchExactly([{ name: "bar" }, { name: "foo" }])) it("sorts descending", () => expectSearch({ query: {}, sort: "name", sortOrder: SortOrder.DESCENDING, - }).toMatch([{ name: "foo" }, { name: "bar" }])) + }).toMatchExactly([{ name: "foo" }, { name: "bar" }])) describe("sortType STRING", () => { it("sorts ascending", () => @@ -211,7 +249,7 @@ describe.each([ sort: "name", sortType: SortType.STRING, sortOrder: SortOrder.ASCENDING, - }).toMatch([{ name: "bar" }, { name: "foo" }])) + }).toMatchExactly([{ name: "bar" }, { name: "foo" }])) it("sorts descending", () => expectSearch({ @@ -219,7 +257,7 @@ describe.each([ sort: "name", sortType: SortType.STRING, sortOrder: SortOrder.DESCENDING, - }).toMatch([{ name: "foo" }, { name: "bar" }])) + }).toMatchExactly([{ name: "foo" }, { name: "bar" }])) }) }) }) @@ -234,7 +272,7 @@ describe.each([ describe("equal", () => { it("successfully finds a row", () => - expectQuery({ equal: { age: 1 } }).toContain([{ age: 1 }])) + expectQuery({ equal: { age: 1 } }).toContainExactly([{ age: 1 }])) it("fails to find nonexistent row", () => expectQuery({ equal: { age: 2 } }).toFindNothing()) @@ -242,15 +280,15 @@ describe.each([ describe("notEqual", () => { it("successfully finds a row", () => - expectQuery({ notEqual: { age: 1 } }).toContain([{ age: 10 }])) + expectQuery({ notEqual: { age: 1 } }).toContainExactly([{ age: 10 }])) it("fails to find nonexistent row", () => - expectQuery({ notEqual: { age: 10 } }).toContain([{ age: 1 }])) + expectQuery({ notEqual: { age: 10 } }).toContainExactly([{ age: 1 }])) }) describe("oneOf", () => { it("successfully finds a row", () => - expectQuery({ oneOf: { age: [1] } }).toContain([{ age: 1 }])) + expectQuery({ oneOf: { age: [1] } }).toContainExactly([{ age: 1 }])) it("fails to find nonexistent row", () => expectQuery({ oneOf: { age: [2] } }).toFindNothing()) @@ -260,17 +298,17 @@ describe.each([ it("successfully finds a row", () => expectQuery({ range: { age: { low: 1, high: 5 } }, - }).toContain([{ age: 1 }])) + }).toContainExactly([{ age: 1 }])) it("successfully finds multiple rows", () => expectQuery({ range: { age: { low: 1, high: 10 } }, - }).toContain([{ age: 1 }, { age: 10 }])) + }).toContainExactly([{ age: 1 }, { age: 10 }])) it("successfully finds a row with a high bound", () => expectQuery({ range: { age: { low: 5, high: 10 } }, - }).toContain([{ age: 10 }])) + }).toContainExactly([{ age: 10 }])) }) describe("sort", () => { @@ -279,14 +317,14 @@ describe.each([ query: {}, sort: "age", sortOrder: SortOrder.ASCENDING, - }).toMatch([{ age: 1 }, { age: 10 }])) + }).toMatchExactly([{ age: 1 }, { age: 10 }])) it("sorts descending", () => expectSearch({ query: {}, sort: "age", sortOrder: SortOrder.DESCENDING, - }).toMatch([{ age: 10 }, { age: 1 }])) + }).toMatchExactly([{ age: 10 }, { age: 1 }])) }) describe("sortType NUMBER", () => { @@ -296,7 +334,7 @@ describe.each([ sort: "age", sortType: SortType.NUMBER, sortOrder: SortOrder.ASCENDING, - }).toMatch([{ age: 1 }, { age: 10 }])) + }).toMatchExactly([{ age: 1 }, { age: 10 }])) it("sorts descending", () => expectSearch({ @@ -304,7 +342,7 @@ describe.each([ sort: "age", sortType: SortType.NUMBER, sortOrder: SortOrder.DESCENDING, - }).toMatch([{ age: 10 }, { age: 1 }])) + }).toMatchExactly([{ age: 10 }, { age: 1 }])) }) }) @@ -324,7 +362,9 @@ describe.each([ describe("equal", () => { it("successfully finds a row", () => - expectQuery({ equal: { dob: JAN_1ST } }).toContain([{ dob: JAN_1ST }])) + expectQuery({ equal: { dob: JAN_1ST } }).toContainExactly([ + { dob: JAN_1ST }, + ])) it("fails to find nonexistent row", () => expectQuery({ equal: { dob: JAN_2ND } }).toFindNothing()) @@ -332,19 +372,19 @@ describe.each([ describe("notEqual", () => { it("successfully finds a row", () => - expectQuery({ notEqual: { dob: JAN_1ST } }).toContain([ + expectQuery({ notEqual: { dob: JAN_1ST } }).toContainExactly([ { dob: JAN_10TH }, ])) it("fails to find nonexistent row", () => - expectQuery({ notEqual: { dob: JAN_10TH } }).toContain([ + expectQuery({ notEqual: { dob: JAN_10TH } }).toContainExactly([ { dob: JAN_1ST }, ])) }) describe("oneOf", () => { it("successfully finds a row", () => - expectQuery({ oneOf: { dob: [JAN_1ST] } }).toContain([ + expectQuery({ oneOf: { dob: [JAN_1ST] } }).toContainExactly([ { dob: JAN_1ST }, ])) @@ -356,17 +396,17 @@ describe.each([ it("successfully finds a row", () => expectQuery({ range: { dob: { low: JAN_1ST, high: JAN_5TH } }, - }).toContain([{ dob: JAN_1ST }])) + }).toContainExactly([{ dob: JAN_1ST }])) it("successfully finds multiple rows", () => expectQuery({ range: { dob: { low: JAN_1ST, high: JAN_10TH } }, - }).toContain([{ dob: JAN_1ST }, { dob: JAN_10TH }])) + }).toContainExactly([{ dob: JAN_1ST }, { dob: JAN_10TH }])) it("successfully finds a row with a high bound", () => expectQuery({ range: { dob: { low: JAN_5TH, high: JAN_10TH } }, - }).toContain([{ dob: JAN_10TH }])) + }).toContainExactly([{ dob: JAN_10TH }])) }) describe("sort", () => { @@ -375,14 +415,14 @@ describe.each([ query: {}, sort: "dob", sortOrder: SortOrder.ASCENDING, - }).toMatch([{ dob: JAN_1ST }, { dob: JAN_10TH }])) + }).toMatchExactly([{ dob: JAN_1ST }, { dob: JAN_10TH }])) it("sorts descending", () => expectSearch({ query: {}, sort: "dob", sortOrder: SortOrder.DESCENDING, - }).toMatch([{ dob: JAN_10TH }, { dob: JAN_1ST }])) + }).toMatchExactly([{ dob: JAN_10TH }, { dob: JAN_1ST }])) describe("sortType STRING", () => { it("sorts ascending", () => @@ -391,7 +431,7 @@ describe.each([ sort: "dob", sortType: SortType.STRING, sortOrder: SortOrder.ASCENDING, - }).toMatch([{ dob: JAN_1ST }, { dob: JAN_10TH }])) + }).toMatchExactly([{ dob: JAN_1ST }, { dob: JAN_10TH }])) it("sorts descending", () => expectSearch({ @@ -399,7 +439,7 @@ describe.each([ sort: "dob", sortType: SortType.STRING, sortOrder: SortOrder.DESCENDING, - }).toMatch([{ dob: JAN_10TH }, { dob: JAN_1ST }])) + }).toMatchExactly([{ dob: JAN_10TH }, { dob: JAN_1ST }])) }) }) })