@remit/web-client 0.0.216 → 0.0.217

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/web-client",
3
- "version": "0.0.216",
3
+ "version": "0.0.217",
4
4
  "type": "module",
5
5
  "description": "Remit web client, published as composable primitives — the app shell, auth shells, and runtime config. A distributor imports what it composes and bundles it.",
6
6
  "exports": {
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Deleting the open message leaves the reader on the list they are browsing
3
+ * (#798, #946).
4
+ *
5
+ * The row vanishes optimistically, so the address that names it has to move
6
+ * before the server answers. It moves up to the list underneath — the brief,
7
+ * Flagged, the folder — and never to the deleted message's own home folder,
8
+ * which ejected a reader out of the brief into a folder they had not opened.
9
+ */
10
+
11
+ import assert from "node:assert/strict";
12
+ import { afterEach, describe, it } from "node:test";
13
+ import {
14
+ type AnyRouter,
15
+ createMemoryHistory,
16
+ createRootRoute,
17
+ createRoute,
18
+ createRouter,
19
+ Outlet,
20
+ RouterProvider,
21
+ } from "@tanstack/react-router";
22
+ import { createElement } from "react";
23
+ import { createDomHarness, type DomHarness } from "@/test-support/dom";
24
+ import { type HttpMock, mockFetch } from "@/test-support/http";
25
+ import { MessageActionMenu } from "./MessageActionMenu";
26
+
27
+ const THREAD_ID = "thread-1";
28
+ const MESSAGE_ID = "msg-1";
29
+ const MAILBOX_ID = "mbx-inbox";
30
+
31
+ let harness: DomHarness | undefined;
32
+ let http: HttpMock | undefined;
33
+
34
+ afterEach(() => {
35
+ harness?.close();
36
+ harness = undefined;
37
+ http?.restore();
38
+ http = undefined;
39
+ });
40
+
41
+ // The router reads `self` at construction; the shared jsdom globals stop at
42
+ // `window`.
43
+ (globalThis as { self?: typeof globalThis }).self ??= globalThis;
44
+
45
+ /**
46
+ * The three lists a conversation can be opened from, each with the thread and
47
+ * the message under it. The menu is what the message route mounts, so the
48
+ * mailbox it is handed is the message's own — which on the brief and on Flagged
49
+ * is not the list being browsed.
50
+ */
51
+ const testRouter = (href: string): AnyRouter => {
52
+ const rootRoute = createRootRoute({ component: Outlet });
53
+ const mailRoute = createRoute({
54
+ getParentRoute: () => rootRoute,
55
+ path: "/mail",
56
+ validateSearch: (search: Record<string, unknown>) => search,
57
+ component: Outlet,
58
+ });
59
+ const listRoute = (path: string) => {
60
+ const list = createRoute({
61
+ getParentRoute: () => mailRoute,
62
+ path,
63
+ component: Outlet,
64
+ });
65
+ const thread = createRoute({
66
+ getParentRoute: () => list,
67
+ path: "$threadId",
68
+ component: Outlet,
69
+ });
70
+ const message = createRoute({
71
+ getParentRoute: () => thread,
72
+ path: "$messageId",
73
+ component: () =>
74
+ createElement(MessageActionMenu, {
75
+ messageId: MESSAGE_ID,
76
+ threadId: THREAD_ID,
77
+ mailboxId: MAILBOX_ID,
78
+ isRead: true,
79
+ }),
80
+ });
81
+ return list.addChildren([thread.addChildren([message])]);
82
+ };
83
+ const routeTree = rootRoute.addChildren([
84
+ mailRoute.addChildren([
85
+ listRoute("/brief"),
86
+ listRoute("/flagged"),
87
+ listRoute("/$mailboxId"),
88
+ ]),
89
+ ]);
90
+ return createRouter({
91
+ routeTree,
92
+ history: createMemoryHistory({ initialEntries: [href] }),
93
+ }) as unknown as AnyRouter;
94
+ };
95
+
96
+ const settle = async (mounted: DomHarness): Promise<void> => {
97
+ await mounted.flush();
98
+ await mounted.wait(20);
99
+ await mounted.flush();
100
+ };
101
+
102
+ const mountAt = async (href: string): Promise<[DomHarness, AnyRouter]> => {
103
+ http = mockFetch(() => ({}));
104
+ const router = testRouter(href);
105
+ await router.load();
106
+ const mounted = createDomHarness();
107
+ harness = mounted;
108
+ mounted.renderApp(createElement(RouterProvider, { router }));
109
+ await settle(mounted);
110
+ return [mounted, router];
111
+ };
112
+
113
+ /** The overflow menu on the open message, and the Delete inside it. */
114
+ const pressDelete = async (mounted: DomHarness): Promise<void> => {
115
+ const overflow = mounted.query("button");
116
+ assert.ok(overflow, "the message carries an overflow menu");
117
+ mounted.click(overflow);
118
+ await mounted.flush();
119
+
120
+ mounted.click(mounted.byText("button", "Delete"));
121
+ await settle(mounted);
122
+ };
123
+
124
+ describe("deleting the open message keeps the list it was opened from", () => {
125
+ it("stays on the brief", async () => {
126
+ const [mounted, router] = await mountAt(
127
+ `/mail/brief/${THREAD_ID}/${MESSAGE_ID}`,
128
+ );
129
+
130
+ await pressDelete(mounted);
131
+
132
+ assert.equal(router.state.location.pathname, "/mail/brief");
133
+ });
134
+
135
+ it("stays on the flagged list", async () => {
136
+ const [mounted, router] = await mountAt(
137
+ `/mail/flagged/${THREAD_ID}/${MESSAGE_ID}`,
138
+ );
139
+
140
+ await pressDelete(mounted);
141
+
142
+ assert.equal(router.state.location.pathname, "/mail/flagged");
143
+ });
144
+
145
+ it("stays in the folder being browsed", async () => {
146
+ const [mounted, router] = await mountAt(
147
+ `/mail/${MAILBOX_ID}/${THREAD_ID}/${MESSAGE_ID}`,
148
+ );
149
+
150
+ await pressDelete(mounted);
151
+
152
+ assert.equal(router.state.location.pathname, `/mail/${MAILBOX_ID}`);
153
+ });
154
+ });