@applicaster/zapp-react-native-utils 16.0.0-alpha.9183418480 → 16.0.0-alpha.9530606740

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.
Files changed (84) hide show
  1. package/actionsExecutor/ActionExecutor.ts +8 -10
  2. package/actionsExecutor/ActionExecutorContext.tsx +43 -326
  3. package/actionsExecutor/actions/appRestart.ts +24 -0
  4. package/actionsExecutor/actions/confirmDialog.ts +53 -0
  5. package/actionsExecutor/actions/index.ts +30 -0
  6. package/actionsExecutor/actions/localStorageRemove.ts +27 -0
  7. package/actionsExecutor/actions/localStorageSet.ts +28 -0
  8. package/actionsExecutor/actions/localStorageToggleFlag.ts +28 -0
  9. package/actionsExecutor/actions/navigateToScreen.ts +123 -0
  10. package/actionsExecutor/actions/openBottomSheet.ts +177 -0
  11. package/actionsExecutor/actions/refreshComponent.ts +79 -0
  12. package/actionsExecutor/actions/screenSetVariable.ts +35 -0
  13. package/actionsExecutor/actions/screenToggleFlag.ts +38 -0
  14. package/actionsExecutor/actions/sendCloudEvent.ts +88 -0
  15. package/actionsExecutor/actions/sessionStorageRemove.ts +19 -0
  16. package/actionsExecutor/actions/sessionStorageSet.ts +20 -0
  17. package/actionsExecutor/actions/sessionStorageToggleFlag.ts +15 -0
  18. package/actionsExecutor/actions/switchLayout.ts +40 -0
  19. package/actionsExecutor/types.ts +59 -0
  20. package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -2
  21. package/analyticsUtils/PlayerAnalyticsManager.ts +11 -1
  22. package/analyticsUtils/__tests__/analyticsMapper.test.ts +35 -0
  23. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testACP_events.json +1 -1
  24. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testBlockUnlistedParams_rules.json +1 -1
  25. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEmptyRenameKeepsName_events.json +4 -0
  26. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEmptyRenameKeepsName_rules.json +6 -0
  27. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEventRegex_events.json +3 -9
  28. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testIgnoreOrdering_events.json +18 -0
  29. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testIgnoreOrdering_rules.json +16 -0
  30. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexEventNoFallback_events.json +4 -0
  31. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexEventNoFallback_rules.json +6 -0
  32. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexMultiGroupRename_events.json +4 -0
  33. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexMultiGroupRename_rules.json +6 -0
  34. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexNonNamedFullMatch_events.json +4 -0
  35. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexNonNamedFullMatch_rules.json +6 -0
  36. package/analyticsUtils/__tests__/fixtures/index.js +20 -0
  37. package/analyticsUtils/analyticsMapper.ts +4 -1
  38. package/analyticsUtils/playerAnalyticsTracker.ts +26 -3
  39. package/appUtils/HooksManager/index.ts +3 -8
  40. package/appUtils/contextKeysManager/__tests__/getKey/failure.test.ts +1 -1
  41. package/appUtils/contextKeysManager/__tests__/removeKey/failure.test.ts +1 -1
  42. package/appUtils/contextKeysManager/__tests__/setKey/failure/invalidKey.test.ts +4 -4
  43. package/appUtils/contextKeysManager/index.ts +1 -1
  44. package/appUtils/contextKeysManager/utils/index.ts +38 -25
  45. package/cellUtils/index.ts +3 -5
  46. package/colorUtils/__tests__/isTransparentColor.test.ts +76 -0
  47. package/colorUtils/__tests__/isValidColor.test.ts +70 -0
  48. package/colorUtils/index.ts +50 -0
  49. package/manifestUtils/_internals/index.js +6 -0
  50. package/manifestUtils/defaultManifestConfigurations/generalContent.js +35 -0
  51. package/manifestUtils/fieldUtils/__tests__/fieldUtils.test.js +84 -0
  52. package/manifestUtils/fieldUtils/index.js +125 -0
  53. package/manifestUtils/keys.js +9 -0
  54. package/manifestUtils/mobileAction/button/index.js +16 -0
  55. package/manifestUtils/mobileAction/container/index.js +3 -1
  56. package/manifestUtils/mobileAction/groups/defaults.js +3 -1
  57. package/manifestUtils/platformIsTV.js +1 -0
  58. package/modalState/ContentViewModel.ts +59 -0
  59. package/modalState/ModalOrchestrator.ts +204 -0
  60. package/modalState/__tests__/ContentViewModel.test.ts +107 -0
  61. package/modalState/components/ActionItem.tsx +155 -0
  62. package/modalState/components/BottomSheetHeader.tsx +245 -0
  63. package/modalState/components/PrimaryButton.tsx +54 -0
  64. package/modalState/components/TrackItem.tsx +241 -0
  65. package/modalState/index.ts +25 -74
  66. package/modalState/store.ts +102 -0
  67. package/modalState/types.ts +55 -0
  68. package/package.json +2 -2
  69. package/pipesUtils/__tests__/buildUrlWithQuery.test.ts +33 -0
  70. package/pipesUtils/__tests__/withPipesEndpoint.test.tsx +56 -0
  71. package/pipesUtils/index.ts +1 -0
  72. package/pipesUtils/withPipesEndpoint.tsx +54 -0
  73. package/playerUtils/index.ts +4 -2
  74. package/reactHooks/actions/index.ts +51 -1
  75. package/reactHooks/cell-click/index.ts +18 -10
  76. package/reactHooks/feed/__mocks__/useMarkPipesDataStale.ts +4 -0
  77. package/reactHooks/feed/index.ts +5 -1
  78. package/reactHooks/feed/useBatchLoading.ts +9 -1
  79. package/reactHooks/feed/{usePipesCacheReset.ts → useMarkPipesDataStale.ts} +12 -4
  80. package/reactHooks/utils/index.ts +3 -2
  81. package/reactHooks/videoModal/hooks/useVideoModalScreenData.tsx +22 -4
  82. package/riverComponetsMeasurementProvider/index.tsx +12 -8
  83. package/uiActionsRegistrator/index.ts +203 -0
  84. package/reactHooks/feed/__mocks__/usePipesCacheReset.ts +0 -1
@@ -0,0 +1,54 @@
1
+ import React from "react";
2
+ import { Text, StyleSheet, Pressable } from "react-native";
3
+
4
+ const styles = StyleSheet.create({
5
+ primaryButton: {
6
+ flexDirection: "row",
7
+ backgroundColor: "#FFFFFF",
8
+ borderRadius: 12,
9
+ padding: 12,
10
+ marginHorizontal: 16,
11
+ alignItems: "center",
12
+ justifyContent: "center",
13
+ marginTop: 16,
14
+ marginBottom: 8,
15
+ },
16
+ primaryButtonText: {
17
+ color: "#000000",
18
+ fontSize: 15,
19
+ fontWeight: "bold",
20
+ },
21
+ primaryButtonIcon: {
22
+ color: "#000000",
23
+ fontSize: 18,
24
+ marginRight: 8,
25
+ },
26
+ pressedState: {
27
+ opacity: 0.8,
28
+ },
29
+ });
30
+
31
+ export interface PrimaryButtonProps {
32
+ title: string;
33
+ icon?: string;
34
+ onPress?: () => void;
35
+ }
36
+
37
+ export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
38
+ title,
39
+ icon,
40
+ onPress,
41
+ }) => {
42
+ return (
43
+ <Pressable
44
+ style={({ pressed }) => [
45
+ styles.primaryButton,
46
+ pressed && styles.pressedState,
47
+ ]}
48
+ onPress={onPress}
49
+ >
50
+ {icon ? <Text style={styles.primaryButtonIcon}>{icon}</Text> : null}
51
+ <Text style={styles.primaryButtonText}>{title}</Text>
52
+ </Pressable>
53
+ );
54
+ };
@@ -0,0 +1,241 @@
1
+ import React, { useState } from "react";
2
+ import { View, Text, StyleSheet, Pressable, Image } from "react-native";
3
+
4
+ const styles = StyleSheet.create({
5
+ trackItem: {
6
+ flexDirection: "row",
7
+ alignItems: "center",
8
+ paddingVertical: 10,
9
+ paddingHorizontal: 12,
10
+ borderBottomWidth: 1,
11
+ borderBottomColor: "#2a2a2a",
12
+ backgroundColor: "#1e1e1e",
13
+ },
14
+ trackItemFocused: {
15
+ backgroundColor: "#2d2d2d",
16
+ },
17
+ trackImage: {
18
+ width: 48,
19
+ height: 48,
20
+ borderRadius: 6,
21
+ marginRight: 12,
22
+ backgroundColor: "#2a2a2a",
23
+ },
24
+ trackImagePlaceholder: {
25
+ width: 48,
26
+ height: 48,
27
+ borderRadius: 6,
28
+ borderWidth: 1,
29
+ borderColor: "rgba(255, 255, 255, 0.1)",
30
+ backgroundColor: "#333",
31
+ marginRight: 12,
32
+ alignItems: "center",
33
+ justifyContent: "center",
34
+ },
35
+ trackImageText: {
36
+ color: "#fff",
37
+ fontSize: 20,
38
+ },
39
+ trackTextContainer: {
40
+ flex: 1,
41
+ justifyContent: "center",
42
+ },
43
+ nowPlayingLabel: {
44
+ color: "#30d158",
45
+ fontSize: 11,
46
+ fontWeight: "600",
47
+ marginBottom: 2,
48
+ textTransform: "uppercase",
49
+ },
50
+ trackTitle: {
51
+ color: "#FFFFFF",
52
+ fontSize: 15,
53
+ fontWeight: "600",
54
+ },
55
+ trackSubtitle: {
56
+ color: "#999999",
57
+ fontSize: 13,
58
+ fontWeight: "500",
59
+ marginTop: 2,
60
+ },
61
+ trackActions: {
62
+ flexDirection: "row",
63
+ alignItems: "center",
64
+ },
65
+ queuedActionsContainer: {
66
+ flexDirection: "row",
67
+ alignItems: "center",
68
+ },
69
+ trackActionButton: {
70
+ padding: 10,
71
+ marginLeft: 4,
72
+ },
73
+ trackActionIcon: {
74
+ color: "#FFFFFF",
75
+ fontSize: 18,
76
+ },
77
+ playPauseButton: {
78
+ width: 36,
79
+ height: 36,
80
+ backgroundColor: "#FFFFFF",
81
+ borderRadius: 18,
82
+ alignItems: "center",
83
+ justifyContent: "center",
84
+ marginLeft: 8,
85
+ },
86
+ playPauseText: {
87
+ color: "#000000",
88
+ fontWeight: "bold",
89
+ fontSize: 12,
90
+ },
91
+ radioContainer: {
92
+ padding: 10,
93
+ },
94
+ radioUnselected: {
95
+ width: 24,
96
+ height: 24,
97
+ borderRadius: 12,
98
+ borderWidth: 1,
99
+ borderColor: "#999",
100
+ },
101
+ radioSelected: {
102
+ width: 24,
103
+ height: 24,
104
+ borderRadius: 12,
105
+ backgroundColor: "#FFFFFF",
106
+ alignItems: "center",
107
+ justifyContent: "center",
108
+ },
109
+ radioCheck: {
110
+ color: "#000000",
111
+ fontSize: 14,
112
+ fontWeight: "bold",
113
+ },
114
+ pressedState: {
115
+ opacity: 0.7,
116
+ },
117
+ });
118
+
119
+ export interface TrackItemProps {
120
+ variant: "nowPlaying" | "queued" | "playlist";
121
+ title: string;
122
+ subtitle?: string;
123
+ icon?: string;
124
+ isSelected?: boolean;
125
+ isPlaying?: boolean;
126
+ onPress?: () => void;
127
+ onSecondaryPress?: () => void;
128
+ onRemovePress?: () => void;
129
+ onPlayPausePress?: () => void;
130
+ }
131
+
132
+ export const TrackItem: React.FC<TrackItemProps> = ({
133
+ variant,
134
+ title,
135
+ subtitle,
136
+ icon,
137
+ isSelected = false,
138
+ isPlaying = false,
139
+ onPress,
140
+ onSecondaryPress,
141
+ onRemovePress,
142
+ onPlayPausePress,
143
+ }) => {
144
+ const [isFocused, setIsFocused] = useState(false);
145
+
146
+ const isIconUri =
147
+ icon &&
148
+ (icon.startsWith("http") ||
149
+ icon.startsWith("file") ||
150
+ icon.includes("/") ||
151
+ icon.includes("."));
152
+
153
+ return (
154
+ <Pressable
155
+ style={({ pressed }) => [
156
+ styles.trackItem,
157
+ (pressed || isFocused) && styles.trackItemFocused,
158
+ ]}
159
+ onPress={onPress}
160
+ onPressIn={() => setIsFocused(true)}
161
+ onPressOut={() => setIsFocused(false)}
162
+ >
163
+ {icon ? (
164
+ isIconUri ? (
165
+ <Image source={{ uri: icon }} style={styles.trackImage} />
166
+ ) : (
167
+ <View style={styles.trackImagePlaceholder}>
168
+ <Text style={styles.trackImageText}>{icon}</Text>
169
+ </View>
170
+ )
171
+ ) : (
172
+ <View style={styles.trackImagePlaceholder} />
173
+ )}
174
+
175
+ <View style={styles.trackTextContainer}>
176
+ {variant === "nowPlaying" ? (
177
+ <Text style={styles.nowPlayingLabel}>♪ Now Playing</Text>
178
+ ) : null}
179
+ <Text numberOfLines={1} style={styles.trackTitle}>
180
+ {title}
181
+ </Text>
182
+ {subtitle ? (
183
+ <Text numberOfLines={1} style={styles.trackSubtitle}>
184
+ {subtitle}
185
+ </Text>
186
+ ) : null}
187
+ </View>
188
+
189
+ <View style={styles.trackActions}>
190
+ {variant === "nowPlaying" ? (
191
+ <Pressable
192
+ style={({ pressed }) => [
193
+ styles.playPauseButton,
194
+ pressed && styles.pressedState,
195
+ ]}
196
+ onPress={onPlayPausePress || onPress}
197
+ >
198
+ <Text style={styles.playPauseText}>{isPlaying ? "II" : "▶"}</Text>
199
+ </Pressable>
200
+ ) : null}
201
+
202
+ {variant === "queued" ? (
203
+ <View style={styles.queuedActionsContainer}>
204
+ {onRemovePress ? (
205
+ <Pressable
206
+ style={({ pressed }) => [
207
+ styles.trackActionButton,
208
+ pressed && styles.pressedState,
209
+ ]}
210
+ onPress={onRemovePress}
211
+ >
212
+ <Text style={styles.trackActionIcon}>⊖</Text>
213
+ </Pressable>
214
+ ) : null}
215
+ <Pressable
216
+ style={({ pressed }) => [
217
+ styles.trackActionButton,
218
+ pressed && styles.pressedState,
219
+ ]}
220
+ onPress={onSecondaryPress || onPress}
221
+ >
222
+ <Text style={styles.trackActionIcon}>≡</Text>
223
+ </Pressable>
224
+ </View>
225
+ ) : null}
226
+
227
+ {variant === "playlist" ? (
228
+ <View style={styles.radioContainer}>
229
+ {isSelected ? (
230
+ <View style={styles.radioSelected}>
231
+ <Text style={styles.radioCheck}>✓</Text>
232
+ </View>
233
+ ) : (
234
+ <View style={styles.radioUnselected} />
235
+ )}
236
+ </View>
237
+ ) : null}
238
+ </View>
239
+ </Pressable>
240
+ );
241
+ };
@@ -1,83 +1,34 @@
1
- import { useStore, createStore } from "zustand";
1
+ export {
2
+ modalStore,
3
+ useModalStore,
4
+ openModal,
5
+ dismissModal,
6
+ openBottomSheetModal,
7
+ useModalStoreState,
8
+ } from "./store";
2
9
 
3
- interface ModalState {
4
- visible: boolean;
5
- screen: any;
6
- options: Record<string, unknown>;
7
- props: Record<string, unknown>;
8
- }
10
+ export type { ModalState, ModalStore } from "./store";
9
11
 
10
- interface ModalStore {
11
- modalState: ModalState;
12
- setModalState: (newState: Partial<ModalState>) => void;
13
- openModal: (
14
- item: any,
15
- options?: Record<string, unknown>,
16
- props?: Record<string, unknown>
17
- ) => void;
18
- dismissModal: () => void;
19
- }
12
+ export { modalOrchestrator } from "./ModalOrchestrator";
20
13
 
21
- const initialModalState: ModalState = {
22
- visible: false,
23
- screen: null,
24
- options: {},
25
- props: {},
26
- };
14
+ export { ContentViewModel } from "./ContentViewModel";
27
15
 
28
- export const modalStore = createStore<ModalStore>((set) => ({
29
- modalState: initialModalState,
30
- setModalState: (newState) =>
31
- set((state) => ({
32
- modalState: { ...state.modalState, ...newState },
33
- })),
34
- openModal: ({ item, options = {}, props }: OpenModalArg) =>
35
- set({
36
- modalState: {
37
- visible: true,
38
- screen: item,
39
- options: {
40
- animated: true,
41
- animationType: "slide",
42
- onShow: () => {},
43
- presentationStyle: "overFullScreen",
44
- transparent: true,
45
- ...options,
46
- onDismiss: () => set({ modalState: initialModalState }),
47
- onRequestClose: () => set({ modalState: initialModalState }),
48
- },
49
- props,
50
- },
51
- }),
52
- dismissModal: () => set(() => ({ modalState: initialModalState })),
53
- }));
16
+ export { BottomSheetHeader } from "./components/BottomSheetHeader";
54
17
 
55
- export const useModalStore = <T>(selector: (state: ModalStore) => T) =>
56
- useStore(modalStore, selector);
18
+ export { ActionItem } from "./components/ActionItem";
57
19
 
58
- export const openModal = modalStore.getInitialState().openModal;
20
+ export { TrackItem } from "./components/TrackItem";
59
21
 
60
- export const dismissModal = modalStore.getInitialState().dismissModal;
22
+ export { PrimaryButton } from "./components/PrimaryButton";
61
23
 
62
- export const openBottomSheetModal = ({
63
- ModalBottomSheetContent,
64
- modalBottomSheetContentProps,
65
- props = {},
66
- options = {},
67
- }: OpenModalBottomSheetArgs) => {
68
- openModal({
69
- item: {
70
- ModalBottomSheetContent,
71
- modalBottomSheetContentProps,
72
- },
73
- props,
74
- options: {
75
- animationType: "none",
76
- animated: false,
77
- ...options,
78
- },
79
- });
80
- };
24
+ export type {
25
+ Menu,
26
+ MenuItem,
27
+ Header,
28
+ Content,
29
+ Action,
30
+ PresentSubMenuAction,
31
+ SecondaryAction,
32
+ } from "./types";
81
33
 
82
- export const useModalStoreState = (): ModalState =>
83
- useModalStore<ModalState>((state) => state.modalState);
34
+ export { isPresentSubMenuAction } from "./types";
@@ -0,0 +1,102 @@
1
+ import { createStore, useStore } from "zustand";
2
+
3
+ export interface ModalState {
4
+ visible: boolean;
5
+ screen: any;
6
+ options: Record<string, unknown>;
7
+ props: Record<string, unknown>;
8
+ }
9
+
10
+ export interface ModalStore {
11
+ modalState: ModalState;
12
+ setModalState: (newState: Partial<ModalState>) => void;
13
+ openModal: (
14
+ item: any,
15
+ options?: Record<string, unknown>,
16
+ props?: Record<string, unknown>
17
+ ) => void;
18
+ dismissModal: () => void;
19
+ }
20
+
21
+ const initialModalState: ModalState = {
22
+ visible: false,
23
+ screen: null,
24
+ options: {},
25
+ props: {},
26
+ };
27
+
28
+ export const modalStore = createStore<ModalStore>((set, get) => ({
29
+ modalState: initialModalState,
30
+ setModalState: (newState) =>
31
+ set((state) => ({
32
+ modalState: { ...state.modalState, ...newState },
33
+ })),
34
+ openModal: ({ item, options = {}, props }: OpenModalArg) => {
35
+ const resetStore = () => set({ modalState: initialModalState });
36
+ const { onDismiss, onRequestClose, ...restOptions } = options as any;
37
+
38
+ set({
39
+ modalState: {
40
+ visible: true,
41
+ screen: item,
42
+ options: {
43
+ animated: true,
44
+ animationType: "slide",
45
+ onShow: () => {},
46
+ presentationStyle: "overFullScreen",
47
+ transparent: true,
48
+ ...restOptions,
49
+ // Compose caller-provided callbacks with the store reset so both run
50
+ onDismiss: () => {
51
+ resetStore();
52
+ onDismiss?.();
53
+ },
54
+ onRequestClose: () => {
55
+ resetStore();
56
+ onRequestClose?.();
57
+ },
58
+ },
59
+ props,
60
+ },
61
+ });
62
+ },
63
+ dismissModal: () => {
64
+ const onDismiss = (get().modalState?.options as any)?.onDismiss;
65
+
66
+ if (typeof onDismiss === "function") {
67
+ onDismiss();
68
+ } else {
69
+ set({ modalState: initialModalState });
70
+ }
71
+ },
72
+ }));
73
+
74
+ export const useModalStore = <T>(selector: (state: ModalStore) => T) =>
75
+ useStore(modalStore, selector);
76
+
77
+ export const openModal = modalStore.getInitialState().openModal;
78
+
79
+ export const dismissModal = modalStore.getInitialState().dismissModal;
80
+
81
+ export const openBottomSheetModal = ({
82
+ ModalBottomSheetContent,
83
+ modalBottomSheetContentProps,
84
+ props = {},
85
+ options = {},
86
+ }: OpenModalBottomSheetArgs) => {
87
+ openModal({
88
+ item: {
89
+ ModalBottomSheetContent,
90
+ modalBottomSheetContentProps,
91
+ },
92
+ props,
93
+ options: {
94
+ animationType: "none",
95
+ animated: false,
96
+ ...options,
97
+ },
98
+ });
99
+ };
100
+
101
+ export const useModalStoreState = (): ModalState =>
102
+ useModalStore<ModalState>((state) => state.modalState);
@@ -0,0 +1,55 @@
1
+ export interface Header {
2
+ title: string;
3
+ subtitle?: string;
4
+ icon?: string;
5
+ }
6
+
7
+ export interface Action {
8
+ type: string;
9
+ payload?: any;
10
+ options?: any;
11
+ }
12
+
13
+ // Represented as 3 dots icon on top menu (opens new second level menu on desktop
14
+ // or a new bottom sheet on mobile)
15
+ export interface PresentSubMenuAction extends Action {
16
+ menu: Menu;
17
+ }
18
+
19
+ export interface SecondaryAction {
20
+ action: Action | PresentSubMenuAction;
21
+ icon?: string;
22
+ }
23
+
24
+ export interface MenuItem {
25
+ title: string;
26
+ summary?: string;
27
+ icon?: string;
28
+ isSelected?: boolean;
29
+ // When item represents an audio item, clicking on it will start playback
30
+ // and secondary action will allow to add it to queue or playlist
31
+ action?: Action | PresentSubMenuAction;
32
+ secondaryAction?: SecondaryAction;
33
+ submenu?: MenuItem[]; // todo: not needed. Must be replaced with PresentSubMenuAction
34
+ trailingButton?: any;
35
+ }
36
+
37
+ export interface Content {
38
+ title: string;
39
+ stickyTitle?: boolean;
40
+ items: MenuItem[];
41
+ itemsUrl?: string;
42
+ action?: Action;
43
+ }
44
+
45
+ export interface Menu {
46
+ header?: Header;
47
+ content: Content;
48
+ closeButton?: boolean;
49
+ }
50
+
51
+ export function isPresentSubMenuAction(
52
+ action: any
53
+ ): action is PresentSubMenuAction {
54
+ return !!action && typeof action === "object" && "menu" in action;
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "16.0.0-alpha.9183418480",
3
+ "version": "16.0.0-alpha.9530606740",
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-alpha.9183418480",
30
+ "@applicaster/applicaster-types": "16.0.0-alpha.9530606740",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -0,0 +1,33 @@
1
+ import { buildUrlWithQuery } from "../withPipesEndpoint";
2
+
3
+ describe("buildUrlWithQuery", () => {
4
+ it("returns the url unchanged when requestParams is null", () => {
5
+ expect(buildUrlWithQuery("https://foo.com/path", null)).toBe(
6
+ "https://foo.com/path"
7
+ );
8
+ });
9
+
10
+ it("returns the url unchanged when requestParams has no params", () => {
11
+ expect(buildUrlWithQuery("https://foo.com/path", { headers: {} })).toBe(
12
+ "https://foo.com/path"
13
+ );
14
+ });
15
+
16
+ it("returns the url unchanged when url is empty", () => {
17
+ expect(buildUrlWithQuery("", { params: { a: "1" } })).toBe("");
18
+ });
19
+
20
+ it("appends params to a url with no existing query", () => {
21
+ expect(
22
+ buildUrlWithQuery("https://foo.com/path", { params: { token: "abc" } })
23
+ ).toBe("https://foo.com/path?token=abc");
24
+ });
25
+
26
+ it("merges params with an existing query string", () => {
27
+ expect(
28
+ buildUrlWithQuery("https://foo.com/path?existing=1", {
29
+ params: { token: "abc" },
30
+ })
31
+ ).toBe("https://foo.com/path?existing=1&token=abc");
32
+ });
33
+ });
@@ -0,0 +1,56 @@
1
+ import * as React from "react";
2
+ import { render } from "@testing-library/react-native";
3
+ import { Text } from "react-native";
4
+
5
+ const mockedUseBuildPipesUrl = jest.fn();
6
+
7
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/feed", () => ({
8
+ useBuildPipesUrl: (args) => mockedUseBuildPipesUrl(args),
9
+ }));
10
+
11
+ const { withPipesEndpoint } = require("../withPipesEndpoint");
12
+
13
+ // eslint-disable-next-line react/display-name
14
+ const Wrapped = React.forwardRef((props: any, _ref) => (
15
+ <Text testID="wrapped">{`${props.uri}|${JSON.stringify(props.headers)}`}</Text>
16
+ ));
17
+
18
+ describe("withPipesEndpoint", () => {
19
+ afterEach(() => jest.clearAllMocks());
20
+
21
+ it("renders nothing while the endpoint context is resolving", () => {
22
+ mockedUseBuildPipesUrl.mockReturnValue({ requestParams: null });
23
+
24
+ const Decorated = withPipesEndpoint(Wrapped);
25
+ const { queryByTestId } = render(<Decorated uri="https://foo.com" />);
26
+
27
+ expect(queryByTestId("wrapped")).toBeNull();
28
+ });
29
+
30
+ it("passes the uri through unchanged when there are no params", () => {
31
+ mockedUseBuildPipesUrl.mockReturnValue({ requestParams: {} });
32
+
33
+ const Decorated = withPipesEndpoint(Wrapped);
34
+ const { getByTestId } = render(<Decorated uri="https://foo.com/path" />);
35
+
36
+ expect(getByTestId("wrapped").props.children).toBe(
37
+ "https://foo.com/path|{}"
38
+ );
39
+ });
40
+
41
+ it("merges resolved params into the uri and forwards headers", () => {
42
+ mockedUseBuildPipesUrl.mockReturnValue({
43
+ requestParams: {
44
+ params: { token: "abc" },
45
+ headers: { Authorization: "Bearer xyz" },
46
+ },
47
+ });
48
+
49
+ const Decorated = withPipesEndpoint(Wrapped);
50
+ const { getByTestId } = render(<Decorated uri="https://foo.com/path" />);
51
+
52
+ expect(getByTestId("wrapped").props.children).toBe(
53
+ 'https://foo.com/path?token=abc|{"Authorization":"Bearer xyz"}'
54
+ );
55
+ });
56
+ });
@@ -0,0 +1 @@
1
+ export { withPipesEndpoint, buildUrlWithQuery } from "./withPipesEndpoint";