Skip to content

Commit

Permalink
Update auto component error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
alanko0511 committed Sep 18, 2024
1 parent 8fe1cf5 commit e270c73
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 17 deletions.
10 changes: 5 additions & 5 deletions packages/react/spec/auto/PolarisAutoForm.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -676,28 +676,28 @@ describe("PolarisAutoForm", () => {
it("throws an error when a model action without triggers", () => {
expect(() => {
render(<PolarisAutoForm action={api.autoTableTest.noTriggerAction as any} />, { wrapper: PolarisMockedProviders });
}).toThrow(TriggerableActionRequiredErrorMessage);
}).toThrow(MissingApiTriggerErrorMessage);
});

it("throws an error when a global action without triggers", () => {
expect(() => {
render(<PolarisAutoForm action={api.noTriggerGlobalAction as any} />, { wrapper: PolarisMockedProviders });
}).toThrow(TriggerableActionRequiredErrorMessage);
}).toThrow(MissingApiTriggerErrorMessage);
});
});
describe("Has triggers in api client but no triggers in action metadata", () => {
it("throws an error when a model action without triggers", () => {
expect(() => {
render(<PolarisAutoForm action={api.widget.create as any} />, { 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(<PolarisAutoForm action={api.flipAll as any} />, { wrapper: PolarisMockedProviders });
loadMockFlipAllMetadata({ triggers: [{ specID: "non/api/trigger", __typename: "GadgetTrigger" }] });
}).toThrow(TriggerableActionRequiredErrorMessage);
}).toThrow(MissingApiTriggerErrorMessage);
});
});
});
Expand Down
10 changes: 10 additions & 0 deletions packages/react/spec/auto/form/PolarisAutoFormErrors.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ export default {
],
};

export const InvalidModelAction = {
args: {
action: api.autoTableTest.invalidAction,
},
};

export const MissingActionProp = {
args: {},
};

export const FieldNameCustomParamCollisionError = {
args: {
findBy: "1",
Expand Down
11 changes: 11 additions & 0 deletions packages/react/spec/auto/table/PolarisAutoTable.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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(<PolarisAutoTable model={(api as any).invalidModel} />, {
wrapper: PolarisMockedProviders,
});
}).toThrow(InvalidModelErrorMessage);
});
});
});
21 changes: 18 additions & 3 deletions packages/react/src/auto/AutoFormActionValidators.ts
Original file line number Diff line number Diff line change
@@ -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<any, any, any, any, any> | GlobalActionFunction<any>) => {
if (action.isBulk) {
Expand All @@ -8,14 +9,28 @@ export const validateNonBulkAction = (action: ActionFunction<any, any, any, any,
};

const GadgetApiTriggerSpecId = "gadget/trigger/graphql_api";
export const TriggerableActionRequiredErrorMessage = `"action" must be a valid Gadget action with an API trigger to be used in AutoForms`;

export const MissingActionPropErrorMessage = "Specify a valid Gagdet action to use AutoForm";
export const InvalidActionErrorMessage = `"action" is not a valid Gadget action`;
export const MissingApiTriggerErrorMessage = `"action" requires an API trigger to be used in AutoForm`;

const validActionTypes = ["globalAction", "action"];

export const validateTriggersFromApiClient = (action: ActionFunction<any, any, any, any, any> | GlobalActionFunction<any>) => {
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<typeof useAutoForm>[0]) => {
if (!("action" in props)) {
throw new Error(MissingActionPropErrorMessage);
}

if (!props.action) {
throw new Error(InvalidActionErrorMessage);
}
};

Expand All @@ -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);
}
}
};
9 changes: 9 additions & 0 deletions packages/react/src/auto/AutoTableValidators.ts
Original file line number Diff line number Diff line change
@@ -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<typeof useTable>[0]) => {
if (!manager) {
throw new Error(InvalidModelErrorMessage);
}
};
3 changes: 3 additions & 0 deletions packages/react/src/auto/hooks/useTableBulkActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useMemo } from "react";
import deepEqual from "react-fast-compare";
import type { ActionCallback, TableOptions } from "../../use-table/types.js";
import { humanizeCamelCase } from "../../utils.js";
import { validateAutoTableProps } from "../AutoTableValidators.js";

export type ModelActionDetails =
| {
Expand Down Expand Up @@ -32,6 +33,8 @@ export const useTableBulkActions = (props: {
}) => {
const { model, actions, excludeActions } = props;

validateAutoTableProps(props.model);

if (actions && excludeActions) {
throw new Error("Cannot have both actions and excludeActions in the same table");
}
Expand Down
9 changes: 4 additions & 5 deletions packages/react/src/auto/mui/MUIAutoForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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 { validateAutoFormProps } from "../AutoFormActionValidators.js";
import { AutoFormMetadataContext } from "../AutoFormContext.js";
import { MUIAutoInput } from "./inputs/MUIAutoInput.js";
import { MUIAutoSubmit } from "./submit/MUIAutoSubmit.js";
Expand Down Expand Up @@ -36,10 +36,6 @@ export const MUIAutoForm = <
) => {
const { action, findBy } = props as MUIAutoFormProps<GivenOptions, SchemaT, ActionFunc> & { 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}`;

Expand All @@ -53,6 +49,9 @@ export const MUIAutoFormComponent = <
props: MUIAutoFormProps<GivenOptions, SchemaT, ActionFunc>
) => {
const { record: _record, action, findBy, ...rest } = props as MUIAutoFormProps<GivenOptions, SchemaT, ActionFunc> & { findBy: any };

validateAutoFormProps(props);

const { metadata, fetchingMetadata, metadataError, fields, submit, formError, isSubmitting, isSubmitSuccessful, originalFormMethods } =
useAutoForm(props);

Expand Down
6 changes: 2 additions & 4 deletions packages/react/src/auto/polaris/PolarisAutoForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ 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 { validateAutoFormProps } from "../AutoFormActionValidators.js";
import { AutoFormMetadataContext } from "../AutoFormContext.js";
import { PolarisAutoInput } from "./inputs/PolarisAutoInput.js";
import { PolarisAutoSubmit } from "./submit/PolarisAutoSubmit.js";
Expand All @@ -33,9 +33,7 @@ export const PolarisAutoForm = <
const { action, findBy } = props as AutoFormProps<GivenOptions, SchemaT, ActionFunc> &
Omit<Partial<FormProps>, "action"> & { findBy: any };

if (!action) {
throw new Error(TriggerableActionRequiredErrorMessage);
}
validateAutoFormProps(props);

// Component key to force re-render when the action or findBy changes
const componentKey = `${action.modelApiIdentifier ?? ""}.${action.operationName}.${findBy}`;
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/auto/polaris/PolarisAutoTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -65,6 +66,9 @@ export const PolarisAutoTable = <
props: AutoTableProps<GivenOptions, SchemaT, FinderFunction, Options>
) => {
const { model } = props;

validateAutoTableProps(props.model);

const componentKey = `${[model.findMany.namespace, model.findMany.modelApiIdentifier].join("_")}AutoTable`;

return <PolarisAutoTableComponent key={componentKey} {...props} />;
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/useTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
type Select,
} from "@gadgetinc/api-client-core";
import { useCallback, useMemo, useState } from "react";
import { validateAutoTableProps } from "./auto/AutoTableValidators.js";
import { useModelMetadata } from "./metadata.js";
import { getTableColumns, getTableRows, getTableSelectionMap, getTableSpec } from "./use-table/helpers.js";
import type { TableOptions, TableResult } from "./use-table/types.js";
Expand Down Expand Up @@ -48,6 +49,8 @@ export const useTable = <
): TableResult<
GadgetRecord<Select<Exclude<F["schemaType"], null | undefined>, DefaultSelection<F["selectionType"], Options, F["defaultSelection"]>>>[]
> => {
validateAutoTableProps(manager);

const namespace = manager.findMany.namespace;
const namespaceAsArray: string[] = namespace ? (Array.isArray(namespace) ? namespace : [namespace]) : [];
const {
Expand Down

0 comments on commit e270c73

Please sign in to comment.