diff --git a/packages/builder/src/builder/dataBinding.js b/packages/builder/src/builder/dataBinding.js index a2b1f65e3e..b33f288085 100644 --- a/packages/builder/src/builder/dataBinding.js +++ b/packages/builder/src/builder/dataBinding.js @@ -4,7 +4,6 @@ import { findAllMatchingComponents, findComponent, findComponentPath, - getComponentSettings, } from "stores/builder/components/utils" import { currentAsset, @@ -281,7 +280,7 @@ export const getActionProviders = ( * Gets a datasource object for a certain data provider component */ export const getDatasourceForProvider = (asset, component) => { - const settings = getComponentSettings(component?._component) + const settings = componentStore.getComponentSettings(component?._component) // If this component has a dataProvider setting, go up the stack and use it const dataProviderSetting = settings.find(setting => { @@ -706,7 +705,7 @@ export const getEventContextBindings = ({ const definition = componentDefinition ?? componentStore.getDefinition(component?._component) - const settings = getComponentSettings(component?._component) + const settings = componentStore.getComponentSettings(component?._component) const eventSetting = settings.find(setting => setting.key === settingKey) if (eventSetting?.context?.length) { @@ -1011,7 +1010,7 @@ export const buildFormSchema = (component, asset) => { } // Otherwise find all field component children - const settings = getComponentSettings(component._component) + const settings = componentStore.getComponentSettings(component._component) const fieldSetting = settings.find( setting => setting.key === "field" && setting.type.startsWith("field/") ) @@ -1037,7 +1036,7 @@ export const getAllStateVariables = () => { let eventSettings = [] getAllAssets().forEach(asset => { findAllMatchingComponents(asset.props, component => { - const settings = getComponentSettings(component._component) + const settings = componentStore.getComponentSettings(component._component) settings .filter(setting => setting.type === "event") .forEach(setting => { diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ConditionalUIDrawer.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ConditionalUIDrawer.svelte index 3dc80151ed..e4cc4a4f99 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ConditionalUIDrawer.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ConditionalUIDrawer.svelte @@ -13,10 +13,9 @@ import { generate } from "shortid" import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import { LuceneUtils, Constants } from "@budibase/frontend-core" - import { selectedComponent } from "stores/builder" + import { selectedComponent, componentStore } from "stores/builder" import { getComponentForSetting } from "components/design/settings/componentSettings" import PropertyControl from "components/design/settings/controls/PropertyControl.svelte" - import { getComponentSettings } from "stores/builder/components/utils" export let conditions = [] export let bindings = [] @@ -56,11 +55,13 @@ ] let dragDisabled = true - $: settings = getComponentSettings($selectedComponent?._component)?.concat({ - label: "Custom CSS", - key: "_css", - type: "text", - }) + $: settings = componentStore + .getComponentSettings($selectedComponent?._component) + ?.concat({ + label: "Custom CSS", + key: "_css", + type: "text", + }) $: settingOptions = settings .filter(setting => setting.supportsConditions !== false) .map(setting => ({ diff --git a/packages/builder/src/stores/builder/components/index.js b/packages/builder/src/stores/builder/components/index.js index 9b7c9b325c..429fdfa062 100644 --- a/packages/builder/src/stores/builder/components/index.js +++ b/packages/builder/src/stores/builder/components/index.js @@ -5,20 +5,20 @@ import { Helpers } from "@budibase/bbui" import analytics, { Events } from "analytics" import { makePropSafe as safe } from "@budibase/string-templates" import { - getComponentSettings, findComponentPath, findClosestMatchingComponent, findComponent, findComponentParent, findAllMatchingComponents, makeComponentUnique, -} from "../components/utils" +} from "stores/builder/components/utils" import { getComponentFieldOptions } from "helpers/formFields" import { selectedScreen } from "../screens" import { screenStore, appStore, previewStore, tables } from "stores/builder" import { buildFormSchema, getSchemaForDatasource } from "builder/dataBinding" import { BUDIBASE_INTERNAL_DB_ID, + DEFAULT_BB_DATASOURCE_ID, DB_TYPE_INTERNAL, DB_TYPE_EXTERNAL, } from "constants/backend" @@ -26,10 +26,11 @@ import BudiStore from "../BudiStore" import { Utils } from "@budibase/frontend-core" export const INITIAL_COMPONENTS_STATE = { - components: [], + components: {}, customComponents: [], selectedComponentId: null, componentToPaste: null, + settingsCache: {}, } export class ComponentStore extends BudiStore { @@ -63,6 +64,9 @@ export class ComponentStore extends BudiStore { this.updateSetting = this.updateSetting.bind(this) this.updateComponentSetting = this.updateComponentSetting.bind(this) this.addParent = this.addParent.bind(this) + this.isCached = this.isCached.bind(this) + this.cacheSettings = this.cacheSettings.bind(this) + this.getComponentSettings = this.getComponentSettings.bind(this) this.selected = derived( [this.store, selectedScreen], @@ -112,14 +116,14 @@ export class ComponentStore extends BudiStore { */ async refreshDefinitions(appId) { if (!appId) { - appId = get(this.store).appId + return } // Fetch definitions and filter out custom component definitions so we // can flag them const components = await API.fetchComponentLibDefinitions(appId) - const customComponents = Object.keys(components).filter(name => - name.startsWith("plugin/") + const customComponents = Object.keys(components).filter(key => + key.startsWith("plugin/") ) // Update store @@ -136,17 +140,17 @@ export class ComponentStore extends BudiStore { } /** - * - * @param {string} componentName + * Retrieve the component definition object + * @param {string} componentType * @example * '@budibase/standard-components/container' * @returns {object} */ - getDefinition(componentName) { - if (!componentName) { + getDefinition(componentType) { + if (!componentType) { return null } - return get(this.store).components[componentName] + return get(this.store).components[componentType] } /** @@ -160,7 +164,7 @@ export class ComponentStore extends BudiStore { // Try to use their own internal table first let table = validTables.find(table => { return ( - table.sourceId !== BUDIBASE_INTERNAL_DB_ID && + table.sourceId === BUDIBASE_INTERNAL_DB_ID && table.sourceType === DB_TYPE_INTERNAL ) }) @@ -171,7 +175,7 @@ export class ComponentStore extends BudiStore { // Then try sample data table = validTables.find(table => { return ( - table.sourceId === BUDIBASE_INTERNAL_DB_ID && + table.sourceId === DEFAULT_BB_DATASOURCE_ID && table.sourceType === DB_TYPE_INTERNAL ) }) @@ -231,7 +235,7 @@ export class ComponentStore extends BudiStore { return } const defaultDS = this.getDefaultDatasource() - const settings = getComponentSettings(component._component) + const settings = this.getComponentSettings(component._component) const { parent, screen, useDefaultValues } = opts || {} const treeId = parent?._id || component._id if (!screen) { @@ -933,7 +937,7 @@ export class ComponentStore extends BudiStore { return false } - const settings = getComponentSettings(component._component) + const settings = this.getComponentSettings(component._component) const updatedSetting = settings.find(setting => setting.key === name) // Reset dependent fields @@ -1057,6 +1061,82 @@ export class ComponentStore extends BudiStore { return state }) } + + /** + * Check if the components settings have been cached + * @param {string} componentType + * @example + * '@budibase/standard-components/container' + * @returns {boolean} + */ + isCached(componentType) { + const settings = get(this.store).settingsCache + return componentType in settings + } + + /** + * Cache component settings + * @param {string} componentType + * @param {object} definition + * @example + * '@budibase/standard-components/container' + * @returns {boolean} + */ + cacheSettings(componentType, definition) { + let settings = [] + if (definition && componentType) { + settings = definition.settings?.filter(setting => !setting.section) ?? [] + definition.settings + ?.filter(setting => setting.section) + .forEach(section => { + settings = settings.concat( + (section.settings || []).map(setting => ({ + ...setting, + section: section.name, + })) + ) + }) + this.update(state => ({ + ...state, + settingsCache: { + ...state.settingsCache, + [componentType]: settings, + }, + })) + } + } + + /** + * Retrieve an array of the component settings. + * These settings are cached because they cannot change at run time. + * + * Searches a component's definition for a setting matching a certain predicate. + * @param {string} componentType + * @example + * '@budibase/standard-components/container' + * @returns {Array} + */ + getComponentSettings(componentType) { + if (!componentType) { + return [] + } + + // Ensure whole component name is used + if ( + !componentType.startsWith("plugin/") && + !componentType.startsWith("@budibase") + ) { + componentType = `@budibase/standard-components/${componentType}` + } + + if (this.isCached(componentType)) { + return get(this.store).settingsCache[componentType] + } else { + const def = this.getDefinition(componentType) + this.cacheSettings(componentType, def) + return get(this.store).settingsCache[componentType] + } + } } export const componentStore = new ComponentStore() diff --git a/packages/builder/src/stores/builder/components/utils.js b/packages/builder/src/stores/builder/components/utils.js index 72cff56503..c74a26ff05 100644 --- a/packages/builder/src/stores/builder/components/utils.js +++ b/packages/builder/src/stores/builder/components/utils.js @@ -1,4 +1,4 @@ -import { componentStore } from "." +import { componentStore } from "stores/builder" import { get } from "svelte/store" import { Helpers } from "@budibase/bbui" import { @@ -134,50 +134,6 @@ const searchComponentTree = (rootComponent, matchComponent) => { return null } -/** - * Searches a component's definition for a setting matching a certain predicate. - * These settings are cached because they cannot change at run time. - */ -let componentSettingCache = {} -export const getComponentSettings = componentType => { - if (!componentType) { - return [] - } - - // Ensure whole component name is used - if ( - !componentType.startsWith("plugin/") && - !componentType.startsWith("@budibase") - ) { - componentType = `@budibase/standard-components/${componentType}` - } - - // Check if we have cached this type already - if (componentSettingCache[componentType]) { - return componentSettingCache[componentType] - } - - // Otherwise get the settings and cache them - const def = componentStore.getDefinition(componentType) - let settings = [] - if (def) { - settings = def.settings?.filter(setting => !setting.section) ?? [] - def.settings - ?.filter(setting => setting.section) - .forEach(section => { - settings = settings.concat( - (section.settings || []).map(setting => ({ - ...setting, - section: section.name, - })) - ) - }) - } - componentSettingCache[componentType] = settings - - return settings -} - /** * Randomises a components ID's, including all child component IDs, and also * updates all data bindings to still be valid. diff --git a/packages/builder/src/stores/builder/tests/component.test.js b/packages/builder/src/stores/builder/tests/component.test.js index 029d2cb3f3..b6c9ca27cd 100644 --- a/packages/builder/src/stores/builder/tests/component.test.js +++ b/packages/builder/src/stores/builder/tests/component.test.js @@ -1,13 +1,82 @@ import { it, expect, describe, beforeEach, vi } from "vitest" -import { get } from "svelte/store" +import { get, writable } from "svelte/store" import { INITIAL_COMPONENTS_STATE, ComponentStore, } from "stores/builder/components" +import { API } from "api" +import { appStore, tables } from "stores/builder" +import { + componentDefinitionMap, + getComponentFixture, + getScreenFixture, + pluginDefinitionMap, + clientFeaturesResp, + sampleTableDoc, + internalTableDoc, + userTableDoc, + externalTableDoc, + componentsToNested, +} from "./fixtures" +import { + DB_TYPE_INTERNAL, + DB_TYPE_EXTERNAL, + DEFAULT_BB_DATASOURCE_ID, +} from "constants/backend" + +// Could move to fixtures +const COMP_PREFIX = "@budibase/standard-components" + +vi.mock("api", () => { + return { + API: { + fetchComponentLibDefinitions: vi.fn(), + }, + } +}) + +vi.mock("stores/builder", async () => { + const mockAppStore = writable() + const appStore = { + subscribe: mockAppStore.subscribe, + update: mockAppStore.update, + set: mockAppStore.set, + syncClientFeatures: vi.fn(), + } + const mockTableStore = writable() + const tables = { + subscribe: mockTableStore.subscribe, + update: mockTableStore.update, + set: mockTableStore.set, + } + return { + appStore, + tables, + } +}) + +// Simple base config for components and data sources +const baseInitialisation = ctx => { + // Init components + ctx.test.componentStore.update(state => ({ + ...state, + components: { + ...componentDefinitionMap(), + }, + })) + + // Add datasources + tables.update(state => ({ + ...state, + list: [sampleTableDoc, internalTableDoc, userTableDoc, externalTableDoc], + })) +} describe("Component store", () => { beforeEach(ctx => { vi.clearAllMocks() + vi.resetAllMocks() + const componentStore = new ComponentStore() ctx.test = {} ctx.test = { @@ -24,4 +93,379 @@ describe("Component store", () => { it("Create base component store with defaults", ctx => { expect(ctx.test.store).toStrictEqual(INITIAL_COMPONENTS_STATE) }) + + it("Reset the component store to default", ctx => { + const container = getComponentFixture(`${COMP_PREFIX}/container`) + const pluginDefs = pluginDefinitionMap() + + ctx.test.componentStore.update(state => ({ + ...state, + components: { + ...componentDefinitionMap(), + ...pluginDefs, + }, + customComponents: Object.keys(pluginDefs), + componentToPaste: container.json(), + selectedComponentId: container._id, + })) + + expect(ctx.test.store).not.toStrictEqual(INITIAL_COMPONENTS_STATE) + + ctx.test.componentStore.reset() + + expect(ctx.test.store).toStrictEqual(INITIAL_COMPONENTS_STATE) + }) + + it("Refresh the component definitions", async ctx => { + const componentDefs = componentDefinitionMap() + let mockAPIResponse = { + features: clientFeaturesResp, + ...componentDefs, + } + const apiDefRequest = vi + .spyOn(API, "fetchComponentLibDefinitions") + .mockResolvedValue(mockAPIResponse) + + const fakeAppId = "abc123" + const components = await ctx.test.componentStore.refreshDefinitions( + fakeAppId + ) + + expect(components).toStrictEqual(mockAPIResponse) + expect(ctx.test.store.components).toStrictEqual(mockAPIResponse) + + expect(apiDefRequest).toBeCalled() + expect(appStore.syncClientFeatures).toBeCalledWith(clientFeaturesResp) + }) + + it("Refresh and sync component and plugin definitions", async ctx => { + const componentDefs = componentDefinitionMap() + const pluginDefs = pluginDefinitionMap() + + let mockAPIResponse = { + features: clientFeaturesResp, + ...componentDefs, + ...pluginDefs, + } + const apiDefRequest = vi + .spyOn(API, "fetchComponentLibDefinitions") + .mockResolvedValue(mockAPIResponse) + + const fakeAppId = "abc123" + const components = await ctx.test.componentStore.refreshDefinitions( + fakeAppId + ) + + expect(components).toStrictEqual(mockAPIResponse) + expect(ctx.test.store.components).toStrictEqual(mockAPIResponse) + + expect(apiDefRequest).toBeCalled() + expect(appStore.syncClientFeatures).toBeCalledWith(clientFeaturesResp) + + expect(ctx.test.store.customComponents).toStrictEqual( + Object.keys(pluginDefs) + ) + }) + + it("Ignores definition sync if no appId is specified.", async ctx => { + const apiDefRequest = vi.spyOn(API, "fetchComponentLibDefinitions") + + const components = await ctx.test.componentStore.refreshDefinitions() + + expect(components).toBeUndefined() + + expect(apiDefRequest).not.toBeCalled() + }) + + it("Retrieves component definitions by component type", ctx => { + const pluginDefs = pluginDefinitionMap() + const componentDefs = componentDefinitionMap() + + ctx.test.componentStore.update(state => ({ + ...state, + components: { + ...componentDefs, + ...pluginDefs, + }, + customComponents: Object.keys(pluginDefs), + })) + + const def = ctx.test.componentStore.getDefinition( + "@budibase/standard-components/container" + ) + + expect(def).toStrictEqual( + componentDefs["@budibase/standard-components/container"] + ) + + const pluginDef = ctx.test.componentStore.getDefinition("plugin/budi-video") + + expect(pluginDef).toStrictEqual(pluginDefs["plugin/budi-video"]) + }) + + it("Handle missing or invalid component definition keys", ctx => { + const pluginDefs = pluginDefinitionMap() + const componentDefs = componentDefinitionMap() + + ctx.test.componentStore.update(state => ({ + ...state, + components: { + ...componentDefs, + ...pluginDefs, + }, + customComponents: Object.keys(pluginDefs), + })) + + const def = ctx.test.componentStore.getDefinition( + "@budibase/standard-components/mystery" + ) + expect(def).toBeUndefined() + + const defEmpty = ctx.test.componentStore.getDefinition() + expect(defEmpty).toBeNull() + }) + + it("Select an appropriate default datasource - Internal Table ", ctx => { + tables.update(state => ({ + ...state, + list: [sampleTableDoc, internalTableDoc, userTableDoc, externalTableDoc], + })) + + const table = ctx.test.componentStore.getDefaultDatasource() + expect(table.sourceType).toBe(DB_TYPE_INTERNAL) + expect(table.sourceId).not.toBe(DEFAULT_BB_DATASOURCE_ID) + }) + + it("Select an appropriate default datasource - Sample Table ", ctx => { + tables.update(state => ({ + ...state, + list: [sampleTableDoc, userTableDoc, externalTableDoc], + })) + + const table = ctx.test.componentStore.getDefaultDatasource() + expect(table.sourceType).toBe(DB_TYPE_INTERNAL) + expect(table.sourceId).toBe(DEFAULT_BB_DATASOURCE_ID) + }) + + it("Select an appropriate default datasource - External Table ", ctx => { + tables.update(state => ({ + ...state, + list: [userTableDoc, externalTableDoc], + })) + + const table = ctx.test.componentStore.getDefaultDatasource() + expect(table.sourceType).toBe(DB_TYPE_EXTERNAL) + }) + + it("Select an appropriate default datasource - No Table and ignore user table", ctx => { + tables.update(state => ({ + ...state, + list: [userTableDoc], + })) + + const table = ctx.test.componentStore.getDefaultDatasource() + expect(table).toBeUndefined() + }) + + it("Apply no migrations if component is not a formblock", ctx => { + const comp = getComponentFixture(`${COMP_PREFIX}/container`).json() + const orig = { ...comp } + const migrated = ctx.test.componentStore.migrateSettings(orig) + + expect(migrated).toBe(false) + expect(comp).toStrictEqual(orig) + }) + + it("Should initialise the buttons prop if it didnt exist", ctx => { + const formBlock = getComponentFixture(`${COMP_PREFIX}/formblock`).json() + const migrated = ctx.test.componentStore.migrateSettings(formBlock) + + expect(migrated).toBe(true) + expect(formBlock.buttons).toEqual([]) + }) + + it("Should initialise the buttons prop if it was nullified/reset", ctx => { + const formBlock = getComponentFixture(`${COMP_PREFIX}/formblock`).json() + formBlock.buttons = null + const migrated = ctx.test.componentStore.migrateSettings(formBlock) + + expect(migrated).toBe(true) + expect(formBlock.buttons).toEqual([]) + }) + + it("Should initialise formblock button position when not set", ctx => { + const formBlock = getComponentFixture(`${COMP_PREFIX}/formblock`).json() + const migrated = ctx.test.componentStore.migrateSettings(formBlock) + + expect(migrated).toBe(true) + expect(formBlock.buttonPosition).toEqual("top") + }) + + it("Should ignore formblock migration if already initialised", ctx => { + const formBlock = getComponentFixture(`${COMP_PREFIX}/formblock`).json() + formBlock.buttonPosition = "bottom" + formBlock.buttons = [] + const migrated = ctx.test.componentStore.migrateSettings(formBlock) + + expect(migrated).toBe(false) + expect(formBlock.buttonPosition).toEqual("bottom") + }) + + it("enrichEmptySettings - initialise multifield type with schema keys", async ctx => { + const coreScreen = getScreenFixture() + + baseInitialisation(ctx) + + const componentDefs = componentDefinitionMap() + const targetCompDef = + componentDefs["@budibase/standard-components/rowexplorer"] + + const comp = getComponentFixture(`${COMP_PREFIX}/rowexplorer`).json() + ctx.test.componentStore.enrichEmptySettings(comp, { + parent: null, + screen: coreScreen.json(), + useDefaultValues: true, + }) + + const multifieldKey = targetCompDef.settings[0].key + const multifieldOptions = comp[multifieldKey] + + expect(multifieldOptions).toStrictEqual( + Object.keys(internalTableDoc.schema) + ) + }) + + const enrichSettingsDS = (type, ctx) => { + const coreScreen = getScreenFixture() + + baseInitialisation(ctx) + + const componentDefs = componentDefinitionMap() + const targetCompDef = componentDefs[`${COMP_PREFIX}/${type}`] + + const comp = getComponentFixture(`${COMP_PREFIX}/${type}`).json() + ctx.test.componentStore.enrichEmptySettings(comp, { + parent: null, + screen: coreScreen.json(), + useDefaultValues: true, + }) + + const settingKey = targetCompDef.settings[0].key + const settingValue = comp[settingKey] + + expect(settingValue).toStrictEqual({ + label: internalTableDoc.name, + tableId: internalTableDoc._id, + resourceId: internalTableDoc._id, + type: "table", + }) + } + + it("enrichEmptySettings - set default datasource for 'table' setting type", async ctx => { + enrichSettingsDS("formblock", ctx) + }) + + it("enrichEmptySettings - set default datasource for 'dataSource' setting type", async ctx => { + enrichSettingsDS("dataprovider", ctx) + }) + + it("enrichEmptySettings - set default datasource for type dataprovider", ctx => { + const coreScreen = getScreenFixture() + + baseInitialisation(ctx) + + const componentDefs = componentDefinitionMap() + const targetCompDef = componentDefs[`${COMP_PREFIX}/table`] + + const providerOne = getComponentFixture(`${COMP_PREFIX}/dataprovider`) + const tableOne = getComponentFixture(`${COMP_PREFIX}/table`) + + const components = Array(10) + .fill() + .map(() => getComponentFixture(`${COMP_PREFIX}/container`)) + + components.splice(5, 0, providerOne) + components.push(tableOne) + + let nested = componentsToNested(components) + coreScreen.addChild(nested) + + const comp = tableOne.json() + ctx.test.componentStore.enrichEmptySettings(comp, { + parent: null, + screen: coreScreen.json(), + useDefaultValues: true, + }) + const settingKey = targetCompDef.settings[0].key + const settingValue = comp[settingKey] + + expect(settingValue).toBe(`{{ literal [${providerOne.json()._id}] }}`) + }) + + it("enrichEmptySettings - set default datasource for type dataprovider - get closest provider", ctx => { + const coreScreen = getScreenFixture() + + baseInitialisation(ctx) + + const componentDefs = componentDefinitionMap() + const targetCompDef = componentDefs[`${COMP_PREFIX}/table`] + + const providerOne = getComponentFixture(`${COMP_PREFIX}/dataprovider`) + const providerTwo = getComponentFixture(`${COMP_PREFIX}/dataprovider`) + const tableOne = getComponentFixture(`${COMP_PREFIX}/table`) + + const components = Array(10) + .fill() + .map(() => getComponentFixture(`${COMP_PREFIX}/container`)) + + components.unshift(providerOne) + components.splice(5, 0, providerTwo) + components.push(tableOne) + + let nested = componentsToNested(components) + coreScreen.addChild(nested) + + const comp = tableOne.json() + ctx.test.componentStore.enrichEmptySettings(comp, { + parent: null, + screen: coreScreen.json(), + useDefaultValues: true, + }) + const settingKey = targetCompDef.settings[0].key + const settingValue = comp[settingKey] + + // Get the closest data provider in the tree. + expect(settingValue).toBe(`{{ literal [${providerTwo.json()._id}] }}`) + }) + + it("enrichEmptySettings - set default datasource for type dataprovider - no providers in tree", ctx => { + const coreScreen = getScreenFixture() + + baseInitialisation(ctx) + + const componentDefs = componentDefinitionMap() + const targetCompDef = componentDefs[`${COMP_PREFIX}/table`] + + const tableOne = getComponentFixture(`${COMP_PREFIX}/table`) + const components = Array(10) + .fill() + .map(() => getComponentFixture(`${COMP_PREFIX}/container`)) + + components.push(tableOne) + + let nested = componentsToNested(components) + coreScreen.addChild(nested) + + const comp = tableOne.json() + ctx.test.componentStore.enrichEmptySettings(comp, { + parent: null, + screen: coreScreen.json(), + useDefaultValues: true, + }) + const settingKey = targetCompDef.settings[0].key + const settingValue = comp[settingKey] + + // The value should remain unset + expect(settingValue).toBeUndefined() + }) }) diff --git a/packages/builder/src/stores/builder/tests/fixtures/index.js b/packages/builder/src/stores/builder/tests/fixtures/index.js index ba349d6ba8..87cfa5a43f 100644 --- a/packages/builder/src/stores/builder/tests/fixtures/index.js +++ b/packages/builder/src/stores/builder/tests/fixtures/index.js @@ -2,6 +2,12 @@ import { v4 } from "uuid" import { Component } from "builder/store/screenTemplates/utils/Component" import { Screen } from "builder/store/screenTemplates/utils/Screen" import { get } from "svelte/store" +import { + BUDIBASE_INTERNAL_DB_ID, + DB_TYPE_INTERNAL, + DB_TYPE_EXTERNAL, + DEFAULT_BB_DATASOURCE_ID, +} from "constants/backend" const getDocId = () => { return v4().replace(/-/g, "") @@ -22,20 +28,90 @@ export const getComponentFixture = type => { return new Component(type) } +// Sample Definitions export const COMPONENT_DEFINITIONS = { form: { name: "Form", - icon: "Form", hasChildren: true, illegalChildren: ["section", "form", "formblock"], }, formblock: { name: "Form Block", block: true, + settings: [ + { + type: "table", + key: "dataSource", + }, + ], }, container: { name: "Container", }, + rowexplorer: { + name: "Row Explorer", + settings: [ + { + // combo unique to the row explorer + type: "multifield", + selectAllFields: true, + key: "detailFields", + }, + ], + }, + dataprovider: { + name: "Data Provider", + settings: [ + { + type: "dataSource", + }, + ], + }, + table: { + name: "Table", + settings: [ + { + type: "dataProvider", + key: "dataProvider", + }, + ], + }, + stringfield: { + name: "Text Field", + settings: [ + { + type: "field/string", + key: "field", + }, + ], + }, +} + +// Sample plugin definitions +export const PLUGIN_DEFINITIONS = { + "budi-video": { + component: "plugin/budi-video", + description: "Embedded video component. ", + friendlyName: "Budi Video", + icon: "VideoOutline", + name: "budi-video", + }, +} + +// Take a component array and turn it into a deeply nested tree +export const componentsToNested = components => { + let nested + do { + const current = components.pop() + if (!nested) { + nested = current + continue + } + //review this for the empty + current.addChild(nested) + nested = current + } while (components.length) + return nested } export const getFakeScreenPatch = store => { @@ -48,7 +124,7 @@ export const getFakeScreenPatch = store => { } } -export const componentMap = () => { +export const componentDefinitionMap = () => { return Object.keys(COMPONENT_DEFINITIONS).reduce((acc, key) => { const def = COMPONENT_DEFINITIONS[key] acc[`@budibase/standard-components/${key}`] = def @@ -56,6 +132,14 @@ export const componentMap = () => { }, {}) } +export const pluginDefinitionMap = () => { + return Object.keys(PLUGIN_DEFINITIONS).reduce((acc, key) => { + const def = PLUGIN_DEFINITIONS[key] + acc[`plugin/${key}`] = def + return acc + }, {}) +} + export const getPluginFixture = pluginName => { const fakeName = pluginName || v4().replace(/-/g, "") return { @@ -145,15 +229,56 @@ export const clientFeaturesResp = { sidePanel: true, } -export const fetchDefinitionsResp = { - "@budibase/standard-components/text": { - component: "@budibase/standard-components/text", - }, - "plugin/budi-video": { - component: "plugin/budi-video", - }, - "plugin/budi-audio": { - component: "plugin/budi-audio", - }, - features: clientFeaturesResp, +export const userTableDoc = { + _id: "ta_users", + type: "table", + name: "Users", + schema: {}, +} + +export const sampleTableDoc = { + _id: "ta_bb_employee", + type: "table", + name: "Employees", + sourceId: DEFAULT_BB_DATASOURCE_ID, + sourceType: DB_TYPE_INTERNAL, + primaryDisplay: "First Name", + schema: { + "First Name": { + name: "First Name", + type: "string", + }, + "Last Name": { + name: "Last Name", + type: "string", + }, + }, +} + +export const internalTableDoc = { + _id: "ta_db5ac9e254da415899adcec21a025b3f", + tableId: "ta_db5ac9e254da415899adcec21a025b3f", + type: "table", + name: "Media", + sourceId: BUDIBASE_INTERNAL_DB_ID, + sourceType: DB_TYPE_INTERNAL, + schema: { + MediaTitle: { + name: "MediaTitle", + type: "string", + }, + MediaVersion: { + name: "MediaVersion", + type: "string", + }, + }, +} + +export const externalTableDoc = { + type: "table", + _id: "datasource_plus_c5e6ae7fbe534da6917c44b284c54b45__Tester", + name: "Tester", + sourceId: "datasource_plus_c5e6ae7fbe534da6917c44b284c54b45", + sourceType: DB_TYPE_EXTERNAL, + sql: true, } diff --git a/packages/builder/src/stores/builder/tests/screens.test.js b/packages/builder/src/stores/builder/tests/screens.test.js index 0fd604b4f1..51e3a8d830 100644 --- a/packages/builder/src/stores/builder/tests/screens.test.js +++ b/packages/builder/src/stores/builder/tests/screens.test.js @@ -8,9 +8,10 @@ import { getScreenFixture, getComponentFixture, COMPONENT_DEFINITIONS, - componentMap, + componentDefinitionMap, getScreenDocId, getPluginFixture, + componentsToNested, } from "./fixtures" const COMP_PREFIX = "@budibase/standard-components" @@ -173,7 +174,7 @@ describe("Screens store", () => { const defSpy = vi .spyOn(componentStore, "getDefinition") .mockImplementation(comp => { - const defMap = componentMap() + const defMap = componentDefinitionMap() return defMap[comp] }) @@ -198,23 +199,14 @@ describe("Screens store", () => { components.push(formTwo) //Take the array and turn it into a deeply nested tree - let nested - do { - const current = components.pop() - if (!nested) { - nested = current - continue - } - current.addChild(nested) - nested = current - } while (components.length) + let nested = componentsToNested(components) coreScreen.addChild(nested) const defSpy = vi .spyOn(componentStore, "getDefinition") .mockImplementation(comp => { - const defMap = componentMap() + const defMap = componentDefinitionMap() return defMap[comp] })