diff --git a/packages/react/spec/auto/PolarisAutoForm.spec.tsx b/packages/react/spec/auto/PolarisAutoForm.spec.tsx index c10a5c408..7aa6858bb 100644 --- a/packages/react/spec/auto/PolarisAutoForm.spec.tsx +++ b/packages/react/spec/auto/PolarisAutoForm.spec.tsx @@ -6,7 +6,7 @@ import type { UserEvent } from "@testing-library/user-event"; import { userEvent } from "@testing-library/user-event"; import type { ReactNode } from "react"; import React from "react"; -import { TriggerableActionRequiredErrorMessage } from "../../src/auto/AutoFormActionValidators.js"; +import { MissingApiTriggerErrorMessage } from "../../src/auto/AutoFormActionValidators.js"; import { PolarisAutoForm } from "../../src/auto/polaris/PolarisAutoForm.js"; import { PolarisAutoInput } from "../../src/auto/polaris/inputs/PolarisAutoInput.js"; import { PolarisAutoSubmit } from "../../src/auto/polaris/submit/PolarisAutoSubmit.js"; @@ -676,13 +676,13 @@ describe("PolarisAutoForm", () => { it("throws an error when a model action without triggers", () => { expect(() => { render(, { wrapper: PolarisMockedProviders }); - }).toThrow(TriggerableActionRequiredErrorMessage); + }).toThrow(MissingApiTriggerErrorMessage); }); it("throws an error when a global action without triggers", () => { expect(() => { render(, { wrapper: PolarisMockedProviders }); - }).toThrow(TriggerableActionRequiredErrorMessage); + }).toThrow(MissingApiTriggerErrorMessage); }); }); describe("Has triggers in api client but no triggers in action metadata", () => { @@ -690,14 +690,14 @@ describe("PolarisAutoForm", () => { expect(() => { render(, { wrapper: PolarisMockedProviders }); loadMockWidgetCreateMetadata({ triggers: [{ specID: "non/api/trigger", __typename: "GadgetTrigger" }] }); - }).toThrow(TriggerableActionRequiredErrorMessage); + }).toThrow(MissingApiTriggerErrorMessage); }); it("throws an error when a global action without triggers", () => { expect(() => { render(, { wrapper: PolarisMockedProviders }); loadMockFlipAllMetadata({ triggers: [{ specID: "non/api/trigger", __typename: "GadgetTrigger" }] }); - }).toThrow(TriggerableActionRequiredErrorMessage); + }).toThrow(MissingApiTriggerErrorMessage); }); }); }); diff --git a/packages/react/spec/auto/form/PolarisAutoFormErrors.stories.jsx b/packages/react/spec/auto/form/PolarisAutoFormErrors.stories.jsx index 99163b13e..992b6937e 100644 --- a/packages/react/spec/auto/form/PolarisAutoFormErrors.stories.jsx +++ b/packages/react/spec/auto/form/PolarisAutoFormErrors.stories.jsx @@ -28,6 +28,16 @@ export default { ], }; +export const InvalidModelAction = { + args: { + action: api.autoTableTest.invalidAction, + }, +}; + +export const MissingActionProp = { + args: {}, +}; + export const FieldNameCustomParamCollisionError = { args: { findBy: "1", diff --git a/packages/react/spec/auto/table/PolarisAutoTable.spec.tsx b/packages/react/spec/auto/table/PolarisAutoTable.spec.tsx index e55bce590..1136b6dcb 100644 --- a/packages/react/spec/auto/table/PolarisAutoTable.spec.tsx +++ b/packages/react/spec/auto/table/PolarisAutoTable.spec.tsx @@ -3,6 +3,7 @@ import { act, render } from "@testing-library/react"; import type { UserEvent } from "@testing-library/user-event"; import { userEvent } from "@testing-library/user-event"; import React from "react"; +import { InvalidModelErrorMessage } from "../../../src/auto/AutoTableValidators.js"; import { GadgetFieldType } from "../../../src/internal/gql/graphql.js"; import type { TableColumn } from "../../../src/use-table/types.js"; import { testApi as api } from "../../apis.js"; @@ -451,4 +452,14 @@ describe("PolarisAutoTable", () => { expect(firstRowCells[1].innerHTML).not.toContain("..."); }); }); + + describe("invalid model", () => { + it("throws an error when the model is not valid", () => { + expect(() => { + render(, { + wrapper: PolarisMockedProviders, + }); + }).toThrow(InvalidModelErrorMessage); + }); + }); }); diff --git a/packages/react/src/auto/AutoForm.ts b/packages/react/src/auto/AutoForm.ts index 76c8e7dff..4aa9fc9d9 100644 --- a/packages/react/src/auto/AutoForm.ts +++ b/packages/react/src/auto/AutoForm.ts @@ -10,7 +10,12 @@ import type { FieldErrors, FieldValues } from "../useActionForm.js"; import { useActionForm } from "../useActionForm.js"; import { get, getFlattenedObjectKeys, type OptionsType } from "../utils.js"; import { validationSchema } from "../validationSchema.js"; -import { validateNonBulkAction, validateTriggersFromApiClient, validateTriggersFromMetadata } from "./AutoFormActionValidators.js"; +import { + validateAutoFormProps, + validateNonBulkAction, + validateTriggersFromApiClient, + validateTriggersFromMetadata, +} from "./AutoFormActionValidators.js"; /** The props that any component accepts */ export type AutoFormProps< @@ -137,6 +142,7 @@ export const useAutoForm = < ) => { const { action, record, onSuccess, onFailure, findBy } = props; + validateAutoFormProps(props); validateNonBulkAction(action); validateTriggersFromApiClient(action); diff --git a/packages/react/src/auto/AutoFormActionValidators.ts b/packages/react/src/auto/AutoFormActionValidators.ts index 361911e16..e71eabb69 100644 --- a/packages/react/src/auto/AutoFormActionValidators.ts +++ b/packages/react/src/auto/AutoFormActionValidators.ts @@ -1,5 +1,6 @@ import type { ActionFunction, GlobalActionFunction } from "@gadgetinc/api-client-core"; import type { ActionMetadata, GlobalActionMetadata } from "../metadata.js"; +import type { useAutoForm } from "./AutoForm.js"; export const validateNonBulkAction = (action: ActionFunction | GlobalActionFunction) => { if (action.isBulk) { @@ -8,14 +9,28 @@ export const validateNonBulkAction = (action: ActionFunction | GlobalActionFunction) => { if (!validActionTypes.includes(action.type)) { // When the API client is built with an action without the API trigger, the type will be "stubbedAction" // action.type === "globalAction" | "action" // Only when the action has the API trigger when the api client is built - throw new Error(TriggerableActionRequiredErrorMessage); + throw new Error(MissingApiTriggerErrorMessage); + } +}; + +export const validateAutoFormProps = (props: Parameters[0]) => { + if (!("action" in props)) { + throw new Error(MissingActionPropErrorMessage); + } + + if (!props.action) { + throw new Error(InvalidActionErrorMessage); } }; @@ -32,7 +47,7 @@ export const validateTriggersFromMetadata = (metadata?: ActionMetadata | GlobalA const hasApiTrigger = triggersAsArray.some((trigger) => trigger.specID === GadgetApiTriggerSpecId); if (!hasApiTrigger) { - throw new Error(TriggerableActionRequiredErrorMessage); + throw new Error(MissingApiTriggerErrorMessage); } } }; diff --git a/packages/react/src/auto/AutoTableValidators.ts b/packages/react/src/auto/AutoTableValidators.ts new file mode 100644 index 000000000..a887d2397 --- /dev/null +++ b/packages/react/src/auto/AutoTableValidators.ts @@ -0,0 +1,9 @@ +import type { useTable } from "../useTable.js"; + +export const InvalidModelErrorMessage = `"model" is not a valid Gadget model`; + +export const validateAutoTableProps = (manager: Parameters[0]) => { + if (!manager) { + throw new Error(InvalidModelErrorMessage); + } +}; diff --git a/packages/react/src/auto/mui/MUIAutoForm.tsx b/packages/react/src/auto/mui/MUIAutoForm.tsx index b32269688..257a3be9a 100644 --- a/packages/react/src/auto/mui/MUIAutoForm.tsx +++ b/packages/react/src/auto/mui/MUIAutoForm.tsx @@ -5,7 +5,6 @@ import React from "react"; import { FormProvider } from "react-hook-form"; import { humanizeCamelCase, type OptionsType } from "../../utils.js"; import { useAutoForm, type AutoFormProps } from "../AutoForm.js"; -import { TriggerableActionRequiredErrorMessage } from "../AutoFormActionValidators.js"; import { AutoFormMetadataContext } from "../AutoFormContext.js"; import { MUIAutoInput } from "./inputs/MUIAutoInput.js"; import { MUIAutoSubmit } from "./submit/MUIAutoSubmit.js"; @@ -36,10 +35,6 @@ export const MUIAutoForm = < ) => { const { action, findBy } = props as MUIAutoFormProps & { findBy: any }; - if (!action) { - throw new Error(TriggerableActionRequiredErrorMessage); - } - // Component key to force re-render when the action or findBy changes const componentKey = `${action.modelApiIdentifier ?? ""}.${action.operationName}.${findBy}`; diff --git a/packages/react/src/auto/polaris/PolarisAutoForm.tsx b/packages/react/src/auto/polaris/PolarisAutoForm.tsx index 80221b3d8..17e0b35e4 100644 --- a/packages/react/src/auto/polaris/PolarisAutoForm.tsx +++ b/packages/react/src/auto/polaris/PolarisAutoForm.tsx @@ -6,7 +6,6 @@ import { FormProvider } from "react-hook-form"; import { humanizeCamelCase, type OptionsType } from "../../utils.js"; import type { AutoFormProps } from "../AutoForm.js"; import { useAutoForm } from "../AutoForm.js"; -import { TriggerableActionRequiredErrorMessage } from "../AutoFormActionValidators.js"; import { AutoFormMetadataContext } from "../AutoFormContext.js"; import { PolarisAutoInput } from "./inputs/PolarisAutoInput.js"; import { PolarisAutoSubmit } from "./submit/PolarisAutoSubmit.js"; @@ -33,10 +32,6 @@ export const PolarisAutoForm = < const { action, findBy } = props as AutoFormProps & Omit, "action"> & { findBy: any }; - if (!action) { - throw new Error(TriggerableActionRequiredErrorMessage); - } - // Component key to force re-render when the action or findBy changes const componentKey = `${action.modelApiIdentifier ?? ""}.${action.operationName}.${findBy}`; diff --git a/packages/react/src/auto/polaris/PolarisAutoTable.tsx b/packages/react/src/auto/polaris/PolarisAutoTable.tsx index 88c8b63f7..938393718 100644 --- a/packages/react/src/auto/polaris/PolarisAutoTable.tsx +++ b/packages/react/src/auto/polaris/PolarisAutoTable.tsx @@ -21,6 +21,7 @@ import { useTable } from "../../useTable.js"; import type { ColumnValueType, OptionsType } from "../../utils.js"; import type { AutoTableProps } from "../AutoTable.js"; import { AutoTableContext } from "../AutoTableContext.js"; +import { validateAutoTableProps } from "../AutoTableValidators.js"; import type { BulkActionOption } from "../hooks/useTableBulkActions.js"; import { useTableBulkActions } from "../hooks/useTableBulkActions.js"; import { PolarisAutoBulkActionModal } from "./PolarisAutoBulkActionModal.js"; @@ -65,7 +66,10 @@ export const PolarisAutoTable = < props: AutoTableProps ) => { const { model } = props; - const componentKey = `${[model.findMany.namespace, model.findMany.modelApiIdentifier].join("_")}AutoTable`; + + const componentKey = model + ? `${[model.findMany.namespace, model.findMany.modelApiIdentifier].join("_")}AutoTable` + : "undefinedModelAutoTable"; return ; }; @@ -82,6 +86,8 @@ const PolarisAutoTableComponent = < const searchable = props.searchable ?? true; const paginate = props.paginate ?? true; + validateAutoTableProps(props.model); + const [methods, refresh] = useTable(props.model, { select: props.select, columns: props.columns,