@agent-native/toolkit 0.211.0 → 0.212.0-nightly-20261021010049

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 (143) hide show
  1. package/agent-native.eject.json +1 -0
  2. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  3. package/dist/app/i18n/catalogs/ar-SA.js +23 -0
  4. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  5. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  6. package/dist/app/i18n/catalogs/de-DE.js +23 -0
  7. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  8. package/dist/app/i18n/catalogs/en-US.d.ts +23 -0
  9. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  10. package/dist/app/i18n/catalogs/en-US.js +23 -0
  11. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  12. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  13. package/dist/app/i18n/catalogs/es-ES.js +23 -0
  14. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  15. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  16. package/dist/app/i18n/catalogs/fr-FR.js +23 -0
  17. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  18. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  19. package/dist/app/i18n/catalogs/hi-IN.js +23 -0
  20. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  21. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  22. package/dist/app/i18n/catalogs/ja-JP.js +23 -0
  23. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  24. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  25. package/dist/app/i18n/catalogs/ko-KR.js +23 -0
  26. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  27. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  28. package/dist/app/i18n/catalogs/pt-BR.js +23 -0
  29. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  30. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  31. package/dist/app/i18n/catalogs/zh-CN.js +23 -0
  32. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  33. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  34. package/dist/app/i18n/catalogs/zh-TW.js +23 -0
  35. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  36. package/dist/app/sharing/OwnershipFilter.d.ts +8 -0
  37. package/dist/app/sharing/OwnershipFilter.d.ts.map +1 -0
  38. package/dist/app/sharing/OwnershipFilter.js +20 -0
  39. package/dist/app/sharing/OwnershipFilter.js.map +1 -0
  40. package/dist/app/sharing/ShareButton.d.ts +43 -1
  41. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  42. package/dist/app/sharing/ShareButton.js +102 -63
  43. package/dist/app/sharing/ShareButton.js.map +1 -1
  44. package/dist/app/sharing/ShareDialog.d.ts +3 -1
  45. package/dist/app/sharing/ShareDialog.d.ts.map +1 -1
  46. package/dist/app/sharing/ShareDialog.js +18 -12
  47. package/dist/app/sharing/ShareDialog.js.map +1 -1
  48. package/dist/app/sharing/ShareVisibilityConfirmDialog.d.ts +20 -0
  49. package/dist/app/sharing/ShareVisibilityConfirmDialog.d.ts.map +1 -0
  50. package/dist/app/sharing/ShareVisibilityConfirmDialog.js +57 -0
  51. package/dist/app/sharing/ShareVisibilityConfirmDialog.js.map +1 -0
  52. package/dist/app/sharing/VisibilityBadge.d.ts +8 -1
  53. package/dist/app/sharing/VisibilityBadge.d.ts.map +1 -1
  54. package/dist/app/sharing/VisibilityBadge.js +16 -1
  55. package/dist/app/sharing/VisibilityBadge.js.map +1 -1
  56. package/dist/app/sharing/index.d.ts +2 -0
  57. package/dist/app/sharing/index.d.ts.map +1 -1
  58. package/dist/app/sharing/index.js +2 -0
  59. package/dist/app/sharing/index.js.map +1 -1
  60. package/dist/chat-history/ChatHistoryList.d.ts +19 -0
  61. package/dist/chat-history/ChatHistoryList.d.ts.map +1 -1
  62. package/dist/chat-history/ChatHistoryList.js +19 -7
  63. package/dist/chat-history/ChatHistoryList.js.map +1 -1
  64. package/dist/chat-history/ChatHistoryRail.d.ts +55 -8
  65. package/dist/chat-history/ChatHistoryRail.d.ts.map +1 -1
  66. package/dist/chat-history/ChatHistoryRail.js +33 -7
  67. package/dist/chat-history/ChatHistoryRail.js.map +1 -1
  68. package/dist/chat-history/index.d.ts +3 -3
  69. package/dist/chat-history/index.d.ts.map +1 -1
  70. package/dist/chat-history/index.js +1 -1
  71. package/dist/chat-history/index.js.map +1 -1
  72. package/dist/chat-history/useChatHistoryRailController.d.ts +44 -0
  73. package/dist/chat-history/useChatHistoryRailController.d.ts.map +1 -1
  74. package/dist/chat-history/useChatHistoryRailController.js +46 -0
  75. package/dist/chat-history/useChatHistoryRailController.js.map +1 -1
  76. package/dist/chat-history.css +29 -0
  77. package/dist/composer/TiptapComposer.d.ts +7 -0
  78. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  79. package/dist/composer/TiptapComposer.js +15 -2
  80. package/dist/composer/TiptapComposer.js.map +1 -1
  81. package/dist/sharing/OwnershipFilter.d.ts +21 -0
  82. package/dist/sharing/OwnershipFilter.d.ts.map +1 -0
  83. package/dist/sharing/OwnershipFilter.js +30 -0
  84. package/dist/sharing/OwnershipFilter.js.map +1 -0
  85. package/dist/sharing/VisibilityBadge.d.ts +12 -2
  86. package/dist/sharing/VisibilityBadge.d.ts.map +1 -1
  87. package/dist/sharing/VisibilityBadge.js +13 -4
  88. package/dist/sharing/VisibilityBadge.js.map +1 -1
  89. package/dist/sharing/index.d.ts +2 -1
  90. package/dist/sharing/index.d.ts.map +1 -1
  91. package/dist/sharing/index.js +2 -1
  92. package/dist/sharing/index.js.map +1 -1
  93. package/dist/ui/alert-dialog.d.ts +2 -0
  94. package/dist/ui/alert-dialog.d.ts.map +1 -1
  95. package/dist/ui/alert-dialog.js +1 -1
  96. package/dist/ui/alert-dialog.js.map +1 -1
  97. package/dist/ui/tooltip.d.ts +6 -1
  98. package/dist/ui/tooltip.d.ts.map +1 -1
  99. package/dist/ui/tooltip.js +13 -1
  100. package/dist/ui/tooltip.js.map +1 -1
  101. package/package.json +4 -4
  102. package/src/app/i18n/catalogs/ar-SA.ts +26 -0
  103. package/src/app/i18n/catalogs/de-DE.ts +25 -0
  104. package/src/app/i18n/catalogs/en-US.ts +25 -0
  105. package/src/app/i18n/catalogs/es-ES.ts +25 -0
  106. package/src/app/i18n/catalogs/fr-FR.ts +25 -0
  107. package/src/app/i18n/catalogs/hi-IN.ts +25 -0
  108. package/src/app/i18n/catalogs/ja-JP.ts +25 -0
  109. package/src/app/i18n/catalogs/ko-KR.ts +25 -0
  110. package/src/app/i18n/catalogs/pt-BR.ts +25 -0
  111. package/src/app/i18n/catalogs/zh-CN.ts +25 -0
  112. package/src/app/i18n/catalogs/zh-TW.ts +25 -0
  113. package/src/app/sharing/OwnershipFilter.tsx +35 -0
  114. package/src/app/sharing/ShareButton.tsx +384 -259
  115. package/src/app/sharing/ShareDialog.spec.tsx +50 -0
  116. package/src/app/sharing/ShareDialog.tsx +51 -15
  117. package/src/app/sharing/ShareVisibilityConfirmDialog.spec.tsx +138 -0
  118. package/src/app/sharing/ShareVisibilityConfirmDialog.tsx +124 -0
  119. package/src/app/sharing/ShareVisibilityKeyboard.spec.tsx +244 -0
  120. package/src/app/sharing/VisibilityBadge.spec.tsx +65 -0
  121. package/src/app/sharing/VisibilityBadge.tsx +31 -3
  122. package/src/app/sharing/index.ts +2 -0
  123. package/src/app/sharing/share-visibility-mutation-lifecycle.spec.tsx +1107 -0
  124. package/src/app/sharing/useShareButtonController.spec.tsx +226 -37
  125. package/src/app/sharing/useShareDialogController.spec.tsx +357 -9
  126. package/src/chat-history/ChatHistoryList.spec.tsx +157 -0
  127. package/src/chat-history/ChatHistoryList.tsx +104 -10
  128. package/src/chat-history/ChatHistoryRail.spec.tsx +50 -1
  129. package/src/chat-history/ChatHistoryRail.tsx +177 -26
  130. package/src/chat-history/ChatHistoryRailSections.spec.tsx +410 -0
  131. package/src/chat-history/index.ts +8 -0
  132. package/src/chat-history/useChatHistoryRailController.ts +94 -0
  133. package/src/chat-history/useChatHistoryRailSectionsController.spec.tsx +133 -0
  134. package/src/chat-history.css +29 -0
  135. package/src/composer/TiptapComposer.spec.ts +19 -0
  136. package/src/composer/TiptapComposer.tsx +26 -5
  137. package/src/sharing/OwnershipFilter.spec.tsx +108 -0
  138. package/src/sharing/OwnershipFilter.tsx +103 -0
  139. package/src/sharing/VisibilityBadge.spec.ts +53 -35
  140. package/src/sharing/VisibilityBadge.tsx +23 -3
  141. package/src/sharing/index.ts +11 -0
  142. package/src/ui/alert-dialog.tsx +7 -2
  143. package/src/ui/tooltip.tsx +22 -1
@@ -5,7 +5,9 @@ import {
5
5
  type ShareButtonController,
6
6
  type ShareButtonControllerOptions,
7
7
  type ShareButtonSharesResponse,
8
+ type ShareButtonVisibility,
8
9
  } from "@agent-native/core/client/sharing/useShareButtonController";
10
+ import { ShareVisibilityNotSavedError } from "@agent-native/core/client/sharing/useShareVisibilityTransition";
9
11
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
10
12
  import React, { act } from "react";
11
13
  import { createRoot, type Root } from "react-dom/client";
@@ -17,11 +19,23 @@ const mocks = vi.hoisted(() => ({
17
19
  data: undefined as ShareButtonSharesResponse | undefined,
18
20
  refetch: vi.fn(async () => undefined),
19
21
  },
20
- setVisibility: { mutate: vi.fn() },
22
+ setVisibility: { mutate: vi.fn(), mutateAsync: vi.fn() },
21
23
  share: { mutate: vi.fn() },
22
24
  unshare: { mutate: vi.fn() },
23
25
  }));
24
26
 
27
+ // The controllers settle each save through mutateAsync; the tests drive it
28
+ // through the callbacks recorded on `mutate`.
29
+ mocks.setVisibility.mutateAsync.mockImplementation(
30
+ (variables: unknown) =>
31
+ new Promise((resolve, reject) => {
32
+ mocks.setVisibility.mutate(variables, {
33
+ onSuccess: resolve,
34
+ onError: reject,
35
+ });
36
+ }),
37
+ );
38
+
25
39
  vi.mock("@agent-native/core/client/use-action", () => ({
26
40
  useActionQuery: mocks.useActionQuery,
27
41
  useActionMutation: vi.fn((name: string) => {
@@ -61,6 +75,25 @@ describe("useShareButtonController", () => {
61
75
  return controller as ShareButtonController;
62
76
  }
63
77
 
78
+ async function renderOpen(
79
+ nextOptions: ShareButtonControllerOptions = options,
80
+ ): Promise<ShareButtonController> {
81
+ const result = await render(nextOptions);
82
+ act(() => result.handleOpenChange(true));
83
+ return controller as ShareButtonController;
84
+ }
85
+
86
+ function chooseVisibility(next: ShareButtonVisibility) {
87
+ act(() => (controller as ShareButtonController).handleVisibility(next));
88
+ if ((controller as ShareButtonController).visibilityTransition.pending) {
89
+ act(() => {
90
+ void (
91
+ controller as ShareButtonController
92
+ ).visibilityTransition.confirm();
93
+ });
94
+ }
95
+ }
96
+
64
97
  beforeEach(() => {
65
98
  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
66
99
  vi.stubGlobal(
@@ -100,7 +133,13 @@ describe("useShareButtonController", () => {
100
133
  root = createRoot(container);
101
134
  });
102
135
 
103
- afterEach(() => {
136
+ afterEach(async () => {
137
+ await act(async () => {
138
+ for (const [variables, callbacks] of mocks.setVisibility.mutate.mock
139
+ .calls) {
140
+ callbacks?.onSuccess?.({ visibility: variables.visibility });
141
+ }
142
+ });
104
143
  act(() => root.unmount());
105
144
  queryClient.clear();
106
145
  container.remove();
@@ -208,8 +247,8 @@ describe("useShareButtonController", () => {
208
247
 
209
248
  it("reports a private plan becoming shared to its host", async () => {
210
249
  const onShareSuccess = vi.fn();
211
- const result = await render({ ...options, onShareSuccess });
212
- act(() => result.handleVisibility("org"));
250
+ await renderOpen({ ...options, onShareSuccess });
251
+ chooseVisibility("org");
213
252
 
214
253
  const mutation = mocks.setVisibility.mutate.mock.calls[0]?.[1];
215
254
  await act(async () => {
@@ -220,34 +259,34 @@ describe("useShareButtonController", () => {
220
259
  expect(onShareSuccess).toHaveBeenCalledOnce();
221
260
  });
222
261
 
223
- it("reports a rapid private-to-shared visibility sequence from its final result", async () => {
262
+ it("reports the actual private-to-shared save once after refusing a different target", async () => {
224
263
  const onShareSuccess = vi.fn();
225
- const result = await render({ ...options, onShareSuccess });
226
- act(() => result.handleVisibility("org"));
227
- const staleMutation = mocks.setVisibility.mutate.mock.calls[0]?.[1];
228
- act(() => (controller as ShareButtonController).handleVisibility("public"));
229
- const latestMutation = mocks.setVisibility.mutate.mock.calls[1]?.[1];
264
+ await renderOpen({ ...options, onShareSuccess });
265
+ chooseVisibility("public");
266
+ const mutation = mocks.setVisibility.mutate.mock.calls[0]?.[1];
267
+ chooseVisibility("org");
268
+ expect(mocks.setVisibility.mutate).toHaveBeenCalledOnce();
269
+ expect((controller as ShareButtonController).visibility).toBe("public");
270
+ expect(onShareSuccess).not.toHaveBeenCalled();
230
271
 
231
272
  await act(async () => {
232
- staleMutation?.onSuccess?.({ visibility: "org" });
233
- latestMutation?.onSuccess?.({ visibility: "public" });
273
+ mutation?.onSuccess?.({ visibility: "public" });
234
274
  await Promise.resolve();
235
275
  });
236
276
 
237
277
  expect(onShareSuccess).toHaveBeenCalledOnce();
238
278
  });
239
279
 
240
- it("ignores stale visibility success and requires an authoritative shared result", async () => {
280
+ it("requires an authoritative shared result after refusing a different target", async () => {
241
281
  const onShareSuccess = vi.fn();
242
- const result = await render({ ...options, onShareSuccess });
243
- act(() => result.handleVisibility("org"));
244
- const staleMutation = mocks.setVisibility.mutate.mock.calls[0]?.[1];
245
- act(() => result.handleVisibility("org"));
246
- const latestMutation = mocks.setVisibility.mutate.mock.calls[1]?.[1];
282
+ await renderOpen({ ...options, onShareSuccess });
283
+ chooseVisibility("public");
284
+ const mutation = mocks.setVisibility.mutate.mock.calls[0]?.[1];
285
+ chooseVisibility("org");
286
+ expect(mocks.setVisibility.mutate).toHaveBeenCalledOnce();
247
287
 
248
288
  await act(async () => {
249
- staleMutation?.onSuccess?.({ visibility: "org" });
250
- latestMutation?.onSuccess?.({ visibility: "private" });
289
+ mutation?.onSuccess?.({ visibility: "private" });
251
290
  await Promise.resolve();
252
291
  });
253
292
 
@@ -267,8 +306,8 @@ describe("useShareButtonController", () => {
267
306
  ],
268
307
  initial,
269
308
  );
270
- const result = await render();
271
- act(() => result.handleVisibility("org"));
309
+ await renderOpen();
310
+ chooseVisibility("org");
272
311
  const callback = mocks.setVisibility.mutate.mock.calls[0]?.[1];
273
312
  await act(async () => {
274
313
  callback?.onError(new Error("visibility failed"));
@@ -283,9 +322,12 @@ describe("useShareButtonController", () => {
283
322
  ]);
284
323
  expect(restored).toEqual(initial);
285
324
  expect((controller as ShareButtonController).visibilityOverride).toBeNull();
286
- expect((controller as ShareButtonController).shareError).toBe(
287
- "visibility failed",
288
- );
325
+ expect(
326
+ (controller as ShareButtonController).visibilityTransition.error,
327
+ ).toBe("visibility failed");
328
+ expect(
329
+ (controller as ShareButtonController).visibilityTransition.pending,
330
+ ).toEqual({ from: "private", to: "org" });
289
331
  });
290
332
 
291
333
  it("restores the sequence snapshot and refetches when the latest visibility update fails", async () => {
@@ -299,24 +341,17 @@ describe("useShareButtonController", () => {
299
341
  { resourceType: "document", resourceId: "doc-1" },
300
342
  ] as const;
301
343
  queryClient.setQueryData(shareQueryKey, initial);
302
- const result = await render();
303
- let firstPromise!: Promise<void>;
304
- act(() => {
305
- firstPromise = result.handleVisibilityChange("org");
306
- });
344
+ await renderOpen();
345
+ chooseVisibility("public");
307
346
  const firstMutation = mocks.setVisibility.mutate.mock.calls[0]?.[1];
308
- let latestPromise!: Promise<void>;
309
- act(() => {
310
- latestPromise = (
311
- controller as ShareButtonController
312
- ).handleVisibilityChange("public");
313
- });
347
+ chooseVisibility("org");
314
348
  const latestMutation = mocks.setVisibility.mutate.mock.calls[1]?.[1];
315
349
 
316
350
  await act(async () => {
317
351
  firstMutation?.onError(new Error("stale visibility failure"));
318
352
  latestMutation?.onError(new Error("latest visibility failure"));
319
- await Promise.allSettled([firstPromise, latestPromise]);
353
+ await Promise.resolve();
354
+ await Promise.resolve();
320
355
  });
321
356
 
322
357
  expect(queryClient.getQueryData(shareQueryKey)).toEqual(initial);
@@ -325,6 +360,160 @@ describe("useShareButtonController", () => {
325
360
  expect((controller as ShareButtonController).visibility).toBe("private");
326
361
  });
327
362
 
363
+ it("keeps the deprecated handleVisibilityChange behind the confirmation", async () => {
364
+ await renderOpen();
365
+
366
+ act(() => {
367
+ void (controller as ShareButtonController).handleVisibilityChange("org");
368
+ });
369
+
370
+ expect(mocks.setVisibility.mutate).not.toHaveBeenCalled();
371
+ expect(
372
+ (controller as ShareButtonController).visibilityTransition.pending,
373
+ ).toEqual({ from: "private", to: "org" });
374
+ });
375
+
376
+ it("resolves the deprecated alias only once a direct change is persisted", async () => {
377
+ await renderOpen();
378
+ let settled = false;
379
+ let alias!: Promise<void>;
380
+ act(() => {
381
+ alias = (controller as ShareButtonController)
382
+ .handleVisibilityChange("public")
383
+ .then(() => {
384
+ settled = true;
385
+ });
386
+ });
387
+ await act(async () => {
388
+ await Promise.resolve();
389
+ });
390
+ expect(settled).toBe(false);
391
+
392
+ await act(async () => {
393
+ mocks.setVisibility.mutate.mock.calls[0]?.[1]?.onSuccess({
394
+ visibility: "public",
395
+ });
396
+ await alias;
397
+ });
398
+ expect(settled).toBe(true);
399
+ });
400
+
401
+ it("rejects the deprecated alias when a direct change fails", async () => {
402
+ await renderOpen();
403
+ let alias!: Promise<void>;
404
+ act(() => {
405
+ alias = (controller as ShareButtonController).handleVisibilityChange(
406
+ "public",
407
+ );
408
+ });
409
+
410
+ await act(async () => {
411
+ mocks.setVisibility.mutate.mock.calls[0]?.[1]?.onError(
412
+ new Error("visibility failed"),
413
+ );
414
+ await expect(alias).rejects.toThrow("visibility failed");
415
+ });
416
+ });
417
+
418
+ it("keeps a held alias change unresolved until confirmed and rejects it when cancelled", async () => {
419
+ await renderOpen();
420
+ const transition = () =>
421
+ (controller as ShareButtonController).visibilityTransition;
422
+ let confirmed = false;
423
+ let alias!: Promise<void>;
424
+ act(() => {
425
+ alias = (controller as ShareButtonController)
426
+ .handleVisibilityChange("org")
427
+ .then(() => {
428
+ confirmed = true;
429
+ });
430
+ });
431
+ await act(async () => {
432
+ await Promise.resolve();
433
+ });
434
+ expect(confirmed).toBe(false);
435
+
436
+ await act(async () => {
437
+ void transition().confirm();
438
+ });
439
+ await act(async () => {
440
+ mocks.setVisibility.mutate.mock.calls[0]?.[1]?.onSuccess({
441
+ visibility: "org",
442
+ });
443
+ await alias;
444
+ });
445
+ expect(confirmed).toBe(true);
446
+
447
+ mocks.setVisibility.mutate.mockReset();
448
+ mocks.query.data = { ...mocks.query.data!, visibility: "private" };
449
+ await render();
450
+ let cancelled!: Promise<void>;
451
+ act(() => {
452
+ cancelled = (controller as ShareButtonController).handleVisibilityChange(
453
+ "org",
454
+ );
455
+ });
456
+ act(() => transition().cancel());
457
+ await expect(cancelled).rejects.toBeInstanceOf(
458
+ ShareVisibilityNotSavedError,
459
+ );
460
+ expect(mocks.setVisibility.mutate).not.toHaveBeenCalled();
461
+ });
462
+
463
+ it("does not carry a pending change from one resource into the next", async () => {
464
+ mocks.query.data = { ...mocks.query.data!, visibility: "private" };
465
+ await renderOpen();
466
+ act(() => (controller as ShareButtonController).handleVisibility("public"));
467
+ expect(mocks.setVisibility.mutate).toHaveBeenCalledTimes(1);
468
+ expect((controller as ShareButtonController).visibility).toBe("public");
469
+
470
+ // The mounted controller moves to another resource while the first write
471
+ // is still in flight.
472
+ await render({ ...options, resourceId: "doc-2" });
473
+ expect((controller as ShareButtonController).visibility).toBe("private");
474
+ expect((controller as ShareButtonController).visibilityOverride).toBeNull();
475
+
476
+ act(() => (controller as ShareButtonController).handleVisibility("org"));
477
+ expect(
478
+ (controller as ShareButtonController).visibilityTransition.pending,
479
+ ).toEqual({ from: "private", to: "org" });
480
+ expect(mocks.setVisibility.mutate).toHaveBeenCalledTimes(1);
481
+
482
+ // The first resource's write finishes late: it neither clears nor
483
+ // confirms anything for the second one.
484
+ await act(async () => {
485
+ mocks.setVisibility.mutate.mock.calls[0]?.[1]?.onSuccess({
486
+ visibility: "public",
487
+ });
488
+ await Promise.resolve();
489
+ });
490
+ expect(
491
+ (controller as ShareButtonController).visibilityTransition.pending,
492
+ ).toEqual({ from: "private", to: "org" });
493
+ expect((controller as ShareButtonController).visibility).toBe("private");
494
+
495
+ await act(async () => {
496
+ void (controller as ShareButtonController).visibilityTransition.confirm();
497
+ });
498
+ expect(mocks.setVisibility.mutate).toHaveBeenCalledTimes(2);
499
+ expect(mocks.setVisibility.mutate.mock.calls[1]?.[0]).toMatchObject({
500
+ resourceId: "doc-2",
501
+ visibility: "org",
502
+ });
503
+ });
504
+
505
+ it("ignores visibility requests while the surface is closed", async () => {
506
+ const result = await render();
507
+
508
+ act(() => result.handleVisibility("public"));
509
+ act(() => result.handleVisibility("org"));
510
+
511
+ expect(mocks.setVisibility.mutate).not.toHaveBeenCalled();
512
+ expect(
513
+ (controller as ShareButtonController).visibilityTransition.pending,
514
+ ).toBeNull();
515
+ });
516
+
328
517
  it("gates every sharing mutation for non-managers", async () => {
329
518
  mocks.query.data = { ...mocks.query.data!, role: "viewer" };
330
519
  const result = await render();