@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.js
CHANGED
|
@@ -31,8 +31,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
BugBearButton: () => BugBearButton,
|
|
34
|
+
BugBearErrorBoundary: () => BugBearErrorBoundary,
|
|
34
35
|
BugBearProvider: () => BugBearProvider,
|
|
35
|
-
useBugBear: () => useBugBear
|
|
36
|
+
useBugBear: () => useBugBear,
|
|
37
|
+
useErrorContext: () => useErrorContext
|
|
36
38
|
});
|
|
37
39
|
module.exports = __toCommonJS(index_exports);
|
|
38
40
|
|
|
@@ -11625,6 +11627,18 @@ var ContextCaptureManager = class {
|
|
|
11625
11627
|
}
|
|
11626
11628
|
};
|
|
11627
11629
|
var contextCapture = new ContextCaptureManager();
|
|
11630
|
+
function captureError(error, errorInfo) {
|
|
11631
|
+
return {
|
|
11632
|
+
errorMessage: error.message,
|
|
11633
|
+
errorStack: error.stack,
|
|
11634
|
+
componentStack: errorInfo?.componentStack
|
|
11635
|
+
};
|
|
11636
|
+
}
|
|
11637
|
+
var formatPgError = (e) => {
|
|
11638
|
+
if (!e || typeof e !== "object") return { raw: e };
|
|
11639
|
+
const { message, code, details, hint } = e;
|
|
11640
|
+
return { message, code, details, hint };
|
|
11641
|
+
};
|
|
11628
11642
|
var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
|
|
11629
11643
|
var getEnvVar = (key) => {
|
|
11630
11644
|
try {
|
|
@@ -11792,9 +11806,9 @@ var BugBearClient = class {
|
|
|
11792
11806
|
sort_order
|
|
11793
11807
|
)
|
|
11794
11808
|
)
|
|
11795
|
-
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true });
|
|
11809
|
+
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
|
|
11796
11810
|
if (error) {
|
|
11797
|
-
console.error("BugBear: Failed to fetch assignments", error);
|
|
11811
|
+
console.error("BugBear: Failed to fetch assignments", formatPgError(error));
|
|
11798
11812
|
return [];
|
|
11799
11813
|
}
|
|
11800
11814
|
const mapped = (data || []).map((item) => ({
|
|
@@ -11946,7 +11960,7 @@ var BugBearClient = class {
|
|
|
11946
11960
|
if (options?.testResult) {
|
|
11947
11961
|
updateData.test_result = options.testResult;
|
|
11948
11962
|
}
|
|
11949
|
-
const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
|
|
11963
|
+
const { data: updatedRow, error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId).eq("status", currentAssignment.status).select("id").maybeSingle();
|
|
11950
11964
|
if (error) {
|
|
11951
11965
|
console.error("BugBear: Failed to update assignment status", {
|
|
11952
11966
|
message: error.message,
|
|
@@ -11959,6 +11973,9 @@ var BugBearClient = class {
|
|
|
11959
11973
|
});
|
|
11960
11974
|
return { success: false, error: error.message };
|
|
11961
11975
|
}
|
|
11976
|
+
if (!updatedRow) {
|
|
11977
|
+
return { success: false, error: "Assignment status has changed. Please refresh and try again." };
|
|
11978
|
+
}
|
|
11962
11979
|
if (options?.feedback && ["passed", "failed", "blocked"].includes(status)) {
|
|
11963
11980
|
const { data: assignmentData, error: fetchError2 } = await this.supabase.from("test_assignments").select("test_case_id").eq("id", assignmentId).single();
|
|
11964
11981
|
if (fetchError2) {
|
|
@@ -12019,7 +12036,7 @@ var BugBearClient = class {
|
|
|
12019
12036
|
}
|
|
12020
12037
|
const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
|
|
12021
12038
|
if (error) {
|
|
12022
|
-
console.error("BugBear: Failed to skip assignment", error);
|
|
12039
|
+
console.error("BugBear: Failed to skip assignment", formatPgError(error));
|
|
12023
12040
|
return { success: false, error: error.message };
|
|
12024
12041
|
}
|
|
12025
12042
|
return { success: true };
|
|
@@ -12185,6 +12202,28 @@ var BugBearClient = class {
|
|
|
12185
12202
|
return null;
|
|
12186
12203
|
}
|
|
12187
12204
|
}
|
|
12205
|
+
/**
|
|
12206
|
+
* Get detailed assignment stats for the current tester via RPC.
|
|
12207
|
+
* Returns counts by status (pending, in_progress, passed, failed, blocked, skipped, total).
|
|
12208
|
+
*/
|
|
12209
|
+
async getTesterStats() {
|
|
12210
|
+
try {
|
|
12211
|
+
const testerInfo = await this.getTesterInfo();
|
|
12212
|
+
if (!testerInfo) return null;
|
|
12213
|
+
const { data, error } = await this.supabase.rpc("get_tester_stats", {
|
|
12214
|
+
p_project_id: this.config.projectId,
|
|
12215
|
+
p_tester_id: testerInfo.id
|
|
12216
|
+
});
|
|
12217
|
+
if (error) {
|
|
12218
|
+
console.error("BugBear: Failed to fetch tester stats", formatPgError(error));
|
|
12219
|
+
return null;
|
|
12220
|
+
}
|
|
12221
|
+
return data;
|
|
12222
|
+
} catch (err) {
|
|
12223
|
+
console.error("BugBear: Error fetching tester stats", err);
|
|
12224
|
+
return null;
|
|
12225
|
+
}
|
|
12226
|
+
}
|
|
12188
12227
|
/**
|
|
12189
12228
|
* Basic email format validation (defense in depth)
|
|
12190
12229
|
*/
|
|
@@ -12327,7 +12366,7 @@ var BugBearClient = class {
|
|
|
12327
12366
|
if (updates.platforms !== void 0) updateData.platforms = updates.platforms;
|
|
12328
12367
|
const { error } = await this.supabase.from("testers").update(updateData).eq("id", testerInfo.id);
|
|
12329
12368
|
if (error) {
|
|
12330
|
-
console.error("BugBear: updateTesterProfile error", error);
|
|
12369
|
+
console.error("BugBear: updateTesterProfile error", formatPgError(error));
|
|
12331
12370
|
return { success: false, error: error.message };
|
|
12332
12371
|
}
|
|
12333
12372
|
return { success: true };
|
|
@@ -12349,14 +12388,14 @@ var BugBearClient = class {
|
|
|
12349
12388
|
*/
|
|
12350
12389
|
async isQAEnabled() {
|
|
12351
12390
|
try {
|
|
12352
|
-
const { data, error } = await this.supabase.
|
|
12391
|
+
const { data, error } = await this.supabase.rpc("check_qa_enabled", {
|
|
12392
|
+
p_project_id: this.config.projectId
|
|
12393
|
+
});
|
|
12353
12394
|
if (error) {
|
|
12354
|
-
|
|
12355
|
-
console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
|
|
12356
|
-
}
|
|
12395
|
+
console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
|
|
12357
12396
|
return true;
|
|
12358
12397
|
}
|
|
12359
|
-
return data
|
|
12398
|
+
return data ?? true;
|
|
12360
12399
|
} catch (err) {
|
|
12361
12400
|
return true;
|
|
12362
12401
|
}
|
|
@@ -12386,7 +12425,7 @@ var BugBearClient = class {
|
|
|
12386
12425
|
upsert: false
|
|
12387
12426
|
});
|
|
12388
12427
|
if (error) {
|
|
12389
|
-
console.error("BugBear: Failed to upload screenshot", error);
|
|
12428
|
+
console.error("BugBear: Failed to upload screenshot", formatPgError(error));
|
|
12390
12429
|
return null;
|
|
12391
12430
|
}
|
|
12392
12431
|
const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
|
|
@@ -12417,7 +12456,7 @@ var BugBearClient = class {
|
|
|
12417
12456
|
upsert: false
|
|
12418
12457
|
});
|
|
12419
12458
|
if (error) {
|
|
12420
|
-
console.error("BugBear: Failed to upload image from URI", error);
|
|
12459
|
+
console.error("BugBear: Failed to upload image from URI", formatPgError(error));
|
|
12421
12460
|
return null;
|
|
12422
12461
|
}
|
|
12423
12462
|
const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
|
|
@@ -12492,7 +12531,7 @@ var BugBearClient = class {
|
|
|
12492
12531
|
}
|
|
12493
12532
|
const { data, error } = await query;
|
|
12494
12533
|
if (error) {
|
|
12495
|
-
console.error("BugBear: Failed to fetch fix requests", error);
|
|
12534
|
+
console.error("BugBear: Failed to fetch fix requests", formatPgError(error));
|
|
12496
12535
|
return [];
|
|
12497
12536
|
}
|
|
12498
12537
|
return (data || []).map((fr) => ({
|
|
@@ -12518,75 +12557,35 @@ var BugBearClient = class {
|
|
|
12518
12557
|
try {
|
|
12519
12558
|
const testerInfo = await this.getTesterInfo();
|
|
12520
12559
|
if (!testerInfo) return [];
|
|
12521
|
-
const { data
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
priority,
|
|
12526
|
-
is_pinned,
|
|
12527
|
-
is_resolved,
|
|
12528
|
-
last_message_at,
|
|
12529
|
-
created_at
|
|
12530
|
-
`).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 });
|
|
12560
|
+
const { data, error } = await this.supabase.rpc("get_threads_with_unread", {
|
|
12561
|
+
p_project_id: this.config.projectId,
|
|
12562
|
+
p_tester_id: testerInfo.id
|
|
12563
|
+
});
|
|
12531
12564
|
if (error) {
|
|
12532
|
-
console.error("BugBear: Failed to fetch threads", error);
|
|
12565
|
+
console.error("BugBear: Failed to fetch threads via RPC", formatPgError(error));
|
|
12533
12566
|
return [];
|
|
12534
12567
|
}
|
|
12535
|
-
if (!
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
12539
|
-
|
|
12540
|
-
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
}
|
|
12555
|
-
}
|
|
12556
|
-
const unreadCounts = await Promise.all(
|
|
12557
|
-
threads.map(async (thread) => {
|
|
12558
|
-
const readStatus = readStatusMap.get(thread.id);
|
|
12559
|
-
const lastReadAt = readStatus?.last_read_at || "1970-01-01T00:00:00Z";
|
|
12560
|
-
const { count, error: countError } = await this.supabase.from("discussion_messages").select("*", { count: "exact", head: true }).eq("thread_id", thread.id).gt("created_at", lastReadAt);
|
|
12561
|
-
return { threadId: thread.id, count: countError ? 0 : count || 0 };
|
|
12562
|
-
})
|
|
12563
|
-
);
|
|
12564
|
-
const unreadCountMap = new Map(
|
|
12565
|
-
unreadCounts.map((uc) => [uc.threadId, uc.count])
|
|
12566
|
-
);
|
|
12567
|
-
return threads.map((thread) => {
|
|
12568
|
-
const lastMsg = lastMessageMap.get(thread.id);
|
|
12569
|
-
return {
|
|
12570
|
-
id: thread.id,
|
|
12571
|
-
subject: thread.subject,
|
|
12572
|
-
threadType: thread.thread_type,
|
|
12573
|
-
priority: thread.priority,
|
|
12574
|
-
isPinned: thread.is_pinned,
|
|
12575
|
-
isResolved: thread.is_resolved,
|
|
12576
|
-
lastMessageAt: thread.last_message_at,
|
|
12577
|
-
createdAt: thread.created_at,
|
|
12578
|
-
unreadCount: unreadCountMap.get(thread.id) || 0,
|
|
12579
|
-
lastMessage: lastMsg ? {
|
|
12580
|
-
id: lastMsg.id,
|
|
12581
|
-
threadId: lastMsg.thread_id,
|
|
12582
|
-
senderType: lastMsg.sender_type,
|
|
12583
|
-
senderName: lastMsg.sender_name,
|
|
12584
|
-
content: lastMsg.content,
|
|
12585
|
-
createdAt: lastMsg.created_at,
|
|
12586
|
-
attachments: lastMsg.attachments || []
|
|
12587
|
-
} : void 0
|
|
12588
|
-
};
|
|
12589
|
-
});
|
|
12568
|
+
if (!data || data.length === 0) return [];
|
|
12569
|
+
return data.map((row) => ({
|
|
12570
|
+
id: row.thread_id,
|
|
12571
|
+
subject: row.thread_subject,
|
|
12572
|
+
threadType: row.thread_type,
|
|
12573
|
+
priority: row.thread_priority,
|
|
12574
|
+
isPinned: row.is_pinned,
|
|
12575
|
+
isResolved: row.is_resolved,
|
|
12576
|
+
lastMessageAt: row.last_message_at,
|
|
12577
|
+
createdAt: row.created_at,
|
|
12578
|
+
unreadCount: Number(row.unread_count) || 0,
|
|
12579
|
+
lastMessage: row.last_message_preview ? {
|
|
12580
|
+
id: "",
|
|
12581
|
+
threadId: row.thread_id,
|
|
12582
|
+
senderType: row.last_message_sender_type || "system",
|
|
12583
|
+
senderName: row.last_message_sender_name || "",
|
|
12584
|
+
content: row.last_message_preview,
|
|
12585
|
+
createdAt: row.last_message_at,
|
|
12586
|
+
attachments: []
|
|
12587
|
+
} : void 0
|
|
12588
|
+
}));
|
|
12590
12589
|
} catch (err) {
|
|
12591
12590
|
console.error("BugBear: Error fetching threads", err);
|
|
12592
12591
|
return [];
|
|
@@ -12605,9 +12604,9 @@ var BugBearClient = class {
|
|
|
12605
12604
|
content,
|
|
12606
12605
|
created_at,
|
|
12607
12606
|
attachments
|
|
12608
|
-
`).eq("thread_id", threadId).order("created_at", { ascending: true });
|
|
12607
|
+
`).eq("thread_id", threadId).order("created_at", { ascending: true }).limit(200);
|
|
12609
12608
|
if (error) {
|
|
12610
|
-
console.error("BugBear: Failed to fetch messages", error);
|
|
12609
|
+
console.error("BugBear: Failed to fetch messages", formatPgError(error));
|
|
12611
12610
|
return [];
|
|
12612
12611
|
}
|
|
12613
12612
|
return (data || []).map((msg) => ({
|
|
@@ -12651,10 +12650,9 @@ var BugBearClient = class {
|
|
|
12651
12650
|
}
|
|
12652
12651
|
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
12653
12652
|
if (error) {
|
|
12654
|
-
console.error("BugBear: Failed to send message", error);
|
|
12653
|
+
console.error("BugBear: Failed to send message", formatPgError(error));
|
|
12655
12654
|
return false;
|
|
12656
12655
|
}
|
|
12657
|
-
await this.supabase.from("discussion_threads").update({ last_message_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", threadId);
|
|
12658
12656
|
await this.markThreadAsRead(threadId);
|
|
12659
12657
|
return true;
|
|
12660
12658
|
} catch (err) {
|
|
@@ -12768,7 +12766,7 @@ var BugBearClient = class {
|
|
|
12768
12766
|
p_platform: options.platform || null
|
|
12769
12767
|
});
|
|
12770
12768
|
if (error) {
|
|
12771
|
-
console.error("BugBear: Failed to start session", error);
|
|
12769
|
+
console.error("BugBear: Failed to start session", formatPgError(error));
|
|
12772
12770
|
return { success: false, error: error.message };
|
|
12773
12771
|
}
|
|
12774
12772
|
const session = await this.getSession(data);
|
|
@@ -12793,7 +12791,7 @@ var BugBearClient = class {
|
|
|
12793
12791
|
p_routes_covered: options.routesCovered || null
|
|
12794
12792
|
});
|
|
12795
12793
|
if (error) {
|
|
12796
|
-
console.error("BugBear: Failed to end session", error);
|
|
12794
|
+
console.error("BugBear: Failed to end session", formatPgError(error));
|
|
12797
12795
|
return { success: false, error: error.message };
|
|
12798
12796
|
}
|
|
12799
12797
|
const session = this.transformSession(data);
|
|
@@ -12841,7 +12839,7 @@ var BugBearClient = class {
|
|
|
12841
12839
|
if (!testerInfo) return [];
|
|
12842
12840
|
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);
|
|
12843
12841
|
if (error) {
|
|
12844
|
-
console.error("BugBear: Failed to fetch session history", error);
|
|
12842
|
+
console.error("BugBear: Failed to fetch session history", formatPgError(error));
|
|
12845
12843
|
return [];
|
|
12846
12844
|
}
|
|
12847
12845
|
return (data || []).map((s) => this.transformSession(s));
|
|
@@ -12869,7 +12867,7 @@ var BugBearClient = class {
|
|
|
12869
12867
|
p_app_context: options.appContext || null
|
|
12870
12868
|
});
|
|
12871
12869
|
if (error) {
|
|
12872
|
-
console.error("BugBear: Failed to add finding", error);
|
|
12870
|
+
console.error("BugBear: Failed to add finding", formatPgError(error));
|
|
12873
12871
|
return { success: false, error: error.message };
|
|
12874
12872
|
}
|
|
12875
12873
|
const finding = this.transformFinding(data);
|
|
@@ -12885,9 +12883,9 @@ var BugBearClient = class {
|
|
|
12885
12883
|
*/
|
|
12886
12884
|
async getSessionFindings(sessionId) {
|
|
12887
12885
|
try {
|
|
12888
|
-
const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true });
|
|
12886
|
+
const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true }).limit(100);
|
|
12889
12887
|
if (error) {
|
|
12890
|
-
console.error("BugBear: Failed to fetch findings", error);
|
|
12888
|
+
console.error("BugBear: Failed to fetch findings", formatPgError(error));
|
|
12891
12889
|
return [];
|
|
12892
12890
|
}
|
|
12893
12891
|
return (data || []).map((f) => this.transformFinding(f));
|
|
@@ -12905,7 +12903,7 @@ var BugBearClient = class {
|
|
|
12905
12903
|
p_finding_id: findingId
|
|
12906
12904
|
});
|
|
12907
12905
|
if (error) {
|
|
12908
|
-
console.error("BugBear: Failed to convert finding", error);
|
|
12906
|
+
console.error("BugBear: Failed to convert finding", formatPgError(error));
|
|
12909
12907
|
return { success: false, error: error.message };
|
|
12910
12908
|
}
|
|
12911
12909
|
return { success: true, bugId: data };
|
|
@@ -12926,7 +12924,7 @@ var BugBearClient = class {
|
|
|
12926
12924
|
dismissed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
12927
12925
|
}).eq("id", findingId);
|
|
12928
12926
|
if (error) {
|
|
12929
|
-
console.error("BugBear: Failed to dismiss finding", error);
|
|
12927
|
+
console.error("BugBear: Failed to dismiss finding", formatPgError(error));
|
|
12930
12928
|
return { success: false, error: error.message };
|
|
12931
12929
|
}
|
|
12932
12930
|
return { success: true };
|
|
@@ -13029,7 +13027,8 @@ var BugBearContext = (0, import_react.createContext)({
|
|
|
13029
13027
|
updateTesterProfile: async () => ({ success: false }),
|
|
13030
13028
|
refreshTesterInfo: async () => {
|
|
13031
13029
|
},
|
|
13032
|
-
dashboardUrl: void 0
|
|
13030
|
+
dashboardUrl: void 0,
|
|
13031
|
+
onError: void 0
|
|
13033
13032
|
});
|
|
13034
13033
|
function useBugBear() {
|
|
13035
13034
|
return (0, import_react.useContext)(BugBearContext);
|
|
@@ -13178,6 +13177,9 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13178
13177
|
}
|
|
13179
13178
|
} catch (err) {
|
|
13180
13179
|
console.error("BugBear: Init error", err);
|
|
13180
|
+
if (err instanceof Error) {
|
|
13181
|
+
config.onError?.(err, { phase: "init" });
|
|
13182
|
+
}
|
|
13181
13183
|
} finally {
|
|
13182
13184
|
setIsLoading(false);
|
|
13183
13185
|
}
|
|
@@ -13235,7 +13237,8 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13235
13237
|
refreshTesterStatus,
|
|
13236
13238
|
updateTesterProfile,
|
|
13237
13239
|
refreshTesterInfo,
|
|
13238
|
-
dashboardUrl: config.dashboardUrl
|
|
13240
|
+
dashboardUrl: config.dashboardUrl,
|
|
13241
|
+
onError: config.onError
|
|
13239
13242
|
}
|
|
13240
13243
|
},
|
|
13241
13244
|
children
|
|
@@ -13745,6 +13748,7 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13745
13748
|
const [selectedSkipReason, setSelectedSkipReason] = (0, import_react4.useState)(null);
|
|
13746
13749
|
const [skipNotes, setSkipNotes] = (0, import_react4.useState)("");
|
|
13747
13750
|
const [skipping, setSkipping] = (0, import_react4.useState)(false);
|
|
13751
|
+
const [isSubmitting, setIsSubmitting] = (0, import_react4.useState)(false);
|
|
13748
13752
|
(0, import_react4.useEffect)(() => {
|
|
13749
13753
|
setCriteriaResults({});
|
|
13750
13754
|
setShowSteps(true);
|
|
@@ -13767,26 +13771,39 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13767
13771
|
const allTests = assignments;
|
|
13768
13772
|
const currentIndex = displayedAssignment ? assignments.indexOf(displayedAssignment) : -1;
|
|
13769
13773
|
const handlePass = (0, import_react4.useCallback)(async () => {
|
|
13770
|
-
if (!client || !displayedAssignment) return;
|
|
13774
|
+
if (!client || !displayedAssignment || isSubmitting) return;
|
|
13771
13775
|
import_react_native4.Keyboard.dismiss();
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
+
setIsSubmitting(true);
|
|
13777
|
+
try {
|
|
13778
|
+
await client.passAssignment(displayedAssignment.id);
|
|
13779
|
+
await refreshAssignments();
|
|
13780
|
+
nav.replace({ name: "TEST_FEEDBACK", status: "passed", assignmentId: displayedAssignment.id });
|
|
13781
|
+
} finally {
|
|
13782
|
+
setIsSubmitting(false);
|
|
13783
|
+
}
|
|
13784
|
+
}, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
|
|
13776
13785
|
const handleFail = (0, import_react4.useCallback)(async () => {
|
|
13777
|
-
if (!client || !displayedAssignment) return;
|
|
13786
|
+
if (!client || !displayedAssignment || isSubmitting) return;
|
|
13778
13787
|
import_react_native4.Keyboard.dismiss();
|
|
13779
|
-
|
|
13780
|
-
|
|
13781
|
-
|
|
13782
|
-
|
|
13783
|
-
|
|
13784
|
-
type: "test_fail",
|
|
13785
|
-
assignmentId: displayedAssignment.id,
|
|
13786
|
-
testCaseId: displayedAssignment.testCase.id
|
|
13788
|
+
setIsSubmitting(true);
|
|
13789
|
+
try {
|
|
13790
|
+
const result = await client.failAssignment(displayedAssignment.id);
|
|
13791
|
+
if (!result.success) {
|
|
13792
|
+
console.error("BugBear: Failed to mark assignment as failed", result.error);
|
|
13787
13793
|
}
|
|
13788
|
-
|
|
13789
|
-
|
|
13794
|
+
await refreshAssignments();
|
|
13795
|
+
nav.replace({
|
|
13796
|
+
name: "REPORT",
|
|
13797
|
+
prefill: {
|
|
13798
|
+
type: "test_fail",
|
|
13799
|
+
assignmentId: displayedAssignment.id,
|
|
13800
|
+
testCaseId: displayedAssignment.testCase.id
|
|
13801
|
+
}
|
|
13802
|
+
});
|
|
13803
|
+
} finally {
|
|
13804
|
+
setIsSubmitting(false);
|
|
13805
|
+
}
|
|
13806
|
+
}, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
|
|
13790
13807
|
const handleSkip = (0, import_react4.useCallback)(async () => {
|
|
13791
13808
|
if (!client || !displayedAssignment || !selectedSkipReason) return;
|
|
13792
13809
|
import_react_native4.Keyboard.dismiss();
|
|
@@ -13867,7 +13884,7 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13867
13884
|
}
|
|
13868
13885
|
},
|
|
13869
13886
|
/* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.navigateText }, "\u{1F9ED} Go to test location")
|
|
13870
|
-
), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { onPress: () => setShowDetails(!showDetails), style: styles2.detailsToggle }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.detailsSection }, testCase.key && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.detailMeta }, testCase.key, " \xB7 ", testCase.priority, " \xB7 ", info.name), testCase.description && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.detailDesc }, testCase.description), testCase.group && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.folderProgress }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.folderName }, "\u{1F4C1} ", testCase.group.name))), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.actionButtons }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { style: [styles2.actionBtn, styles2.failBtn], onPress: handleFail }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.failBtnText }, "Fail")), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { style: [styles2.actionBtn, styles2.skipBtn], onPress: () => setShowSkipModal(true) }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.skipBtnText }, "Skip")), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { style: [styles2.actionBtn, styles2.passBtn], onPress: handlePass }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.passBtnText }, "Pass"))), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.modalOverlay }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.modalContent }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.modalTitle }, "Skip this test?"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.modalSubtitle }, "Select a reason:"), [
|
|
13887
|
+
), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { onPress: () => setShowDetails(!showDetails), style: styles2.detailsToggle }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.detailsSection }, testCase.key && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.detailMeta }, testCase.key, " \xB7 ", testCase.priority, " \xB7 ", info.name), testCase.description && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.detailDesc }, testCase.description), testCase.group && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.folderProgress }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.folderName }, "\u{1F4C1} ", testCase.group.name))), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.actionButtons }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { style: [styles2.actionBtn, styles2.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.failBtnText }, isSubmitting ? "Failing..." : "Fail")), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { style: [styles2.actionBtn, styles2.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.skipBtnText }, "Skip")), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { style: [styles2.actionBtn, styles2.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.passBtnText }, isSubmitting ? "Passing..." : "Pass"))), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.modalOverlay }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.modalContent }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.modalTitle }, "Skip this test?"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.modalSubtitle }, "Select a reason:"), [
|
|
13871
13888
|
{ reason: "blocked", label: "\u{1F6AB} Blocked by a bug" },
|
|
13872
13889
|
{ reason: "not_ready", label: "\u{1F6A7} Feature not ready" },
|
|
13873
13890
|
{ reason: "dependency", label: "\u{1F517} Needs another test first" },
|
|
@@ -14437,6 +14454,7 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14437
14454
|
const [submitting, setSubmitting] = (0, import_react10.useState)(false);
|
|
14438
14455
|
const [error, setError] = (0, import_react10.useState)(null);
|
|
14439
14456
|
const images = useImageAttachments(uploadImage, 5, "screenshots");
|
|
14457
|
+
const isRetestFailure = prefill?.type === "test_fail";
|
|
14440
14458
|
const isBugType = reportType === "bug" || reportType === "test_fail";
|
|
14441
14459
|
const handleSubmit = async () => {
|
|
14442
14460
|
if (!client || !description.trim()) return;
|
|
@@ -14476,7 +14494,50 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14476
14494
|
setSubmitting(false);
|
|
14477
14495
|
}
|
|
14478
14496
|
};
|
|
14479
|
-
return /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, null, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style:
|
|
14497
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, null, isRetestFailure ? /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.retestBanner }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.retestIcon }, "\u{1F504}"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, null, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.retestTitle }, "Bug Still Present"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.section }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.label }, "Severity"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.severityRow }, [
|
|
14498
|
+
{ sev: "critical", color: "#ef4444" },
|
|
14499
|
+
{ sev: "high", color: "#f97316" },
|
|
14500
|
+
{ sev: "medium", color: "#eab308" },
|
|
14501
|
+
{ sev: "low", color: "#6b7280" }
|
|
14502
|
+
].map(({ sev, color }) => /* @__PURE__ */ import_react10.default.createElement(
|
|
14503
|
+
import_react_native9.TouchableOpacity,
|
|
14504
|
+
{
|
|
14505
|
+
key: sev,
|
|
14506
|
+
style: [styles7.sevButton, severity === sev && { backgroundColor: `${color}30`, borderColor: color }],
|
|
14507
|
+
onPress: () => setSeverity(sev)
|
|
14508
|
+
},
|
|
14509
|
+
/* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: [styles7.sevText, severity === sev && { color }] }, sev)
|
|
14510
|
+
)))), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.section }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.label }, "What went wrong?"), /* @__PURE__ */ import_react10.default.createElement(
|
|
14511
|
+
import_react_native9.TextInput,
|
|
14512
|
+
{
|
|
14513
|
+
style: styles7.descInput,
|
|
14514
|
+
value: description,
|
|
14515
|
+
onChangeText: setDescription,
|
|
14516
|
+
placeholder: "Describe what you observed. What still doesn't work?",
|
|
14517
|
+
placeholderTextColor: colors.textMuted,
|
|
14518
|
+
multiline: true,
|
|
14519
|
+
numberOfLines: 4,
|
|
14520
|
+
textAlignVertical: "top"
|
|
14521
|
+
}
|
|
14522
|
+
)), /* @__PURE__ */ import_react10.default.createElement(
|
|
14523
|
+
ImagePickerButtons,
|
|
14524
|
+
{
|
|
14525
|
+
images: images.images,
|
|
14526
|
+
maxImages: 5,
|
|
14527
|
+
onPickGallery: images.pickFromGallery,
|
|
14528
|
+
onPickCamera: images.pickFromCamera,
|
|
14529
|
+
onRemove: images.removeImage,
|
|
14530
|
+
label: "Attachments (optional)"
|
|
14531
|
+
}
|
|
14532
|
+
), error && /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.errorBanner }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.errorText }, error)), /* @__PURE__ */ import_react10.default.createElement(
|
|
14533
|
+
import_react_native9.TouchableOpacity,
|
|
14534
|
+
{
|
|
14535
|
+
style: [shared.primaryButton, styles7.retestSubmitButton, (!description.trim() || submitting || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
|
|
14536
|
+
onPress: handleSubmit,
|
|
14537
|
+
disabled: !description.trim() || submitting || images.isUploading
|
|
14538
|
+
},
|
|
14539
|
+
/* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Failed Retest")
|
|
14540
|
+
)) : /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.label }, "What are you reporting?"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.typeRow }, [
|
|
14480
14541
|
{ type: "bug", label: "Bug", icon: "\u{1F41B}" },
|
|
14481
14542
|
{ type: "feedback", label: "Feedback", icon: "\u{1F4A1}" },
|
|
14482
14543
|
{ type: "suggestion", label: "Idea", icon: "\u2728" }
|
|
@@ -14541,7 +14602,7 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14541
14602
|
disabled: !description.trim() || submitting || images.isUploading
|
|
14542
14603
|
},
|
|
14543
14604
|
/* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Report")
|
|
14544
|
-
));
|
|
14605
|
+
)));
|
|
14545
14606
|
}
|
|
14546
14607
|
var styles7 = import_react_native9.StyleSheet.create({
|
|
14547
14608
|
typeRow: { flexDirection: "row", gap: 10, marginBottom: 20 },
|
|
@@ -14558,7 +14619,12 @@ var styles7 = import_react_native9.StyleSheet.create({
|
|
|
14558
14619
|
screenInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 13, color: colors.textPrimary },
|
|
14559
14620
|
screenHint: { fontSize: 11, color: colors.textMuted, marginTop: 4 },
|
|
14560
14621
|
errorBanner: { backgroundColor: "#7f1d1d", borderWidth: 1, borderColor: "#991b1b", borderRadius: 8, padding: 12, marginTop: 16 },
|
|
14561
|
-
errorText: { fontSize: 13, color: "#fca5a5", lineHeight: 18 }
|
|
14622
|
+
errorText: { fontSize: 13, color: "#fca5a5", lineHeight: 18 },
|
|
14623
|
+
retestBanner: { flexDirection: "row", alignItems: "center", gap: 10, backgroundColor: "#422006", borderWidth: 1, borderColor: "#854d0e", borderRadius: 10, paddingVertical: 12, paddingHorizontal: 14, marginBottom: 20 },
|
|
14624
|
+
retestIcon: { fontSize: 16 },
|
|
14625
|
+
retestTitle: { fontSize: 15, fontWeight: "600", color: "#fbbf24", lineHeight: 20 },
|
|
14626
|
+
retestSubtitle: { fontSize: 12, color: "#d97706", lineHeight: 16 },
|
|
14627
|
+
retestSubmitButton: { backgroundColor: "#b45309" }
|
|
14562
14628
|
});
|
|
14563
14629
|
|
|
14564
14630
|
// src/widget/screens/ReportSuccessScreen.tsx
|
|
@@ -15261,9 +15327,108 @@ var styles13 = import_react_native15.StyleSheet.create({
|
|
|
15261
15327
|
fontSize: 14
|
|
15262
15328
|
}
|
|
15263
15329
|
});
|
|
15330
|
+
|
|
15331
|
+
// src/BugBearErrorBoundary.tsx
|
|
15332
|
+
var import_react17 = __toESM(require("react"));
|
|
15333
|
+
var import_react_native16 = require("react-native");
|
|
15334
|
+
var BugBearErrorBoundary = class extends import_react17.Component {
|
|
15335
|
+
constructor(props) {
|
|
15336
|
+
super(props);
|
|
15337
|
+
this.reset = () => {
|
|
15338
|
+
this.setState({
|
|
15339
|
+
hasError: false,
|
|
15340
|
+
error: null,
|
|
15341
|
+
errorInfo: null
|
|
15342
|
+
});
|
|
15343
|
+
};
|
|
15344
|
+
this.state = {
|
|
15345
|
+
hasError: false,
|
|
15346
|
+
error: null,
|
|
15347
|
+
errorInfo: null
|
|
15348
|
+
};
|
|
15349
|
+
}
|
|
15350
|
+
static getDerivedStateFromError(error) {
|
|
15351
|
+
return { hasError: true, error };
|
|
15352
|
+
}
|
|
15353
|
+
componentDidCatch(error, errorInfo) {
|
|
15354
|
+
this.setState({ errorInfo });
|
|
15355
|
+
captureError(error, {
|
|
15356
|
+
componentStack: errorInfo.componentStack ?? void 0
|
|
15357
|
+
});
|
|
15358
|
+
console.error("BugBear: Error caught by ErrorBoundary", {
|
|
15359
|
+
error: error.message,
|
|
15360
|
+
componentStack: errorInfo.componentStack?.slice(0, 500)
|
|
15361
|
+
});
|
|
15362
|
+
this.props.onError?.(error, errorInfo);
|
|
15363
|
+
this.props.errorReporter?.(error, {
|
|
15364
|
+
componentStack: errorInfo.componentStack ?? void 0,
|
|
15365
|
+
source: "BugBearErrorBoundary"
|
|
15366
|
+
});
|
|
15367
|
+
}
|
|
15368
|
+
render() {
|
|
15369
|
+
const { hasError, error } = this.state;
|
|
15370
|
+
const { children, fallback } = this.props;
|
|
15371
|
+
if (hasError && error) {
|
|
15372
|
+
if (typeof fallback === "function") {
|
|
15373
|
+
return fallback(error, this.reset);
|
|
15374
|
+
}
|
|
15375
|
+
if (fallback) {
|
|
15376
|
+
return fallback;
|
|
15377
|
+
}
|
|
15378
|
+
return /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles14.container }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles14.title }, "Something went wrong"), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles14.message }, error.message), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.TouchableOpacity, { style: styles14.button, onPress: this.reset }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles14.buttonText }, "Try Again")), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles14.caption }, "The error has been captured by BugBear"));
|
|
15379
|
+
}
|
|
15380
|
+
return children;
|
|
15381
|
+
}
|
|
15382
|
+
};
|
|
15383
|
+
function useErrorContext() {
|
|
15384
|
+
return {
|
|
15385
|
+
captureError,
|
|
15386
|
+
getEnhancedContext: () => contextCapture.getEnhancedContext()
|
|
15387
|
+
};
|
|
15388
|
+
}
|
|
15389
|
+
var styles14 = import_react_native16.StyleSheet.create({
|
|
15390
|
+
container: {
|
|
15391
|
+
padding: 20,
|
|
15392
|
+
margin: 20,
|
|
15393
|
+
backgroundColor: "#fef2f2",
|
|
15394
|
+
borderWidth: 1,
|
|
15395
|
+
borderColor: "#fecaca",
|
|
15396
|
+
borderRadius: 8
|
|
15397
|
+
},
|
|
15398
|
+
title: {
|
|
15399
|
+
fontSize: 16,
|
|
15400
|
+
fontWeight: "600",
|
|
15401
|
+
color: "#991b1b",
|
|
15402
|
+
marginBottom: 8
|
|
15403
|
+
},
|
|
15404
|
+
message: {
|
|
15405
|
+
fontSize: 14,
|
|
15406
|
+
color: "#7f1d1d",
|
|
15407
|
+
marginBottom: 12
|
|
15408
|
+
},
|
|
15409
|
+
button: {
|
|
15410
|
+
backgroundColor: "#dc2626",
|
|
15411
|
+
paddingHorizontal: 16,
|
|
15412
|
+
paddingVertical: 8,
|
|
15413
|
+
borderRadius: 6,
|
|
15414
|
+
alignSelf: "flex-start"
|
|
15415
|
+
},
|
|
15416
|
+
buttonText: {
|
|
15417
|
+
color: "white",
|
|
15418
|
+
fontSize: 14,
|
|
15419
|
+
fontWeight: "500"
|
|
15420
|
+
},
|
|
15421
|
+
caption: {
|
|
15422
|
+
fontSize: 12,
|
|
15423
|
+
color: "#9ca3af",
|
|
15424
|
+
marginTop: 12
|
|
15425
|
+
}
|
|
15426
|
+
});
|
|
15264
15427
|
// Annotate the CommonJS export names for ESM import in node:
|
|
15265
15428
|
0 && (module.exports = {
|
|
15266
15429
|
BugBearButton,
|
|
15430
|
+
BugBearErrorBoundary,
|
|
15267
15431
|
BugBearProvider,
|
|
15268
|
-
useBugBear
|
|
15432
|
+
useBugBear,
|
|
15433
|
+
useErrorContext
|
|
15269
15434
|
});
|