@bbearai/react-native 0.3.11 → 0.4.1
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/dist/index.d.mts +38 -3
- package/dist/index.d.ts +38 -3
- package/dist/index.js +280 -115
- package/dist/index.mjs +277 -114
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -11594,6 +11594,18 @@ var ContextCaptureManager = class {
|
|
|
11594
11594
|
}
|
|
11595
11595
|
};
|
|
11596
11596
|
var contextCapture = new ContextCaptureManager();
|
|
11597
|
+
function captureError(error, errorInfo) {
|
|
11598
|
+
return {
|
|
11599
|
+
errorMessage: error.message,
|
|
11600
|
+
errorStack: error.stack,
|
|
11601
|
+
componentStack: errorInfo?.componentStack
|
|
11602
|
+
};
|
|
11603
|
+
}
|
|
11604
|
+
var formatPgError = (e) => {
|
|
11605
|
+
if (!e || typeof e !== "object") return { raw: e };
|
|
11606
|
+
const { message, code, details, hint } = e;
|
|
11607
|
+
return { message, code, details, hint };
|
|
11608
|
+
};
|
|
11597
11609
|
var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
|
|
11598
11610
|
var getEnvVar = (key) => {
|
|
11599
11611
|
try {
|
|
@@ -11761,9 +11773,9 @@ var BugBearClient = class {
|
|
|
11761
11773
|
sort_order
|
|
11762
11774
|
)
|
|
11763
11775
|
)
|
|
11764
|
-
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true });
|
|
11776
|
+
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
|
|
11765
11777
|
if (error) {
|
|
11766
|
-
console.error("BugBear: Failed to fetch assignments", error);
|
|
11778
|
+
console.error("BugBear: Failed to fetch assignments", formatPgError(error));
|
|
11767
11779
|
return [];
|
|
11768
11780
|
}
|
|
11769
11781
|
const mapped = (data || []).map((item) => ({
|
|
@@ -11915,7 +11927,7 @@ var BugBearClient = class {
|
|
|
11915
11927
|
if (options?.testResult) {
|
|
11916
11928
|
updateData.test_result = options.testResult;
|
|
11917
11929
|
}
|
|
11918
|
-
const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
|
|
11930
|
+
const { data: updatedRow, error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId).eq("status", currentAssignment.status).select("id").maybeSingle();
|
|
11919
11931
|
if (error) {
|
|
11920
11932
|
console.error("BugBear: Failed to update assignment status", {
|
|
11921
11933
|
message: error.message,
|
|
@@ -11928,6 +11940,9 @@ var BugBearClient = class {
|
|
|
11928
11940
|
});
|
|
11929
11941
|
return { success: false, error: error.message };
|
|
11930
11942
|
}
|
|
11943
|
+
if (!updatedRow) {
|
|
11944
|
+
return { success: false, error: "Assignment status has changed. Please refresh and try again." };
|
|
11945
|
+
}
|
|
11931
11946
|
if (options?.feedback && ["passed", "failed", "blocked"].includes(status)) {
|
|
11932
11947
|
const { data: assignmentData, error: fetchError2 } = await this.supabase.from("test_assignments").select("test_case_id").eq("id", assignmentId).single();
|
|
11933
11948
|
if (fetchError2) {
|
|
@@ -11988,7 +12003,7 @@ var BugBearClient = class {
|
|
|
11988
12003
|
}
|
|
11989
12004
|
const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
|
|
11990
12005
|
if (error) {
|
|
11991
|
-
console.error("BugBear: Failed to skip assignment", error);
|
|
12006
|
+
console.error("BugBear: Failed to skip assignment", formatPgError(error));
|
|
11992
12007
|
return { success: false, error: error.message };
|
|
11993
12008
|
}
|
|
11994
12009
|
return { success: true };
|
|
@@ -12154,6 +12169,28 @@ var BugBearClient = class {
|
|
|
12154
12169
|
return null;
|
|
12155
12170
|
}
|
|
12156
12171
|
}
|
|
12172
|
+
/**
|
|
12173
|
+
* Get detailed assignment stats for the current tester via RPC.
|
|
12174
|
+
* Returns counts by status (pending, in_progress, passed, failed, blocked, skipped, total).
|
|
12175
|
+
*/
|
|
12176
|
+
async getTesterStats() {
|
|
12177
|
+
try {
|
|
12178
|
+
const testerInfo = await this.getTesterInfo();
|
|
12179
|
+
if (!testerInfo) return null;
|
|
12180
|
+
const { data, error } = await this.supabase.rpc("get_tester_stats", {
|
|
12181
|
+
p_project_id: this.config.projectId,
|
|
12182
|
+
p_tester_id: testerInfo.id
|
|
12183
|
+
});
|
|
12184
|
+
if (error) {
|
|
12185
|
+
console.error("BugBear: Failed to fetch tester stats", formatPgError(error));
|
|
12186
|
+
return null;
|
|
12187
|
+
}
|
|
12188
|
+
return data;
|
|
12189
|
+
} catch (err) {
|
|
12190
|
+
console.error("BugBear: Error fetching tester stats", err);
|
|
12191
|
+
return null;
|
|
12192
|
+
}
|
|
12193
|
+
}
|
|
12157
12194
|
/**
|
|
12158
12195
|
* Basic email format validation (defense in depth)
|
|
12159
12196
|
*/
|
|
@@ -12296,7 +12333,7 @@ var BugBearClient = class {
|
|
|
12296
12333
|
if (updates.platforms !== void 0) updateData.platforms = updates.platforms;
|
|
12297
12334
|
const { error } = await this.supabase.from("testers").update(updateData).eq("id", testerInfo.id);
|
|
12298
12335
|
if (error) {
|
|
12299
|
-
console.error("BugBear: updateTesterProfile error", error);
|
|
12336
|
+
console.error("BugBear: updateTesterProfile error", formatPgError(error));
|
|
12300
12337
|
return { success: false, error: error.message };
|
|
12301
12338
|
}
|
|
12302
12339
|
return { success: true };
|
|
@@ -12318,14 +12355,14 @@ var BugBearClient = class {
|
|
|
12318
12355
|
*/
|
|
12319
12356
|
async isQAEnabled() {
|
|
12320
12357
|
try {
|
|
12321
|
-
const { data, error } = await this.supabase.
|
|
12358
|
+
const { data, error } = await this.supabase.rpc("check_qa_enabled", {
|
|
12359
|
+
p_project_id: this.config.projectId
|
|
12360
|
+
});
|
|
12322
12361
|
if (error) {
|
|
12323
|
-
|
|
12324
|
-
console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
|
|
12325
|
-
}
|
|
12362
|
+
console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
|
|
12326
12363
|
return true;
|
|
12327
12364
|
}
|
|
12328
|
-
return data
|
|
12365
|
+
return data ?? true;
|
|
12329
12366
|
} catch (err) {
|
|
12330
12367
|
return true;
|
|
12331
12368
|
}
|
|
@@ -12355,7 +12392,7 @@ var BugBearClient = class {
|
|
|
12355
12392
|
upsert: false
|
|
12356
12393
|
});
|
|
12357
12394
|
if (error) {
|
|
12358
|
-
console.error("BugBear: Failed to upload screenshot", error);
|
|
12395
|
+
console.error("BugBear: Failed to upload screenshot", formatPgError(error));
|
|
12359
12396
|
return null;
|
|
12360
12397
|
}
|
|
12361
12398
|
const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
|
|
@@ -12386,7 +12423,7 @@ var BugBearClient = class {
|
|
|
12386
12423
|
upsert: false
|
|
12387
12424
|
});
|
|
12388
12425
|
if (error) {
|
|
12389
|
-
console.error("BugBear: Failed to upload image from URI", error);
|
|
12426
|
+
console.error("BugBear: Failed to upload image from URI", formatPgError(error));
|
|
12390
12427
|
return null;
|
|
12391
12428
|
}
|
|
12392
12429
|
const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
|
|
@@ -12461,7 +12498,7 @@ var BugBearClient = class {
|
|
|
12461
12498
|
}
|
|
12462
12499
|
const { data, error } = await query;
|
|
12463
12500
|
if (error) {
|
|
12464
|
-
console.error("BugBear: Failed to fetch fix requests", error);
|
|
12501
|
+
console.error("BugBear: Failed to fetch fix requests", formatPgError(error));
|
|
12465
12502
|
return [];
|
|
12466
12503
|
}
|
|
12467
12504
|
return (data || []).map((fr) => ({
|
|
@@ -12487,75 +12524,35 @@ var BugBearClient = class {
|
|
|
12487
12524
|
try {
|
|
12488
12525
|
const testerInfo = await this.getTesterInfo();
|
|
12489
12526
|
if (!testerInfo) return [];
|
|
12490
|
-
const { data
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
priority,
|
|
12495
|
-
is_pinned,
|
|
12496
|
-
is_resolved,
|
|
12497
|
-
last_message_at,
|
|
12498
|
-
created_at
|
|
12499
|
-
`).eq("project_id", this.config.projectId).or(`audience.eq.all,audience_tester_ids.cs.{${testerInfo.id}}`).order("is_pinned", { ascending: false }).order("last_message_at", { ascending: false });
|
|
12527
|
+
const { data, error } = await this.supabase.rpc("get_threads_with_unread", {
|
|
12528
|
+
p_project_id: this.config.projectId,
|
|
12529
|
+
p_tester_id: testerInfo.id
|
|
12530
|
+
});
|
|
12500
12531
|
if (error) {
|
|
12501
|
-
console.error("BugBear: Failed to fetch threads", error);
|
|
12532
|
+
console.error("BugBear: Failed to fetch threads via RPC", formatPgError(error));
|
|
12502
12533
|
return [];
|
|
12503
12534
|
}
|
|
12504
|
-
if (!
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
}
|
|
12524
|
-
}
|
|
12525
|
-
const unreadCounts = await Promise.all(
|
|
12526
|
-
threads.map(async (thread) => {
|
|
12527
|
-
const readStatus = readStatusMap.get(thread.id);
|
|
12528
|
-
const lastReadAt = readStatus?.last_read_at || "1970-01-01T00:00:00Z";
|
|
12529
|
-
const { count, error: countError } = await this.supabase.from("discussion_messages").select("*", { count: "exact", head: true }).eq("thread_id", thread.id).gt("created_at", lastReadAt);
|
|
12530
|
-
return { threadId: thread.id, count: countError ? 0 : count || 0 };
|
|
12531
|
-
})
|
|
12532
|
-
);
|
|
12533
|
-
const unreadCountMap = new Map(
|
|
12534
|
-
unreadCounts.map((uc) => [uc.threadId, uc.count])
|
|
12535
|
-
);
|
|
12536
|
-
return threads.map((thread) => {
|
|
12537
|
-
const lastMsg = lastMessageMap.get(thread.id);
|
|
12538
|
-
return {
|
|
12539
|
-
id: thread.id,
|
|
12540
|
-
subject: thread.subject,
|
|
12541
|
-
threadType: thread.thread_type,
|
|
12542
|
-
priority: thread.priority,
|
|
12543
|
-
isPinned: thread.is_pinned,
|
|
12544
|
-
isResolved: thread.is_resolved,
|
|
12545
|
-
lastMessageAt: thread.last_message_at,
|
|
12546
|
-
createdAt: thread.created_at,
|
|
12547
|
-
unreadCount: unreadCountMap.get(thread.id) || 0,
|
|
12548
|
-
lastMessage: lastMsg ? {
|
|
12549
|
-
id: lastMsg.id,
|
|
12550
|
-
threadId: lastMsg.thread_id,
|
|
12551
|
-
senderType: lastMsg.sender_type,
|
|
12552
|
-
senderName: lastMsg.sender_name,
|
|
12553
|
-
content: lastMsg.content,
|
|
12554
|
-
createdAt: lastMsg.created_at,
|
|
12555
|
-
attachments: lastMsg.attachments || []
|
|
12556
|
-
} : void 0
|
|
12557
|
-
};
|
|
12558
|
-
});
|
|
12535
|
+
if (!data || data.length === 0) return [];
|
|
12536
|
+
return data.map((row) => ({
|
|
12537
|
+
id: row.thread_id,
|
|
12538
|
+
subject: row.thread_subject,
|
|
12539
|
+
threadType: row.thread_type,
|
|
12540
|
+
priority: row.thread_priority,
|
|
12541
|
+
isPinned: row.is_pinned,
|
|
12542
|
+
isResolved: row.is_resolved,
|
|
12543
|
+
lastMessageAt: row.last_message_at,
|
|
12544
|
+
createdAt: row.created_at,
|
|
12545
|
+
unreadCount: Number(row.unread_count) || 0,
|
|
12546
|
+
lastMessage: row.last_message_preview ? {
|
|
12547
|
+
id: "",
|
|
12548
|
+
threadId: row.thread_id,
|
|
12549
|
+
senderType: row.last_message_sender_type || "system",
|
|
12550
|
+
senderName: row.last_message_sender_name || "",
|
|
12551
|
+
content: row.last_message_preview,
|
|
12552
|
+
createdAt: row.last_message_at,
|
|
12553
|
+
attachments: []
|
|
12554
|
+
} : void 0
|
|
12555
|
+
}));
|
|
12559
12556
|
} catch (err) {
|
|
12560
12557
|
console.error("BugBear: Error fetching threads", err);
|
|
12561
12558
|
return [];
|
|
@@ -12574,9 +12571,9 @@ var BugBearClient = class {
|
|
|
12574
12571
|
content,
|
|
12575
12572
|
created_at,
|
|
12576
12573
|
attachments
|
|
12577
|
-
`).eq("thread_id", threadId).order("created_at", { ascending: true });
|
|
12574
|
+
`).eq("thread_id", threadId).order("created_at", { ascending: true }).limit(200);
|
|
12578
12575
|
if (error) {
|
|
12579
|
-
console.error("BugBear: Failed to fetch messages", error);
|
|
12576
|
+
console.error("BugBear: Failed to fetch messages", formatPgError(error));
|
|
12580
12577
|
return [];
|
|
12581
12578
|
}
|
|
12582
12579
|
return (data || []).map((msg) => ({
|
|
@@ -12620,10 +12617,9 @@ var BugBearClient = class {
|
|
|
12620
12617
|
}
|
|
12621
12618
|
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
12622
12619
|
if (error) {
|
|
12623
|
-
console.error("BugBear: Failed to send message", error);
|
|
12620
|
+
console.error("BugBear: Failed to send message", formatPgError(error));
|
|
12624
12621
|
return false;
|
|
12625
12622
|
}
|
|
12626
|
-
await this.supabase.from("discussion_threads").update({ last_message_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", threadId);
|
|
12627
12623
|
await this.markThreadAsRead(threadId);
|
|
12628
12624
|
return true;
|
|
12629
12625
|
} catch (err) {
|
|
@@ -12737,7 +12733,7 @@ var BugBearClient = class {
|
|
|
12737
12733
|
p_platform: options.platform || null
|
|
12738
12734
|
});
|
|
12739
12735
|
if (error) {
|
|
12740
|
-
console.error("BugBear: Failed to start session", error);
|
|
12736
|
+
console.error("BugBear: Failed to start session", formatPgError(error));
|
|
12741
12737
|
return { success: false, error: error.message };
|
|
12742
12738
|
}
|
|
12743
12739
|
const session = await this.getSession(data);
|
|
@@ -12762,7 +12758,7 @@ var BugBearClient = class {
|
|
|
12762
12758
|
p_routes_covered: options.routesCovered || null
|
|
12763
12759
|
});
|
|
12764
12760
|
if (error) {
|
|
12765
|
-
console.error("BugBear: Failed to end session", error);
|
|
12761
|
+
console.error("BugBear: Failed to end session", formatPgError(error));
|
|
12766
12762
|
return { success: false, error: error.message };
|
|
12767
12763
|
}
|
|
12768
12764
|
const session = this.transformSession(data);
|
|
@@ -12810,7 +12806,7 @@ var BugBearClient = class {
|
|
|
12810
12806
|
if (!testerInfo) return [];
|
|
12811
12807
|
const { data, error } = await this.supabase.from("qa_sessions").select("*").eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).order("started_at", { ascending: false }).limit(limit);
|
|
12812
12808
|
if (error) {
|
|
12813
|
-
console.error("BugBear: Failed to fetch session history", error);
|
|
12809
|
+
console.error("BugBear: Failed to fetch session history", formatPgError(error));
|
|
12814
12810
|
return [];
|
|
12815
12811
|
}
|
|
12816
12812
|
return (data || []).map((s) => this.transformSession(s));
|
|
@@ -12838,7 +12834,7 @@ var BugBearClient = class {
|
|
|
12838
12834
|
p_app_context: options.appContext || null
|
|
12839
12835
|
});
|
|
12840
12836
|
if (error) {
|
|
12841
|
-
console.error("BugBear: Failed to add finding", error);
|
|
12837
|
+
console.error("BugBear: Failed to add finding", formatPgError(error));
|
|
12842
12838
|
return { success: false, error: error.message };
|
|
12843
12839
|
}
|
|
12844
12840
|
const finding = this.transformFinding(data);
|
|
@@ -12854,9 +12850,9 @@ var BugBearClient = class {
|
|
|
12854
12850
|
*/
|
|
12855
12851
|
async getSessionFindings(sessionId) {
|
|
12856
12852
|
try {
|
|
12857
|
-
const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true });
|
|
12853
|
+
const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true }).limit(100);
|
|
12858
12854
|
if (error) {
|
|
12859
|
-
console.error("BugBear: Failed to fetch findings", error);
|
|
12855
|
+
console.error("BugBear: Failed to fetch findings", formatPgError(error));
|
|
12860
12856
|
return [];
|
|
12861
12857
|
}
|
|
12862
12858
|
return (data || []).map((f) => this.transformFinding(f));
|
|
@@ -12874,7 +12870,7 @@ var BugBearClient = class {
|
|
|
12874
12870
|
p_finding_id: findingId
|
|
12875
12871
|
});
|
|
12876
12872
|
if (error) {
|
|
12877
|
-
console.error("BugBear: Failed to convert finding", error);
|
|
12873
|
+
console.error("BugBear: Failed to convert finding", formatPgError(error));
|
|
12878
12874
|
return { success: false, error: error.message };
|
|
12879
12875
|
}
|
|
12880
12876
|
return { success: true, bugId: data };
|
|
@@ -12895,7 +12891,7 @@ var BugBearClient = class {
|
|
|
12895
12891
|
dismissed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
12896
12892
|
}).eq("id", findingId);
|
|
12897
12893
|
if (error) {
|
|
12898
|
-
console.error("BugBear: Failed to dismiss finding", error);
|
|
12894
|
+
console.error("BugBear: Failed to dismiss finding", formatPgError(error));
|
|
12899
12895
|
return { success: false, error: error.message };
|
|
12900
12896
|
}
|
|
12901
12897
|
return { success: true };
|
|
@@ -12998,7 +12994,8 @@ var BugBearContext = createContext({
|
|
|
12998
12994
|
updateTesterProfile: async () => ({ success: false }),
|
|
12999
12995
|
refreshTesterInfo: async () => {
|
|
13000
12996
|
},
|
|
13001
|
-
dashboardUrl: void 0
|
|
12997
|
+
dashboardUrl: void 0,
|
|
12998
|
+
onError: void 0
|
|
13002
12999
|
});
|
|
13003
13000
|
function useBugBear() {
|
|
13004
13001
|
return useContext(BugBearContext);
|
|
@@ -13147,6 +13144,9 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13147
13144
|
}
|
|
13148
13145
|
} catch (err) {
|
|
13149
13146
|
console.error("BugBear: Init error", err);
|
|
13147
|
+
if (err instanceof Error) {
|
|
13148
|
+
config.onError?.(err, { phase: "init" });
|
|
13149
|
+
}
|
|
13150
13150
|
} finally {
|
|
13151
13151
|
setIsLoading(false);
|
|
13152
13152
|
}
|
|
@@ -13204,7 +13204,8 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13204
13204
|
refreshTesterStatus,
|
|
13205
13205
|
updateTesterProfile,
|
|
13206
13206
|
refreshTesterInfo,
|
|
13207
|
-
dashboardUrl: config.dashboardUrl
|
|
13207
|
+
dashboardUrl: config.dashboardUrl,
|
|
13208
|
+
onError: config.onError
|
|
13208
13209
|
}
|
|
13209
13210
|
},
|
|
13210
13211
|
children
|
|
@@ -13729,6 +13730,7 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13729
13730
|
const [selectedSkipReason, setSelectedSkipReason] = useState2(null);
|
|
13730
13731
|
const [skipNotes, setSkipNotes] = useState2("");
|
|
13731
13732
|
const [skipping, setSkipping] = useState2(false);
|
|
13733
|
+
const [isSubmitting, setIsSubmitting] = useState2(false);
|
|
13732
13734
|
useEffect3(() => {
|
|
13733
13735
|
setCriteriaResults({});
|
|
13734
13736
|
setShowSteps(true);
|
|
@@ -13751,26 +13753,39 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13751
13753
|
const allTests = assignments;
|
|
13752
13754
|
const currentIndex = displayedAssignment ? assignments.indexOf(displayedAssignment) : -1;
|
|
13753
13755
|
const handlePass = useCallback2(async () => {
|
|
13754
|
-
if (!client || !displayedAssignment) return;
|
|
13756
|
+
if (!client || !displayedAssignment || isSubmitting) return;
|
|
13755
13757
|
Keyboard.dismiss();
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13759
|
-
|
|
13758
|
+
setIsSubmitting(true);
|
|
13759
|
+
try {
|
|
13760
|
+
await client.passAssignment(displayedAssignment.id);
|
|
13761
|
+
await refreshAssignments();
|
|
13762
|
+
nav.replace({ name: "TEST_FEEDBACK", status: "passed", assignmentId: displayedAssignment.id });
|
|
13763
|
+
} finally {
|
|
13764
|
+
setIsSubmitting(false);
|
|
13765
|
+
}
|
|
13766
|
+
}, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
|
|
13760
13767
|
const handleFail = useCallback2(async () => {
|
|
13761
|
-
if (!client || !displayedAssignment) return;
|
|
13768
|
+
if (!client || !displayedAssignment || isSubmitting) return;
|
|
13762
13769
|
Keyboard.dismiss();
|
|
13763
|
-
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
type: "test_fail",
|
|
13769
|
-
assignmentId: displayedAssignment.id,
|
|
13770
|
-
testCaseId: displayedAssignment.testCase.id
|
|
13770
|
+
setIsSubmitting(true);
|
|
13771
|
+
try {
|
|
13772
|
+
const result = await client.failAssignment(displayedAssignment.id);
|
|
13773
|
+
if (!result.success) {
|
|
13774
|
+
console.error("BugBear: Failed to mark assignment as failed", result.error);
|
|
13771
13775
|
}
|
|
13772
|
-
|
|
13773
|
-
|
|
13776
|
+
await refreshAssignments();
|
|
13777
|
+
nav.replace({
|
|
13778
|
+
name: "REPORT",
|
|
13779
|
+
prefill: {
|
|
13780
|
+
type: "test_fail",
|
|
13781
|
+
assignmentId: displayedAssignment.id,
|
|
13782
|
+
testCaseId: displayedAssignment.testCase.id
|
|
13783
|
+
}
|
|
13784
|
+
});
|
|
13785
|
+
} finally {
|
|
13786
|
+
setIsSubmitting(false);
|
|
13787
|
+
}
|
|
13788
|
+
}, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
|
|
13774
13789
|
const handleSkip = useCallback2(async () => {
|
|
13775
13790
|
if (!client || !displayedAssignment || !selectedSkipReason) return;
|
|
13776
13791
|
Keyboard.dismiss();
|
|
@@ -13851,7 +13866,7 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13851
13866
|
}
|
|
13852
13867
|
},
|
|
13853
13868
|
/* @__PURE__ */ React3.createElement(Text2, { style: styles2.navigateText }, "\u{1F9ED} Go to test location")
|
|
13854
|
-
), /* @__PURE__ */ React3.createElement(TouchableOpacity2, { onPress: () => setShowDetails(!showDetails), style: styles2.detailsToggle }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ React3.createElement(View2, { style: styles2.detailsSection }, testCase.key && /* @__PURE__ */ React3.createElement(Text2, { style: styles2.detailMeta }, testCase.key, " \xB7 ", testCase.priority, " \xB7 ", info.name), testCase.description && /* @__PURE__ */ React3.createElement(Text2, { style: styles2.detailDesc }, testCase.description), testCase.group && /* @__PURE__ */ React3.createElement(View2, { style: styles2.folderProgress }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.folderName }, "\u{1F4C1} ", testCase.group.name))), /* @__PURE__ */ React3.createElement(View2, { style: styles2.actionButtons }, /* @__PURE__ */ React3.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.failBtn], onPress: handleFail }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.failBtnText }, "Fail")), /* @__PURE__ */ React3.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.skipBtn], onPress: () => setShowSkipModal(true) }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.skipBtnText }, "Skip")), /* @__PURE__ */ React3.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.passBtn], onPress: handlePass }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.passBtnText }, "Pass"))), /* @__PURE__ */ React3.createElement(Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ React3.createElement(View2, { style: styles2.modalOverlay }, /* @__PURE__ */ React3.createElement(View2, { style: styles2.modalContent }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.modalTitle }, "Skip this test?"), /* @__PURE__ */ React3.createElement(Text2, { style: styles2.modalSubtitle }, "Select a reason:"), [
|
|
13869
|
+
), /* @__PURE__ */ React3.createElement(TouchableOpacity2, { onPress: () => setShowDetails(!showDetails), style: styles2.detailsToggle }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ React3.createElement(View2, { style: styles2.detailsSection }, testCase.key && /* @__PURE__ */ React3.createElement(Text2, { style: styles2.detailMeta }, testCase.key, " \xB7 ", testCase.priority, " \xB7 ", info.name), testCase.description && /* @__PURE__ */ React3.createElement(Text2, { style: styles2.detailDesc }, testCase.description), testCase.group && /* @__PURE__ */ React3.createElement(View2, { style: styles2.folderProgress }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.folderName }, "\u{1F4C1} ", testCase.group.name))), /* @__PURE__ */ React3.createElement(View2, { style: styles2.actionButtons }, /* @__PURE__ */ React3.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.failBtnText }, isSubmitting ? "Failing..." : "Fail")), /* @__PURE__ */ React3.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.skipBtnText }, "Skip")), /* @__PURE__ */ React3.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.passBtnText }, isSubmitting ? "Passing..." : "Pass"))), /* @__PURE__ */ React3.createElement(Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ React3.createElement(View2, { style: styles2.modalOverlay }, /* @__PURE__ */ React3.createElement(View2, { style: styles2.modalContent }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.modalTitle }, "Skip this test?"), /* @__PURE__ */ React3.createElement(Text2, { style: styles2.modalSubtitle }, "Select a reason:"), [
|
|
13855
13870
|
{ reason: "blocked", label: "\u{1F6AB} Blocked by a bug" },
|
|
13856
13871
|
{ reason: "not_ready", label: "\u{1F6A7} Feature not ready" },
|
|
13857
13872
|
{ reason: "dependency", label: "\u{1F517} Needs another test first" },
|
|
@@ -14421,6 +14436,7 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14421
14436
|
const [submitting, setSubmitting] = useState6(false);
|
|
14422
14437
|
const [error, setError] = useState6(null);
|
|
14423
14438
|
const images = useImageAttachments(uploadImage, 5, "screenshots");
|
|
14439
|
+
const isRetestFailure = prefill?.type === "test_fail";
|
|
14424
14440
|
const isBugType = reportType === "bug" || reportType === "test_fail";
|
|
14425
14441
|
const handleSubmit = async () => {
|
|
14426
14442
|
if (!client || !description.trim()) return;
|
|
@@ -14460,7 +14476,50 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14460
14476
|
setSubmitting(false);
|
|
14461
14477
|
}
|
|
14462
14478
|
};
|
|
14463
|
-
return /* @__PURE__ */ React8.createElement(View7, null, /* @__PURE__ */ React8.createElement(Text7, { style:
|
|
14479
|
+
return /* @__PURE__ */ React8.createElement(View7, null, isRetestFailure ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(View7, { style: styles7.retestBanner }, /* @__PURE__ */ React8.createElement(Text7, { style: styles7.retestIcon }, "\u{1F504}"), /* @__PURE__ */ React8.createElement(View7, null, /* @__PURE__ */ React8.createElement(Text7, { style: styles7.retestTitle }, "Bug Still Present"), /* @__PURE__ */ React8.createElement(Text7, { style: styles7.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ React8.createElement(View7, { style: styles7.section }, /* @__PURE__ */ React8.createElement(Text7, { style: shared.label }, "Severity"), /* @__PURE__ */ React8.createElement(View7, { style: styles7.severityRow }, [
|
|
14480
|
+
{ sev: "critical", color: "#ef4444" },
|
|
14481
|
+
{ sev: "high", color: "#f97316" },
|
|
14482
|
+
{ sev: "medium", color: "#eab308" },
|
|
14483
|
+
{ sev: "low", color: "#6b7280" }
|
|
14484
|
+
].map(({ sev, color }) => /* @__PURE__ */ React8.createElement(
|
|
14485
|
+
TouchableOpacity7,
|
|
14486
|
+
{
|
|
14487
|
+
key: sev,
|
|
14488
|
+
style: [styles7.sevButton, severity === sev && { backgroundColor: `${color}30`, borderColor: color }],
|
|
14489
|
+
onPress: () => setSeverity(sev)
|
|
14490
|
+
},
|
|
14491
|
+
/* @__PURE__ */ React8.createElement(Text7, { style: [styles7.sevText, severity === sev && { color }] }, sev)
|
|
14492
|
+
)))), /* @__PURE__ */ React8.createElement(View7, { style: styles7.section }, /* @__PURE__ */ React8.createElement(Text7, { style: shared.label }, "What went wrong?"), /* @__PURE__ */ React8.createElement(
|
|
14493
|
+
TextInput3,
|
|
14494
|
+
{
|
|
14495
|
+
style: styles7.descInput,
|
|
14496
|
+
value: description,
|
|
14497
|
+
onChangeText: setDescription,
|
|
14498
|
+
placeholder: "Describe what you observed. What still doesn't work?",
|
|
14499
|
+
placeholderTextColor: colors.textMuted,
|
|
14500
|
+
multiline: true,
|
|
14501
|
+
numberOfLines: 4,
|
|
14502
|
+
textAlignVertical: "top"
|
|
14503
|
+
}
|
|
14504
|
+
)), /* @__PURE__ */ React8.createElement(
|
|
14505
|
+
ImagePickerButtons,
|
|
14506
|
+
{
|
|
14507
|
+
images: images.images,
|
|
14508
|
+
maxImages: 5,
|
|
14509
|
+
onPickGallery: images.pickFromGallery,
|
|
14510
|
+
onPickCamera: images.pickFromCamera,
|
|
14511
|
+
onRemove: images.removeImage,
|
|
14512
|
+
label: "Attachments (optional)"
|
|
14513
|
+
}
|
|
14514
|
+
), error && /* @__PURE__ */ React8.createElement(View7, { style: styles7.errorBanner }, /* @__PURE__ */ React8.createElement(Text7, { style: styles7.errorText }, error)), /* @__PURE__ */ React8.createElement(
|
|
14515
|
+
TouchableOpacity7,
|
|
14516
|
+
{
|
|
14517
|
+
style: [shared.primaryButton, styles7.retestSubmitButton, (!description.trim() || submitting || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
|
|
14518
|
+
onPress: handleSubmit,
|
|
14519
|
+
disabled: !description.trim() || submitting || images.isUploading
|
|
14520
|
+
},
|
|
14521
|
+
/* @__PURE__ */ React8.createElement(Text7, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Failed Retest")
|
|
14522
|
+
)) : /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text7, { style: shared.label }, "What are you reporting?"), /* @__PURE__ */ React8.createElement(View7, { style: styles7.typeRow }, [
|
|
14464
14523
|
{ type: "bug", label: "Bug", icon: "\u{1F41B}" },
|
|
14465
14524
|
{ type: "feedback", label: "Feedback", icon: "\u{1F4A1}" },
|
|
14466
14525
|
{ type: "suggestion", label: "Idea", icon: "\u2728" }
|
|
@@ -14525,7 +14584,7 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14525
14584
|
disabled: !description.trim() || submitting || images.isUploading
|
|
14526
14585
|
},
|
|
14527
14586
|
/* @__PURE__ */ React8.createElement(Text7, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Report")
|
|
14528
|
-
));
|
|
14587
|
+
)));
|
|
14529
14588
|
}
|
|
14530
14589
|
var styles7 = StyleSheet8.create({
|
|
14531
14590
|
typeRow: { flexDirection: "row", gap: 10, marginBottom: 20 },
|
|
@@ -14542,7 +14601,12 @@ var styles7 = StyleSheet8.create({
|
|
|
14542
14601
|
screenInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 13, color: colors.textPrimary },
|
|
14543
14602
|
screenHint: { fontSize: 11, color: colors.textMuted, marginTop: 4 },
|
|
14544
14603
|
errorBanner: { backgroundColor: "#7f1d1d", borderWidth: 1, borderColor: "#991b1b", borderRadius: 8, padding: 12, marginTop: 16 },
|
|
14545
|
-
errorText: { fontSize: 13, color: "#fca5a5", lineHeight: 18 }
|
|
14604
|
+
errorText: { fontSize: 13, color: "#fca5a5", lineHeight: 18 },
|
|
14605
|
+
retestBanner: { flexDirection: "row", alignItems: "center", gap: 10, backgroundColor: "#422006", borderWidth: 1, borderColor: "#854d0e", borderRadius: 10, paddingVertical: 12, paddingHorizontal: 14, marginBottom: 20 },
|
|
14606
|
+
retestIcon: { fontSize: 16 },
|
|
14607
|
+
retestTitle: { fontSize: 15, fontWeight: "600", color: "#fbbf24", lineHeight: 20 },
|
|
14608
|
+
retestSubtitle: { fontSize: 12, color: "#d97706", lineHeight: 16 },
|
|
14609
|
+
retestSubmitButton: { backgroundColor: "#b45309" }
|
|
14546
14610
|
});
|
|
14547
14611
|
|
|
14548
14612
|
// src/widget/screens/ReportSuccessScreen.tsx
|
|
@@ -15245,8 +15309,107 @@ var styles13 = StyleSheet14.create({
|
|
|
15245
15309
|
fontSize: 14
|
|
15246
15310
|
}
|
|
15247
15311
|
});
|
|
15312
|
+
|
|
15313
|
+
// src/BugBearErrorBoundary.tsx
|
|
15314
|
+
import React15, { Component } from "react";
|
|
15315
|
+
import { View as View14, Text as Text14, TouchableOpacity as TouchableOpacity13, StyleSheet as StyleSheet15 } from "react-native";
|
|
15316
|
+
var BugBearErrorBoundary = class extends Component {
|
|
15317
|
+
constructor(props) {
|
|
15318
|
+
super(props);
|
|
15319
|
+
this.reset = () => {
|
|
15320
|
+
this.setState({
|
|
15321
|
+
hasError: false,
|
|
15322
|
+
error: null,
|
|
15323
|
+
errorInfo: null
|
|
15324
|
+
});
|
|
15325
|
+
};
|
|
15326
|
+
this.state = {
|
|
15327
|
+
hasError: false,
|
|
15328
|
+
error: null,
|
|
15329
|
+
errorInfo: null
|
|
15330
|
+
};
|
|
15331
|
+
}
|
|
15332
|
+
static getDerivedStateFromError(error) {
|
|
15333
|
+
return { hasError: true, error };
|
|
15334
|
+
}
|
|
15335
|
+
componentDidCatch(error, errorInfo) {
|
|
15336
|
+
this.setState({ errorInfo });
|
|
15337
|
+
captureError(error, {
|
|
15338
|
+
componentStack: errorInfo.componentStack ?? void 0
|
|
15339
|
+
});
|
|
15340
|
+
console.error("BugBear: Error caught by ErrorBoundary", {
|
|
15341
|
+
error: error.message,
|
|
15342
|
+
componentStack: errorInfo.componentStack?.slice(0, 500)
|
|
15343
|
+
});
|
|
15344
|
+
this.props.onError?.(error, errorInfo);
|
|
15345
|
+
this.props.errorReporter?.(error, {
|
|
15346
|
+
componentStack: errorInfo.componentStack ?? void 0,
|
|
15347
|
+
source: "BugBearErrorBoundary"
|
|
15348
|
+
});
|
|
15349
|
+
}
|
|
15350
|
+
render() {
|
|
15351
|
+
const { hasError, error } = this.state;
|
|
15352
|
+
const { children, fallback } = this.props;
|
|
15353
|
+
if (hasError && error) {
|
|
15354
|
+
if (typeof fallback === "function") {
|
|
15355
|
+
return fallback(error, this.reset);
|
|
15356
|
+
}
|
|
15357
|
+
if (fallback) {
|
|
15358
|
+
return fallback;
|
|
15359
|
+
}
|
|
15360
|
+
return /* @__PURE__ */ React15.createElement(View14, { style: styles14.container }, /* @__PURE__ */ React15.createElement(Text14, { style: styles14.title }, "Something went wrong"), /* @__PURE__ */ React15.createElement(Text14, { style: styles14.message }, error.message), /* @__PURE__ */ React15.createElement(TouchableOpacity13, { style: styles14.button, onPress: this.reset }, /* @__PURE__ */ React15.createElement(Text14, { style: styles14.buttonText }, "Try Again")), /* @__PURE__ */ React15.createElement(Text14, { style: styles14.caption }, "The error has been captured by BugBear"));
|
|
15361
|
+
}
|
|
15362
|
+
return children;
|
|
15363
|
+
}
|
|
15364
|
+
};
|
|
15365
|
+
function useErrorContext() {
|
|
15366
|
+
return {
|
|
15367
|
+
captureError,
|
|
15368
|
+
getEnhancedContext: () => contextCapture.getEnhancedContext()
|
|
15369
|
+
};
|
|
15370
|
+
}
|
|
15371
|
+
var styles14 = StyleSheet15.create({
|
|
15372
|
+
container: {
|
|
15373
|
+
padding: 20,
|
|
15374
|
+
margin: 20,
|
|
15375
|
+
backgroundColor: "#fef2f2",
|
|
15376
|
+
borderWidth: 1,
|
|
15377
|
+
borderColor: "#fecaca",
|
|
15378
|
+
borderRadius: 8
|
|
15379
|
+
},
|
|
15380
|
+
title: {
|
|
15381
|
+
fontSize: 16,
|
|
15382
|
+
fontWeight: "600",
|
|
15383
|
+
color: "#991b1b",
|
|
15384
|
+
marginBottom: 8
|
|
15385
|
+
},
|
|
15386
|
+
message: {
|
|
15387
|
+
fontSize: 14,
|
|
15388
|
+
color: "#7f1d1d",
|
|
15389
|
+
marginBottom: 12
|
|
15390
|
+
},
|
|
15391
|
+
button: {
|
|
15392
|
+
backgroundColor: "#dc2626",
|
|
15393
|
+
paddingHorizontal: 16,
|
|
15394
|
+
paddingVertical: 8,
|
|
15395
|
+
borderRadius: 6,
|
|
15396
|
+
alignSelf: "flex-start"
|
|
15397
|
+
},
|
|
15398
|
+
buttonText: {
|
|
15399
|
+
color: "white",
|
|
15400
|
+
fontSize: 14,
|
|
15401
|
+
fontWeight: "500"
|
|
15402
|
+
},
|
|
15403
|
+
caption: {
|
|
15404
|
+
fontSize: 12,
|
|
15405
|
+
color: "#9ca3af",
|
|
15406
|
+
marginTop: 12
|
|
15407
|
+
}
|
|
15408
|
+
});
|
|
15248
15409
|
export {
|
|
15249
15410
|
BugBearButton,
|
|
15411
|
+
BugBearErrorBoundary,
|
|
15250
15412
|
BugBearProvider,
|
|
15251
|
-
useBugBear
|
|
15413
|
+
useBugBear,
|
|
15414
|
+
useErrorContext
|
|
15252
15415
|
};
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bbearai/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "BugBear React Native components for mobile apps",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
10
11
|
"import": "./dist/index.mjs",
|
|
11
|
-
"require": "./dist/index.js"
|
|
12
|
-
"types": "./dist/index.d.ts"
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@bbearai/core": "^0.
|
|
52
|
+
"@bbearai/core": "^0.3.0",
|
|
53
53
|
"@eslint/js": "^9.39.2",
|
|
54
54
|
"@types/react": "^18.2.0",
|
|
55
55
|
"eslint": "^9.39.2",
|