@agent-native/toolkit 0.198.3 → 0.198.5
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/dist/app/agentkit/react/components.d.ts +8 -3
- package/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +24 -10
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +65 -33
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/app/chat/AgentSidebar.d.ts.map +1 -1
- package/dist/app/chat/AgentSidebar.js +2 -0
- package/dist/app/chat/AgentSidebar.js.map +1 -1
- package/dist/app/chat/FileStorageSetupPopover.js +2 -2
- package/dist/app/chat/FileStorageSetupPopover.js.map +1 -1
- package/dist/app/chat/MultiTabAssistantChat.js +1 -1
- package/dist/app/chat/agentkit-chat/connections.d.ts +1 -4
- package/dist/app/chat/agentkit-chat/connections.d.ts.map +1 -1
- package/dist/app/chat/agentkit-chat/connections.js +1 -26
- package/dist/app/chat/agentkit-chat/connections.js.map +1 -1
- package/dist/app/chat/agentkit-chat/history.d.ts +4 -1
- package/dist/app/chat/agentkit-chat/history.d.ts.map +1 -1
- package/dist/app/chat/agentkit-chat/history.js +17 -5
- package/dist/app/chat/agentkit-chat/history.js.map +1 -1
- package/dist/app/chat/chat/history-types.d.ts +1 -1
- package/dist/app/chat/chat/history-types.d.ts.map +1 -1
- package/dist/app/chat/chat-first/copy.d.ts +1 -2
- package/dist/app/chat/chat-first/copy.d.ts.map +1 -1
- package/dist/app/chat/chat-first/copy.js +1 -121
- package/dist/app/chat/chat-first/copy.js.map +1 -1
- package/dist/app/chat/chat-first-copy.d.ts +3 -0
- package/dist/app/chat/chat-first-copy.d.ts.map +1 -0
- package/dist/app/chat/chat-first-copy.js +122 -0
- package/dist/app/chat/chat-first-copy.js.map +1 -0
- package/dist/app/feature-flags/FeatureFlagsPanel.d.ts.map +1 -1
- package/dist/app/feature-flags/FeatureFlagsPanel.js +1 -0
- package/dist/app/feature-flags/FeatureFlagsPanel.js.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.js +2 -1
- package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.js +2 -1
- package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
- package/dist/app/i18n/catalogs/en-US.d.ts +2 -1
- package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/en-US.js +2 -1
- package/dist/app/i18n/catalogs/en-US.js.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.js +2 -1
- package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.js +2 -1
- package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.js +2 -1
- package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.js +2 -1
- package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.js +2 -1
- package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.js +2 -1
- package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.js +2 -1
- package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.js +2 -1
- package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
- package/dist/app/labs/LabsSettings.d.ts.map +1 -1
- package/dist/app/labs/LabsSettings.js +7 -3
- package/dist/app/labs/LabsSettings.js.map +1 -1
- package/dist/app/org/OrgGeneralSection.d.ts.map +1 -1
- package/dist/app/org/OrgGeneralSection.js +21 -18
- package/dist/app/org/OrgGeneralSection.js.map +1 -1
- package/dist/app/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/app/org/OrgSwitcher.js +3 -2
- package/dist/app/org/OrgSwitcher.js.map +1 -1
- package/dist/app/org/workspace-app-links.d.ts +1 -0
- package/dist/app/org/workspace-app-links.d.ts.map +1 -1
- package/dist/app/org/workspace-app-links.js +3 -0
- package/dist/app/org/workspace-app-links.js.map +1 -1
- package/dist/app/resources/McpAgentKitConnectionRequest.d.ts +7 -2
- package/dist/app/resources/McpAgentKitConnectionRequest.d.ts.map +1 -1
- package/dist/app/resources/McpAgentKitConnectionRequest.js +132 -13
- package/dist/app/resources/McpAgentKitConnectionRequest.js.map +1 -1
- package/dist/composer/PromptComposer.d.ts +3 -0
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +2 -2
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts +4 -1
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +8 -4
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/dist/icons/ResourceIconPicker.d.ts +6 -1
- package/dist/icons/ResourceIconPicker.d.ts.map +1 -1
- package/dist/icons/ResourceIconPicker.js +45 -6
- package/dist/icons/ResourceIconPicker.js.map +1 -1
- package/package.json +6 -4
- package/src/app/agentkit/react/components.interaction.spec.tsx +93 -0
- package/src/app/agentkit/react/components.spec.tsx +4 -5
- package/src/app/agentkit/react/components.tsx +51 -8
- package/src/app/agentkit/react/composer-context.spec.tsx +7 -0
- package/src/app/agentkit/react/lifecycle.spec.tsx +42 -0
- package/src/app/chat/AgentKitAssistantChat.spec.tsx +151 -11
- package/src/app/chat/AgentKitAssistantChat.tsx +110 -28
- package/src/app/chat/AgentPanel.header.spec.ts +59 -22
- package/src/app/chat/AgentSidebar.tsx +2 -0
- package/src/app/chat/FileStorageSetupPopover.tsx +2 -2
- package/src/app/chat/MultiTabAssistantChat.messageCount.spec.tsx +3 -1
- package/src/app/chat/MultiTabAssistantChat.spec.tsx +8 -4
- package/src/app/chat/MultiTabAssistantChat.tsx +1 -1
- package/src/app/chat/agentkit-chat/action-widget.spec.tsx +5 -17
- package/src/app/chat/agentkit-chat/connections.spec.tsx +137 -0
- package/src/app/chat/agentkit-chat/connections.ts +2 -37
- package/src/app/chat/agentkit-chat/history.tsx +23 -8
- package/src/app/chat/chat/history-types.ts +1 -1
- package/src/app/chat/chat/tool-call-display.spec.tsx +2 -2
- package/src/app/chat/chat/widgets/record-change-widget.spec.tsx +20 -29
- package/src/app/chat/chat-first/copy.ts +1 -133
- package/src/app/chat/chat-first-copy.ts +133 -0
- package/src/app/chat/composer/use-agentkit-capabilities.hardening.spec.tsx +3 -2
- package/src/app/feature-flags/FeatureFlagsPanel.tsx +1 -0
- package/src/app/i18n/catalogs/ar-SA.ts +2 -1
- package/src/app/i18n/catalogs/de-DE.ts +3 -1
- package/src/app/i18n/catalogs/en-US.ts +2 -1
- package/src/app/i18n/catalogs/es-ES.ts +2 -1
- package/src/app/i18n/catalogs/fr-FR.ts +3 -1
- package/src/app/i18n/catalogs/hi-IN.ts +2 -1
- package/src/app/i18n/catalogs/ja-JP.ts +2 -1
- package/src/app/i18n/catalogs/ko-KR.ts +2 -1
- package/src/app/i18n/catalogs/pt-BR.ts +3 -1
- package/src/app/i18n/catalogs/zh-CN.ts +2 -1
- package/src/app/i18n/catalogs/zh-TW.ts +2 -1
- package/src/app/integrations/IntegrationDetailPage.spec.tsx +1 -1
- package/src/app/labs/LabsSettings.tsx +46 -10
- package/src/app/observability/ThumbsFeedback.spec.tsx +15 -0
- package/src/app/onboarding/FirstRunOnboarding.spec.tsx +4 -0
- package/src/app/org/OrgGeneralSection.tsx +32 -22
- package/src/app/org/OrgSwitcher.tsx +3 -2
- package/src/app/org/workspace-app-links.spec.ts +4 -0
- package/src/app/org/workspace-app-links.ts +8 -1
- package/src/app/resources/McpAgentKitConnectionRequest.spec.tsx +371 -2
- package/src/app/resources/McpAgentKitConnectionRequest.tsx +171 -11
- package/src/app/settings/SettingsTabsPage.spec.tsx +4 -2
- package/src/app/settings/shell/SettingsShell.spec.tsx +14 -8
- package/src/app/settings/shell/pages/app-group-pages.spec.tsx +35 -5
- package/src/app/settings/shell/pages/resource-pages.spec.tsx +3 -0
- package/src/app/settings/usage/UsagePage.spec.tsx +3 -3
- package/src/app/shared/lazy-chunk-error-boundary.spec.tsx +2 -0
- package/src/app-shell/sidebar-overflow.spec.tsx +4 -1
- package/src/composer/PromptComposer.tsx +7 -0
- package/src/composer/TiptapComposer.spec.ts +26 -4
- package/src/composer/TiptapComposer.tsx +12 -3
- package/src/design-tweaks/visual-style-controls.spec.tsx +38 -0
- package/src/editor/useCollabReconcile.concurrent.spec.ts +2 -1
- package/src/icons/ResourceIconPicker.spec.tsx +270 -0
- package/src/icons/ResourceIconPicker.tsx +72 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @vitest-environment happy-dom
|
|
2
2
|
|
|
3
|
+
import type { LabStates } from "@agent-native/core/client/labs/use-lab";
|
|
3
4
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
4
5
|
import React, { act, Suspense } from "react";
|
|
5
6
|
import { createRoot, type Root } from "react-dom/client";
|
|
@@ -32,17 +33,17 @@ vi.mock("@agent-native/core/client/app-model-default", () => ({
|
|
|
32
33
|
}));
|
|
33
34
|
|
|
34
35
|
const labsActions = vi.hoisted(() => ({
|
|
35
|
-
query: { data: undefined as
|
|
36
|
+
query: { data: undefined as LabStates | undefined },
|
|
36
37
|
isError: false,
|
|
37
38
|
mutate: vi.fn(),
|
|
38
39
|
}));
|
|
39
40
|
vi.mock("@agent-native/core/client/hooks", () => ({
|
|
40
|
-
useChangeVersions: () => 0,
|
|
41
41
|
useDemoModeStatus: () => ({
|
|
42
42
|
enabled: false,
|
|
43
43
|
forced: false,
|
|
44
44
|
isLoading: false,
|
|
45
45
|
}),
|
|
46
|
+
useChangeVersions: () => 0,
|
|
46
47
|
}));
|
|
47
48
|
vi.mock("@agent-native/core/client/use-action", () => ({
|
|
48
49
|
useActionQuery: () => ({
|
|
@@ -422,9 +423,13 @@ describe("app group pages", () => {
|
|
|
422
423
|
|
|
423
424
|
it("hides labs the server didn't register once it answers", async () => {
|
|
424
425
|
labsActions.query.data = {
|
|
425
|
-
"chatgpt-subscription":
|
|
426
|
-
|
|
427
|
-
|
|
426
|
+
"chatgpt-subscription": {
|
|
427
|
+
enabled: false,
|
|
428
|
+
source: "default",
|
|
429
|
+
mixed: false,
|
|
430
|
+
},
|
|
431
|
+
"clips.wisprflow": { enabled: false, source: "choice", mixed: false },
|
|
432
|
+
"clips.meetings": { enabled: true, source: "choice", mixed: false },
|
|
428
433
|
};
|
|
429
434
|
await renderPage(LabsSettingsPage, { input: { labs } });
|
|
430
435
|
expect(switchFor("Retired lab")).toBeNull();
|
|
@@ -436,6 +441,31 @@ describe("app group pages", () => {
|
|
|
436
441
|
).toBe("true");
|
|
437
442
|
expect(switchFor("Voice dictation")?.disabled).toBe(false);
|
|
438
443
|
});
|
|
444
|
+
|
|
445
|
+
it("offers explicit choices for inherited mixed settings", async () => {
|
|
446
|
+
labsActions.query.data = {
|
|
447
|
+
"clips.meetings": {
|
|
448
|
+
enabled: false,
|
|
449
|
+
source: "legacy",
|
|
450
|
+
mixed: true,
|
|
451
|
+
legacyValues: { capture: false, recovery: true },
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
await renderPage(LabsSettingsPage, { input: { labs } });
|
|
455
|
+
expect(switchFor("Meetings and transcription")).toBeNull();
|
|
456
|
+
const on = container.querySelector<HTMLButtonElement>(
|
|
457
|
+
'[aria-label="Meetings and transcription: On"]',
|
|
458
|
+
);
|
|
459
|
+
expect(on?.disabled).toBe(false);
|
|
460
|
+
act(() => on?.click());
|
|
461
|
+
expect(labsActions.mutate).toHaveBeenCalledWith(
|
|
462
|
+
{ key: "clips.meetings", enabled: true },
|
|
463
|
+
expect.any(Object),
|
|
464
|
+
);
|
|
465
|
+
expect(
|
|
466
|
+
switchFor("Meetings and transcription")?.getAttribute("aria-checked"),
|
|
467
|
+
).toBe("true");
|
|
468
|
+
});
|
|
439
469
|
});
|
|
440
470
|
|
|
441
471
|
describe("Automations", () => {
|
|
@@ -10,6 +10,9 @@ const orgState = vi.hoisted(() => ({
|
|
|
10
10
|
value: { orgId: "org-1", orgName: "Acme", role: "member" as string },
|
|
11
11
|
}));
|
|
12
12
|
|
|
13
|
+
vi.mock("@agent-native/core/client/org/hooks", () => ({
|
|
14
|
+
useOrg: () => ({ data: orgState.value, isLoading: false }),
|
|
15
|
+
}));
|
|
13
16
|
vi.mock("@agent-native/core/client/org/hooks", () => ({
|
|
14
17
|
useOrg: () => ({ data: orgState.value, isLoading: false }),
|
|
15
18
|
}));
|
|
@@ -54,9 +54,9 @@ vi.mock("@agent-native/core/client/hooks", () => ({
|
|
|
54
54
|
}),
|
|
55
55
|
}));
|
|
56
56
|
|
|
57
|
-
vi.mock("@agent-native/core/client/use-action", () =>
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
vi.mock("@agent-native/core/client/use-action", async () =>
|
|
58
|
+
vi.importMock("@agent-native/core/client/hooks"),
|
|
59
|
+
);
|
|
60
60
|
|
|
61
61
|
vi.mock("@agent-native/core/client/i18n", () => ({
|
|
62
62
|
useT:
|
|
@@ -12,6 +12,7 @@ vi.mock("@agent-native/core/client/route-chunk-recovery", () => ({
|
|
|
12
12
|
|
|
13
13
|
import { AgentNativeI18nProvider } from "@agent-native/core/client/i18n";
|
|
14
14
|
|
|
15
|
+
import { createToolkitI18nCatalog } from "../i18n.js";
|
|
15
16
|
import { LazyChunkErrorBoundary } from "./LazyChunkErrorBoundary.js";
|
|
16
17
|
import { LazyChunkRetryFallback } from "./LazyChunkRetryFallback.js";
|
|
17
18
|
|
|
@@ -42,6 +43,7 @@ describe("LazyChunkErrorBoundary", () => {
|
|
|
42
43
|
await act(async () => {
|
|
43
44
|
root.render(
|
|
44
45
|
<AgentNativeI18nProvider
|
|
46
|
+
catalog={createToolkitI18nCatalog({ messages: {} })}
|
|
45
47
|
initialLocale="en-US"
|
|
46
48
|
initialPreference="en-US"
|
|
47
49
|
persistPreference={false}
|
|
@@ -32,7 +32,10 @@ describe("AppSidebar overflow affordances", () => {
|
|
|
32
32
|
root = createRoot(container);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
afterEach(() => {
|
|
35
|
+
afterEach(async () => {
|
|
36
|
+
await act(async () => {
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
38
|
+
});
|
|
36
39
|
act(() => root.unmount());
|
|
37
40
|
container.remove();
|
|
38
41
|
vi.unstubAllGlobals();
|
|
@@ -106,6 +106,7 @@ export interface PromptComposerProps {
|
|
|
106
106
|
) => void | Promise<void>;
|
|
107
107
|
/** Return false to stop a submit before it reaches the host runtime. */
|
|
108
108
|
onBeforeSubmit?: () => boolean | Promise<boolean>;
|
|
109
|
+
onSubmissionPendingChange?: (pending: boolean) => void;
|
|
109
110
|
/** Scope where a failed submission should be recovered after the host forks. */
|
|
110
111
|
getSubmitFailureDraftScope?: () => string | null;
|
|
111
112
|
/** Handle file paste/drop errors in the host chat surface. */
|
|
@@ -118,6 +119,8 @@ export interface PromptComposerProps {
|
|
|
118
119
|
disabled?: boolean;
|
|
119
120
|
/** Block all submission paths while allowing draft, file, and context staging. */
|
|
120
121
|
submissionDisabled?: boolean;
|
|
122
|
+
/** Disable only the send control while the submission is being accepted. */
|
|
123
|
+
sendButtonDisabled?: boolean;
|
|
121
124
|
/** Prevent submission while preserving editor focus and draft entry. */
|
|
122
125
|
submitting?: boolean;
|
|
123
126
|
/** Present the primary action as queueing instead of immediate send. */
|
|
@@ -608,6 +611,7 @@ function PromptComposerInner({
|
|
|
608
611
|
ariaLabel,
|
|
609
612
|
disabled,
|
|
610
613
|
submissionDisabled,
|
|
614
|
+
sendButtonDisabled,
|
|
611
615
|
submitting,
|
|
612
616
|
willQueue = false,
|
|
613
617
|
onDisabledClick,
|
|
@@ -680,6 +684,7 @@ function PromptComposerInner({
|
|
|
680
684
|
imageModelMenu,
|
|
681
685
|
composerRef,
|
|
682
686
|
onBeforeSubmit,
|
|
687
|
+
onSubmissionPendingChange,
|
|
683
688
|
getSubmitFailureDraftScope,
|
|
684
689
|
onAttachmentError,
|
|
685
690
|
interceptBuildRequestsForBuilder,
|
|
@@ -890,6 +895,7 @@ function PromptComposerInner({
|
|
|
890
895
|
disabled={disabled}
|
|
891
896
|
contextControlsDisabled={engineSubmissionBlocked}
|
|
892
897
|
submissionDisabled={submissionDisabled || engineSubmissionBlocked}
|
|
898
|
+
sendButtonDisabled={sendButtonDisabled}
|
|
893
899
|
submitting={submitting}
|
|
894
900
|
willQueue={willQueue}
|
|
895
901
|
maxDocumentAttachmentBytes={maxDocumentAttachmentBytes}
|
|
@@ -899,6 +905,7 @@ function PromptComposerInner({
|
|
|
899
905
|
initialTextKey={initialTextKey}
|
|
900
906
|
onSubmit={handleSubmit}
|
|
901
907
|
onBeforeSubmit={onBeforeSubmit}
|
|
908
|
+
onSubmissionPendingChange={onSubmissionPendingChange}
|
|
902
909
|
getSubmitFailureDraftScope={getSubmitFailureDraftScope}
|
|
903
910
|
onAttachmentError={onAttachmentError}
|
|
904
911
|
interceptBuildRequestsForBuilder={interceptBuildRequestsForBuilder}
|
|
@@ -2166,7 +2166,13 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2166
2166
|
resolveSubmit = resolve;
|
|
2167
2167
|
});
|
|
2168
2168
|
const onBeforeSubmit = vi.fn(() => preflight);
|
|
2169
|
-
const
|
|
2169
|
+
const onSubmissionPendingChange = vi.fn();
|
|
2170
|
+
const onSubmit = vi.fn<NonNullable<TiptapComposerProps["onSubmit"]>>(
|
|
2171
|
+
async (_text, _references, _attachments, options) => {
|
|
2172
|
+
await submission;
|
|
2173
|
+
options?.onLocalSubmit?.();
|
|
2174
|
+
},
|
|
2175
|
+
);
|
|
2170
2176
|
const focusRef = React.createRef<TiptapComposerHandle>();
|
|
2171
2177
|
|
|
2172
2178
|
function Harness() {
|
|
@@ -2180,6 +2186,7 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2180
2186
|
React.createElement(TiptapComposer, {
|
|
2181
2187
|
focusRef,
|
|
2182
2188
|
onBeforeSubmit,
|
|
2189
|
+
onSubmissionPendingChange,
|
|
2183
2190
|
onSubmit,
|
|
2184
2191
|
clearOnSubmitImmediately: true,
|
|
2185
2192
|
includeDefaultSlashSkills: false,
|
|
@@ -2209,6 +2216,7 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2209
2216
|
|
|
2210
2217
|
expect(editor.textContent).toBe("");
|
|
2211
2218
|
expect(onSubmit).not.toHaveBeenCalled();
|
|
2219
|
+
expect(onSubmissionPendingChange).toHaveBeenLastCalledWith(true);
|
|
2212
2220
|
act(() => focusRef.current?.setText("follow-up prompt"));
|
|
2213
2221
|
|
|
2214
2222
|
await act(async () => {
|
|
@@ -2225,6 +2233,8 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2225
2233
|
);
|
|
2226
2234
|
expect(editor.textContent).toBe("follow-up prompt");
|
|
2227
2235
|
await act(async () => resolveSubmit());
|
|
2236
|
+
expect(editor.textContent).toBe("follow-up prompt");
|
|
2237
|
+
expect(onSubmissionPendingChange).toHaveBeenLastCalledWith(false);
|
|
2228
2238
|
});
|
|
2229
2239
|
|
|
2230
2240
|
it("restores a prompt when async preflight declines it", async () => {
|
|
@@ -2337,10 +2347,11 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2337
2347
|
|
|
2338
2348
|
it("clears immediately while submitting and restores the draft on failure", async () => {
|
|
2339
2349
|
let rejectSubmit!: (error: Error) => void;
|
|
2340
|
-
const onSubmit = vi.fn(
|
|
2341
|
-
() =>
|
|
2350
|
+
const onSubmit = vi.fn<NonNullable<TiptapComposerProps["onSubmit"]>>(
|
|
2351
|
+
(_text, _references, _attachments, options) =>
|
|
2342
2352
|
new Promise<void>((_resolve, reject) => {
|
|
2343
2353
|
rejectSubmit = reject;
|
|
2354
|
+
options?.onLocalSubmit?.();
|
|
2344
2355
|
}),
|
|
2345
2356
|
);
|
|
2346
2357
|
const focusRef = React.createRef<TiptapComposerHandle>();
|
|
@@ -2384,6 +2395,7 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2384
2395
|
|
|
2385
2396
|
expect(onSubmit).toHaveBeenCalledOnce();
|
|
2386
2397
|
expect(editor.textContent).toBe("");
|
|
2398
|
+
act(() => focusRef.current?.setText("follow-up prompt"));
|
|
2387
2399
|
|
|
2388
2400
|
await act(async () => {
|
|
2389
2401
|
rejectSubmit(new Error("network unavailable"));
|
|
@@ -2391,7 +2403,17 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2391
2403
|
await Promise.resolve();
|
|
2392
2404
|
});
|
|
2393
2405
|
|
|
2394
|
-
expect(
|
|
2406
|
+
expect(
|
|
2407
|
+
[...editor.querySelectorAll("p")].map(
|
|
2408
|
+
(paragraph) => paragraph.textContent,
|
|
2409
|
+
),
|
|
2410
|
+
).toEqual(["send this once", "follow-up prompt"]);
|
|
2411
|
+
expect(localStorage.getItem(getComposerDraftKey())).toContain(
|
|
2412
|
+
"send this once",
|
|
2413
|
+
);
|
|
2414
|
+
expect(localStorage.getItem(getComposerDraftKey())).toContain(
|
|
2415
|
+
"follow-up prompt",
|
|
2416
|
+
);
|
|
2395
2417
|
});
|
|
2396
2418
|
|
|
2397
2419
|
it("restores the persisted draft when an immediate submit fails after unmount", async () => {
|
|
@@ -888,6 +888,8 @@ export interface TiptapComposerProps {
|
|
|
888
888
|
contextControlsDisabled?: boolean;
|
|
889
889
|
/** Prevent submission without making the editable surface lose focus. */
|
|
890
890
|
submissionDisabled?: boolean;
|
|
891
|
+
/** Disable only the send control while the submission is being accepted. */
|
|
892
|
+
sendButtonDisabled?: boolean;
|
|
891
893
|
/** Prevent submission while a host request is in flight. */
|
|
892
894
|
submitting?: boolean;
|
|
893
895
|
/** Override the generic document attachment cap for a multipart host. */
|
|
@@ -916,6 +918,7 @@ export interface TiptapComposerProps {
|
|
|
916
918
|
) => void | Promise<void>;
|
|
917
919
|
/** Return false to stop a submit before it enters the chat runtime. */
|
|
918
920
|
onBeforeSubmit?: () => boolean | Promise<boolean>;
|
|
921
|
+
onSubmissionPendingChange?: (pending: boolean) => void;
|
|
919
922
|
/** Scope where a failed submission should be recovered after the host forks. */
|
|
920
923
|
getSubmitFailureDraftScope?: () => string | null;
|
|
921
924
|
/**
|
|
@@ -2547,6 +2550,7 @@ export function TiptapComposer({
|
|
|
2547
2550
|
disabled = false,
|
|
2548
2551
|
contextControlsDisabled = false,
|
|
2549
2552
|
submissionDisabled = false,
|
|
2553
|
+
sendButtonDisabled = false,
|
|
2550
2554
|
submitting = false,
|
|
2551
2555
|
maxDocumentAttachmentBytes = MAX_DOCUMENT_ATTACHMENT_BYTES,
|
|
2552
2556
|
documentAttachmentLimitLabel = "PDFs",
|
|
@@ -2558,6 +2562,7 @@ export function TiptapComposer({
|
|
|
2558
2562
|
initialTextKey,
|
|
2559
2563
|
onSubmit,
|
|
2560
2564
|
onBeforeSubmit,
|
|
2565
|
+
onSubmissionPendingChange,
|
|
2561
2566
|
getSubmitFailureDraftScope,
|
|
2562
2567
|
clearOnSubmit = true,
|
|
2563
2568
|
clearOnSubmitImmediately = false,
|
|
@@ -4243,7 +4248,6 @@ export function TiptapComposer({
|
|
|
4243
4248
|
slotReferencesRef.current.length > 0);
|
|
4244
4249
|
const canPreserveFollowUp =
|
|
4245
4250
|
preserveFollowUp &&
|
|
4246
|
-
clearedBeforePreflight &&
|
|
4247
4251
|
!isForkRecovery &&
|
|
4248
4252
|
hasFollowUp &&
|
|
4249
4253
|
(currentDraft === null ||
|
|
@@ -4330,6 +4334,7 @@ export function TiptapComposer({
|
|
|
4330
4334
|
|
|
4331
4335
|
if (onBeforeSubmit) {
|
|
4332
4336
|
submitInFlightRef.current = true;
|
|
4337
|
+
onSubmissionPendingChange?.(true);
|
|
4333
4338
|
try {
|
|
4334
4339
|
const shouldSubmit = await onBeforeSubmit();
|
|
4335
4340
|
if (!shouldSubmit) {
|
|
@@ -4351,6 +4356,7 @@ export function TiptapComposer({
|
|
|
4351
4356
|
return false;
|
|
4352
4357
|
} finally {
|
|
4353
4358
|
submitInFlightRef.current = false;
|
|
4359
|
+
onSubmissionPendingChange?.(false);
|
|
4354
4360
|
}
|
|
4355
4361
|
}
|
|
4356
4362
|
if (
|
|
@@ -4498,6 +4504,7 @@ export function TiptapComposer({
|
|
|
4498
4504
|
submitInFlightRef.current = true;
|
|
4499
4505
|
let locallySubmitted = false;
|
|
4500
4506
|
let settled = false;
|
|
4507
|
+
onSubmissionPendingChange?.(true);
|
|
4501
4508
|
const clearSubmittedComposer = () => {
|
|
4502
4509
|
if (clearOnSubmit) {
|
|
4503
4510
|
clearComposerDraft(submittingDraftKey, submittingDraftSnapshot);
|
|
@@ -4575,7 +4582,7 @@ export function TiptapComposer({
|
|
|
4575
4582
|
);
|
|
4576
4583
|
} catch (error) {
|
|
4577
4584
|
if (locallySubmitted) {
|
|
4578
|
-
restoreSubmittedDraft();
|
|
4585
|
+
restoreSubmittedDraft(true);
|
|
4579
4586
|
return true;
|
|
4580
4587
|
}
|
|
4581
4588
|
restoreSubmittedDraft(true);
|
|
@@ -4593,6 +4600,7 @@ export function TiptapComposer({
|
|
|
4593
4600
|
} finally {
|
|
4594
4601
|
settled = true;
|
|
4595
4602
|
submitInFlightRef.current = false;
|
|
4603
|
+
onSubmissionPendingChange?.(false);
|
|
4596
4604
|
}
|
|
4597
4605
|
|
|
4598
4606
|
if (!isCurrentDraftScope()) {
|
|
@@ -4685,6 +4693,7 @@ export function TiptapComposer({
|
|
|
4685
4693
|
clearOnSubmitImmediately,
|
|
4686
4694
|
getSubmitFailureDraftScope,
|
|
4687
4695
|
onBeforeSubmit,
|
|
4696
|
+
onSubmissionPendingChange,
|
|
4688
4697
|
extractComposerPayload,
|
|
4689
4698
|
syncComposerState,
|
|
4690
4699
|
updateSlotReferences,
|
|
@@ -5391,7 +5400,7 @@ export function TiptapComposer({
|
|
|
5391
5400
|
<button
|
|
5392
5401
|
type="button"
|
|
5393
5402
|
onClick={() => void submitComposer("immediate")}
|
|
5394
|
-
disabled={!canSend}
|
|
5403
|
+
disabled={!canSend || sendButtonDisabled}
|
|
5395
5404
|
aria-label={
|
|
5396
5405
|
submitting ? t("common.loading") : sendButtonTooltip
|
|
5397
5406
|
}
|
|
@@ -378,6 +378,44 @@ describe("visual style controls", () => {
|
|
|
378
378
|
expect(input.getAttribute("data-design-history-hotkeys")).toBe("true");
|
|
379
379
|
});
|
|
380
380
|
|
|
381
|
+
it("commits a zero pixel value with leading zeros", () => {
|
|
382
|
+
const onChange = vi.fn();
|
|
383
|
+
act(() => {
|
|
384
|
+
root.render(
|
|
385
|
+
<VisualScrubInput
|
|
386
|
+
label="Border width"
|
|
387
|
+
value={1}
|
|
388
|
+
min={0}
|
|
389
|
+
unit="px"
|
|
390
|
+
onChange={onChange}
|
|
391
|
+
/>,
|
|
392
|
+
);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
const input = container.querySelector<HTMLInputElement>("input")!;
|
|
396
|
+
act(() => input.focus());
|
|
397
|
+
act(() => {
|
|
398
|
+
const setter = Object.getOwnPropertyDescriptor(
|
|
399
|
+
HTMLInputElement.prototype,
|
|
400
|
+
"value",
|
|
401
|
+
)!.set!;
|
|
402
|
+
setter.call(input, "00px");
|
|
403
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
404
|
+
});
|
|
405
|
+
act(() => {
|
|
406
|
+
input.dispatchEvent(
|
|
407
|
+
new KeyboardEvent("keydown", { key: "Enter", bubbles: true }),
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
expect(onChange).toHaveBeenCalledWith(
|
|
412
|
+
0,
|
|
413
|
+
expect.objectContaining({ source: "commit", expression: "00px" }),
|
|
414
|
+
);
|
|
415
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
416
|
+
expect(input.value).toBe("0px");
|
|
417
|
+
});
|
|
418
|
+
|
|
381
419
|
it("releases focus on Enter when the consumer opts in", () => {
|
|
382
420
|
const onChange = vi.fn();
|
|
383
421
|
act(() => {
|
|
@@ -1786,6 +1786,7 @@ describe("useCollabReconcile — concurrent edit / lost-update guards", () => {
|
|
|
1786
1786
|
});
|
|
1787
1787
|
|
|
1788
1788
|
it("still clears a stale collab value when there is no local emission", async () => {
|
|
1789
|
+
vi.useFakeTimers();
|
|
1789
1790
|
const { captured, Harness } = makeCollabSeedHarness("stale persisted body");
|
|
1790
1791
|
act(() =>
|
|
1791
1792
|
root.render(
|
|
@@ -1799,7 +1800,7 @@ describe("useCollabReconcile — concurrent edit / lost-update guards", () => {
|
|
|
1799
1800
|
),
|
|
1800
1801
|
);
|
|
1801
1802
|
expect(getEditorMarkdown(captured.editor!)).toBe("stale persisted body");
|
|
1802
|
-
await act(async () =>
|
|
1803
|
+
await act(async () => vi.advanceTimersByTimeAsync(30));
|
|
1803
1804
|
|
|
1804
1805
|
expect(getEditorMarkdown(captured.editor!)).toBe("canonical SQL body");
|
|
1805
1806
|
expect(captured.emitted).toEqual([]);
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { act } from "react";
|
|
4
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ResourceIconPicker,
|
|
9
|
+
type ResourceIconPickerLabels,
|
|
10
|
+
} from "./ResourceIconPicker.js";
|
|
11
|
+
import type { ResourceIconImage, ResourceIconValue } from "./types.js";
|
|
12
|
+
|
|
13
|
+
vi.mock("./catalog.js", () => ({ loadTablerCatalog: async () => [] }));
|
|
14
|
+
vi.mock("./emoji-catalog.js", () => ({
|
|
15
|
+
EMOJI_CATEGORIES: [],
|
|
16
|
+
loadEmojiCatalog: async () => [],
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
const labels: ResourceIconPickerLabels = {
|
|
20
|
+
trigger: "Choose icon",
|
|
21
|
+
iconsTab: "Icons",
|
|
22
|
+
emojiTab: "Emoji",
|
|
23
|
+
uploadTab: "Upload",
|
|
24
|
+
search: "Search",
|
|
25
|
+
noResults: "No results",
|
|
26
|
+
recents: "Recent",
|
|
27
|
+
colors: "Colors",
|
|
28
|
+
defaultColor: "Default",
|
|
29
|
+
remove: "Remove",
|
|
30
|
+
upload: "Choose image",
|
|
31
|
+
uploading: "Uploading",
|
|
32
|
+
saveError: "Could not save icon.",
|
|
33
|
+
uploadFailed: "Could not upload icon image. Try again.",
|
|
34
|
+
uploadTooLarge: "Icon image must be 5 MiB or smaller.",
|
|
35
|
+
uploadUnsupportedType: "Only PNG, JPEG, WebP, or SVG images can be uploaded.",
|
|
36
|
+
loadError: "Could not load uploaded icons.",
|
|
37
|
+
retry: "Retry",
|
|
38
|
+
};
|
|
39
|
+
const previousIcon: ResourceIconValue = {
|
|
40
|
+
version: 1,
|
|
41
|
+
kind: "emoji",
|
|
42
|
+
emoji: "🌿",
|
|
43
|
+
};
|
|
44
|
+
const uploadedIcon: ResourceIconImage = {
|
|
45
|
+
version: 1,
|
|
46
|
+
kind: "image",
|
|
47
|
+
authority: "url",
|
|
48
|
+
assetId: "/uploaded.svg",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe("ResourceIconPicker uploads", () => {
|
|
52
|
+
let container: HTMLDivElement;
|
|
53
|
+
let root: Root;
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
57
|
+
vi.stubGlobal(
|
|
58
|
+
"ResizeObserver",
|
|
59
|
+
class {
|
|
60
|
+
observe() {}
|
|
61
|
+
unobserve() {}
|
|
62
|
+
disconnect() {}
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
container = document.createElement("div");
|
|
66
|
+
document.body.append(container);
|
|
67
|
+
root = createRoot(container);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
act(() => root.unmount());
|
|
72
|
+
container.remove();
|
|
73
|
+
vi.unstubAllGlobals();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
async function renderPicker(
|
|
77
|
+
onUpload: (file: File) => Promise<ResourceIconImage>,
|
|
78
|
+
onValueChange = vi.fn(),
|
|
79
|
+
formatUploadError?: (error: unknown) => string,
|
|
80
|
+
) {
|
|
81
|
+
await act(async () => {
|
|
82
|
+
root.render(
|
|
83
|
+
<ResourceIconPicker
|
|
84
|
+
value={previousIcon}
|
|
85
|
+
onValueChange={onValueChange}
|
|
86
|
+
onUpload={onUpload}
|
|
87
|
+
formatUploadError={formatUploadError}
|
|
88
|
+
labels={labels}
|
|
89
|
+
open
|
|
90
|
+
portalled={false}
|
|
91
|
+
/>,
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
const uploadTab = Array.from(container.querySelectorAll("[role=tab]")).find(
|
|
95
|
+
(tab) => tab.textContent === labels.uploadTab,
|
|
96
|
+
);
|
|
97
|
+
expect(uploadTab).toBeDefined();
|
|
98
|
+
await act(async () => {
|
|
99
|
+
uploadTab!.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
|
100
|
+
uploadTab!.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function choose(file: File) {
|
|
105
|
+
const input =
|
|
106
|
+
container.querySelector<HTMLInputElement>('input[type="file"]')!;
|
|
107
|
+
Object.defineProperty(input, "files", {
|
|
108
|
+
configurable: true,
|
|
109
|
+
value: [file],
|
|
110
|
+
});
|
|
111
|
+
await act(async () => {
|
|
112
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function drop(file: File) {
|
|
117
|
+
const target = Array.from(container.querySelectorAll("button")).find(
|
|
118
|
+
(button) => button.textContent?.includes(labels.upload),
|
|
119
|
+
)!;
|
|
120
|
+
const event = new Event("drop", { bubbles: true, cancelable: true });
|
|
121
|
+
Object.defineProperty(event, "dataTransfer", { value: { files: [file] } });
|
|
122
|
+
await act(async () => target.dispatchEvent(event));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function alertText() {
|
|
126
|
+
return container.querySelector('[role="alert"]')?.textContent;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
it("rejects unsupported types from input and drop without replacing the icon", async () => {
|
|
130
|
+
const onUpload = vi.fn().mockResolvedValue(uploadedIcon);
|
|
131
|
+
const onValueChange = vi.fn();
|
|
132
|
+
await renderPicker(onUpload, onValueChange);
|
|
133
|
+
|
|
134
|
+
await choose(new File(["gif"], "animation.gif", { type: "image/gif" }));
|
|
135
|
+
expect(alertText()).toBe(labels.uploadUnsupportedType);
|
|
136
|
+
|
|
137
|
+
await drop(new File(["text"], "notes.txt", { type: "text/plain" }));
|
|
138
|
+
expect(alertText()).toBe(labels.uploadUnsupportedType);
|
|
139
|
+
expect(onUpload).not.toHaveBeenCalled();
|
|
140
|
+
expect(onValueChange).not.toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("shows the size limit for a supported file from either path", async () => {
|
|
144
|
+
const onUpload = vi.fn().mockResolvedValue(uploadedIcon);
|
|
145
|
+
await renderPicker(onUpload);
|
|
146
|
+
|
|
147
|
+
const large = new File([new Uint8Array(5 * 1024 * 1024 + 1)], "large.png", {
|
|
148
|
+
type: "image/png",
|
|
149
|
+
});
|
|
150
|
+
await choose(large);
|
|
151
|
+
expect(alertText()).toBe(labels.uploadTooLarge);
|
|
152
|
+
await drop(large);
|
|
153
|
+
expect(alertText()).toBe(labels.uploadTooLarge);
|
|
154
|
+
await drop(
|
|
155
|
+
new File([new Uint8Array(5 * 1024 * 1024 + 1)], "large.txt", {
|
|
156
|
+
type: "text/plain",
|
|
157
|
+
}),
|
|
158
|
+
);
|
|
159
|
+
expect(alertText()).toBe(labels.uploadTooLarge);
|
|
160
|
+
expect(onUpload).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("keeps a mapped failure visible and clears it after a successful retry", async () => {
|
|
164
|
+
const onUpload = vi
|
|
165
|
+
.fn()
|
|
166
|
+
.mockRejectedValueOnce(new Error("private server detail"))
|
|
167
|
+
.mockResolvedValueOnce(uploadedIcon);
|
|
168
|
+
const onValueChange = vi.fn();
|
|
169
|
+
await renderPicker(
|
|
170
|
+
onUpload,
|
|
171
|
+
onValueChange,
|
|
172
|
+
() => "Storage is unavailable.",
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
await choose(new File(["first"], "first.png", { type: "image/png" }));
|
|
176
|
+
expect(alertText()).toBe("Storage is unavailable.");
|
|
177
|
+
expect(container.textContent).not.toContain("private server detail");
|
|
178
|
+
expect(onValueChange).not.toHaveBeenCalled();
|
|
179
|
+
|
|
180
|
+
await drop(new File(["<svg />"], "retry.SVG"));
|
|
181
|
+
expect(onUpload).toHaveBeenCalledTimes(2);
|
|
182
|
+
expect(onUpload.mock.calls[1]?.[0].type).toBe("image/svg+xml");
|
|
183
|
+
expect(onValueChange).toHaveBeenCalledWith(uploadedIcon);
|
|
184
|
+
expect(alertText()).toBeUndefined();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("uses the localized upload failure when the caller has no safe detail", async () => {
|
|
188
|
+
const onUpload = vi
|
|
189
|
+
.fn()
|
|
190
|
+
.mockRejectedValue(new Error("private server detail"));
|
|
191
|
+
await renderPicker(onUpload);
|
|
192
|
+
|
|
193
|
+
await choose(new File(["first"], "first.webp", { type: "image/webp" }));
|
|
194
|
+
expect(alertText()).toBe(labels.uploadFailed);
|
|
195
|
+
expect(container.textContent).not.toContain("private server detail");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("makes an unavailable upload library visible and retryable", async () => {
|
|
199
|
+
const onRetry = vi.fn();
|
|
200
|
+
await act(async () => {
|
|
201
|
+
root.render(
|
|
202
|
+
<ResourceIconPicker
|
|
203
|
+
value={previousIcon}
|
|
204
|
+
onValueChange={vi.fn()}
|
|
205
|
+
onUpload={vi.fn().mockResolvedValue(uploadedIcon)}
|
|
206
|
+
uploadedImagesError
|
|
207
|
+
onUploadedImagesRetry={onRetry}
|
|
208
|
+
labels={labels}
|
|
209
|
+
open
|
|
210
|
+
portalled={false}
|
|
211
|
+
/>,
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
const uploadTab = Array.from(container.querySelectorAll("[role=tab]")).find(
|
|
215
|
+
(tab) => tab.textContent === labels.uploadTab,
|
|
216
|
+
);
|
|
217
|
+
await act(async () => {
|
|
218
|
+
uploadTab!.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
|
219
|
+
uploadTab!.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(alertText()).toContain(labels.loadError);
|
|
223
|
+
const retryButton = Array.from(container.querySelectorAll("button")).find(
|
|
224
|
+
(button) => button.textContent === labels.retry,
|
|
225
|
+
);
|
|
226
|
+
await act(async () => retryButton!.click());
|
|
227
|
+
expect(onRetry).toHaveBeenCalledOnce();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("does not offer private recent images outside the current library", async () => {
|
|
231
|
+
const personal: ResourceIconImage = {
|
|
232
|
+
version: 1,
|
|
233
|
+
kind: "image",
|
|
234
|
+
authority: "private-icon",
|
|
235
|
+
assetId: "personal-asset",
|
|
236
|
+
alt: "Personal logo",
|
|
237
|
+
};
|
|
238
|
+
const workspace: ResourceIconImage = {
|
|
239
|
+
...personal,
|
|
240
|
+
assetId: "workspace-asset",
|
|
241
|
+
alt: "Workspace logo",
|
|
242
|
+
};
|
|
243
|
+
await act(async () => {
|
|
244
|
+
root.render(
|
|
245
|
+
<ResourceIconPicker
|
|
246
|
+
value={previousIcon}
|
|
247
|
+
onValueChange={vi.fn()}
|
|
248
|
+
onUpload={vi.fn()}
|
|
249
|
+
uploadedImages={[workspace]}
|
|
250
|
+
recentValues={[personal, workspace]}
|
|
251
|
+
labels={labels}
|
|
252
|
+
open
|
|
253
|
+
portalled={false}
|
|
254
|
+
/>,
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
const uploadTab = Array.from(container.querySelectorAll("[role=tab]")).find(
|
|
258
|
+
(tab) => tab.textContent === labels.uploadTab,
|
|
259
|
+
);
|
|
260
|
+
await act(async () => {
|
|
261
|
+
uploadTab!.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
|
262
|
+
uploadTab!.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(
|
|
266
|
+
container.querySelector('[aria-label="Workspace logo"]'),
|
|
267
|
+
).not.toBeNull();
|
|
268
|
+
expect(container.querySelector('[aria-label="Personal logo"]')).toBeNull();
|
|
269
|
+
});
|
|
270
|
+
});
|