@bbearai/react-native 0.3.8 → 0.3.9
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/README.md +139 -92
- package/dist/index.js +65 -8
- package/dist/index.mjs +68 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ function App() {
|
|
|
34
34
|
getCurrentUser: async () => ({
|
|
35
35
|
id: user.id,
|
|
36
36
|
email: user.email,
|
|
37
|
+
name: user.name, // Optional, shown on reports
|
|
37
38
|
}),
|
|
38
39
|
}}
|
|
39
40
|
>
|
|
@@ -44,6 +45,138 @@ function App() {
|
|
|
44
45
|
}
|
|
45
46
|
```
|
|
46
47
|
|
|
48
|
+
## Configuration Options
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<BugBearProvider
|
|
52
|
+
config={{
|
|
53
|
+
// Required
|
|
54
|
+
projectId: 'your-project-id',
|
|
55
|
+
getCurrentUser: async () => ({ id: user.id, email: user.email }),
|
|
56
|
+
|
|
57
|
+
// Optional — rich context for bug reports
|
|
58
|
+
getAppContext: () => ({
|
|
59
|
+
currentRoute: currentScreenName, // Current screen name
|
|
60
|
+
userRole: currentUser?.role, // 'owner', 'manager', 'guest', etc.
|
|
61
|
+
propertyId: selectedProperty?.id, // App-specific context
|
|
62
|
+
custom: { theme: 'dark' }, // Any additional data
|
|
63
|
+
}),
|
|
64
|
+
|
|
65
|
+
// Optional — callbacks
|
|
66
|
+
dashboardUrl: 'https://app.bugbear.ai', // Web dashboard link in widget
|
|
67
|
+
onNavigate: (route) => navigation.navigate(route), // Deep link handler for test cases
|
|
68
|
+
onReportSubmitted: (report) => { ... }, // After report submission
|
|
69
|
+
getNavigationHistory: () => [...recentScreens], // Custom nav tracking
|
|
70
|
+
|
|
71
|
+
// Optional — self-hosted
|
|
72
|
+
supabaseUrl: '...',
|
|
73
|
+
supabaseAnonKey: '...',
|
|
74
|
+
}}
|
|
75
|
+
enabled={isAuthReady} // Delay init until auth is ready (default: true)
|
|
76
|
+
>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Why use `getAppContext`?
|
|
80
|
+
|
|
81
|
+
When a tester reports a bug, BugBear attaches the app context to the report. This tells the developer fixing the bug:
|
|
82
|
+
- **Which screen** the bug is on
|
|
83
|
+
- **What role** the user has (critical for role-dependent bugs)
|
|
84
|
+
- **App-specific state** like selected property, active filters, etc.
|
|
85
|
+
|
|
86
|
+
The bug report form also includes a **"Which screen?"** field so testers can specify the affected screen manually.
|
|
87
|
+
|
|
88
|
+
## Automatic Context Capture
|
|
89
|
+
|
|
90
|
+
BugBearProvider automatically captures debugging context with zero configuration:
|
|
91
|
+
|
|
92
|
+
| Data | Details |
|
|
93
|
+
|------|---------|
|
|
94
|
+
| **Console logs** | Last 50 `console.log/warn/error/info` calls |
|
|
95
|
+
| **Network requests** | Last 20 `fetch()` calls with method, URL, status, duration |
|
|
96
|
+
| **Failed response bodies** | First 500 chars of 4xx/5xx response bodies |
|
|
97
|
+
| **Performance** | Page load time, memory usage |
|
|
98
|
+
| **Environment** | Language, timezone, online status |
|
|
99
|
+
|
|
100
|
+
This data is attached to every bug report and available to the MCP server's `get_report_context` tool for AI-assisted debugging.
|
|
101
|
+
|
|
102
|
+
> **Note:** For React Native, navigation history is not auto-captured (no `pushState`). Use `getNavigationHistory` or React Navigation integration below for screen tracking.
|
|
103
|
+
|
|
104
|
+
## Navigation Tracking
|
|
105
|
+
|
|
106
|
+
### React Navigation integration
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { useNavigationContainerRef } from '@react-navigation/native';
|
|
110
|
+
|
|
111
|
+
function App() {
|
|
112
|
+
const navigationRef = useNavigationContainerRef();
|
|
113
|
+
const routeHistory = useRef<string[]>([]);
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<BugBearProvider
|
|
117
|
+
config={{
|
|
118
|
+
projectId: 'your-project-id',
|
|
119
|
+
getCurrentUser: async () => ({ id: user.id, email: user.email }),
|
|
120
|
+
getNavigationHistory: () => routeHistory.current,
|
|
121
|
+
getAppContext: () => ({
|
|
122
|
+
currentRoute: navigationRef.getCurrentRoute()?.name || 'unknown',
|
|
123
|
+
userRole: currentUser?.role,
|
|
124
|
+
}),
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
<NavigationContainer
|
|
128
|
+
ref={navigationRef}
|
|
129
|
+
onStateChange={() => {
|
|
130
|
+
const name = navigationRef.getCurrentRoute()?.name;
|
|
131
|
+
if (name) {
|
|
132
|
+
routeHistory.current.push(name);
|
|
133
|
+
// Keep last 20
|
|
134
|
+
if (routeHistory.current.length > 20) routeHistory.current.shift();
|
|
135
|
+
}
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
{/* Your navigation */}
|
|
139
|
+
</NavigationContainer>
|
|
140
|
+
<BugBearButton />
|
|
141
|
+
</BugBearProvider>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Widget Architecture
|
|
147
|
+
|
|
148
|
+
The widget uses a navigation stack pattern with 10 screens:
|
|
149
|
+
|
|
150
|
+
| Screen | Purpose |
|
|
151
|
+
|--------|---------|
|
|
152
|
+
| **Home** | Smart hero banner + 2x2 action grid + progress bar + web dashboard link |
|
|
153
|
+
| **Test Detail** | One-test-at-a-time execution with pass/fail/skip actions |
|
|
154
|
+
| **Test List** | All assignments grouped by folder with filter bar |
|
|
155
|
+
| **Test Feedback** | Star rating + quality flags after pass/fail |
|
|
156
|
+
| **Report** | Bug/feedback submission with type, severity, description, affected screen |
|
|
157
|
+
| **Report Success** | Confirmation with auto-return to home |
|
|
158
|
+
| **Message List** | Thread list with unread badges + compose button |
|
|
159
|
+
| **Thread Detail** | Chat bubbles + reply composer |
|
|
160
|
+
| **Compose Message** | New thread form with subject + message |
|
|
161
|
+
| **Profile** | Tester info, stats, and editable fields |
|
|
162
|
+
|
|
163
|
+
## Button Configuration
|
|
164
|
+
|
|
165
|
+
The BugBear button is draggable by default with edge-snapping behavior:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<BugBearButton
|
|
169
|
+
draggable={true} // Enable/disable dragging (default: true)
|
|
170
|
+
position="bottom-right" // Initial position: 'bottom-right' | 'bottom-left'
|
|
171
|
+
initialX={100} // Custom initial X position (optional)
|
|
172
|
+
initialY={500} // Custom initial Y position (optional)
|
|
173
|
+
minY={100} // Minimum Y from top (default: 100)
|
|
174
|
+
maxYOffset={160} // Max Y offset from bottom (default: 160)
|
|
175
|
+
/>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The button automatically snaps to the nearest screen edge when released.
|
|
179
|
+
|
|
47
180
|
## Monorepo Setup
|
|
48
181
|
|
|
49
182
|
When using BugBear in a monorepo (e.g., with Turborepo, Nx, or Yarn Workspaces), you may encounter React duplicate instance errors like:
|
|
@@ -105,90 +238,6 @@ If you're using Yarn workspaces, you can also configure hoisting to ensure a sin
|
|
|
105
238
|
}
|
|
106
239
|
```
|
|
107
240
|
|
|
108
|
-
## Configuration Options
|
|
109
|
-
|
|
110
|
-
```tsx
|
|
111
|
-
<BugBearProvider
|
|
112
|
-
config={{
|
|
113
|
-
projectId: 'your-project-id', // Required: Your BugBear project ID
|
|
114
|
-
getCurrentUser: async () => {...}, // Required: Return current user info
|
|
115
|
-
dashboardUrl: 'https://app.bugbear.ai', // Optional: Web dashboard link in widget
|
|
116
|
-
getNavigationHistory: () => [...], // Optional: Custom navigation tracking
|
|
117
|
-
onNavigate: (route) => {...}, // Optional: Deep link handler for test cases
|
|
118
|
-
onReportSubmitted: (report) => {...}, // Optional: Callback after report submission
|
|
119
|
-
}}
|
|
120
|
-
>
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
## Widget Architecture
|
|
124
|
-
|
|
125
|
-
The widget uses a navigation stack pattern with 10 screens:
|
|
126
|
-
|
|
127
|
-
| Screen | Purpose |
|
|
128
|
-
|--------|---------|
|
|
129
|
-
| **Home** | Smart hero banner + 2x2 action grid + progress bar + web dashboard link |
|
|
130
|
-
| **Test Detail** | One-test-at-a-time execution with pass/fail/skip actions |
|
|
131
|
-
| **Test List** | All assignments grouped by folder with filter bar |
|
|
132
|
-
| **Test Feedback** | Star rating + quality flags after pass/fail |
|
|
133
|
-
| **Report** | Bug/feedback submission with type, severity, description |
|
|
134
|
-
| **Report Success** | Confirmation with auto-return to home |
|
|
135
|
-
| **Message List** | Thread list with unread badges + compose button |
|
|
136
|
-
| **Thread Detail** | Chat bubbles + reply composer |
|
|
137
|
-
| **Compose Message** | New thread form with subject + message |
|
|
138
|
-
| **Profile** | Tester info, stats, and editable fields |
|
|
139
|
-
|
|
140
|
-
## Navigation Tracking
|
|
141
|
-
|
|
142
|
-
BugBear can automatically track navigation history for better bug context. If you're using React Navigation:
|
|
143
|
-
|
|
144
|
-
```tsx
|
|
145
|
-
import { useNavigationContainerRef } from '@react-navigation/native';
|
|
146
|
-
|
|
147
|
-
function App() {
|
|
148
|
-
const navigationRef = useNavigationContainerRef();
|
|
149
|
-
const routeNameRef = useRef<string>();
|
|
150
|
-
|
|
151
|
-
return (
|
|
152
|
-
<BugBearProvider
|
|
153
|
-
projectId="your-project-id"
|
|
154
|
-
getCurrentUser={...}
|
|
155
|
-
getNavigationHistory={() => routeNameRef.current ? [routeNameRef.current] : []}
|
|
156
|
-
>
|
|
157
|
-
<NavigationContainer
|
|
158
|
-
ref={navigationRef}
|
|
159
|
-
onReady={() => {
|
|
160
|
-
routeNameRef.current = navigationRef.getCurrentRoute()?.name;
|
|
161
|
-
}}
|
|
162
|
-
onStateChange={() => {
|
|
163
|
-
const currentRouteName = navigationRef.getCurrentRoute()?.name;
|
|
164
|
-
routeNameRef.current = currentRouteName;
|
|
165
|
-
}}
|
|
166
|
-
>
|
|
167
|
-
{/* Your navigation */}
|
|
168
|
-
</NavigationContainer>
|
|
169
|
-
<BugBearWidget />
|
|
170
|
-
</BugBearProvider>
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
## Button Configuration
|
|
176
|
-
|
|
177
|
-
The BugBear button is draggable by default with edge-snapping behavior:
|
|
178
|
-
|
|
179
|
-
```tsx
|
|
180
|
-
<BugBearButton
|
|
181
|
-
draggable={true} // Enable/disable dragging (default: true)
|
|
182
|
-
position="bottom-right" // Initial position: 'bottom-right' | 'bottom-left'
|
|
183
|
-
initialX={100} // Custom initial X position (optional)
|
|
184
|
-
initialY={500} // Custom initial Y position (optional)
|
|
185
|
-
minY={100} // Minimum Y from top (default: 100)
|
|
186
|
-
maxYOffset={160} // Max Y offset from bottom (default: 160)
|
|
187
|
-
/>
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
The button automatically snaps to the nearest screen edge when released.
|
|
191
|
-
|
|
192
241
|
## Troubleshooting
|
|
193
242
|
|
|
194
243
|
### Widget not showing
|
|
@@ -203,10 +252,12 @@ If you're using your own Supabase instance, provide the credentials:
|
|
|
203
252
|
|
|
204
253
|
```tsx
|
|
205
254
|
<BugBearProvider
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
255
|
+
config={{
|
|
256
|
+
projectId: 'your-project-id',
|
|
257
|
+
supabaseUrl: 'https://your-instance.supabase.co',
|
|
258
|
+
supabaseAnonKey: 'your-anon-key',
|
|
259
|
+
getCurrentUser: async () => ({ id: user.id, email: user.email }),
|
|
260
|
+
}}
|
|
210
261
|
>
|
|
211
262
|
```
|
|
212
263
|
|
|
@@ -216,15 +267,11 @@ If you're using your own Supabase instance, provide the credentials:
|
|
|
216
267
|
|
|
217
268
|
The BugBear button renders within the normal React Native view hierarchy using absolute positioning. When your app displays modals, bottom sheets, or the keyboard, the button may be hidden behind them.
|
|
218
269
|
|
|
219
|
-
This is a platform limitation - React Native doesn't provide a built-in way to render views above all modals without using a Modal component (which would block touch events on the underlying app).
|
|
220
|
-
|
|
221
270
|
**Workarounds:**
|
|
222
271
|
- Accept that the button won't be accessible when modals are open
|
|
223
272
|
- Consider dismissing the modal to access BugBear
|
|
224
273
|
- For testing flows that involve modals, provide an alternative way to trigger the BugBear widget
|
|
225
274
|
|
|
226
|
-
We're investigating solutions using `react-native-portal` or similar libraries for future releases.
|
|
227
|
-
|
|
228
275
|
## License
|
|
229
276
|
|
|
230
277
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -11982,19 +11982,40 @@ var BugBearClient = class {
|
|
|
11982
11982
|
return { success: false, error: message };
|
|
11983
11983
|
}
|
|
11984
11984
|
}
|
|
11985
|
+
/**
|
|
11986
|
+
* Pass a test assignment — convenience wrapper around updateAssignmentStatus
|
|
11987
|
+
*/
|
|
11988
|
+
async passAssignment(assignmentId) {
|
|
11989
|
+
return this.updateAssignmentStatus(assignmentId, "passed");
|
|
11990
|
+
}
|
|
11991
|
+
/**
|
|
11992
|
+
* Fail a test assignment — convenience wrapper around updateAssignmentStatus
|
|
11993
|
+
*/
|
|
11994
|
+
async failAssignment(assignmentId) {
|
|
11995
|
+
return this.updateAssignmentStatus(assignmentId, "failed");
|
|
11996
|
+
}
|
|
11985
11997
|
/**
|
|
11986
11998
|
* Skip a test assignment with a required reason
|
|
11987
11999
|
* Marks the assignment as 'skipped' and records why it was skipped
|
|
11988
12000
|
*/
|
|
11989
12001
|
async skipAssignment(assignmentId, reason, notes) {
|
|
12002
|
+
let actualReason;
|
|
12003
|
+
let actualNotes;
|
|
12004
|
+
if (typeof reason === "object") {
|
|
12005
|
+
actualReason = reason.reason;
|
|
12006
|
+
actualNotes = reason.notes;
|
|
12007
|
+
} else {
|
|
12008
|
+
actualReason = reason;
|
|
12009
|
+
actualNotes = notes;
|
|
12010
|
+
}
|
|
11990
12011
|
try {
|
|
11991
12012
|
const updateData = {
|
|
11992
12013
|
status: "skipped",
|
|
11993
|
-
skip_reason:
|
|
12014
|
+
skip_reason: actualReason,
|
|
11994
12015
|
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
11995
12016
|
};
|
|
11996
|
-
if (
|
|
11997
|
-
updateData.notes =
|
|
12017
|
+
if (actualNotes) {
|
|
12018
|
+
updateData.notes = actualNotes;
|
|
11998
12019
|
}
|
|
11999
12020
|
const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
|
|
12000
12021
|
if (error) {
|
|
@@ -13478,7 +13499,7 @@ function HomeScreen({ nav }) {
|
|
|
13478
13499
|
import_react_native3.TouchableOpacity,
|
|
13479
13500
|
{
|
|
13480
13501
|
style: styles.actionCard,
|
|
13481
|
-
onPress: () => nav.push({ name: "
|
|
13502
|
+
onPress: () => nav.push({ name: "TEST_LIST" }),
|
|
13482
13503
|
activeOpacity: 0.7
|
|
13483
13504
|
},
|
|
13484
13505
|
/* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.actionIcon }, "\u2705"),
|
|
@@ -13746,12 +13767,14 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13746
13767
|
const currentIndex = displayedAssignment ? assignments.indexOf(displayedAssignment) : -1;
|
|
13747
13768
|
const handlePass = (0, import_react4.useCallback)(async () => {
|
|
13748
13769
|
if (!client || !displayedAssignment) return;
|
|
13770
|
+
import_react_native4.Keyboard.dismiss();
|
|
13749
13771
|
await client.passAssignment(displayedAssignment.id);
|
|
13750
13772
|
await refreshAssignments();
|
|
13751
13773
|
nav.replace({ name: "TEST_FEEDBACK", status: "passed", assignmentId: displayedAssignment.id });
|
|
13752
13774
|
}, [client, displayedAssignment, refreshAssignments, nav]);
|
|
13753
13775
|
const handleFail = (0, import_react4.useCallback)(async () => {
|
|
13754
13776
|
if (!client || !displayedAssignment) return;
|
|
13777
|
+
import_react_native4.Keyboard.dismiss();
|
|
13755
13778
|
await client.failAssignment(displayedAssignment.id);
|
|
13756
13779
|
await refreshAssignments();
|
|
13757
13780
|
nav.replace({
|
|
@@ -13765,6 +13788,7 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13765
13788
|
}, [client, displayedAssignment, refreshAssignments, nav]);
|
|
13766
13789
|
const handleSkip = (0, import_react4.useCallback)(async () => {
|
|
13767
13790
|
if (!client || !displayedAssignment || !selectedSkipReason) return;
|
|
13791
|
+
import_react_native4.Keyboard.dismiss();
|
|
13768
13792
|
setSkipping(true);
|
|
13769
13793
|
await client.skipAssignment(displayedAssignment.id, {
|
|
13770
13794
|
reason: selectedSkipReason,
|
|
@@ -13833,7 +13857,9 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13833
13857
|
{
|
|
13834
13858
|
style: styles2.navigateButton,
|
|
13835
13859
|
onPress: () => {
|
|
13860
|
+
import_react_native4.Keyboard.dismiss();
|
|
13836
13861
|
onNavigate(testCase.targetRoute);
|
|
13862
|
+
nav.closeWidget?.();
|
|
13837
13863
|
}
|
|
13838
13864
|
},
|
|
13839
13865
|
/* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.navigateText }, "\u{1F9ED} Go to test location")
|
|
@@ -14974,10 +15000,29 @@ function BugBearButton({
|
|
|
14974
15000
|
}
|
|
14975
15001
|
};
|
|
14976
15002
|
const handleClose = () => {
|
|
15003
|
+
import_react_native15.Keyboard.dismiss();
|
|
14977
15004
|
setModalVisible(false);
|
|
14978
|
-
reset();
|
|
14979
15005
|
};
|
|
14980
|
-
const nav = {
|
|
15006
|
+
const nav = {
|
|
15007
|
+
push: (screen) => {
|
|
15008
|
+
import_react_native15.Keyboard.dismiss();
|
|
15009
|
+
push(screen);
|
|
15010
|
+
},
|
|
15011
|
+
pop: () => {
|
|
15012
|
+
import_react_native15.Keyboard.dismiss();
|
|
15013
|
+
pop();
|
|
15014
|
+
},
|
|
15015
|
+
replace: (screen) => {
|
|
15016
|
+
import_react_native15.Keyboard.dismiss();
|
|
15017
|
+
replace(screen);
|
|
15018
|
+
},
|
|
15019
|
+
reset: () => {
|
|
15020
|
+
import_react_native15.Keyboard.dismiss();
|
|
15021
|
+
reset();
|
|
15022
|
+
},
|
|
15023
|
+
canGoBack,
|
|
15024
|
+
closeWidget: handleClose
|
|
15025
|
+
};
|
|
14981
15026
|
const renderScreen = () => {
|
|
14982
15027
|
switch (currentScreen.name) {
|
|
14983
15028
|
case "HOME":
|
|
@@ -15034,7 +15079,7 @@ function BugBearButton({
|
|
|
15034
15079
|
behavior: import_react_native15.Platform.OS === "ios" ? "padding" : "height",
|
|
15035
15080
|
style: styles13.modalOverlay
|
|
15036
15081
|
},
|
|
15037
|
-
/* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.modalContainer }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.header }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.headerLeft }, canGoBack ? /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: pop, style: styles13.backButton }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.backText }, "\u2190 Back")) : /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.headerTitleRow }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: handleClose, style: styles13.closeButton }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.closeText }, "\u2715"))), /* @__PURE__ */ import_react16.default.createElement(
|
|
15082
|
+
/* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.modalContainer }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.header }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.headerLeft }, canGoBack ? /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.headerNavRow }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: () => nav.pop(), style: styles13.backButton }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.backText }, "\u2190 Back")), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: () => nav.reset(), style: styles13.homeButton }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.homeText }, "\u{1F3E0}"))) : /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.headerTitleRow }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: handleClose, style: styles13.closeButton }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.closeText }, "\u2715"))), /* @__PURE__ */ import_react16.default.createElement(
|
|
15038
15083
|
import_react_native15.ScrollView,
|
|
15039
15084
|
{
|
|
15040
15085
|
style: styles13.content,
|
|
@@ -15135,15 +15180,27 @@ var styles13 = import_react_native15.StyleSheet.create({
|
|
|
15135
15180
|
flex: 1,
|
|
15136
15181
|
textAlign: "center"
|
|
15137
15182
|
},
|
|
15183
|
+
headerNavRow: {
|
|
15184
|
+
flexDirection: "row",
|
|
15185
|
+
alignItems: "center",
|
|
15186
|
+
gap: 8
|
|
15187
|
+
},
|
|
15138
15188
|
backButton: {
|
|
15139
15189
|
paddingVertical: 2,
|
|
15140
|
-
paddingRight:
|
|
15190
|
+
paddingRight: 4
|
|
15141
15191
|
},
|
|
15142
15192
|
backText: {
|
|
15143
15193
|
fontSize: 15,
|
|
15144
15194
|
color: colors.blue,
|
|
15145
15195
|
fontWeight: "500"
|
|
15146
15196
|
},
|
|
15197
|
+
homeButton: {
|
|
15198
|
+
paddingVertical: 2,
|
|
15199
|
+
paddingHorizontal: 6
|
|
15200
|
+
},
|
|
15201
|
+
homeText: {
|
|
15202
|
+
fontSize: 16
|
|
15203
|
+
},
|
|
15147
15204
|
closeButton: {
|
|
15148
15205
|
width: 32,
|
|
15149
15206
|
height: 32,
|
package/dist/index.mjs
CHANGED
|
@@ -11951,19 +11951,40 @@ var BugBearClient = class {
|
|
|
11951
11951
|
return { success: false, error: message };
|
|
11952
11952
|
}
|
|
11953
11953
|
}
|
|
11954
|
+
/**
|
|
11955
|
+
* Pass a test assignment — convenience wrapper around updateAssignmentStatus
|
|
11956
|
+
*/
|
|
11957
|
+
async passAssignment(assignmentId) {
|
|
11958
|
+
return this.updateAssignmentStatus(assignmentId, "passed");
|
|
11959
|
+
}
|
|
11960
|
+
/**
|
|
11961
|
+
* Fail a test assignment — convenience wrapper around updateAssignmentStatus
|
|
11962
|
+
*/
|
|
11963
|
+
async failAssignment(assignmentId) {
|
|
11964
|
+
return this.updateAssignmentStatus(assignmentId, "failed");
|
|
11965
|
+
}
|
|
11954
11966
|
/**
|
|
11955
11967
|
* Skip a test assignment with a required reason
|
|
11956
11968
|
* Marks the assignment as 'skipped' and records why it was skipped
|
|
11957
11969
|
*/
|
|
11958
11970
|
async skipAssignment(assignmentId, reason, notes) {
|
|
11971
|
+
let actualReason;
|
|
11972
|
+
let actualNotes;
|
|
11973
|
+
if (typeof reason === "object") {
|
|
11974
|
+
actualReason = reason.reason;
|
|
11975
|
+
actualNotes = reason.notes;
|
|
11976
|
+
} else {
|
|
11977
|
+
actualReason = reason;
|
|
11978
|
+
actualNotes = notes;
|
|
11979
|
+
}
|
|
11959
11980
|
try {
|
|
11960
11981
|
const updateData = {
|
|
11961
11982
|
status: "skipped",
|
|
11962
|
-
skip_reason:
|
|
11983
|
+
skip_reason: actualReason,
|
|
11963
11984
|
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
11964
11985
|
};
|
|
11965
|
-
if (
|
|
11966
|
-
updateData.notes =
|
|
11986
|
+
if (actualNotes) {
|
|
11987
|
+
updateData.notes = actualNotes;
|
|
11967
11988
|
}
|
|
11968
11989
|
const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
|
|
11969
11990
|
if (error) {
|
|
@@ -13204,7 +13225,8 @@ import {
|
|
|
13204
13225
|
Platform as Platform4,
|
|
13205
13226
|
PanResponder,
|
|
13206
13227
|
Animated,
|
|
13207
|
-
ActivityIndicator as ActivityIndicator2
|
|
13228
|
+
ActivityIndicator as ActivityIndicator2,
|
|
13229
|
+
Keyboard as Keyboard2
|
|
13208
13230
|
} from "react-native";
|
|
13209
13231
|
|
|
13210
13232
|
// src/widget/logo.ts
|
|
@@ -13461,7 +13483,7 @@ function HomeScreen({ nav }) {
|
|
|
13461
13483
|
TouchableOpacity,
|
|
13462
13484
|
{
|
|
13463
13485
|
style: styles.actionCard,
|
|
13464
|
-
onPress: () => nav.push({ name: "
|
|
13486
|
+
onPress: () => nav.push({ name: "TEST_LIST" }),
|
|
13465
13487
|
activeOpacity: 0.7
|
|
13466
13488
|
},
|
|
13467
13489
|
/* @__PURE__ */ React2.createElement(Text, { style: styles.actionIcon }, "\u2705"),
|
|
@@ -13694,7 +13716,7 @@ var styles = StyleSheet2.create({
|
|
|
13694
13716
|
|
|
13695
13717
|
// src/widget/screens/TestDetailScreen.tsx
|
|
13696
13718
|
import React3, { useState as useState2, useEffect as useEffect3, useCallback as useCallback2 } from "react";
|
|
13697
|
-
import { View as View2, Text as Text2, TouchableOpacity as TouchableOpacity2, StyleSheet as StyleSheet3, Modal, TextInput } from "react-native";
|
|
13719
|
+
import { View as View2, Text as Text2, TouchableOpacity as TouchableOpacity2, StyleSheet as StyleSheet3, Modal, TextInput, Keyboard } from "react-native";
|
|
13698
13720
|
function TestDetailScreen({ testId, nav }) {
|
|
13699
13721
|
const { client, assignments, currentAssignment, refreshAssignments, getDeviceInfo, onNavigate } = useBugBear();
|
|
13700
13722
|
const displayedAssignment = testId ? assignments.find((a) => a.id === testId) || currentAssignment : currentAssignment;
|
|
@@ -13729,12 +13751,14 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13729
13751
|
const currentIndex = displayedAssignment ? assignments.indexOf(displayedAssignment) : -1;
|
|
13730
13752
|
const handlePass = useCallback2(async () => {
|
|
13731
13753
|
if (!client || !displayedAssignment) return;
|
|
13754
|
+
Keyboard.dismiss();
|
|
13732
13755
|
await client.passAssignment(displayedAssignment.id);
|
|
13733
13756
|
await refreshAssignments();
|
|
13734
13757
|
nav.replace({ name: "TEST_FEEDBACK", status: "passed", assignmentId: displayedAssignment.id });
|
|
13735
13758
|
}, [client, displayedAssignment, refreshAssignments, nav]);
|
|
13736
13759
|
const handleFail = useCallback2(async () => {
|
|
13737
13760
|
if (!client || !displayedAssignment) return;
|
|
13761
|
+
Keyboard.dismiss();
|
|
13738
13762
|
await client.failAssignment(displayedAssignment.id);
|
|
13739
13763
|
await refreshAssignments();
|
|
13740
13764
|
nav.replace({
|
|
@@ -13748,6 +13772,7 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13748
13772
|
}, [client, displayedAssignment, refreshAssignments, nav]);
|
|
13749
13773
|
const handleSkip = useCallback2(async () => {
|
|
13750
13774
|
if (!client || !displayedAssignment || !selectedSkipReason) return;
|
|
13775
|
+
Keyboard.dismiss();
|
|
13751
13776
|
setSkipping(true);
|
|
13752
13777
|
await client.skipAssignment(displayedAssignment.id, {
|
|
13753
13778
|
reason: selectedSkipReason,
|
|
@@ -13816,7 +13841,9 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13816
13841
|
{
|
|
13817
13842
|
style: styles2.navigateButton,
|
|
13818
13843
|
onPress: () => {
|
|
13844
|
+
Keyboard.dismiss();
|
|
13819
13845
|
onNavigate(testCase.targetRoute);
|
|
13846
|
+
nav.closeWidget?.();
|
|
13820
13847
|
}
|
|
13821
13848
|
},
|
|
13822
13849
|
/* @__PURE__ */ React3.createElement(Text2, { style: styles2.navigateText }, "\u{1F9ED} Go to test location")
|
|
@@ -14957,10 +14984,29 @@ function BugBearButton({
|
|
|
14957
14984
|
}
|
|
14958
14985
|
};
|
|
14959
14986
|
const handleClose = () => {
|
|
14987
|
+
Keyboard2.dismiss();
|
|
14960
14988
|
setModalVisible(false);
|
|
14961
|
-
reset();
|
|
14962
14989
|
};
|
|
14963
|
-
const nav = {
|
|
14990
|
+
const nav = {
|
|
14991
|
+
push: (screen) => {
|
|
14992
|
+
Keyboard2.dismiss();
|
|
14993
|
+
push(screen);
|
|
14994
|
+
},
|
|
14995
|
+
pop: () => {
|
|
14996
|
+
Keyboard2.dismiss();
|
|
14997
|
+
pop();
|
|
14998
|
+
},
|
|
14999
|
+
replace: (screen) => {
|
|
15000
|
+
Keyboard2.dismiss();
|
|
15001
|
+
replace(screen);
|
|
15002
|
+
},
|
|
15003
|
+
reset: () => {
|
|
15004
|
+
Keyboard2.dismiss();
|
|
15005
|
+
reset();
|
|
15006
|
+
},
|
|
15007
|
+
canGoBack,
|
|
15008
|
+
closeWidget: handleClose
|
|
15009
|
+
};
|
|
14964
15010
|
const renderScreen = () => {
|
|
14965
15011
|
switch (currentScreen.name) {
|
|
14966
15012
|
case "HOME":
|
|
@@ -15017,7 +15063,7 @@ function BugBearButton({
|
|
|
15017
15063
|
behavior: Platform4.OS === "ios" ? "padding" : "height",
|
|
15018
15064
|
style: styles13.modalOverlay
|
|
15019
15065
|
},
|
|
15020
|
-
/* @__PURE__ */ React14.createElement(View13, { style: styles13.modalContainer }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.header }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerLeft }, canGoBack ? /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: pop, style: styles13.backButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.backText }, "\u2190 Back")) : /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerTitleRow }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: handleClose, style: styles13.closeButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.closeText }, "\u2715"))), /* @__PURE__ */ React14.createElement(
|
|
15066
|
+
/* @__PURE__ */ React14.createElement(View13, { style: styles13.modalContainer }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.header }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerLeft }, canGoBack ? /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerNavRow }, /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => nav.pop(), style: styles13.backButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.backText }, "\u2190 Back")), /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => nav.reset(), style: styles13.homeButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.homeText }, "\u{1F3E0}"))) : /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerTitleRow }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: handleClose, style: styles13.closeButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.closeText }, "\u2715"))), /* @__PURE__ */ React14.createElement(
|
|
15021
15067
|
ScrollView2,
|
|
15022
15068
|
{
|
|
15023
15069
|
style: styles13.content,
|
|
@@ -15118,15 +15164,27 @@ var styles13 = StyleSheet14.create({
|
|
|
15118
15164
|
flex: 1,
|
|
15119
15165
|
textAlign: "center"
|
|
15120
15166
|
},
|
|
15167
|
+
headerNavRow: {
|
|
15168
|
+
flexDirection: "row",
|
|
15169
|
+
alignItems: "center",
|
|
15170
|
+
gap: 8
|
|
15171
|
+
},
|
|
15121
15172
|
backButton: {
|
|
15122
15173
|
paddingVertical: 2,
|
|
15123
|
-
paddingRight:
|
|
15174
|
+
paddingRight: 4
|
|
15124
15175
|
},
|
|
15125
15176
|
backText: {
|
|
15126
15177
|
fontSize: 15,
|
|
15127
15178
|
color: colors.blue,
|
|
15128
15179
|
fontWeight: "500"
|
|
15129
15180
|
},
|
|
15181
|
+
homeButton: {
|
|
15182
|
+
paddingVertical: 2,
|
|
15183
|
+
paddingHorizontal: 6
|
|
15184
|
+
},
|
|
15185
|
+
homeText: {
|
|
15186
|
+
fontSize: 16
|
|
15187
|
+
},
|
|
15130
15188
|
closeButton: {
|
|
15131
15189
|
width: 32,
|
|
15132
15190
|
height: 32,
|