@buoy-gg/redux 7.0.30 → 7.0.31
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/lib/commonjs/index.js +25 -0
- package/lib/commonjs/redux/components/ReduxActionButton.js +3 -1
- package/lib/commonjs/redux/components/ReduxActionDetailContent.js +60 -6
- package/lib/commonjs/redux/components/ReduxActionInfoView.js +61 -12
- package/lib/commonjs/redux/components/ReduxActionItem.js +20 -5
- package/lib/commonjs/redux/components/ReduxDetailViewToggle.js +7 -0
- package/lib/commonjs/redux/components/ReduxModal.js +168 -32
- package/lib/commonjs/redux/utils/activeStoreRegistry.js +31 -0
- package/lib/commonjs/redux/utils/autoInstrument.js +23 -40
- package/lib/commonjs/redux/utils/buoyReduxMiddleware.js +36 -9
- package/lib/commonjs/redux/utils/devtoolsHook.js +4 -0
- package/lib/commonjs/redux/utils/testIDs.js +75 -0
- package/lib/module/index.js +5 -0
- package/lib/module/redux/components/ReduxActionButton.js +3 -1
- package/lib/module/redux/components/ReduxActionDetailContent.js +60 -6
- package/lib/module/redux/components/ReduxActionInfoView.js +63 -14
- package/lib/module/redux/components/ReduxActionItem.js +20 -5
- package/lib/module/redux/components/ReduxDetailViewToggle.js +7 -0
- package/lib/module/redux/components/ReduxModal.js +170 -33
- package/lib/module/redux/utils/activeStoreRegistry.js +29 -0
- package/lib/module/redux/utils/autoInstrument.js +8 -40
- package/lib/module/redux/utils/buoyReduxMiddleware.js +32 -10
- package/lib/module/redux/utils/devtoolsHook.js +5 -1
- package/lib/module/redux/utils/testIDs.js +68 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxActionButton.d.ts +2 -1
- package/lib/typescript/redux/components/ReduxActionButton.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxActionDetailContent.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxActionInfoView.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxActionItem.d.ts +15 -0
- package/lib/typescript/redux/components/ReduxActionItem.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxDetailViewToggle.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxModal.d.ts.map +1 -1
- package/lib/typescript/redux/utils/activeStoreRegistry.d.ts +27 -0
- package/lib/typescript/redux/utils/activeStoreRegistry.d.ts.map +1 -1
- package/lib/typescript/redux/utils/autoInstrument.d.ts +7 -11
- package/lib/typescript/redux/utils/autoInstrument.d.ts.map +1 -1
- package/lib/typescript/redux/utils/buoyReduxMiddleware.d.ts +17 -3
- package/lib/typescript/redux/utils/buoyReduxMiddleware.d.ts.map +1 -1
- package/lib/typescript/redux/utils/devtoolsHook.d.ts.map +1 -1
- package/lib/typescript/redux/utils/testIDs.d.ts +51 -0
- package/lib/typescript/redux/utils/testIDs.d.ts.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.REDUX_TEST_ID = void 0;
|
|
7
|
+
exports.actionRowTestID = actionRowTestID;
|
|
8
|
+
exports.buildActionRowTestIDs = buildActionRowTestIDs;
|
|
9
|
+
exports.slugForTestID = slugForTestID;
|
|
10
|
+
/**
|
|
11
|
+
* Redux tool test handles.
|
|
12
|
+
*
|
|
13
|
+
* Every tappable and every value the e2e suite reads back is addressed
|
|
14
|
+
* `buoy-redux-<thing>`. One rule in here needs explaining, because it is the
|
|
15
|
+
* opposite of what the Events tool does and the difference is not cosmetic:
|
|
16
|
+
*
|
|
17
|
+
* **A redux row's handle carries an ORDINAL.** The Events tool can address a
|
|
18
|
+
* row by what it is about, because an event is a thing that happened to a
|
|
19
|
+
* named subject — one key, one URL, one atom. A redux action is not: an app
|
|
20
|
+
* dispatches `counter/increment` fifty times and every one of them is a
|
|
21
|
+
* separate, equally real row. Addressing them by type alone would give fifty
|
|
22
|
+
* views one handle, and Detox fails a *matcher* that hits more than one
|
|
23
|
+
* element — so the suite would break on `toExist()`, before any assertion,
|
|
24
|
+
* with an error that says nothing about duplicate actions.
|
|
25
|
+
*
|
|
26
|
+
* So the handle is `buoy-redux-row-<slug(type)>-<n>`, where n counts back from
|
|
27
|
+
* the newest row of that type (1 = newest). It is deliberately relative to the
|
|
28
|
+
* list rather than absolute: an action has no identity of its own that a host
|
|
29
|
+
* could know in advance — `id` is a timestamp plus a per-process counter, and
|
|
30
|
+
* the counter's value depends on how many actions the app happened to dispatch
|
|
31
|
+
* before the test looked. What a test CAN know is "the increment I just fired
|
|
32
|
+
* is the newest increment", which is exactly what `-1` means.
|
|
33
|
+
*
|
|
34
|
+
* The consequence, and it is the same one the Events tool documents for its
|
|
35
|
+
* positional-free handles: read and tap in one beat. A row's ordinal changes
|
|
36
|
+
* the moment another action of that type arrives.
|
|
37
|
+
*
|
|
38
|
+
* The suite mirrors these rules in CommonJS (`example-dev-build/utils/
|
|
39
|
+
* reduxBench.js`) and asserts the two agree — Detox runs in Node on the host
|
|
40
|
+
* and cannot import TypeScript.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/** Prefix every handle in this tool shares. */
|
|
44
|
+
const REDUX_TEST_ID = exports.REDUX_TEST_ID = "buoy-redux";
|
|
45
|
+
|
|
46
|
+
/** `counter/increment` → `counter-increment`. Shared with every Buoy tool. */
|
|
47
|
+
function slugForTestID(value) {
|
|
48
|
+
return String(value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Handle for one action row: `buoy-redux-row-counter-increment-1`.
|
|
53
|
+
*
|
|
54
|
+
* `ordinal` is 1-based and counts back from the newest row of this type.
|
|
55
|
+
*/
|
|
56
|
+
function actionRowTestID(type, ordinal) {
|
|
57
|
+
return `${REDUX_TEST_ID}-row-${slugForTestID(type)}-${ordinal}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Row handle per action id for one rendered list, newest-first.
|
|
62
|
+
*
|
|
63
|
+
* Built once for the whole list rather than per row, because an ordinal is a
|
|
64
|
+
* fact about the list and a row cannot compute its own.
|
|
65
|
+
*/
|
|
66
|
+
function buildActionRowTestIDs(actions) {
|
|
67
|
+
const seen = new Map();
|
|
68
|
+
const handles = new Map();
|
|
69
|
+
for (const action of actions) {
|
|
70
|
+
const ordinal = (seen.get(action.type) ?? 0) + 1;
|
|
71
|
+
seen.set(action.type, ordinal);
|
|
72
|
+
handles.set(action.id, actionRowTestID(action.type, ordinal));
|
|
73
|
+
}
|
|
74
|
+
return handles;
|
|
75
|
+
}
|
package/lib/module/index.js
CHANGED
|
@@ -109,6 +109,11 @@ export { ReduxIcon } from "./redux/components/ReduxIcon";
|
|
|
109
109
|
// FloatingDevTools; exported for hosts that mount devtools their own way.
|
|
110
110
|
export { ReduxAutoInstrument } from "./redux/components/ReduxAutoInstrument";
|
|
111
111
|
|
|
112
|
+
// =============================================================================
|
|
113
|
+
// TEST HANDLES (shared with the e2e suite and the website's live tour)
|
|
114
|
+
// =============================================================================
|
|
115
|
+
export { REDUX_TEST_ID, actionRowTestID, buildActionRowTestIDs, slugForTestID } from "./redux/utils/testIDs";
|
|
116
|
+
|
|
112
117
|
// =============================================================================
|
|
113
118
|
// TYPES
|
|
114
119
|
// =============================================================================
|
|
@@ -54,10 +54,12 @@ export function ReduxActionButton({
|
|
|
54
54
|
onPress,
|
|
55
55
|
text,
|
|
56
56
|
type,
|
|
57
|
-
disabled = false
|
|
57
|
+
disabled = false,
|
|
58
|
+
testID
|
|
58
59
|
}) {
|
|
59
60
|
const config = buttonConfigs[type];
|
|
60
61
|
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
62
|
+
testID: testID,
|
|
61
63
|
disabled: disabled,
|
|
62
64
|
onPress: onPress,
|
|
63
65
|
style: [styles.button, {
|
|
@@ -16,6 +16,8 @@ import { DataViewer, SplitDiffViewer, TreeDiffViewer, diffThemes } from "@buoy-g
|
|
|
16
16
|
import { ReduxDetailViewToggle } from "./ReduxDetailViewToggle";
|
|
17
17
|
import { ReduxActionInfoView } from "./ReduxActionInfoView";
|
|
18
18
|
import { DiffModeTabs, CompareBar } from "@buoy-gg/shared-ui";
|
|
19
|
+
import { REDUX_TEST_ID } from "../utils/testIDs";
|
|
20
|
+
import { isEvictedState, STATE_RETENTION_LIMIT } from "../utils/reduxActionStore";
|
|
19
21
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
20
22
|
/**
|
|
21
23
|
* Get color for action type
|
|
@@ -85,6 +87,20 @@ export function ReduxActionDetailContent({
|
|
|
85
87
|
const [activeView, setActiveView] = useState(initialView);
|
|
86
88
|
const [diffMode, setDiffMode] = useState("tree");
|
|
87
89
|
const totalActions = actions.length;
|
|
90
|
+
/**
|
|
91
|
+
* The state trees this action was recorded with are gone.
|
|
92
|
+
*
|
|
93
|
+
* Not a failure: the store keeps raw prev/next state for the most recent
|
|
94
|
+
* actions only, because with structural sharing broken (a store switch, a
|
|
95
|
+
* rehydration) each retained action pins a distinct multi-MB subtree and a
|
|
96
|
+
* few dozen of them are enough to OOM-kill a large app. But it has to be
|
|
97
|
+
* SAID. The sentinel left behind is an ordinary object, so a state view that
|
|
98
|
+
* just rendered it showed `{ __buoyStateEvicted: true, … }` under the
|
|
99
|
+
* heading "STATE AFTER ACTION" — which reads as the app's actual state — and
|
|
100
|
+
* a diff of two identical sentinels reported "no changes", which is a lie
|
|
101
|
+
* about the app rather than about the recording.
|
|
102
|
+
*/
|
|
103
|
+
const stateEvicted = isEvictedState(action.nextState);
|
|
88
104
|
const actionColor = getActionColor(action.type);
|
|
89
105
|
const actionLabel = getActionLabel(action.type);
|
|
90
106
|
const stateType = getValueType(parseValue(action.nextState));
|
|
@@ -135,9 +151,26 @@ export function ReduxActionDetailContent({
|
|
|
135
151
|
|
|
136
152
|
// Render state view
|
|
137
153
|
const renderStateView = useCallback(() => {
|
|
154
|
+
if (stateEvicted) {
|
|
155
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
156
|
+
style: styles.contentCard,
|
|
157
|
+
testID: `${REDUX_TEST_ID}-state-evicted`,
|
|
158
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
159
|
+
style: styles.valueHeader,
|
|
160
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
161
|
+
style: styles.valueLabel,
|
|
162
|
+
children: "STATE AFTER ACTION"
|
|
163
|
+
})
|
|
164
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
165
|
+
style: styles.evictedText,
|
|
166
|
+
children: `Not retained. Raw state is kept for the ${STATE_RETENTION_LIMIT} most recent actions, so this action's before/after trees have been released.`
|
|
167
|
+
})]
|
|
168
|
+
});
|
|
169
|
+
}
|
|
138
170
|
const parsed = parseValue(action.nextState);
|
|
139
171
|
return /*#__PURE__*/_jsxs(View, {
|
|
140
172
|
style: styles.contentCard,
|
|
173
|
+
testID: `${REDUX_TEST_ID}-state-card`,
|
|
141
174
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
142
175
|
style: styles.valueHeader,
|
|
143
176
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
@@ -159,6 +192,7 @@ export function ReduxActionDetailContent({
|
|
|
159
192
|
style: styles.typeBadge,
|
|
160
193
|
children: /*#__PURE__*/_jsx(Text, {
|
|
161
194
|
style: styles.typeText,
|
|
195
|
+
testID: `${REDUX_TEST_ID}-state-type`,
|
|
162
196
|
children: stateType.toUpperCase()
|
|
163
197
|
})
|
|
164
198
|
})]
|
|
@@ -174,7 +208,7 @@ export function ReduxActionDetailContent({
|
|
|
174
208
|
})
|
|
175
209
|
})]
|
|
176
210
|
});
|
|
177
|
-
}, [action.nextState, actionColor, actionLabel, stateType]);
|
|
211
|
+
}, [action.nextState, actionColor, actionLabel, stateType, stateEvicted]);
|
|
178
212
|
|
|
179
213
|
// Render diff content
|
|
180
214
|
const renderDiffContent = useCallback(() => {
|
|
@@ -214,7 +248,15 @@ export function ReduxActionDetailContent({
|
|
|
214
248
|
const handleFooterNext = useCallback(() => {
|
|
215
249
|
onIndexChange(Math.max(0, selectedIndex - 1));
|
|
216
250
|
}, [selectedIndex, onIndexChange]);
|
|
217
|
-
|
|
251
|
+
|
|
252
|
+
// A diff needs two real trees, and both live on THIS action
|
|
253
|
+
// (prevState/nextState) — no siblings needed, so the action count says
|
|
254
|
+
// nothing (gating on it greyed DIFF out for every redux event opened from
|
|
255
|
+
// the Events timeline, which passes a one-element array). Eviction of
|
|
256
|
+
// EITHER side does disable it: two identical sentinels diff cleanly to
|
|
257
|
+
// "no changes", which reads as a lie.
|
|
258
|
+
const diffDisabled = stateEvicted || isEvictedState(action.prevState) || action.prevState === undefined;
|
|
259
|
+
const showInternalFooter = !disableInternalFooter && totalActions > 1;
|
|
218
260
|
return /*#__PURE__*/_jsxs(View, {
|
|
219
261
|
style: styles.container,
|
|
220
262
|
children: [/*#__PURE__*/_jsx(ReduxDetailViewToggle, {
|
|
@@ -222,7 +264,7 @@ export function ReduxActionDetailContent({
|
|
|
222
264
|
onViewChange: setActiveView,
|
|
223
265
|
diffDisabled: diffDisabled
|
|
224
266
|
}), /*#__PURE__*/_jsxs(View, {
|
|
225
|
-
style: styles.contentArea,
|
|
267
|
+
style: [styles.contentArea, showInternalFooter && styles.contentAreaFooterPad],
|
|
226
268
|
children: [activeView === "action" && /*#__PURE__*/_jsx(ReduxActionInfoView, {
|
|
227
269
|
action: action
|
|
228
270
|
}), activeView === "state" && /*#__PURE__*/_jsx(View, {
|
|
@@ -230,19 +272,22 @@ export function ReduxActionDetailContent({
|
|
|
230
272
|
children: renderStateView()
|
|
231
273
|
}), activeView === "diff" && /*#__PURE__*/_jsxs(View, {
|
|
232
274
|
style: styles.diffContainer,
|
|
275
|
+
testID: `${REDUX_TEST_ID}-diff`,
|
|
233
276
|
children: [/*#__PURE__*/_jsx(DiffModeTabs, {
|
|
234
277
|
tabs: DIFF_MODE_TABS,
|
|
235
278
|
activeTab: diffMode,
|
|
236
|
-
onTabChange: setDiffMode
|
|
279
|
+
onTabChange: setDiffMode,
|
|
280
|
+
testIDPrefix: `${REDUX_TEST_ID}-diff-tab`
|
|
237
281
|
}), /*#__PURE__*/_jsx(CompareBar, {
|
|
238
282
|
leftEvent: leftEvent,
|
|
239
283
|
rightEvent: rightEvent
|
|
240
284
|
}), /*#__PURE__*/_jsx(View, {
|
|
241
285
|
style: styles.diffContent,
|
|
286
|
+
testID: `${REDUX_TEST_ID}-diff-content`,
|
|
242
287
|
children: renderDiffContent()
|
|
243
288
|
})]
|
|
244
289
|
})]
|
|
245
|
-
}),
|
|
290
|
+
}), showInternalFooter && /*#__PURE__*/_jsx(EventStepperFooter, {
|
|
246
291
|
currentIndex: chronologicalNumber - 1,
|
|
247
292
|
totalItems: totalActions,
|
|
248
293
|
onPrevious: handleFooterPrevious,
|
|
@@ -282,7 +327,11 @@ const styles = StyleSheet.create({
|
|
|
282
327
|
backgroundColor: "transparent"
|
|
283
328
|
},
|
|
284
329
|
contentArea: {
|
|
285
|
-
flex: 1
|
|
330
|
+
flex: 1
|
|
331
|
+
},
|
|
332
|
+
// Clearance for the absolute EventStepperFooter — only when it renders,
|
|
333
|
+
// otherwise hosts like the Events tool get a dead band at the sheet bottom.
|
|
334
|
+
contentAreaFooterPad: {
|
|
286
335
|
paddingBottom: 96
|
|
287
336
|
},
|
|
288
337
|
// State view styles
|
|
@@ -346,6 +395,11 @@ const styles = StyleSheet.create({
|
|
|
346
395
|
borderColor: night.color.border,
|
|
347
396
|
padding: 8
|
|
348
397
|
},
|
|
398
|
+
evictedText: {
|
|
399
|
+
fontSize: 12,
|
|
400
|
+
color: night.color.textSecondary,
|
|
401
|
+
lineHeight: 18
|
|
402
|
+
},
|
|
349
403
|
valueText: {
|
|
350
404
|
fontSize: 12,
|
|
351
405
|
color: night.color.text,
|
|
@@ -15,9 +15,10 @@ import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from "react-nati
|
|
|
15
15
|
import { buoyColors, night, Clock, AlertTriangle, Zap, Hash, Info, Box, AlertCircle, FileText, parseValue, ProUpgradeModal, copyToClipboard } from "@buoy-gg/shared-ui";
|
|
16
16
|
import { useIsPro } from "@buoy-gg/license";
|
|
17
17
|
import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
|
|
18
|
-
import { reduxActionStore } from "../utils/reduxActionStore";
|
|
19
|
-
import { jumpToState, replayAction } from "../utils/buoyReduxMiddleware";
|
|
18
|
+
import { reduxActionStore, isEvictedState } from "../utils/reduxActionStore";
|
|
19
|
+
import { jumpToState, replayAction, isTimeTravelAvailable } from "../utils/buoyReduxMiddleware";
|
|
20
20
|
import { ReduxActionButton } from "./ReduxActionButton";
|
|
21
|
+
import { REDUX_TEST_ID } from "../utils/testIDs";
|
|
21
22
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
22
23
|
/**
|
|
23
24
|
* Get category display info
|
|
@@ -84,10 +85,12 @@ function formatDuration(duration) {
|
|
|
84
85
|
function SectionHeader({
|
|
85
86
|
icon: Icon,
|
|
86
87
|
title,
|
|
87
|
-
badge
|
|
88
|
+
badge,
|
|
89
|
+
testID
|
|
88
90
|
}) {
|
|
89
91
|
return /*#__PURE__*/_jsxs(View, {
|
|
90
92
|
style: styles.sectionHeader,
|
|
93
|
+
testID: testID,
|
|
91
94
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
92
95
|
style: styles.sectionHeaderLeft,
|
|
93
96
|
children: [/*#__PURE__*/_jsx(Icon, {
|
|
@@ -108,7 +111,8 @@ function InfoRow({
|
|
|
108
111
|
label,
|
|
109
112
|
value,
|
|
110
113
|
valueColor,
|
|
111
|
-
mono = false
|
|
114
|
+
mono = false,
|
|
115
|
+
testID
|
|
112
116
|
}) {
|
|
113
117
|
return /*#__PURE__*/_jsxs(View, {
|
|
114
118
|
style: styles.infoRow,
|
|
@@ -118,9 +122,14 @@ function InfoRow({
|
|
|
118
122
|
}), /*#__PURE__*/_jsx(Text, {
|
|
119
123
|
style: [styles.infoValue, mono && styles.infoValueMono, valueColor && {
|
|
120
124
|
color: valueColor
|
|
121
|
-
}]
|
|
125
|
+
}]
|
|
126
|
+
// `ellipsizeMode="middle"` truncates the RENDERED text and iOS reports
|
|
127
|
+
// the truncated string back, so anything a test reads off one of these
|
|
128
|
+
// rows has to be short enough to fit on the line.
|
|
129
|
+
,
|
|
122
130
|
numberOfLines: 1,
|
|
123
131
|
ellipsizeMode: "middle",
|
|
132
|
+
testID: testID,
|
|
124
133
|
children: value
|
|
125
134
|
})]
|
|
126
135
|
});
|
|
@@ -151,6 +160,18 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
151
160
|
}
|
|
152
161
|
}, [showProModal, isPro]);
|
|
153
162
|
const categoryInfo = getCategoryInfo(action.category);
|
|
163
|
+
// Time travel needs a REAL tree. The store releases raw state for older
|
|
164
|
+
// actions (see ReduxActionDetailContent), and `jumpToState` refuses the
|
|
165
|
+
// sentinel — but it refused into a console warning, so from the screen the
|
|
166
|
+
// button simply did nothing at all. A disabled button that says why is the
|
|
167
|
+
// difference between a limit and a bug.
|
|
168
|
+
const stateEvicted = isEvictedState(action.nextState);
|
|
169
|
+
// Whether this app wired time travel at all. Capture and time travel are
|
|
170
|
+
// separate: an app can be fully captured through `buoyReduxMiddleware` and
|
|
171
|
+
// still have no reducer that answers a jump, in which case the press
|
|
172
|
+
// dispatched, was ignored, and left the screen exactly as it was.
|
|
173
|
+
const canTimeTravel = isTimeTravelAvailable();
|
|
174
|
+
const jumpDisabled = stateEvicted || !canTimeTravel;
|
|
154
175
|
const hasPayload = action.payload !== undefined;
|
|
155
176
|
const hasMeta = action.meta && Object.keys(action.meta).length > 0;
|
|
156
177
|
const hasError = action.error !== undefined;
|
|
@@ -191,11 +212,13 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
191
212
|
}
|
|
192
213
|
}, [action]);
|
|
193
214
|
return /*#__PURE__*/_jsxs(ScrollView, {
|
|
215
|
+
testID: `${REDUX_TEST_ID}-detail-scroll`,
|
|
194
216
|
style: styles.container,
|
|
195
217
|
contentContainerStyle: styles.contentContainer,
|
|
196
218
|
showsVerticalScrollIndicator: false,
|
|
197
219
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
198
220
|
style: styles.card,
|
|
221
|
+
testID: `${REDUX_TEST_ID}-info-card`,
|
|
199
222
|
children: [/*#__PURE__*/_jsx(SectionHeader, {
|
|
200
223
|
icon: Info,
|
|
201
224
|
title: "ACTION INFO"
|
|
@@ -204,15 +227,18 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
204
227
|
children: [/*#__PURE__*/_jsx(InfoRow, {
|
|
205
228
|
label: "Type",
|
|
206
229
|
value: action.type,
|
|
207
|
-
mono: true
|
|
230
|
+
mono: true,
|
|
231
|
+
testID: `${REDUX_TEST_ID}-info-type`
|
|
208
232
|
}), action.sliceName && /*#__PURE__*/_jsx(InfoRow, {
|
|
209
233
|
label: "Slice",
|
|
210
234
|
value: action.sliceName,
|
|
211
|
-
mono: true
|
|
235
|
+
mono: true,
|
|
236
|
+
testID: `${REDUX_TEST_ID}-info-slice`
|
|
212
237
|
}), /*#__PURE__*/_jsx(InfoRow, {
|
|
213
238
|
label: "Action",
|
|
214
239
|
value: action.actionName,
|
|
215
|
-
mono: true
|
|
240
|
+
mono: true,
|
|
241
|
+
testID: `${REDUX_TEST_ID}-info-action`
|
|
216
242
|
}), /*#__PURE__*/_jsxs(View, {
|
|
217
243
|
style: styles.badgeRow,
|
|
218
244
|
children: [/*#__PURE__*/_jsx(View, {
|
|
@@ -223,6 +249,7 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
223
249
|
style: [styles.categoryText, {
|
|
224
250
|
color: categoryInfo.color
|
|
225
251
|
}],
|
|
252
|
+
testID: `${REDUX_TEST_ID}-info-category`,
|
|
226
253
|
children: categoryInfo.label
|
|
227
254
|
})
|
|
228
255
|
}), /*#__PURE__*/_jsxs(View, {
|
|
@@ -232,6 +259,7 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
232
259
|
color: action.isSlowAction ? buoyColors.error : night.color.textSecondary
|
|
233
260
|
}), /*#__PURE__*/_jsx(Text, {
|
|
234
261
|
style: [styles.durationText, action.isSlowAction && styles.durationTextSlow],
|
|
262
|
+
testID: `${REDUX_TEST_ID}-info-duration`,
|
|
235
263
|
children: formatDuration(action.duration)
|
|
236
264
|
}), action.isSlowAction && /*#__PURE__*/_jsx(AlertTriangle, {
|
|
237
265
|
size: 10,
|
|
@@ -244,12 +272,14 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
244
272
|
color: buoyColors.success
|
|
245
273
|
}), /*#__PURE__*/_jsx(Text, {
|
|
246
274
|
style: styles.changesText,
|
|
275
|
+
testID: `${REDUX_TEST_ID}-info-changes`,
|
|
247
276
|
children: action.diffSummary || "changed"
|
|
248
277
|
})]
|
|
249
278
|
}) : /*#__PURE__*/_jsx(View, {
|
|
250
279
|
style: styles.noChangesBadge,
|
|
251
280
|
children: /*#__PURE__*/_jsx(Text, {
|
|
252
281
|
style: styles.noChangesText,
|
|
282
|
+
testID: `${REDUX_TEST_ID}-info-no-changes`,
|
|
253
283
|
children: "no change"
|
|
254
284
|
})
|
|
255
285
|
})]
|
|
@@ -257,6 +287,7 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
257
287
|
})]
|
|
258
288
|
}), hasRelatedActions && /*#__PURE__*/_jsxs(View, {
|
|
259
289
|
style: styles.card,
|
|
290
|
+
testID: `${REDUX_TEST_ID}-async-card`,
|
|
260
291
|
children: [/*#__PURE__*/_jsx(SectionHeader, {
|
|
261
292
|
icon: Hash,
|
|
262
293
|
title: "ASYNC FLOW",
|
|
@@ -264,6 +295,7 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
264
295
|
style: styles.asyncBadge,
|
|
265
296
|
children: /*#__PURE__*/_jsxs(Text, {
|
|
266
297
|
style: styles.asyncBadgeText,
|
|
298
|
+
testID: `${REDUX_TEST_ID}-async-count`,
|
|
267
299
|
children: [relatedActions.length, " ACTIONS"]
|
|
268
300
|
})
|
|
269
301
|
})
|
|
@@ -271,6 +303,7 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
271
303
|
style: styles.cardContent,
|
|
272
304
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
273
305
|
style: styles.baseActionType,
|
|
306
|
+
testID: `${REDUX_TEST_ID}-async-base`,
|
|
274
307
|
children: baseActionType
|
|
275
308
|
}), totalDuration !== null && /*#__PURE__*/_jsxs(View, {
|
|
276
309
|
style: styles.totalDurationRow,
|
|
@@ -279,6 +312,7 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
279
312
|
children: "Total Duration"
|
|
280
313
|
}), /*#__PURE__*/_jsxs(Text, {
|
|
281
314
|
style: styles.totalDurationValue,
|
|
315
|
+
testID: `${REDUX_TEST_ID}-async-total`,
|
|
282
316
|
children: [totalDuration, "ms"]
|
|
283
317
|
})]
|
|
284
318
|
}), /*#__PURE__*/_jsx(View, {
|
|
@@ -291,6 +325,7 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
291
325
|
onPress: () => onNavigateToAction?.(relatedAction),
|
|
292
326
|
disabled: isCurrent || !onNavigateToAction,
|
|
293
327
|
activeOpacity: 0.7,
|
|
328
|
+
testID: `${REDUX_TEST_ID}-async-step-${index}`,
|
|
294
329
|
children: [index < relatedActions.length - 1 && /*#__PURE__*/_jsx(View, {
|
|
295
330
|
style: styles.timelineConnector
|
|
296
331
|
}), /*#__PURE__*/_jsx(View, {
|
|
@@ -309,10 +344,12 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
309
344
|
style: [styles.timelineStatusText, {
|
|
310
345
|
color: relatedCategoryInfo.color
|
|
311
346
|
}],
|
|
347
|
+
testID: `${REDUX_TEST_ID}-async-step-${index}-status`,
|
|
312
348
|
children: relatedCategoryInfo.label
|
|
313
349
|
})
|
|
314
350
|
}), isCurrent && /*#__PURE__*/_jsx(View, {
|
|
315
351
|
style: styles.currentBadge,
|
|
352
|
+
testID: `${REDUX_TEST_ID}-async-step-${index}-current`,
|
|
316
353
|
children: /*#__PURE__*/_jsx(Text, {
|
|
317
354
|
style: styles.currentBadgeText,
|
|
318
355
|
children: "CURRENT"
|
|
@@ -329,6 +366,7 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
329
366
|
})]
|
|
330
367
|
}), hasPayload && /*#__PURE__*/_jsxs(View, {
|
|
331
368
|
style: styles.card,
|
|
369
|
+
testID: `${REDUX_TEST_ID}-payload-card`,
|
|
332
370
|
children: [/*#__PURE__*/_jsx(SectionHeader, {
|
|
333
371
|
icon: Box,
|
|
334
372
|
title: "PAYLOAD",
|
|
@@ -351,6 +389,7 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
351
389
|
})]
|
|
352
390
|
}), hasMeta && /*#__PURE__*/_jsxs(View, {
|
|
353
391
|
style: styles.card,
|
|
392
|
+
testID: `${REDUX_TEST_ID}-meta-card`,
|
|
354
393
|
children: [/*#__PURE__*/_jsx(SectionHeader, {
|
|
355
394
|
icon: Hash,
|
|
356
395
|
title: "ASYNC METADATA",
|
|
@@ -374,7 +413,8 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
374
413
|
}), action.meta?.requestStatus && /*#__PURE__*/_jsx(InfoRow, {
|
|
375
414
|
label: "Status",
|
|
376
415
|
value: action.meta.requestStatus.toUpperCase(),
|
|
377
|
-
valueColor: categoryInfo.color
|
|
416
|
+
valueColor: categoryInfo.color,
|
|
417
|
+
testID: `${REDUX_TEST_ID}-meta-status`
|
|
378
418
|
}), action.meta?.arg !== undefined && /*#__PURE__*/_jsxs(View, {
|
|
379
419
|
style: styles.argSection,
|
|
380
420
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
@@ -409,6 +449,7 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
409
449
|
})]
|
|
410
450
|
}), hasError && /*#__PURE__*/_jsxs(View, {
|
|
411
451
|
style: [styles.card, styles.errorCard],
|
|
452
|
+
testID: `${REDUX_TEST_ID}-error-card`,
|
|
412
453
|
children: [/*#__PURE__*/_jsx(SectionHeader, {
|
|
413
454
|
icon: AlertCircle,
|
|
414
455
|
title: "ERROR",
|
|
@@ -416,6 +457,7 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
416
457
|
style: styles.errorBadge,
|
|
417
458
|
children: /*#__PURE__*/_jsx(Text, {
|
|
418
459
|
style: styles.errorBadgeText,
|
|
460
|
+
testID: `${REDUX_TEST_ID}-error-badge`,
|
|
419
461
|
children: "REJECTED"
|
|
420
462
|
})
|
|
421
463
|
})
|
|
@@ -434,6 +476,7 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
434
476
|
})]
|
|
435
477
|
}), /*#__PURE__*/_jsxs(View, {
|
|
436
478
|
style: styles.card,
|
|
479
|
+
testID: `${REDUX_TEST_ID}-full-action-card`,
|
|
437
480
|
children: [/*#__PURE__*/_jsx(SectionHeader, {
|
|
438
481
|
icon: FileText,
|
|
439
482
|
title: "FULL ACTION OBJECT"
|
|
@@ -456,7 +499,8 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
456
499
|
children: [/*#__PURE__*/_jsx(ReduxActionButton, {
|
|
457
500
|
type: "replay",
|
|
458
501
|
text: "REPLAY",
|
|
459
|
-
onPress: handleReplay
|
|
502
|
+
onPress: handleReplay,
|
|
503
|
+
testID: `${REDUX_TEST_ID}-replay`
|
|
460
504
|
}), /*#__PURE__*/_jsx(Text, {
|
|
461
505
|
style: styles.actionDescription,
|
|
462
506
|
children: "Dispatch again"
|
|
@@ -466,17 +510,21 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
466
510
|
children: [/*#__PURE__*/_jsx(ReduxActionButton, {
|
|
467
511
|
type: "jump",
|
|
468
512
|
text: "JUMP",
|
|
469
|
-
onPress: handleJump
|
|
513
|
+
onPress: handleJump,
|
|
514
|
+
disabled: jumpDisabled,
|
|
515
|
+
testID: `${REDUX_TEST_ID}-jump`
|
|
470
516
|
}), /*#__PURE__*/_jsx(Text, {
|
|
471
517
|
style: styles.actionDescription,
|
|
472
|
-
|
|
518
|
+
testID: `${REDUX_TEST_ID}-jump-description`,
|
|
519
|
+
children: stateEvicted ? "State not retained" : canTimeTravel ? "Restore this state" : "Time travel not wired"
|
|
473
520
|
})]
|
|
474
521
|
}), /*#__PURE__*/_jsxs(View, {
|
|
475
522
|
style: styles.actionButtonWrapper,
|
|
476
523
|
children: [/*#__PURE__*/_jsx(ReduxActionButton, {
|
|
477
524
|
type: "copy",
|
|
478
525
|
text: "COPY ACTION",
|
|
479
|
-
onPress: handleCopyAction
|
|
526
|
+
onPress: handleCopyAction,
|
|
527
|
+
testID: `${REDUX_TEST_ID}-copy-action`
|
|
480
528
|
}), /*#__PURE__*/_jsx(Text, {
|
|
481
529
|
style: styles.actionDescription,
|
|
482
530
|
children: isPro ? "Copy to clipboard" : "Pro feature"
|
|
@@ -486,7 +534,8 @@ export const ReduxActionInfoView = /*#__PURE__*/memo(function ReduxActionInfoVie
|
|
|
486
534
|
children: [/*#__PURE__*/_jsx(ReduxActionButton, {
|
|
487
535
|
type: "copy",
|
|
488
536
|
text: "COPY PAYLOAD",
|
|
489
|
-
onPress: handleCopyPayload
|
|
537
|
+
onPress: handleCopyPayload,
|
|
538
|
+
testID: `${REDUX_TEST_ID}-copy-payload`
|
|
490
539
|
}), /*#__PURE__*/_jsx(Text, {
|
|
491
540
|
style: styles.actionDescription,
|
|
492
541
|
children: isPro ? "Copy payload only" : "Pro feature"
|
|
@@ -210,7 +210,9 @@ function getPrimaryText(action) {
|
|
|
210
210
|
// tick re-renders only that Text, not this row.
|
|
211
211
|
export const ReduxActionItem = /*#__PURE__*/memo(function ReduxActionItem({
|
|
212
212
|
action,
|
|
213
|
-
onPress
|
|
213
|
+
onPress,
|
|
214
|
+
linkedCount,
|
|
215
|
+
testID
|
|
214
216
|
}) {
|
|
215
217
|
const sliceName = action.sliceName ?? undefined;
|
|
216
218
|
const statusColor = getStatusColor(action.category, action.hasStateChange, sliceName);
|
|
@@ -219,10 +221,11 @@ export const ReduxActionItem = /*#__PURE__*/memo(function ReduxActionItem({
|
|
|
219
221
|
const primaryText = getPrimaryText(action);
|
|
220
222
|
const actionBadge = getActionBadge(action);
|
|
221
223
|
|
|
222
|
-
// Check for linked async actions
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const hasLinkedActions = linkedCount > 1;
|
|
224
|
+
// Check for linked async actions. The COUNT comes from the list (see the
|
|
225
|
+
// prop's doc); the ordered list is only needed to say where in the flow this
|
|
226
|
+
// action sits, and only when there is a flow at all.
|
|
227
|
+
const hasLinkedActions = (linkedCount ?? 0) > 1;
|
|
228
|
+
const linkedActions = hasLinkedActions ? reduxActionStore.getLinkedActions(action) : [];
|
|
226
229
|
|
|
227
230
|
// Get position in async sequence (1/2, 2/2, etc.)
|
|
228
231
|
const asyncPosition = hasLinkedActions ? linkedActions.findIndex(a => a.id === action.id) + 1 : 0;
|
|
@@ -247,10 +250,12 @@ export const ReduxActionItem = /*#__PURE__*/memo(function ReduxActionItem({
|
|
|
247
250
|
style: [styles.asyncSequenceText, {
|
|
248
251
|
color: opColor.text
|
|
249
252
|
}],
|
|
253
|
+
testID: testID ? `${testID}-seq` : undefined,
|
|
250
254
|
children: [asyncPosition, "/", linkedCount, " #", operationIndex]
|
|
251
255
|
})
|
|
252
256
|
}), /*#__PURE__*/_jsx(View, {
|
|
253
257
|
style: styles.slowBadge,
|
|
258
|
+
testID: testID ? `${testID}-slow` : undefined,
|
|
254
259
|
children: /*#__PURE__*/_jsx(Clock, {
|
|
255
260
|
size: 12,
|
|
256
261
|
color: buoyColors.error
|
|
@@ -269,10 +274,12 @@ export const ReduxActionItem = /*#__PURE__*/memo(function ReduxActionItem({
|
|
|
269
274
|
style: [styles.asyncSequenceText, {
|
|
270
275
|
color: opColor.text
|
|
271
276
|
}],
|
|
277
|
+
testID: testID ? `${testID}-seq` : undefined,
|
|
272
278
|
children: [asyncPosition, "/", linkedCount, " #", operationIndex]
|
|
273
279
|
})
|
|
274
280
|
}), /*#__PURE__*/_jsx(View, {
|
|
275
281
|
style: styles.errorBadge,
|
|
282
|
+
testID: testID ? `${testID}-error` : undefined,
|
|
276
283
|
children: /*#__PURE__*/_jsx(AlertCircle, {
|
|
277
284
|
size: 12,
|
|
278
285
|
color: buoyColors.error
|
|
@@ -291,10 +298,12 @@ export const ReduxActionItem = /*#__PURE__*/memo(function ReduxActionItem({
|
|
|
291
298
|
style: [styles.asyncSequenceText, {
|
|
292
299
|
color: opColor.text
|
|
293
300
|
}],
|
|
301
|
+
testID: testID ? `${testID}-seq` : undefined,
|
|
294
302
|
children: [asyncPosition, "/", linkedCount, " #", operationIndex]
|
|
295
303
|
})
|
|
296
304
|
}), action.hasStateChange && /*#__PURE__*/_jsx(View, {
|
|
297
305
|
style: styles.changeBadge,
|
|
306
|
+
testID: testID ? `${testID}-change` : undefined,
|
|
298
307
|
children: /*#__PURE__*/_jsx(Zap, {
|
|
299
308
|
size: 12,
|
|
300
309
|
color: buoyColors.warning
|
|
@@ -309,9 +318,11 @@ export const ReduxActionItem = /*#__PURE__*/memo(function ReduxActionItem({
|
|
|
309
318
|
style: [styles.actionBadgeText, {
|
|
310
319
|
color: statusColor
|
|
311
320
|
}],
|
|
321
|
+
testID: testID ? `${testID}-badge` : undefined,
|
|
312
322
|
children: actionBadge
|
|
313
323
|
}), /*#__PURE__*/_jsx(View, {
|
|
314
324
|
style: styles.changeBadge,
|
|
325
|
+
testID: testID ? `${testID}-change` : undefined,
|
|
315
326
|
children: /*#__PURE__*/_jsx(Zap, {
|
|
316
327
|
size: 12,
|
|
317
328
|
color: buoyColors.warning
|
|
@@ -320,6 +331,10 @@ export const ReduxActionItem = /*#__PURE__*/memo(function ReduxActionItem({
|
|
|
320
331
|
});
|
|
321
332
|
}
|
|
322
333
|
return /*#__PURE__*/_jsx(CompactRow, {
|
|
334
|
+
testID: testID,
|
|
335
|
+
statusTestID: testID ? `${testID}-status` : undefined,
|
|
336
|
+
statusSublabelTestID: testID ? `${testID}-sublabel` : undefined,
|
|
337
|
+
accessibilityLabel: `${primaryText}, ${statusLabel}, ${sublabel}`,
|
|
323
338
|
statusDotColor: statusColor,
|
|
324
339
|
statusLabel: statusLabel,
|
|
325
340
|
statusSublabel: sublabel,
|
|
@@ -50,6 +50,13 @@ export const ReduxDetailViewToggle = /*#__PURE__*/memo(function ReduxDetailViewT
|
|
|
50
50
|
onPress: () => !isDisabled && onViewChange(config.key),
|
|
51
51
|
activeOpacity: isDisabled ? 1 : 0.8,
|
|
52
52
|
disabled: isDisabled,
|
|
53
|
+
testID: `buoy-redux-view-${config.key}`,
|
|
54
|
+
accessibilityRole: "button",
|
|
55
|
+
accessibilityLabel: config.label,
|
|
56
|
+
accessibilityState: {
|
|
57
|
+
selected: isActive,
|
|
58
|
+
disabled: isDisabled
|
|
59
|
+
},
|
|
53
60
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
54
61
|
style: styles.cardContent,
|
|
55
62
|
children: [/*#__PURE__*/_jsx(IconComponent, {
|