@buoy-gg/react-query 7.0.28 → 7.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/lib/commonjs/react-query/components/DataEditorMode.js +7 -0
  2. package/lib/commonjs/react-query/components/FilterStatusBadge.js +4 -0
  3. package/lib/commonjs/react-query/components/MutationEditorMode.js +4 -0
  4. package/lib/commonjs/react-query/components/WifiToggle.js +3 -1
  5. package/lib/commonjs/react-query/components/modals/DataEditorModal.js +24 -0
  6. package/lib/commonjs/react-query/components/modals/MutationEditorModal.js +32 -0
  7. package/lib/commonjs/react-query/components/modals/QueryBrowserModal.js +1 -0
  8. package/lib/commonjs/react-query/components/modals/ReactQueryModalHeader.js +19 -2
  9. package/lib/commonjs/react-query/components/query-browser/ActionButton.js +3 -1
  10. package/lib/commonjs/react-query/components/query-browser/Explorer.js +20 -1
  11. package/lib/commonjs/react-query/components/query-browser/MutationButton.js +12 -1
  12. package/lib/commonjs/react-query/components/query-browser/MutationDetails.js +3 -1
  13. package/lib/commonjs/react-query/components/query-browser/MutationDetailsChips.js +3 -1
  14. package/lib/commonjs/react-query/components/query-browser/MutationStatusCount.js +4 -0
  15. package/lib/commonjs/react-query/components/query-browser/MutationsList.js +7 -2
  16. package/lib/commonjs/react-query/components/query-browser/QueryBrowser.js +9 -2
  17. package/lib/commonjs/react-query/components/query-browser/QueryDetails.js +5 -1
  18. package/lib/commonjs/react-query/components/query-browser/QueryDetailsChip.js +3 -1
  19. package/lib/commonjs/react-query/components/query-browser/QueryRow.js +12 -1
  20. package/lib/commonjs/react-query/components/query-browser/QueryStatus.js +12 -1
  21. package/lib/commonjs/react-query/components/query-browser/QueryStatusCount.js +5 -0
  22. package/lib/commonjs/react-query/utils/testIDs.js +61 -0
  23. package/lib/module/react-query/components/DataEditorMode.js +7 -0
  24. package/lib/module/react-query/components/FilterStatusBadge.js +4 -0
  25. package/lib/module/react-query/components/MutationEditorMode.js +4 -0
  26. package/lib/module/react-query/components/WifiToggle.js +3 -1
  27. package/lib/module/react-query/components/modals/DataEditorModal.js +25 -1
  28. package/lib/module/react-query/components/modals/MutationEditorModal.js +33 -1
  29. package/lib/module/react-query/components/modals/QueryBrowserModal.js +1 -0
  30. package/lib/module/react-query/components/modals/ReactQueryModalHeader.js +19 -2
  31. package/lib/module/react-query/components/query-browser/ActionButton.js +3 -1
  32. package/lib/module/react-query/components/query-browser/Explorer.js +20 -1
  33. package/lib/module/react-query/components/query-browser/MutationButton.js +12 -1
  34. package/lib/module/react-query/components/query-browser/MutationDetails.js +3 -1
  35. package/lib/module/react-query/components/query-browser/MutationDetailsChips.js +3 -1
  36. package/lib/module/react-query/components/query-browser/MutationStatusCount.js +4 -0
  37. package/lib/module/react-query/components/query-browser/MutationsList.js +7 -2
  38. package/lib/module/react-query/components/query-browser/QueryBrowser.js +9 -2
  39. package/lib/module/react-query/components/query-browser/QueryDetails.js +5 -1
  40. package/lib/module/react-query/components/query-browser/QueryDetailsChip.js +3 -1
  41. package/lib/module/react-query/components/query-browser/QueryRow.js +12 -1
  42. package/lib/module/react-query/components/query-browser/QueryStatus.js +12 -1
  43. package/lib/module/react-query/components/query-browser/QueryStatusCount.js +5 -0
  44. package/lib/module/react-query/utils/testIDs.js +54 -0
  45. package/lib/typescript/react-query/components/DataEditorMode.d.ts.map +1 -1
  46. package/lib/typescript/react-query/components/FilterStatusBadge.d.ts.map +1 -1
  47. package/lib/typescript/react-query/components/MutationEditorMode.d.ts.map +1 -1
  48. package/lib/typescript/react-query/components/WifiToggle.d.ts +2 -1
  49. package/lib/typescript/react-query/components/WifiToggle.d.ts.map +1 -1
  50. package/lib/typescript/react-query/components/modals/DataEditorModal.d.ts.map +1 -1
  51. package/lib/typescript/react-query/components/modals/MutationEditorModal.d.ts.map +1 -1
  52. package/lib/typescript/react-query/components/modals/QueryBrowserModal.d.ts.map +1 -1
  53. package/lib/typescript/react-query/components/modals/ReactQueryModalHeader.d.ts.map +1 -1
  54. package/lib/typescript/react-query/components/query-browser/ActionButton.d.ts +8 -1
  55. package/lib/typescript/react-query/components/query-browser/ActionButton.d.ts.map +1 -1
  56. package/lib/typescript/react-query/components/query-browser/Explorer.d.ts.map +1 -1
  57. package/lib/typescript/react-query/components/query-browser/MutationButton.d.ts.map +1 -1
  58. package/lib/typescript/react-query/components/query-browser/MutationDetails.d.ts.map +1 -1
  59. package/lib/typescript/react-query/components/query-browser/MutationDetailsChips.d.ts +2 -1
  60. package/lib/typescript/react-query/components/query-browser/MutationDetailsChips.d.ts.map +1 -1
  61. package/lib/typescript/react-query/components/query-browser/MutationStatusCount.d.ts.map +1 -1
  62. package/lib/typescript/react-query/components/query-browser/MutationsList.d.ts.map +1 -1
  63. package/lib/typescript/react-query/components/query-browser/QueryBrowser.d.ts.map +1 -1
  64. package/lib/typescript/react-query/components/query-browser/QueryDetails.d.ts.map +1 -1
  65. package/lib/typescript/react-query/components/query-browser/QueryDetailsChip.d.ts +2 -1
  66. package/lib/typescript/react-query/components/query-browser/QueryDetailsChip.d.ts.map +1 -1
  67. package/lib/typescript/react-query/components/query-browser/QueryRow.d.ts.map +1 -1
  68. package/lib/typescript/react-query/components/query-browser/QueryStatus.d.ts +2 -0
  69. package/lib/typescript/react-query/components/query-browser/QueryStatus.d.ts.map +1 -1
  70. package/lib/typescript/react-query/components/query-browser/QueryStatusCount.d.ts.map +1 -1
  71. package/lib/typescript/react-query/utils/testIDs.d.ts +35 -0
  72. package/lib/typescript/react-query/utils/testIDs.d.ts.map +1 -0
  73. package/package.json +3 -3
@@ -33,6 +33,7 @@ function DataEditorMode({
33
33
  const actionButtons = (0, _useActionButtons.useActionButtons)(selectedQuery, queryClient, queryVersion);
34
34
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
35
35
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
36
+ testID: "buoy-rq-detail-scroll",
36
37
  accessibilityLabel: "Data editor mode",
37
38
  accessibilityHint: "View data editor mode",
38
39
  "sentry-label": "ignore data editor mode",
@@ -60,6 +61,7 @@ function DataEditorMode({
60
61
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
61
62
  style: styles.queryExplorerContainer,
62
63
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
64
+ testID: "buoy-rq-query-explorer-header",
63
65
  style: styles.queryExplorerHeader,
64
66
  children: "Query Explorer"
65
67
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -82,6 +84,7 @@ function DataEditorMode({
82
84
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
83
85
  style: styles.actionsRow,
84
86
  children: actionButtons.map((action, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_ActionButton.default, {
87
+ testID: `buoy-rq-action-${action.variant}`,
85
88
  onClick: action.onPress,
86
89
  text: action.label,
87
90
  variant: action.variant,
@@ -114,6 +117,7 @@ function DataEditorActionsFooter({
114
117
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
115
118
  style: styles.actionsRow,
116
119
  children: actionButtons.map((action, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_ActionButton.default, {
120
+ testID: `buoy-rq-action-${action.variant}`,
117
121
  onClick: action.onPress,
118
122
  text: action.label,
119
123
  variant: action.variant,
@@ -131,6 +135,7 @@ function DataExplorer({
131
135
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
132
136
  style: styles.dataContainer,
133
137
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
138
+ testID: "buoy-rq-data-header",
134
139
  style: styles.dataHeader,
135
140
  children: "Data Editor"
136
141
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -183,9 +188,11 @@ function DataEmptyState({
183
188
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
184
189
  style: styles.emptyState,
185
190
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
191
+ testID: "buoy-rq-detail-empty-title",
186
192
  style: styles.emptyTitle,
187
193
  children: title
188
194
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
195
+ testID: "buoy-rq-detail-empty-description",
189
196
  style: styles.emptyDescription,
190
197
  children: description
191
198
  })]
@@ -23,12 +23,16 @@ function FilterStatusBadge({
23
23
  const content = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
24
24
  style: styles.container,
25
25
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
26
+ testID: "buoy-rq-filter-badge",
26
27
  style: styles.text,
27
28
  children: ["Showing ", filteredCount, " of ", totalCount]
28
29
  })
29
30
  });
30
31
  if (onPress) {
31
32
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
33
+ testID: "buoy-rq-filter-badge-button",
34
+ accessibilityRole: "button",
35
+ accessibilityLabel: `Showing ${filteredCount} of ${totalCount}, open filters`,
32
36
  onPress: onPress,
33
37
  activeOpacity: 0.7,
34
38
  children: content
@@ -25,6 +25,7 @@ function MutationEditorMode({
25
25
  }) {
26
26
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
27
27
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
28
+ testID: "buoy-rq-mutation-detail-scroll",
28
29
  accessibilityLabel: "Mutation editor mode",
29
30
  accessibilityHint: "View mutation editor mode",
30
31
  "sentry-label": "ignore mutation editor mode",
@@ -90,6 +91,7 @@ function MutationEditorActionsFooter({
90
91
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
91
92
  style: styles.actionsGrid,
92
93
  children: actionButtons.map((action, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_ActionButton.default, {
94
+ testID: `buoy-rq-action-${action.variant}`,
93
95
  onClick: action.onPress,
94
96
  text: action.label,
95
97
  variant: action.variant,
@@ -149,9 +151,11 @@ function DataEmptyState({
149
151
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
150
152
  style: styles.emptyState,
151
153
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
154
+ testID: "buoy-rq-mutation-detail-empty-title",
152
155
  style: styles.emptyTitle,
153
156
  children: title
154
157
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
158
+ testID: "buoy-rq-mutation-detail-empty-description",
155
159
  style: styles.emptyDescription,
156
160
  children: description
157
161
  })]
@@ -13,7 +13,8 @@ var _jsxRuntime = require("react/jsx-runtime");
13
13
  * offline scenarios directly from the modal header.
14
14
  */
15
15
  function WifiToggle({
16
- size = 16
16
+ size = 16,
17
+ testID
17
18
  }) {
18
19
  const {
19
20
  isOnline,
@@ -21,6 +22,7 @@ function WifiToggle({
21
22
  } = (0, _useWifiState.useWifiState)();
22
23
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
23
24
  "sentry-label": `ignore toggle WiFi ${isOnline ? "On" : "Off"}`,
25
+ testID: testID,
24
26
  accessibilityRole: "button",
25
27
  accessibilityLabel: `WiFi ${isOnline ? "On" : "Off"}`,
26
28
  accessibilityHint: `Tap to turn WiFi ${isOnline ? "off" : "on"} for React Query`,
@@ -28,6 +28,30 @@ function DataEditorModal({
28
28
  version: queryVersion
29
29
  } = (0, _useSelectedQuery.useGetQueryByQueryKeyWithVersion)(selectedQueryKey);
30
30
  const [modalMode, setModalMode] = (0, _react.useState)("bottomSheet");
31
+
32
+ /**
33
+ * Fall back to the list when the selected query leaves the cache — a
34
+ * removal, a cache clear, or garbage collection. Same failure as the
35
+ * mutation editor: the key that selected it is still set, so this modal
36
+ * bails out and the browser stays hidden behind it, leaving an "open" tool
37
+ * with nothing on screen. The ref covers the tick before the subscription
38
+ * has resolved the query, when undefined does not yet mean gone.
39
+ */
40
+ const currentQueryKey = selectedQueryKey ? JSON.stringify(selectedQueryKey) : undefined;
41
+ const seenQueryKey = (0, _react.useRef)(undefined);
42
+ (0, _react.useEffect)(() => {
43
+ if (selectedQuery) {
44
+ seenQueryKey.current = currentQueryKey;
45
+ return;
46
+ }
47
+ // Keyed to THIS query for the same reason as the mutation editor: the
48
+ // last-seen key outlives the detail view, so an un-keyed check would fire
49
+ // on the next selection before its lookup has resolved.
50
+ if (visible && seenQueryKey.current !== undefined && seenQueryKey.current === currentQueryKey) {
51
+ seenQueryKey.current = undefined;
52
+ onQuerySelect(undefined);
53
+ }
54
+ }, [visible, selectedQuery, currentQueryKey, onQuerySelect]);
31
55
  const handleModeChange = (0, _react.useCallback)(mode => {
32
56
  setModalMode(mode);
33
57
  }, []);
@@ -25,6 +25,38 @@ function MutationEditorModal({
25
25
  }) {
26
26
  const selectedMutation = (0, _useSelectedMutation.useGetMutationById)(selectedMutationId);
27
27
  const [modalMode, setModalMode] = (0, _react.useState)("bottomSheet");
28
+
29
+ /**
30
+ * Fall back to the list when the selected mutation leaves the cache.
31
+ *
32
+ * Without this the whole tool disappears. `Remove` (and a mutation-cache
33
+ * clear, and ordinary garbage collection) drops the entry while the id that
34
+ * selected it is still set, so this modal bails out on the missing mutation
35
+ * and the browser stays hidden behind an `inDetail` that is still true —
36
+ * leaving an "open" tool with nothing on screen and no way back except
37
+ * closing and reopening it from the dial.
38
+ *
39
+ * The ref is what makes it safe: `useGetMutationById` resolves on a
40
+ * `setTimeout(0)`, so the mutation is legitimately undefined for the first
41
+ * tick after a selection. Only a mutation that was HERE and then went away
42
+ * counts as gone.
43
+ */
44
+ const seenMutationId = (0, _react.useRef)(undefined);
45
+ (0, _react.useEffect)(() => {
46
+ if (selectedMutation) {
47
+ seenMutationId.current = selectedMutation.mutationId;
48
+ return;
49
+ }
50
+ // Only THIS mutation going missing counts. Keying the check to the id
51
+ // matters: leaving a detail view leaves the last-seen id behind, and a
52
+ // plain "we had one and now we don't" flag would fire on the very next
53
+ // selection — during the tick before its own lookup resolves — and bounce
54
+ // the user straight back out of the mutation they just opened.
55
+ if (visible && seenMutationId.current !== undefined && seenMutationId.current === selectedMutationId) {
56
+ seenMutationId.current = undefined;
57
+ onMutationSelect(undefined);
58
+ }
59
+ }, [visible, selectedMutation, selectedMutationId, onMutationSelect]);
28
60
  const handleModeChange = (0, _react.useCallback)(mode => {
29
61
  setModalMode(mode);
30
62
  }, []);
@@ -172,6 +172,7 @@ function QueryBrowserModal({
172
172
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TabSelector, {
173
173
  tabs: tabs,
174
174
  activeTab: "filters",
175
+ testIDPrefix: "buoy-rq-filterview-tab",
175
176
  onTabChange: () => {}
176
177
  })
177
178
  })]
@@ -73,7 +73,8 @@ function ReactQueryModalHeader({
73
73
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
74
74
  onBack: onBack
75
75
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
76
- title: getItemText(selectedQuery ?? selectedMutation)
76
+ title: getItemText(selectedQuery ?? selectedMutation),
77
+ testID: "buoy-rq-detail-title"
77
78
  })]
78
79
  });
79
80
  }
@@ -85,11 +86,13 @@ function ReactQueryModalHeader({
85
86
  noMargin: true,
86
87
  children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
87
88
  style: styles.headerSearchContainer,
89
+ testID: "buoy-rq-search-field",
88
90
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
89
91
  size: 14,
90
92
  color: _sharedUi.night.color.textSecondary
91
93
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
92
94
  ref: searchInputRef,
95
+ testID: "buoy-rq-search",
93
96
  style: styles.headerSearchInput,
94
97
  placeholder: "Search query keys...",
95
98
  placeholderTextColor: _sharedUi.night.color.textTertiary,
@@ -108,6 +111,9 @@ function ReactQueryModalHeader({
108
111
  setIsSearchActive(false);
109
112
  },
110
113
  "sentry-label": "ignore clear search header",
114
+ testID: "buoy-rq-search-clear",
115
+ accessibilityRole: "button",
116
+ accessibilityLabel: "Clear search",
111
117
  style: styles.headerSearchClear,
112
118
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
113
119
  size: 14,
@@ -121,19 +127,25 @@ function ReactQueryModalHeader({
121
127
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TabSelector, {
122
128
  tabs: tabs,
123
129
  activeTab: activeTab,
130
+ testIDPrefix: "buoy-rq-tab",
124
131
  onTabChange: tab => onTabChange(tab)
125
132
  })
126
133
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_WifiToggle.WifiToggle, {
127
- size: 16
134
+ size: 16,
135
+ testID: "buoy-rq-wifi"
128
136
  })]
129
137
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TabSelector, {
130
138
  tabs: tabs,
131
139
  activeTab: activeTab,
140
+ testIDPrefix: "buoy-rq-tab",
132
141
  onTabChange: tab => onTabChange(tab)
133
142
  })
134
143
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader.Actions, {
135
144
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
136
145
  "sentry-label": "ignore open search",
146
+ testID: "buoy-rq-search-open",
147
+ accessibilityRole: "button",
148
+ accessibilityLabel: "Search queries and mutations",
137
149
  onPress: handleSearchToggle,
138
150
  style: styles.headerActionButton,
139
151
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
@@ -142,6 +154,9 @@ function ReactQueryModalHeader({
142
154
  })
143
155
  }), onFilterPress && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
144
156
  "sentry-label": "ignore open filter",
157
+ testID: "buoy-rq-filters",
158
+ accessibilityRole: "button",
159
+ accessibilityLabel: hasActiveFilters ? "Filters, active" : "Filters",
145
160
  onPress: onFilterPress,
146
161
  style: [styles.headerActionButton, hasActiveFilters && styles.activeFilterButton],
147
162
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Filter, {
@@ -150,6 +165,8 @@ function ReactQueryModalHeader({
150
165
  })
151
166
  }), onClearCache && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
152
167
  "sentry-label": "ignore clear cache",
168
+ testID: "buoy-rq-clear-cache",
169
+ accessibilityRole: "button",
153
170
  onPress: onClearCache,
154
171
  style: [styles.headerActionButton, styles.clearCacheButton],
155
172
  accessibilityLabel: `Clear ${activeTab} cache`,
@@ -45,11 +45,13 @@ function ActionButton({
45
45
  onClick,
46
46
  text,
47
47
  variant,
48
- disabled
48
+ disabled,
49
+ testID
49
50
  }) {
50
51
  const config = buttonConfigs[variant];
51
52
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
52
53
  "sentry-label": "ignore devtools action button",
54
+ testID: testID,
53
55
  disabled: disabled,
54
56
  onPress: onClick,
55
57
  style: [styles.button, {
@@ -9,6 +9,7 @@ var _reactQuery = require("@tanstack/react-query");
9
9
  var _updateNestedDataByPath = require("../../utils/updateNestedDataByPath");
10
10
  var _sharedUi = require("@buoy-gg/shared-ui");
11
11
  var _deleteItem = _interopRequireDefault(require("../../utils/actions/deleteItem"));
12
+ var _testIDs = require("../../utils/testIDs");
12
13
  var _reactNative = require("react-native");
13
14
  var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
14
15
  var _jsxRuntime = require("react/jsx-runtime");
@@ -86,6 +87,8 @@ const DeleteItemButton = /*#__PURE__*/(0, _react.memo)(({
86
87
  if (!activeQuery) return null;
87
88
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
88
89
  "sentry-label": "ignore devtools explorer delete button",
90
+ testID: `${(0, _testIDs.explorerTestID)(dataPath)}-delete`,
91
+ accessibilityRole: "button",
89
92
  onPress: handleDelete,
90
93
  style: [styles.deleteButton, isFocused && styles.deleteButtonFocused],
91
94
  accessibilityLabel: "Delete item",
@@ -115,7 +118,10 @@ const ClearArrayButton = /*#__PURE__*/(0, _react.memo)(({
115
118
  if (!activeQuery) return null;
116
119
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
117
120
  "sentry-label": "ignore devtools explorer clear button",
121
+ testID: `${(0, _testIDs.explorerTestID)(dataPath)}-clear`,
118
122
  style: [styles.clearButton, isFocused && styles.clearButtonFocused],
123
+ accessibilityRole: "button",
124
+ accessibilityLabel: "Remove all items",
119
125
  "aria-label": "Remove all items",
120
126
  onPress: handleClear,
121
127
  hitSlop: HIT_SLOP_OPTIMIZED,
@@ -160,6 +166,9 @@ function ToggleValueButton({
160
166
  const textStyle = value ? styles.toggleTextTrue : styles.toggleTextFalse;
161
167
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
162
168
  "sentry-label": "ignore devtools explorer toggle button",
169
+ testID: `${(0, _testIDs.explorerTestID)(dataPath)}-toggle`,
170
+ accessibilityRole: "button",
171
+ accessibilityLabel: `${dataPath[dataPath.length - 1] ?? "value"}, ${value ? "true" : "false"}`,
163
172
  style: styles.modernToggleButton,
164
173
  onPress: handleClick,
165
174
  hitSlop: HIT_SLOP_OPTIMIZED,
@@ -172,6 +181,8 @@ function ToggleValueButton({
172
181
  })
173
182
  }), itemsDeletable && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
174
183
  "sentry-label": "ignore devtools explorer delete button in toggle",
184
+ testID: `${(0, _testIDs.explorerTestID)(dataPath)}-delete`,
185
+ accessibilityRole: "button",
175
186
  onPress: handleDelete,
176
187
  style: styles.deleteButtonInToggle,
177
188
  accessibilityLabel: "Delete item",
@@ -314,6 +325,12 @@ function Explorer({
314
325
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
315
326
  "sentry-label": "ignore devtools explorer expander button",
316
327
  style: styles.expanderButton,
328
+ testID: (0, _testIDs.explorerTestID)(currentDataPath),
329
+ accessibilityRole: "button",
330
+ accessibilityLabel: `${label}, ${subEntries.length} ${subEntries.length === 1 ? "item" : "items"}`,
331
+ accessibilityState: {
332
+ expanded: isExpanded
333
+ },
317
334
  onPress: toggleExpanded,
318
335
  hitSlop: HIT_SLOP_OPTIMIZED,
319
336
  activeOpacity: 0.6,
@@ -395,7 +412,8 @@ function Explorer({
395
412
  style: styles.nebulaInputWrapper,
396
413
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.CyberpunkInput, {
397
414
  label: label,
398
- accessibilityLabel: "Data input field for editing values",
415
+ testID: `${(0, _testIDs.explorerTestID)(currentDataPath)}-input`,
416
+ accessibilityLabel: `${label}, editable value`,
399
417
  style: [valueType === "number" ? styles.textNumber : styles.textString],
400
418
  keyboardType: valueType === "number" ? "numeric" : "default",
401
419
  value: localInputValue,
@@ -440,6 +458,7 @@ function Explorer({
440
458
  style: styles.text344054,
441
459
  children: label
442
460
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
461
+ testID: `${(0, _testIDs.explorerTestID)(currentDataPath)}-value`,
443
462
  style: styles.displayValueText,
444
463
  children: (0, _sharedUi.displayValue)(value)
445
464
  })]
@@ -7,6 +7,7 @@ exports.default = MutationButton;
7
7
  var _reactNative = require("react-native");
8
8
  var _svgs = require("./svgs");
9
9
  var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _testIDs = require("../../utils/testIDs");
10
11
  var _jsxRuntime = require("react/jsx-runtime");
11
12
  const getMutationText = mutation => {
12
13
  if (!mutation.options.mutationKey) return "Anonymous Mutation";
@@ -73,8 +74,16 @@ function MutationButton({
73
74
  }
74
75
  };
75
76
  const statusInfo = getStatusInfo();
77
+ const mutationText = getMutationText(mutation);
78
+ const testID = (0, _testIDs.mutationRowTestID)(mutation.options.mutationKey, mutation.mutationId);
76
79
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
77
80
  "sentry-label": "ignore devtools mutation button",
81
+ testID: testID,
82
+ accessibilityRole: "button"
83
+ // Same Fabric merge as the query rows: without a spelled-out label the
84
+ // row announces as an unnamed button.
85
+ ,
86
+ accessibilityLabel: `${mutationText}, ${statusInfo.status}`,
78
87
  onPress: () => setSelectedMutation(mutation === selected ? undefined : mutation),
79
88
  style: [styles.button, selected?.mutationId === mutation.mutationId && styles.selected],
80
89
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
@@ -88,6 +97,7 @@ function MutationButton({
88
97
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
89
98
  style: styles.statusInfo,
90
99
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
100
+ testID: `${testID}-status`,
91
101
  style: [styles.statusLabel, {
92
102
  color: statusInfo.color
93
103
  }],
@@ -100,8 +110,9 @@ function MutationButton({
100
110
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
101
111
  style: styles.mutationSection,
102
112
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
113
+ testID: `${testID}-key`,
103
114
  style: styles.mutationKey,
104
- children: getMutationText(mutation)
115
+ children: mutationText
105
116
  })
106
117
  })]
107
118
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -31,11 +31,13 @@ function MutationDetails({
31
31
  horizontal: true,
32
32
  style: styles.flex1,
33
33
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
34
+ testID: "buoy-rq-mutation-detail-key",
34
35
  style: styles.flexWrap,
35
36
  children: `${selectedMutation.options.mutationKey ? (0, _sharedUi.displayValue)(selectedMutation.options.mutationKey, true) : "No mutationKey found"}`
36
37
  })
37
38
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_MutationDetailsChips.default, {
38
- status: selectedMutation.state.status
39
+ status: selectedMutation.state.status,
40
+ testID: "buoy-rq-mutation-detail-status"
39
41
  })]
40
42
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
41
43
  style: styles.row,
@@ -29,7 +29,8 @@ const textColors = {
29
29
  * Small status chip used in mutation detail views that adapts styling to the mutation status.
30
30
  */
31
31
  function QueryDetailsChip({
32
- status
32
+ status,
33
+ testID
33
34
  }) {
34
35
  const statusToColor = status;
35
36
  const backgroundColor = backgroundColors[statusToColor];
@@ -41,6 +42,7 @@ function QueryDetailsChip({
41
42
  borderColor
42
43
  }],
43
44
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
45
+ testID: testID,
44
46
  style: [styles.text, {
45
47
  color: textColor
46
48
  }],
@@ -63,6 +63,7 @@ const MutationStatusCount = ({
63
63
  isActive: activeFilter === "pending",
64
64
  onPress: event => handleFilterClick("pending", event),
65
65
  onTouchStart: handleTouchStart,
66
+ testID: "buoy-rq-mstatus-pending",
66
67
  showLabel: true,
67
68
  size: size
68
69
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_QueryStatus.default, {
@@ -72,6 +73,7 @@ const MutationStatusCount = ({
72
73
  isActive: activeFilter === "success",
73
74
  onPress: event => handleFilterClick("success", event),
74
75
  onTouchStart: handleTouchStart,
76
+ testID: "buoy-rq-mstatus-success",
75
77
  showLabel: true,
76
78
  size: size
77
79
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_QueryStatus.default, {
@@ -81,6 +83,7 @@ const MutationStatusCount = ({
81
83
  isActive: activeFilter === "error",
82
84
  onPress: event => handleFilterClick("error", event),
83
85
  onTouchStart: handleTouchStart,
86
+ testID: "buoy-rq-mstatus-error",
84
87
  showLabel: true,
85
88
  size: size
86
89
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_QueryStatus.default, {
@@ -90,6 +93,7 @@ const MutationStatusCount = ({
90
93
  isActive: activeFilter === "paused",
91
94
  onPress: event => handleFilterClick("paused", event),
92
95
  onTouchStart: handleTouchStart,
96
+ testID: "buoy-rq-mstatus-paused",
93
97
  showLabel: true,
94
98
  size: size
95
99
  })]
@@ -119,12 +119,16 @@ function MutationsList({
119
119
  style: styles.listWrapper,
120
120
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
121
121
  "sentry-label": "ignore devtools mutations list",
122
+ testID: "buoy-rq-mutation-list",
122
123
  data: filteredMutations,
123
124
  renderItem: renderMutation,
124
125
  keyExtractor: (item, index) => `${item.mutationId}-${item.state.submittedAt}-${item.state.status}-${index}`,
125
126
  inverted: true,
126
- showsVerticalScrollIndicator: true,
127
- removeClippedSubviews: true,
127
+ showsVerticalScrollIndicator: true
128
+ // See QueryBrowser: e2e bundles keep every cell mounted so Detox's
129
+ // hittability check doesn't reject an on-screen row as clipped.
130
+ ,
131
+ removeClippedSubviews: !(typeof process !== "undefined" && process.env?.EXPO_PUBLIC_E2E),
128
132
  contentContainerStyle: contentContainerStyle || styles.listContent,
129
133
  initialNumToRender: 10,
130
134
  maxToRenderPerBatch: 10,
@@ -135,6 +139,7 @@ function MutationsList({
135
139
  style: styles.emptyContainer,
136
140
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
137
141
  style: styles.emptyText,
142
+ testID: "buoy-rq-mutations-empty",
138
143
  children: activeFilter ? `No ${activeFilter} mutations found` : "No mutations found"
139
144
  })
140
145
  }), selectedMutation && !hideInfoPanel && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
@@ -102,6 +102,7 @@ function QueryBrowser({
102
102
  style: styles.emptyContainer,
103
103
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
104
104
  style: styles.emptyText,
105
+ testID: "buoy-rq-empty",
105
106
  children: emptyStateMessage || (activeFilter ? `No ${activeFilter} queries found` : "No queries found")
106
107
  })
107
108
  });
@@ -116,6 +117,7 @@ function QueryBrowser({
116
117
  // mutable with stable identity, so `stateSig` carries the changing
117
118
  // fields for the row's memo comparison.
118
119
  (0, _jsxRuntime.jsx)(_reactNative.FlatList, {
120
+ testID: "buoy-rq-query-list",
119
121
  style: styles.listWrapper,
120
122
  contentContainerStyle: contentContainerStyle || styles.listContent,
121
123
  data: filteredQueries,
@@ -128,8 +130,13 @@ function QueryBrowser({
128
130
  isSelected: selectedQuery?.queryHash === query.queryHash,
129
131
  onSelect: handleQuerySelect
130
132
  }),
131
- showsVerticalScrollIndicator: true,
132
- removeClippedSubviews: true,
133
+ showsVerticalScrollIndicator: true
134
+ // Clipped cells fail Detox's hittability check even when they are on
135
+ // screen, so e2e bundles (Metro with EXPO_PUBLIC_E2E=1) turn clipping
136
+ // off. Inlined at bundle time; a non-inlining bundler keeps prod
137
+ // behavior.
138
+ ,
139
+ removeClippedSubviews: !(typeof process !== "undefined" && process.env?.EXPO_PUBLIC_E2E),
133
140
  initialNumToRender: 12,
134
141
  maxToRenderPerBatch: 8,
135
142
  windowSize: 5,
@@ -37,11 +37,13 @@ function QueryDetails({
37
37
  horizontal: true,
38
38
  style: styles.flexOne,
39
39
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
40
+ testID: "buoy-rq-detail-key",
40
41
  style: styles.queryKeyText,
41
42
  children: (0, _sharedUi.displayValue)(query.queryKey, true)
42
43
  })
43
44
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_QueryDetailsChip.default, {
44
- query: query
45
+ query: query,
46
+ testID: "buoy-rq-detail-status"
45
47
  })]
46
48
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
47
49
  style: styles.row,
@@ -49,6 +51,7 @@ function QueryDetails({
49
51
  style: styles.labelText,
50
52
  children: "Observers:"
51
53
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
54
+ testID: "buoy-rq-detail-observers",
52
55
  style: styles.valueText,
53
56
  children: `${query.getObserversCount()}`
54
57
  })]
@@ -58,6 +61,7 @@ function QueryDetails({
58
61
  style: styles.labelText,
59
62
  children: "Last Updated:"
60
63
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
64
+ testID: "buoy-rq-detail-updated",
61
65
  style: styles.valueText,
62
66
  children: `${lastUpdated}`
63
67
  })]
@@ -39,7 +39,8 @@ const textColors = {
39
39
  * Chip component used to display small query metadata values (status, observers, etc.).
40
40
  */
41
41
  function QueryDetailsChip({
42
- query
42
+ query,
43
+ testID
43
44
  }) {
44
45
  const status = (0, _getQueryStatusLabel.getQueryStatusLabel)(query);
45
46
  const backgroundColor = backgroundColors[status];
@@ -51,6 +52,7 @@ function QueryDetailsChip({
51
52
  borderColor
52
53
  }],
53
54
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
55
+ testID: testID,
54
56
  style: [styles.text, {
55
57
  color: textColor
56
58
  }],
@@ -7,6 +7,7 @@ exports.default = void 0;
7
7
  var _react = require("react");
8
8
  var _getQueryStatusLabel = require("../../utils/getQueryStatusLabel");
9
9
  var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _testIDs = require("../../utils/testIDs");
10
11
  var _jsxRuntime = require("react/jsx-runtime");
11
12
  const getQueryText = query => {
12
13
  if (!query?.queryKey) return "Unknown Query";
@@ -70,10 +71,20 @@ const QueryRow = /*#__PURE__*/(0, _react.memo)(function QueryRow({
70
71
  })();
71
72
  const relativeTime = (0, _sharedUi.useRelativeTime)(bestTimestamp);
72
73
  const lastUpdated = isDisabled ? "Disabled" : relativeTime || (query.state.dataUpdatedAt === 0 ? "Not fetched" : `N/A (${query.state.dataUpdatedAt || 0})`);
74
+ const testID = (0, _testIDs.queryRowTestID)(query.queryKey);
75
+ const observerText = `${observerCount} observer${observerCount !== 1 ? "s" : ""}`;
73
76
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CompactRow, {
77
+ testID: testID,
78
+ statusTestID: `${testID}-status`,
79
+ statusSublabelTestID: `${testID}-observers`
80
+ // Spelled out for assistive tech: on Fabric the dot + two words merge
81
+ // into the row's own accessibility container, so without this the row
82
+ // announces as a control VoiceOver cannot name.
83
+ ,
84
+ accessibilityLabel: `${queryHash}, ${status}, ${observerText}`,
74
85
  statusDotColor: getStatusHexColor(status),
75
86
  statusLabel: status.charAt(0).toUpperCase() + status.slice(1),
76
- statusSublabel: `${observerCount} observer${observerCount !== 1 ? "s" : ""}`,
87
+ statusSublabel: observerText,
77
88
  primaryText: queryHash,
78
89
  bottomRightText: lastUpdated,
79
90
  badgeText: observerCount,
@@ -18,7 +18,8 @@ const QueryStatus = ({
18
18
  isActive = false,
19
19
  onPress,
20
20
  onTouchStart,
21
- size = "default"
21
+ size = "default",
22
+ testID
22
23
  }) => {
23
24
  const isLarge = size === "large";
24
25
  // Buoy theme color mapping for status indicators
@@ -66,6 +67,15 @@ const QueryStatus = ({
66
67
  const statusColors = getStatusColors(color);
67
68
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
68
69
  "sentry-label": "ignore devtools query status",
70
+ testID: testID,
71
+ accessibilityRole: "button"
72
+ // The dot, the word and the number merge into one container on Fabric,
73
+ // so the chip is announced without any of them unless it is spelled out.
74
+ ,
75
+ accessibilityLabel: `${label}, ${count}${isActive ? ", selected" : ""}`,
76
+ accessibilityState: {
77
+ selected: isActive
78
+ },
69
79
  style: [styles.queryStatusTag, isLarge && styles.queryStatusTagLarge,
70
80
  // The night chip contract (see NightChip): resting fill never changes;
71
81
  // selection is signaled by the tinted border and label.
@@ -89,6 +99,7 @@ const QueryStatus = ({
89
99
  ellipsizeMode: "tail",
90
100
  children: label
91
101
  }), count > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
102
+ testID: testID ? `${testID}-count` : undefined,
92
103
  style: [styles.count, isLarge && styles.countLarge, {
93
104
  color: statusColors.dot
94
105
  }],