1import { renderToStaticMarkup } from "react-dom/server";
2import { describe, expect, it } from "vite-plus/test";
3
4import {
5 dismissThreadErrorBannerForSession,
6 getThreadErrorBannerKey,
7 isThreadErrorBannerDismissedForSession,
8 shouldShowThreadErrorBanner,
9 ThreadErrorBanner,
10} from "./ThreadErrorBanner";
11
12describe("ThreadErrorBanner", () => {
13 it("stays hidden after its current error is dismissed", () => {
14 const bannerKey = getThreadErrorBannerKey("env:thread-a", "Aborted");
15 dismissThreadErrorBannerForSession(bannerKey);
16
17 expect(
18 shouldShowThreadErrorBanner(
19 "env:thread-a",
20 "Aborted",
21 isThreadErrorBannerDismissedForSession(bannerKey),
22 ),
23 ).toBe(false);
24 });
25
26 it("reappears when a new error arrives on the same thread", () => {
27 dismissThreadErrorBannerForSession(getThreadErrorBannerKey("env:thread-b", "Turn failed"));
28 const newErrorKey = getThreadErrorBannerKey("env:thread-b", "Provider crashed");
29
30 expect(isThreadErrorBannerDismissedForSession(newErrorKey)).toBe(false);
31 expect(
32 shouldShowThreadErrorBanner(
33 "env:thread-b",
34 "Provider crashed",
35 isThreadErrorBannerDismissedForSession(newErrorKey),
36 ),
37 ).toBe(true);
38 });
39
40 it("scopes dismissals to the thread that dismissed them", () => {
41 dismissThreadErrorBannerForSession(getThreadErrorBannerKey("env:thread-c", "Aborted"));
42 const otherThreadKey = getThreadErrorBannerKey("env:other-thread", "Aborted");
43
44 expect(isThreadErrorBannerDismissedForSession(otherThreadKey)).toBe(false);
45 expect(
46 shouldShowThreadErrorBanner(
47 "env:other-thread",
48 "Aborted",
49 isThreadErrorBannerDismissedForSession(otherThreadKey),
50 ),
51 ).toBe(true);
52 });
53
54 it("keeps a dismissal across visiting threads with no error", () => {
55 const bannerKey = getThreadErrorBannerKey("env:thread-d", "Aborted");
56 dismissThreadErrorBannerForSession(bannerKey);
57
58 expect(shouldShowThreadErrorBanner("env:thread-d", null, false)).toBe(false);
59 expect(isThreadErrorBannerDismissedForSession(bannerKey)).toBe(true);
60 expect(
61 shouldShowThreadErrorBanner(
62 "env:thread-d",
63 "Aborted",
64 isThreadErrorBannerDismissedForSession(bannerKey),
65 ),
66 ).toBe(false);
67 });
68
69 it("never shows a null error", () => {
70 expect(shouldShowThreadErrorBanner("env:thread-e", null, false)).toBe(false);
71 });
72 it("aligns the warning and dismiss icons with the first line of a multi-line error", () => {
73 const markup = renderToStaticMarkup(
74 <ThreadErrorBanner
75 error={"The first error line\ncontinues on a second line"}
76 onDismiss={() => {}}
77 />,
78 );
79
80 expect(markup).toContain('role="alert"');
81 expect(markup).toContain('aria-label="Dismiss error"');
82 expect(markup).not.toContain("controlAlignment");
83 expect(markup).toContain("flex gap-2 items-start");
84 expect(markup).toContain("min-h-7 pt-1 sm:min-h-6 sm:pt-0.5");
85 expect(markup).toContain("h-lh w-4");
86 expect(markup).toContain("h-lh self-start");
87 });
88});