@applicaster/zapp-react-native-utils 16.0.0-rc.89 → 16.0.0-rc.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -56,7 +56,6 @@ export class ActionExecutor {
56
56
  context?: ActionExecutionContext
57
57
  ): Promise<ActionResult> => {
58
58
  for (const action of actionTypes) {
59
- // TODO: Handle action result error
60
59
  const result = await this.handleAction(action, context);
61
60
 
62
61
  if (result === ActionResult.Cancel) {
@@ -64,6 +63,16 @@ export class ActionExecutor {
64
63
 
65
64
  return result;
66
65
  }
66
+
67
+ if (result === ActionResult.Error) {
68
+ // Everything after a failed step was written for a state that never
69
+ // happened — the event was refused, the screen never opened — so
70
+ // running it is how a chain ends up telling the user their mail was
71
+ // sent when it was not.
72
+ log_error(`handleActions: action: ${action.type} failed`, { action });
73
+
74
+ return result;
75
+ }
67
76
  }
68
77
 
69
78
  return ActionResult.Success;
@@ -18,6 +18,7 @@ import { createLogger } from "../logger";
18
18
  import {
19
19
  appRestartAction,
20
20
  confirmDialogAction,
21
+ showAlertAction,
21
22
  createGoBackAction,
22
23
  createGoHomeAction,
23
24
  createNavigateToScreenAction,
@@ -110,6 +111,8 @@ const prepareDefaultActions = (actionExecutor) => {
110
111
  confirmDialogAction
111
112
  );
112
113
 
114
+ actionExecutor.registerAction(ACTION_TYPES.SHOW_ALERT, showAlertAction);
115
+
113
116
  actionExecutor.registerAction(
114
117
  ACTION_TYPES.SEND_CLOUD_EVENT,
115
118
  sendCloudEventAction
@@ -0,0 +1,71 @@
1
+ import { ActionExecutor, ActionResult } from "../ActionExecutor";
2
+
3
+ jest.mock("../../logger", () => ({
4
+ createLogger: () => ({
5
+ log_error: jest.fn(),
6
+ log_debug: jest.fn(),
7
+ log_info: jest.fn(),
8
+ }),
9
+ }));
10
+
11
+ describe("handleActions", () => {
12
+ const executor = () => {
13
+ const ran: string[] = [];
14
+ const actionExecutor = new ActionExecutor();
15
+
16
+ const register = (type: string, result?: ActionResult) =>
17
+ actionExecutor.registerAction(type, async () => {
18
+ ran.push(type);
19
+
20
+ return result as ActionResult;
21
+ });
22
+
23
+ register("ok", ActionResult.Success);
24
+ register("fails", ActionResult.Error);
25
+ register("cancels", ActionResult.Cancel);
26
+ register("recover", ActionResult.Success);
27
+ register("after", ActionResult.Success);
28
+
29
+ return { actionExecutor, ran };
30
+ };
31
+
32
+ // Everything after a failed step was written for a state that never
33
+ // happened: the event was refused, the screen never opened.
34
+ it("stops when an action fails", async () => {
35
+ const { actionExecutor, ran } = executor();
36
+
37
+ const result = await actionExecutor.handleActions([
38
+ { type: "fails" },
39
+ { type: "after" },
40
+ ]);
41
+
42
+ expect(ran).toEqual(["fails"]);
43
+ expect(result).toBe(ActionResult.Error);
44
+ });
45
+
46
+ it("still stops on a cancel", async () => {
47
+ const { actionExecutor, ran } = executor();
48
+
49
+ const result = await actionExecutor.handleActions([
50
+ { type: "cancels" },
51
+ { type: "after" },
52
+ ]);
53
+
54
+ expect(ran).toEqual(["cancels"]);
55
+ expect(result).toBe(ActionResult.Cancel);
56
+ });
57
+
58
+ // An action nobody registered never ran, which is a misconfigured feed
59
+ // rather than a step that failed. It is stepped over, as it always was.
60
+ it("steps over an action with no handler", async () => {
61
+ const { actionExecutor, ran } = executor();
62
+
63
+ const result = await actionExecutor.handleActions([
64
+ { type: "nobody-registered-this" },
65
+ { type: "after" },
66
+ ]);
67
+
68
+ expect(ran).toEqual(["after"]);
69
+ expect(result).toBe(ActionResult.Success);
70
+ });
71
+ });
@@ -0,0 +1,49 @@
1
+ import { showAlertAction } from "../showAlert";
2
+ import { ActionResult } from "../../ActionExecutor";
3
+ import { showAlertDialog } from "../../../alertUtils";
4
+
5
+ jest.mock("../../../alertUtils", () => ({
6
+ showAlertDialog: jest.fn(),
7
+ }));
8
+
9
+ const dialog = showAlertDialog as jest.Mock;
10
+
11
+ const dismiss = () => dialog.mock.calls[0][0].completion();
12
+
13
+ describe("showAlert", () => {
14
+ beforeEach(() => dialog.mockReset());
15
+
16
+ it("shows the notice and resolves once it is dismissed", async () => {
17
+ const result = showAlertAction({
18
+ type: "showAlert",
19
+ options: { title: "Locked", message: "Ask the account owner." },
20
+ });
21
+
22
+ expect(dialog).toHaveBeenCalledWith(
23
+ expect.objectContaining({
24
+ title: "Locked",
25
+ message: "Ask the account owner.",
26
+ okButtonText: "OK",
27
+ })
28
+ );
29
+
30
+ dismiss();
31
+
32
+ // Dismissing a notice is not a cancellation: the chain carries on.
33
+ await expect(result).resolves.toBe(ActionResult.Success);
34
+ });
35
+
36
+ it("takes the dismiss label from the action", async () => {
37
+ const result = showAlertAction({
38
+ type: "showAlert",
39
+ options: { title: "Locked", okButtonText: "Got it" },
40
+ });
41
+
42
+ expect(dialog).toHaveBeenCalledWith(
43
+ expect.objectContaining({ okButtonText: "Got it" })
44
+ );
45
+
46
+ dismiss();
47
+ await result;
48
+ });
49
+ });
@@ -15,6 +15,8 @@ export { appRestartAction } from "./appRestart";
15
15
 
16
16
  export { confirmDialogAction } from "./confirmDialog";
17
17
 
18
+ export { showAlertAction } from "./showAlert";
19
+
18
20
  export { sendCloudEventAction } from "./sendCloudEvent";
19
21
 
20
22
  export { sessionStorageToggleFlagAction } from "./sessionStorageToggleFlag";
@@ -0,0 +1,51 @@
1
+ /// <reference types="@applicaster/applicaster-types" />
2
+ import { showAlertDialog } from "../../alertUtils";
3
+ import { ActionResult } from "../ActionExecutor";
4
+ import { ActionHandler } from "../types";
5
+ import { createLogger } from "../../logger";
6
+
7
+ const { log_info } = createLogger({
8
+ subsystem: "ActionExecutorContext",
9
+ category: "General",
10
+ });
11
+
12
+ /**
13
+ * Options for the showAlert action.
14
+ */
15
+ export interface ShowAlertOptions {
16
+ /** Dialog title (required) */
17
+ title: string;
18
+
19
+ /** Dialog message */
20
+ message?: string;
21
+
22
+ /** Dismiss button text. Defaults to "OK" */
23
+ okButtonText?: string;
24
+ }
25
+
26
+ /**
27
+ * Tells the user something and waits for them to acknowledge it.
28
+ *
29
+ * One button, because there is nothing to decide. Use `confirmDialog` when the
30
+ * user is choosing between going on and backing out; a `confirmDialog` without
31
+ * a cancel label still renders two buttons, the second one blank.
32
+ *
33
+ * Always resolves to Success: dismissing a notice is not a cancellation, so the
34
+ * rest of the chain runs.
35
+ */
36
+ export const showAlertAction: ActionHandler<ShowAlertOptions> = async (
37
+ action
38
+ ): Promise<ActionResult> => {
39
+ log_info("handleAction: showAlert event");
40
+
41
+ const options = action.options as ShowAlertOptions;
42
+
43
+ return new Promise<ActionResult>((resolve) => {
44
+ showAlertDialog({
45
+ title: options?.title,
46
+ message: options?.message,
47
+ okButtonText: options?.okButtonText || "OK",
48
+ completion: () => resolve(ActionResult.Success),
49
+ });
50
+ });
51
+ };
@@ -23,6 +23,7 @@ export const ACTION_TYPES = {
23
23
  SWITCH_LAYOUT: "switchLayout",
24
24
  APP_RESTART: "appRestart",
25
25
  CONFIRM_DIALOG: "confirmDialog",
26
+ SHOW_ALERT: "showAlert",
26
27
  SCREEN_SET_VARIABLE: "screenSetVariable",
27
28
  SCREEN_TOGGLE_FLAG: "screenToggleFlag",
28
29
  } as const;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "16.0.0-rc.89",
3
+ "version": "16.0.0-rc.90",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "homepage": "https://github.com/applicaster/quickbrick#readme",
29
29
  "dependencies": {
30
- "@applicaster/applicaster-types": "16.0.0-rc.89",
30
+ "@applicaster/applicaster-types": "16.0.0-rc.90",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",