@remit/web-client 0.0.244 → 0.0.245

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.244",
3
+ "version": "0.0.245",
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": {
@@ -9,6 +9,7 @@
9
9
 
10
10
  import assert from "node:assert/strict";
11
11
  import { afterEach, describe, it } from "node:test";
12
+ import { meOperationsListQuarantineOptions } from "@remit/api-http-client/@tanstack/react-query.gen.ts";
12
13
  import { createElement } from "react";
13
14
  import { createDomHarness, type DomHarness } from "../../test-support/dom";
14
15
  import { type HttpMock, httpError, mockFetch } from "../../test-support/http";
@@ -62,26 +63,47 @@ afterEach(() => {
62
63
  http = undefined;
63
64
  });
64
65
 
65
- const mount = async (mailboxes: () => unknown): Promise<DomHarness> => {
66
+ /**
67
+ * The quarantine list is the read that gives the pane something to paint: until
68
+ * it has landed, an assertion that no path is showing passes because there are
69
+ * no rows at all, which is not what any of these cases claims to test.
70
+ */
71
+ const quarantineLanded = (view: DomHarness): boolean =>
72
+ view.queryClient.getQueryState(meOperationsListQuarantineOptions().queryKey)
73
+ ?.status === "success";
74
+
75
+ const mount = async (
76
+ mailboxes: () => unknown,
77
+ settled: (view: DomHarness) => boolean,
78
+ description: string,
79
+ ): Promise<DomHarness> => {
66
80
  http = mockFetch((call) => {
67
81
  if (call.path.endsWith("/config")) return CONFIG;
68
82
  if (call.path.endsWith("/mailboxes")) return mailboxes();
69
83
  if (call.path.endsWith("/quarantine")) return QUARANTINE;
70
84
  return {};
71
85
  });
72
- harness = createDomHarness();
73
- harness.renderApp(createElement(QuarantinePanel));
86
+ const view = createDomHarness();
87
+ harness = view;
88
+ view.renderApp(createElement(QuarantinePanel));
74
89
  // Two waves: the mailbox-list fan-out is keyed on the accounts `/config`
75
- // returns, so it cannot even start until that first read has settled.
76
- await harness.flush();
77
- await harness.wait(0);
78
- await harness.flush();
79
- return harness;
90
+ // returns, so it cannot even start until that first read has settled. How
91
+ // many turns those waves take is the runner's business, so each case names
92
+ // the state it needs instead of counting them.
93
+ await view.waitFor(() => settled(view), description);
94
+ return view;
80
95
  };
81
96
 
82
97
  describe("QuarantinePanel folder names", () => {
83
98
  it("waits for the account's delimiter rather than cutting on a guess", async () => {
84
- const view = await mount(() => new Promise(() => {}));
99
+ // The mailbox read never resolves here, so settled means every read that
100
+ // can land has: the entries are in hand and only the delimiter is missing.
101
+ const view = await mount(
102
+ () => new Promise(() => {}),
103
+ (pane) =>
104
+ quarantineLanded(pane) && (http?.to("/mailboxes").length ?? 0) > 0,
105
+ "the quarantine list to land while the mailbox read hangs",
106
+ );
85
107
 
86
108
  assert.match(view.text(), /Checking for messages set aside/);
87
109
  assert.doesNotMatch(
@@ -91,7 +113,11 @@ describe("QuarantinePanel folder names", () => {
91
113
  });
92
114
 
93
115
  it("names the folder by its leaf once the delimiter has arrived", async () => {
94
- const view = await mount(() => MAILBOXES);
116
+ const view = await mount(
117
+ () => MAILBOXES,
118
+ (pane) => /Q3/.test(pane.text()),
119
+ "the folder leaf to be named",
120
+ );
95
121
 
96
122
  assert.match(view.text(), /Q3/);
97
123
  assert.doesNotMatch(
@@ -101,7 +127,11 @@ describe("QuarantinePanel folder names", () => {
101
127
  });
102
128
 
103
129
  it("reports a failed mailbox read instead of naming folders on a guess", async () => {
104
- const view = await mount(() => httpError(500));
130
+ const view = await mount(
131
+ () => httpError(500),
132
+ (pane) => /role="alert"/.test(pane.html()),
133
+ "the failed mailbox read to be reported",
134
+ );
105
135
 
106
136
  assert.match(view.html(), /role="alert"/);
107
137
  assert.doesNotMatch(
@@ -39,6 +39,16 @@ export interface DomHarness {
39
39
  flush: () => Promise<void>;
40
40
  /** Let real timers fire — some flows stage their steps on a delay. */
41
41
  wait: (ms: number) => Promise<void>;
42
+ /**
43
+ * Pump the render until `predicate` holds, then return with the DOM current.
44
+ * A drain counted in ticks is a guess about how many awaits a read chain
45
+ * takes, and a loaded runner always finds one more; a condition is not.
46
+ */
47
+ waitFor: (
48
+ predicate: () => boolean,
49
+ description?: string,
50
+ timeoutMs?: number,
51
+ ) => Promise<void>;
42
52
  }
43
53
 
44
54
  export interface DomOptions {
@@ -186,6 +196,24 @@ export const createDomHarness = (options: DomOptions = {}): DomHarness => {
186
196
  await new Promise((resolve) => setTimeout(resolve, ms));
187
197
  });
188
198
  },
199
+ waitFor: async (
200
+ predicate,
201
+ description = "the render to settle",
202
+ timeoutMs = 2_000,
203
+ ) => {
204
+ const deadline = Date.now() + timeoutMs;
205
+ for (;;) {
206
+ await harness.flush();
207
+ if (predicate()) return;
208
+ if (Date.now() > deadline) {
209
+ const shown = harness.text().trim() || "(nothing rendered)";
210
+ throw new Error(
211
+ `waited ${timeoutMs}ms for ${description}; the render shows: ${shown.slice(0, 400)}`,
212
+ );
213
+ }
214
+ await harness.wait(1);
215
+ }
216
+ },
189
217
  flush: async () => {
190
218
  // Enough turns for a chain of awaits — form validation, then the
191
219
  // mutation, then its `onSuccess` — to settle.