@buoy-gg/jotai 7.0.30 → 7.0.32
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 +7 -0
- package/lib/commonjs/jotai/components/JotaiAtomBrowser.js +30 -119
- package/lib/commonjs/jotai/components/JotaiAtomChangeItem.js +19 -7
- package/lib/commonjs/jotai/components/JotaiAtomDetailContent.js +41 -5
- package/lib/commonjs/jotai/components/JotaiAtomValueDetailContent.js +444 -0
- package/lib/commonjs/jotai/components/JotaiEventFilterView.js +15 -0
- package/lib/commonjs/jotai/components/JotaiModal.js +159 -23
- package/lib/commonjs/jotai/utils/jotaiStateStore.js +9 -4
- package/lib/commonjs/jotai/utils/testIDs.js +90 -0
- package/lib/commonjs/jotai/utils/watchAtoms.js +8 -2
- package/lib/commonjs/jotai/utils/writeSemantics.js +47 -0
- package/lib/module/index.js +1 -0
- package/lib/module/jotai/components/JotaiAtomBrowser.js +33 -122
- package/lib/module/jotai/components/JotaiAtomChangeItem.js +19 -7
- package/lib/module/jotai/components/JotaiAtomDetailContent.js +41 -5
- package/lib/module/jotai/components/JotaiAtomValueDetailContent.js +442 -0
- package/lib/module/jotai/components/JotaiEventFilterView.js +15 -0
- package/lib/module/jotai/components/JotaiModal.js +160 -24
- package/lib/module/jotai/utils/jotaiStateStore.js +9 -4
- package/lib/module/jotai/utils/testIDs.js +82 -0
- package/lib/module/jotai/utils/watchAtoms.js +8 -2
- package/lib/module/jotai/utils/writeSemantics.js +43 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/jotai/components/JotaiAtomBrowser.d.ts +6 -5
- package/lib/typescript/jotai/components/JotaiAtomBrowser.d.ts.map +1 -1
- package/lib/typescript/jotai/components/JotaiAtomChangeItem.d.ts +5 -0
- package/lib/typescript/jotai/components/JotaiAtomChangeItem.d.ts.map +1 -1
- package/lib/typescript/jotai/components/JotaiAtomDetailContent.d.ts.map +1 -1
- package/lib/typescript/jotai/components/JotaiAtomValueDetailContent.d.ts +40 -0
- package/lib/typescript/jotai/components/JotaiAtomValueDetailContent.d.ts.map +1 -0
- package/lib/typescript/jotai/components/JotaiEventFilterView.d.ts.map +1 -1
- package/lib/typescript/jotai/components/JotaiModal.d.ts.map +1 -1
- package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts +1 -0
- package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/jotai/types/index.d.ts +12 -0
- package/lib/typescript/jotai/types/index.d.ts.map +1 -1
- package/lib/typescript/jotai/utils/jotaiStateStore.d.ts +1 -1
- package/lib/typescript/jotai/utils/jotaiStateStore.d.ts.map +1 -1
- package/lib/typescript/jotai/utils/testIDs.d.ts +62 -0
- package/lib/typescript/jotai/utils/testIDs.d.ts.map +1 -0
- package/lib/typescript/jotai/utils/watchAtoms.d.ts +8 -2
- package/lib/typescript/jotai/utils/watchAtoms.d.ts.map +1 -1
- package/lib/typescript/jotai/utils/writeSemantics.d.ts +25 -0
- package/lib/typescript/jotai/utils/writeSemantics.d.ts.map +1 -0
- package/package.json +3 -3
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
* JotaiAtomBrowser
|
|
5
5
|
*
|
|
6
6
|
* Atoms tab — shows all registered Jotai atoms and their current value.
|
|
7
|
-
* Mirrors ZustandStoreBrowser.tsx from @buoy-gg/zustand
|
|
7
|
+
* Mirrors ZustandStoreBrowser.tsx from @buoy-gg/zustand: tapping a card
|
|
8
|
+
* NAVIGATES to the atom's detail screen (live editor + details + read-only
|
|
9
|
+
* tree) rather than expanding in place — the modal owns that selection.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
|
-
import {
|
|
11
|
-
import { View, Text, StyleSheet, ScrollView
|
|
12
|
-
import { CompactRow,
|
|
13
|
-
import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
|
|
12
|
+
import { useMemo } from "react";
|
|
13
|
+
import { View, Text, StyleSheet, ScrollView } from "react-native";
|
|
14
|
+
import { CompactRow, night, Box } from "@buoy-gg/shared-ui";
|
|
14
15
|
import { jotaiStateStore } from "../utils/jotaiStateStore";
|
|
16
|
+
import { JOTAI_TEST_ID, atomRowTestID } from "../utils/testIDs";
|
|
15
17
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
16
18
|
function getValuePreview(atom) {
|
|
17
19
|
try {
|
|
@@ -36,84 +38,26 @@ function getValuePreview(atom) {
|
|
|
36
38
|
return "";
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
function AtomExpandedContent({
|
|
51
|
-
atom,
|
|
52
|
-
onViewHistory
|
|
53
|
-
}) {
|
|
54
|
-
const value = atom.getValue();
|
|
55
|
-
const displayValue = useMemo(() => {
|
|
56
|
-
if (value && typeof value === "object") {
|
|
57
|
-
const filtered = {};
|
|
58
|
-
for (const [key, v] of Object.entries(value)) {
|
|
59
|
-
if (typeof v !== "function") filtered[key] = v;
|
|
60
|
-
}
|
|
61
|
-
return parseValue(filtered);
|
|
62
|
-
}
|
|
63
|
-
return parseValue(value);
|
|
64
|
-
}, [value]);
|
|
65
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
66
|
-
style: expandedStyles.container,
|
|
67
|
-
children: [/*#__PURE__*/_jsx(ExpandedInfoRow, {
|
|
68
|
-
label: "Type",
|
|
69
|
-
children: /*#__PURE__*/_jsx(PillBadge, {
|
|
70
|
-
color: atom.color,
|
|
71
|
-
children: "JOTAI"
|
|
72
|
-
})
|
|
73
|
-
}), atom.changeCount > 0 && /*#__PURE__*/_jsxs(ExpandedInfoRow, {
|
|
74
|
-
label: "Changes",
|
|
75
|
-
children: [/*#__PURE__*/_jsx(PillBadge, {
|
|
76
|
-
color: buoyColors.warning,
|
|
77
|
-
children: String(atom.changeCount)
|
|
78
|
-
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
79
|
-
onPress: () => onViewHistory(atom.label),
|
|
80
|
-
style: expandedStyles.viewHistoryButton,
|
|
81
|
-
hitSlop: {
|
|
82
|
-
top: 6,
|
|
83
|
-
bottom: 6,
|
|
84
|
-
left: 6,
|
|
85
|
-
right: 6
|
|
86
|
-
},
|
|
87
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
88
|
-
style: [expandedStyles.viewHistoryText, {
|
|
89
|
-
color: atom.color
|
|
90
|
-
}],
|
|
91
|
-
children: "view history \u2192"
|
|
92
|
-
})
|
|
93
|
-
})]
|
|
94
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
95
|
-
style: expandedStyles.dataContainer,
|
|
96
|
-
children: displayValue && typeof displayValue === "object" ? /*#__PURE__*/_jsx(DataViewer, {
|
|
97
|
-
title: "",
|
|
98
|
-
data: displayValue,
|
|
99
|
-
showTypeFilter: true,
|
|
100
|
-
rawMode: true,
|
|
101
|
-
initialExpanded: true
|
|
102
|
-
}) : /*#__PURE__*/_jsx(Text, {
|
|
103
|
-
style: expandedStyles.primitiveText,
|
|
104
|
-
children: String(value ?? "undefined")
|
|
105
|
-
})
|
|
106
|
-
})]
|
|
107
|
-
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The card's categorical slot — the atom's write story, mirroring the
|
|
44
|
+
* zustand card's "persisted"/"in-memory" backing label.
|
|
45
|
+
*/
|
|
46
|
+
function getWriteKindLabel(atom) {
|
|
47
|
+
if (atom.writeKind === "value") return "primitive";
|
|
48
|
+
if (atom.writeKind === "action") return "custom write";
|
|
49
|
+
return atom.setValue ? "writable" : "derived";
|
|
108
50
|
}
|
|
109
51
|
function EmptyBrowserState() {
|
|
110
52
|
return /*#__PURE__*/_jsxs(View, {
|
|
111
53
|
style: styles.emptyState,
|
|
54
|
+
testID: `${JOTAI_TEST_ID}-atoms-empty`,
|
|
112
55
|
children: [/*#__PURE__*/_jsx(Box, {
|
|
113
56
|
size: 32,
|
|
114
57
|
color: night.color.textTertiary
|
|
115
58
|
}), /*#__PURE__*/_jsx(Text, {
|
|
116
59
|
style: styles.emptyTitle,
|
|
60
|
+
testID: `${JOTAI_TEST_ID}-atoms-empty-title`,
|
|
117
61
|
children: "No atoms registered"
|
|
118
62
|
}), /*#__PURE__*/_jsx(Text, {
|
|
119
63
|
style: styles.emptyText,
|
|
@@ -124,30 +68,23 @@ function EmptyBrowserState() {
|
|
|
124
68
|
export function JotaiAtomBrowser({
|
|
125
69
|
atoms,
|
|
126
70
|
searchQuery,
|
|
127
|
-
|
|
128
|
-
onAtomExpand
|
|
71
|
+
onOpenAtom
|
|
129
72
|
}) {
|
|
130
|
-
const [expandedAtom, setExpandedAtom] = useState(null);
|
|
131
73
|
const filteredAtoms = useMemo(() => {
|
|
132
74
|
if (!searchQuery) return atoms;
|
|
133
75
|
const search = searchQuery.toLowerCase();
|
|
134
76
|
return atoms.filter(a => a.label.toLowerCase().includes(search));
|
|
135
77
|
}, [atoms, searchQuery]);
|
|
136
|
-
const handleAtomPress = useCallback(atom => {
|
|
137
|
-
setExpandedAtom(prev => {
|
|
138
|
-
const next = prev === atom.label ? null : atom.label;
|
|
139
|
-
if (next) onAtomExpand?.(atom.label);
|
|
140
|
-
return next;
|
|
141
|
-
});
|
|
142
|
-
}, [onAtomExpand]);
|
|
143
78
|
if (filteredAtoms.length === 0 && !searchQuery) {
|
|
144
79
|
return /*#__PURE__*/_jsx(EmptyBrowserState, {});
|
|
145
80
|
}
|
|
146
81
|
if (filteredAtoms.length === 0 && searchQuery) {
|
|
147
82
|
return /*#__PURE__*/_jsxs(View, {
|
|
148
83
|
style: styles.emptyState,
|
|
84
|
+
testID: `${JOTAI_TEST_ID}-atoms-no-match`,
|
|
149
85
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
150
86
|
style: styles.emptyTitle,
|
|
87
|
+
testID: `${JOTAI_TEST_ID}-atoms-no-match-title`,
|
|
151
88
|
children: "No matching atoms"
|
|
152
89
|
}), /*#__PURE__*/_jsxs(Text, {
|
|
153
90
|
style: styles.emptyText,
|
|
@@ -159,6 +96,7 @@ export function JotaiAtomBrowser({
|
|
|
159
96
|
style: styles.container,
|
|
160
97
|
contentContainerStyle: styles.scrollContent,
|
|
161
98
|
showsVerticalScrollIndicator: true,
|
|
99
|
+
testID: `${JOTAI_TEST_ID}-atoms-scroll`,
|
|
162
100
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
163
101
|
style: styles.sectionHeader,
|
|
164
102
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
@@ -168,27 +106,27 @@ export function JotaiAtomBrowser({
|
|
|
168
106
|
style: styles.sectionCountBadge,
|
|
169
107
|
children: /*#__PURE__*/_jsx(Text, {
|
|
170
108
|
style: styles.sectionCountText,
|
|
109
|
+
testID: `${JOTAI_TEST_ID}-atoms-count`,
|
|
171
110
|
children: filteredAtoms.length
|
|
172
111
|
})
|
|
173
112
|
})]
|
|
174
113
|
}), filteredAtoms.map(atom => {
|
|
175
|
-
const isExpanded = expandedAtom === atom.label;
|
|
176
114
|
const valuePreview = getValuePreview(atom);
|
|
177
115
|
const atomColor = jotaiStateStore.getAtomColor(atom.label);
|
|
116
|
+
const rowID = atomRowTestID(atom.label);
|
|
178
117
|
return /*#__PURE__*/_jsxs(View, {
|
|
179
118
|
style: styles.atomRowWrapper,
|
|
180
119
|
children: [/*#__PURE__*/_jsx(CompactRow, {
|
|
181
120
|
statusDotColor: atomColor,
|
|
182
|
-
statusLabel: atom
|
|
183
|
-
|
|
184
|
-
|
|
121
|
+
statusLabel: getWriteKindLabel(atom),
|
|
122
|
+
primaryText: atom.label,
|
|
123
|
+
secondaryText: valuePreview || undefined,
|
|
185
124
|
showChevron: true,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}) : undefined
|
|
125
|
+
onPress: () => onOpenAtom(atom.label),
|
|
126
|
+
testID: rowID,
|
|
127
|
+
statusTestID: `${rowID}-kind`,
|
|
128
|
+
primaryTestID: `${rowID}-name`,
|
|
129
|
+
secondaryTestID: `${rowID}-preview`
|
|
192
130
|
}), atom.changeCount > 0 && /*#__PURE__*/_jsx(View, {
|
|
193
131
|
style: [styles.absCountBadge, {
|
|
194
132
|
backgroundColor: atomColor + "22",
|
|
@@ -199,6 +137,7 @@ export function JotaiAtomBrowser({
|
|
|
199
137
|
style: [styles.absCountText, {
|
|
200
138
|
color: atomColor
|
|
201
139
|
}],
|
|
140
|
+
testID: `${rowID}-count`,
|
|
202
141
|
children: String(atom.changeCount)
|
|
203
142
|
})
|
|
204
143
|
})]
|
|
@@ -274,32 +213,4 @@ const styles = StyleSheet.create({
|
|
|
274
213
|
textAlign: "center",
|
|
275
214
|
lineHeight: 18
|
|
276
215
|
}
|
|
277
|
-
});
|
|
278
|
-
const expandedStyles = StyleSheet.create({
|
|
279
|
-
container: {
|
|
280
|
-
gap: 10
|
|
281
|
-
},
|
|
282
|
-
viewHistoryButton: {
|
|
283
|
-
marginLeft: 4
|
|
284
|
-
},
|
|
285
|
-
viewHistoryText: {
|
|
286
|
-
fontSize: 10,
|
|
287
|
-
fontWeight: "600",
|
|
288
|
-
fontFamily: "monospace"
|
|
289
|
-
},
|
|
290
|
-
dataContainer: {
|
|
291
|
-
// Inset display panel — darker than the card, the night track color.
|
|
292
|
-
backgroundColor: night.color.bg,
|
|
293
|
-
borderRadius: 6,
|
|
294
|
-
borderWidth: 1,
|
|
295
|
-
borderColor: night.color.border,
|
|
296
|
-
overflow: "hidden",
|
|
297
|
-
minHeight: 60
|
|
298
|
-
},
|
|
299
|
-
primitiveText: {
|
|
300
|
-
color: night.color.text,
|
|
301
|
-
fontSize: 12,
|
|
302
|
-
fontFamily: "monospace",
|
|
303
|
-
padding: 14
|
|
304
|
-
}
|
|
305
216
|
});
|
|
@@ -60,28 +60,37 @@ function getPrimaryText(change) {
|
|
|
60
60
|
// ZustandStateChangeItem (same pattern).
|
|
61
61
|
export const JotaiAtomChangeItem = /*#__PURE__*/memo(function JotaiAtomChangeItem({
|
|
62
62
|
change,
|
|
63
|
-
onPress
|
|
63
|
+
onPress,
|
|
64
|
+
testID
|
|
64
65
|
}) {
|
|
65
66
|
const statusColor = getCategoryColor(change.category, change.atomLabel);
|
|
66
67
|
const statusLabel = getStatusLabel(change.category, change.atomLabel);
|
|
67
68
|
const sublabel = getSublabel(change);
|
|
68
69
|
const primaryText = getPrimaryText(change);
|
|
69
70
|
const badgeText = getBadgeText(change);
|
|
70
|
-
|
|
71
|
+
|
|
72
|
+
// Always a custom badge, even when there is no icon to hang off it. The
|
|
73
|
+
// alternative — letting CompactRow draw `badgeText` itself — leaves the one
|
|
74
|
+
// row whose badge matters most (a write that moved the atom's identity but
|
|
75
|
+
// none of its contents) as the only row in the list with no handle on its
|
|
76
|
+
// badge. Same call, and the same reason, as ZustandStateChangeItem.
|
|
77
|
+
const customBadge = /*#__PURE__*/_jsxs(View, {
|
|
71
78
|
style: styles.badgeContainer,
|
|
72
79
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
73
80
|
style: [styles.badgeText, {
|
|
74
81
|
color: statusColor
|
|
75
82
|
}],
|
|
83
|
+
testID: testID ? `${testID}-badge` : undefined,
|
|
76
84
|
children: badgeText
|
|
77
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
85
|
+
}), change.hasValueChange ? /*#__PURE__*/_jsx(View, {
|
|
78
86
|
style: styles.changeBadge,
|
|
87
|
+
testID: testID ? `${testID}-change` : undefined,
|
|
79
88
|
children: /*#__PURE__*/_jsx(Zap, {
|
|
80
89
|
size: 12,
|
|
81
90
|
color: buoyColors.warning
|
|
82
91
|
})
|
|
83
|
-
})]
|
|
84
|
-
})
|
|
92
|
+
}) : null]
|
|
93
|
+
});
|
|
85
94
|
return /*#__PURE__*/_jsx(CompactRow, {
|
|
86
95
|
statusDotColor: statusColor,
|
|
87
96
|
statusLabel: statusLabel,
|
|
@@ -91,10 +100,13 @@ export const JotaiAtomChangeItem = /*#__PURE__*/memo(function JotaiAtomChangeIte
|
|
|
91
100
|
timestamp: change.timestamp
|
|
92
101
|
}),
|
|
93
102
|
customBadge: customBadge,
|
|
94
|
-
badgeText: customBadge ? undefined : badgeText,
|
|
95
103
|
badgeColor: statusColor,
|
|
96
104
|
showChevron: true,
|
|
97
|
-
onPress: () => onPress(change)
|
|
105
|
+
onPress: () => onPress(change),
|
|
106
|
+
testID: testID,
|
|
107
|
+
statusTestID: testID ? `${testID}-status` : undefined,
|
|
108
|
+
statusSublabelTestID: testID ? `${testID}-sublabel` : undefined,
|
|
109
|
+
primaryTestID: testID ? `${testID}-keys` : undefined
|
|
98
110
|
});
|
|
99
111
|
});
|
|
100
112
|
const styles = StyleSheet.create({
|
|
@@ -17,6 +17,7 @@ import { buoyColors, night, EventStepperFooter, formatRelativeTime, parseValue,
|
|
|
17
17
|
import { DataViewer, SplitDiffViewer, TreeDiffViewer, diffThemes } from "@buoy-gg/shared-ui/dataViewer";
|
|
18
18
|
import { useIsPro } from "@buoy-gg/license";
|
|
19
19
|
import { jotaiStateStore } from "../utils/jotaiStateStore";
|
|
20
|
+
import { JOTAI_TEST_ID, slugForTestID } from "../utils/testIDs";
|
|
20
21
|
|
|
21
22
|
// ---- View Toggle ----
|
|
22
23
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -53,6 +54,13 @@ function DetailViewToggle({
|
|
|
53
54
|
}], isDisabled && toggleStyles.cardDisabled],
|
|
54
55
|
onPress: () => !isDisabled && onViewChange(config.key),
|
|
55
56
|
activeOpacity: 0.7,
|
|
57
|
+
testID: `${JOTAI_TEST_ID}-view-${config.key}`,
|
|
58
|
+
accessibilityRole: "button",
|
|
59
|
+
accessibilityLabel: config.label,
|
|
60
|
+
accessibilityState: {
|
|
61
|
+
disabled: isDisabled,
|
|
62
|
+
selected: isActive
|
|
63
|
+
},
|
|
56
64
|
children: /*#__PURE__*/_jsxs(View, {
|
|
57
65
|
style: toggleStyles.cardContent,
|
|
58
66
|
children: [/*#__PURE__*/_jsx(IconComponent, {
|
|
@@ -143,8 +151,10 @@ const JotaiAtomInfoView = /*#__PURE__*/memo(function JotaiAtomInfoView({
|
|
|
143
151
|
style: infoStyles.container,
|
|
144
152
|
contentContainerStyle: infoStyles.content,
|
|
145
153
|
showsVerticalScrollIndicator: false,
|
|
154
|
+
testID: `${JOTAI_TEST_ID}-detail-scroll`,
|
|
146
155
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
147
156
|
style: infoStyles.card,
|
|
157
|
+
testID: `${JOTAI_TEST_ID}-info-card`,
|
|
148
158
|
children: [/*#__PURE__*/_jsx(View, {
|
|
149
159
|
style: infoStyles.sectionHeader,
|
|
150
160
|
children: /*#__PURE__*/_jsxs(View, {
|
|
@@ -170,6 +180,7 @@ const JotaiAtomInfoView = /*#__PURE__*/memo(function JotaiAtomInfoView({
|
|
|
170
180
|
}],
|
|
171
181
|
numberOfLines: 1,
|
|
172
182
|
ellipsizeMode: "middle",
|
|
183
|
+
testID: `${JOTAI_TEST_ID}-info-atom`,
|
|
173
184
|
children: change.atomLabel
|
|
174
185
|
})]
|
|
175
186
|
}), /*#__PURE__*/_jsxs(View, {
|
|
@@ -179,6 +190,7 @@ const JotaiAtomInfoView = /*#__PURE__*/memo(function JotaiAtomInfoView({
|
|
|
179
190
|
children: "Time"
|
|
180
191
|
}), /*#__PURE__*/_jsx(Text, {
|
|
181
192
|
style: [infoStyles.infoValue, infoStyles.infoValueMono],
|
|
193
|
+
testID: `${JOTAI_TEST_ID}-info-time`,
|
|
182
194
|
children: formatTime(change.timestamp)
|
|
183
195
|
})]
|
|
184
196
|
}), /*#__PURE__*/_jsxs(View, {
|
|
@@ -191,6 +203,7 @@ const JotaiAtomInfoView = /*#__PURE__*/memo(function JotaiAtomInfoView({
|
|
|
191
203
|
style: [infoStyles.categoryText, {
|
|
192
204
|
color: categoryInfo.color
|
|
193
205
|
}],
|
|
206
|
+
testID: `${JOTAI_TEST_ID}-info-category`,
|
|
194
207
|
children: categoryInfo.label
|
|
195
208
|
})
|
|
196
209
|
}), change.hasValueChange ? /*#__PURE__*/_jsxs(View, {
|
|
@@ -200,12 +213,14 @@ const JotaiAtomInfoView = /*#__PURE__*/memo(function JotaiAtomInfoView({
|
|
|
200
213
|
color: buoyColors.success
|
|
201
214
|
}), /*#__PURE__*/_jsx(Text, {
|
|
202
215
|
style: infoStyles.changesText,
|
|
216
|
+
testID: `${JOTAI_TEST_ID}-info-changes`,
|
|
203
217
|
children: change.diffSummary || "changed"
|
|
204
218
|
})]
|
|
205
219
|
}) : /*#__PURE__*/_jsx(View, {
|
|
206
220
|
style: infoStyles.noChangesBadge,
|
|
207
221
|
children: /*#__PURE__*/_jsx(Text, {
|
|
208
222
|
style: infoStyles.noChangesText,
|
|
223
|
+
testID: `${JOTAI_TEST_ID}-info-no-changes`,
|
|
209
224
|
children: "no change"
|
|
210
225
|
})
|
|
211
226
|
})]
|
|
@@ -213,6 +228,7 @@ const JotaiAtomInfoView = /*#__PURE__*/memo(function JotaiAtomInfoView({
|
|
|
213
228
|
})]
|
|
214
229
|
}), change.changedKeys.length > 0 && /*#__PURE__*/_jsxs(View, {
|
|
215
230
|
style: infoStyles.card,
|
|
231
|
+
testID: `${JOTAI_TEST_ID}-keys-card`,
|
|
216
232
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
217
233
|
style: infoStyles.sectionHeader,
|
|
218
234
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
@@ -228,6 +244,7 @@ const JotaiAtomInfoView = /*#__PURE__*/memo(function JotaiAtomInfoView({
|
|
|
228
244
|
style: infoStyles.countBadge,
|
|
229
245
|
children: /*#__PURE__*/_jsx(Text, {
|
|
230
246
|
style: infoStyles.countText,
|
|
247
|
+
testID: `${JOTAI_TEST_ID}-keys-count`,
|
|
231
248
|
children: change.changedKeys.length
|
|
232
249
|
})
|
|
233
250
|
})]
|
|
@@ -239,6 +256,7 @@ const JotaiAtomInfoView = /*#__PURE__*/memo(function JotaiAtomInfoView({
|
|
|
239
256
|
style: infoStyles.keyBadge,
|
|
240
257
|
children: /*#__PURE__*/_jsx(Text, {
|
|
241
258
|
style: infoStyles.keyText,
|
|
259
|
+
testID: `${JOTAI_TEST_ID}-key-${slugForTestID(key)}`,
|
|
242
260
|
children: key
|
|
243
261
|
})
|
|
244
262
|
}, key))
|
|
@@ -249,6 +267,9 @@ const JotaiAtomInfoView = /*#__PURE__*/memo(function JotaiAtomInfoView({
|
|
|
249
267
|
children: /*#__PURE__*/_jsx(View, {
|
|
250
268
|
style: [infoStyles.actionButton, infoStyles.actionButtonCopy],
|
|
251
269
|
onTouchEnd: handleCopyValue,
|
|
270
|
+
testID: `${JOTAI_TEST_ID}-copy-value`,
|
|
271
|
+
accessibilityRole: "button",
|
|
272
|
+
accessibilityLabel: "Copy value",
|
|
252
273
|
children: /*#__PURE__*/_jsx(Text, {
|
|
253
274
|
style: [infoStyles.actionButtonText, {
|
|
254
275
|
color: night.color.accent
|
|
@@ -323,6 +344,7 @@ export function JotaiAtomDetailContent({
|
|
|
323
344
|
const parsed = parseValue(change.nextValue);
|
|
324
345
|
return /*#__PURE__*/_jsxs(View, {
|
|
325
346
|
style: styles.contentCard,
|
|
347
|
+
testID: `${JOTAI_TEST_ID}-value-card`,
|
|
326
348
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
327
349
|
style: styles.valueHeader,
|
|
328
350
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
@@ -344,6 +366,7 @@ export function JotaiAtomDetailContent({
|
|
|
344
366
|
style: styles.typeBadge,
|
|
345
367
|
children: /*#__PURE__*/_jsx(Text, {
|
|
346
368
|
style: styles.typeText,
|
|
369
|
+
testID: `${JOTAI_TEST_ID}-value-type`,
|
|
347
370
|
children: valueType.toUpperCase()
|
|
348
371
|
})
|
|
349
372
|
})]
|
|
@@ -395,7 +418,13 @@ export function JotaiAtomDetailContent({
|
|
|
395
418
|
const handleFooterNext = useCallback(() => {
|
|
396
419
|
onIndexChange(Math.max(0, selectedIndex - 1));
|
|
397
420
|
}, [selectedIndex, onIndexChange]);
|
|
398
|
-
|
|
421
|
+
|
|
422
|
+
// The diff compares THIS change's prevValue/nextValue — no siblings needed.
|
|
423
|
+
// Gating on `changes.length` greyed DIFF out for every jotai event opened
|
|
424
|
+
// from the Events timeline (a one-element array). Only a change with no
|
|
425
|
+
// captured previous value has nothing to compare.
|
|
426
|
+
const diffDisabled = change.prevValue === undefined;
|
|
427
|
+
const showInternalFooter = !disableInternalFooter && totalChanges > 1;
|
|
399
428
|
return /*#__PURE__*/_jsxs(View, {
|
|
400
429
|
style: styles.container,
|
|
401
430
|
children: [/*#__PURE__*/_jsx(DetailViewToggle, {
|
|
@@ -403,7 +432,7 @@ export function JotaiAtomDetailContent({
|
|
|
403
432
|
onViewChange: setActiveView,
|
|
404
433
|
diffDisabled: diffDisabled
|
|
405
434
|
}), /*#__PURE__*/_jsxs(View, {
|
|
406
|
-
style: styles.contentArea,
|
|
435
|
+
style: [styles.contentArea, showInternalFooter && styles.contentAreaFooterPad],
|
|
407
436
|
children: [activeView === "change" && /*#__PURE__*/_jsx(JotaiAtomInfoView, {
|
|
408
437
|
change: change
|
|
409
438
|
}), activeView === "value" && /*#__PURE__*/_jsx(View, {
|
|
@@ -411,19 +440,22 @@ export function JotaiAtomDetailContent({
|
|
|
411
440
|
children: renderValueView()
|
|
412
441
|
}), activeView === "diff" && /*#__PURE__*/_jsxs(View, {
|
|
413
442
|
style: styles.diffContainer,
|
|
443
|
+
testID: `${JOTAI_TEST_ID}-diff`,
|
|
414
444
|
children: [/*#__PURE__*/_jsx(DiffModeTabs, {
|
|
415
445
|
tabs: DIFF_MODE_TABS,
|
|
416
446
|
activeTab: diffMode,
|
|
417
|
-
onTabChange: setDiffMode
|
|
447
|
+
onTabChange: setDiffMode,
|
|
448
|
+
testIDPrefix: `${JOTAI_TEST_ID}-diff-tab`
|
|
418
449
|
}), /*#__PURE__*/_jsx(CompareBar, {
|
|
419
450
|
leftEvent: leftEvent,
|
|
420
451
|
rightEvent: rightEvent
|
|
421
452
|
}), /*#__PURE__*/_jsx(View, {
|
|
422
453
|
style: styles.diffContent,
|
|
454
|
+
testID: `${JOTAI_TEST_ID}-diff-content`,
|
|
423
455
|
children: renderDiffContent()
|
|
424
456
|
})]
|
|
425
457
|
})]
|
|
426
|
-
}),
|
|
458
|
+
}), showInternalFooter && /*#__PURE__*/_jsx(EventStepperFooter, {
|
|
427
459
|
currentIndex: chronologicalNumber - 1,
|
|
428
460
|
totalItems: totalChanges,
|
|
429
461
|
onPrevious: handleFooterPrevious,
|
|
@@ -459,7 +491,11 @@ const styles = StyleSheet.create({
|
|
|
459
491
|
backgroundColor: "transparent"
|
|
460
492
|
},
|
|
461
493
|
contentArea: {
|
|
462
|
-
flex: 1
|
|
494
|
+
flex: 1
|
|
495
|
+
},
|
|
496
|
+
// Clearance for the absolute EventStepperFooter — only when it renders,
|
|
497
|
+
// otherwise hosts like the Events tool get a dead band at the sheet bottom.
|
|
498
|
+
contentAreaFooterPad: {
|
|
463
499
|
paddingBottom: 96
|
|
464
500
|
},
|
|
465
501
|
stateContainer: {
|