@remit/web-client 0.0.321 → 0.0.323

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.
Files changed (190) hide show
  1. package/package.json +4 -4
  2. package/src/auth/SignInAgain.tsx +18 -0
  3. package/src/auth/amplify-config.test.ts +1 -107
  4. package/src/auth/auth-interceptor.test.ts +0 -6
  5. package/src/auth/cognito/cognito-token.test.ts +2 -4
  6. package/src/components/calendar/AgendaStrip.tsx +1 -1
  7. package/src/components/calendar/CalendarWorkspace.tsx +1 -1
  8. package/src/components/calendar/ChangeCalendarSuggestion.tsx +1 -1
  9. package/src/components/calendar/OpenCalendarEvent.tsx +1 -18
  10. package/src/components/compose/ComposeForm.tsx +11 -6
  11. package/src/components/compose/FullCompose.tsx +1 -1
  12. package/src/components/compose/MobileComposeSheet.tsx +1 -2
  13. package/src/components/compose/compose.stories.tsx +18 -0
  14. package/src/components/compose/draft-discard-failure.render.test.ts +138 -0
  15. package/src/components/config-import/config-import.fixtures.ts +8 -9
  16. package/src/components/config-import/config-import.stories.tsx +2 -2
  17. package/src/components/layout/MailTopBar.tsx +3 -0
  18. package/src/components/mail/BriefPane.tsx +0 -3
  19. package/src/components/mail/ConversationView.tsx +1 -3
  20. package/src/components/mail/DailyBrief.tsx +5 -28
  21. package/src/components/mail/FlaggedList.tsx +3 -4
  22. package/src/components/mail/FlaggedPane.tsx +0 -3
  23. package/src/components/mail/IntelligenceDrawer.tsx +0 -3
  24. package/src/components/mail/IntelligencePane.render.test.ts +0 -1
  25. package/src/components/mail/IntelligencePane.test.ts +1 -95
  26. package/src/components/mail/IntelligencePane.tsx +3 -93
  27. package/src/components/mail/MailListHeader.tsx +12 -43
  28. package/src/components/mail/MailNav.tsx +6 -2
  29. package/src/components/mail/MailViewChrome.tsx +1 -11
  30. package/src/components/mail/MailboxPane.tsx +4 -34
  31. package/src/components/mail/MessageActionMenu.tsx +6 -6
  32. package/src/components/mail/MessageAttachments.tsx +35 -0
  33. package/src/components/mail/MessageBody.tsx +1 -0
  34. package/src/components/mail/MessageBodyErrorBanner.test.ts +0 -30
  35. package/src/components/mail/MessageCard.tsx +16 -4
  36. package/src/components/mail/MessageList.run-reporting.test.ts +2 -46
  37. package/src/components/mail/MessageList.tsx +23 -11
  38. package/src/components/mail/MessageRow.render.test.ts +0 -7
  39. package/src/components/mail/MoveToTrigger.tsx +1 -1
  40. package/src/components/mail/RawMessageView.tsx +1 -1
  41. package/src/components/mail/SelectionWizardHost.search-entry.test.ts +0 -1
  42. package/src/components/mail/SelectionWizardHost.tsx +16 -102
  43. package/src/components/mail/authenticity-warning.stories.tsx +0 -21
  44. package/src/components/mail/daily-brief.stories.tsx +31 -17
  45. package/src/components/mail/enter-cursor-late-open.render.test.ts +197 -0
  46. package/src/components/mail/intelligence-drawer.stories.tsx +0 -2
  47. package/src/components/mail/message-gone.stories.tsx +44 -0
  48. package/src/components/mail/offer-times-reply.render.test.ts +25 -1
  49. package/src/components/mail/reclassify-remove-override.render.test.ts +2 -6
  50. package/src/components/overlay-escape.render.test.ts +7 -3
  51. package/src/components/settings/AccountFormPanel.render.test.ts +26 -3
  52. package/src/components/settings/AccountFormPanel.test.ts +1 -36
  53. package/src/components/settings/AccountFormPanel.tsx +5 -25
  54. package/src/components/settings/AccountServices.tsx +1 -1
  55. package/src/components/settings/AdvancedNavIcon.tsx +3 -3
  56. package/src/components/settings/CalendarFeedCard.tsx +2 -1
  57. package/src/components/settings/CalendarSubscribeCard.tsx +1 -1
  58. package/src/components/settings/CalendarSubscriptionStatus.tsx +1 -2
  59. package/src/components/settings/FilterActionError.tsx +1 -1
  60. package/src/components/settings/FilterEditor.render.test.ts +3 -27
  61. package/src/components/settings/FilterEditor.tsx +4 -24
  62. package/src/components/settings/FilterEditorSurface.tsx +0 -3
  63. package/src/components/settings/FiltersList.render.test.ts +5 -24
  64. package/src/components/settings/FiltersList.stories.tsx +0 -1
  65. package/src/components/settings/FiltersList.tsx +10 -35
  66. package/src/components/settings/WarningsNavIcon.tsx +12 -0
  67. package/src/components/settings/{QuarantinePanel.delimiter.test.ts → WarningsPanel.dom.test.ts} +64 -5
  68. package/src/components/settings/WarningsPanel.render.test.ts +56 -0
  69. package/src/components/settings/WarningsPanel.tsx +244 -0
  70. package/src/components/settings/accounts.stories.tsx +18 -0
  71. package/src/components/settings/advanced.stories.tsx +3 -125
  72. package/src/components/settings/settings-filter.stories.tsx +2 -42
  73. package/src/components/settings/warnings.stories.tsx +290 -0
  74. package/src/components/ui/ErrorBannerProvider.tsx +5 -3
  75. package/src/components/ui/error-banners.test.ts +1 -22
  76. package/src/components/ui/error-banners.ts +9 -20
  77. package/src/hooks/calendar/useCalendarCollectionWrites.ts +2 -1
  78. package/src/hooks/calendar/useCalendarSuggestions.ts +2 -1
  79. package/src/hooks/calendar/useCalendarWrites.ts +2 -1
  80. package/src/hooks/intelligence-calendar.render.test.ts +0 -1
  81. package/src/hooks/useDeleteMessages.test.ts +1 -42
  82. package/src/hooks/useFilters.test.ts +0 -8
  83. package/src/hooks/useFollowFocusOpen.render.test.ts +1 -11
  84. package/src/hooks/useIntelligenceData.test.ts +9 -47
  85. package/src/hooks/useIntelligenceData.ts +7 -100
  86. package/src/hooks/useLayoutTier.test.ts +0 -48
  87. package/src/hooks/useReportSpam.ts +1 -1
  88. package/src/hooks/useRescueCandidates.test.ts +0 -23
  89. package/src/hooks/useRulePreview.ts +8 -7
  90. package/src/hooks/useSearchTokenContext.ts +2 -3
  91. package/src/hooks/useStaleAccountSync.test.ts +0 -17
  92. package/src/hooks/useStarredThreads.test.ts +0 -10
  93. package/src/hooks/useTriggerSync.test.ts +0 -6
  94. package/src/hooks/useVisualViewport.render.test.ts +63 -0
  95. package/src/hooks/useWarningsAttention.ts +37 -0
  96. package/src/lib/account-order.test.ts +0 -10
  97. package/src/lib/adjacent-message.test.ts +0 -9
  98. package/src/lib/app-info.ts +0 -3
  99. package/src/lib/autodiscovery.test.ts +0 -47
  100. package/src/lib/bug-report.test.ts +38 -0
  101. package/src/lib/bug-report.ts +10 -7
  102. package/src/lib/bulk-action-copy.ts +1 -1
  103. package/src/lib/calendar-route.test.ts +0 -5
  104. package/src/lib/config-import.test.ts +3 -39
  105. package/src/lib/dedupe-thread-messages.test.ts +0 -4
  106. package/src/lib/desktop-breakpoint.test.ts +0 -8
  107. package/src/lib/drafts.test.ts +0 -23
  108. package/src/lib/fatal-error.test.ts +0 -19
  109. package/src/lib/folder-roles.test.ts +0 -4
  110. package/src/lib/hot-sync-window.test.ts +0 -5
  111. package/src/lib/list-header-chrome.ts +1 -1
  112. package/src/lib/mail-context.ts +1 -1
  113. package/src/lib/message-attachments.test.ts +77 -0
  114. package/src/lib/message-attachments.ts +31 -15
  115. package/src/lib/message-body-source.test.ts +21 -4
  116. package/src/lib/message-body-source.ts +5 -2
  117. package/src/lib/onboarding-completion.test.ts +0 -16
  118. package/src/lib/organize/filter-edit-model.test.ts +2 -32
  119. package/src/lib/organize/filter-edit-model.ts +6 -16
  120. package/src/lib/organize/organize-model.test.ts +7 -40
  121. package/src/lib/organize/organize-model.ts +16 -18
  122. package/src/lib/organize/rule-model.test.ts +24 -202
  123. package/src/lib/organize/rule-model.ts +19 -166
  124. package/src/lib/organize/search-to-rule.test.ts +2 -36
  125. package/src/lib/organize/search-to-rule.ts +1 -16
  126. package/src/lib/provider-presets.test.ts +1 -77
  127. package/src/lib/query-error-handler.test.ts +0 -61
  128. package/src/lib/query-escalation.integration.test.ts +4 -4
  129. package/src/lib/result-count.ts +1 -1
  130. package/src/lib/result-folder.ts +1 -1
  131. package/src/lib/rum-adapter.test.ts +0 -104
  132. package/src/lib/search-query.test.ts +0 -11
  133. package/src/lib/search-result.test.ts +2 -142
  134. package/src/lib/search-result.ts +3 -73
  135. package/src/lib/search-scope.test.ts +1 -92
  136. package/src/lib/search-scope.ts +0 -29
  137. package/src/lib/search-surface.ts +2 -2
  138. package/src/lib/search-token-index.test.ts +0 -8
  139. package/src/lib/search-tokens.ts +15 -16
  140. package/src/lib/starred-rows.test.ts +0 -4
  141. package/src/lib/sync-warning-entries.ts +33 -0
  142. package/src/lib/wizard-selection.ts +1 -1
  143. package/src/mocks/story-frame/mail-handlers.ts +12 -8
  144. package/src/mocks/story-frame/mail-world.ts +5 -0
  145. package/src/routeTree.gen.ts +21 -0
  146. package/src/routes/calendar/$view.$date/index.tsx +1 -1
  147. package/src/routes/calendar.tsx +2 -1
  148. package/src/routes/mail/brief.tsx +1 -1
  149. package/src/routes/mail/flagged.tsx +1 -1
  150. package/src/routes/mail.tsx +5 -3
  151. package/src/routes/settings/-accounts.test.ts +0 -5
  152. package/src/routes/settings/-filters.test.ts +0 -1
  153. package/src/routes/settings/-shared/help-copy.tsx +15 -0
  154. package/src/routes/settings/accounts.tsx +1 -1
  155. package/src/routes/settings/advanced.tsx +1 -4
  156. package/src/routes/settings/filters.tsx +1 -2
  157. package/src/routes/settings/folders.tsx +1 -1
  158. package/src/routes/settings/labels.tsx +1 -1
  159. package/src/routes/settings/senders.tsx +1 -1
  160. package/src/routes/settings/warnings.tsx +36 -0
  161. package/src/routes/settings.tsx +7 -0
  162. package/src/routing/wizard-history.test.ts +1 -45
  163. package/src/runtime-config.ts +5 -0
  164. package/src/test-support/fixtures.ts +1 -1
  165. package/src/components/compose/draft-discard-banner.test.ts +0 -76
  166. package/src/components/mail/ReadingPaneEmpty.render.test.ts +0 -42
  167. package/src/components/mail/SpamRescue.render.test.ts +0 -35
  168. package/src/components/mail/intelligence-semantic-off.render.test.ts +0 -111
  169. package/src/components/mail/selection-surface-rollcall.test.ts +0 -270
  170. package/src/components/settings/QuarantinePanel.render.test.ts +0 -71
  171. package/src/components/settings/QuarantinePanel.tsx +0 -178
  172. package/src/components/ui/DropdownMenu.tsx +0 -99
  173. package/src/components/ui/EmptyState.tsx +0 -11
  174. package/src/components/ui/ErrorBanner.render.test.ts +0 -104
  175. package/src/components/ui/ErrorBanner.stories.tsx +0 -132
  176. package/src/components/ui/ErrorBanner.tsx +0 -106
  177. package/src/components/ui/ErrorBannerStack.tsx +0 -35
  178. package/src/components/ui/ErrorState.render.test.ts +0 -82
  179. package/src/components/ui/ErrorState.tsx +0 -80
  180. package/src/components/ui/KeyboardShortcutsModal.tsx +0 -113
  181. package/src/hooks/useMoveMessages.test.ts +0 -63
  182. package/src/hooks/useOrganizeWiden.test.ts +0 -217
  183. package/src/hooks/useOrganizeWiden.ts +0 -112
  184. package/src/hooks/useSemanticSearch.ts +0 -132
  185. package/src/hooks/useSemanticSearchEnabled.ts +0 -23
  186. package/src/hooks/useVisualViewport.test.ts +0 -126
  187. package/src/lib/organize/sender-fallback.test.ts +0 -52
  188. package/src/lib/organize/sender-fallback.ts +0 -38
  189. package/src/lib/rescue-telemetry.test.ts +0 -75
  190. package/src/router.test.ts +0 -81
@@ -13,6 +13,7 @@ import type {
13
13
  import {
14
14
  Banner,
15
15
  type CandidateFolder,
16
+ ErrorState,
16
17
  FolderManager,
17
18
  FolderRenameDialog,
18
19
  type FolderRole,
@@ -26,7 +27,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
26
27
  import { createFileRoute, useNavigate } from "@tanstack/react-router";
27
28
  import { useMemo, useState } from "react";
28
29
  import { DeleteFolderDialog } from "@/components/settings/DeleteFolderDialog";
29
- import { ErrorState } from "@/components/ui/ErrorState";
30
30
  import { isMailboxNotSettledRefusal } from "@/components/ui/folder-role-refusal";
31
31
  import { useCreateMailbox } from "@/hooks/useCreateMailbox";
32
32
  import { useFolderLabelTranslator } from "@/hooks/useFolderLabelTranslator";
@@ -8,6 +8,7 @@ import {
8
8
  Banner,
9
9
  Button,
10
10
  ConfirmDialog,
11
+ ErrorState,
11
12
  Input,
12
13
  labelColorOptions,
13
14
  Select,
@@ -17,7 +18,6 @@ import { useQuery } from "@tanstack/react-query";
17
18
  import { createFileRoute, useNavigate } from "@tanstack/react-router";
18
19
  import { useState } from "react";
19
20
  import { LabelsList } from "@/components/settings/LabelsList";
20
- import { ErrorState } from "@/components/ui/ErrorState";
21
21
  import {
22
22
  useCreateLabel,
23
23
  useDeleteLabel,
@@ -20,6 +20,7 @@ import type {
20
20
  } from "@remit/api-http-client/types.gen.ts";
21
21
  import {
22
22
  Button,
23
+ ErrorState,
23
24
  Input,
24
25
  Kbd,
25
26
  SenderFlagRow,
@@ -30,7 +31,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
30
31
  import { createFileRoute, useNavigate } from "@tanstack/react-router";
31
32
  import { Search, Star, X } from "lucide-react";
32
33
  import { useCallback, useEffect, useState } from "react";
33
- import { ErrorState } from "@/components/ui/ErrorState";
34
34
  import { useDebouncedValue } from "@/hooks/useDebouncedValue";
35
35
  import { formatDate } from "@/lib/format";
36
36
  import { SETTINGS_ID_TO_PATH, SETTINGS_NAV_ITEMS } from "@/routes/settings";
@@ -0,0 +1,36 @@
1
+ import { SettingsShell } from "@remit/ui";
2
+ import { createFileRoute, useNavigate } from "@tanstack/react-router";
3
+ import { useState } from "react";
4
+ import { WarningsPanel } from "@/components/settings/WarningsPanel";
5
+ import { SETTINGS_ID_TO_PATH, SETTINGS_NAV_ITEMS } from "@/routes/settings";
6
+ import { warningsHelp } from "./-shared/help-copy";
7
+
8
+ export const Route = createFileRoute("/settings/warnings")({
9
+ component: WarningsSettings,
10
+ });
11
+
12
+ function WarningsSettings() {
13
+ const navigate = useNavigate();
14
+ const [helpOpen, setHelpOpen] = useState(true);
15
+
16
+ const handleSelectNav = (id: string) => {
17
+ const path = SETTINGS_ID_TO_PATH[id];
18
+ if (path) void navigate({ to: path });
19
+ };
20
+
21
+ return (
22
+ <SettingsShell
23
+ items={SETTINGS_NAV_ITEMS}
24
+ activeId="warnings"
25
+ title="Warnings"
26
+ description="Messages that failed to sync, and messages set aside."
27
+ help={warningsHelp}
28
+ helpOpen={helpOpen}
29
+ onToggleHelp={() => setHelpOpen((v) => !v)}
30
+ onSelect={handleSelectNav}
31
+ onBackToMail={() => void navigate({ to: "/mail" })}
32
+ >
33
+ <WarningsPanel />
34
+ </SettingsShell>
35
+ );
36
+ }
@@ -20,6 +20,7 @@ import {
20
20
  Users,
21
21
  } from "lucide-react";
22
22
  import { AdvancedNavIcon } from "@/components/settings/AdvancedNavIcon";
23
+ import { WarningsNavIcon } from "@/components/settings/WarningsNavIcon";
23
24
 
24
25
  /* ------------------------------------------------------------------ */
25
26
  /* Nav items — single source of truth shared by all settings pages */
@@ -57,6 +58,11 @@ export const SETTINGS_NAV_ITEMS: SettingsNavItem[] = [
57
58
  label: "Appearance",
58
59
  icon: <Palette className="size-4" />,
59
60
  },
61
+ {
62
+ id: "warnings",
63
+ label: "Warnings",
64
+ icon: <WarningsNavIcon />,
65
+ },
60
66
  { id: "advanced", label: "Advanced", icon: <AdvancedNavIcon /> },
61
67
  ];
62
68
 
@@ -68,6 +74,7 @@ export const SETTINGS_ID_TO_PATH: Record<string, string> = {
68
74
  labels: "/settings/labels",
69
75
  calendars: "/settings/calendars",
70
76
  appearance: "/settings/appearance",
77
+ warnings: "/settings/warnings",
71
78
  advanced: "/settings/advanced",
72
79
  };
73
80
 
@@ -1,8 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
- import { readFileSync } from "node:fs";
3
- import { dirname, resolve } from "node:path";
4
2
  import { describe, it } from "node:test";
5
- import { fileURLToPath } from "node:url";
6
3
  import {
7
4
  backExits,
8
5
  type MatchMode,
@@ -28,7 +25,7 @@ const VERBS: readonly Verb[] = [
28
25
  "markRead",
29
26
  "organize",
30
27
  ];
31
- const MODES: readonly MatchMode[] = ["selected", "similar", "properties"];
28
+ const MODES: readonly MatchMode[] = ["selected", "properties"];
32
29
  const SCOPES: readonly (RuleScope | undefined)[] = [
33
30
  undefined,
34
31
  "once",
@@ -50,51 +47,10 @@ const answerSets = (): WizardAnswers[] => {
50
47
  return sets;
51
48
  };
52
49
 
53
- const here = dirname(fileURLToPath(import.meta.url));
54
- const source = readFileSync(resolve(here, "wizard-history.ts"), "utf8");
55
-
56
50
  /** The steps up to and including the one an answer is given on. */
57
51
  const prefixThrough = (steps: readonly StepId[], step: StepId): StepId[] =>
58
52
  steps.slice(0, steps.indexOf(step) + 1);
59
53
 
60
- /**
61
- * The re-root runs inside a router, so the rule is enforced by reading the
62
- * source — as `../components/mail/MessageList.selection.test.ts` does for its
63
- * own component-level rules. What it pins is where the decision comes from: a
64
- * step the app itself pushed must never be re-rooted, or the push that opens
65
- * the wizard from a verb (#483) duplicates the entry underneath it and the
66
- * first back after Cancel appears to do nothing.
67
- */
68
- describe("re-rooting a wizard that was loaded into", () => {
69
- it("decides from what the first render held, not from a flag", () => {
70
- assert.match(source, /useRef\(step !== undefined\)/);
71
- assert.match(
72
- source,
73
- /if \(!loadedHoldingStep\.current\) return;\s*\n\s*loadedHoldingStep\.current = false;/,
74
- );
75
- });
76
-
77
- // The root is the wizard-closed state, so it carries neither the step nor
78
- // the affordance that opened it; otherwise closing the wizard leaves the
79
- // entry marker behind in the address bar (#484).
80
- it("puts the wizard back on a root that names no entry", () => {
81
- assert.match(source, /wizard: undefined,\s*\n\s*wizardFrom: undefined,/);
82
- assert.match(
83
- source,
84
- /wizard: openingStep,\s*\n\s*wizardFrom: openingEntry,/,
85
- );
86
- });
87
-
88
- it("is never armed by a step the app pushed", () => {
89
- const goToStep = source.slice(source.indexOf("const goToStep"));
90
- assert.doesNotMatch(goToStep, /loadedHoldingStep/);
91
- });
92
-
93
- it("does not re-run when the step changes", () => {
94
- assert.doesNotMatch(source, /\}, \[step, openingStep, navigate\]\)/);
95
- });
96
- });
97
-
98
54
  describe("the wizard step in the URL", () => {
99
55
  it("round-trips every step the wizard can reach", () => {
100
56
  for (const answers of answerSets()) {
@@ -26,6 +26,8 @@ export interface RumConfig {
26
26
  */
27
27
  export type TlsMode = "internal" | "acme" | "tailscale" | "off";
28
28
 
29
+ const DEFAULT_ISSUES_URL = "https://github.com/remit-mail/reader/issues/new";
30
+
29
31
  const TLS_MODES: readonly TlsMode[] = ["internal", "acme", "tailscale", "off"];
30
32
 
31
33
  const isTlsMode = (value: unknown): value is TlsMode =>
@@ -40,6 +42,7 @@ export interface RemitRuntimeConfig {
40
42
  mailboxPollIntervalSeconds?: string;
41
43
  disableDevtools: boolean;
42
44
  tlsMode: TlsMode;
45
+ issuesUrl: string;
43
46
  }
44
47
 
45
48
  interface RawRuntimeConfig {
@@ -51,6 +54,7 @@ interface RawRuntimeConfig {
51
54
  mailboxPollIntervalSeconds?: string | number;
52
55
  disableDevtools?: boolean;
53
56
  tlsMode?: string;
57
+ issuesUrl?: string;
54
58
  }
55
59
 
56
60
  declare global {
@@ -85,5 +89,6 @@ export const getRuntimeConfig = (): RemitRuntimeConfig => {
85
89
  : undefined,
86
90
  disableDevtools: raw.disableDevtools ?? false,
87
91
  tlsMode: isTlsMode(raw.tlsMode) ? raw.tlsMode : "off",
92
+ issuesUrl: raw.issuesUrl ?? DEFAULT_ISSUES_URL,
88
93
  };
89
94
  };
@@ -104,6 +104,6 @@ export const makeConfig = (
104
104
  updatedAt: 0,
105
105
  },
106
106
  accounts,
107
- semanticSearchEnabled: true,
107
+ buildCommit: "dev",
108
108
  ...overrides,
109
109
  });
@@ -1,76 +0,0 @@
1
- /**
2
- * Draft-discard must surface a banner on a soft failure.
3
- *
4
- * `ComposeForm.handleDiscard` closes the compose dialog optimistically, then
5
- * fires the delete mutation. Before #741 the delete had no `onError`, so a soft
6
- * 4xx (409/404 "the draft is already gone") closed the dialog as if it
7
- * succeeded — silent discard. The fix adds an `onError` that pushes a banner via
8
- * `buildMutationErrorBanner("Couldn't discard draft", …)`, mirroring the
9
- * outbox-delete pattern. A fatal 5xx still escalates globally; this pins the
10
- * non-fatal branch.
11
- *
12
- * Exercising the real mutation `onError` headlessly (no DOM) via the same
13
- * `MutationCache` wiring `ComposeForm` uses through `QueryClient`.
14
- */
15
-
16
- import assert from "node:assert/strict";
17
- import { describe, it } from "node:test";
18
- import { MutationCache, QueryClient } from "@tanstack/react-query";
19
- import { ApiError } from "../../lib/api.js";
20
- import {
21
- buildMutationErrorBanner,
22
- type PushErrorInput,
23
- } from "../ui/error-banners.js";
24
-
25
- describe("draft discard surfaces a banner on a soft failure (#741)", () => {
26
- const buildDiscardMutation = (
27
- pushError: (input: PushErrorInput) => void,
28
- fail: () => Promise<never>,
29
- ) => {
30
- const client = new QueryClient({
31
- mutationCache: new MutationCache(),
32
- });
33
- return client.getMutationCache().build(client, {
34
- mutationFn: fail,
35
- onError: (error: unknown) => {
36
- pushError(
37
- buildMutationErrorBanner(
38
- "Couldn't discard draft",
39
- "The draft wasn't discarded.",
40
- error,
41
- ),
42
- );
43
- },
44
- });
45
- };
46
-
47
- it("pushes a 'Couldn't discard draft' banner on a 409 already-gone", async () => {
48
- const pushed: PushErrorInput[] = [];
49
- const mutation = buildDiscardMutation(
50
- (input) => pushed.push(input),
51
- async () => {
52
- throw new ApiError("Draft already deleted", 409);
53
- },
54
- );
55
-
56
- await mutation.execute(undefined).catch(() => {});
57
-
58
- assert.equal(pushed.length, 1);
59
- assert.equal(pushed[0].title, "Couldn't discard draft");
60
- assert.equal(pushed[0].detail, "Draft already deleted");
61
- });
62
-
63
- it("falls back to a human detail when the error carries no message", async () => {
64
- const pushed: PushErrorInput[] = [];
65
- const mutation = buildDiscardMutation(
66
- (input) => pushed.push(input),
67
- async () => {
68
- throw {};
69
- },
70
- );
71
-
72
- await mutation.execute(undefined).catch(() => {});
73
-
74
- assert.equal(pushed[0].detail, "The draft wasn't discarded.");
75
- });
76
- });
@@ -1,42 +0,0 @@
1
- /**
2
- * Render invariants for the shared reading-pane zero-state and the keyboard
3
- * hint bar (#785). Both ship as remit-ui components consumed by the live mail
4
- * routes. Uses `react-dom/server` so no jsdom is needed.
5
- */
6
-
7
- import assert from "node:assert/strict";
8
- import { describe, it } from "node:test";
9
- import { KeyboardHintBar, ReadingPaneEmpty } from "@remit/ui";
10
- import React, { createElement } from "react";
11
- import { renderToString } from "react-dom/server";
12
-
13
- // See MessageToolbar.render.test.ts: the SSR test loader transpiles remit-ui
14
- // `.tsx` with the classic JSX runtime, which needs a global `React`.
15
- (globalThis as { React?: typeof React }).React = React;
16
-
17
- describe("ReadingPaneEmpty (#785)", () => {
18
- it("renders the prompt and the j/k/Enter Kbd hints by default", () => {
19
- const html = renderToString(createElement(ReadingPaneEmpty) as never);
20
- assert.match(html, /Select a thread to read/);
21
- assert.match(html, /<kbd[^>]*>j<\/kbd>/);
22
- assert.match(html, /<kbd[^>]*>k<\/kbd>/);
23
- assert.match(html, /<kbd[^>]*>Enter<\/kbd>/);
24
- });
25
-
26
- it("can hide the hint line on touch surfaces", () => {
27
- const html = renderToString(
28
- createElement(ReadingPaneEmpty, { showHints: false }) as never,
29
- );
30
- assert.equal(/<kbd/.test(html), false);
31
- });
32
- });
33
-
34
- describe("KeyboardHintBar (#785)", () => {
35
- it("renders the default hint set", () => {
36
- const html = renderToString(createElement(KeyboardHintBar) as never);
37
- assert.match(html, /navigate/);
38
- assert.match(html, /mute/);
39
- assert.match(html, /all shortcuts/);
40
- assert.match(html, /<kbd[^>]*>j<\/kbd>/);
41
- });
42
- });
@@ -1,35 +0,0 @@
1
- /**
2
- * The rescue banner is rendered by SpamRescue above the spam list. These tests
3
- * cover the banner's rendering with a count sourced from the backend (via
4
- * useRescueCandidates), exercising the component's output in isolation without
5
- * app providers.
6
- */
7
-
8
- import assert from "node:assert/strict";
9
- import { describe, it } from "node:test";
10
- import { RescueBanner } from "@remit/ui";
11
- import React, { createElement } from "react";
12
- import { renderToString } from "react-dom/server";
13
-
14
- (globalThis as { React?: typeof React }).React = React;
15
-
16
- const noop = () => {};
17
-
18
- const renderBanner = (count: number): string =>
19
- renderToString(createElement(RescueBanner, { count, onReview: noop }));
20
-
21
- describe("RescueBanner rendering", () => {
22
- it("shows the candidate count for multiple messages", () => {
23
- const html = renderBanner(2);
24
- assert.match(html, /2 messages here/);
25
- });
26
-
27
- it("shows singular phrasing for a single message", () => {
28
- const html = renderBanner(1);
29
- assert.ok(html.includes("1"), "count should appear");
30
- });
31
-
32
- it("renders without throwing for zero count", () => {
33
- assert.doesNotThrow(() => renderBanner(0));
34
- });
35
- });
@@ -1,111 +0,0 @@
1
- /**
2
- * "Similar messages" on an instance with semantic search off (#1068).
3
- *
4
- * Off is a deployment setting, and the client learns it from the config read
5
- * every surface already makes. Before this the section rendered nothing at all,
6
- * which reads as a sender nothing resembles — the one thing an operator who
7
- * has not turned semantic search on cannot tell from the truth.
8
- *
9
- * Drives the real chain: `useIntelligenceData` reads `GET /config`, the state
10
- * resolver turns `semanticSearchEnabled: false` into the off state, and the
11
- * real `IntelligencePanel` renders it. Both halves are asserted — the copy the
12
- * operator acts on, and that no semantic query is sent at all.
13
- */
14
-
15
- import assert from "node:assert/strict";
16
- import { afterEach, describe, it } from "node:test";
17
- import { IntelligencePanel } from "@remit/ui";
18
- import { createElement } from "react";
19
- import { useIntelligenceData } from "@/hooks/useIntelligenceData";
20
- import { createDomHarness, type DomHarness } from "@/test-support/dom";
21
- import {
22
- makeAccount,
23
- makeConfig,
24
- makeThreadMessage,
25
- } from "@/test-support/fixtures";
26
- import { type HttpMock, mockFetch } from "@/test-support/http";
27
- import { resolveSimilarState } from "./IntelligencePane";
28
-
29
- const thread = makeThreadMessage({ messageId: "msg-1" });
30
-
31
- const SimilarHarness = () => {
32
- const {
33
- data,
34
- isSimilarLoading,
35
- similarError,
36
- similarErrorIsFatal,
37
- semanticEnabled,
38
- } = useIntelligenceData(thread, "mbx-inbox");
39
- if (!data) return null;
40
- return createElement(IntelligencePanel, {
41
- data,
42
- similarState: resolveSimilarState({
43
- similarError,
44
- similarErrorIsFatal,
45
- isSimilarLoading,
46
- semanticEnabled,
47
- }),
48
- });
49
- };
50
-
51
- const respond =
52
- (semanticSearchEnabled: boolean) => (call: { path: string }) => {
53
- if (call.path.endsWith("/config")) {
54
- return makeConfig([makeAccount({ accountId: "acc-1" })], {
55
- semanticSearchEnabled,
56
- });
57
- }
58
- if (call.path.endsWith("/addresses/search")) return { items: [] };
59
- return { items: [] };
60
- };
61
-
62
- let harness: DomHarness | undefined;
63
- let http: HttpMock;
64
-
65
- afterEach(() => {
66
- harness?.close();
67
- harness = undefined;
68
- http.restore();
69
- });
70
-
71
- describe("the Similar messages section on an instance with semantic search off", () => {
72
- it("states the setting, what turning it on buys, and the command", async () => {
73
- http = mockFetch(respond(false));
74
- harness = createDomHarness();
75
- harness.renderApp(createElement(SimilarHarness));
76
-
77
- await harness.waitFor(
78
- () => harness?.text().includes("Semantic search is off") === true,
79
- );
80
-
81
- const text = harness.text();
82
- assert.match(text, /Semantic search is off on this instance/);
83
- assert.match(text, /Organize/);
84
- assert.match(text, /filters/);
85
- assert.match(text, /remit semantic on/);
86
- });
87
-
88
- it("sends no semantic query, because an empty answer is the same as no match", async () => {
89
- http = mockFetch(respond(false));
90
- harness = createDomHarness();
91
- harness.renderApp(createElement(SimilarHarness));
92
-
93
- await harness.waitFor(
94
- () => harness?.text().includes("Semantic search is off") === true,
95
- );
96
- await harness.wait(30);
97
- await harness.flush();
98
-
99
- assert.deepEqual(http.to("/search/semantic"), []);
100
- });
101
-
102
- it("says none of it while the instance has semantic search on", async () => {
103
- http = mockFetch(respond(true));
104
- harness = createDomHarness();
105
- harness.renderApp(createElement(SimilarHarness));
106
-
107
- await harness.waitFor(() => http.to("/search/semantic").length > 0);
108
-
109
- assert.doesNotMatch(harness.text(), /Semantic search is off/);
110
- });
111
- });