@applicaster/zapp-react-native-utils 12.0.0-alpha.8417280639 → 12.0.0-alpha.8961274073

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.
@@ -7,6 +7,10 @@ import { sendHeaderClickEvent } from "./sendHeaderClickEvent";
7
7
  import { sendHardwareBackButtonClickEvent } from "./sendHardwareBackButtonClickEvent";
8
8
  import { sendDownloadsEvent } from "./sendDownloadsEvent";
9
9
  import { sendScreenEvent } from "./sendScreenEvent";
10
+ import {
11
+ sendSearchCanceledEvent,
12
+ sendSearchExecutedEvent,
13
+ } from "./searchEvents";
10
14
 
11
15
  export {
12
16
  sendScreenEvent,
@@ -18,4 +22,6 @@ export {
18
22
  sendHeaderClickEvent,
19
23
  sendHardwareBackButtonClickEvent,
20
24
  sendDownloadsEvent,
25
+ sendSearchCanceledEvent,
26
+ sendSearchExecutedEvent,
21
27
  };
@@ -0,0 +1,21 @@
1
+ import { log_debug } from "../logger";
2
+ import { postAnalyticEvent } from "../manager";
3
+ import { ANALYTICS_CORE_EVENTS } from "../events";
4
+
5
+ export const sendSearchCanceledEvent = (analyticsProps) => {
6
+ const eventName = ANALYTICS_CORE_EVENTS.SEARCH_CANCELED;
7
+ postAnalyticEvent(eventName, analyticsProps);
8
+
9
+ log_debug(`sendSearchCanceledEvent: send event: ${eventName}`, {
10
+ analyticsProps,
11
+ });
12
+ };
13
+
14
+ export const sendSearchExecutedEvent = (analyticsProps) => {
15
+ const eventName = ANALYTICS_CORE_EVENTS.SEARCH_EXECUTED;
16
+ postAnalyticEvent(eventName, analyticsProps);
17
+
18
+ log_debug(`sendSearchExecutedEvent: send event: ${eventName}`, {
19
+ analyticsProps,
20
+ });
21
+ };
@@ -23,10 +23,16 @@ export const TAPPING_EVENTS = {
23
23
  TAP_NAVBAR_BACK_BUTTON: "tap_navbar_back_button",
24
24
  };
25
25
 
26
+ export const SEARCH_EVENTS = {
27
+ SEARCH_EXECUTED: "search_executed",
28
+ SEARCH_CANCELED: "search_canceled",
29
+ };
30
+
26
31
  export const ANALYTICS_CORE_EVENTS = {
27
32
  ...LEGACY_EVENTS,
28
33
  ...SCREEN_VIEW_EVENTS,
29
34
  ...TAPPING_EVENTS,
35
+ ...SEARCH_EVENTS,
30
36
  };
31
37
 
32
38
  export const WEBVIEW_EVENTS = {
@@ -18,6 +18,8 @@ import {
18
18
  sendHardwareBackButtonClickEvent,
19
19
  sendDownloadsEvent,
20
20
  sendScreenEvent,
21
+ sendSearchCanceledEvent,
22
+ sendSearchExecutedEvent,
21
23
  } from "./AnalyticsEvents";
22
24
 
23
25
  import { postAnalyticEvent } from "./manager";
@@ -66,6 +68,8 @@ type AnalyticsFunctions = {
66
68
  item: ZappEntry;
67
69
  state: OfflineItemState;
68
70
  }) => void;
71
+ sendSearchCanceledEvent: (props: AnalyticsDefaultProperties) => void;
72
+ sendSearchExecutedEvent: (props: AnalyticsDefaultProperties) => void;
69
73
  };
70
74
 
71
75
  type GetAnalyticsFunctions = (
@@ -102,6 +106,8 @@ export function getAnalyticsFunctions({
102
106
  sendHardwareBackButtonClickEvent,
103
107
  sendScreenEvent,
104
108
  sendDownloadsEvent,
109
+ sendSearchCanceledEvent,
110
+ sendSearchExecutedEvent,
105
111
  };
106
112
  }
107
113
 
@@ -727,4 +727,5 @@ export class Player {
727
727
  }
728
728
 
729
729
  public supportsNativeControls = (): boolean => false;
730
+ public supportNativeCast = (): boolean => false;
730
731
  }
@@ -295,4 +295,9 @@ export class PlayerNative extends Player {
295
295
 
296
296
  supportsNativeControls = (): boolean =>
297
297
  toBooleanWithDefaultFalse(this.getConfig()?.["supports_native_controls"]);
298
+
299
+ supportNativeCast = (): boolean =>
300
+ toBooleanWithDefaultFalse(
301
+ this.playerPluginId === "quick-brick-player-brightcove"
302
+ );
298
303
  }
@@ -24,7 +24,6 @@ import {
24
24
  EMPTY,
25
25
  FALLBACK,
26
26
  TRANSFORM,
27
- defaultColor,
28
27
  } from "./fixtures";
29
28
 
30
29
  describe("getKeyForLabel", () => {
@@ -395,9 +394,7 @@ describe("getColorFromData", () => {
395
394
  it("should return value directly if valid color", () => {
396
395
  const result = getColorFromData({
397
396
  data: entry,
398
- key: "background_color",
399
- getValue: getValue(componentStyle),
400
- defaultColor,
397
+ valueFromLayout: "#123456",
401
398
  });
402
399
 
403
400
  expect(result).toBe("#123456");
@@ -406,9 +403,7 @@ describe("getColorFromData", () => {
406
403
  it("should return valid rgba color name", () => {
407
404
  const result = getColorFromData({
408
405
  data: entry,
409
- key: "component_background_color",
410
- getValue: getValue(componentStyle),
411
- defaultColor,
406
+ valueFromLayout: "rgba(255, 255, 255, 0)",
412
407
  });
413
408
 
414
409
  expect(result).toBe("rgba(255, 255, 255, 0)");
@@ -417,33 +412,27 @@ describe("getColorFromData", () => {
417
412
  it("should return path value if path value is set and is valid color", () => {
418
413
  const result = getColorFromData({
419
414
  data: entry,
420
- key: VAL.colorKeyPath,
421
- getValue: getValue(componentStyle),
422
- defaultColor,
415
+ valueFromLayout: "background_color",
423
416
  });
424
417
 
425
418
  expect(result).toBe("#123456");
426
419
  });
427
420
 
428
- it("should return defaultColor if key value and path value are invalid", () => {
421
+ it("should return path value if path value is set, nested and is valid color", () => {
429
422
  const result = getColorFromData({
430
423
  data: entry,
431
- key: VAL.invalidColorKey,
432
- getValue: getValue(componentStyle),
433
- defaultColor,
424
+ valueFromLayout: "extensions.background_color",
434
425
  });
435
426
 
436
- expect(result).toBe(defaultColor);
427
+ expect(result).toBe("#123456");
437
428
  });
438
429
 
439
- it("should return defaultColor if path does not exist", () => {
430
+ it("should return valueFromLayout if path does not exist", () => {
440
431
  const result = getColorFromData({
441
432
  data: entry,
442
- key: VAL.invalidColorKeyPath,
443
- getValue: getValue(componentStyle),
444
- defaultColor,
433
+ valueFromLayout: "invalid_color",
445
434
  });
446
435
 
447
- expect(result).toBe(defaultColor);
436
+ expect(result).toBe("invalid_color");
448
437
  });
449
438
  });
@@ -447,30 +447,24 @@ export const toResizeMode = (
447
447
 
448
448
  type GetColorFromData = {
449
449
  data: Record<string, any>;
450
- key: string;
451
- getValue: Function;
452
- defaultColor: string;
450
+ valueFromLayout: string;
453
451
  };
454
452
 
455
453
  export const getColorFromData = ({
456
454
  data,
457
- key,
458
- getValue,
459
- defaultColor,
455
+ valueFromLayout,
460
456
  }: GetColorFromData): string => {
461
- const value = getValue(key);
462
-
463
457
  // Temporary hack to fix color validation when alpha is floating point number
464
458
  // https://github.com/dreamyguy/validate-color/issues/44
465
- if (validateColor(value.replace(".00", ""))) {
466
- return value;
459
+ if (validateColor(valueFromLayout.replace(".00", ""))) {
460
+ return valueFromLayout;
467
461
  }
468
462
 
469
- const pathValue = R.path(value.split("."), data);
463
+ const pathValue = R.path(valueFromLayout.split("."), data);
470
464
 
471
465
  if (pathValue && validateColor(pathValue)) {
472
466
  return pathValue;
473
467
  }
474
468
 
475
- return defaultColor;
469
+ return valueFromLayout;
476
470
  };
@@ -0,0 +1,11 @@
1
+ import dayjs from "dayjs";
2
+ import utc from "dayjs/plugin/utc";
3
+ import timezone from "dayjs/plugin/timezone";
4
+ import isoWeek from "dayjs/plugin/isoWeek";
5
+
6
+ // Extend dayjs with plugins
7
+ dayjs.extend(utc);
8
+ dayjs.extend(timezone);
9
+ dayjs.extend(isoWeek);
10
+
11
+ export { dayjs };
@@ -28,5 +28,6 @@ export const getAppDataHeaders = (screenId: string) => {
28
28
  "x-applicaster-signed-device-info-token": appData.signedDeviceInfoToken,
29
29
  "x-applicaster-device-make": appData.deviceMake,
30
30
  "x-applicaster-device-model": appData.deviceModel,
31
+ "x-applicaster-uuid": appData?.uuid,
31
32
  };
32
33
  };
@@ -1,4 +1,4 @@
1
- import { last, head, isNil, includes, all, pathOr } from "ramda";
1
+ import { last, head, isNil, includes, pathOr } from "ramda";
2
2
 
3
3
  const stripBrackets = (path) => {
4
4
  const withoutPrepened = last(path.split("{{"));
@@ -13,15 +13,15 @@ const hasValue = (context) => (path) => {
13
13
  return !isNil(value);
14
14
  };
15
15
 
16
- export const isEnoughDataToInflateString = (context, string): boolean => {
16
+ export const getMissingValues = (context, string): string[] => {
17
17
  if (!includes("{{", string)) {
18
- return true;
18
+ return [];
19
19
  }
20
20
 
21
21
  const SEARCH_REGEX = new RegExp(/{{.*?}}/g);
22
22
  const paths = string.toString().match(SEARCH_REGEX);
23
23
 
24
- return all(hasValue(context), paths);
24
+ return paths.filter((path) => !hasValue(context)(path));
25
25
  };
26
26
 
27
27
  export const isNonInflatedUrl = (url: string): boolean =>
@@ -75,3 +75,7 @@ export const isLikeNumber = (value: any): boolean => {
75
75
 
76
76
  export const isZero = (value: unknown): boolean =>
77
77
  Object.is(value, -0) || Object.is(value, 0);
78
+
79
+ export const isMinusZero = (value: number) => {
80
+ return 1 / value === -Infinity;
81
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "12.0.0-alpha.8417280639",
3
+ "version": "12.0.0-alpha.8961274073",
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.8417280639",
31
+ "@applicaster/applicaster-types": "12.0.0-alpha.8961274073",
32
32
  "buffer": "^5.2.1",
33
33
  "camelize": "^1.0.0",
34
34
  "create-react-context": "^0.2.3",
@@ -12,9 +12,21 @@ const {
12
12
 
13
13
  const { reactHooksLogger } = require("../../logger");
14
14
 
15
+ let mockStore;
16
+
15
17
  describe("getInflatedDataSourceUrl", () => {
16
18
  beforeEach(() => {
17
19
  reactHooksLogger.warning.mockClear();
20
+
21
+ const {
22
+ appStore,
23
+ } = require("@applicaster/zapp-react-native-redux/AppStore");
24
+
25
+ mockStore = {
26
+ getState: jest.fn().mockReturnValue({ pipesEndpoints: {} }),
27
+ };
28
+
29
+ appStore.set(mockStore);
18
30
  });
19
31
 
20
32
  it("returns the url inflated with the right params", () => {
@@ -72,7 +84,8 @@ describe("getInflatedDataSourceUrl", () => {
72
84
  expect(reactHooksLogger.warning).toHaveBeenCalledTimes(1);
73
85
 
74
86
  expect(reactHooksLogger.warning).toHaveBeenCalledWith({
75
- message: "Not enough data to fully inflate url",
87
+ message:
88
+ "Not enough data to fully inflate url: {{entry.id}} source: http://foo.com/show/seriesId={{seriesId}}",
76
89
  data: { source, contexts, mapping },
77
90
  });
78
91
  });
@@ -107,7 +120,8 @@ describe("getInflatedDataSourceUrl", () => {
107
120
  expect(reactHooksLogger.warning).toHaveBeenCalledTimes(1);
108
121
 
109
122
  expect(reactHooksLogger.warning).toHaveBeenCalledWith({
110
- message: "Not enough data to fully inflate url",
123
+ message:
124
+ "Not enough data to fully inflate url: {{entry.extensions.groupName}} source: https://zapp-1689-emmys.web.app/emmys/group-categories/{{groupName}}",
111
125
  data: { source, contexts, mapping },
112
126
  });
113
127
  });
@@ -148,7 +162,8 @@ describe("getInflatedDataSourceUrl", () => {
148
162
  expect(reactHooksLogger.warning).toHaveBeenCalledTimes(1);
149
163
 
150
164
  expect(reactHooksLogger.warning).toHaveBeenCalledWith({
151
- message: "Not enough data to fully inflate url",
165
+ message:
166
+ "Not enough data to fully inflate url: {{entry.extensions.groupName2}} source: https://zapp-1689-emmys.web.app/emmys/group-categories/{{groupName1}}?test={{groupName2}}",
152
167
  data: { source, contexts, mapping },
153
168
  });
154
169
  });
@@ -9,9 +9,15 @@ import {
9
9
 
10
10
  import { inflateString } from "../../stringUtils";
11
11
  import { useRoute } from "../navigation";
12
- import { isEnoughDataToInflateString } from "../../inflatedUrlUtils";
12
+ import { getMissingValues } from "../../inflatedUrlUtils";
13
13
  import { reactHooksLogger as logger } from "../logger";
14
14
  import { isTV } from "../../reactUtils";
15
+ import {
16
+ findEndpointForURL,
17
+ HTTP_METHODS,
18
+ } from "@applicaster/zapp-pipes-v2-client";
19
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
20
+ import { ENDPOINT_TAGS } from "../../types";
15
21
 
16
22
  /**
17
23
  * will match any occurrence in a string of one or more word characters
@@ -105,24 +111,47 @@ export const getInflatedDataSourceUrl: GetInflatedDataSourceUrl = ({
105
111
  R.keys(mapping)
106
112
  );
107
113
 
108
- if (!isEnoughDataToInflateString(contexts, string)) {
109
- const message = "Not enough data to fully inflate url";
114
+ const missingValues = getMissingValues(contexts, string);
110
115
 
111
- // todo: add js only payload instead
112
- if (__DEV__) {
113
- logger.warning({
114
- message,
115
- data: { source, contexts, mapping },
116
- });
116
+ if (missingValues && missingValues.length > 0) {
117
+ const pipesEndpoints = appStore.get("pipesEndpoints");
118
+
119
+ const endpointURL = findEndpointForURL(
120
+ source,
121
+ pipesEndpoints,
122
+ HTTP_METHODS.GET
123
+ );
124
+
125
+ const tags = pipesEndpoints?.[endpointURL]?.tags;
126
+ const allowMissingKeys = tags?.includes(ENDPOINT_TAGS.allow_missing_keys);
127
+
128
+ if (!allowMissingKeys) {
129
+ const message = `Not enough data to fully inflate url: ${missingValues.join(
130
+ ","
131
+ )} source: ${source}`;
132
+
133
+ // todo: add js only payload instead
134
+ if (__DEV__) {
135
+ logger.warning({
136
+ message,
137
+ data: { source, contexts, mapping },
138
+ });
139
+ } else {
140
+ // todo: log values we've failed to resolve
141
+ logger.warning({
142
+ message,
143
+ });
144
+ }
145
+
146
+ // return null in order to prevent start fetching data with invalid url
147
+ return null;
117
148
  } else {
118
- // todo: log values we've failed to resolve
119
- logger.warning({
120
- message,
149
+ logger.debug({
150
+ message: `Ignoring missing values in url because ${
151
+ ENDPOINT_TAGS.allow_missing_keys
152
+ } endpoint tag, keys: ${missingValues.join(",")}`,
121
153
  });
122
154
  }
123
-
124
- // return null in order to prevent start fetching data with invalid url
125
- return null;
126
155
  }
127
156
 
128
157
  return inflateString({ string, data: contexts });
@@ -15,8 +15,21 @@ export const mockMenu: ZappNavigation = {
15
15
  // preset_id?: null | unknown;
16
16
  };
17
17
 
18
+ export const useRoute = jest.fn(() => ({
19
+ screenData: {
20
+ name: "someName",
21
+ },
22
+ pathname: "someRoute",
23
+ }));
24
+
18
25
  export const useNavigation = jest.fn(() => ({
19
26
  data: {},
27
+ screenData: {
28
+ name: "someName",
29
+ },
30
+ activeRiver: {
31
+ id: "someId",
32
+ },
20
33
  currentRoute: "someRoute",
21
34
  }));
22
35
 
@@ -1,4 +1,7 @@
1
1
  export const useNavigation = jest.fn(() => ({
2
2
  data: {},
3
+ screenData: {
4
+ name: "someName",
5
+ },
3
6
  currentRoute: "someRoute",
4
7
  }));
@@ -6,7 +6,7 @@ import { useNavigation } from "../navigation/useNavigation";
6
6
 
7
7
  import { ScreenContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenContext";
8
8
 
9
- function useScreenContextV2() {
9
+ export function useScreenContextV2() {
10
10
  return useContext(ScreenContext);
11
11
  }
12
12
 
@@ -49,11 +49,3 @@ export function useScreenState(
49
49
  setSelectedEntry: (entryId) => setSelectedEntry(screenId, entryId),
50
50
  };
51
51
  }
52
-
53
- export const useScreenScrollStore = create<{
54
- scrollPosition: number;
55
- scrollingVelocity: number;
56
- }>(() => ({
57
- scrollPosition: 0,
58
- scrollingVelocity: 0,
59
- }));
@@ -152,3 +152,8 @@ export function objectToCamelCase(obj) {
152
152
  export function isString(value: unknown): value is string {
153
153
  return typeof value === "string" || value instanceof String;
154
154
  }
155
+
156
+ /** Checks if a value is a non-empty string */
157
+ export function isNotEmptyString(value: unknown): value is string {
158
+ return isString(value) && value.length > 0;
159
+ }
@@ -0,0 +1,14 @@
1
+ export const getByTestId = (wrapper, testId) => {
2
+ try {
3
+ if (wrapper?.getByTestId) {
4
+ return wrapper?.queryAllByTestId(testId)?.[0] ?? undefined;
5
+ } else if (wrapper?.root?.findByProps) {
6
+ return wrapper.root.findAllByProps({ testID: testId })?.[0] ?? undefined;
7
+ }
8
+ } catch (e) {
9
+ // eslint-disable-next-line no-console
10
+ console.log("getByTestId E:", e);
11
+
12
+ return undefined;
13
+ }
14
+ };
@@ -1,6 +1,6 @@
1
1
  import * as R from "ramda";
2
2
 
3
- import React from "react";
3
+ import React, { PropsWithChildren } from "react";
4
4
  import { View } from "react-native";
5
5
 
6
6
  import { Provider } from "react-redux";
@@ -11,6 +11,8 @@ import { SafeAreaProvider } from "react-native-safe-area-context";
11
11
  import { render } from "@testing-library/react-native";
12
12
  import { AnalyticsProvider } from "../analyticsUtils";
13
13
 
14
+ export { getByTestId } from "./getByTestId";
15
+
14
16
  /**
15
17
  * Function to be used in tandem with enzyme findWhere(node => node.prop('testID') === 'yourID')
16
18
  * Example:
@@ -21,17 +23,6 @@ import { AnalyticsProvider } from "../analyticsUtils";
21
23
  export const testIDEquals = (testId: any): boolean =>
22
24
  R.compose(R.equals(testId), R.invoker(1, "prop")("testID"));
23
25
 
24
- export const getByTestId = (wrapper, testId) => {
25
- try {
26
- return wrapper?.queryAllByTestId(testId)?.[0] ?? undefined;
27
- } catch (e) {
28
- // eslint-disable-next-line no-console
29
- console.log("getByTestId E:", e);
30
-
31
- return undefined;
32
- }
33
- };
34
-
35
26
  /** Helper function for react-test-renderer, accepts type and renderer
36
27
  * * @param {*} type React Component type instance or name of component
37
28
  * * @param {*} renderer renderer instance
@@ -47,11 +38,11 @@ export const findByType = (type, renderer) => {
47
38
  export function createComponentMock(mockName, hasChildren = false) {
48
39
  return hasChildren
49
40
  ? jest.fn(({ children }) => (
50
- <View comp={mockName} testID={mockName}>
41
+ <View testID={mockName}>
51
42
  {typeof children === "function" ? children() : children}
52
43
  </View>
53
44
  ))
54
- : jest.fn(() => <View comp={mockName} testID={mockName} />);
45
+ : jest.fn(() => <View testID={mockName} />);
55
46
  }
56
47
 
57
48
  const initialState = {
@@ -64,14 +55,21 @@ const initialState = {
64
55
  AppContainer: createComponentMock("appContainer", true),
65
56
  DisplayError: createComponentMock("displayError"),
66
57
  },
67
- rivers: [{ id: "A1234", home: true }],
58
+ rivers: { id: "A1234", home: true },
68
59
  appSettings: {},
69
60
  plugins: [],
70
- appState: { appReady: true },
61
+ appState: { appReady: true, appLaunched: true },
62
+ appData: { layoutVersion: "v1" },
63
+ contentTypes: null,
71
64
  };
72
65
 
73
- export const WrappedWithProviders = ({ children, store = {} }: any) => {
74
- const _store = configureStore([thunk])({ ...initialState, ...store });
66
+ export const WrappedWithProviders = ({
67
+ children,
68
+ store: storeObj = {},
69
+ }: any) => {
70
+ const _store = configureStore([thunk])(
71
+ R.mergeDeepWith(R.concat, { ...initialState }, storeObj)
72
+ );
75
73
 
76
74
  return (
77
75
  <SafeAreaProvider
@@ -93,8 +91,12 @@ export const WrappedWithProviders = ({ children, store = {} }: any) => {
93
91
  * @param store optional store to pass to the provider
94
92
  * @returns
95
93
  */
96
- export const renderWithProviders = (component, store?: unknown) => {
97
- return render(
98
- <WrappedWithProviders store={store}>{component}</WrappedWithProviders>
99
- );
94
+ export const renderWithProviders = (component, storeObj?: unknown) => {
95
+ return render(component, {
96
+ wrapper: function TestWrapper({ children }: PropsWithChildren<any>) {
97
+ return (
98
+ <WrappedWithProviders store={storeObj}>{children}</WrappedWithProviders>
99
+ );
100
+ },
101
+ });
100
102
  };
package/types.ts CHANGED
@@ -5,3 +5,7 @@ export type ManifestField<T> = {
5
5
  group?: boolean;
6
6
  fields?: ManifestField<any>[];
7
7
  };
8
+
9
+ export enum ENDPOINT_TAGS {
10
+ allow_missing_keys = "allow_missing_keys",
11
+ }