@applicaster/zapp-react-native-utils 12.0.0-alpha.4102526423 → 12.0.0-alpha.4173603919

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.
@@ -85,18 +85,7 @@ export class AccessibilityManager {
85
85
  textToRead = localizedMessage;
86
86
  }
87
87
 
88
- if (this.ttsManager) {
89
- logger.debug({
90
- message: `Reading text: ${textToRead}`,
91
- data: {
92
- textToRead,
93
- text,
94
- keyOfLocalizedText,
95
- },
96
- });
97
-
98
- this.ttsManager.readText(textToRead);
99
- }
88
+ this.ttsManager && this.ttsManager.readText(textToRead);
100
89
 
101
90
  this.announcements$.next({
102
91
  message: textToRead,
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable unused-imports/no-unused-vars */
2
2
  import * as R from "ramda";
3
+ import { invariant } from "@applicaster/zapp-react-native-utils/errorUtils";
3
4
 
4
5
  /**
5
6
  * shifts an array by the given offset. a negative offset will take items
@@ -100,3 +101,13 @@ export const makeListOf = (value: unknown, size: number): number[] => {
100
101
  export function isFilledArray(value: unknown): boolean {
101
102
  return R.is(Array, value) && R.length(value) > 0;
102
103
  }
104
+
105
+ // get random item from the list
106
+ export const sample = (xs: unknown[]): unknown => {
107
+ invariant(R.is(Array, xs), `input value is not a array: ${xs}`);
108
+ invariant(isFilledArray(xs), `input array is empty: ${xs}`);
109
+
110
+ const index = Math.floor(Math.random() * xs.length);
111
+
112
+ return xs[index];
113
+ };
@@ -18,6 +18,7 @@ class FocusManager {
18
18
  private static instance: FocusManager;
19
19
 
20
20
  private _focusedId: string | null = null;
21
+ private _prevFocusedId: string | null = null;
21
22
  public previousNavigationDirection: FocusManager.Android.NavDir = null;
22
23
 
23
24
  /**
@@ -47,10 +48,22 @@ class FocusManager {
47
48
  return focusedRef?.current;
48
49
  }
49
50
 
51
+ get prevFocused() {
52
+ const focusedRef = this.prevFocusedId
53
+ ? FocusManager.findFocusable(this.prevFocusedId)
54
+ : { current: null };
55
+
56
+ return focusedRef?.current;
57
+ }
58
+
50
59
  get focusedId() {
51
60
  return FocusManager.instance._focusedId;
52
61
  }
53
62
 
63
+ get prevFocusedId() {
64
+ return FocusManager.instance._prevFocusedId;
65
+ }
66
+
54
67
  public static getInstance(): FocusManager {
55
68
  if (!FocusManager.instance) {
56
69
  FocusManager.instance = new FocusManager();
@@ -151,9 +164,8 @@ class FocusManager {
151
164
  }
152
165
 
153
166
  private setFocusLocal(nextFocus: FocusManager.TouchableReactRef) {
167
+ FocusManager.instance._prevFocusedId = FocusManager.instance._focusedId;
154
168
  FocusManager.instance._focusedId = nextFocus?.current?.props?.id ?? null;
155
-
156
- this.eventHandler?.invokeHandler?.(FOCUS_EVENTS.FOCUS, nextFocus);
157
169
  }
158
170
 
159
171
  private setPreviousNavigationDirection(
@@ -217,8 +229,11 @@ class FocusManager {
217
229
 
218
230
  FocusManager.instance.setFocus(nextNextFocus, options);
219
231
  } else {
220
- FocusManager.instance.blurPrevious(options);
221
232
  FocusManager.instance.setFocusLocal(nextFocus);
233
+
234
+ FocusManager.instance.blurPrevious(options);
235
+ this.eventHandler?.invokeHandler?.(FOCUS_EVENTS.FOCUS, nextFocus);
236
+
222
237
  FocusManager.instance.setPreviousNavigationDirection(options ?? null);
223
238
 
224
239
  nextFocus?.current?.onFocus?.(nextFocus.current, options ?? {});
@@ -227,8 +242,8 @@ class FocusManager {
227
242
 
228
243
  blurPrevious(options?: FocusManager.Android.CallbackOptions) {
229
244
  if (options) {
230
- FocusManager.instance.focused?.onBlur?.(
231
- FocusManager.instance.focused,
245
+ FocusManager.instance.prevFocused?.onBlur?.(
246
+ FocusManager.instance.prevFocused,
232
247
  options
233
248
  );
234
249
  }
@@ -156,7 +156,6 @@ describe("FocusManager", () => {
156
156
  child1.current.onBlur.mockClear();
157
157
  focusManager.setFocus(child1.current.props.id);
158
158
  focusManager.blurPrevious({ direction: "down" });
159
- expect(child1.current.onBlur).toBeCalled();
160
159
  });
161
160
 
162
161
  it("focusManager - focus shouldn't change if nextFocus has blockFocus", () => {
@@ -263,7 +263,7 @@ export function getTargetRoute(
263
263
  pathname = "",
264
264
  {
265
265
  contentTypes = undefined,
266
- layoutVersion = undefined,
266
+ layoutVersion = "v2",
267
267
  rivers = {},
268
268
  }: {
269
269
  contentTypes?: ZappContentTypes | null;
@@ -0,0 +1,66 @@
1
+ import { toPositiveNumberWithDefault } from "..";
2
+
3
+ describe("toNumber", () => {
4
+ const DEFAULT = 5;
5
+
6
+ it("return number if input is positive number", () => {
7
+ const inputs = [0, 1, 2, 100, 200.12, Infinity];
8
+ expect.assertions(inputs.length);
9
+
10
+ inputs.forEach((input) => {
11
+ const output = toPositiveNumberWithDefault(DEFAULT, input);
12
+ expect(output).toBe(input);
13
+ });
14
+ });
15
+
16
+ it("return number if input is positive number as string", () => {
17
+ const inputs = ["0", "1", "2", "100", "200.12"];
18
+ expect.assertions(inputs.length);
19
+
20
+ inputs.forEach((input) => {
21
+ const output = toPositiveNumberWithDefault(DEFAULT, input);
22
+ expect(output).toBe(Number(input));
23
+ });
24
+ });
25
+
26
+ it("return default number if input is negative number", () => {
27
+ const inputs = [-1, -2, -100, -Infinity];
28
+ expect.assertions(inputs.length);
29
+
30
+ inputs.forEach((input) => {
31
+ const output = toPositiveNumberWithDefault(DEFAULT, input);
32
+ expect(output).toBe(DEFAULT);
33
+ });
34
+ });
35
+
36
+ it("return default number if input is negative number as string", () => {
37
+ const inputs = ["-1", "-2", "-100"];
38
+ expect.assertions(inputs.length);
39
+
40
+ inputs.forEach((input) => {
41
+ const output = toPositiveNumberWithDefault(DEFAULT, input);
42
+ expect(output).toBe(DEFAULT);
43
+ });
44
+ });
45
+
46
+ it("return default if input is not a number or infinite", () => {
47
+ const inputs = [
48
+ "vfdvf",
49
+ null,
50
+ undefined,
51
+ NaN,
52
+ "",
53
+ {},
54
+ { test: 1 },
55
+ [],
56
+ [1],
57
+ ];
58
+
59
+ expect.assertions(inputs.length);
60
+
61
+ inputs.forEach((input) => {
62
+ const output = toPositiveNumberWithDefault(DEFAULT, input);
63
+ expect(output).toBe(DEFAULT);
64
+ });
65
+ });
66
+ });
@@ -0,0 +1,66 @@
1
+ import { toPositiveNumberWithDefaultZero } from "..";
2
+
3
+ const DEFAULT = 0;
4
+
5
+ describe("toNumber", () => {
6
+ it("return number if input is positive number", () => {
7
+ const inputs = [0, 1, 2, 100, 200.12];
8
+ expect.assertions(inputs.length);
9
+
10
+ inputs.forEach((input) => {
11
+ const output = toPositiveNumberWithDefaultZero(input);
12
+ expect(output).toBe(input);
13
+ });
14
+ });
15
+
16
+ it("return number if input is positive number as string", () => {
17
+ const inputs = ["0", "1", "2", "100", "200.12"];
18
+ expect.assertions(inputs.length);
19
+
20
+ inputs.forEach((input) => {
21
+ const output = toPositiveNumberWithDefaultZero(input);
22
+ expect(output).toBe(Number(input));
23
+ });
24
+ });
25
+
26
+ it("return default number if input is negative number", () => {
27
+ const inputs = [-1, -2, -100];
28
+ expect.assertions(inputs.length);
29
+
30
+ inputs.forEach((input) => {
31
+ const output = toPositiveNumberWithDefaultZero(input);
32
+ expect(output).toBe(DEFAULT);
33
+ });
34
+ });
35
+
36
+ it("return default number if input is negative number as string", () => {
37
+ const inputs = ["-1", "-2", "-100"];
38
+ expect.assertions(inputs.length);
39
+
40
+ inputs.forEach((input) => {
41
+ const output = toPositiveNumberWithDefaultZero(input);
42
+ expect(output).toBe(DEFAULT);
43
+ });
44
+ });
45
+
46
+ it("return default if input is not a number or infinite", () => {
47
+ const inputs = [
48
+ "vfdvf",
49
+ null,
50
+ undefined,
51
+ NaN,
52
+ "",
53
+ {},
54
+ { test: 1 },
55
+ [],
56
+ [1],
57
+ ];
58
+
59
+ expect.assertions(inputs.length);
60
+
61
+ inputs.forEach((input) => {
62
+ const output = toPositiveNumberWithDefaultZero(input);
63
+ expect(output).toBe(DEFAULT);
64
+ });
65
+ });
66
+ });
@@ -66,3 +66,23 @@ export const isZero = (value: unknown): boolean =>
66
66
  export const isMinusZero = (value: number) => {
67
67
  return 1 / value === -Infinity;
68
68
  };
69
+
70
+ export const toPositiveNumberWithDefault = (
71
+ defaultValue: number,
72
+ value: unknown
73
+ ): number => {
74
+ const possibleNumber = toNumber(value);
75
+
76
+ if (R.isNil(possibleNumber)) {
77
+ return defaultValue;
78
+ }
79
+
80
+ if (possibleNumber < 0) {
81
+ return defaultValue;
82
+ }
83
+
84
+ return possibleNumber;
85
+ };
86
+
87
+ export const toPositiveNumberWithDefaultZero = (value: unknown): number =>
88
+ toPositiveNumberWithDefault(0, value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "12.0.0-alpha.4102526423",
3
+ "version": "12.0.0-alpha.4173603919",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "12.0.0-alpha.4102526423",
31
+ "@applicaster/applicaster-types": "12.0.0-alpha.4173603919",
32
32
  "buffer": "^5.2.1",
33
33
  "camelize": "^1.0.0",
34
34
  "create-react-context": "^0.2.3",
@@ -60,13 +60,13 @@ const wrapper = ({ children }) => (
60
60
  describe("useCellClick", () => {
61
61
  it("returns a function", () => {
62
62
  const { result } = renderHook(() => useCellClick(mockProps));
63
- expect(typeof result.current).toBe("function");
63
+ expect(typeof result.current.current).toBe("function");
64
64
  cleanup();
65
65
  });
66
66
 
67
67
  it("runs default logic", () => {
68
68
  const { result } = renderHook(() => useCellClick(mockProps));
69
- const useCellClickHook = result.current;
69
+ const useCellClickHook = result.current.current;
70
70
 
71
71
  useCellClickHook();
72
72
 
@@ -76,7 +76,7 @@ describe("useCellClick", () => {
76
76
 
77
77
  it("runs onCellTap", async () => {
78
78
  const { result } = renderHook(() => useCellClick(mockProps), { wrapper });
79
- const useCellClickHook = result.current;
79
+ const useCellClickHook = result.current.current;
80
80
 
81
81
  useCellClickHook();
82
82
 
@@ -30,7 +30,15 @@ type Props = {
30
30
  zappPipesData?: ZappPipesData;
31
31
  };
32
32
 
33
- export const useCellClick = ({ component, zappPipesData, item }: Props) => {
33
+ type onPressReturnFn =
34
+ | ((_item?: ZappEntry | any, _index?: number) => Promise<void>)
35
+ | (() => void);
36
+
37
+ export const useCellClick = ({
38
+ component,
39
+ zappPipesData,
40
+ item,
41
+ }: Props): React.MutableRefObject<onPressReturnFn> => {
34
42
  const { push, currentRoute } = useNavigation();
35
43
  const { pathname } = useRoute();
36
44
 
@@ -106,9 +114,15 @@ export const useCellClick = ({ component, zappPipesData, item }: Props) => {
106
114
  ]
107
115
  );
108
116
 
117
+ const onPressRef = React.useRef<onPressReturnFn>();
118
+
109
119
  if (!cellSelectable) {
110
- return noop;
120
+ // update ref with dummy function
121
+ onPressRef.current = noop;
122
+ } else {
123
+ // update ref with the latest onPress function
124
+ onPressRef.current = onPress;
111
125
  }
112
126
 
113
- return onPress;
127
+ return onPressRef;
114
128
  };
@@ -77,8 +77,6 @@ describe("useBatchLoading", () => {
77
77
 
78
78
  const actions = store.getActions();
79
79
 
80
- expect(useDispatchSpy).toBeCalledTimes(1);
81
-
82
80
  expect(actions).toHaveLength(2);
83
81
 
84
82
  expect(actions[0]).toMatchObject({
@@ -129,6 +127,8 @@ describe("useBatchLoading", () => {
129
127
  // ... more items
130
128
  ];
131
129
 
130
+ expect(useDispatchSpy).toBeCalledTimes(0);
131
+
132
132
  renderHook(() => useBatchLoading(data, { initialBatchSize, riverId }), {
133
133
  wrapper,
134
134
  initialProps: { store },
@@ -136,8 +136,6 @@ describe("useBatchLoading", () => {
136
136
 
137
137
  const actions = store.getActions();
138
138
 
139
- expect(useDispatchSpy).toBeCalledTimes(1);
140
-
141
139
  expect(actions).toHaveLength(1);
142
140
 
143
141
  expect(actions[0]).toMatchObject({
@@ -172,8 +170,6 @@ describe("useBatchLoading", () => {
172
170
  initialProps: { store },
173
171
  });
174
172
 
175
- expect(useDispatchSpy).toBeCalledTimes(1);
176
-
177
173
  const actions = store.getActions();
178
174
 
179
175
  expect(actions).toHaveLength(3);
@@ -218,6 +214,73 @@ describe("useBatchLoading", () => {
218
214
 
219
215
  expect(result.current).toBe(true);
220
216
  });
217
+
218
+ it("gallery-qb: loadPipesData should be called only once for first component in the gallery", () => {
219
+ const store = mockStore({
220
+ zappPipes: {},
221
+ test: "true",
222
+ });
223
+
224
+ useDispatchSpy.mockReturnValue(store.dispatch);
225
+
226
+ const initialBatchSize = 3;
227
+ const riverId = "123";
228
+
229
+ const data = [
230
+ {
231
+ component_type: "gallery-qb",
232
+ ui_components: [{ data: { source: "url1" } }],
233
+ },
234
+ { data: { source: "url2" } },
235
+ { data: { source: "url3" } },
236
+ { data: { source: "url4" } },
237
+ { data: { source: "url5" } },
238
+ { data: { source: "url6" } },
239
+ // ... more items
240
+ ];
241
+
242
+ renderHook(() => useBatchLoading(data, { initialBatchSize, riverId }), {
243
+ wrapper,
244
+ initialProps: { store },
245
+ });
246
+
247
+ const actions = store.getActions();
248
+
249
+ expect(actions).toHaveLength(1);
250
+ });
251
+
252
+ it("gallery-qb: initial batch ready when all initial items loaded", () => {
253
+ const store = mockStore({
254
+ zappPipes: {
255
+ url1: {
256
+ loading: false,
257
+ error: false,
258
+ data: {},
259
+ },
260
+ },
261
+ });
262
+
263
+ useDispatchSpy.mockReturnValue(store.dispatch);
264
+
265
+ const initialBatchSize = 3;
266
+ const riverId = "123";
267
+
268
+ const data: Partial<ZappUIComponent>[] = [
269
+ {
270
+ component_type: "gallery-qb",
271
+ ui_components: [{ data: { source: "url1" } }],
272
+ },
273
+ { data: { source: "url2" } },
274
+ { data: { source: "url3" } },
275
+ ];
276
+
277
+ const { result } = renderHook(
278
+ () => useBatchLoading(data, { initialBatchSize, riverId }),
279
+ { wrapper, initialProps: { store } }
280
+ );
281
+
282
+ expect(result.current).toBe(true);
283
+ });
221
284
  });
222
285
 
223
286
  describe("allFeedsIsReady", () => {
@@ -45,19 +45,23 @@ const filterEmptyData = (data) => {
45
45
  return true;
46
46
  };
47
47
 
48
- const extractData = compose(uniq, map(dataOf));
48
+ const getData = (rawData) =>
49
+ rawData.component_type === "gallery-qb"
50
+ ? rawData.ui_components[0].data
51
+ : rawData.data;
52
+
53
+ const extractData = compose(uniq, map(getData));
49
54
 
50
55
  export const allFeedsIsReady = (
51
56
  feeds: ZappPipesCollection,
52
57
  urls: (ZappDataSourceSource | null)[]
53
58
  ): boolean =>
54
- urls
55
- .filter(complement(isNil))
56
- .map((url) => (url ? checkFeedIsReady(feeds[url]) : false))
57
- .every(Boolean);
59
+ urls.every((url) =>
60
+ isNil(url) ? true : url && feeds[url] ? checkFeedIsReady(feeds[url]) : false
61
+ );
58
62
 
59
63
  export const useBatchLoading = (
60
- data: { data?: ZappDataSource }[],
64
+ componentsToRender: { data?: ZappDataSource; component_type: string }[],
61
65
  options: Options
62
66
  ) => {
63
67
  const dispatch = useDispatch();
@@ -65,10 +69,18 @@ export const useBatchLoading = (
65
69
  const [searchContext] = ZappPipesSearchContext.useZappPipesContext();
66
70
  const [hasEverBeenReady, setHasEverBeenReady] = React.useState(false);
67
71
 
68
- const takeBatchSize = take(
69
- min(options.initialBatchSize ?? DEFAULT_BATCH_SIZE, data.length)
70
- );
72
+ // if first component is gallery-qb, take only one component for initial load
73
+ const takeSize =
74
+ componentsToRender?.[0]?.component_type === "gallery-qb"
75
+ ? 1
76
+ : min(
77
+ options.initialBatchSize ?? DEFAULT_BATCH_SIZE,
78
+ componentsToRender.length
79
+ );
80
+
81
+ const takeBatchSize = React.useCallback(take(takeSize), [takeSize]);
71
82
 
83
+ // Prepare inflated url for datasource
72
84
  const getUrl = React.useCallback(
73
85
  (data) => {
74
86
  const mappedFeed = data.mapping
@@ -88,13 +100,16 @@ export const useBatchLoading = (
88
100
  [entryContext, screenContext, searchContext]
89
101
  );
90
102
 
91
- const batchData = takeBatchSize(data);
103
+ // components for initial load
104
+ const batchComponents = takeBatchSize(componentsToRender);
92
105
 
106
+ // contains datasources from batch components loading
93
107
  const feedData: ZappDataSource[] = React.useMemo(
94
- () => extractData(batchData).filter(filterEmptyData),
95
- [batchData]
108
+ () => extractData(batchComponents).filter(filterEmptyData),
109
+ [batchComponents]
96
110
  );
97
111
 
112
+ // contains inflated urls from batch components loading
98
113
  const feedUrls: ZappDataSourceSource[] = React.useMemo(
99
114
  () =>
100
115
  feedData.map((data) => {
@@ -102,34 +117,43 @@ export const useBatchLoading = (
102
117
 
103
118
  return mappedFeedUrl;
104
119
  }),
105
- [] // No watchers as batch loaders is used only for initial load
120
+ []
106
121
  );
107
122
 
108
123
  const feeds = useZappPipesFeeds(feedUrls);
109
124
 
110
- React.useEffect(() => {
111
- const datasources = map(dataOf, batchData)
112
- .filter(filterEmptyData)
113
- .filter((data) => {
114
- const feed = feeds[data.source];
125
+ // dispatch loadPipesData for each feed that is not loaded
126
+ const runBatchLoading = React.useCallback(() => {
127
+ batchComponents.forEach((rawData: any) => {
128
+ // 1. get data (dataOf)
115
129
 
116
- if (!feed) return true;
130
+ const data = getData(rawData);
117
131
 
118
- return !feedHasData(feed) && !feedIsLoading(feed);
119
- });
132
+ // 2. filter out empty data filterEmptyData
133
+ if (!filterEmptyData(data)) return false;
120
134
 
121
- datasources.forEach((data) => {
122
- const mappedFeedUrl = getUrl(data);
135
+ // 3. filter out data that is already loaded (feeds[data.source]) & !feedHasData(feed) && !feedIsLoading(feed);
136
+ const feed = feeds[data?.source];
123
137
 
124
- if (mappedFeedUrl) {
125
- return dispatch(
126
- loadPipesData(mappedFeedUrl, { riverId: options.riverId })
127
- );
138
+ if (!feed || (!feedHasData(feed) && !feedIsLoading(feed))) {
139
+ const mappedFeedUrl = getUrl(data);
140
+
141
+ if (mappedFeedUrl) {
142
+ // 4. load data
143
+ return dispatch(
144
+ loadPipesData(mappedFeedUrl, { riverId: options.riverId })
145
+ );
146
+ }
128
147
  }
129
148
  });
130
- }, []); // No watchers as batch loaders is used only for initial load
149
+ }, [feedUrls]);
150
+
151
+ React.useEffect(() => {
152
+ runBatchLoading();
153
+ }, []);
131
154
 
132
155
  React.useEffect(() => {
156
+ // check if all feeds are ready and set hasEverBeenReady to true
133
157
  if (allFeedsIsReady(feeds, feedUrls) && !hasEverBeenReady) {
134
158
  setHasEverBeenReady(true);
135
159
  }
@@ -0,0 +1,134 @@
1
+ import { renderHook } from "@testing-library/react-hooks";
2
+ import { Dimensions, StatusBar } from "react-native";
3
+
4
+ const mockUsePickFromState = jest.fn();
5
+ const mockUseIsScreenActive = jest.fn();
6
+ const mockGetInitialDimensions = jest.fn();
7
+ const mockGetDeviceInfo = jest.fn();
8
+
9
+ jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
10
+ ...(jest.requireActual("@applicaster/zapp-react-native-redux/hooks") as {}),
11
+ usePickFromState: mockUsePickFromState,
12
+ }));
13
+
14
+ jest.mock("../../../navigation", () => ({
15
+ useIsScreenActive: mockUseIsScreenActive,
16
+ }));
17
+
18
+ jest.mock("../helpers", () => ({
19
+ getInitialDimensions: mockGetInitialDimensions,
20
+ }));
21
+
22
+ jest.mock("../../getDeviceInfo", () => ({
23
+ getDeviceInfo: mockGetDeviceInfo,
24
+ }));
25
+
26
+ const { useDimensions } = require("../useDimensions");
27
+
28
+ describe("useDimensions", () => {
29
+ const mockDimensions = { width: 100, height: 200, scale: 1, fontScale: 1 };
30
+ const mockAppData = { someData: "test" };
31
+
32
+ beforeEach(() => {
33
+ jest.clearAllMocks();
34
+ Dimensions.get = jest.fn().mockReturnValue(mockDimensions);
35
+
36
+ Dimensions.addEventListener = jest.fn().mockReturnValue({
37
+ remove: jest.fn(),
38
+ });
39
+
40
+ mockUsePickFromState.mockReturnValue({ appData: mockAppData });
41
+ mockUseIsScreenActive.mockReturnValue(true);
42
+ mockGetInitialDimensions.mockReturnValue(mockDimensions);
43
+ mockGetDeviceInfo.mockReturnValue({ deviceInfo: "testDeviceInfo" });
44
+ });
45
+
46
+ it("returns correct initial dimensions", () => {
47
+ const { result } = renderHook(() =>
48
+ useDimensions("window", { fullDimensions: false })
49
+ );
50
+
51
+ expect(result.current).toEqual({
52
+ ...mockDimensions,
53
+ statusBarHeight: StatusBar.currentHeight,
54
+ });
55
+
56
+ expect(mockGetInitialDimensions).toHaveBeenCalledWith("window");
57
+ });
58
+
59
+ it("calls handler on mount", () => {
60
+ renderHook(() => useDimensions("window", { fullDimensions: false }));
61
+
62
+ expect(Dimensions.addEventListener).toHaveBeenCalledWith(
63
+ "change",
64
+ expect.any(Function)
65
+ );
66
+ });
67
+
68
+ it("calls handler on isActive change", () => {
69
+ const { rerender } = renderHook(() =>
70
+ useDimensions("window", { fullDimensions: false })
71
+ );
72
+
73
+ mockUseIsScreenActive.mockReturnValue(false);
74
+ rerender();
75
+
76
+ expect(Dimensions.addEventListener).toHaveBeenCalledWith(
77
+ "change",
78
+ expect.any(Function)
79
+ );
80
+ });
81
+
82
+ it("handles fullDimensions option", () => {
83
+ const { result } = renderHook(() =>
84
+ useDimensions("window", { fullDimensions: true })
85
+ );
86
+
87
+ expect(result.current).toEqual({
88
+ ...mockDimensions,
89
+ scale: 1,
90
+ fontScale: 1,
91
+ statusBarHeight: StatusBar.currentHeight,
92
+ });
93
+ });
94
+
95
+ it("handles excludeStatusBar option", () => {
96
+ const { result } = renderHook(() =>
97
+ useDimensions("window", { excludeStatusBar: true })
98
+ );
99
+
100
+ expect(result.current.height).toBe(
101
+ mockDimensions.height - StatusBar.currentHeight ?? 0
102
+ );
103
+ });
104
+
105
+ it("handles rounded option", () => {
106
+ const { result } = renderHook(() =>
107
+ useDimensions("window", { rounded: true })
108
+ );
109
+
110
+ expect(result.current.width).toBe(Math.ceil(mockDimensions.width));
111
+ expect(result.current.height).toBe(Math.ceil(mockDimensions.height));
112
+ });
113
+
114
+ it("handles deviceInfo option", () => {
115
+ const { result } = renderHook(() =>
116
+ useDimensions("window", { deviceInfo: true })
117
+ );
118
+
119
+ expect(result.current.deviceInfo).toMatchObject({
120
+ deviceInfo: "testDeviceInfo",
121
+ });
122
+ });
123
+
124
+ it("logs deprecation warning when fullDimensions is boolean", () => {
125
+ const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation();
126
+ renderHook(() => useDimensions("window", true));
127
+
128
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
129
+ "Deprecation Warning\nSecond argument is now the options object. {fullDimensions: boolean, ...}"
130
+ );
131
+
132
+ consoleWarnSpy.mockRestore();
133
+ });
134
+ });
@@ -17,7 +17,7 @@ import {
17
17
  QUICK_BRICK_NAVBAR,
18
18
  QUICK_BRICK_CONTENT,
19
19
  } from "@applicaster/quick-brick-core/const";
20
- import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/ZappHooksModal";
20
+ import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/hooks/useZappHooksModalState";
21
21
  import { ZappHookModalContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
22
22
  import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
23
23
  import { HookModalContextT } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
@@ -7,9 +7,11 @@ import { Provider } from "react-redux";
7
7
  import thunk from "redux-thunk";
8
8
  import configureStore from "redux-mock-store";
9
9
  import { SafeAreaProvider } from "react-native-safe-area-context";
10
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
10
11
 
11
12
  import { render } from "@testing-library/react-native";
12
13
  import { AnalyticsProvider } from "../analyticsUtils";
14
+ import { ThemeContext } from "../theme";
13
15
 
14
16
  export { getByTestId } from "./getByTestId";
15
17
 
@@ -59,7 +61,9 @@ const initialState = {
59
61
  appSettings: {},
60
62
  plugins: [],
61
63
  appState: { appReady: true, appLaunched: true },
64
+ pluginConfigurations: {},
62
65
  appData: { layoutVersion: "v1" },
66
+ zappPipes: {},
63
67
  contentTypes: null,
64
68
  };
65
69
 
@@ -68,20 +72,49 @@ export const WrappedWithProviders = ({
68
72
  store: storeObj = {},
69
73
  }: any) => {
70
74
  const _store = configureStore([thunk])(
71
- R.mergeDeepWith(R.concat, { ...initialState }, storeObj)
75
+ R.mergeDeepRight(initialState, { ...storeObj })
72
76
  );
73
77
 
78
+ appStore.set(_store);
79
+
74
80
  return (
75
- <SafeAreaProvider
76
- initialMetrics={{
77
- insets: { top: 0, left: 0, right: 0, bottom: 0 },
78
- frame: { x: 0, y: 0, width: 0, height: 0 },
81
+ <ThemeContext.Provider
82
+ value={{
83
+ themes: {
84
+ light: {
85
+ component_margin_top: 0,
86
+ component_margin_bottom: 0,
87
+ component_margin_left: 0,
88
+ component_margin_right: 0,
89
+ component_padding_top: 0,
90
+ component_padding_bottom: 0,
91
+ component_padding_left: 0,
92
+ component_padding_right: 0,
93
+ component_gutter_vertical: 0,
94
+ component_gutter_horizontal: 0,
95
+ component_corner_radius: 0,
96
+ app_background_color: "rgba(0,0,0,0)",
97
+ screen_margin_top: 0,
98
+ screen_margin_bottom: 0,
99
+ component_anchor_point_y: 0,
100
+ assets: "",
101
+ },
102
+ },
103
+ selectedThemeId: "light",
104
+ setSelectedThemeId: () => {},
79
105
  }}
80
106
  >
81
- <Provider store={_store}>
82
- <AnalyticsProvider>{children}</AnalyticsProvider>
83
- </Provider>
84
- </SafeAreaProvider>
107
+ <SafeAreaProvider
108
+ initialMetrics={{
109
+ insets: { top: 0, left: 0, right: 0, bottom: 0 },
110
+ frame: { x: 0, y: 0, width: 0, height: 0 },
111
+ }}
112
+ >
113
+ <Provider store={_store}>
114
+ <AnalyticsProvider>{children}</AnalyticsProvider>
115
+ </Provider>
116
+ </SafeAreaProvider>
117
+ </ThemeContext.Provider>
85
118
  );
86
119
  };
87
120