@applicaster/zapp-react-native-utils 16.0.0-rc.9 → 16.0.0-rc.90
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/actionsExecutor/ActionExecutor.ts +57 -12
- package/actionsExecutor/ActionExecutorContext.tsx +113 -314
- package/actionsExecutor/__tests__/ActionExecutor.test.ts +71 -0
- package/actionsExecutor/__tests__/feedDecorator.test.ts +61 -0
- package/actionsExecutor/actions/__tests__/dismissBottomSheet.test.ts +25 -0
- package/actionsExecutor/actions/__tests__/goBack.test.ts +62 -0
- package/actionsExecutor/actions/__tests__/goHome.test.ts +19 -0
- package/actionsExecutor/actions/__tests__/loadItemsFromSource.test.ts +88 -0
- package/actionsExecutor/actions/__tests__/navigateToScreen.test.ts +133 -0
- package/actionsExecutor/actions/__tests__/openBottomSheet.customContent.test.ts +76 -0
- package/actionsExecutor/actions/__tests__/openBottomSheet.inlineItems.test.ts +212 -0
- package/actionsExecutor/actions/__tests__/presentScreen.test.ts +127 -0
- package/actionsExecutor/actions/__tests__/showAlert.test.ts +49 -0
- package/actionsExecutor/actions/__tests__/showToast.test.ts +88 -0
- package/actionsExecutor/actions/appRestart.ts +24 -0
- package/actionsExecutor/actions/confirmDialog.ts +53 -0
- package/actionsExecutor/actions/dismissBottomSheet.ts +23 -0
- package/actionsExecutor/actions/goBack.ts +60 -0
- package/actionsExecutor/actions/goHome.ts +31 -0
- package/actionsExecutor/actions/index.ts +42 -0
- package/actionsExecutor/actions/localStorageRemove.ts +27 -0
- package/actionsExecutor/actions/localStorageSet.ts +28 -0
- package/actionsExecutor/actions/localStorageToggleFlag.ts +28 -0
- package/actionsExecutor/actions/navigateToScreen.ts +137 -0
- package/actionsExecutor/actions/openBottomSheet.ts +329 -0
- package/actionsExecutor/actions/presentScreen.ts +78 -0
- package/actionsExecutor/actions/refreshComponent.ts +85 -0
- package/actionsExecutor/actions/screenSetVariable.ts +35 -0
- package/actionsExecutor/actions/screenToggleFlag.ts +38 -0
- package/actionsExecutor/actions/sendCloudEvent.ts +93 -0
- package/actionsExecutor/actions/sessionStorageRemove.ts +19 -0
- package/actionsExecutor/actions/sessionStorageSet.ts +20 -0
- package/actionsExecutor/actions/sessionStorageToggleFlag.ts +15 -0
- package/actionsExecutor/actions/showAlert.ts +51 -0
- package/actionsExecutor/actions/showToast.ts +87 -0
- package/actionsExecutor/actions/switchLayout.ts +40 -0
- package/actionsExecutor/consts.ts +27 -0
- package/actionsExecutor/feedDecorator.ts +6 -6
- package/actionsExecutor/types.ts +59 -0
- package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -2
- package/analyticsUtils/PlayerAnalyticsManager.ts +12 -2
- package/analyticsUtils/__tests__/analyticsMapper.test.ts +35 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testACP_events.json +1 -1
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testBlockUnlistedParams_rules.json +1 -1
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEmptyRenameKeepsName_events.json +4 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEmptyRenameKeepsName_rules.json +6 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEventRegex_events.json +3 -9
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testIgnoreOrdering_events.json +18 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testIgnoreOrdering_rules.json +16 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexEventNoFallback_events.json +4 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexEventNoFallback_rules.json +6 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexMultiGroupRename_events.json +4 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexMultiGroupRename_rules.json +6 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexNonNamedFullMatch_events.json +4 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexNonNamedFullMatch_rules.json +6 -0
- package/analyticsUtils/__tests__/fixtures/index.js +20 -0
- package/analyticsUtils/analyticsMapper.ts +4 -1
- package/analyticsUtils/playerAnalyticsTracker.ts +26 -3
- package/appUtils/HooksManager/index.ts +12 -8
- package/appUtils/contextKeysManager/__tests__/getKey/failure.test.ts +1 -1
- package/appUtils/contextKeysManager/__tests__/removeKey/failure.test.ts +1 -1
- package/appUtils/contextKeysManager/__tests__/setKey/failure/invalidKey.test.ts +4 -4
- package/appUtils/contextKeysManager/index.ts +1 -1
- package/appUtils/contextKeysManager/utils/index.ts +38 -25
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +143 -53
- package/appUtils/playerManager/OverlayObserver/__tests__/liveContent.test.ts +205 -0
- package/appUtils/playerManager/OverlayObserver/__tests__/utils.test.ts +49 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.android.tv.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ios.tv.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ts +11 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.web.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/utils.ts +54 -20
- package/appUtils/playerManager/__tests__/playerContent.test.ts +403 -0
- package/appUtils/playerManager/__tests__/playerFactory.test.ts +1 -1
- package/appUtils/playerManager/__tests__/playerNative.test.ts +38 -0
- package/appUtils/playerManager/__tests__/usePlayerContent.test.tsx +64 -0
- package/appUtils/playerManager/{conts.ts → const.ts} +20 -0
- package/appUtils/playerManager/index.ts +1 -1
- package/appUtils/playerManager/player.ts +115 -3
- package/appUtils/playerManager/playerFactory.ts +1 -1
- package/appUtils/playerManager/playerNative.ts +6 -4
- package/appUtils/playerManager/usePlayerContent.tsx +43 -0
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +1 -1
- package/appUtils/playerManager/userLanguagePreference.ts +1 -1
- package/arrayUtils/__tests__/{anyThruthy.test.ts → anyTruthy.test.ts} +8 -0
- package/arrayUtils/__tests__/arrayUtils.test.ts +165 -108
- package/arrayUtils/__tests__/isEmptyArray.test.ts +11 -0
- package/arrayUtils/__tests__/isFilledArray.test.ts +12 -0
- package/arrayUtils/__tests__/isFirst.test.ts +17 -0
- package/arrayUtils/__tests__/isIndexInRange.test.ts +14 -0
- package/arrayUtils/__tests__/isLast.test.ts +31 -0
- package/arrayUtils/__tests__/makeListOf.test.ts +31 -0
- package/arrayUtils/__tests__/makeListOfIndexes.test.ts +20 -0
- package/arrayUtils/__tests__/reorderByIds.test.ts +50 -0
- package/arrayUtils/__tests__/sample.test.ts +61 -0
- package/arrayUtils/index.ts +136 -20
- package/bottomSheet/__tests__/scrollContext.test.tsx +35 -0
- package/bottomSheet/index.ts +17 -0
- package/cellUtils/index.ts +33 -8
- package/colorUtils/__tests__/isTransparentColor.test.ts +76 -0
- package/colorUtils/__tests__/isValidColor.test.ts +70 -0
- package/colorUtils/index.ts +50 -0
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +21 -0
- package/configurationUtils/__tests__/modalBlocksParser.test.ts +260 -0
- package/configurationUtils/manifestKeyParser.ts +50 -10
- package/configurationUtils/modalBlocksParser.ts +296 -0
- package/entryActions/__tests__/buildEntryActions.test.ts +195 -0
- package/entryActions/aliasPresentation.ts +124 -0
- package/entryActions/buildEntryActions.ts +167 -0
- package/entryActions/index.ts +17 -0
- package/entryActions/types.ts +39 -0
- package/manifestUtils/__tests__/player.audioControls.test.js +158 -0
- package/manifestUtils/_internals/index.js +14 -3
- package/manifestUtils/defaultManifestConfigurations/generalContent.js +41 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +349 -20
- package/manifestUtils/fieldUtils/__tests__/fieldUtils.test.js +84 -0
- package/manifestUtils/fieldUtils/index.js +125 -0
- package/manifestUtils/index.js +3 -0
- package/manifestUtils/keys.js +9 -0
- package/manifestUtils/mobileAction/button/index.js +16 -0
- package/manifestUtils/mobileAction/container/index.js +3 -1
- package/manifestUtils/mobileAction/groups/defaults.js +3 -1
- package/manifestUtils/modalBlocks.js +304 -0
- package/manifestUtils/platformIsTV.js +1 -0
- package/modalState/ContentViewModel.ts +113 -0
- package/modalState/EditableCollection.ts +17 -0
- package/modalState/EditableCollectionRegistry.ts +51 -0
- package/modalState/ModalOrchestrator.ts +211 -0
- package/modalState/RemoteEditableCollection.ts +235 -0
- package/modalState/__tests__/ContentViewModel.editable.test.ts +69 -0
- package/modalState/__tests__/ContentViewModel.test.ts +165 -0
- package/modalState/__tests__/EditableCollectionRegistry.test.ts +112 -0
- package/modalState/__tests__/ModalOrchestrator.phase3.test.ts +47 -0
- package/modalState/__tests__/RemoteEditableCollection.test.ts +415 -0
- package/modalState/__tests__/index.test.ts +30 -1
- package/modalState/__tests__/useBottomSheetContent.test.ts +349 -0
- package/modalState/__tests__/useModalStoreState.test.ts +142 -0
- package/modalState/index.ts +35 -83
- package/modalState/store.ts +109 -0
- package/modalState/types.ts +160 -0
- package/modalState/useBottomSheetContent.ts +109 -0
- package/numberUtils/__tests__/isMinusZero.test.ts +20 -0
- package/numberUtils/__tests__/isZero.test.ts +27 -0
- package/numberUtils/__tests__/requireNumber.test.ts +50 -0
- package/numberUtils/__tests__/toNumber.test.ts +27 -0
- package/numberUtils/__tests__/toNumberWithDefault.test.ts +64 -0
- package/numberUtils/__tests__/toNumberWithDefaultZero.test.ts +48 -0
- package/numberUtils/index.ts +27 -8
- package/package.json +2 -2
- package/pipesUtils/__tests__/buildUrlWithQuery.test.ts +33 -0
- package/pipesUtils/__tests__/withPipesEndpoint.test.tsx +56 -0
- package/pipesUtils/index.ts +1 -0
- package/pipesUtils/withPipesEndpoint.tsx +54 -0
- package/playerUtils/__tests__/contentDataKeys.test.ts +297 -0
- package/playerUtils/__tests__/resolvePlayerActions.test.ts +138 -0
- package/playerUtils/__tests__/resolvePlayerTitleSubtitle.test.ts +180 -0
- package/playerUtils/contentDataKeys.ts +134 -0
- package/playerUtils/index.ts +41 -18
- package/playerUtils/isAudioItem.ts +26 -0
- package/playerUtils/resolvePlayerActions.ts +71 -0
- package/playerUtils/resolvePlayerTitleSubtitle.ts +76 -0
- package/reactHooks/actions/index.ts +117 -2
- package/reactHooks/cell-click/__tests__/index.test.js +64 -0
- package/reactHooks/cell-click/index.ts +39 -24
- package/reactHooks/feed/__mocks__/useMarkPipesDataStale.ts +4 -0
- package/reactHooks/feed/__tests__/useInflatedUrl.test.tsx +25 -0
- package/reactHooks/feed/index.ts +5 -1
- package/reactHooks/feed/useBatchLoading.ts +9 -1
- package/reactHooks/feed/{usePipesCacheReset.ts → useMarkPipesDataStale.ts} +12 -4
- package/reactHooks/index.ts +2 -0
- package/reactHooks/layout/index.ts +1 -1
- package/reactHooks/navigation/__mocks__/index.ts +4 -0
- package/reactHooks/navigation/__tests__/index.test.tsx +176 -1
- package/reactHooks/navigation/__tests__/useIsScreenActive.test.ts +42 -0
- package/reactHooks/navigation/__tests__/usePresentScreen.test.ts +154 -0
- package/reactHooks/navigation/index.ts +3 -1
- package/reactHooks/navigation/useIsScreenActive.ts +29 -8
- package/reactHooks/navigation/usePresentScreen.ts +157 -0
- package/reactHooks/navigation/useRoute.ts +22 -5
- package/reactHooks/state/useComponentScreenState.ts +1 -1
- package/reactHooks/usePluginConfiguration.ts +4 -1
- package/reactHooks/utils/__tests__/index.test.js +22 -2
- package/reactHooks/utils/index.ts +13 -2
- package/reactHooks/videoModal/hooks/useVideoModalScreenData.tsx +22 -4
- package/riverComponetsMeasurementProvider/index.tsx +12 -8
- package/stringUtils/__tests__/stringUtils.test.js +42 -0
- package/stringUtils/index.ts +2 -2
- package/uiActionsRegistrator/__tests__/resolveActionIdentifiers.test.ts +93 -0
- package/uiActionsRegistrator/__tests__/subscribe.test.ts +107 -0
- package/uiActionsRegistrator/__tests__/usableActionItems.test.ts +52 -0
- package/uiActionsRegistrator/index.ts +25 -0
- package/uiActionsRegistrator/registry.ts +335 -0
- package/uiActionsRegistrator/resolveActionIdentifiers.ts +100 -0
- package/uiActionsRegistrator/usableActionItems.ts +66 -0
- package/zappFrameworkUtils/__tests__/localStorageHelper.test.ts +146 -0
- package/zappFrameworkUtils/localStorageHelper.ts +19 -15
- package/appUtils/playerManager/usePlayerTitleSummaryOverlay.tsx +0 -56
- package/playerUtils/__tests__/getPlayerActionButtons.test.ts +0 -54
- package/playerUtils/getPlayerActionButtons.ts +0 -17
- package/reactHooks/feed/__mocks__/usePipesCacheReset.ts +0 -1
- /package/reactHooks/actions/__tests__/{index.test.js → index.test.tsx} +0 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { RemoteEditableCollection } from "../RemoteEditableCollection";
|
|
2
|
+
import { actionExecutor } from "../../actionsExecutor/ActionExecutor";
|
|
3
|
+
import type { Content, MenuItem } from "../types";
|
|
4
|
+
|
|
5
|
+
jest.mock("../../actionsExecutor/ActionExecutor", () => ({
|
|
6
|
+
actionExecutor: { handleAction: jest.fn() },
|
|
7
|
+
ActionResult: { Success: "success", Error: "error" },
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
const handleAction = actionExecutor.handleAction as jest.Mock;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* BehaviorSubject emits its current value synchronously on subscribe, so this
|
|
14
|
+
* returns the latest items snapshot.
|
|
15
|
+
*/
|
|
16
|
+
function currentItems(col: RemoteEditableCollection): MenuItem[] {
|
|
17
|
+
let items: MenuItem[] = [];
|
|
18
|
+
const sub = col.items$.subscribe((i) => (items = i));
|
|
19
|
+
sub.unsubscribe();
|
|
20
|
+
|
|
21
|
+
return items;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const removeAction = { type: "sendCloudEvent", options: { foo: "bar" } };
|
|
25
|
+
|
|
26
|
+
const itemWithRemove: MenuItem = {
|
|
27
|
+
id: "1",
|
|
28
|
+
title: "A",
|
|
29
|
+
entryActions: [{ button: { alias: "remove_item" }, actions: [removeAction] }],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const itemPlain: MenuItem = { id: "2", title: "B" };
|
|
33
|
+
|
|
34
|
+
function makeCollection(
|
|
35
|
+
items: MenuItem[],
|
|
36
|
+
refetch = jest.fn().mockResolvedValue(undefined),
|
|
37
|
+
operations = ["remove", "reorder"],
|
|
38
|
+
context?: Record<string, unknown>
|
|
39
|
+
) {
|
|
40
|
+
const content = {
|
|
41
|
+
items,
|
|
42
|
+
context,
|
|
43
|
+
dynamicCollection: { operations, postUrl: "https://x/cloud-events" },
|
|
44
|
+
} as unknown as Content;
|
|
45
|
+
|
|
46
|
+
return { col: new RemoteEditableCollection(content, refetch), refetch };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const openingContext = {
|
|
50
|
+
component: {
|
|
51
|
+
id: "list-1",
|
|
52
|
+
data: { source: "https://example.com/user/collections/abc" },
|
|
53
|
+
},
|
|
54
|
+
screenData: { id: "playlist-screen" },
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
describe("RemoteEditableCollection", () => {
|
|
58
|
+
beforeEach(() => jest.clearAllMocks());
|
|
59
|
+
|
|
60
|
+
it("exposes the declared operations and initial items", () => {
|
|
61
|
+
const { col } = makeCollection([itemWithRemove, itemPlain]);
|
|
62
|
+
|
|
63
|
+
expect(col.operations).toEqual(["remove", "reorder"]);
|
|
64
|
+
expect(currentItems(col)).toEqual([itemWithRemove, itemPlain]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("remove", () => {
|
|
68
|
+
it("optimistically removes, dispatches the aliased action, then refetches", async () => {
|
|
69
|
+
handleAction.mockResolvedValue("success");
|
|
70
|
+
const { col, refetch } = makeCollection([itemWithRemove, itemPlain]);
|
|
71
|
+
|
|
72
|
+
await col.remove(0);
|
|
73
|
+
|
|
74
|
+
expect(handleAction).toHaveBeenCalledWith(removeAction, {
|
|
75
|
+
entry: itemWithRemove,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(refetch).toHaveBeenCalledTimes(1);
|
|
79
|
+
expect(currentItems(col)).toEqual([itemPlain]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("rolls back to the snapshot when the action dispatch fails", async () => {
|
|
83
|
+
handleAction.mockRejectedValue(new Error("network"));
|
|
84
|
+
const { col, refetch } = makeCollection([itemWithRemove, itemPlain]);
|
|
85
|
+
|
|
86
|
+
await col.remove(0);
|
|
87
|
+
|
|
88
|
+
// optimistic removal reverted
|
|
89
|
+
expect(currentItems(col)).toEqual([itemWithRemove, itemPlain]);
|
|
90
|
+
// refetch never reached on failure
|
|
91
|
+
expect(refetch).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("lets the refetched server result replace the optimistic list", async () => {
|
|
95
|
+
handleAction.mockResolvedValue("success");
|
|
96
|
+
const serverItems: MenuItem[] = [{ id: "99", title: "server truth" }];
|
|
97
|
+
|
|
98
|
+
const refetch = jest.fn();
|
|
99
|
+
const { col } = makeCollection([itemWithRemove, itemPlain], refetch);
|
|
100
|
+
|
|
101
|
+
// Emulate ContentViewModel.refetch pushing authoritative items via sync()
|
|
102
|
+
refetch.mockImplementation(async () => {
|
|
103
|
+
col.sync(serverItems);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await col.remove(0);
|
|
107
|
+
|
|
108
|
+
expect(currentItems(col)).toEqual(serverItems);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("is a no-op for an out-of-range index", async () => {
|
|
112
|
+
const { col, refetch } = makeCollection([itemWithRemove]);
|
|
113
|
+
|
|
114
|
+
await col.remove(5);
|
|
115
|
+
|
|
116
|
+
expect(handleAction).not.toHaveBeenCalled();
|
|
117
|
+
expect(refetch).not.toHaveBeenCalled();
|
|
118
|
+
expect(currentItems(col)).toEqual([itemWithRemove]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("skips dispatch (no throw) when no matching alias exists", async () => {
|
|
122
|
+
const { col, refetch } = makeCollection([itemPlain, itemWithRemove]);
|
|
123
|
+
|
|
124
|
+
await col.remove(0); // itemPlain has no entryActions
|
|
125
|
+
|
|
126
|
+
expect(handleAction).not.toHaveBeenCalled();
|
|
127
|
+
// still optimistic-removes and refetches (server is source of truth)
|
|
128
|
+
expect(refetch).toHaveBeenCalledTimes(1);
|
|
129
|
+
expect(currentItems(col)).toEqual([itemWithRemove]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("forwards stored sheet context plus the removed item as entry", async () => {
|
|
133
|
+
handleAction.mockResolvedValue("success");
|
|
134
|
+
|
|
135
|
+
const { col } = makeCollection(
|
|
136
|
+
[itemWithRemove, itemPlain],
|
|
137
|
+
jest.fn().mockResolvedValue(undefined),
|
|
138
|
+
["remove", "reorder"],
|
|
139
|
+
openingContext
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
await col.remove(0);
|
|
143
|
+
|
|
144
|
+
expect(handleAction).toHaveBeenCalledWith(removeAction, {
|
|
145
|
+
...openingContext,
|
|
146
|
+
entry: itemWithRemove,
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("move", () => {
|
|
152
|
+
it("optimistically reorders and dispatches reorder_item with itemIds", async () => {
|
|
153
|
+
handleAction.mockResolvedValue("success");
|
|
154
|
+
|
|
155
|
+
const itemA: MenuItem = {
|
|
156
|
+
id: "2",
|
|
157
|
+
title: "B",
|
|
158
|
+
entryActions: [
|
|
159
|
+
{ button: { alias: "reorder_item" }, actions: [removeAction] },
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const moved: MenuItem = {
|
|
164
|
+
id: "3",
|
|
165
|
+
title: "C",
|
|
166
|
+
entryActions: [
|
|
167
|
+
{ button: { alias: "reorder_item" }, actions: [removeAction] },
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const { col, refetch } = makeCollection([moved, itemA]);
|
|
172
|
+
|
|
173
|
+
await col.move(["2", "3"]);
|
|
174
|
+
|
|
175
|
+
expect(currentItems(col)).toEqual([itemA, moved]);
|
|
176
|
+
|
|
177
|
+
expect(handleAction).toHaveBeenCalledWith(
|
|
178
|
+
{
|
|
179
|
+
...removeAction,
|
|
180
|
+
options: {
|
|
181
|
+
...removeAction.options,
|
|
182
|
+
itemIds: ["2", "3"],
|
|
183
|
+
data: {
|
|
184
|
+
...removeAction.options?.data,
|
|
185
|
+
itemIds: ["2", "3"],
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{ entry: itemA }
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(refetch).toHaveBeenCalledTimes(1);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("forwards stored sheet context plus the moved item as entry", async () => {
|
|
196
|
+
handleAction.mockResolvedValue("success");
|
|
197
|
+
|
|
198
|
+
const itemA: MenuItem = {
|
|
199
|
+
id: "2",
|
|
200
|
+
title: "B",
|
|
201
|
+
entryActions: [
|
|
202
|
+
{ button: { alias: "reorder_item" }, actions: [removeAction] },
|
|
203
|
+
],
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const { col } = makeCollection(
|
|
207
|
+
[itemA],
|
|
208
|
+
jest.fn().mockResolvedValue(undefined),
|
|
209
|
+
["remove", "reorder"],
|
|
210
|
+
openingContext
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
await col.move(["2"]);
|
|
214
|
+
|
|
215
|
+
expect(handleAction).toHaveBeenCalledWith(
|
|
216
|
+
expect.objectContaining({ type: "sendCloudEvent" }),
|
|
217
|
+
{
|
|
218
|
+
...openingContext,
|
|
219
|
+
entry: itemA,
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("rolls back on failure", async () => {
|
|
225
|
+
handleAction.mockRejectedValue(new Error("nope"));
|
|
226
|
+
|
|
227
|
+
const itemA: MenuItem = {
|
|
228
|
+
id: "2",
|
|
229
|
+
title: "B",
|
|
230
|
+
entryActions: [
|
|
231
|
+
{ button: { alias: "reorder_item" }, actions: [removeAction] },
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const moved: MenuItem = {
|
|
236
|
+
id: "3",
|
|
237
|
+
title: "C",
|
|
238
|
+
entryActions: [
|
|
239
|
+
{ button: { alias: "reorder_item" }, actions: [removeAction] },
|
|
240
|
+
],
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const { col } = makeCollection([moved, itemA]);
|
|
244
|
+
|
|
245
|
+
await col.move(["2", "3"]);
|
|
246
|
+
|
|
247
|
+
expect(currentItems(col)).toEqual([moved, itemA]);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("is a no-op for empty or non-existent IDs", async () => {
|
|
251
|
+
const { col, refetch } = makeCollection([itemWithRemove, itemPlain]);
|
|
252
|
+
|
|
253
|
+
await col.move([]);
|
|
254
|
+
|
|
255
|
+
expect(refetch).not.toHaveBeenCalled();
|
|
256
|
+
expect(currentItems(col)).toEqual([itemWithRemove, itemPlain]);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
describe("sync", () => {
|
|
261
|
+
it("replaces the current items with the provided list", () => {
|
|
262
|
+
const { col } = makeCollection([itemWithRemove]);
|
|
263
|
+
const next: MenuItem[] = [{ id: "7", title: "next" }];
|
|
264
|
+
|
|
265
|
+
col.sync(next);
|
|
266
|
+
|
|
267
|
+
expect(currentItems(col)).toEqual(next);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("suppresses sync updates while a local mutation is in-flight", async () => {
|
|
271
|
+
let resolveAction: () => void = () => {};
|
|
272
|
+
|
|
273
|
+
handleAction.mockImplementation(
|
|
274
|
+
() =>
|
|
275
|
+
new Promise<void>((resolve) => {
|
|
276
|
+
resolveAction = resolve;
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const { col } = makeCollection([itemWithRemove, itemPlain]);
|
|
281
|
+
const staleServerItems: MenuItem[] = [{ id: "stale", title: "Stale" }];
|
|
282
|
+
|
|
283
|
+
// Initiate optimistic removal (mutation in-flight)
|
|
284
|
+
const removePromise = col.remove(0);
|
|
285
|
+
|
|
286
|
+
// Verify optimistic state applied locally
|
|
287
|
+
expect(currentItems(col)).toEqual([itemPlain]);
|
|
288
|
+
|
|
289
|
+
// Attempt sync while mutation is in-flight
|
|
290
|
+
col.sync(staleServerItems);
|
|
291
|
+
|
|
292
|
+
// Verify sync was suppressed and optimistic state remained
|
|
293
|
+
expect(currentItems(col)).toEqual([itemPlain]);
|
|
294
|
+
|
|
295
|
+
// Resolve the in-flight mutation
|
|
296
|
+
resolveAction();
|
|
297
|
+
await removePromise;
|
|
298
|
+
|
|
299
|
+
// After mutation completes, sync applies normally
|
|
300
|
+
const freshServerItems: MenuItem[] = [{ id: "fresh", title: "Fresh" }];
|
|
301
|
+
col.sync(freshServerItems);
|
|
302
|
+
|
|
303
|
+
expect(currentItems(col)).toEqual(freshServerItems);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe("deleteCollection", () => {
|
|
308
|
+
it("optimistically clears items and calls refetch", async () => {
|
|
309
|
+
handleAction.mockResolvedValue("success");
|
|
310
|
+
const { col, refetch } = makeCollection([itemWithRemove, itemPlain]);
|
|
311
|
+
|
|
312
|
+
await col.deleteCollection();
|
|
313
|
+
|
|
314
|
+
expect(currentItems(col)).toEqual([]);
|
|
315
|
+
expect(refetch).toHaveBeenCalledTimes(1);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("dispatches aliased action when first item contains delete_collection alias", async () => {
|
|
319
|
+
handleAction.mockResolvedValue("success");
|
|
320
|
+
|
|
321
|
+
const deleteAction = {
|
|
322
|
+
type: "sendCloudEvent",
|
|
323
|
+
options: { subject: "delete_collection" },
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const itemWithDelete: MenuItem = {
|
|
327
|
+
id: "100",
|
|
328
|
+
title: "Collection Item",
|
|
329
|
+
entryActions: [
|
|
330
|
+
{
|
|
331
|
+
button: { alias: "delete_collection" },
|
|
332
|
+
actions: [deleteAction],
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const { col, refetch } = makeCollection([itemWithDelete]);
|
|
338
|
+
|
|
339
|
+
await col.deleteCollection();
|
|
340
|
+
|
|
341
|
+
expect(handleAction).toHaveBeenCalledWith(deleteAction, {
|
|
342
|
+
entry: itemWithDelete,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
expect(refetch).toHaveBeenCalledTimes(1);
|
|
346
|
+
expect(currentItems(col)).toEqual([]);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
describe("add", () => {
|
|
351
|
+
it("dispatches actions from dynamicCollection.events.add and refetches", async () => {
|
|
352
|
+
handleAction.mockResolvedValue("success");
|
|
353
|
+
|
|
354
|
+
const addAction = {
|
|
355
|
+
type: "sendCloudEvent",
|
|
356
|
+
options: {
|
|
357
|
+
url: "https://x/cloud-events",
|
|
358
|
+
type: "com.applicaster.collection.create.v1",
|
|
359
|
+
subject: "create_collection",
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const content = {
|
|
364
|
+
items: [],
|
|
365
|
+
dynamicCollection: {
|
|
366
|
+
operations: ["add"],
|
|
367
|
+
postUrl: "https://x/cloud-events",
|
|
368
|
+
events: {
|
|
369
|
+
add: [addAction],
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
} as any;
|
|
373
|
+
|
|
374
|
+
const refetch = jest.fn().mockResolvedValue(undefined);
|
|
375
|
+
const col = new RemoteEditableCollection(content, refetch);
|
|
376
|
+
|
|
377
|
+
await col.add();
|
|
378
|
+
|
|
379
|
+
expect(handleAction).toHaveBeenCalledWith(addAction);
|
|
380
|
+
expect(refetch).toHaveBeenCalledTimes(1);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("forwards stored sheet context into events.add actions", async () => {
|
|
384
|
+
handleAction.mockResolvedValue("success");
|
|
385
|
+
|
|
386
|
+
const addAction = {
|
|
387
|
+
type: "showTextInput",
|
|
388
|
+
options: {
|
|
389
|
+
headerTitle: "Create Playlist",
|
|
390
|
+
buttonLabel: "Create",
|
|
391
|
+
actions: [{ type: "sendCloudEvent" }],
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const content = {
|
|
396
|
+
items: [],
|
|
397
|
+
context: openingContext,
|
|
398
|
+
dynamicCollection: {
|
|
399
|
+
operations: ["add"],
|
|
400
|
+
postUrl: "https://x/cloud-events",
|
|
401
|
+
events: {
|
|
402
|
+
add: [addAction],
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
} as any;
|
|
406
|
+
|
|
407
|
+
const refetch = jest.fn().mockResolvedValue(undefined);
|
|
408
|
+
const col = new RemoteEditableCollection(content, refetch);
|
|
409
|
+
|
|
410
|
+
await col.add();
|
|
411
|
+
|
|
412
|
+
expect(handleAction).toHaveBeenCalledWith(addAction, openingContext);
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { modalStore } from "../";
|
|
1
|
+
import { modalStore, getMenuItemActions } from "../";
|
|
2
2
|
|
|
3
3
|
describe("Modal State", () => {
|
|
4
4
|
it("should have initial state", () => {
|
|
@@ -48,4 +48,33 @@ describe("Modal State", () => {
|
|
|
48
48
|
});
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
|
+
|
|
52
|
+
describe("getMenuItemActions", () => {
|
|
53
|
+
it("returns actions array when actions is provided", () => {
|
|
54
|
+
const actions = [{ type: "action1" }, { type: "action2" }];
|
|
55
|
+
const item = { id: "1", title: "Item", actions };
|
|
56
|
+
|
|
57
|
+
expect(getMenuItemActions(item)).toEqual(actions);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("returns [action] when single action is provided", () => {
|
|
61
|
+
const action = { type: "action1" };
|
|
62
|
+
const item = { id: "1", title: "Item", action };
|
|
63
|
+
|
|
64
|
+
expect(getMenuItemActions(item)).toEqual([action]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("returns action when action is passed as an array", () => {
|
|
68
|
+
const actions = [{ type: "action1" }];
|
|
69
|
+
const item = { id: "1", title: "Item", action: actions as any };
|
|
70
|
+
|
|
71
|
+
expect(getMenuItemActions(item)).toEqual(actions);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("returns empty array when no action or actions are provided", () => {
|
|
75
|
+
expect(getMenuItemActions({ id: "1", title: "Item" })).toEqual([]);
|
|
76
|
+
expect(getMenuItemActions(null)).toEqual([]);
|
|
77
|
+
expect(getMenuItemActions(undefined)).toEqual([]);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
51
80
|
});
|