@applicaster/zapp-react-native-utils 12.0.0-alpha.9739074790 → 12.0.0-alpha.9903765553
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.
- package/analyticsUtils/AnalyticsEvents/index.ts +7 -0
- package/analyticsUtils/AnalyticsEvents/sendShareEvent.ts +21 -0
- package/analyticsUtils/events.ts +6 -0
- package/analyticsUtils/index.tsx +6 -0
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +2 -3
- package/appUtils/focusManager/__tests__/focusManager.test.js +4 -24
- package/appUtils/focusManager/__tests__/index.test.ts +2 -3
- package/appUtils/focusManager/index.ts +76 -56
- package/appUtils/focusManager/treeDataStructure/Utils/index.js +74 -43
- package/appUtils/focusManager/utils/index.ts +100 -0
- package/appUtils/focusManagerAux/index.ts +76 -0
- package/appUtils/focusManagerAux/utils/index.ts +100 -0
- package/appUtils/playerManager/playerNative.ts +1 -1
- package/arrayUtils/__tests__/isFilledArray.test.ts +63 -0
- package/arrayUtils/index.ts +5 -0
- package/numberUtils/__tests__/isLikeNumber.test.ts +18 -0
- package/numberUtils/index.ts +5 -18
- package/package.json +2 -2
- package/reactHooks/cell-click/__tests__/index.test.js +9 -5
- package/reactHooks/cell-click/index.ts +11 -11
- package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +30 -70
- package/reactHooks/feed/useBuildPipesUrl.ts +7 -10
- package/reactHooks/navigation/index.ts +32 -1
- package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +21 -23
- package/reactHooks/screen/index.ts +2 -0
- package/reactHooks/screen/useScreenBackgroundColor.ts +23 -0
- package/reactHooks/screen/useScreenContext.ts +1 -2
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
|
|
2
|
+
import { find, path } from "ramda";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
waitForActiveScreen,
|
|
6
|
+
waitForContent,
|
|
7
|
+
getContentNode,
|
|
8
|
+
getNavbarNode,
|
|
9
|
+
} from "./utils";
|
|
10
|
+
|
|
11
|
+
import { coreLogger } from "../../logger";
|
|
12
|
+
|
|
13
|
+
const logger = coreLogger.addSubsystem("focusManagerAux");
|
|
14
|
+
|
|
15
|
+
export async function setFocusOnMenu(currentRoute: string) {
|
|
16
|
+
const { setFocus, focusableTree } = focusManager;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
await waitForActiveScreen(currentRoute, focusableTree);
|
|
20
|
+
|
|
21
|
+
const navbar = getNavbarNode(focusableTree);
|
|
22
|
+
|
|
23
|
+
const selectedMenuItemId = find(
|
|
24
|
+
(child) => child.component.props.selected,
|
|
25
|
+
navbar.children
|
|
26
|
+
)?.id;
|
|
27
|
+
|
|
28
|
+
if (selectedMenuItemId) {
|
|
29
|
+
// set focus on selected menu item
|
|
30
|
+
setFocus(selectedMenuItemId);
|
|
31
|
+
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const firstMenuItemId = path(["children", 0, "id"], navbar);
|
|
36
|
+
|
|
37
|
+
if (firstMenuItemId) {
|
|
38
|
+
// set focus on first menu item
|
|
39
|
+
setFocus(firstMenuItemId);
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
logger.log({
|
|
43
|
+
message: "setFocusOnMenu – failed to put focus on top-menu",
|
|
44
|
+
data: {
|
|
45
|
+
currentRoute,
|
|
46
|
+
error,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function setFocusOnContent(currentRoute: string) {
|
|
53
|
+
// we need to put focus on content here
|
|
54
|
+
// For doing it we have to wait registration of first focusable node into focusableTree
|
|
55
|
+
// we wait until content node starts having children
|
|
56
|
+
|
|
57
|
+
const { setInitialFocus, focusableTree } = focusManager;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
await waitForActiveScreen(currentRoute, focusableTree);
|
|
61
|
+
await waitForContent(focusableTree);
|
|
62
|
+
|
|
63
|
+
const content = getContentNode(focusableTree);
|
|
64
|
+
|
|
65
|
+
const { success } = setInitialFocus(content);
|
|
66
|
+
|
|
67
|
+
if (!success) {
|
|
68
|
+
// we've failed to put initial focus on content, re-run "setFocusOnContent" again as a fallback
|
|
69
|
+
throw Error(
|
|
70
|
+
"we've failed to put initial focus on content, re-run 'setFocusOnContent' again as a fallback"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
setFocusOnContent(currentRoute);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
2
|
+
import { find, last, startsWith, pathOr } from "ramda";
|
|
3
|
+
import {
|
|
4
|
+
QUICK_BRICK_NAVBAR,
|
|
5
|
+
QUICK_BRICK_CONTENT,
|
|
6
|
+
} from "@applicaster/quick-brick-core/const";
|
|
7
|
+
|
|
8
|
+
// run check each 300 ms
|
|
9
|
+
// run this check too often could lead to performance penalty on low-end devices
|
|
10
|
+
const HOW_OFTEN_TO_CHECK_CONDITION = 300; // ms
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
maxTimeout: number;
|
|
14
|
+
conditionFn: () => boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const waitUntil = ({ maxTimeout, conditionFn }: Props) => {
|
|
18
|
+
let interval;
|
|
19
|
+
let timeout;
|
|
20
|
+
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
// run interval check
|
|
23
|
+
interval = setInterval(() => {
|
|
24
|
+
if (conditionFn()) {
|
|
25
|
+
clearInterval(interval);
|
|
26
|
+
clearTimeout(timeout);
|
|
27
|
+
|
|
28
|
+
resolve(undefined);
|
|
29
|
+
}
|
|
30
|
+
}, HOW_OFTEN_TO_CHECK_CONDITION);
|
|
31
|
+
|
|
32
|
+
// max timeout when we need to stop interval check
|
|
33
|
+
timeout = setTimeout(() => {
|
|
34
|
+
clearInterval(interval);
|
|
35
|
+
clearTimeout(timeout);
|
|
36
|
+
|
|
37
|
+
const error = Error(
|
|
38
|
+
"Failed to wait satisfy condition. Max timeout is reached."
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
reject(error);
|
|
42
|
+
}, maxTimeout);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
|
|
47
|
+
const isActiveScreenAvailableToFocus = (): boolean => {
|
|
48
|
+
const routes = focusableTree.root.children;
|
|
49
|
+
|
|
50
|
+
const route = find((route) => route.id === currentRoute, routes);
|
|
51
|
+
|
|
52
|
+
return isNotNil(route);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return waitUntil({
|
|
56
|
+
maxTimeout: HOW_OFTEN_TO_CHECK_CONDITION * 10, // ms
|
|
57
|
+
conditionFn: isActiveScreenAvailableToFocus,
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const getActiveScreenChildren = (focusableTree) => {
|
|
62
|
+
return pathOr([], ["children"], last(focusableTree.root.children));
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const getContentNode = (focusableTree) => {
|
|
66
|
+
const content = find(
|
|
67
|
+
(child) =>
|
|
68
|
+
startsWith(QUICK_BRICK_CONTENT, child.id) ||
|
|
69
|
+
startsWith("player", child.id),
|
|
70
|
+
getActiveScreenChildren(focusableTree)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return content;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const getNavbarNode = (focusableTree) => {
|
|
77
|
+
const navbar = find(
|
|
78
|
+
(child) => startsWith(QUICK_BRICK_NAVBAR, child.id),
|
|
79
|
+
getActiveScreenChildren(focusableTree)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return navbar;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const waitForContent = (focusableTree) => {
|
|
86
|
+
const contentHasAnyChildren = (): boolean => {
|
|
87
|
+
const countOfChildren = pathOr(
|
|
88
|
+
0,
|
|
89
|
+
["children", "length"],
|
|
90
|
+
getContentNode(focusableTree)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return countOfChildren > 0;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return waitUntil({
|
|
97
|
+
maxTimeout: HOW_OFTEN_TO_CHECK_CONDITION * 10, // ms
|
|
98
|
+
conditionFn: contentHasAnyChildren,
|
|
99
|
+
});
|
|
100
|
+
};
|
|
@@ -111,7 +111,7 @@ export class PlayerNative extends Player {
|
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
const newPosition = R.clamp(0,
|
|
114
|
+
const newPosition = R.clamp(0, this.playerState.seekableDuration, position);
|
|
115
115
|
|
|
116
116
|
if (
|
|
117
117
|
!this.invokeNativeFunction(
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { isFilledArray } from "..";
|
|
2
|
+
|
|
3
|
+
describe("isFilledArray", () => {
|
|
4
|
+
it("non-empty array is not empty", () => {
|
|
5
|
+
const value = [1, 2, 3];
|
|
6
|
+
|
|
7
|
+
expect(isFilledArray(value)).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("empty array is empty", () => {
|
|
11
|
+
const value = [];
|
|
12
|
+
|
|
13
|
+
expect(isFilledArray(value)).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("number is not array", () => {
|
|
17
|
+
const value = 123;
|
|
18
|
+
|
|
19
|
+
expect(isFilledArray(value)).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("string is not array", () => {
|
|
23
|
+
const value = "vfnjdk";
|
|
24
|
+
|
|
25
|
+
expect(isFilledArray(value)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("empty string is not array", () => {
|
|
29
|
+
const value = "";
|
|
30
|
+
|
|
31
|
+
expect(isFilledArray(value)).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("NaN is not array", () => {
|
|
35
|
+
const value = "";
|
|
36
|
+
|
|
37
|
+
expect(isFilledArray(value)).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("object is not array", () => {
|
|
41
|
+
const value = { test: 1 };
|
|
42
|
+
|
|
43
|
+
expect(isFilledArray(value)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("empty object is not array", () => {
|
|
47
|
+
const value = {};
|
|
48
|
+
|
|
49
|
+
expect(isFilledArray(value)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("undefined is not array", () => {
|
|
53
|
+
const value = undefined;
|
|
54
|
+
|
|
55
|
+
expect(isFilledArray(value)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("null is not array", () => {
|
|
59
|
+
const value = null;
|
|
60
|
+
|
|
61
|
+
expect(isFilledArray(value)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
package/arrayUtils/index.ts
CHANGED
|
@@ -95,3 +95,8 @@ export const makeListOfIndexes = (size: number): number[] =>
|
|
|
95
95
|
export const makeListOf = (value: unknown, size: number): number[] => {
|
|
96
96
|
return Array(size).fill(value);
|
|
97
97
|
};
|
|
98
|
+
|
|
99
|
+
/** Checks if a value is a non-empty array */
|
|
100
|
+
export function isFilledArray(value: unknown): boolean {
|
|
101
|
+
return R.is(Array, value) && R.length(value) > 0;
|
|
102
|
+
}
|
|
@@ -7,12 +7,24 @@ describe("isLikeNumber", () => {
|
|
|
7
7
|
expect(isLikeNumber(value)).toBe(true);
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
+
it("value is a number", () => {
|
|
11
|
+
const value = 123.45;
|
|
12
|
+
|
|
13
|
+
expect(isLikeNumber(value)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
10
16
|
it("value is a string as number", () => {
|
|
11
17
|
const value = "123";
|
|
12
18
|
|
|
13
19
|
expect(isLikeNumber(value)).toBe(true);
|
|
14
20
|
});
|
|
15
21
|
|
|
22
|
+
it("value is a string as number", () => {
|
|
23
|
+
const value = "123.45";
|
|
24
|
+
|
|
25
|
+
expect(isLikeNumber(value)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
16
28
|
it("value is empty string", () => {
|
|
17
29
|
const value = "";
|
|
18
30
|
|
|
@@ -54,4 +66,10 @@ describe("isLikeNumber", () => {
|
|
|
54
66
|
|
|
55
67
|
expect(isLikeNumber(value)).toBe(false);
|
|
56
68
|
});
|
|
69
|
+
|
|
70
|
+
it("NaN is not a number", () => {
|
|
71
|
+
const value = NaN;
|
|
72
|
+
|
|
73
|
+
expect(isLikeNumber(value)).toBe(false);
|
|
74
|
+
});
|
|
57
75
|
});
|
package/numberUtils/index.ts
CHANGED
|
@@ -50,27 +50,14 @@ export const toFiniteNumberWithDefault = (
|
|
|
50
50
|
return possibleNumber;
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
export const isLikeNumber = (value:
|
|
54
|
-
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (R.is(Number, value)) {
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (R.is(String, value)) {
|
|
63
|
-
const onlyDigit = value
|
|
64
|
-
.split("")
|
|
65
|
-
.filter((char) =>
|
|
66
|
-
["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].includes(char)
|
|
67
|
-
)
|
|
68
|
-
.join("");
|
|
53
|
+
export const isLikeNumber = (value: unknown): boolean => {
|
|
54
|
+
const possibleNumber = toNumber(value);
|
|
69
55
|
|
|
70
|
-
|
|
56
|
+
if (R.isNil(possibleNumber)) {
|
|
57
|
+
return false;
|
|
71
58
|
}
|
|
72
59
|
|
|
73
|
-
return
|
|
60
|
+
return true;
|
|
74
61
|
};
|
|
75
62
|
|
|
76
63
|
export const isZero = (value: unknown): boolean =>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "12.0.0-alpha.
|
|
3
|
+
"version": "12.0.0-alpha.9903765553",
|
|
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.
|
|
31
|
+
"@applicaster/applicaster-types": "12.0.0-alpha.9903765553",
|
|
32
32
|
"buffer": "^5.2.1",
|
|
33
33
|
"camelize": "^1.0.0",
|
|
34
34
|
"create-react-context": "^0.2.3",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { cleanup, renderHook } from "@testing-library/react-hooks";
|
|
3
3
|
import { CellTapContext } from "@applicaster/zapp-react-native-ui-components/Contexts/CellTapContext";
|
|
4
|
+
import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutorContext";
|
|
5
|
+
|
|
4
6
|
import { waitFor } from "@testing-library/react-native";
|
|
5
7
|
|
|
6
8
|
const navigator = {
|
|
@@ -29,10 +31,6 @@ jest.mock("@applicaster/zapp-react-native-ui-components/Contexts", () => ({
|
|
|
29
31
|
},
|
|
30
32
|
}));
|
|
31
33
|
|
|
32
|
-
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
|
|
33
|
-
useScreenContext: jest.fn(() => navigator.data),
|
|
34
|
-
}));
|
|
35
|
-
|
|
36
34
|
const { useCellClick } = require("../");
|
|
37
35
|
|
|
38
36
|
const mockProps = {
|
|
@@ -47,9 +45,15 @@ const mockProps = {
|
|
|
47
45
|
|
|
48
46
|
const onCellTap = jest.fn();
|
|
49
47
|
|
|
48
|
+
const actionExecutor = {
|
|
49
|
+
handleEntryActions: jest.fn(() => Promise.resolve()),
|
|
50
|
+
};
|
|
51
|
+
|
|
50
52
|
const wrapper = ({ children }) => (
|
|
51
53
|
<CellTapContext.Provider value={onCellTap}>
|
|
52
|
-
{
|
|
54
|
+
<ActionExecutorContext.Provider value={actionExecutor}>
|
|
55
|
+
{children}
|
|
56
|
+
</ActionExecutorContext.Provider>
|
|
53
57
|
</CellTapContext.Provider>
|
|
54
58
|
);
|
|
55
59
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/// <reference types="@applicaster/applicaster-types" />
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { useContext } from "react";
|
|
5
4
|
import * as R from "ramda";
|
|
6
5
|
import { handleActionSchemeUrl } from "@applicaster/quick-brick-core/App/DeepLinking/URLSchemeHandler/SchemeHandlerHooks/useUrlSchemeHandler";
|
|
7
6
|
import { CellTapContext } from "@applicaster/zapp-react-native-ui-components/Contexts/CellTapContext";
|
|
@@ -12,8 +11,9 @@ import {
|
|
|
12
11
|
} from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
13
12
|
import { ZappPipesEntryContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
|
|
14
13
|
import { toBooleanWithDefaultTrue } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
15
|
-
import {
|
|
16
|
-
|
|
14
|
+
import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutorContext";
|
|
15
|
+
|
|
16
|
+
import { isFunction, noop } from "../../functionUtils";
|
|
17
17
|
import { useSendAnalyticsOnPress } from "../analytics";
|
|
18
18
|
import { logOnPress, warnEmptyContentType } from "./helpers";
|
|
19
19
|
|
|
@@ -30,12 +30,12 @@ type Props = {
|
|
|
30
30
|
zappPipesData?: ZappPipesData;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export const useCellClick = (
|
|
34
|
-
const { component, zappPipesData } = props;
|
|
33
|
+
export const useCellClick = ({ component, zappPipesData, item }: Props) => {
|
|
35
34
|
const { push, currentRoute } = useNavigation();
|
|
36
35
|
const { pathname } = useRoute();
|
|
37
|
-
|
|
38
|
-
const
|
|
36
|
+
|
|
37
|
+
const onCellTap: Option<Function> = React.useContext(CellTapContext);
|
|
38
|
+
const actionExecutor = React.useContext(ActionExecutorContext);
|
|
39
39
|
|
|
40
40
|
const cellSelectable = toBooleanWithDefaultTrue(
|
|
41
41
|
component?.rules?.component_cells_selectable
|
|
@@ -47,7 +47,7 @@ export const useCellClick = (props: Props) => {
|
|
|
47
47
|
const logTimestamp = useProfilerLogging();
|
|
48
48
|
|
|
49
49
|
const sendAnalyticsOnPress = useSendAnalyticsOnPress(
|
|
50
|
-
|
|
50
|
+
item,
|
|
51
51
|
component,
|
|
52
52
|
zappPipesData
|
|
53
53
|
);
|
|
@@ -56,7 +56,7 @@ export const useCellClick = (props: Props) => {
|
|
|
56
56
|
async (_item?: ZappEntry | any, _index?: number) => {
|
|
57
57
|
// Making sure we are not getting GestureResponderEvent in here.
|
|
58
58
|
const isZappEntry = _item?.id && _item?.type;
|
|
59
|
-
const selectedItem = isZappEntry ? _item :
|
|
59
|
+
const selectedItem = isZappEntry ? _item : item;
|
|
60
60
|
setEntryContext?.(selectedItem);
|
|
61
61
|
|
|
62
62
|
if (!selectedItem?.type?.value) {
|
|
@@ -80,8 +80,8 @@ export const useCellClick = (props: Props) => {
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
} else {
|
|
83
|
-
if (
|
|
84
|
-
onCellTap(selectedItem, _index);
|
|
83
|
+
if (isFunction(onCellTap)) {
|
|
84
|
+
onCellTap?.(selectedItem, _index);
|
|
85
85
|
} else {
|
|
86
86
|
if (currentRoute === pathname || pathname.includes("video-modal")) {
|
|
87
87
|
const targetScreen = component?.data?.target;
|
|
@@ -1,77 +1,26 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { renderHook } from "@testing-library/react-hooks";
|
|
3
|
-
import configureStore from "redux-mock-store";
|
|
4
|
-
import thunk from "redux-thunk";
|
|
5
|
-
import { Provider } from "react-redux";
|
|
6
|
-
|
|
7
2
|
import { encodeParams } from "@applicaster/zapp-pipes-v2-client/src/utils";
|
|
8
3
|
|
|
9
|
-
const mockStore = configureStore([thunk]);
|
|
10
|
-
|
|
11
|
-
jest.useFakeTimers();
|
|
12
|
-
|
|
13
4
|
const appData = {
|
|
14
5
|
platform: "foo",
|
|
15
6
|
version_name: "1.2.0",
|
|
16
7
|
sdk_version: "2.0.0",
|
|
17
8
|
};
|
|
18
9
|
|
|
19
|
-
|
|
20
|
-
"http://foo.com/show": {
|
|
21
|
-
context_obj: [
|
|
22
|
-
{ type: "ctx", key: "platform" },
|
|
23
|
-
{ type: "query", mapper: "version", key: "appVersion" },
|
|
24
|
-
{ type: "header", mapper: "SDK", key: "sdkVersion" },
|
|
25
|
-
],
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const store_mock = { appData, pipesEndpoints };
|
|
30
|
-
|
|
31
|
-
const defaultStore = mockStore({
|
|
32
|
-
pipesEndpoints,
|
|
33
|
-
appData,
|
|
34
|
-
});
|
|
10
|
+
let INFLATED_URL;
|
|
35
11
|
|
|
36
|
-
|
|
37
|
-
|
|
12
|
+
jest.mock(
|
|
13
|
+
"@applicaster/zapp-react-native-utils/reactHooks/feed/useInflatedUrl",
|
|
14
|
+
() => ({
|
|
15
|
+
useInflatedUrl: () => INFLATED_URL,
|
|
16
|
+
})
|
|
38
17
|
);
|
|
39
18
|
|
|
40
|
-
const navigator = {
|
|
41
|
-
currentRoute: "/river/A1234",
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
jest.mock("../../navigation", () => ({
|
|
45
|
-
useNavigation: jest.fn(() => navigator),
|
|
46
|
-
useRoute: jest.fn(() => ({})),
|
|
47
|
-
}));
|
|
48
|
-
|
|
49
|
-
const contexts = {
|
|
50
|
-
entry: { id: "show-1" },
|
|
51
|
-
search: {},
|
|
52
|
-
screen: {},
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const contextMock = (type) => ({
|
|
56
|
-
useZappPipesContext: () => [contexts[type] || {}],
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
jest.mock("@applicaster/zapp-react-native-ui-components/Contexts", () => ({
|
|
60
|
-
ZappPipesEntryContext: contextMock("entry"),
|
|
61
|
-
ZappPipesSearchContext: contextMock("search"),
|
|
62
|
-
ZappPipesScreenContext: contextMock("screen"),
|
|
63
|
-
}));
|
|
64
|
-
|
|
65
|
-
jest.mock("@applicaster/zapp-react-native-redux/AppStore", () => ({
|
|
66
|
-
appStore: {
|
|
67
|
-
get: jest.fn((prop) => store_mock[prop]),
|
|
68
|
-
},
|
|
69
|
-
}));
|
|
70
|
-
|
|
71
19
|
const { useBuildPipesUrl } = require("../useBuildPipesUrl");
|
|
72
20
|
|
|
73
21
|
describe("useBuildPipesUrl", () => {
|
|
74
22
|
it("returns the proper url", async () => {
|
|
23
|
+
INFLATED_URL = "http://foo.com/show/show-1";
|
|
75
24
|
const url = "http://foo.com/show/{{showId}}";
|
|
76
25
|
|
|
77
26
|
const mapping = {
|
|
@@ -81,12 +30,20 @@ describe("useBuildPipesUrl", () => {
|
|
|
81
30
|
},
|
|
82
31
|
};
|
|
83
32
|
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
33
|
+
const buildResult = {
|
|
34
|
+
params: {
|
|
35
|
+
ctx: encodeParams({ platform: appData.platform }),
|
|
36
|
+
version: appData.version_name,
|
|
37
|
+
},
|
|
38
|
+
headers: { SDK: appData.sdk_version },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const mockedRequestBuilder = {
|
|
42
|
+
build: jest.fn(() => Promise.resolve(buildResult)),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const { result, waitForNextUpdate } = renderHook(() =>
|
|
46
|
+
useBuildPipesUrl({ url, mapping, requestBuilder: mockedRequestBuilder })
|
|
90
47
|
);
|
|
91
48
|
|
|
92
49
|
expect(result.current.url).toBe("http://foo.com/show/show-1");
|
|
@@ -108,6 +65,7 @@ describe("useBuildPipesUrl", () => {
|
|
|
108
65
|
});
|
|
109
66
|
|
|
110
67
|
it("returns empty object if url is not in endpoints", async () => {
|
|
68
|
+
INFLATED_URL = "http://foobar.com/show-1";
|
|
111
69
|
const url = "http://foobar.com/{{showId}}";
|
|
112
70
|
|
|
113
71
|
const mapping = {
|
|
@@ -117,12 +75,14 @@ describe("useBuildPipesUrl", () => {
|
|
|
117
75
|
},
|
|
118
76
|
};
|
|
119
77
|
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
78
|
+
const buildResult = {};
|
|
79
|
+
|
|
80
|
+
const mockedRequestBuilder = {
|
|
81
|
+
build: jest.fn(() => Promise.resolve(buildResult)),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const { result, waitForNextUpdate } = renderHook(() =>
|
|
85
|
+
useBuildPipesUrl({ url, mapping, requestBuilder: mockedRequestBuilder })
|
|
126
86
|
);
|
|
127
87
|
|
|
128
88
|
expect(result.current.url).toBe("http://foobar.com/show-1");
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// @ts-ignore
|
|
2
1
|
import { RequestBuilder } from "@applicaster/zapp-pipes-v2-client";
|
|
3
|
-
import
|
|
4
|
-
import { useInflatedUrl } from "
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useInflatedUrl } from "@applicaster/zapp-react-native-utils/reactHooks/feed/useInflatedUrl";
|
|
5
4
|
import { reactHooksLogger } from "../logger";
|
|
6
5
|
|
|
7
6
|
export const logger = reactHooksLogger.addSubsystem("build-pipes-url");
|
|
@@ -15,18 +14,16 @@ type PipesBuildRequestObject = {
|
|
|
15
14
|
export function useBuildPipesUrl({
|
|
16
15
|
url,
|
|
17
16
|
mapping = {},
|
|
17
|
+
requestBuilder = new RequestBuilder(),
|
|
18
18
|
}): PipesBuildRequestObject {
|
|
19
|
-
const [requestParams, setRequestParams] = useState<object | null>(null);
|
|
20
|
-
const [error, setError] = useState(null);
|
|
19
|
+
const [requestParams, setRequestParams] = React.useState<object | null>(null);
|
|
21
20
|
|
|
21
|
+
const [error, setError] = React.useState(null);
|
|
22
22
|
const inflatedUrl = useInflatedUrl({ feedUrl: url, mapping });
|
|
23
23
|
|
|
24
|
-
const requestBuilder = new RequestBuilder();
|
|
25
|
-
|
|
26
24
|
const buildRequest = async (_url) => {
|
|
27
25
|
try {
|
|
28
26
|
const result = await requestBuilder.build(_url);
|
|
29
|
-
|
|
30
27
|
setRequestParams(result);
|
|
31
28
|
} catch (e) {
|
|
32
29
|
setError(e);
|
|
@@ -34,7 +31,7 @@ export function useBuildPipesUrl({
|
|
|
34
31
|
}
|
|
35
32
|
};
|
|
36
33
|
|
|
37
|
-
useEffect(() => {
|
|
34
|
+
React.useEffect(() => {
|
|
38
35
|
if (inflatedUrl && !requestParams) {
|
|
39
36
|
buildRequest(inflatedUrl);
|
|
40
37
|
}
|
|
@@ -42,7 +39,7 @@ export function useBuildPipesUrl({
|
|
|
42
39
|
|
|
43
40
|
return {
|
|
44
41
|
url: inflatedUrl || "",
|
|
45
|
-
requestParams
|
|
42
|
+
requestParams, // TODO should it be: || {} ?
|
|
46
43
|
error: error || undefined,
|
|
47
44
|
};
|
|
48
45
|
}
|
|
@@ -13,7 +13,11 @@ import {
|
|
|
13
13
|
import { HooksManager } from "@applicaster/zapp-react-native-utils/appUtils/HooksManager";
|
|
14
14
|
|
|
15
15
|
import { legacyScreenData } from "@applicaster/quick-brick-core/App/NavigationProvider/utils";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
LONG_KEY_PRESS_TIMEOUT,
|
|
18
|
+
QUICK_BRICK_NAVBAR,
|
|
19
|
+
QUICK_BRICK_CONTENT,
|
|
20
|
+
} from "@applicaster/quick-brick-core/const";
|
|
17
21
|
import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/ZappHooksModal";
|
|
18
22
|
import { ZappHookModalContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
|
|
19
23
|
import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
|
|
@@ -452,3 +456,30 @@ export const useIsScreenActive = () => {
|
|
|
452
456
|
|
|
453
457
|
return pathname === currentRoute;
|
|
454
458
|
};
|
|
459
|
+
|
|
460
|
+
type Return = (prefix: string) => string;
|
|
461
|
+
|
|
462
|
+
export const useUniqueRouteSuffix = (): Return => {
|
|
463
|
+
const pathname = usePathname();
|
|
464
|
+
const { currentRoute } = useNavigation();
|
|
465
|
+
|
|
466
|
+
const suffix = `currentRoute:${currentRoute}_pathname:${pathname}`;
|
|
467
|
+
|
|
468
|
+
const withUniqueRouteSuffix = React.useCallback((prefix: string) => {
|
|
469
|
+
return `${prefix}___${suffix}`;
|
|
470
|
+
}, []);
|
|
471
|
+
|
|
472
|
+
return withUniqueRouteSuffix;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
export const useNavbarId = () => {
|
|
476
|
+
const withUniqueRouteSuffix = useUniqueRouteSuffix();
|
|
477
|
+
|
|
478
|
+
return withUniqueRouteSuffix(QUICK_BRICK_NAVBAR);
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
export const useContentId = () => {
|
|
482
|
+
const withUniqueRouteSuffix = useUniqueRouteSuffix();
|
|
483
|
+
|
|
484
|
+
return withUniqueRouteSuffix(QUICK_BRICK_CONTENT);
|
|
485
|
+
};
|