@applicaster/zapp-react-native-ui-components 16.0.0-alpha.5652127751 → 16.0.0-alpha.5803191443

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.
@@ -4,6 +4,7 @@ import { TouchableOpacity, ViewStyle } from "react-native";
4
4
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
5
5
 
6
6
  import Image from "./Image";
7
+
7
8
  type Props = {
8
9
  item: ZappEntry | ZappFeed;
9
10
  flavour?: "flavour_1" | "flavour_2";
@@ -12,7 +13,7 @@ type Props = {
12
13
  src: {
13
14
  active: string;
14
15
  inactive: string;
15
- } & Record<string, string>;
16
+ };
16
17
  style: {};
17
18
  type: "Image" | "Svg" | "Lottie";
18
19
  };
@@ -32,6 +33,9 @@ function isStringAsset(asset) {
32
33
  return typeof asset === "string" || Array.isArray(asset);
33
34
  }
34
35
 
36
+ /** return asset based on the asset, flavour, and fallbackAsset
37
+ * usully fallback asset is set on cell configuration, while the asset is configured on action plugin configuration
38
+ */
35
39
  function getAssetValue(asset, flavour, fallbackAsset = null) {
36
40
  if (!asset) {
37
41
  return null;
@@ -60,26 +64,33 @@ export const ActionButton = React.memo(function ActionButtonComponent(
60
64
  typeof actionContext?.isActionAvailable === "function" &&
61
65
  !actionContext.isActionAvailable(item);
62
66
 
67
+ const getActionEntryState = useCallback(() => {
68
+ return actionContext.initialEntryState(item, {
69
+ fallbackAssetSrc: asset?.src,
70
+ });
71
+ // eslint-disable-next-line @wogns3623/better-exhaustive-deps/exhaustive-deps
72
+ }, [actionContext, item?.id, asset?.src]);
73
+
63
74
  // Note: in theory initialization is not needed anymore, we are using useEffect
64
75
  const [actionState, setActionState] = React.useState(
65
- actionDisabled || !actionContext
66
- ? null
67
- : actionContext.initialEntryState(item)
76
+ actionDisabled || !actionContext ? null : getActionEntryState()
68
77
  );
69
78
 
70
79
  useEffect(() => {
71
80
  if (!((actionDisabled || !actionContext) && actionState !== null)) {
72
- setActionState(actionContext.initialEntryState(item));
81
+ setActionState(getActionEntryState());
73
82
  }
74
- }, [actionDisabled, item?.id, actionContext, action, setActionState]);
83
+ // eslint-disable-next-line @wogns3623/better-exhaustive-deps/exhaustive-deps
84
+ }, [actionDisabled, item?.id, actionContext, getActionEntryState]);
75
85
 
76
86
  const onPress = useCallback(() => {
77
87
  actionContext.invokeAction(item, {
78
- updateState: (state) => {
79
- setActionState(state);
88
+ updateState: () => {
89
+ setActionState(getActionEntryState());
80
90
  },
81
91
  });
82
- }, [actionState, actionContext?.state, item?.id]);
92
+ // eslint-disable-next-line @wogns3623/better-exhaustive-deps/exhaustive-deps
93
+ }, [actionContext, item?.id, getActionEntryState]);
83
94
 
84
95
  useEffect(() => {
85
96
  if (typeof actionContext?.addListener === "function") {
@@ -8,7 +8,7 @@ export function ButtonContainerView({
8
8
  children,
9
9
  }: ContainerProps) {
10
10
  return (
11
- <View style={style} pointerEvents="box-none">
11
+ <View style={style}>
12
12
  <View style={contentStyle}>{children}</View>
13
13
  </View>
14
14
  );
@@ -0,0 +1,156 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import { act, fireEvent, render } from "@testing-library/react-native";
4
+ import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
5
+
6
+ import { ActionButton } from "../ActionButton";
7
+
8
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/actions", () => ({
9
+ useActions: jest.fn(),
10
+ }));
11
+
12
+ jest.mock("../Image", () => {
13
+ const React = require("react");
14
+ const { Text } = require("react-native");
15
+
16
+ return {
17
+ __esModule: true,
18
+ default: ({ uri }: { uri: string }) =>
19
+ React.createElement(Text, { testID: "action-button-image" }, uri),
20
+ };
21
+ });
22
+
23
+ const mockUseActions = useActions as jest.Mock;
24
+
25
+ const item = { id: "entry-1" } as ZappEntry;
26
+
27
+ const fallbackAsset = {
28
+ props: {},
29
+ src: {
30
+ active: "cell-active.png",
31
+ inactive: "cell-inactive.png",
32
+ },
33
+ style: {},
34
+ type: "Image" as const,
35
+ };
36
+
37
+ function renderActionButton(actionContext: any, props = {}) {
38
+ mockUseActions.mockReturnValue(actionContext);
39
+
40
+ return render(
41
+ <ActionButton
42
+ item={item}
43
+ action={{ identifier: "test-action" }}
44
+ asset={fallbackAsset}
45
+ flavour="flavour_1"
46
+ style={{ width: 40, height: 40 }}
47
+ testID="action-button"
48
+ {...props}
49
+ />
50
+ );
51
+ }
52
+
53
+ describe("ActionButton", () => {
54
+ beforeEach(() => {
55
+ jest.clearAllMocks();
56
+ });
57
+
58
+ it("passes cell fallback assets to initialEntryState", () => {
59
+ const actionContext = {
60
+ initialEntryState: jest.fn(() => ({ asset: ["action-1", "action-2"] })),
61
+ invokeAction: jest.fn(),
62
+ };
63
+
64
+ renderActionButton(actionContext);
65
+
66
+ expect(actionContext.initialEntryState).toHaveBeenCalledWith(item, {
67
+ fallbackAssetSrc: fallbackAsset.src,
68
+ });
69
+ });
70
+
71
+ it("refreshes entry state when invokeAction calls updateState", () => {
72
+ let stateAsset = ["initial-1.png", "initial-2.png"];
73
+
74
+ const actionContext = {
75
+ initialEntryState: jest.fn(() => ({ asset: stateAsset })),
76
+ invokeAction: jest.fn((_entry, options) => {
77
+ stateAsset = ["recomputed-1.png", "recomputed-2.png"];
78
+ options.updateState({ asset: ["ignored-1.png", "ignored-2.png"] });
79
+ }),
80
+ };
81
+
82
+ const { getByTestId, getByText, queryByText } =
83
+ renderActionButton(actionContext);
84
+
85
+ fireEvent.press(getByTestId("action-button"));
86
+
87
+ expect(actionContext.invokeAction).toHaveBeenCalledWith(
88
+ item,
89
+ expect.objectContaining({ updateState: expect.any(Function) })
90
+ );
91
+
92
+ expect(getByText("recomputed-1.png")).toBeTruthy();
93
+ expect(queryByText("ignored-1.png")).toBeNull();
94
+ });
95
+
96
+ it("updates entry state from addListener", () => {
97
+ let listener: (state: unknown) => void;
98
+
99
+ const actionContext = {
100
+ initialEntryState: jest.fn(() => ({ asset: ["initial-1.png"] })),
101
+ invokeAction: jest.fn(),
102
+ addListener: jest.fn((_entryId, callback) => {
103
+ listener = callback;
104
+
105
+ return jest.fn();
106
+ }),
107
+ };
108
+
109
+ const { getByText } = renderActionButton(actionContext);
110
+
111
+ act(() => {
112
+ listener({ asset: ["listener-1.png"] });
113
+ });
114
+
115
+ expect(actionContext.addListener).toHaveBeenCalledWith(
116
+ String(item.id),
117
+ expect.any(Function)
118
+ );
119
+
120
+ expect(getByText("listener-1.png")).toBeTruthy();
121
+ });
122
+
123
+ it("renders selected flavour from action state asset arrays", () => {
124
+ const actionContext = {
125
+ initialEntryState: jest.fn(() => ({
126
+ asset: ["flavour-1.png", "flavour-2.png"],
127
+ })),
128
+ invokeAction: jest.fn(),
129
+ };
130
+
131
+ const { getByText } = renderActionButton(actionContext, {
132
+ flavour: "flavour_2",
133
+ });
134
+
135
+ expect(getByText("flavour-2.png")).toBeTruthy();
136
+ });
137
+
138
+ it("passes fallback cell asset to component assets", () => {
139
+ const AssetComponent = jest.fn(() => <View testID="component-asset" />);
140
+
141
+ const actionContext = {
142
+ initialEntryState: jest.fn(() => ({ asset: AssetComponent })),
143
+ invokeAction: jest.fn(),
144
+ };
145
+
146
+ renderActionButton(actionContext);
147
+
148
+ expect(AssetComponent).toHaveBeenCalledWith(
149
+ expect.objectContaining({
150
+ asset: fallbackAsset.src,
151
+ flavour: "flavour_1",
152
+ }),
153
+ expect.anything()
154
+ );
155
+ });
156
+ });
@@ -624,7 +624,48 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
624
624
  return this._preparePromise;
625
625
  }
626
626
 
627
- this._preparePromise = this.createPreparedPlayer()
627
+ this._preparePromise = (async (): Promise<boolean> => {
628
+ // 1. Run hooks if configured
629
+ let entry = this.factoryConfig.entry;
630
+
631
+ if (this.preloadHooks?.length) {
632
+ const result = await executePreloadHooks({
633
+ preloadHooks: this.preloadHooks,
634
+ entry,
635
+ });
636
+
637
+ if (result) {
638
+ this.processedEntry = result;
639
+ entry = result;
640
+ } else {
641
+ return false;
642
+ }
643
+ }
644
+
645
+ // 2. Create the player with the correct entry
646
+ const factoryItem = playerFactory({
647
+ player: this.factoryConfig.player,
648
+ playerId: this.factoryConfig.playerId,
649
+ autoplay: false,
650
+ entry,
651
+ muted: this.initiallyMuted,
652
+ playerPluginId: this.factoryConfig.playerPluginId,
653
+ screenConfig: this.factoryConfig.screenConfig,
654
+ playerRole: PlayerRole.Cell,
655
+ });
656
+
657
+ if (!factoryItem) {
658
+ throw new Error("Player factory returned null");
659
+ }
660
+
661
+ this.player = factoryItem.controller;
662
+ this.component = factoryItem.Component;
663
+
664
+ // 3. Register callbacks — player now exists
665
+ this.player.addListener({ id: "live-image", listener: this });
666
+
667
+ return true;
668
+ })()
628
669
  .then((result) => {
629
670
  this._preparePromise = null;
630
671
 
@@ -644,45 +685,6 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
644
685
  return this._preparePromise;
645
686
  }
646
687
 
647
- private async createPreparedPlayer(): Promise<boolean> {
648
- let entry = this.factoryConfig.entry;
649
-
650
- if (this.preloadHooks?.length) {
651
- const result = await executePreloadHooks({
652
- preloadHooks: this.preloadHooks,
653
- entry,
654
- });
655
-
656
- if (!result) return false;
657
-
658
- this.processedEntry = result;
659
- entry = result;
660
- }
661
-
662
- const factoryItem = await playerFactory({
663
- player: this.factoryConfig.player,
664
- playerId: this.factoryConfig.playerId,
665
- autoplay: false,
666
- entry,
667
- muted: this.initiallyMuted,
668
- playerPluginId: this.factoryConfig.playerPluginId,
669
- screenConfig: this.factoryConfig.screenConfig,
670
- playerRole: PlayerRole.Cell,
671
- });
672
-
673
- if (!factoryItem) {
674
- throw new Error(
675
- `Player factory returned null (playerId: ${this.factoryConfig.playerId}, playerPluginId: ${this.factoryConfig.playerPluginId})`
676
- );
677
- }
678
-
679
- this.player = factoryItem.controller;
680
- this.component = factoryItem.Component;
681
- this.player.addListener({ id: "live-image", listener: this });
682
-
683
- return true;
684
- }
685
-
686
688
  public getPlayer = (): Player | null => {
687
689
  return this.player;
688
690
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "16.0.0-alpha.5652127751",
3
+ "version": "16.0.0-alpha.5803191443",
4
4
  "description": "Applicaster Zapp React Native ui components for the Quick Brick App",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,10 +28,10 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "16.0.0-alpha.5652127751",
32
- "@applicaster/zapp-react-native-bridge": "16.0.0-alpha.5652127751",
33
- "@applicaster/zapp-react-native-redux": "16.0.0-alpha.5652127751",
34
- "@applicaster/zapp-react-native-utils": "16.0.0-alpha.5652127751",
31
+ "@applicaster/applicaster-types": "16.0.0-alpha.5803191443",
32
+ "@applicaster/zapp-react-native-bridge": "16.0.0-alpha.5803191443",
33
+ "@applicaster/zapp-react-native-redux": "16.0.0-alpha.5803191443",
34
+ "@applicaster/zapp-react-native-utils": "16.0.0-alpha.5803191443",
35
35
  "fast-json-stable-stringify": "^2.1.0",
36
36
  "promise": "^8.3.0",
37
37
  "url": "^0.11.0",