@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.
- package/lib/commonjs/react-query/components/DataEditorMode.js +7 -0
- package/lib/commonjs/react-query/components/FilterStatusBadge.js +4 -0
- package/lib/commonjs/react-query/components/MutationEditorMode.js +4 -0
- package/lib/commonjs/react-query/components/WifiToggle.js +3 -1
- package/lib/commonjs/react-query/components/modals/DataEditorModal.js +24 -0
- package/lib/commonjs/react-query/components/modals/MutationEditorModal.js +32 -0
- package/lib/commonjs/react-query/components/modals/QueryBrowserModal.js +1 -0
- package/lib/commonjs/react-query/components/modals/ReactQueryModalHeader.js +19 -2
- package/lib/commonjs/react-query/components/query-browser/ActionButton.js +3 -1
- package/lib/commonjs/react-query/components/query-browser/Explorer.js +20 -1
- package/lib/commonjs/react-query/components/query-browser/MutationButton.js +12 -1
- package/lib/commonjs/react-query/components/query-browser/MutationDetails.js +3 -1
- package/lib/commonjs/react-query/components/query-browser/MutationDetailsChips.js +3 -1
- package/lib/commonjs/react-query/components/query-browser/MutationStatusCount.js +4 -0
- package/lib/commonjs/react-query/components/query-browser/MutationsList.js +7 -2
- package/lib/commonjs/react-query/components/query-browser/QueryBrowser.js +9 -2
- package/lib/commonjs/react-query/components/query-browser/QueryDetails.js +5 -1
- package/lib/commonjs/react-query/components/query-browser/QueryDetailsChip.js +3 -1
- package/lib/commonjs/react-query/components/query-browser/QueryRow.js +12 -1
- package/lib/commonjs/react-query/components/query-browser/QueryStatus.js +12 -1
- package/lib/commonjs/react-query/components/query-browser/QueryStatusCount.js +5 -0
- package/lib/commonjs/react-query/utils/testIDs.js +61 -0
- package/lib/module/react-query/components/DataEditorMode.js +7 -0
- package/lib/module/react-query/components/FilterStatusBadge.js +4 -0
- package/lib/module/react-query/components/MutationEditorMode.js +4 -0
- package/lib/module/react-query/components/WifiToggle.js +3 -1
- package/lib/module/react-query/components/modals/DataEditorModal.js +25 -1
- package/lib/module/react-query/components/modals/MutationEditorModal.js +33 -1
- package/lib/module/react-query/components/modals/QueryBrowserModal.js +1 -0
- package/lib/module/react-query/components/modals/ReactQueryModalHeader.js +19 -2
- package/lib/module/react-query/components/query-browser/ActionButton.js +3 -1
- package/lib/module/react-query/components/query-browser/Explorer.js +20 -1
- package/lib/module/react-query/components/query-browser/MutationButton.js +12 -1
- package/lib/module/react-query/components/query-browser/MutationDetails.js +3 -1
- package/lib/module/react-query/components/query-browser/MutationDetailsChips.js +3 -1
- package/lib/module/react-query/components/query-browser/MutationStatusCount.js +4 -0
- package/lib/module/react-query/components/query-browser/MutationsList.js +7 -2
- package/lib/module/react-query/components/query-browser/QueryBrowser.js +9 -2
- package/lib/module/react-query/components/query-browser/QueryDetails.js +5 -1
- package/lib/module/react-query/components/query-browser/QueryDetailsChip.js +3 -1
- package/lib/module/react-query/components/query-browser/QueryRow.js +12 -1
- package/lib/module/react-query/components/query-browser/QueryStatus.js +12 -1
- package/lib/module/react-query/components/query-browser/QueryStatusCount.js +5 -0
- package/lib/module/react-query/utils/testIDs.js +54 -0
- package/lib/typescript/react-query/components/DataEditorMode.d.ts.map +1 -1
- package/lib/typescript/react-query/components/FilterStatusBadge.d.ts.map +1 -1
- package/lib/typescript/react-query/components/MutationEditorMode.d.ts.map +1 -1
- package/lib/typescript/react-query/components/WifiToggle.d.ts +2 -1
- package/lib/typescript/react-query/components/WifiToggle.d.ts.map +1 -1
- package/lib/typescript/react-query/components/modals/DataEditorModal.d.ts.map +1 -1
- package/lib/typescript/react-query/components/modals/MutationEditorModal.d.ts.map +1 -1
- package/lib/typescript/react-query/components/modals/QueryBrowserModal.d.ts.map +1 -1
- package/lib/typescript/react-query/components/modals/ReactQueryModalHeader.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/ActionButton.d.ts +8 -1
- package/lib/typescript/react-query/components/query-browser/ActionButton.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/Explorer.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/MutationButton.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/MutationDetails.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/MutationDetailsChips.d.ts +2 -1
- package/lib/typescript/react-query/components/query-browser/MutationDetailsChips.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/MutationStatusCount.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/MutationsList.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/QueryBrowser.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/QueryDetails.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/QueryDetailsChip.d.ts +2 -1
- package/lib/typescript/react-query/components/query-browser/QueryDetailsChip.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/QueryRow.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/QueryStatus.d.ts +2 -0
- package/lib/typescript/react-query/components/query-browser/QueryStatus.d.ts.map +1 -1
- package/lib/typescript/react-query/components/query-browser/QueryStatusCount.d.ts.map +1 -1
- package/lib/typescript/react-query/utils/testIDs.d.ts +35 -0
- package/lib/typescript/react-query/utils/testIDs.d.ts.map +1 -0
- package/package.json +3 -3
|
@@ -69,6 +69,7 @@ const QueryStatusCount = ({
|
|
|
69
69
|
isActive: activeFilter === "fresh",
|
|
70
70
|
onPress: event => handleFilterClick("fresh", event),
|
|
71
71
|
onTouchStart: handleTouchStart,
|
|
72
|
+
testID: "buoy-rq-status-fresh",
|
|
72
73
|
showLabel: true // Always show labels now
|
|
73
74
|
,
|
|
74
75
|
size: size
|
|
@@ -79,6 +80,7 @@ const QueryStatusCount = ({
|
|
|
79
80
|
isActive: activeFilter === "fetching",
|
|
80
81
|
onPress: event => handleFilterClick("fetching", event),
|
|
81
82
|
onTouchStart: handleTouchStart,
|
|
83
|
+
testID: "buoy-rq-status-fetching",
|
|
82
84
|
showLabel: true // Always show labels now
|
|
83
85
|
,
|
|
84
86
|
size: size
|
|
@@ -89,6 +91,7 @@ const QueryStatusCount = ({
|
|
|
89
91
|
isActive: activeFilter === "paused",
|
|
90
92
|
onPress: event => handleFilterClick("paused", event),
|
|
91
93
|
onTouchStart: handleTouchStart,
|
|
94
|
+
testID: "buoy-rq-status-paused",
|
|
92
95
|
showLabel: true // Always show labels now
|
|
93
96
|
,
|
|
94
97
|
size: size
|
|
@@ -99,6 +102,7 @@ const QueryStatusCount = ({
|
|
|
99
102
|
isActive: activeFilter === "stale",
|
|
100
103
|
onPress: event => handleFilterClick("stale", event),
|
|
101
104
|
onTouchStart: handleTouchStart,
|
|
105
|
+
testID: "buoy-rq-status-stale",
|
|
102
106
|
showLabel: true // Always show labels now
|
|
103
107
|
,
|
|
104
108
|
size: size
|
|
@@ -109,6 +113,7 @@ const QueryStatusCount = ({
|
|
|
109
113
|
isActive: activeFilter === "inactive",
|
|
110
114
|
onPress: event => handleFilterClick("inactive", event),
|
|
111
115
|
onTouchStart: handleTouchStart,
|
|
116
|
+
testID: "buoy-rq-status-inactive",
|
|
112
117
|
showLabel: true // Always show labels now
|
|
113
118
|
,
|
|
114
119
|
size: size
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.explorerTestID = explorerTestID;
|
|
7
|
+
exports.keySlug = keySlug;
|
|
8
|
+
exports.mutationRowTestID = mutationRowTestID;
|
|
9
|
+
exports.queryRowTestID = queryRowTestID;
|
|
10
|
+
/**
|
|
11
|
+
* Stable e2e handles for the React Query tool.
|
|
12
|
+
*
|
|
13
|
+
* Rows are addressed by their KEY, never by an index or a per-session id: a
|
|
14
|
+
* query's position moves every time the list re-sorts by `dataUpdatedAt`, and
|
|
15
|
+
* a mutation's `mutationId` is a counter that restarts with the process. The
|
|
16
|
+
* key is the one thing that means the same query on the next launch, which is
|
|
17
|
+
* also the only thing a test can name up front.
|
|
18
|
+
*
|
|
19
|
+
* `queryHash` would be stable too, but it is raw JSON (`["qbench","fresh"]`) —
|
|
20
|
+
* quotes and brackets that neither Detox's `by.id` nor a Maestro selector
|
|
21
|
+
* handles cleanly. So the parts are slugged and joined the way the rest of
|
|
22
|
+
* Buoy's handles read: `buoy-rq-query-row-qbench-fresh`.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** One key part → a testID-safe slug. */
|
|
26
|
+
function slugPart(part) {
|
|
27
|
+
return String(part).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A query/mutation key (array or scalar) → its slug, e.g. `qbench-fresh`. */
|
|
31
|
+
function keySlug(key) {
|
|
32
|
+
const parts = Array.isArray(key) ? key : [key];
|
|
33
|
+
return parts.filter(part => part != null).map(slugPart).filter(Boolean).join("-") || "unkeyed";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** `buoy-rq-query-row-<slug>` for a query key. */
|
|
37
|
+
function queryRowTestID(queryKey) {
|
|
38
|
+
return `buoy-rq-query-row-${keySlug(queryKey)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `buoy-rq-mutation-row-<slug>` for a mutation.
|
|
43
|
+
*
|
|
44
|
+
* Anonymous mutations (no `mutationKey`) fall back to the id — unstable across
|
|
45
|
+
* launches, but so is the mutation itself, and an unkeyed mutation has nothing
|
|
46
|
+
* else to be named by.
|
|
47
|
+
*/
|
|
48
|
+
function mutationRowTestID(mutationKey, mutationId) {
|
|
49
|
+
return mutationKey == null ? `buoy-rq-mutation-row-id-${mutationId}` : `buoy-rq-mutation-row-${keySlug(mutationKey)}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* `buoy-rq-explorer-<path>` for one node of the data explorer.
|
|
54
|
+
*
|
|
55
|
+
* The path is the same array the editor writes through
|
|
56
|
+
* (`updateNestedDataByPath`), so the handle and the write target can't drift:
|
|
57
|
+
* `buoy-rq-explorer-nested-count` edits `data.nested.count`.
|
|
58
|
+
*/
|
|
59
|
+
function explorerTestID(dataPath) {
|
|
60
|
+
return dataPath.length === 0 ? "buoy-rq-explorer-root" : `buoy-rq-explorer-${dataPath.map(slugPart).filter(Boolean).join("-")}`;
|
|
61
|
+
}
|
|
@@ -27,6 +27,7 @@ export function DataEditorMode({
|
|
|
27
27
|
const actionButtons = useActionButtons(selectedQuery, queryClient, queryVersion);
|
|
28
28
|
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
29
29
|
children: [/*#__PURE__*/_jsxs(ScrollView, {
|
|
30
|
+
testID: "buoy-rq-detail-scroll",
|
|
30
31
|
accessibilityLabel: "Data editor mode",
|
|
31
32
|
accessibilityHint: "View data editor mode",
|
|
32
33
|
"sentry-label": "ignore data editor mode",
|
|
@@ -54,6 +55,7 @@ export function DataEditorMode({
|
|
|
54
55
|
children: /*#__PURE__*/_jsxs(View, {
|
|
55
56
|
style: styles.queryExplorerContainer,
|
|
56
57
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
58
|
+
testID: "buoy-rq-query-explorer-header",
|
|
57
59
|
style: styles.queryExplorerHeader,
|
|
58
60
|
children: "Query Explorer"
|
|
59
61
|
}), /*#__PURE__*/_jsx(View, {
|
|
@@ -76,6 +78,7 @@ export function DataEditorMode({
|
|
|
76
78
|
children: /*#__PURE__*/_jsx(View, {
|
|
77
79
|
style: styles.actionsRow,
|
|
78
80
|
children: actionButtons.map((action, index) => /*#__PURE__*/_jsx(ActionButton, {
|
|
81
|
+
testID: `buoy-rq-action-${action.variant}`,
|
|
79
82
|
onClick: action.onPress,
|
|
80
83
|
text: action.label,
|
|
81
84
|
variant: action.variant,
|
|
@@ -108,6 +111,7 @@ export function DataEditorActionsFooter({
|
|
|
108
111
|
children: /*#__PURE__*/_jsx(View, {
|
|
109
112
|
style: styles.actionsRow,
|
|
110
113
|
children: actionButtons.map((action, index) => /*#__PURE__*/_jsx(ActionButton, {
|
|
114
|
+
testID: `buoy-rq-action-${action.variant}`,
|
|
111
115
|
onClick: action.onPress,
|
|
112
116
|
text: action.label,
|
|
113
117
|
variant: action.variant,
|
|
@@ -125,6 +129,7 @@ function DataExplorer({
|
|
|
125
129
|
return /*#__PURE__*/_jsxs(View, {
|
|
126
130
|
style: styles.dataContainer,
|
|
127
131
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
132
|
+
testID: "buoy-rq-data-header",
|
|
128
133
|
style: styles.dataHeader,
|
|
129
134
|
children: "Data Editor"
|
|
130
135
|
}), /*#__PURE__*/_jsx(View, {
|
|
@@ -177,9 +182,11 @@ function DataEmptyState({
|
|
|
177
182
|
return /*#__PURE__*/_jsxs(View, {
|
|
178
183
|
style: styles.emptyState,
|
|
179
184
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
185
|
+
testID: "buoy-rq-detail-empty-title",
|
|
180
186
|
style: styles.emptyTitle,
|
|
181
187
|
children: title
|
|
182
188
|
}), /*#__PURE__*/_jsx(Text, {
|
|
189
|
+
testID: "buoy-rq-detail-empty-description",
|
|
183
190
|
style: styles.emptyDescription,
|
|
184
191
|
children: description
|
|
185
192
|
})]
|
|
@@ -19,12 +19,16 @@ export function FilterStatusBadge({
|
|
|
19
19
|
const content = /*#__PURE__*/_jsx(View, {
|
|
20
20
|
style: styles.container,
|
|
21
21
|
children: /*#__PURE__*/_jsxs(Text, {
|
|
22
|
+
testID: "buoy-rq-filter-badge",
|
|
22
23
|
style: styles.text,
|
|
23
24
|
children: ["Showing ", filteredCount, " of ", totalCount]
|
|
24
25
|
})
|
|
25
26
|
});
|
|
26
27
|
if (onPress) {
|
|
27
28
|
return /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
29
|
+
testID: "buoy-rq-filter-badge-button",
|
|
30
|
+
accessibilityRole: "button",
|
|
31
|
+
accessibilityLabel: `Showing ${filteredCount} of ${totalCount}, open filters`,
|
|
28
32
|
onPress: onPress,
|
|
29
33
|
activeOpacity: 0.7,
|
|
30
34
|
children: content
|
|
@@ -19,6 +19,7 @@ export function MutationEditorMode({
|
|
|
19
19
|
}) {
|
|
20
20
|
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
21
21
|
children: [/*#__PURE__*/_jsxs(ScrollView, {
|
|
22
|
+
testID: "buoy-rq-mutation-detail-scroll",
|
|
22
23
|
accessibilityLabel: "Mutation editor mode",
|
|
23
24
|
accessibilityHint: "View mutation editor mode",
|
|
24
25
|
"sentry-label": "ignore mutation editor mode",
|
|
@@ -84,6 +85,7 @@ export function MutationEditorActionsFooter({
|
|
|
84
85
|
children: /*#__PURE__*/_jsx(View, {
|
|
85
86
|
style: styles.actionsGrid,
|
|
86
87
|
children: actionButtons.map((action, index) => /*#__PURE__*/_jsx(ActionButton, {
|
|
88
|
+
testID: `buoy-rq-action-${action.variant}`,
|
|
87
89
|
onClick: action.onPress,
|
|
88
90
|
text: action.label,
|
|
89
91
|
variant: action.variant,
|
|
@@ -143,9 +145,11 @@ function DataEmptyState({
|
|
|
143
145
|
return /*#__PURE__*/_jsxs(View, {
|
|
144
146
|
style: styles.emptyState,
|
|
145
147
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
148
|
+
testID: "buoy-rq-mutation-detail-empty-title",
|
|
146
149
|
style: styles.emptyTitle,
|
|
147
150
|
children: title
|
|
148
151
|
}), /*#__PURE__*/_jsx(Text, {
|
|
152
|
+
testID: "buoy-rq-mutation-detail-empty-description",
|
|
149
153
|
style: styles.emptyDescription,
|
|
150
154
|
children: description
|
|
151
155
|
})]
|
|
@@ -10,7 +10,8 @@ import { useWifiState } from "../hooks/useWifiState";
|
|
|
10
10
|
*/
|
|
11
11
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
12
12
|
export function WifiToggle({
|
|
13
|
-
size = 16
|
|
13
|
+
size = 16,
|
|
14
|
+
testID
|
|
14
15
|
}) {
|
|
15
16
|
const {
|
|
16
17
|
isOnline,
|
|
@@ -18,6 +19,7 @@ export function WifiToggle({
|
|
|
18
19
|
} = useWifiState();
|
|
19
20
|
return /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
20
21
|
"sentry-label": `ignore toggle WiFi ${isOnline ? "On" : "Off"}`,
|
|
22
|
+
testID: testID,
|
|
21
23
|
accessibilityRole: "button",
|
|
22
24
|
accessibilityLabel: `WiFi ${isOnline ? "On" : "Off"}`,
|
|
23
25
|
accessibilityHint: `Tap to turn WiFi ${isOnline ? "off" : "on"} for React Query`,
|
|
@@ -4,7 +4,7 @@ import { JsModal, devToolsStorageKeys, useSafeAreaInsets } from "@buoy-gg/shared
|
|
|
4
4
|
import { useGetQueryByQueryKeyWithVersion } from "../../hooks/useSelectedQuery";
|
|
5
5
|
import { ReactQueryModalHeader } from "./ReactQueryModalHeader";
|
|
6
6
|
import { DataEditorMode, DataEditorActionsFooter } from "../DataEditorMode";
|
|
7
|
-
import { useState, useCallback } from "react";
|
|
7
|
+
import { useState, useCallback, useEffect, useRef } from "react";
|
|
8
8
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
9
|
/**
|
|
10
10
|
* Specialized modal for data editing following "Decompose by Responsibility"
|
|
@@ -24,6 +24,30 @@ export function DataEditorModal({
|
|
|
24
24
|
version: queryVersion
|
|
25
25
|
} = useGetQueryByQueryKeyWithVersion(selectedQueryKey);
|
|
26
26
|
const [modalMode, setModalMode] = useState("bottomSheet");
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Fall back to the list when the selected query leaves the cache — a
|
|
30
|
+
* removal, a cache clear, or garbage collection. Same failure as the
|
|
31
|
+
* mutation editor: the key that selected it is still set, so this modal
|
|
32
|
+
* bails out and the browser stays hidden behind it, leaving an "open" tool
|
|
33
|
+
* with nothing on screen. The ref covers the tick before the subscription
|
|
34
|
+
* has resolved the query, when undefined does not yet mean gone.
|
|
35
|
+
*/
|
|
36
|
+
const currentQueryKey = selectedQueryKey ? JSON.stringify(selectedQueryKey) : undefined;
|
|
37
|
+
const seenQueryKey = useRef(undefined);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (selectedQuery) {
|
|
40
|
+
seenQueryKey.current = currentQueryKey;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Keyed to THIS query for the same reason as the mutation editor: the
|
|
44
|
+
// last-seen key outlives the detail view, so an un-keyed check would fire
|
|
45
|
+
// on the next selection before its lookup has resolved.
|
|
46
|
+
if (visible && seenQueryKey.current !== undefined && seenQueryKey.current === currentQueryKey) {
|
|
47
|
+
seenQueryKey.current = undefined;
|
|
48
|
+
onQuerySelect(undefined);
|
|
49
|
+
}
|
|
50
|
+
}, [visible, selectedQuery, currentQueryKey, onQuerySelect]);
|
|
27
51
|
const handleModeChange = useCallback(mode => {
|
|
28
52
|
setModalMode(mode);
|
|
29
53
|
}, []);
|
|
@@ -4,7 +4,7 @@ import { JsModal, useSafeAreaInsets, devToolsStorageKeys } from "@buoy-gg/shared
|
|
|
4
4
|
import { useGetMutationById } from "../../hooks/useSelectedMutation";
|
|
5
5
|
import { ReactQueryModalHeader } from "./ReactQueryModalHeader";
|
|
6
6
|
import { MutationEditorMode, MutationEditorActionsFooter } from "../MutationEditorMode";
|
|
7
|
-
import { useState, useCallback } from "react";
|
|
7
|
+
import { useState, useCallback, useEffect, useRef } from "react";
|
|
8
8
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
9
|
/**
|
|
10
10
|
* Modal variant dedicated to editing a single mutation entry. Restores persisted positioning and
|
|
@@ -21,6 +21,38 @@ export function MutationEditorModal({
|
|
|
21
21
|
}) {
|
|
22
22
|
const selectedMutation = useGetMutationById(selectedMutationId);
|
|
23
23
|
const [modalMode, setModalMode] = useState("bottomSheet");
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fall back to the list when the selected mutation leaves the cache.
|
|
27
|
+
*
|
|
28
|
+
* Without this the whole tool disappears. `Remove` (and a mutation-cache
|
|
29
|
+
* clear, and ordinary garbage collection) drops the entry while the id that
|
|
30
|
+
* selected it is still set, so this modal bails out on the missing mutation
|
|
31
|
+
* and the browser stays hidden behind an `inDetail` that is still true —
|
|
32
|
+
* leaving an "open" tool with nothing on screen and no way back except
|
|
33
|
+
* closing and reopening it from the dial.
|
|
34
|
+
*
|
|
35
|
+
* The ref is what makes it safe: `useGetMutationById` resolves on a
|
|
36
|
+
* `setTimeout(0)`, so the mutation is legitimately undefined for the first
|
|
37
|
+
* tick after a selection. Only a mutation that was HERE and then went away
|
|
38
|
+
* counts as gone.
|
|
39
|
+
*/
|
|
40
|
+
const seenMutationId = useRef(undefined);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (selectedMutation) {
|
|
43
|
+
seenMutationId.current = selectedMutation.mutationId;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Only THIS mutation going missing counts. Keying the check to the id
|
|
47
|
+
// matters: leaving a detail view leaves the last-seen id behind, and a
|
|
48
|
+
// plain "we had one and now we don't" flag would fire on the very next
|
|
49
|
+
// selection — during the tick before its own lookup resolves — and bounce
|
|
50
|
+
// the user straight back out of the mutation they just opened.
|
|
51
|
+
if (visible && seenMutationId.current !== undefined && seenMutationId.current === selectedMutationId) {
|
|
52
|
+
seenMutationId.current = undefined;
|
|
53
|
+
onMutationSelect(undefined);
|
|
54
|
+
}
|
|
55
|
+
}, [visible, selectedMutation, selectedMutationId, onMutationSelect]);
|
|
24
56
|
const handleModeChange = useCallback(mode => {
|
|
25
57
|
setModalMode(mode);
|
|
26
58
|
}, []);
|
|
@@ -69,7 +69,8 @@ export function ReactQueryModalHeader({
|
|
|
69
69
|
children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
70
70
|
onBack: onBack
|
|
71
71
|
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
72
|
-
title: getItemText(selectedQuery ?? selectedMutation)
|
|
72
|
+
title: getItemText(selectedQuery ?? selectedMutation),
|
|
73
|
+
testID: "buoy-rq-detail-title"
|
|
73
74
|
})]
|
|
74
75
|
});
|
|
75
76
|
}
|
|
@@ -81,11 +82,13 @@ export function ReactQueryModalHeader({
|
|
|
81
82
|
noMargin: true,
|
|
82
83
|
children: isSearchActive ? /*#__PURE__*/_jsxs(View, {
|
|
83
84
|
style: styles.headerSearchContainer,
|
|
85
|
+
testID: "buoy-rq-search-field",
|
|
84
86
|
children: [/*#__PURE__*/_jsx(Search, {
|
|
85
87
|
size: 14,
|
|
86
88
|
color: night.color.textSecondary
|
|
87
89
|
}), /*#__PURE__*/_jsx(TextInput, {
|
|
88
90
|
ref: searchInputRef,
|
|
91
|
+
testID: "buoy-rq-search",
|
|
89
92
|
style: styles.headerSearchInput,
|
|
90
93
|
placeholder: "Search query keys...",
|
|
91
94
|
placeholderTextColor: night.color.textTertiary,
|
|
@@ -104,6 +107,9 @@ export function ReactQueryModalHeader({
|
|
|
104
107
|
setIsSearchActive(false);
|
|
105
108
|
},
|
|
106
109
|
"sentry-label": "ignore clear search header",
|
|
110
|
+
testID: "buoy-rq-search-clear",
|
|
111
|
+
accessibilityRole: "button",
|
|
112
|
+
accessibilityLabel: "Clear search",
|
|
107
113
|
style: styles.headerSearchClear,
|
|
108
114
|
children: /*#__PURE__*/_jsx(X, {
|
|
109
115
|
size: 14,
|
|
@@ -117,19 +123,25 @@ export function ReactQueryModalHeader({
|
|
|
117
123
|
children: /*#__PURE__*/_jsx(TabSelector, {
|
|
118
124
|
tabs: tabs,
|
|
119
125
|
activeTab: activeTab,
|
|
126
|
+
testIDPrefix: "buoy-rq-tab",
|
|
120
127
|
onTabChange: tab => onTabChange(tab)
|
|
121
128
|
})
|
|
122
129
|
}), /*#__PURE__*/_jsx(WifiToggle, {
|
|
123
|
-
size: 16
|
|
130
|
+
size: 16,
|
|
131
|
+
testID: "buoy-rq-wifi"
|
|
124
132
|
})]
|
|
125
133
|
}) : /*#__PURE__*/_jsx(TabSelector, {
|
|
126
134
|
tabs: tabs,
|
|
127
135
|
activeTab: activeTab,
|
|
136
|
+
testIDPrefix: "buoy-rq-tab",
|
|
128
137
|
onTabChange: tab => onTabChange(tab)
|
|
129
138
|
})
|
|
130
139
|
}), /*#__PURE__*/_jsxs(ModalHeader.Actions, {
|
|
131
140
|
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
132
141
|
"sentry-label": "ignore open search",
|
|
142
|
+
testID: "buoy-rq-search-open",
|
|
143
|
+
accessibilityRole: "button",
|
|
144
|
+
accessibilityLabel: "Search queries and mutations",
|
|
133
145
|
onPress: handleSearchToggle,
|
|
134
146
|
style: styles.headerActionButton,
|
|
135
147
|
children: /*#__PURE__*/_jsx(Search, {
|
|
@@ -138,6 +150,9 @@ export function ReactQueryModalHeader({
|
|
|
138
150
|
})
|
|
139
151
|
}), onFilterPress && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
140
152
|
"sentry-label": "ignore open filter",
|
|
153
|
+
testID: "buoy-rq-filters",
|
|
154
|
+
accessibilityRole: "button",
|
|
155
|
+
accessibilityLabel: hasActiveFilters ? "Filters, active" : "Filters",
|
|
141
156
|
onPress: onFilterPress,
|
|
142
157
|
style: [styles.headerActionButton, hasActiveFilters && styles.activeFilterButton],
|
|
143
158
|
children: /*#__PURE__*/_jsx(Filter, {
|
|
@@ -146,6 +161,8 @@ export function ReactQueryModalHeader({
|
|
|
146
161
|
})
|
|
147
162
|
}), onClearCache && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
148
163
|
"sentry-label": "ignore clear cache",
|
|
164
|
+
testID: "buoy-rq-clear-cache",
|
|
165
|
+
accessibilityRole: "button",
|
|
149
166
|
onPress: onClearCache,
|
|
150
167
|
style: [styles.headerActionButton, styles.clearCacheButton],
|
|
151
168
|
accessibilityLabel: `Clear ${activeTab} cache`,
|
|
@@ -41,11 +41,13 @@ export default function ActionButton({
|
|
|
41
41
|
onClick,
|
|
42
42
|
text,
|
|
43
43
|
variant,
|
|
44
|
-
disabled
|
|
44
|
+
disabled,
|
|
45
|
+
testID
|
|
45
46
|
}) {
|
|
46
47
|
const config = buttonConfigs[variant];
|
|
47
48
|
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
48
49
|
"sentry-label": "ignore devtools action button",
|
|
50
|
+
testID: testID,
|
|
49
51
|
disabled: disabled,
|
|
50
52
|
onPress: onClick,
|
|
51
53
|
style: [styles.button, {
|
|
@@ -5,6 +5,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
|
|
5
5
|
import { updateNestedDataByPath } from "../../utils/updateNestedDataByPath";
|
|
6
6
|
import { displayValue, night } from "@buoy-gg/shared-ui";
|
|
7
7
|
import deleteItem from "../../utils/actions/deleteItem";
|
|
8
|
+
import { explorerTestID } from "../../utils/testIDs";
|
|
8
9
|
import { Text, TouchableOpacity, View, StyleSheet } from "react-native";
|
|
9
10
|
import { CopyButton as SharedCopyButton } from "@buoy-gg/shared-ui";
|
|
10
11
|
import { ChevronRight, ChevronDown, Trash } from "@buoy-gg/shared-ui";
|
|
@@ -84,6 +85,8 @@ const DeleteItemButton = /*#__PURE__*/memo(({
|
|
|
84
85
|
if (!activeQuery) return null;
|
|
85
86
|
return /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
86
87
|
"sentry-label": "ignore devtools explorer delete button",
|
|
88
|
+
testID: `${explorerTestID(dataPath)}-delete`,
|
|
89
|
+
accessibilityRole: "button",
|
|
87
90
|
onPress: handleDelete,
|
|
88
91
|
style: [styles.deleteButton, isFocused && styles.deleteButtonFocused],
|
|
89
92
|
accessibilityLabel: "Delete item",
|
|
@@ -113,7 +116,10 @@ const ClearArrayButton = /*#__PURE__*/memo(({
|
|
|
113
116
|
if (!activeQuery) return null;
|
|
114
117
|
return /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
115
118
|
"sentry-label": "ignore devtools explorer clear button",
|
|
119
|
+
testID: `${explorerTestID(dataPath)}-clear`,
|
|
116
120
|
style: [styles.clearButton, isFocused && styles.clearButtonFocused],
|
|
121
|
+
accessibilityRole: "button",
|
|
122
|
+
accessibilityLabel: "Remove all items",
|
|
117
123
|
"aria-label": "Remove all items",
|
|
118
124
|
onPress: handleClear,
|
|
119
125
|
hitSlop: HIT_SLOP_OPTIMIZED,
|
|
@@ -158,6 +164,9 @@ function ToggleValueButton({
|
|
|
158
164
|
const textStyle = value ? styles.toggleTextTrue : styles.toggleTextFalse;
|
|
159
165
|
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
160
166
|
"sentry-label": "ignore devtools explorer toggle button",
|
|
167
|
+
testID: `${explorerTestID(dataPath)}-toggle`,
|
|
168
|
+
accessibilityRole: "button",
|
|
169
|
+
accessibilityLabel: `${dataPath[dataPath.length - 1] ?? "value"}, ${value ? "true" : "false"}`,
|
|
161
170
|
style: styles.modernToggleButton,
|
|
162
171
|
onPress: handleClick,
|
|
163
172
|
hitSlop: HIT_SLOP_OPTIMIZED,
|
|
@@ -170,6 +179,8 @@ function ToggleValueButton({
|
|
|
170
179
|
})
|
|
171
180
|
}), itemsDeletable && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
172
181
|
"sentry-label": "ignore devtools explorer delete button in toggle",
|
|
182
|
+
testID: `${explorerTestID(dataPath)}-delete`,
|
|
183
|
+
accessibilityRole: "button",
|
|
173
184
|
onPress: handleDelete,
|
|
174
185
|
style: styles.deleteButtonInToggle,
|
|
175
186
|
accessibilityLabel: "Delete item",
|
|
@@ -312,6 +323,12 @@ export default function Explorer({
|
|
|
312
323
|
children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
313
324
|
"sentry-label": "ignore devtools explorer expander button",
|
|
314
325
|
style: styles.expanderButton,
|
|
326
|
+
testID: explorerTestID(currentDataPath),
|
|
327
|
+
accessibilityRole: "button",
|
|
328
|
+
accessibilityLabel: `${label}, ${subEntries.length} ${subEntries.length === 1 ? "item" : "items"}`,
|
|
329
|
+
accessibilityState: {
|
|
330
|
+
expanded: isExpanded
|
|
331
|
+
},
|
|
315
332
|
onPress: toggleExpanded,
|
|
316
333
|
hitSlop: HIT_SLOP_OPTIMIZED,
|
|
317
334
|
activeOpacity: 0.6,
|
|
@@ -393,7 +410,8 @@ export default function Explorer({
|
|
|
393
410
|
style: styles.nebulaInputWrapper,
|
|
394
411
|
children: /*#__PURE__*/_jsx(CyberpunkInput, {
|
|
395
412
|
label: label,
|
|
396
|
-
|
|
413
|
+
testID: `${explorerTestID(currentDataPath)}-input`,
|
|
414
|
+
accessibilityLabel: `${label}, editable value`,
|
|
397
415
|
style: [valueType === "number" ? styles.textNumber : styles.textString],
|
|
398
416
|
keyboardType: valueType === "number" ? "numeric" : "default",
|
|
399
417
|
value: localInputValue,
|
|
@@ -438,6 +456,7 @@ export default function Explorer({
|
|
|
438
456
|
style: styles.text344054,
|
|
439
457
|
children: label
|
|
440
458
|
}), /*#__PURE__*/_jsx(Text, {
|
|
459
|
+
testID: `${explorerTestID(currentDataPath)}-value`,
|
|
441
460
|
style: styles.displayValueText,
|
|
442
461
|
children: displayValue(value)
|
|
443
462
|
})]
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { TouchableOpacity, Text, View, StyleSheet } from "react-native";
|
|
4
4
|
import { CheckCircle, LoadingCircle, PauseCircle, XCircle } from "./svgs";
|
|
5
5
|
import { buoyColors, night, useRelativeTime } from "@buoy-gg/shared-ui";
|
|
6
|
+
import { mutationRowTestID } from "../../utils/testIDs";
|
|
6
7
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
8
|
const getMutationText = mutation => {
|
|
8
9
|
if (!mutation.options.mutationKey) return "Anonymous Mutation";
|
|
@@ -69,8 +70,16 @@ export default function MutationButton({
|
|
|
69
70
|
}
|
|
70
71
|
};
|
|
71
72
|
const statusInfo = getStatusInfo();
|
|
73
|
+
const mutationText = getMutationText(mutation);
|
|
74
|
+
const testID = mutationRowTestID(mutation.options.mutationKey, mutation.mutationId);
|
|
72
75
|
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
73
76
|
"sentry-label": "ignore devtools mutation button",
|
|
77
|
+
testID: testID,
|
|
78
|
+
accessibilityRole: "button"
|
|
79
|
+
// Same Fabric merge as the query rows: without a spelled-out label the
|
|
80
|
+
// row announces as an unnamed button.
|
|
81
|
+
,
|
|
82
|
+
accessibilityLabel: `${mutationText}, ${statusInfo.status}`,
|
|
74
83
|
onPress: () => setSelectedMutation(mutation === selected ? undefined : mutation),
|
|
75
84
|
style: [styles.button, selected?.mutationId === mutation.mutationId && styles.selected],
|
|
76
85
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
@@ -84,6 +93,7 @@ export default function MutationButton({
|
|
|
84
93
|
}), /*#__PURE__*/_jsxs(View, {
|
|
85
94
|
style: styles.statusInfo,
|
|
86
95
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
96
|
+
testID: `${testID}-status`,
|
|
87
97
|
style: [styles.statusLabel, {
|
|
88
98
|
color: statusInfo.color
|
|
89
99
|
}],
|
|
@@ -96,8 +106,9 @@ export default function MutationButton({
|
|
|
96
106
|
}), /*#__PURE__*/_jsx(View, {
|
|
97
107
|
style: styles.mutationSection,
|
|
98
108
|
children: /*#__PURE__*/_jsx(Text, {
|
|
109
|
+
testID: `${testID}-key`,
|
|
99
110
|
style: styles.mutationKey,
|
|
100
|
-
children:
|
|
111
|
+
children: mutationText
|
|
101
112
|
})
|
|
102
113
|
})]
|
|
103
114
|
}), /*#__PURE__*/_jsx(View, {
|
|
@@ -26,11 +26,13 @@ export default function MutationDetails({
|
|
|
26
26
|
horizontal: true,
|
|
27
27
|
style: styles.flex1,
|
|
28
28
|
children: /*#__PURE__*/_jsx(Text, {
|
|
29
|
+
testID: "buoy-rq-mutation-detail-key",
|
|
29
30
|
style: styles.flexWrap,
|
|
30
31
|
children: `${selectedMutation.options.mutationKey ? displayValue(selectedMutation.options.mutationKey, true) : "No mutationKey found"}`
|
|
31
32
|
})
|
|
32
33
|
}), /*#__PURE__*/_jsx(MutationDetailsChips, {
|
|
33
|
-
status: selectedMutation.state.status
|
|
34
|
+
status: selectedMutation.state.status,
|
|
35
|
+
testID: "buoy-rq-mutation-detail-status"
|
|
34
36
|
})]
|
|
35
37
|
}), /*#__PURE__*/_jsxs(View, {
|
|
36
38
|
style: styles.row,
|
|
@@ -25,7 +25,8 @@ const textColors = {
|
|
|
25
25
|
* Small status chip used in mutation detail views that adapts styling to the mutation status.
|
|
26
26
|
*/
|
|
27
27
|
export default function QueryDetailsChip({
|
|
28
|
-
status
|
|
28
|
+
status,
|
|
29
|
+
testID
|
|
29
30
|
}) {
|
|
30
31
|
const statusToColor = status;
|
|
31
32
|
const backgroundColor = backgroundColors[statusToColor];
|
|
@@ -37,6 +38,7 @@ export default function QueryDetailsChip({
|
|
|
37
38
|
borderColor
|
|
38
39
|
}],
|
|
39
40
|
children: /*#__PURE__*/_jsx(Text, {
|
|
41
|
+
testID: testID,
|
|
40
42
|
style: [styles.text, {
|
|
41
43
|
color: textColor
|
|
42
44
|
}],
|
|
@@ -58,6 +58,7 @@ const MutationStatusCount = ({
|
|
|
58
58
|
isActive: activeFilter === "pending",
|
|
59
59
|
onPress: event => handleFilterClick("pending", event),
|
|
60
60
|
onTouchStart: handleTouchStart,
|
|
61
|
+
testID: "buoy-rq-mstatus-pending",
|
|
61
62
|
showLabel: true,
|
|
62
63
|
size: size
|
|
63
64
|
}), /*#__PURE__*/_jsx(QueryStatus, {
|
|
@@ -67,6 +68,7 @@ const MutationStatusCount = ({
|
|
|
67
68
|
isActive: activeFilter === "success",
|
|
68
69
|
onPress: event => handleFilterClick("success", event),
|
|
69
70
|
onTouchStart: handleTouchStart,
|
|
71
|
+
testID: "buoy-rq-mstatus-success",
|
|
70
72
|
showLabel: true,
|
|
71
73
|
size: size
|
|
72
74
|
}), /*#__PURE__*/_jsx(QueryStatus, {
|
|
@@ -76,6 +78,7 @@ const MutationStatusCount = ({
|
|
|
76
78
|
isActive: activeFilter === "error",
|
|
77
79
|
onPress: event => handleFilterClick("error", event),
|
|
78
80
|
onTouchStart: handleTouchStart,
|
|
81
|
+
testID: "buoy-rq-mstatus-error",
|
|
79
82
|
showLabel: true,
|
|
80
83
|
size: size
|
|
81
84
|
}), /*#__PURE__*/_jsx(QueryStatus, {
|
|
@@ -85,6 +88,7 @@ const MutationStatusCount = ({
|
|
|
85
88
|
isActive: activeFilter === "paused",
|
|
86
89
|
onPress: event => handleFilterClick("paused", event),
|
|
87
90
|
onTouchStart: handleTouchStart,
|
|
91
|
+
testID: "buoy-rq-mstatus-paused",
|
|
88
92
|
showLabel: true,
|
|
89
93
|
size: size
|
|
90
94
|
})]
|
|
@@ -114,12 +114,16 @@ export default function MutationsList({
|
|
|
114
114
|
style: styles.listWrapper,
|
|
115
115
|
children: /*#__PURE__*/_jsx(FlatList, {
|
|
116
116
|
"sentry-label": "ignore devtools mutations list",
|
|
117
|
+
testID: "buoy-rq-mutation-list",
|
|
117
118
|
data: filteredMutations,
|
|
118
119
|
renderItem: renderMutation,
|
|
119
120
|
keyExtractor: (item, index) => `${item.mutationId}-${item.state.submittedAt}-${item.state.status}-${index}`,
|
|
120
121
|
inverted: true,
|
|
121
|
-
showsVerticalScrollIndicator: true
|
|
122
|
-
|
|
122
|
+
showsVerticalScrollIndicator: true
|
|
123
|
+
// See QueryBrowser: e2e bundles keep every cell mounted so Detox's
|
|
124
|
+
// hittability check doesn't reject an on-screen row as clipped.
|
|
125
|
+
,
|
|
126
|
+
removeClippedSubviews: !(typeof process !== "undefined" && process.env?.EXPO_PUBLIC_E2E),
|
|
123
127
|
contentContainerStyle: contentContainerStyle || styles.listContent,
|
|
124
128
|
initialNumToRender: 10,
|
|
125
129
|
maxToRenderPerBatch: 10,
|
|
@@ -130,6 +134,7 @@ export default function MutationsList({
|
|
|
130
134
|
style: styles.emptyContainer,
|
|
131
135
|
children: /*#__PURE__*/_jsx(Text, {
|
|
132
136
|
style: styles.emptyText,
|
|
137
|
+
testID: "buoy-rq-mutations-empty",
|
|
133
138
|
children: activeFilter ? `No ${activeFilter} mutations found` : "No mutations found"
|
|
134
139
|
})
|
|
135
140
|
}), selectedMutation && !hideInfoPanel && /*#__PURE__*/_jsxs(Animated.View, {
|
|
@@ -97,6 +97,7 @@ export default function QueryBrowser({
|
|
|
97
97
|
style: styles.emptyContainer,
|
|
98
98
|
children: /*#__PURE__*/_jsx(Text, {
|
|
99
99
|
style: styles.emptyText,
|
|
100
|
+
testID: "buoy-rq-empty",
|
|
100
101
|
children: emptyStateMessage || (activeFilter ? `No ${activeFilter} queries found` : "No queries found")
|
|
101
102
|
})
|
|
102
103
|
});
|
|
@@ -111,6 +112,7 @@ export default function QueryBrowser({
|
|
|
111
112
|
// mutable with stable identity, so `stateSig` carries the changing
|
|
112
113
|
// fields for the row's memo comparison.
|
|
113
114
|
_jsx(FlatList, {
|
|
115
|
+
testID: "buoy-rq-query-list",
|
|
114
116
|
style: styles.listWrapper,
|
|
115
117
|
contentContainerStyle: contentContainerStyle || styles.listContent,
|
|
116
118
|
data: filteredQueries,
|
|
@@ -123,8 +125,13 @@ export default function QueryBrowser({
|
|
|
123
125
|
isSelected: selectedQuery?.queryHash === query.queryHash,
|
|
124
126
|
onSelect: handleQuerySelect
|
|
125
127
|
}),
|
|
126
|
-
showsVerticalScrollIndicator: true
|
|
127
|
-
|
|
128
|
+
showsVerticalScrollIndicator: true
|
|
129
|
+
// Clipped cells fail Detox's hittability check even when they are on
|
|
130
|
+
// screen, so e2e bundles (Metro with EXPO_PUBLIC_E2E=1) turn clipping
|
|
131
|
+
// off. Inlined at bundle time; a non-inlining bundler keeps prod
|
|
132
|
+
// behavior.
|
|
133
|
+
,
|
|
134
|
+
removeClippedSubviews: !(typeof process !== "undefined" && process.env?.EXPO_PUBLIC_E2E),
|
|
128
135
|
initialNumToRender: 12,
|
|
129
136
|
maxToRenderPerBatch: 8,
|
|
130
137
|
windowSize: 5,
|