Skip to content
Source
Sign inSign up
Open History to see the last commit

MarkdownThreadLink.browser.test.tsx

158 lines5.7 KB

1import { RegistryContext } from "@effect/atom-react";
2import { EnvironmentId, ProjectId, ThreadId } from "@t3tools/contracts";
3import * as Cause from "effect/Cause";
4import { AsyncResult, Atom, AtomRegistry } from "effect/reactivity";
5import { expect, it, vi } from "vite-plus/test";
6​
7import { act, render } from "../../../test/render";
8import { MarkdownThreadLink } from "./MarkdownThreadLink";
9​
10const state = vi.hoisted(() => ({
11 reply: null as unknown,
12 remote: null as unknown,
13}));
14vi.mock(import("../../state/entities"), () => ({
15 useThreadShell: ((ref: { environmentId: string } | null) =>
16 ref?.environmentId === "remote" ? state.remote : null) as never,
17 useThreadRefs: (enabled = true) =>
18 enabled
19 ? [{ environmentId: EnvironmentId.make("remote"), threadId: ThreadId.make("older-thread") }]
20 : [],
21 useProject: (ref: { projectId: string } | null) =>
22 ref?.projectId === "essay-project" ? ({ title: "Essays" } as never) : null,
23}));
24vi.mock(import("../../state/environments"), () => ({
25 useEnvironmentIdentities: () => [
26 { environmentId: EnvironmentId.make("environment"), label: "Here" },
27 { environmentId: EnvironmentId.make("remote"), label: "Remote" },
28 ],
29}));
30vi.mock(import("../ProjectFavicon"), () => ({
31 ProjectFavicon: () => (
32 <span role="img" aria-label="Essays project icon">
33 P
34 </span>
35 ),
36}));
37vi.mock(import("../contextChipParts"), () => ({
38 UnresolvedChip: ({ label }: { label: string }) => <span>{label}</span>,
39}));
40vi.mock(import("../../state/orchestration"), () => ({
41 orchestrationEnvironment: { threadLinkMetadata: () => state.reply } as never,
42}));
43​
44function setup() {
45 const reply = Atom.make<
46 AsyncResult.AsyncResult<Record<string, { title: string; projectId: ProjectId | null }>, string>
47 >(AsyncResult.initial());
48 state.reply = reply;
49 state.remote = {
50 environmentId: EnvironmentId.make("remote"),
51 id: ThreadId.make("older-thread"),
52 title: "Remote thread",
53 projectId: ProjectId.make("essay-project"),
54 };
55 return { reply, registry: AtomRegistry.make() };
57​
58it.each([
59 { legacy: false, encoded: false },
60 { legacy: true, encoded: false },
61 { legacy: false, encoded: true },
62])(
63 "resolves an unloaded thread with its icon and the correct destination, %j",
64 async ({ legacy, encoded }) => {
65 const { reply, registry } = setup();
66 using _registry = { [Symbol.dispose]: () => registry.dispose() };
67 const environmentId = EnvironmentId.make("environment");
68 const threadId = ThreadId.make(encoded ? "older%2Dthread" : "older-thread");
69 await using screen = await render(
70 <RegistryContext value={registry}>
71 <MarkdownThreadLink
72 environmentId={environmentId}
73 link={{ environmentId: legacy ? null : environmentId, threadId }}
74 label={null}
75 />
76 </RegistryContext>,
77 );
78 await expect
79 .element(screen.getByRole("link", { name: "Thread, Thread", exact: true }))
80 .toBeVisible();
81 await act(() => registry.set(reply, AsyncResult.failure(Cause.fail("offline"))));
82 await expect
83 .element(screen.getByRole("link", { name: "Thread, Thread", exact: true }))
84 .toBeVisible();
85 await act(() =>
86 registry.set(
87 reply,
88 AsyncResult.success({
89 [threadId]: { title: "Older essay", projectId: ProjectId.make("essay-project") },
90 ...(encoded
91 ? {
92 "older-thread": {
93 title: "Decoded essay",
94 projectId: ProjectId.make("essay-project"),
95 },
96 }
97 : {}),
98 }),
99 ),
100 );
101 const link = screen.getByRole("link", { name: "Thread, Older essay" });
102 await expect.element(link).toBeVisible();
103 await expect.element(screen.getByRole("img", { name: "Essays project icon" })).toBeVisible();
104 await link.click();
105 await vi.waitFor(() =>
106 expect(screen.router?.state.location.pathname).toBe(
107 `/environment/${encodeURIComponent(threadId)}`,
108 ),
109 );
110 if (encoded) {
111 await act(() =>
112 registry.set(
113 reply,
114 AsyncResult.success({
115 "older-thread": { title: "Decoded essay", projectId: ProjectId.make("essay-project") },
116 }),
117 ),
118 );
119 const decoded = screen.getByRole("link", { name: "Thread, Decoded essay" });
120 await expect.element(decoded).toBeVisible();
121 await decoded.click();
122 await vi.waitFor(() =>
123 expect(screen.router?.state.location.pathname).toBe("/environment/older-thread"),
124 );
125 }
126 await act(() => registry.set(reply, AsyncResult.success({})));
127 if (legacy) {
128 const remote = screen.getByRole("link", { name: "Thread, Remote thread" });
129 await expect.element(remote).toBeVisible();
130 await remote.click();
131 await vi.waitFor(() =>
132 expect(screen.router?.state.location.pathname).toBe("/remote/older-thread"),
133 );
134 } else {
135 await expect
136 .element(screen.getByText("Thread no longer available", { exact: true }))
137 .toBeVisible();
138 }
139 },
140);
141​
142it("names a thread on a machine this client has no connection to, without waiting on it", async () => {
143 const { registry } = setup();
144 using _registry = { [Symbol.dispose]: () => registry.dispose() };
145 await using screen = await render(
146 <RegistryContext value={registry}>
147 <MarkdownThreadLink
148 environmentId={EnvironmentId.make("environment")}
149 link={{ environmentId: EnvironmentId.make("peer"), threadId: ThreadId.make("peer-thread") }}
150 label={null}
151 />
152 </RegistryContext>,
153 );
154 await expect
155 .element(screen.getByText("Thread on another machine", { exact: true }))
156 .toBeVisible();
157 await expect.element(screen.getByRole("link")).not.toBeInTheDocument();
158});