Skip to content

Commit

Permalink
Send dev harness event when it is a stubbed action
Browse files Browse the repository at this point in the history
  • Loading branch information
alanko0511 committed Sep 11, 2024
1 parent 4a605de commit bf8f5bb
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 4 deletions.
15 changes: 15 additions & 0 deletions packages/api-client-core/src/GadgetFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,21 @@ export interface ActionFunctionMetadata<OptionsT, VariablesT, SelectionT, Schema
hasCreateOrUpdateEffect?: boolean;
}

export type StubbedActionReason = "MissingApiTrigger";

export interface StubbedActionFunctionMetadata {
type: "stubbedAction";
functionName: string;
operationName?: string;
errorMessage: string;
modelApiIdentifier?: string;
variables: VariablesOptions;
reason: StubbedActionReason;
dataPath: string;
}

export type StubbedActionFunction<OptionsT> = StubbedActionFunctionMetadata & ActionWithNoIdAndNoVariables<OptionsT>;

export type ActionFunction<OptionsT, VariablesT, SelectionT, SchemaT, DefaultsT> = ActionFunctionMetadata<
OptionsT,
VariablesT,
Expand Down
34 changes: 34 additions & 0 deletions packages/react/spec/useAction.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -805,4 +805,38 @@ describe("useAction", () => {

expect(result.current[0]).toBe(beforeObject);
});

test("sends a stubbedActionError event when the action is stubbed", () => {
let eventDispatched: CustomEvent | undefined;

globalThis.addEventListener("gadget:devharness:stubbedActionError", (event) => {
eventDispatched = event as CustomEvent;
});

const { result, rerender } = renderHook(
() =>
useAction({
// @ts-expect-error intentionally passing the wrong type
type: "stubbedAction",
reason: "MissingApiTrigger",
dataPath: "fakePath",
functionName: "fakeFunction",
modelApiIdentifier: "fakeModel",
variables: {},
}),
{
wrapper: MockClientWrapper(relatedProductsApi),
}
);

expect(eventDispatched).toBeTruthy();
expect(eventDispatched!.detail).toEqual({
reason: "MissingApiTrigger",
action: {
actionApiIdentifier: "fakeFunction",
dataPath: "fakePath",
modelApiIdentifier: "fakeModel",
},
});
});
});
32 changes: 32 additions & 0 deletions packages/react/spec/useGlobalAction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,4 +255,36 @@ describe("useGlobalAction", () => {

expect(result.current[0]).toBe(beforeObject);
});

test("sends a stubbedActionError event when the action is stubbed", () => {
let eventDispatched: CustomEvent | undefined;

globalThis.addEventListener("gadget:devharness:stubbedActionError", (event) => {
eventDispatched = event as CustomEvent;
});

const { result, rerender } = renderHook(
() =>
useGlobalAction({
// @ts-expect-error intentionally passing the wrong type
type: "stubbedAction",
reason: "MissingApiTrigger",
dataPath: "fakePath",
functionName: "fakeFunction",
variables: {},
}),
{
wrapper: MockClientWrapper(bulkExampleApi),
}
);

expect(eventDispatched).toBeTruthy();
expect(eventDispatched!.detail).toEqual({
reason: "MissingApiTrigger",
action: {
actionApiIdentifier: "fakeFunction",
dataPath: "fakePath",
},
});
});
});
33 changes: 31 additions & 2 deletions packages/react/src/useAction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import type { ActionFunction, DefaultSelection, GadgetRecord, LimitToKnownKeys, Select } from "@gadgetinc/api-client-core";
import type {
ActionFunction,
DefaultSelection,
GadgetRecord,
LimitToKnownKeys,
Select,
StubbedActionFunction,
} from "@gadgetinc/api-client-core";
import {
actionOperation,
capitalizeIdentifier,
Expand All @@ -7,7 +14,7 @@ import {
hydrateRecord,
namespaceDataPath,
} from "@gadgetinc/api-client-core";
import { useCallback, useContext, useMemo } from "react";
import { useCallback, useContext, useEffect, useMemo } from "react";
import type { AnyVariables, OperationContext, UseMutationState } from "urql";
import { GadgetUrqlClientContext } from "./GadgetProvider.js";
import { useGadgetMutation } from "./useGadgetMutation.js";
Expand Down Expand Up @@ -62,6 +69,28 @@ export const useAction = <
> => {
if (!useContext(GadgetUrqlClientContext)) throw new Error(noProviderErrorMessage);

useEffect(() => {
if (action.type === ("stubbedAction" as string)) {
const stubbedAction = action as unknown as StubbedActionFunction<GivenOptions>;
if (!("reason" in stubbedAction) || !("dataPath" in stubbedAction)) {
// Don't dispatch an event if the generated client has not yet been updated with the updated parameters
return;
}

const event = new CustomEvent("gadget:devharness:stubbedActionError", {
detail: {
reason: stubbedAction.reason,
action: {
actionApiIdentifier: stubbedAction.functionName,
modelApiIdentifier: stubbedAction.modelApiIdentifier,
dataPath: stubbedAction.dataPath,
},
},
});
globalThis.dispatchEvent(event);
}
}, []);

const memoizedOptions = useStructuralMemo(options);
const plan = useMemo(() => {
return actionOperation(
Expand Down
25 changes: 23 additions & 2 deletions packages/react/src/useGlobalAction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { GlobalActionFunction } from "@gadgetinc/api-client-core";
import type { GlobalActionFunction, StubbedActionFunction } from "@gadgetinc/api-client-core";
import { get, globalActionOperation, namespaceDataPath } from "@gadgetinc/api-client-core";
import { useCallback, useMemo } from "react";
import { useCallback, useEffect, useMemo } from "react";
import type { OperationContext, UseMutationState } from "urql";
import { useGadgetMutation } from "./useGadgetMutation.js";
import type { ActionHookResult } from "./utils.js";
Expand Down Expand Up @@ -30,6 +30,27 @@ import { ErrorWrapper } from "./utils.js";
export const useGlobalAction = <F extends GlobalActionFunction<any>>(
action: F
): ActionHookResult<any, Exclude<F["variablesType"], null | undefined>> => {
useEffect(() => {
if (action.type === ("stubbedAction" as string)) {
const stubbedAction = action as unknown as StubbedActionFunction<any>;
if (!("reason" in stubbedAction) || !("dataPath" in stubbedAction)) {
// Don't dispatch an event if the generated client has not yet been updated with the updated parameters
return;
}

const event = new CustomEvent("gadget:devharness:stubbedActionError", {
detail: {
reason: stubbedAction.reason,
action: {
actionApiIdentifier: stubbedAction.functionName,
dataPath: stubbedAction.dataPath,
},
},
});
globalThis.dispatchEvent(event);
}
}, []);

const plan = useMemo(() => {
return globalActionOperation(action.operationName, action.variables, action.namespace);
}, [action]);
Expand Down

0 comments on commit bf8f5bb

Please sign in to comment.