@applicaster/zapp-react-native-ui-components 16.0.0-alpha.3112220329 → 16.0.0-alpha.3202041045
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/Components/LinkHandler/LinkHandler.tsx +2 -7
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +17 -1
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +19 -4
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +43 -1
- package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +2 -4
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +14 -6
- package/Components/MasterCell/DefaultComponents/PressableView.tsx +3 -0
- package/Components/MasterCell/DefaultComponents/__tests__/ActionButton.test.tsx +288 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +2 -1
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +4 -2
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/factory.test.ts +32 -0
- package/Components/MasterCell/__tests__/masterCell.test.js +60 -0
- package/Components/MasterCell/index.tsx +28 -4
- package/Components/MasterCell/utils/__tests__/withoutFocusedState.test.js +20 -0
- package/Components/MasterCell/utils/index.ts +18 -0
- package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +4 -2
- package/Components/ModalComponent/ActionsBottomSheet/__tests__/openActionsBottomSheet.test.ts +38 -0
- package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +5 -2
- package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +4 -1
- package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +58 -73
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +65 -56
- package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +140 -65
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +115 -47
- package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +148 -32
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +164 -0
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/NowPlayingHeaderSection.test.tsx +36 -0
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +0 -2
- package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +209 -6
- package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +250 -19
- package/Components/ModalComponent/BottomSheetModalContent.tsx +126 -77
- package/Components/ModalComponent/SortableList.tsx +36 -22
- package/Components/ModalComponent/TextInputContent.tsx +42 -7
- package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +111 -6
- package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +50 -1
- package/Components/ModalComponent/__tests__/showTextInput.test.ts +25 -0
- package/Components/ModalComponent/index.tsx +7 -9
- package/Components/ModalComponent/presets/__tests__/getCell.test.ts +24 -0
- package/Components/ModalComponent/presets/selectableCell.tsx +1 -1
- package/Components/ModalComponent/showTextInput.ts +13 -2
- package/Components/OfflineHandler/__tests__/index.test.tsx +109 -0
- package/Components/OfflineHandler/index.tsx +42 -4
- package/Components/PlayerContainer/PlayerContainer.tsx +30 -6
- package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +51 -0
- package/Components/River/ComponentsMap/ComponentsMap.tsx +14 -13
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +140 -75
- package/Components/River/__tests__/componentsMap.test.js +8 -3
- package/Components/Screen/__tests__/Screen.test.tsx +24 -0
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -0
- package/Components/Screen/index.tsx +5 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +1 -1
- package/Components/Transitioner/AnimationManager.js +1 -1
- package/Components/Transitioner/Scene.tsx +4 -3
- package/Components/Transitioner/Transitioner.tsx +71 -33
- package/Components/Transitioner/__tests__/transitioner.test.js +372 -3
- package/Components/VideoLive/LiveImageManager.ts +134 -14
- package/Components/VideoLive/MuteUnmuteController.ts +135 -0
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +15 -19
- package/Components/VideoLive/__tests__/LiveImageManager.test.ts +59 -0
- package/Components/VideoLive/__tests__/LiveImageMuteState.test.ts +311 -0
- package/Components/VideoLive/__tests__/MuteUnmuteController.test.ts +135 -0
- package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +63 -1
- package/Components/VideoModal/PlayerWrapper.tsx +4 -1
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
- package/Components/VideoModal/__tests__/playerWrapperStyle.test.ts +58 -0
- package/Components/VideoModal/__tests__/playerWrapperUtils.test.ts +16 -0
- package/Components/VideoModal/playerWrapperStyle.ts +21 -3
- package/Components/VideoModal/playerWrapperUtils.ts +7 -1
- package/Contexts/ModalNavigationContext/__tests__/index.test.tsx +15 -0
- package/Contexts/ModalNavigationContext/index.tsx +2 -2
- package/Decorators/ZappPipesDataConnector/__tests__/screenToolbox.test.ts +347 -0
- package/Decorators/ZappPipesDataConnector/__tests__/screenToolboxUnavailable.test.ts +53 -0
- package/Decorators/ZappPipesDataConnector/index.tsx +19 -4
- package/Decorators/ZappPipesDataConnector/screenToolbox.ts +384 -0
- package/assets/empty-Info.png +0 -0
- package/package.json +5 -5
- package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +0 -314
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
|
|
4
|
+
import { getPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
|
|
6
5
|
import {
|
|
7
6
|
inflateString,
|
|
8
7
|
objectToReadableString,
|
|
@@ -40,12 +39,8 @@ export async function inflateUrl(url) {
|
|
|
40
39
|
|
|
41
40
|
export function LinkHandler(props: Props) {
|
|
42
41
|
const screenData = props?.screenData;
|
|
43
|
-
const plugins = usePlugins();
|
|
44
42
|
|
|
45
|
-
const ScreenPlugin =
|
|
46
|
-
WEBVIEW_SCREEN_IDENTIFIER,
|
|
47
|
-
plugins
|
|
48
|
-
);
|
|
43
|
+
const ScreenPlugin = getPluginByIdentifier(WEBVIEW_SCREEN_IDENTIFIER);
|
|
49
44
|
|
|
50
45
|
const Component =
|
|
51
46
|
R.path(["module", "Component"], ScreenPlugin) ||
|
|
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect } from "react";
|
|
|
2
2
|
import { TouchableOpacity, ViewStyle } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
|
+
import { useUIComponentContext } from "@applicaster/zapp-react-native-ui-components/Contexts/UIComponentContext";
|
|
5
6
|
|
|
6
7
|
import Image from "./Image";
|
|
7
8
|
type Props = {
|
|
@@ -57,6 +58,7 @@ export const ActionButton = React.memo(function ActionButtonComponent(
|
|
|
57
58
|
) {
|
|
58
59
|
const { item, action, asset, flavour = "flavour_1", cellUUID } = props;
|
|
59
60
|
const actionContext = useActions(action?.identifier);
|
|
61
|
+
const component = useUIComponentContext();
|
|
60
62
|
|
|
61
63
|
// TODO: add subscription API for action availability
|
|
62
64
|
const actionDisabled =
|
|
@@ -81,8 +83,9 @@ export const ActionButton = React.memo(function ActionButtonComponent(
|
|
|
81
83
|
updateState: (state) => {
|
|
82
84
|
setActionState(state);
|
|
83
85
|
},
|
|
86
|
+
...(component ? { component } : {}),
|
|
84
87
|
});
|
|
85
|
-
}, [actionState, actionContext?.state, item
|
|
88
|
+
}, [actionState, actionContext?.state, item, component]);
|
|
86
89
|
|
|
87
90
|
useEffect(() => {
|
|
88
91
|
if (typeof actionContext?.addListener === "function") {
|
|
@@ -93,6 +96,14 @@ export const ActionButton = React.memo(function ActionButtonComponent(
|
|
|
93
96
|
}, [item?.id, setActionState, actionContext]);
|
|
94
97
|
|
|
95
98
|
if (actionDisabled || !actionContext) return null;
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
actionState?.visibleInCells &&
|
|
102
|
+
!actionState.visibleInCells.includes(cellUUID)
|
|
103
|
+
) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
96
107
|
const AssetComponent = actionState?.asset;
|
|
97
108
|
|
|
98
109
|
// Default state is not yet ready
|
|
@@ -106,6 +117,11 @@ export const ActionButton = React.memo(function ActionButtonComponent(
|
|
|
106
117
|
accessibilityLabel={props?.accessibilityLabel || `${item?.id}`}
|
|
107
118
|
accessibilityHint={props?.accessibilityHint}
|
|
108
119
|
accessibilityRole="button"
|
|
120
|
+
accessibilityState={
|
|
121
|
+
typeof actionState.checked === "boolean"
|
|
122
|
+
? { checked: actionState.checked }
|
|
123
|
+
: undefined
|
|
124
|
+
}
|
|
109
125
|
accessible={!!(props?.testID || props?.accessibilityLabel)}
|
|
110
126
|
style={props?.style}
|
|
111
127
|
>
|
|
@@ -6,6 +6,7 @@ import React, {
|
|
|
6
6
|
useState,
|
|
7
7
|
} from "react";
|
|
8
8
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
9
|
+
import { useUIComponentContext } from "@applicaster/zapp-react-native-ui-components/Contexts/UIComponentContext";
|
|
9
10
|
|
|
10
11
|
type ActionDefinition = {
|
|
11
12
|
identifier?: string;
|
|
@@ -61,6 +62,7 @@ export function ActionButtonController({
|
|
|
61
62
|
const resolvedEntry = entry;
|
|
62
63
|
const resolvedIdentifier = action?.identifier || pluginIdentifier || "";
|
|
63
64
|
const actionContext = useActions(resolvedIdentifier);
|
|
65
|
+
const component = useUIComponentContext();
|
|
64
66
|
|
|
65
67
|
const actionDisabled =
|
|
66
68
|
typeof actionContext?.isActionAvailable === "function" &&
|
|
@@ -128,20 +130,33 @@ export function ActionButtonController({
|
|
|
128
130
|
return;
|
|
129
131
|
}
|
|
130
132
|
|
|
133
|
+
const invokeOptions: InvokeArgsOptions = {
|
|
134
|
+
...(component ? { component } : {}),
|
|
135
|
+
};
|
|
136
|
+
|
|
131
137
|
if (supportsEntryState) {
|
|
132
138
|
return actionContext.invokeAction?.(resolvedEntry, {
|
|
139
|
+
...invokeOptions,
|
|
133
140
|
updateState: setActionState,
|
|
134
141
|
});
|
|
135
142
|
}
|
|
136
143
|
|
|
144
|
+
if (typeof actionContext.invokeAction === "function") {
|
|
145
|
+
return actionContext.invokeAction(resolvedEntry, invokeOptions);
|
|
146
|
+
}
|
|
147
|
+
|
|
137
148
|
const favouritesAction = legacySelected
|
|
138
149
|
? actionContext.removeFavourite
|
|
139
150
|
: actionContext.addFavourite;
|
|
140
151
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
152
|
+
return favouritesAction?.(resolvedEntry);
|
|
153
|
+
}, [
|
|
154
|
+
actionContext,
|
|
155
|
+
supportsEntryState,
|
|
156
|
+
resolvedEntry,
|
|
157
|
+
legacySelected,
|
|
158
|
+
component,
|
|
159
|
+
]);
|
|
145
160
|
|
|
146
161
|
if (!actionContext || actionDisabled) {
|
|
147
162
|
if (!actionContext) {
|
|
@@ -2,6 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { Text } from "react-native";
|
|
3
3
|
import { render, fireEvent } from "@testing-library/react-native";
|
|
4
4
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
|
+
import { UIComponentContext } from "@applicaster/zapp-react-native-ui-components/Contexts/UIComponentContext";
|
|
5
6
|
|
|
6
7
|
import { ActionButtonController } from "../ActionButtonController";
|
|
7
8
|
|
|
@@ -89,6 +90,47 @@ describe("ActionButtonController", () => {
|
|
|
89
90
|
);
|
|
90
91
|
});
|
|
91
92
|
|
|
93
|
+
it("passes the List UIComponentContext into invokeAction so the sheet can refresh it", () => {
|
|
94
|
+
const listComponent = {
|
|
95
|
+
id: "list-1",
|
|
96
|
+
data: { source: "https://example.com/user/collections/abc" },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const actionContext = {
|
|
100
|
+
isActionAvailable: jest.fn(() => true),
|
|
101
|
+
initialEntryState: jest.fn(() => ({ active: false })),
|
|
102
|
+
invokeAction: jest.fn(),
|
|
103
|
+
addListener: jest.fn(() => jest.fn()),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
mockUseActions.mockReturnValue(actionContext);
|
|
107
|
+
|
|
108
|
+
const { getByTestId } = render(
|
|
109
|
+
<UIComponentContext.Provider value={listComponent}>
|
|
110
|
+
<ActionButtonController
|
|
111
|
+
action={{ identifier: "open-modal-bottom-sheet-cell-action" }}
|
|
112
|
+
entry={entry}
|
|
113
|
+
>
|
|
114
|
+
{({ onPress }) => (
|
|
115
|
+
<Text testID="content" onPress={onPress}>
|
|
116
|
+
press
|
|
117
|
+
</Text>
|
|
118
|
+
)}
|
|
119
|
+
</ActionButtonController>
|
|
120
|
+
</UIComponentContext.Provider>
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
fireEvent.press(getByTestId("content"));
|
|
124
|
+
|
|
125
|
+
expect(actionContext.invokeAction).toHaveBeenCalledWith(
|
|
126
|
+
entry,
|
|
127
|
+
expect.objectContaining({
|
|
128
|
+
updateState: expect.any(Function),
|
|
129
|
+
component: listComponent,
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
92
134
|
it("updates state from addListener", () => {
|
|
93
135
|
let listener;
|
|
94
136
|
|
|
@@ -328,7 +370,7 @@ describe("ActionButtonController", () => {
|
|
|
328
370
|
|
|
329
371
|
fireEvent.press(getByTestId("content"));
|
|
330
372
|
|
|
331
|
-
expect(invokeAction).toHaveBeenCalledWith(entry);
|
|
373
|
+
expect(invokeAction).toHaveBeenCalledWith(entry, {});
|
|
332
374
|
expect(addFavourite).not.toHaveBeenCalled();
|
|
333
375
|
expect(removeFavourite).not.toHaveBeenCalled();
|
|
334
376
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
1
|
+
import { getPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
|
|
3
2
|
import { loggerLiveImageManager } from "../../../VideoLive/loggerHelper";
|
|
4
3
|
|
|
5
4
|
const { log_debug, log_error } = loggerLiveImageManager;
|
|
@@ -74,10 +73,9 @@ export async function executePreloadHooks({
|
|
|
74
73
|
let payload: any = entry;
|
|
75
74
|
|
|
76
75
|
let needsAbort = false;
|
|
77
|
-
const plugins = appStore.get("plugins");
|
|
78
76
|
|
|
79
77
|
for (const hookConfig of sortedHooks) {
|
|
80
|
-
const plugin =
|
|
78
|
+
const plugin = getPluginByIdentifier(hookConfig.identifier, true);
|
|
81
79
|
|
|
82
80
|
if (!plugin?.module) {
|
|
83
81
|
log_debug(
|
|
@@ -8,7 +8,7 @@ import Image from "../Image";
|
|
|
8
8
|
import { useTrackCurrentAutoScrollingElement } from "@applicaster/zapp-react-native-utils/reactHooks/autoscrolling/useTrackCurrentAutoScrollingElement";
|
|
9
9
|
import { useUIComponentContext } from "@applicaster/zapp-react-native-ui-components/Contexts/UIComponentContext";
|
|
10
10
|
import { getPropComponentType } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
11
|
-
import {
|
|
11
|
+
import { getPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
|
|
12
12
|
import { useAccessibilityState } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks";
|
|
13
13
|
|
|
14
14
|
type LiveImageProps = {
|
|
@@ -245,13 +245,21 @@ const LiveImageComponent = (props: LiveImageProps) => {
|
|
|
245
245
|
|
|
246
246
|
const isCurrentlyFocused = useTrackCurrentAutoScrollingElement(componentId);
|
|
247
247
|
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
// New object identities here restart PlayerLiveImage's register effect,
|
|
249
|
+
// which throws away the in-flight player and runs preload hooks again.
|
|
250
|
+
const playerConfig = React.useMemo(
|
|
251
|
+
() => getPlayerConfig(player_screen_id, actionIdentifier),
|
|
252
|
+
[player_screen_id, actionIdentifier]
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const { playerPluginId, screenConfig, preloadHooks } = playerConfig ?? {};
|
|
250
256
|
|
|
251
|
-
const playableEntry =
|
|
252
|
-
|
|
257
|
+
const playableEntry = React.useMemo(
|
|
258
|
+
() => (playerPluginId ? getPlayableEntry(item) : null),
|
|
259
|
+
[playerPluginId, item?.id]
|
|
260
|
+
);
|
|
253
261
|
|
|
254
|
-
const playerPlugin =
|
|
262
|
+
const playerPlugin = getPluginByIdentifier(playerPluginId);
|
|
255
263
|
|
|
256
264
|
const isShouldRender =
|
|
257
265
|
playerPlugin &&
|
|
@@ -11,6 +11,7 @@ type Props = {
|
|
|
11
11
|
accessibilityHint?: string;
|
|
12
12
|
onPress?: () => void;
|
|
13
13
|
testOnly_pressed?: boolean;
|
|
14
|
+
checked?: boolean;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
export function PressableView({
|
|
@@ -22,11 +23,13 @@ export function PressableView({
|
|
|
22
23
|
pressedStyle,
|
|
23
24
|
onPress,
|
|
24
25
|
testOnly_pressed = false,
|
|
26
|
+
checked,
|
|
25
27
|
}: Props) {
|
|
26
28
|
return (
|
|
27
29
|
<Pressable
|
|
28
30
|
testOnly_pressed={testOnly_pressed}
|
|
29
31
|
onPress={onPress}
|
|
32
|
+
accessibilityState={checked === undefined ? undefined : { checked }}
|
|
30
33
|
testID={testID}
|
|
31
34
|
accessibilityLabel={accessibilityLabel}
|
|
32
35
|
accessibilityHint={accessibilityHint}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { act, fireEvent, render } from "@testing-library/react-native";
|
|
3
|
+
|
|
4
|
+
import { ActionButton } from "../ActionButton";
|
|
5
|
+
import { ActionButton as MobileActionButton } from "../mobile/MobileActionButtons/ActionButton";
|
|
6
|
+
import { Text } from "react-native";
|
|
7
|
+
|
|
8
|
+
type EntryStateListener = (state: CellActionEntryState) => void;
|
|
9
|
+
|
|
10
|
+
const PLUGIN_ASSETS = {
|
|
11
|
+
mute: ["plugin_mute_1", "plugin_mute_2"],
|
|
12
|
+
unmute: ["plugin_unmute_1", "plugin_unmute_2"],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Fake of the mute_unmute_action context, following its entry state contract.
|
|
16
|
+
type FakeState = {
|
|
17
|
+
available: boolean;
|
|
18
|
+
muted: boolean;
|
|
19
|
+
visibleInCells?: string[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const createFakeContext = (initial: FakeState) => {
|
|
23
|
+
let current = initial;
|
|
24
|
+
const listeners = new Set<EntryStateListener>();
|
|
25
|
+
|
|
26
|
+
const toEntryState = ({
|
|
27
|
+
available,
|
|
28
|
+
muted,
|
|
29
|
+
visibleInCells,
|
|
30
|
+
}: FakeState): CellActionEntryState => ({
|
|
31
|
+
state: muted ? 1 : 0,
|
|
32
|
+
asset: available
|
|
33
|
+
? muted
|
|
34
|
+
? PLUGIN_ASSETS.unmute
|
|
35
|
+
: PLUGIN_ASSETS.mute
|
|
36
|
+
: undefined,
|
|
37
|
+
label: muted ? "Unmute" : "Mute",
|
|
38
|
+
mobileButtonAssets: muted ? PLUGIN_ASSETS.unmute : PLUGIN_ASSETS.mute,
|
|
39
|
+
visibleInCells,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const emit = (next: FakeState) => {
|
|
43
|
+
current = next;
|
|
44
|
+
listeners.forEach((listener) => listener(toEntryState(current)));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const context = {
|
|
48
|
+
initialEntryState: jest.fn(() => toEntryState(current)),
|
|
49
|
+
addListener: jest.fn((_id: string, listener: EntryStateListener) => {
|
|
50
|
+
listeners.add(listener);
|
|
51
|
+
|
|
52
|
+
return () => listeners.delete(listener);
|
|
53
|
+
}),
|
|
54
|
+
invokeAction: jest.fn((_item, options) => {
|
|
55
|
+
if (!current.available) return;
|
|
56
|
+
|
|
57
|
+
current = { ...current, muted: !current.muted };
|
|
58
|
+
options?.updateState?.(toEntryState(current));
|
|
59
|
+
listeners.forEach((listener) => listener(toEntryState(current)));
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return { context, emit, listeners };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
let mockContext: ReturnType<typeof createFakeContext>["context"];
|
|
67
|
+
|
|
68
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/actions", () => ({
|
|
69
|
+
useActions: jest.fn(() => mockContext),
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
jest.mock(
|
|
73
|
+
"@applicaster/zapp-react-native-ui-components/Contexts/UIComponentContext",
|
|
74
|
+
() => ({ useUIComponentContext: jest.fn(() => undefined) })
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
jest.mock("../Image", () => {
|
|
78
|
+
const { Text: MockText } = require("react-native");
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
__esModule: true,
|
|
82
|
+
default: ({ uri }) => <MockText testID="asset">{uri}</MockText>,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const item = { id: "entry-1" } as unknown as ZappEntry;
|
|
87
|
+
|
|
88
|
+
it("keeps the standalone button visible and limits actions to the playing cell instance", () => {
|
|
89
|
+
const fake = createFakeContext({
|
|
90
|
+
available: true,
|
|
91
|
+
muted: true,
|
|
92
|
+
visibleInCells: [],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
mockContext = fake.context;
|
|
96
|
+
|
|
97
|
+
const Label = ({
|
|
98
|
+
children,
|
|
99
|
+
testID,
|
|
100
|
+
}: {
|
|
101
|
+
children?: React.ReactNode;
|
|
102
|
+
testID?: string;
|
|
103
|
+
mobileActionRole?: string;
|
|
104
|
+
}) => <Text testID={testID}>{children}</Text>;
|
|
105
|
+
|
|
106
|
+
const { getByTestId, queryByTestId } = render(
|
|
107
|
+
<>
|
|
108
|
+
{["first", "second"].map((cellUUID) => (
|
|
109
|
+
<ActionButton
|
|
110
|
+
key={cellUUID}
|
|
111
|
+
item={item}
|
|
112
|
+
action={{ identifier: "mute_unmute_action" }}
|
|
113
|
+
cellUUID={cellUUID}
|
|
114
|
+
testID={`cell-${cellUUID}`}
|
|
115
|
+
style={{}}
|
|
116
|
+
/>
|
|
117
|
+
))}
|
|
118
|
+
<MobileActionButton
|
|
119
|
+
action={{ identifier: "mute_unmute_action" }}
|
|
120
|
+
entry={item}
|
|
121
|
+
testID="standalone"
|
|
122
|
+
style={{}}
|
|
123
|
+
focusedStyles={{}}
|
|
124
|
+
>
|
|
125
|
+
<Label mobileActionRole="label">Toggle sound</Label>
|
|
126
|
+
</MobileActionButton>
|
|
127
|
+
</>
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(queryByTestId("cell-first")).toBeNull();
|
|
131
|
+
expect(queryByTestId("cell-second")).toBeNull();
|
|
132
|
+
expect(getByTestId("standalone")).toBeTruthy();
|
|
133
|
+
fireEvent.press(getByTestId("standalone"));
|
|
134
|
+
expect(fake.context.invokeAction).toHaveBeenCalledTimes(1);
|
|
135
|
+
|
|
136
|
+
act(() =>
|
|
137
|
+
fake.emit({ available: true, muted: false, visibleInCells: ["first"] })
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(getByTestId("cell-first")).toBeTruthy();
|
|
141
|
+
expect(queryByTestId("cell-second")).toBeNull();
|
|
142
|
+
|
|
143
|
+
act(() =>
|
|
144
|
+
fake.emit({ available: true, muted: false, visibleInCells: ["second"] })
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
expect(queryByTestId("cell-first")).toBeNull();
|
|
148
|
+
expect(getByTestId("cell-second")).toBeTruthy();
|
|
149
|
+
|
|
150
|
+
act(() => fake.emit({ available: true, muted: false, visibleInCells: [] }));
|
|
151
|
+
|
|
152
|
+
expect(queryByTestId("cell-first")).toBeNull();
|
|
153
|
+
expect(queryByTestId("cell-second")).toBeNull();
|
|
154
|
+
expect(getByTestId("standalone")).toBeTruthy();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const renderButton = (
|
|
158
|
+
flavour: "flavour_1" | "flavour_2",
|
|
159
|
+
asset?: React.ComponentProps<typeof ActionButton>["asset"]
|
|
160
|
+
) =>
|
|
161
|
+
render(
|
|
162
|
+
<ActionButton
|
|
163
|
+
item={item}
|
|
164
|
+
flavour={flavour}
|
|
165
|
+
action={{ identifier: "mute_unmute_action" }}
|
|
166
|
+
asset={asset}
|
|
167
|
+
style={{}}
|
|
168
|
+
testID="mute-button"
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const cellAsset = {
|
|
173
|
+
props: {},
|
|
174
|
+
src: { active: "cell_selected", inactive: "cell_unselected" },
|
|
175
|
+
style: {},
|
|
176
|
+
type: "Image",
|
|
177
|
+
} as const;
|
|
178
|
+
|
|
179
|
+
describe.each([
|
|
180
|
+
["flavour_1", 0],
|
|
181
|
+
["flavour_2", 1],
|
|
182
|
+
] as const)("mute_unmute_action cell action (%s)", (flavour, index) => {
|
|
183
|
+
let fake: ReturnType<typeof createFakeContext>;
|
|
184
|
+
|
|
185
|
+
const setup = (state: { available: boolean; muted: boolean }, asset?) => {
|
|
186
|
+
fake = createFakeContext(state);
|
|
187
|
+
mockContext = fake.context;
|
|
188
|
+
|
|
189
|
+
return renderButton(flavour, asset);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
it("renders nothing while the preview has not started", () => {
|
|
193
|
+
const { queryByTestId } = setup({ available: false, muted: false });
|
|
194
|
+
|
|
195
|
+
expect(queryByTestId("mute-button")).toBeNull();
|
|
196
|
+
expect(queryByTestId("asset")).toBeNull();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("shows the Mute icon while the preview plays with sound on", () => {
|
|
200
|
+
const { getByTestId } = setup({ available: true, muted: false });
|
|
201
|
+
|
|
202
|
+
expect(getByTestId("asset").props.children).toBe(PLUGIN_ASSETS.mute[index]);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("shows the Unmute icon while the preview plays muted", () => {
|
|
206
|
+
const { getByTestId } = setup({ available: true, muted: true });
|
|
207
|
+
|
|
208
|
+
expect(getByTestId("asset").props.children).toBe(
|
|
209
|
+
PLUGIN_ASSETS.unmute[index]
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("toggles the sound and updates the icon on tap", () => {
|
|
214
|
+
const { getByTestId } = setup({ available: true, muted: false });
|
|
215
|
+
|
|
216
|
+
fireEvent.press(getByTestId("mute-button"));
|
|
217
|
+
|
|
218
|
+
expect(fake.context.invokeAction).toHaveBeenCalledTimes(1);
|
|
219
|
+
|
|
220
|
+
expect(getByTestId("asset").props.children).toBe(
|
|
221
|
+
PLUGIN_ASSETS.unmute[index]
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
fireEvent.press(getByTestId("mute-button"));
|
|
225
|
+
|
|
226
|
+
expect(getByTestId("asset").props.children).toBe(PLUGIN_ASSETS.mute[index]);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("follows state emitted through addListener", () => {
|
|
230
|
+
const { getByTestId, queryByTestId } = setup({
|
|
231
|
+
available: false,
|
|
232
|
+
muted: false,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
act(() => fake.emit({ available: true, muted: false }));
|
|
236
|
+
|
|
237
|
+
expect(getByTestId("asset").props.children).toBe(PLUGIN_ASSETS.mute[index]);
|
|
238
|
+
|
|
239
|
+
act(() => fake.emit({ available: true, muted: true }));
|
|
240
|
+
|
|
241
|
+
expect(getByTestId("asset").props.children).toBe(
|
|
242
|
+
PLUGIN_ASSETS.unmute[index]
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
expect(queryByTestId("mute-button")).not.toBeNull();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("hides again when the preview stops or another cell starts", () => {
|
|
249
|
+
const { queryByTestId } = setup({ available: true, muted: false });
|
|
250
|
+
|
|
251
|
+
expect(queryByTestId("mute-button")).not.toBeNull();
|
|
252
|
+
|
|
253
|
+
act(() => fake.emit({ available: false, muted: false }));
|
|
254
|
+
|
|
255
|
+
expect(queryByTestId("mute-button")).toBeNull();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("unsubscribes from the context on unmount", () => {
|
|
259
|
+
const { unmount } = setup({ available: true, muted: false });
|
|
260
|
+
|
|
261
|
+
expect(fake.listeners.size).toBe(1);
|
|
262
|
+
|
|
263
|
+
unmount();
|
|
264
|
+
|
|
265
|
+
expect(fake.listeners.size).toBe(0);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("prefers the cell's selected icon when muted (state 1)", () => {
|
|
269
|
+
const { getByTestId } = setup({ available: true, muted: true }, cellAsset);
|
|
270
|
+
|
|
271
|
+
expect(getByTestId("asset").props.children).toBe("cell_selected");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("prefers the cell's unselected icon when sound is on (state 0)", () => {
|
|
275
|
+
const { getByTestId } = setup({ available: true, muted: false }, cellAsset);
|
|
276
|
+
|
|
277
|
+
expect(getByTestId("asset").props.children).toBe("cell_unselected");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("uses the plugin asset when the cell has no icons", () => {
|
|
281
|
+
const { getByTestId } = setup({ available: true, muted: false }, {
|
|
282
|
+
...cellAsset,
|
|
283
|
+
src: { active: undefined, inactive: undefined },
|
|
284
|
+
} as any);
|
|
285
|
+
|
|
286
|
+
expect(getByTestId("asset").props.children).toBe(PLUGIN_ASSETS.mute[index]);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
@@ -166,9 +166,10 @@ export const ActionButton = ({
|
|
|
166
166
|
<PressableView
|
|
167
167
|
testOnly_pressed={props.testOnly_pressed}
|
|
168
168
|
testID={props.testID || `${entry?.id}`}
|
|
169
|
-
style={style}
|
|
169
|
+
style={[style, props.alignment]}
|
|
170
170
|
pressedStyle={focusedStyles}
|
|
171
171
|
onPress={renderProps.onPress}
|
|
172
|
+
checked={renderProps.actionState?.checked}
|
|
172
173
|
accessibilityLabel={props.accessibilityLabel || `${entry?.id}`}
|
|
173
174
|
accessibilityHint={props.accessibilityHint}
|
|
174
175
|
>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Pressable, Text as RNText, View } from "react-native";
|
|
2
|
+
import { Pressable, StyleSheet, Text as RNText, View } from "react-native";
|
|
3
3
|
import { render, fireEvent } from "@testing-library/react-native";
|
|
4
4
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
5
|
|
|
@@ -282,7 +282,9 @@ describe("MobileActionButton", () => {
|
|
|
282
282
|
expect(pressable.props.testID).toBe("mobile_action_button_1");
|
|
283
283
|
expect(pressable.props.accessibilityLabel).toBe("entry-1");
|
|
284
284
|
|
|
285
|
-
expect(
|
|
285
|
+
expect(
|
|
286
|
+
StyleSheet.flatten(pressable.props.style({ pressed: false }))
|
|
287
|
+
).toMatchObject({
|
|
286
288
|
backgroundColor: "rgba(1,1,1,1)",
|
|
287
289
|
borderColor: "rgba(2,2,2,1)",
|
|
288
290
|
borderWidth: 1,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CollapsibleTextContainer } from "../index";
|
|
2
|
+
|
|
3
|
+
const getValue = (key: string) => {
|
|
4
|
+
const values: Record<string, unknown> = {
|
|
5
|
+
text_alignment: "left",
|
|
6
|
+
margin_top: 0,
|
|
7
|
+
margin_right: 0,
|
|
8
|
+
margin_bottom: 0,
|
|
9
|
+
margin_left: 0,
|
|
10
|
+
padding_top: 0,
|
|
11
|
+
padding_right: 0,
|
|
12
|
+
padding_bottom: 0,
|
|
13
|
+
padding_left: 0,
|
|
14
|
+
corner_radius: 0,
|
|
15
|
+
data_key: "title",
|
|
16
|
+
custom_data_key: undefined,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return values[key];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("CollapsibleTextContainer view tree", () => {
|
|
23
|
+
it("aligns the label to the text alignment instead of stretching the cell", () => {
|
|
24
|
+
const result = CollapsibleTextContainer({
|
|
25
|
+
getValue,
|
|
26
|
+
elements: [],
|
|
27
|
+
additionalStyles: { backgroundColor: "transparent" },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(result.style.alignSelf).toBe("flex-start");
|
|
31
|
+
});
|
|
32
|
+
});
|