@applicaster/zapp-react-native-ui-components 16.0.0-rc.11 → 16.0.0-rc.111

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 (156) hide show
  1. package/Components/AudioPlayer/tv/__tests__/audioPlayer.test.js +19 -0
  2. package/Components/AudioPlayer/tv/__tests__/index.test.tsx +120 -0
  3. package/Components/AudioPlayer/tv/index.tsx +5 -1
  4. package/Components/Cell/Cell.tsx +3 -6
  5. package/Components/Cell/TvOSCellComponent.tsx +0 -1
  6. package/Components/CellRendererResolver/index.ts +2 -2
  7. package/Components/ComponentResolver/__tests__/componentResolver.test.js +1 -1
  8. package/Components/FocusableGroup/FocusableTvOS.tsx +11 -7
  9. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +54 -0
  10. package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
  11. package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
  12. package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
  13. package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
  14. package/Components/GeneralContentScreen/hookAdapter/__tests__/skipExpression.test.ts +294 -0
  15. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +188 -0
  16. package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
  17. package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
  18. package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
  19. package/Components/GeneralContentScreen/hookAdapter/skipExpression.ts +252 -0
  20. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +90 -0
  21. package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
  22. package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
  23. package/Components/GeneralContentScreen/index.ts +2 -0
  24. package/Components/Layout/FullWidthRow.tsx +38 -0
  25. package/Components/LinkHandler/LinkHandler.tsx +2 -7
  26. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +5 -1
  27. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +47 -21
  28. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +19 -4
  29. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +43 -1
  30. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
  31. package/Components/MasterCell/DefaultComponents/Button.tsx +1 -0
  32. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/__tests__/withDimensions.test.ts +420 -125
  33. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/index.ts +114 -19
  34. package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +19 -8
  35. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  36. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +2 -4
  37. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +2 -3
  38. package/Components/MasterCell/DefaultComponents/PressableView.tsx +29 -9
  39. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +4 -0
  40. package/Components/MasterCell/DefaultComponents/Toggle.tsx +36 -14
  41. package/Components/MasterCell/DefaultComponents/__tests__/Toggle.test.tsx +167 -0
  42. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +154 -100
  43. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +15 -4
  44. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +56 -22
  45. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +6 -4
  46. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +3 -1
  47. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +32 -21
  48. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +15 -4
  49. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +23 -16
  50. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +8 -3
  51. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +2 -2
  52. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  53. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +2 -2
  54. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/factory.test.ts +32 -0
  55. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  56. package/Components/MasterCell/dataAdapter.ts +15 -5
  57. package/Components/MasterCell/hooks/index.ts +2 -0
  58. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  59. package/Components/MasterCell/index.tsx +30 -5
  60. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +169 -132
  61. package/Components/MasterCell/utils/__tests__/resolveColorForProp.test.js +92 -0
  62. package/Components/MasterCell/utils/index.ts +85 -46
  63. package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +56 -0
  64. package/Components/ModalComponent/ActionsBottomSheet/__tests__/openActionsBottomSheet.test.ts +38 -0
  65. package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +24 -0
  66. package/Components/ModalComponent/ActionsBottomSheet/index.ts +9 -0
  67. package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +61 -0
  68. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +392 -0
  69. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +772 -0
  70. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +728 -0
  71. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +1028 -0
  72. package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +205 -0
  73. package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +164 -0
  74. package/Components/ModalComponent/AudioPlayer/Components/__tests__/NowPlayingHeaderSection.test.tsx +36 -0
  75. package/Components/ModalComponent/AudioPlayer/Components/index.ts +9 -0
  76. package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +393 -0
  77. package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +523 -0
  78. package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
  79. package/Components/ModalComponent/BottomSheetModalContent.tsx +436 -50
  80. package/Components/ModalComponent/Button/index.tsx +5 -3
  81. package/Components/ModalComponent/Header/CloseButton.tsx +12 -0
  82. package/Components/ModalComponent/Header/__tests__/CloseButton.test.tsx +72 -0
  83. package/Components/ModalComponent/Header/index.tsx +20 -7
  84. package/Components/ModalComponent/Header/utils.ts +1 -1
  85. package/Components/ModalComponent/ModalBlocksStyleContext.tsx +10 -0
  86. package/Components/ModalComponent/SortableList.tsx +156 -0
  87. package/Components/ModalComponent/SortableList.web.tsx +34 -0
  88. package/Components/ModalComponent/TextInputContent.tsx +135 -0
  89. package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
  90. package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +343 -0
  91. package/Components/ModalComponent/__tests__/SortableList.web.test.tsx +16 -0
  92. package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +169 -0
  93. package/Components/ModalComponent/__tests__/showTextInput.test.ts +95 -0
  94. package/Components/ModalComponent/index.tsx +18 -9
  95. package/Components/ModalComponent/presets/__tests__/getCell.test.ts +90 -0
  96. package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +78 -0
  97. package/Components/ModalComponent/presets/index.ts +27 -0
  98. package/Components/ModalComponent/presets/selectableCell.tsx +28 -0
  99. package/Components/ModalComponent/presets/standardCell.tsx +41 -0
  100. package/Components/ModalComponent/presets/types.ts +35 -0
  101. package/Components/ModalComponent/showTextInput.ts +52 -0
  102. package/Components/ModalComponent/utils.ts +82 -21
  103. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
  104. package/Components/OfflineHandler/__tests__/index.test.tsx +186 -17
  105. package/Components/OfflineHandler/hooks.ts +111 -0
  106. package/Components/OfflineHandler/index.tsx +91 -75
  107. package/Components/OfflineHandler/utils/index.ts +1 -13
  108. package/Components/PlayerContainer/PlayerContainer.tsx +33 -18
  109. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -4
  110. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +337 -0
  111. package/Components/PreloaderWrapper/index.tsx +1 -1
  112. package/Components/River/ComponentsMap/ComponentsMap.tsx +87 -61
  113. package/Components/River/River.tsx +4 -0
  114. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +140 -89
  115. package/Components/River/__tests__/componentsMap.test.js +8 -3
  116. package/Components/River/__tests__/resultCallback.test.tsx +47 -0
  117. package/Components/Screen/__tests__/Screen.test.tsx +24 -0
  118. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -0
  119. package/Components/Screen/index.tsx +13 -4
  120. package/Components/Screen/navigationHandler.ts +8 -8
  121. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
  122. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
  123. package/Components/ScreenRevealManager/ScreenRevealManager.ts +18 -2
  124. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +36 -0
  125. package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +56 -0
  126. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +21 -4
  127. package/Components/TopCutoffOverlay/index.tsx +2 -2
  128. package/Components/Transitioner/AnimationManager.js +1 -1
  129. package/Components/Transitioner/Scene.tsx +4 -3
  130. package/Components/Transitioner/Transitioner.tsx +98 -49
  131. package/Components/Transitioner/__tests__/transitioner.test.js +372 -3
  132. package/Components/VideoLive/LiveImageManager.ts +9 -3
  133. package/Components/VideoLive/__tests__/LiveImageManager.test.ts +59 -0
  134. package/Components/VideoModal/utils.ts +6 -1
  135. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +12 -5
  136. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  137. package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
  138. package/Contexts/CachedDimensionsContext/index.ts +17 -2
  139. package/Contexts/ModalNavigationContext/__tests__/index.test.tsx +15 -0
  140. package/Contexts/ModalNavigationContext/index.tsx +2 -2
  141. package/Helpers/ComponentCellSelectionHelper/index.js +0 -6
  142. package/Helpers/index.js +0 -39
  143. package/Hooks/useEntryActionsExpander.ts +63 -0
  144. package/assets/empty-Info.png +0 -0
  145. package/package.json +6 -5
  146. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  147. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  148. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  149. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -66
  150. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  151. package/Components/Screen/utils.ts +0 -16
  152. package/Helpers/Analytics/index.js +0 -95
  153. /package/Components/BackgroundImage/{BackgroundImage.tv.android.tsx → BackgroundImage.android.tv.tsx} +0 -0
  154. /package/Components/BackgroundImage/{BackgroundImage.tv.ios.tsx → BackgroundImage.ios.tv.tsx} +0 -0
  155. /package/Components/MasterCell/DefaultComponents/ButtonContainerView/{index.tv.android.tsx → index.android.tv.tsx} +0 -0
  156. /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.js → Hero.tsx} +0 -0
@@ -1,8 +1,15 @@
1
1
  import React from "react";
2
- import { Text, View } from "react-native";
2
+ import { Animated, Text, View } from "react-native";
3
+ import { v4 as uuidV4 } from "uuid";
3
4
 
4
- import { TransitionerComponent } from "../Transitioner";
5
- import { render } from "@testing-library/react-native";
5
+ import { AnimationManager } from "../AnimationManager";
6
+ import {
7
+ NAV_ACTION_BACK,
8
+ NAV_ACTION_PUSH,
9
+ NAV_ACTION_REPLACE,
10
+ TransitionerComponent,
11
+ } from "../Transitioner";
12
+ import { act, render } from "@testing-library/react-native";
6
13
 
7
14
  jest.mock("../Scene", () => {
8
15
  const View = jest.requireActual("react-native").View;
@@ -40,6 +47,47 @@ const transitionerComponentProps = {
40
47
 
41
48
  jest.mock("uuid");
42
49
 
50
+ const sceneTransitionConfig = () => ({
51
+ push: {
52
+ duration: 0,
53
+ easing: jest.fn(),
54
+ from: { style: {} },
55
+ to: { style: {} },
56
+ },
57
+ back: {
58
+ duration: 0,
59
+ easing: jest.fn(),
60
+ from: { style: {} },
61
+ to: { style: {} },
62
+ },
63
+ });
64
+
65
+ const sceneFor = (route) => (
66
+ <View pathname={route}>
67
+ <Text>{route}</Text>
68
+ </View>
69
+ );
70
+
71
+ const root = "/river/root";
72
+ const child = `${root}/river/child`;
73
+ const grandchild = `${child}/river/grandchild`;
74
+
75
+ const propsFor = (route, previousAction, children = sceneFor(route)) => ({
76
+ navigator: {
77
+ previousAction,
78
+ currentRoute: route,
79
+ screenData: {},
80
+ },
81
+ transitionConfig: sceneTransitionConfig,
82
+ layoutData: {
83
+ isTablet: false,
84
+ isTabletPortrait: false,
85
+ },
86
+ children,
87
+ });
88
+
89
+ const sceneKeys = (ref) => ref.current.state.scenes.map((scene) => scene.key);
90
+
43
91
  describe("<TransitionerComponent />", () => {
44
92
  it("renders correctly", () => {
45
93
  const wrapper = render(
@@ -50,4 +98,325 @@ describe("<TransitionerComponent />", () => {
50
98
 
51
99
  expect(wrapper.toJSON()).toMatchSnapshot();
52
100
  });
101
+
102
+ it("removes both intermediate scenes when backing out to the tab root", () => {
103
+ jest
104
+ .spyOn(AnimationManager.prototype, "animate")
105
+ .mockImplementation((_, done) => done());
106
+
107
+ const ref = React.createRef();
108
+
109
+ const { rerender } = render(
110
+ <TransitionerComponent
111
+ ref={ref}
112
+ {...propsFor(root, NAV_ACTION_REPLACE)}
113
+ />
114
+ );
115
+
116
+ act(() => {
117
+ rerender(
118
+ <TransitionerComponent
119
+ ref={ref}
120
+ {...propsFor(child, NAV_ACTION_PUSH)}
121
+ />
122
+ );
123
+ });
124
+
125
+ act(() => {
126
+ rerender(
127
+ <TransitionerComponent
128
+ ref={ref}
129
+ {...propsFor(grandchild, NAV_ACTION_PUSH)}
130
+ />
131
+ );
132
+ });
133
+
134
+ act(() => {
135
+ rerender(
136
+ <TransitionerComponent ref={ref} {...propsFor(root, NAV_ACTION_BACK)} />
137
+ );
138
+ });
139
+
140
+ expect(sceneKeys(ref)).toEqual([root]);
141
+ });
142
+
143
+ describe("with interruptible animations", () => {
144
+ // End callback of the animation that is currently "running"
145
+ let runningAnimationEnd = null;
146
+
147
+ const finishAnimation = () => {
148
+ act(() => {
149
+ const done = runningAnimationEnd;
150
+ runningAnimationEnd = null;
151
+ done?.();
152
+ });
153
+ };
154
+
155
+ const mountTransitioner = ({
156
+ initialRoute = root,
157
+ renderScene = sceneFor,
158
+ } = {}) => {
159
+ const ref = React.createRef();
160
+
161
+ const transitioner = (route, action) => (
162
+ <TransitionerComponent
163
+ ref={ref}
164
+ {...propsFor(route, action, renderScene(route))}
165
+ />
166
+ );
167
+
168
+ const { rerender } = render(
169
+ transitioner(initialRoute, NAV_ACTION_REPLACE)
170
+ );
171
+
172
+ const navigate = (route, action) => {
173
+ act(() => {
174
+ rerender(transitioner(route, action));
175
+ });
176
+ };
177
+
178
+ return { ref, navigate };
179
+ };
180
+
181
+ beforeEach(() => {
182
+ let sceneCount = 0;
183
+ uuidV4.mockImplementation(() => `scene-${++sceneCount}`);
184
+
185
+ jest
186
+ .spyOn(AnimationManager.prototype, "animate")
187
+ .mockImplementation((_, done) => {
188
+ runningAnimationEnd = done;
189
+ });
190
+
191
+ const setValue = Animated.Value.prototype.setValue;
192
+
193
+ // RN 0.77: setValue() stops the running animation, which calls
194
+ // its end callback synchronously with { finished: false }
195
+ jest
196
+ .spyOn(Animated.Value.prototype, "setValue")
197
+ .mockImplementation(function (value) {
198
+ const interrupted = runningAnimationEnd;
199
+ runningAnimationEnd = null;
200
+ interrupted?.();
201
+
202
+ return setValue.call(this, value);
203
+ });
204
+ });
205
+
206
+ afterEach(() => {
207
+ runningAnimationEnd = null;
208
+ jest.restoreAllMocks();
209
+ uuidV4.mockReset();
210
+ });
211
+
212
+ it("drops interrupted scenes when going back twice during a back animation", () => {
213
+ const { ref, navigate } = mountTransitioner();
214
+
215
+ navigate(child, NAV_ACTION_PUSH);
216
+ finishAnimation();
217
+ navigate(grandchild, NAV_ACTION_PUSH);
218
+ finishAnimation();
219
+
220
+ navigate(child, NAV_ACTION_BACK);
221
+ navigate(root, NAV_ACTION_BACK);
222
+
223
+ expect(ref.current.state.animating).toBe(true);
224
+
225
+ finishAnimation();
226
+
227
+ expect(sceneKeys(ref)).toEqual([root]);
228
+ expect(ref.current.state.animating).toBe(false);
229
+ });
230
+
231
+ it("remounts the screen when revisiting it after a quick double back", () => {
232
+ const mounts = {};
233
+
234
+ const MountProbe = ({ pathname }) => {
235
+ React.useEffect(() => {
236
+ mounts[pathname] = (mounts[pathname] || 0) + 1;
237
+ }, [pathname]);
238
+
239
+ return <Text>{pathname}</Text>;
240
+ };
241
+
242
+ const { ref, navigate } = mountTransitioner({
243
+ renderScene: (route) => <MountProbe pathname={route} />,
244
+ });
245
+
246
+ navigate(child, NAV_ACTION_PUSH);
247
+ finishAnimation();
248
+
249
+ const originalChildId = ref.current.state.scenes[1].screenUniqueId;
250
+
251
+ navigate(grandchild, NAV_ACTION_PUSH);
252
+ finishAnimation();
253
+ navigate(child, NAV_ACTION_BACK);
254
+ navigate(root, NAV_ACTION_BACK);
255
+ finishAnimation();
256
+
257
+ navigate(child, NAV_ACTION_PUSH);
258
+ finishAnimation();
259
+
260
+ const { scenes, to } = ref.current.state;
261
+
262
+ expect(scenes).toHaveLength(2);
263
+ expect(to.index).toBe(1);
264
+ expect(scenes[1].screenUniqueId).not.toBe(originalChildId);
265
+ expect(mounts[child]).toBe(2);
266
+ });
267
+
268
+ it("keeps a single scene when going back from the first scene", () => {
269
+ const { ref, navigate } = mountTransitioner({ initialRoute: child });
270
+
271
+ navigate(root, NAV_ACTION_BACK);
272
+ finishAnimation();
273
+
274
+ expect(sceneKeys(ref)).toEqual([root]);
275
+ expect(ref.current.state.to.index).toBe(0);
276
+ expect(ref.current.state.animating).toBe(false);
277
+ });
278
+
279
+ it("pushes a fresh scene after a completed single back", () => {
280
+ const { ref, navigate } = mountTransitioner();
281
+
282
+ navigate(child, NAV_ACTION_PUSH);
283
+ finishAnimation();
284
+
285
+ const originalChildId = ref.current.state.scenes[1].screenUniqueId;
286
+
287
+ navigate(root, NAV_ACTION_BACK);
288
+ finishAnimation();
289
+ navigate(child, NAV_ACTION_PUSH);
290
+ finishAnimation();
291
+
292
+ const { scenes, to } = ref.current.state;
293
+
294
+ expect(sceneKeys(ref)).toEqual([root, child]);
295
+ expect(to.index).toBe(1);
296
+ expect(scenes[1].screenUniqueId).not.toBe(originalChildId);
297
+ });
298
+
299
+ it("ignores the end of a back animation that a newer back superseded", () => {
300
+ const { ref, navigate } = mountTransitioner();
301
+
302
+ navigate(child, NAV_ACTION_PUSH);
303
+ finishAnimation();
304
+ navigate(grandchild, NAV_ACTION_PUSH);
305
+ finishAnimation();
306
+
307
+ navigate(child, NAV_ACTION_BACK);
308
+
309
+ const supersededEnd = runningAnimationEnd;
310
+
311
+ // Deliver the interrupted animation's end late, after the newer one started
312
+ Animated.Value.prototype.setValue.mockImplementation(() => {});
313
+
314
+ navigate(root, NAV_ACTION_BACK);
315
+
316
+ act(() => {
317
+ supersededEnd();
318
+ });
319
+
320
+ expect(ref.current.state.animating).toBe(true);
321
+ expect(sceneKeys(ref)).toEqual([root, child, grandchild]);
322
+
323
+ finishAnimation();
324
+
325
+ expect(sceneKeys(ref)).toEqual([root]);
326
+ expect(ref.current.state.animating).toBe(false);
327
+ });
328
+
329
+ it("drops the pushed scene when going back before its push animation ends", () => {
330
+ const { ref, navigate } = mountTransitioner();
331
+
332
+ navigate(child, NAV_ACTION_PUSH);
333
+ navigate(root, NAV_ACTION_BACK);
334
+
335
+ expect(ref.current.state.animating).toBe(true);
336
+
337
+ finishAnimation();
338
+
339
+ expect(sceneKeys(ref)).toEqual([root]);
340
+ expect(ref.current.state.animating).toBe(false);
341
+ });
342
+
343
+ it("pushes right after the visible scene even when stale scenes are left behind", () => {
344
+ const { ref, navigate } = mountTransitioner();
345
+
346
+ navigate(child, NAV_ACTION_PUSH);
347
+ finishAnimation();
348
+
349
+ // Leftover past to.index, as some other path could leave behind
350
+ act(() => {
351
+ ref.current.setState(({ scenes }) => ({
352
+ scenes: [
353
+ ...scenes,
354
+ {
355
+ ...scenes[1],
356
+ key: `${root}/river/stale`,
357
+ screenUniqueId: "stale",
358
+ },
359
+ ],
360
+ }));
361
+ });
362
+
363
+ navigate(grandchild, NAV_ACTION_PUSH);
364
+ finishAnimation();
365
+
366
+ const { scenes, to } = ref.current.state;
367
+
368
+ expect(sceneKeys(ref)).toEqual([root, child, grandchild]);
369
+ expect(scenes[to.index].key).toBe(grandchild);
370
+ });
371
+
372
+ it("shows the replaced screen when going home during a back animation", () => {
373
+ const home = "/river/home";
374
+ const { ref, navigate } = mountTransitioner();
375
+
376
+ navigate(child, NAV_ACTION_PUSH);
377
+ finishAnimation();
378
+
379
+ navigate(root, NAV_ACTION_BACK);
380
+ navigate(home, NAV_ACTION_REPLACE);
381
+
382
+ const { scenes, to } = ref.current.state;
383
+
384
+ expect(scenes[to.index].key).toBe(home);
385
+
386
+ finishAnimation();
387
+
388
+ expect(sceneKeys(ref)).toEqual([home]);
389
+ expect(ref.current.state.animating).toBe(false);
390
+ });
391
+
392
+ it("shows the pushed screen when pushing during a back animation", () => {
393
+ const sibling = `${root}/river/sibling`;
394
+ const { ref, navigate } = mountTransitioner();
395
+
396
+ navigate(child, NAV_ACTION_PUSH);
397
+ finishAnimation();
398
+
399
+ navigate(root, NAV_ACTION_BACK);
400
+ navigate(sibling, NAV_ACTION_PUSH);
401
+
402
+ const { scenes, to } = ref.current.state;
403
+
404
+ expect(scenes[to.index].key).toBe(sibling);
405
+
406
+ finishAnimation();
407
+
408
+ expect(sceneKeys(ref)).not.toContain(child);
409
+ expect(ref.current.state.animating).toBe(false);
410
+ });
411
+
412
+ it("keeps both scenes when pushing during a push animation", () => {
413
+ const { ref, navigate } = mountTransitioner();
414
+
415
+ navigate(child, NAV_ACTION_PUSH);
416
+ navigate(grandchild, NAV_ACTION_PUSH);
417
+
418
+ expect(sceneKeys(ref)).toEqual([child, grandchild]);
419
+ expect(ref.current.state.to.index).toBe(1);
420
+ });
421
+ });
53
422
  });
@@ -5,7 +5,7 @@ import {
5
5
  } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
6
6
  import { setUserCellPlayerMutedPreference } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/userCellPlayerMutedPreference";
7
7
  import { playerFactory } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerFactory";
8
- import { PlayerRole } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/conts";
8
+ import { PlayerRole } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/const";
9
9
  import { loggerLiveImageManager } from "./loggerHelper";
10
10
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
11
11
  import {
@@ -213,10 +213,16 @@ export class LiveImageManager implements PlayerLifecycleListener {
213
213
  log_debug(
214
214
  `pauseItem: live-image not ready, ${item.playerId} - ${item.tag}`
215
215
  );
216
- } else {
217
- item.player.pause();
218
216
  }
219
217
 
218
+ // Pause even when the player is not ready yet: the underlying player may
219
+ // still be loading and would otherwise start playing on its own once it
220
+ // has buffered, leaving the live-image audible underneath the primary
221
+ // player. Note this is best effort - the web controller's pause() only
222
+ // logs while the player component is unmounted - so the player itself also
223
+ // refuses buffer-driven starts when it was never asked to play.
224
+ item.player.pause();
225
+
220
226
  // Fake close event, because we unmount native view
221
227
  item.player.onPlayerClose();
222
228
  item.setMode?.(LiveImageType.Image);
@@ -1,6 +1,8 @@
1
1
  const { LiveImageManager } = require("../LiveImageManager");
2
2
 
3
3
  describe("LiveImageManager", () => {
4
+ const flushPromises = () => new Promise(setImmediate);
5
+
4
6
  it("should subscribe to LiveImageManager events", () => {
5
7
  const sampleListener = {
6
8
  id: "1",
@@ -78,4 +80,61 @@ describe("LiveImageManager", () => {
78
80
  expect(mockOnEnd1).toHaveBeenCalledWith(eventBody);
79
81
  expect(mockOnEnd2).toHaveBeenCalledWith(eventBody);
80
82
  });
83
+
84
+ it("should start a prepared live image when the mounted player loads", async () => {
85
+ const manager = new LiveImageManager();
86
+
87
+ const item = {
88
+ playerId: "live-image",
89
+ tag: "Live image",
90
+ player: {
91
+ playerId: "live-image",
92
+ playerState: { isReadyToPlay: false },
93
+ getEntry: jest.fn(),
94
+ play: jest.fn(),
95
+ },
96
+ getPlayer: jest.fn(),
97
+ prepareForPlayback: jest.fn().mockResolvedValue(true),
98
+ setMode: jest.fn(),
99
+ };
100
+
101
+ manager.playLiveImage(item);
102
+ await flushPromises();
103
+
104
+ expect(item.setMode).toHaveBeenCalledWith("video");
105
+ expect(item.player.play).not.toHaveBeenCalled();
106
+
107
+ manager.onLiveImageVideoLoaded(item);
108
+
109
+ expect(item.player.play).toHaveBeenCalledTimes(1);
110
+ });
111
+
112
+ it("should not start a live image that was paused before the player loaded", async () => {
113
+ const manager = new LiveImageManager();
114
+
115
+ const item = {
116
+ playerId: "live-image",
117
+ tag: "Live image",
118
+ player: {
119
+ playerId: "live-image",
120
+ playerState: { isReadyToPlay: false },
121
+ getEntry: jest.fn(),
122
+ play: jest.fn(),
123
+ pause: jest.fn(),
124
+ onPlayerClose: jest.fn(),
125
+ },
126
+ getPlayer: jest.fn(),
127
+ prepareForPlayback: jest.fn().mockResolvedValue(true),
128
+ setMode: jest.fn(),
129
+ };
130
+
131
+ manager.playLiveImage(item);
132
+ await flushPromises();
133
+ manager.pauseLiveImage(item);
134
+ manager.onLiveImageVideoLoaded(item);
135
+
136
+ expect(item.player.pause).toHaveBeenCalledTimes(1);
137
+ expect(item.player.play).not.toHaveBeenCalled();
138
+ expect(item.setMode).toHaveBeenLastCalledWith("image");
139
+ });
81
140
  });
@@ -10,6 +10,7 @@ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
10
10
  import { create } from "zustand";
11
11
  import { useRivers } from "@applicaster/zapp-react-native-utils/reactHooks";
12
12
  import { selectPluginConfigurationsByPluginId } from "@applicaster/zapp-react-native-redux";
13
+ import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
13
14
 
14
15
  export const useConfiguration = () => {
15
16
  const {
@@ -18,6 +19,7 @@ export const useConfiguration = () => {
18
19
 
19
20
  const rivers = useRivers();
20
21
  const contentTypes = useContentTypes();
22
+ const player = usePlayer();
21
23
 
22
24
  const targetScreenId = contentTypes?.[item?.type?.value]?.screen_id;
23
25
  const targetScreenConfiguration = rivers?.[targetScreenId];
@@ -26,7 +28,10 @@ export const useConfiguration = () => {
26
28
  selectPluginConfigurationsByPluginId(state, targetScreenConfiguration?.type)
27
29
  );
28
30
 
29
- const playerPluginConfig = playerManager.getPluginConfiguration();
31
+ const playerPluginConfig =
32
+ player?.getPluginConfiguration() ??
33
+ playerManager.getInstanceController()?.getPluginConfiguration() ??
34
+ {};
30
35
 
31
36
  const config = mergeRight(playerPluginConfig, {
32
37
  ...configuration_json,
@@ -1,10 +1,11 @@
1
1
  import React from "react";
2
- import { StyleSheet, View } from "react-native";
3
-
2
+ import { StyleSheet, SafeAreaView as RNSafeAreaView } from "react-native";
3
+ import { SafeAreaView } from "react-native-safe-area-context";
4
4
  import { useIsRTL } from "@applicaster/zapp-react-native-utils/localizationUtils";
5
5
  import CloseButton from "./Buttons/CloseButton";
6
6
  import BackButton from "./Buttons/BackButton";
7
7
  import BarTitle from "./BarTitle";
8
+ import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
8
9
 
9
10
  type Props = {
10
11
  screenStyles: {
@@ -23,6 +24,11 @@ const defaultState: NavBarState = {
23
24
  title: "",
24
25
  };
25
26
 
27
+ const SafeAreaViewPlatform = platformSelect({
28
+ ios: RNSafeAreaView,
29
+ android: SafeAreaView,
30
+ });
31
+
26
32
  const BarView = ({ screenStyles, barState = defaultState }: Props) => {
27
33
  const isRTL = useIsRTL();
28
34
 
@@ -44,14 +50,15 @@ const BarView = ({ screenStyles, barState = defaultState }: Props) => {
44
50
  );
45
51
 
46
52
  return (
47
- <View
53
+ <SafeAreaViewPlatform
54
+ edges={["top", "left", "right"]}
48
55
  style={[style.view, isRTL ? style.rtlStyle : {}]}
49
- testID="BarView-Container"
56
+ testID="BarView-safeAreaView"
50
57
  >
51
58
  {backButton}
52
59
  <BarTitle title={barState.title} screenStyles={screenStyles} />
53
60
  {closeButton}
54
- </View>
61
+ </SafeAreaViewPlatform>
55
62
  );
56
63
  };
57
64
 
@@ -46,7 +46,7 @@ describe("BarView", () => {
46
46
  <BarView screenStyles={screenStyles} barState={barState} />
47
47
  );
48
48
 
49
- expect(getByTestId("BarView-Container").props.style).toContainEqual({
49
+ expect(getByTestId("BarView-safeAreaView").props.style).toContainEqual({
50
50
  transform: [{ scaleX: -1 }],
51
51
  });
52
52
  });
@@ -58,7 +58,7 @@ describe("BarView", () => {
58
58
  <BarView screenStyles={screenStyles} barState={barState} />
59
59
  );
60
60
 
61
- expect(getByTestId("BarView-Container").props.style).not.toContainEqual({
61
+ expect(getByTestId("BarView-safeAreaView").props.style).not.toContainEqual({
62
62
  transform: [{ scaleX: -1 }],
63
63
  });
64
64
  });