shadow-claw 1.23.4 → 1.24.0
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/README.md +42 -5
- package/bin/cli.mjs +166 -0
- package/bin/cli.test.mjs +60 -0
- package/bin/commands/backup.mjs +70 -0
- package/bin/commands/clients.mjs +41 -0
- package/bin/commands/peer-id.mjs +76 -0
- package/bin/commands/peer-id.test.mjs +104 -0
- package/bin/commands/send.mjs +51 -0
- package/bin/commands/tasks.mjs +59 -0
- package/bin/commands/webrtc-listen.mjs +254 -0
- package/bin/prerender-dsd-shell/prerender-dsd-shell.test.mjs +46 -0
- package/bin/rolldown.config.test.mjs +401 -8
- package/bin/utils/control-client.mjs +243 -0
- package/bin/utils/control-client.test.mjs +83 -0
- package/bin/utils/ulid.mjs +45 -0
- package/bin/utils/webrtc-control-client.mjs +699 -0
- package/bin/utils/webrtc-control-client.test.mjs +318 -0
- package/dist/electron/main.cjs +1 -1
- package/dist/public/AGENTS.md +5 -3
- package/dist/public/README.md +42 -5
- package/dist/public/agent.worker.js +1 -1
- package/dist/public/backup-controller-C_s9in2t.js +1 -0
- package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +1 -0
- package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +1 -0
- package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +1 -1
- package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
- package/dist/public/components/shadow-claw-settings/shadow-claw-settings.html +1 -1
- package/dist/public/config-CrHchneq.js +1 -0
- package/dist/public/{configurePeerJs-B4Ym9VMj.js → configurePeerJs-CxpZeDJJ.js} +1 -1
- package/dist/public/{connections-BEn6JYdu.js → connections-DvbgdkaB.js} +1 -1
- package/dist/public/{custom-element-security-DdYlUozq.js → custom-element-security-NCqQ5lmC.js} +1 -1
- package/dist/public/docs/README.md +3 -1
- package/dist/public/docs/architecture/overview.md +12 -12
- package/dist/public/docs/decisions/bundled-typescript-architecture.md +1 -1
- package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +2 -2
- package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
- package/dist/public/docs/example/article/index.html +1 -1
- package/dist/public/docs/guides/configuring-messaging-channels.md +41 -6
- package/dist/public/docs/guides/server-development-configuration.md +24 -0
- package/dist/public/docs/publishing/index.html +1 -1
- package/dist/public/docs/skill-creator/index.html +1 -1
- package/dist/public/docs/subsystems/backup.md +56 -0
- package/dist/public/docs/subsystems/cli.md +124 -0
- package/dist/public/docs/subsystems/control-plane.md +158 -0
- package/dist/public/docs/subsystems/notifications.md +54 -2
- package/dist/public/docs/subsystems/reactive-ui.md +1 -0
- package/dist/public/downloadGroupFile-By0GR6eV.js +1 -0
- package/dist/public/e2e/README.md +5 -1
- package/dist/public/{e2e-bridge-BQ4KzUt5.js → e2e-bridge-IGWsRl4i.js} +1 -1
- package/dist/public/file-viewer-D0eflgt6.js +1 -0
- package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
- package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
- package/dist/public/{git-DEUvQTUA.js → git-7Vq_SJSp.js} +2 -2
- package/dist/public/{iframe-sanitizer-0NS4XLOd.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
- package/dist/public/index.html +1 -1
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-llGkSJXg.js → initChatSplitResize-CzV2E7Ul.js} +1 -1
- package/dist/public/initControlPlane-C9jpnWev.js +1 -0
- package/dist/public/llms.txt +1 -1
- package/dist/public/main/index.html +1 -1
- package/dist/public/main/memory/index.html +1 -1
- package/dist/public/{markdown-CyYR4App.js → markdown-BnpKxSxl.js} +1 -1
- package/dist/public/{mcp-reconnect-CSx4aMwy.js → mcp-reconnect-9emsqGRe.js} +1 -1
- package/dist/public/{memoryStorage-Dwg8NhAZ.js → memoryStorage-C0KvLNUp.js} +1 -1
- package/dist/public/{orchestrator-DRfEudXk.js → orchestrator-3h9xxHUm.js} +676 -676
- package/dist/public/orchestrator-nJj59RqD.js +15 -0
- package/dist/public/{peerjs-CurBuKvk.js → peerjs-BP4laMXj.js} +1 -1
- package/dist/public/{push-client-Dii-vPEh.js → push-client-CrjEXK3U.js} +1 -1
- package/dist/public/service-worker/push-handler.js +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-5IHaDDMJ.js → shadow-claw-CbwETBWf.js} +2 -2
- package/dist/public/{shadow-claw-accounts-CZvz4iQz.js → shadow-claw-accounts-B4sBTQEj.js} +1 -1
- package/dist/public/{shadow-claw-channels-BKXFyRS3.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
- package/dist/public/{shadow-claw-chat-CCLuhWdI.js → shadow-claw-chat-Crv0DTCY.js} +6 -6
- package/dist/public/shadow-claw-control-plane-CX1IGbh9.js +199 -0
- package/dist/public/{shadow-claw-conversations-DV-87UUh.js → shadow-claw-conversations-_9qAaRXk.js} +1 -1
- package/dist/public/shadow-claw-dialog-BrOwAOdk.js +1 -0
- package/dist/public/{shadow-claw-file-viewer-Dk2dbAUd.js → shadow-claw-file-viewer-Blf6yGVD.js} +1 -1
- package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
- package/dist/public/{shadow-claw-git-DRA7_qck.js → shadow-claw-git-v8S0JjuR.js} +2 -2
- package/dist/public/{shadow-claw-integrations-1Xu5snuV.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
- package/dist/public/{shadow-claw-llm-6Aci1BDP.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-BUjAeVXC.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
- package/dist/public/{shadow-claw-networking-FQe4nCp3.js → shadow-claw-networking-qHbP95z9.js} +1 -1
- package/dist/public/{shadow-claw-notifications-DV2wibL3.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
- package/dist/public/{shadow-claw-page-header-B63S4HTU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
- package/dist/public/{shadow-claw-pages-DE3jWj5K.js → shadow-claw-pages-DFZHhzFZ.js} +1 -1
- package/dist/public/{shadow-claw-settings-S6DAKfYx.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
- package/dist/public/{shadow-claw-storage-DaacA3_K.js → shadow-claw-storage-BFynbP0j.js} +2 -2
- package/dist/public/{shadow-claw-task-server-IjKX042p.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
- package/dist/public/{shadow-claw-tasks-Cy0JYmGU.js → shadow-claw-tasks-Caxc9atL.js} +1 -1
- package/dist/public/{shadow-claw-terminal-znAt7HyP.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
- package/dist/public/{shadow-claw-tools-Cutv9Si5.js → shadow-claw-tools-BfJx3A9W.js} +1 -1
- package/dist/public/{shadow-claw-webvm-BH8mPDEu.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
- package/dist/public/{syncWebMcpRegistration-BqJxaMGR.js → syncWebMcpRegistration-B_M2fCjH.js} +1 -1
- package/dist/server.js +286 -189
- package/package.json +2 -1
- package/src/__mocks__/jszip.cjs +15 -5
- package/src/components/settings/shadow-claw-accounts/shadow-claw-accounts.test.ts +35 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +117 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +81 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +127 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +292 -0
- package/src/components/settings/shadow-claw-git/shadow-claw-git.test.ts +72 -0
- package/src/components/settings/shadow-claw-integrations/connection-test-auth.test.ts +20 -30
- package/src/components/settings/shadow-claw-integrations/shadow-claw-integrations.test.ts +116 -0
- package/src/components/settings/shadow-claw-llm/model-ranking.test.ts +143 -51
- package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +307 -4
- package/src/components/settings/shadow-claw-llm/shadow-claw-llm.ts +8 -8
- package/src/components/settings/shadow-claw-mcp-remote/shadow-claw-mcp-remote.test.ts +31 -0
- package/src/components/settings/shadow-claw-peerjs/shadow-claw-peerjs.test.ts +186 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +76 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +23 -2
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +19 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +2 -1
- package/src/components/shadow-claw/utils/initChatSplitResize.test.ts +180 -13
- package/src/components/shadow-claw/utils/setupEffects.test.ts +39 -0
- package/src/components/shadow-claw-a2ui/catalog/basic/shared.test.ts +117 -0
- package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.test.ts +1159 -1
- package/src/components/shadow-claw-chat/shadow-claw-chat.test.ts +1523 -19
- package/src/components/shadow-claw-chat/utils/escapeHtml.test.ts +13 -0
- package/src/components/shadow-claw-chat/utils/getPeerChatDisplayStatus.test.ts +20 -0
- package/src/components/shadow-claw-conversations/shadow-claw-conversations.test.ts +494 -0
- package/src/components/shadow-claw-dialog/shadow-claw-dialog.ts +13 -2
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +273 -0
- package/src/components/shadow-claw-files/shadow-claw-files.test.ts +408 -1
- package/src/components/shadow-claw-pdf-viewer/utils/installGetOrInsertComputedPolyfill.test.ts +65 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.html +7 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.test.ts +221 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.ts +2 -0
- package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +407 -0
- package/src/components/shadow-claw-terminal/utils/consumeEscapeSequence.test.ts +58 -0
- package/src/components/shadow-claw-tools/shadow-claw-tools.test.ts +80 -0
- package/src/config/config.ts +10 -0
- package/src/core/backup-controller.test.ts +101 -0
- package/src/core/backup-controller.ts +137 -0
- package/src/core/control-plane-client.test.ts +408 -0
- package/src/core/control-plane-client.ts +425 -0
- package/src/core/orchestrator/orchestrator.ts +4 -0
- package/src/core/orchestrator/utils/compactContext.test.ts +78 -0
- package/src/core/orchestrator/utils/operations/channel.test.ts +254 -9
- package/src/core/orchestrator/utils/operations/provider.test.ts +326 -34
- package/src/core/orchestrator/utils/operations/vm.test.ts +114 -2
- package/src/core/orchestrator/utils/syncProxyConfigToServiceWorker.test.ts +60 -0
- package/src/core/utils/initControlPlane.test.ts +276 -0
- package/src/core/utils/initControlPlane.ts +437 -0
- package/src/core/utils/initializeApp.ts +18 -0
- package/src/db/getSession.test.ts +13 -1
- package/src/server/app.test.ts +12 -0
- package/src/server/app.ts +18 -8
- package/src/server/client-registry.test.ts +250 -0
- package/src/server/client-registry.ts +477 -0
- package/src/server/config.ts +15 -0
- package/src/server/control-plane-types.ts +120 -0
- package/src/server/control-plane.test.ts +472 -0
- package/src/server/control-plane.ts +581 -0
- package/src/server/routes/backup.test.ts +263 -0
- package/src/server/routes/backup.ts +262 -0
- package/src/server/routes/integrations-email.test.ts +532 -0
- package/src/server/routes/llamafile.test.ts +172 -48
- package/src/server/routes/mesh-llm.test.ts +192 -0
- package/src/server/routes/ollama.test.ts +49 -0
- package/src/server/server-peer.test.ts +86 -0
- package/src/server/server-peer.ts +330 -0
- package/src/server/server.ts +35 -0
- package/src/server/services/llamafile-manager.test.ts +230 -6
- package/src/server/services/transformers-runtime.test.ts +186 -18
- package/src/server/webrtc.test.ts +21 -0
- package/src/server/webrtc.ts +72 -0
- package/src/service-worker/fetch-proxy.test.ts +64 -1
- package/src/service-worker/push-handler.ts +39 -0
- package/src/shell/vm.test.ts +744 -12
- package/src/storage/ensureMainGroupIndex.test.ts +153 -0
- package/src/storage/getGroupFile.test.ts +66 -0
- package/src/storage/memoryStorage.test.ts +82 -2
- package/src/storage/writeGroupFileBytes.test.ts +143 -0
- package/src/stores/chat-ui.test.ts +20 -0
- package/src/stores/orchestrator.test.ts +134 -0
- package/src/stores/toast.test.ts +50 -0
- package/src/stores/tools.test.ts +147 -10
- package/src/subsystems/channels/imessage.test.ts +59 -0
- package/src/subsystems/channels/peerjs.test.ts +500 -62
- package/src/subsystems/channels/peerjs.ts +71 -4
- package/src/subsystems/channels/telegram.test.ts +177 -0
- package/src/subsystems/email/catalog.test.ts +1 -0
- package/src/subsystems/email/connections.test.ts +39 -1
- package/src/subsystems/git/credentials.test.ts +160 -825
- package/src/subsystems/git/git.test.ts +463 -0
- package/src/subsystems/notifications/__mocks__/node-sqlite.cjs +12 -1
- package/src/subsystems/notifications/push-routes.test.ts +33 -0
- package/src/subsystems/notifications/push-routes.ts +23 -0
- package/src/subsystems/notifications/push-store.ts +1 -1
- package/src/subsystems/providers/builtin-ai-tasks.test.ts +74 -0
- package/src/subsystems/providers/litert-lm-provider.test.ts +212 -51
- package/src/subsystems/providers/model-registry.test.ts +115 -0
- package/src/subsystems/providers/providers.test.ts +94 -1
- package/src/subsystems/providers/transformers-js-provider.test.ts +279 -0
- package/src/subsystems/providers/utils/createModelCacheFetch.test.ts +2 -2
- package/src/subsystems/providers/utils/downloadModelToCache.test.ts +2 -2
- package/src/subsystems/providers/utils/loadModelStream.test.ts +2 -2
- package/src/subsystems/skills/activateSkill.test.ts +101 -0
- package/src/subsystems/skills/discoverSkills.test.ts +251 -0
- package/src/subsystems/tools/cron.test.ts +64 -0
- package/src/subsystems/tools/declarative.test.ts +255 -89
- package/src/testing/assets/v86.9pfs/libv86.mjs +63 -0
- package/src/testing/assets/v86.ext2/libv86.mjs +3 -0
- package/src/testing/assets_missing_v86/assets/v86.9pfs/libv86.mjs +1 -0
- package/src/testing/jest-setup.ts +328 -40
- package/src/testing/mock-v86.mjs +49 -0
- package/src/ui/a2ui/types.ts +4 -3
- package/src/ui/a2ui/utils/normaliseComponentsToMap.test.ts +102 -0
- package/src/ui/a2ui/utils/resolveChildIds.test.ts +63 -0
- package/src/ui/a2ui/utils/resolveDynamicNumber.test.ts +13 -1
- package/src/worker/tools/bash/utils/waitForVMReady.test.ts +40 -0
- package/src/worker/tools/email/email.test.ts +193 -8
- package/src/worker/tools/email/utils/asEmailAttachmentInputs.test.ts +47 -0
- package/src/worker/tools/git/git.test.ts +214 -3
- package/src/worker/tools/rooms/invite-to-room.test.ts +54 -0
- package/src/worker/tools/rooms/leave-room.test.ts +40 -0
- package/src/worker/tools/rooms/list-room-members.test.ts +75 -0
- package/src/worker/tools/tasks/tasks-utils.test.ts +22 -0
- package/src/worker/tools/ui/ask-user.test.ts +50 -0
- package/src/worker/tools/ui/clear-chat.test.ts +19 -0
- package/src/worker/tools/ui/get-current-time.test.ts +19 -0
- package/src/worker/tools/ui/javascript.test.ts +116 -0
- package/src/worker/tools/ui/list-components.test.ts +10 -0
- package/src/worker/tools/ui/list-tool-profiles.test.ts +42 -0
- package/src/worker/tools/ui/manage-tools.test.ts +34 -0
- package/src/worker/tools/ui/render-component.test.ts +346 -0
- package/src/worker/tools/ui/send-notification.test.ts +40 -0
- package/src/worker/tools/ui/utils/getAllowFullInternetAccess.test.ts +31 -0
- package/src/worker/tools/ui/utils/parseBooleanConfig.test.ts +25 -0
- package/src/worker/tools/workspace/attach-file.test.ts +84 -0
- package/src/worker/tools/workspace/copy-file.test.ts +69 -0
- package/src/worker/tools/workspace/create-directory.test.ts +40 -0
- package/src/worker/tools/workspace/delete-file.test.ts +73 -0
- package/src/worker/tools/workspace/diff-files.test.ts +94 -0
- package/src/worker/tools/workspace/list-files.test.ts +36 -0
- package/src/worker/tools/workspace/move-file.test.ts +66 -0
- package/src/worker/tools/workspace/open-file.test.ts +57 -0
- package/src/worker/tools/workspace/patch-file.test.ts +81 -0
- package/src/worker/tools/workspace/read-file.test.ts +116 -0
- package/src/worker/tools/workspace/send-file.test.ts +82 -0
- package/src/worker/tools/workspace/update-memory.test.ts +29 -0
- package/src/worker/tools/workspace/write-file.test.ts +28 -0
- package/src/worker/utils/rate-limit.test.ts +199 -0
- package/src/worker/utils/tool-state.test.ts +29 -0
- package/src/worker/utils/transformers-js-utils.test.ts +59 -0
- package/dist/public/config-BYAuxx3j.js +0 -1
- package/dist/public/downloadGroupFile-Drdvg1Ev.js +0 -1
- package/dist/public/file-viewer-CcZYw-PS.js +0 -1
- package/dist/public/orchestrator-o9Fj67BN.js +0 -15
- package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
- package/dist/public/shadow-claw-files-BpfpJIuP.js +0 -840
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { escapeHtml } from "./escapeHtml.js";
|
|
2
|
+
|
|
3
|
+
describe("escapeHtml", () => {
|
|
4
|
+
it("escapes special HTML characters (&, <, >, \", ')", () => {
|
|
5
|
+
expect(escapeHtml("<script>alert('xss & \"foo\"')</script>")).toBe(
|
|
6
|
+
"<script>alert('xss & "foo"')</script>",
|
|
7
|
+
);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("handles string without special characters", () => {
|
|
11
|
+
expect(escapeHtml("hello world")).toBe("hello world");
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getPeerChatDisplayStatus } from "./getPeerChatDisplayStatus.js";
|
|
2
|
+
|
|
3
|
+
describe("getPeerChatDisplayStatus", () => {
|
|
4
|
+
it("returns 'responding' when remote status is idle and remote is typing", () => {
|
|
5
|
+
expect(getPeerChatDisplayStatus("idle", true)).toBe("responding");
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it("returns remoteStatus when not typing", () => {
|
|
9
|
+
expect(getPeerChatDisplayStatus("idle", false)).toBe("idle");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns remoteStatus when remote status is not idle even if typing", () => {
|
|
13
|
+
expect(getPeerChatDisplayStatus("compacting" as any, true)).toBe(
|
|
14
|
+
"compacting",
|
|
15
|
+
);
|
|
16
|
+
expect(getPeerChatDisplayStatus("executing_tool" as any, true)).toBe(
|
|
17
|
+
"executing_tool",
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -1449,4 +1449,498 @@ describe("shadow-claw-conversations dialog submission methods", () => {
|
|
|
1449
1449
|
expect(el._pendingDeleteGroupId).toBeNull();
|
|
1450
1450
|
document.body.removeChild(el);
|
|
1451
1451
|
});
|
|
1452
|
+
|
|
1453
|
+
it("handles reordering conversations with draggedId/targetId and precomputed IDs", async () => {
|
|
1454
|
+
mockOrchStore.groups = [
|
|
1455
|
+
{ groupId: "br:1", name: "One", createdAt: 0 },
|
|
1456
|
+
{ groupId: "br:2", name: "Two", createdAt: 1 },
|
|
1457
|
+
{ groupId: "br:3", name: "Three", createdAt: 2 },
|
|
1458
|
+
];
|
|
1459
|
+
|
|
1460
|
+
const el = new ShadowClawConversations() as any;
|
|
1461
|
+
document.body.appendChild(el);
|
|
1462
|
+
await el.render();
|
|
1463
|
+
el.db = {} as any;
|
|
1464
|
+
|
|
1465
|
+
// 1. Precomputed IDs
|
|
1466
|
+
await el.handleReorder("br:1", "br:3", ["br:2", "br:3", "br:1"]);
|
|
1467
|
+
expect(mockOrchStore.reorderConversations).toHaveBeenCalledWith(el.db, [
|
|
1468
|
+
"br:2",
|
|
1469
|
+
"br:3",
|
|
1470
|
+
"br:1",
|
|
1471
|
+
]);
|
|
1472
|
+
|
|
1473
|
+
// 2. Dragged & Target IDs
|
|
1474
|
+
await el.handleReorder("br:1", "br:3");
|
|
1475
|
+
expect(mockOrchStore.reorderConversations).toHaveBeenCalledWith(el.db, [
|
|
1476
|
+
"br:2",
|
|
1477
|
+
"br:3",
|
|
1478
|
+
"br:1",
|
|
1479
|
+
]);
|
|
1480
|
+
|
|
1481
|
+
// 3. Nonexistent ID (no-op)
|
|
1482
|
+
jest.clearAllMocks();
|
|
1483
|
+
await el.handleReorder("br:invalid", "br:3");
|
|
1484
|
+
expect(mockOrchStore.reorderConversations).not.toHaveBeenCalled();
|
|
1485
|
+
|
|
1486
|
+
// 4. Missing DB (no-op)
|
|
1487
|
+
el.db = null;
|
|
1488
|
+
await el.handleReorder("br:1", "br:2");
|
|
1489
|
+
expect(mockOrchStore.reorderConversations).not.toHaveBeenCalled();
|
|
1490
|
+
|
|
1491
|
+
document.body.removeChild(el);
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
it("handles switching conversations and dispatches shadow-claw-navigate", async () => {
|
|
1495
|
+
const el = new ShadowClawConversations() as any;
|
|
1496
|
+
document.body.appendChild(el);
|
|
1497
|
+
await el.render();
|
|
1498
|
+
|
|
1499
|
+
const navigateListener = jest.fn();
|
|
1500
|
+
document.addEventListener("shadow-claw-navigate", navigateListener);
|
|
1501
|
+
|
|
1502
|
+
// 1. Switching to active group is a no-op
|
|
1503
|
+
mockOrchStore.activeGroupId = "br:main";
|
|
1504
|
+
await el.handleSwitch("br:main");
|
|
1505
|
+
expect(navigateListener).not.toHaveBeenCalled();
|
|
1506
|
+
|
|
1507
|
+
// 2. Switching to another group
|
|
1508
|
+
await el.handleSwitch("br:other");
|
|
1509
|
+
expect(navigateListener).toHaveBeenCalledWith(
|
|
1510
|
+
expect.objectContaining({
|
|
1511
|
+
detail: {
|
|
1512
|
+
page: "chat",
|
|
1513
|
+
groupId: "br:other",
|
|
1514
|
+
},
|
|
1515
|
+
}),
|
|
1516
|
+
);
|
|
1517
|
+
|
|
1518
|
+
document.removeEventListener("shadow-claw-navigate", navigateListener);
|
|
1519
|
+
document.body.removeChild(el);
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
it("cancels dialogs and resets pending state on cancel button clicks", async () => {
|
|
1523
|
+
const el = new ShadowClawConversations() as any;
|
|
1524
|
+
document.body.appendChild(el);
|
|
1525
|
+
await el.render();
|
|
1526
|
+
|
|
1527
|
+
el._setupDialogListeners();
|
|
1528
|
+
|
|
1529
|
+
// 1. Create cancel
|
|
1530
|
+
const createDialog = el.shadowRoot?.querySelector(
|
|
1531
|
+
".conversations__create-dialog",
|
|
1532
|
+
) as any;
|
|
1533
|
+
if (createDialog) createDialog.close = jest.fn();
|
|
1534
|
+
const createCancel = el.shadowRoot?.querySelector(
|
|
1535
|
+
".conversations__create-dialog .conversations__cancel",
|
|
1536
|
+
) as HTMLButtonElement;
|
|
1537
|
+
createCancel?.click();
|
|
1538
|
+
expect(createDialog?.close).toHaveBeenCalled();
|
|
1539
|
+
|
|
1540
|
+
// 2. Delete cancel
|
|
1541
|
+
el._pendingDeleteGroupId = "br:to-delete";
|
|
1542
|
+
const deleteDialog = el.shadowRoot?.querySelector(
|
|
1543
|
+
".conversations__delete-dialog",
|
|
1544
|
+
) as any;
|
|
1545
|
+
if (deleteDialog) deleteDialog.close = jest.fn();
|
|
1546
|
+
const deleteCancel = el.shadowRoot?.querySelector(
|
|
1547
|
+
".conversations__delete-dialog .conversations__cancel",
|
|
1548
|
+
) as HTMLButtonElement;
|
|
1549
|
+
deleteCancel?.click();
|
|
1550
|
+
expect(deleteDialog?.close).toHaveBeenCalled();
|
|
1551
|
+
expect(el._pendingDeleteGroupId).toBeNull();
|
|
1552
|
+
|
|
1553
|
+
// 3. Clone cancel
|
|
1554
|
+
el._pendingCloneGroupId = "br:to-clone";
|
|
1555
|
+
const cloneDialog = el.shadowRoot?.querySelector(
|
|
1556
|
+
".conversations__clone-dialog",
|
|
1557
|
+
) as any;
|
|
1558
|
+
if (cloneDialog) cloneDialog.close = jest.fn();
|
|
1559
|
+
const cloneCancel = el.shadowRoot?.querySelector(
|
|
1560
|
+
".conversations__clone-dialog .conversations__cancel",
|
|
1561
|
+
) as HTMLButtonElement;
|
|
1562
|
+
cloneCancel?.click();
|
|
1563
|
+
expect(cloneDialog?.close).toHaveBeenCalled();
|
|
1564
|
+
expect(el._pendingCloneGroupId).toBeNull();
|
|
1565
|
+
|
|
1566
|
+
// 4. Details cancel
|
|
1567
|
+
el._pendingRenameGroupId = "br:to-rename";
|
|
1568
|
+
const detailsDialog = el.shadowRoot?.querySelector(
|
|
1569
|
+
".conversations__details-dialog",
|
|
1570
|
+
) as any;
|
|
1571
|
+
if (detailsDialog) detailsDialog.close = jest.fn();
|
|
1572
|
+
const detailsCancel = el.shadowRoot?.querySelector(
|
|
1573
|
+
".conversations__details-dialog .conversations__cancel",
|
|
1574
|
+
) as HTMLButtonElement;
|
|
1575
|
+
detailsCancel?.click();
|
|
1576
|
+
expect(detailsDialog?.close).toHaveBeenCalled();
|
|
1577
|
+
expect(el._pendingRenameGroupId).toBeNull();
|
|
1578
|
+
|
|
1579
|
+
document.body.removeChild(el);
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
it("submits details dialog with rename and settings updates", async () => {
|
|
1583
|
+
mockOrchStore.groups = [
|
|
1584
|
+
{ groupId: "br:main", name: "Old Name", createdAt: 0, toolTags: [] },
|
|
1585
|
+
];
|
|
1586
|
+
const el = new ShadowClawConversations() as any;
|
|
1587
|
+
document.body.appendChild(el);
|
|
1588
|
+
await el.render();
|
|
1589
|
+
el.db = {} as any;
|
|
1590
|
+
|
|
1591
|
+
el._pendingRenameGroupId = "br:main";
|
|
1592
|
+
el._pendingRenameName = "Old Name";
|
|
1593
|
+
el._pendingDetailsToolTags = ["bash", "read_file"];
|
|
1594
|
+
el._pendingDetailsPinnedProvider = "anthropic";
|
|
1595
|
+
el._pendingDetailsPinnedModel = "claude-3-5-sonnet";
|
|
1596
|
+
el._pendingDetailsPinnedMaxTokens = 4096;
|
|
1597
|
+
el._pendingDetailsSubagentMode = "manual";
|
|
1598
|
+
el._pendingDetailsSubagentProvider = "openai";
|
|
1599
|
+
el._pendingDetailsSubagentModel = "gpt-4o";
|
|
1600
|
+
el._pendingDetailsSubagentMaxTokens = 2048;
|
|
1601
|
+
|
|
1602
|
+
const dialog = el.shadowRoot?.querySelector(
|
|
1603
|
+
".conversations__details-dialog",
|
|
1604
|
+
) as HTMLDialogElement;
|
|
1605
|
+
if (dialog) (dialog as any).close = jest.fn();
|
|
1606
|
+
|
|
1607
|
+
const nameInput = el.shadowRoot?.querySelector(
|
|
1608
|
+
".conversations__details-dialog .conversations__input",
|
|
1609
|
+
) as HTMLInputElement;
|
|
1610
|
+
if (nameInput) nameInput.value = "New Name";
|
|
1611
|
+
|
|
1612
|
+
await el._submitDetailsDialog();
|
|
1613
|
+
|
|
1614
|
+
expect(mockOrchStore.renameConversation).toHaveBeenCalledWith(
|
|
1615
|
+
el.db,
|
|
1616
|
+
"br:main",
|
|
1617
|
+
"New Name",
|
|
1618
|
+
);
|
|
1619
|
+
expect(mockOrchStore.updateConversationToolTags).toHaveBeenCalledWith(
|
|
1620
|
+
el.db,
|
|
1621
|
+
"br:main",
|
|
1622
|
+
["bash", "read_file"],
|
|
1623
|
+
);
|
|
1624
|
+
expect(mockOrchStore.updateConversationPinnedProvider).toHaveBeenCalledWith(
|
|
1625
|
+
el.db,
|
|
1626
|
+
"br:main",
|
|
1627
|
+
"anthropic",
|
|
1628
|
+
"claude-3-5-sonnet",
|
|
1629
|
+
4096,
|
|
1630
|
+
);
|
|
1631
|
+
expect(
|
|
1632
|
+
mockOrchStore.updateConversationSubagentSettings,
|
|
1633
|
+
).toHaveBeenCalledWith(
|
|
1634
|
+
el.db,
|
|
1635
|
+
"br:main",
|
|
1636
|
+
"manual",
|
|
1637
|
+
"openai",
|
|
1638
|
+
"gpt-4o",
|
|
1639
|
+
2048,
|
|
1640
|
+
);
|
|
1641
|
+
|
|
1642
|
+
document.body.removeChild(el);
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1645
|
+
it("handles item action button clicks (clone, details, delete, toggle actions)", async () => {
|
|
1646
|
+
mockOrchStore.groups = [
|
|
1647
|
+
{ groupId: "br:g1", name: "Group 1", createdAt: 0 },
|
|
1648
|
+
{ groupId: "br:g2", name: "Group 2", createdAt: 1 },
|
|
1649
|
+
];
|
|
1650
|
+
const el = new ShadowClawConversations() as any;
|
|
1651
|
+
document.body.appendChild(el);
|
|
1652
|
+
await el.render();
|
|
1653
|
+
el.db = {} as any;
|
|
1654
|
+
|
|
1655
|
+
const items = el.shadowRoot?.querySelectorAll(
|
|
1656
|
+
".conversation-item",
|
|
1657
|
+
) as NodeListOf<HTMLElement>;
|
|
1658
|
+
expect(items.length).toBe(2);
|
|
1659
|
+
|
|
1660
|
+
const dialogs = el.shadowRoot?.querySelectorAll("dialog") || [];
|
|
1661
|
+
dialogs.forEach((d: any) => {
|
|
1662
|
+
d.showModal = jest.fn();
|
|
1663
|
+
d.close = jest.fn();
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
const firstItem = items[0];
|
|
1667
|
+
const cloneBtn = firstItem.querySelector(
|
|
1668
|
+
"[data-action='clone']",
|
|
1669
|
+
) as HTMLButtonElement;
|
|
1670
|
+
const detailsBtn = firstItem.querySelector(
|
|
1671
|
+
"[data-action='details']",
|
|
1672
|
+
) as HTMLButtonElement;
|
|
1673
|
+
const deleteBtn = firstItem.querySelector(
|
|
1674
|
+
"[data-action='delete']",
|
|
1675
|
+
) as HTMLButtonElement;
|
|
1676
|
+
const toggleActionsBtn = firstItem.querySelector(
|
|
1677
|
+
".conversation-actions-toggle",
|
|
1678
|
+
) as HTMLButtonElement;
|
|
1679
|
+
|
|
1680
|
+
// Toggle actions menu
|
|
1681
|
+
toggleActionsBtn.click();
|
|
1682
|
+
expect(firstItem.classList.contains("show-actions")).toBe(true);
|
|
1683
|
+
|
|
1684
|
+
// Clone click opens clone dialog
|
|
1685
|
+
const handleCloneSpy = jest.spyOn(el, "handleClone");
|
|
1686
|
+
cloneBtn.click();
|
|
1687
|
+
expect(handleCloneSpy).toHaveBeenCalledWith("br:g1");
|
|
1688
|
+
|
|
1689
|
+
// Details click opens details dialog
|
|
1690
|
+
const handleDetailsSpy = jest.spyOn(el, "handleDetails");
|
|
1691
|
+
detailsBtn.click();
|
|
1692
|
+
expect(handleDetailsSpy).toHaveBeenCalledWith("br:g1", "Group 1");
|
|
1693
|
+
|
|
1694
|
+
// Delete click opens delete dialog
|
|
1695
|
+
const handleDeleteSpy = jest.spyOn(el, "handleDelete");
|
|
1696
|
+
deleteBtn.click();
|
|
1697
|
+
expect(handleDeleteSpy).toHaveBeenCalledWith("br:g1", "Group 1");
|
|
1698
|
+
|
|
1699
|
+
document.body.removeChild(el);
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
it("handles drag and drop reordering events on items", async () => {
|
|
1703
|
+
mockOrchStore.groups = [
|
|
1704
|
+
{ groupId: "br:g1", name: "Group 1", createdAt: 0 },
|
|
1705
|
+
{ groupId: "br:g2", name: "Group 2", createdAt: 1 },
|
|
1706
|
+
];
|
|
1707
|
+
const el = new ShadowClawConversations() as any;
|
|
1708
|
+
document.body.appendChild(el);
|
|
1709
|
+
await el.render();
|
|
1710
|
+
el.db = {} as any;
|
|
1711
|
+
|
|
1712
|
+
const list = el.shadowRoot?.querySelector(
|
|
1713
|
+
".conversation-list",
|
|
1714
|
+
) as HTMLElement;
|
|
1715
|
+
const items = el.shadowRoot?.querySelectorAll(
|
|
1716
|
+
".conversation-item",
|
|
1717
|
+
) as NodeListOf<HTMLElement>;
|
|
1718
|
+
const firstHandle = items[0].querySelector(".drag-handle") as HTMLElement;
|
|
1719
|
+
|
|
1720
|
+
// dragstart
|
|
1721
|
+
const dragStartEvent = new Event("dragstart", { bubbles: true }) as any;
|
|
1722
|
+
dragStartEvent.dataTransfer = { setData: jest.fn() };
|
|
1723
|
+
firstHandle.dispatchEvent(dragStartEvent);
|
|
1724
|
+
expect(el._draggedGroupId).toBe("br:g1");
|
|
1725
|
+
|
|
1726
|
+
// dragover on second item
|
|
1727
|
+
const dragOverEvent = new Event("dragover", { bubbles: true }) as any;
|
|
1728
|
+
dragOverEvent.preventDefault = jest.fn();
|
|
1729
|
+
list.dispatchEvent(dragOverEvent);
|
|
1730
|
+
|
|
1731
|
+
// drop on list
|
|
1732
|
+
const dropEvent = new Event("drop", { bubbles: true }) as any;
|
|
1733
|
+
dropEvent.preventDefault = jest.fn();
|
|
1734
|
+
list.dispatchEvent(dropEvent);
|
|
1735
|
+
|
|
1736
|
+
// dragend
|
|
1737
|
+
firstHandle.dispatchEvent(new Event("dragend", { bubbles: true }));
|
|
1738
|
+
expect(el._draggedGroupId).toBeNull();
|
|
1739
|
+
|
|
1740
|
+
document.body.removeChild(el);
|
|
1741
|
+
});
|
|
1742
|
+
|
|
1743
|
+
it("handles touch dragging reordering events on drag handle", async () => {
|
|
1744
|
+
mockOrchStore.groups = [
|
|
1745
|
+
{ groupId: "br:g1", name: "Group 1", createdAt: 0 },
|
|
1746
|
+
{ groupId: "br:g2", name: "Group 2", createdAt: 1 },
|
|
1747
|
+
];
|
|
1748
|
+
const el = new ShadowClawConversations() as any;
|
|
1749
|
+
document.body.appendChild(el);
|
|
1750
|
+
await el.render();
|
|
1751
|
+
el.db = {} as any;
|
|
1752
|
+
|
|
1753
|
+
const items = el.shadowRoot?.querySelectorAll(
|
|
1754
|
+
".conversation-item",
|
|
1755
|
+
) as NodeListOf<HTMLElement>;
|
|
1756
|
+
const firstHandle = items[0].querySelector(".drag-handle") as HTMLElement;
|
|
1757
|
+
|
|
1758
|
+
const list = el.shadowRoot?.querySelector(
|
|
1759
|
+
".conversation-list",
|
|
1760
|
+
) as HTMLElement;
|
|
1761
|
+
el.shadowRoot.elementFromPoint = jest.fn(() => items[1]);
|
|
1762
|
+
|
|
1763
|
+
// touchstart
|
|
1764
|
+
const touchStartEvent = new Event("touchstart", { bubbles: true }) as any;
|
|
1765
|
+
touchStartEvent.touches = [{ identifier: 1, clientY: 50 }];
|
|
1766
|
+
firstHandle.dispatchEvent(touchStartEvent);
|
|
1767
|
+
expect(el._touchDraggedGroupId).toBe("br:g1");
|
|
1768
|
+
|
|
1769
|
+
// touchmove
|
|
1770
|
+
const touchMoveEvent = new Event("touchmove", { bubbles: true }) as any;
|
|
1771
|
+
touchMoveEvent.touches = [{ identifier: 1, clientY: 100 }];
|
|
1772
|
+
list.dispatchEvent(touchMoveEvent);
|
|
1773
|
+
|
|
1774
|
+
// touchend
|
|
1775
|
+
const touchEndEvent = new Event("touchend", { bubbles: true }) as any;
|
|
1776
|
+
touchEndEvent.changedTouches = [{ identifier: 1, clientY: 100 }];
|
|
1777
|
+
list.dispatchEvent(touchEndEvent);
|
|
1778
|
+
expect(el._touchDraggedGroupId).toBeNull();
|
|
1779
|
+
|
|
1780
|
+
document.body.removeChild(el);
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
it("submits clone, create, and delete dialogs", async () => {
|
|
1784
|
+
const el = new ShadowClawConversations() as any;
|
|
1785
|
+
document.body.appendChild(el);
|
|
1786
|
+
await el.render();
|
|
1787
|
+
el.db = {} as any;
|
|
1788
|
+
|
|
1789
|
+
// 1. Clone dialog submit
|
|
1790
|
+
const cloneDialog = el.shadowRoot?.querySelector(
|
|
1791
|
+
".conversations__clone-dialog",
|
|
1792
|
+
);
|
|
1793
|
+
if (cloneDialog) (cloneDialog as any).close = jest.fn();
|
|
1794
|
+
el._pendingCloneGroupId = "br:g1";
|
|
1795
|
+
await el._submitCloneDialog();
|
|
1796
|
+
expect(mockOrchStore.cloneConversation).toHaveBeenCalledWith(
|
|
1797
|
+
el.db,
|
|
1798
|
+
"br:g1",
|
|
1799
|
+
);
|
|
1800
|
+
expect(el._pendingCloneGroupId).toBeNull();
|
|
1801
|
+
|
|
1802
|
+
// 2. Create dialog submit with empty name
|
|
1803
|
+
const createInput = el.shadowRoot?.querySelector(
|
|
1804
|
+
".conversations__create-dialog .conversations__input",
|
|
1805
|
+
) as HTMLInputElement;
|
|
1806
|
+
const createDialog = el.shadowRoot?.querySelector(
|
|
1807
|
+
".conversations__create-dialog",
|
|
1808
|
+
);
|
|
1809
|
+
if (createDialog) (createDialog as any).close = jest.fn();
|
|
1810
|
+
|
|
1811
|
+
if (createInput) createInput.value = " ";
|
|
1812
|
+
await el._submitCreateDialog();
|
|
1813
|
+
expect(mockOrchStore.createConversation).not.toHaveBeenCalled();
|
|
1814
|
+
|
|
1815
|
+
// Create dialog submit with valid name
|
|
1816
|
+
if (createInput) createInput.value = "New Conv";
|
|
1817
|
+
await el._submitCreateDialog();
|
|
1818
|
+
expect(mockOrchStore.createConversation).toHaveBeenCalledWith(
|
|
1819
|
+
el.db,
|
|
1820
|
+
"New Conv",
|
|
1821
|
+
);
|
|
1822
|
+
|
|
1823
|
+
// 3. Delete dialog submit
|
|
1824
|
+
const deleteDialog = el.shadowRoot?.querySelector(
|
|
1825
|
+
".conversations__delete-dialog",
|
|
1826
|
+
);
|
|
1827
|
+
if (deleteDialog) (deleteDialog as any).close = jest.fn();
|
|
1828
|
+
el._pendingDeleteGroupId = "br:g2";
|
|
1829
|
+
await el._submitDeleteDialog();
|
|
1830
|
+
expect(mockOrchStore.deleteConversation).toHaveBeenCalledWith(
|
|
1831
|
+
el.db,
|
|
1832
|
+
"br:g2",
|
|
1833
|
+
);
|
|
1834
|
+
expect(el._pendingDeleteGroupId).toBeNull();
|
|
1835
|
+
|
|
1836
|
+
document.body.removeChild(el);
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
it("submits details dialog with tool tags and provider settings", async () => {
|
|
1840
|
+
const el = new ShadowClawConversations() as any;
|
|
1841
|
+
document.body.appendChild(el);
|
|
1842
|
+
await el.render();
|
|
1843
|
+
el.db = {} as any;
|
|
1844
|
+
|
|
1845
|
+
const detailsDialog = el.shadowRoot?.querySelector(
|
|
1846
|
+
".conversations__details-dialog",
|
|
1847
|
+
);
|
|
1848
|
+
if (detailsDialog) (detailsDialog as any).close = jest.fn();
|
|
1849
|
+
|
|
1850
|
+
const detailsInput = el.shadowRoot?.querySelector(
|
|
1851
|
+
".conversations__details-dialog .conversations__input",
|
|
1852
|
+
) as HTMLInputElement;
|
|
1853
|
+
|
|
1854
|
+
el._pendingRenameGroupId = "br:g1";
|
|
1855
|
+
el._pendingRenameName = "Old Name";
|
|
1856
|
+
el._pendingDetailsToolTags = ["bash", "git"];
|
|
1857
|
+
el._pendingDetailsPinnedProvider = "anthropic";
|
|
1858
|
+
el._pendingDetailsPinnedModel = "claude-3";
|
|
1859
|
+
el._pendingDetailsPinnedMaxTokens = 2048;
|
|
1860
|
+
el._pendingDetailsProviderRuntimeOverrides = { temperature: 0.7 };
|
|
1861
|
+
el._pendingDetailsSubagentMode = "manual";
|
|
1862
|
+
el._pendingDetailsSubagentProvider = "openai";
|
|
1863
|
+
el._pendingDetailsSubagentModel = "gpt-4";
|
|
1864
|
+
el._pendingDetailsSubagentMaxTokens = 1024;
|
|
1865
|
+
|
|
1866
|
+
if (detailsInput) detailsInput.value = "Updated Name";
|
|
1867
|
+
|
|
1868
|
+
await el._submitDetailsDialog();
|
|
1869
|
+
|
|
1870
|
+
expect(mockOrchStore.renameConversation).toHaveBeenCalledWith(
|
|
1871
|
+
el.db,
|
|
1872
|
+
"br:g1",
|
|
1873
|
+
"Updated Name",
|
|
1874
|
+
);
|
|
1875
|
+
expect(mockOrchStore.updateConversationToolTags).toHaveBeenCalledWith(
|
|
1876
|
+
el.db,
|
|
1877
|
+
"br:g1",
|
|
1878
|
+
["bash", "git"],
|
|
1879
|
+
);
|
|
1880
|
+
expect(mockOrchStore.updateConversationPinnedProvider).toHaveBeenCalledWith(
|
|
1881
|
+
el.db,
|
|
1882
|
+
"br:g1",
|
|
1883
|
+
"anthropic",
|
|
1884
|
+
"claude-3",
|
|
1885
|
+
2048,
|
|
1886
|
+
);
|
|
1887
|
+
expect(
|
|
1888
|
+
mockOrchStore.updateConversationProviderRuntimeOverrides,
|
|
1889
|
+
).toHaveBeenCalledWith(el.db, "br:g1", { temperature: 0.7 });
|
|
1890
|
+
expect(
|
|
1891
|
+
mockOrchStore.updateConversationSubagentSettings,
|
|
1892
|
+
).toHaveBeenCalledWith(el.db, "br:g1", "manual", "openai", "gpt-4", 1024);
|
|
1893
|
+
|
|
1894
|
+
expect(el._pendingRenameGroupId).toBeNull();
|
|
1895
|
+
expect(el._pendingRenameName).toBeNull();
|
|
1896
|
+
|
|
1897
|
+
// Reorder tests
|
|
1898
|
+
mockOrchStore.groups = [
|
|
1899
|
+
{ groupId: "g1", name: "G1" },
|
|
1900
|
+
{ groupId: "g2", name: "G2" },
|
|
1901
|
+
{ groupId: "g3", name: "G3" },
|
|
1902
|
+
];
|
|
1903
|
+
await el.handleReorder("g3", "g1");
|
|
1904
|
+
expect(mockOrchStore.reorderConversations).toHaveBeenCalledWith(el.db, [
|
|
1905
|
+
"g3",
|
|
1906
|
+
"g1",
|
|
1907
|
+
"g2",
|
|
1908
|
+
]);
|
|
1909
|
+
|
|
1910
|
+
await el.handleReorder("g1", "g2", ["g2", "g1", "g3"]);
|
|
1911
|
+
expect(mockOrchStore.reorderConversations).toHaveBeenCalledWith(el.db, [
|
|
1912
|
+
"g2",
|
|
1913
|
+
"g1",
|
|
1914
|
+
"g3",
|
|
1915
|
+
]);
|
|
1916
|
+
|
|
1917
|
+
// Persist height test
|
|
1918
|
+
await el._persistHeight(350);
|
|
1919
|
+
expect(mockSetConfig).toHaveBeenCalledWith(
|
|
1920
|
+
el.db,
|
|
1921
|
+
"conversations_height",
|
|
1922
|
+
350,
|
|
1923
|
+
);
|
|
1924
|
+
|
|
1925
|
+
// _loadProviderModels tests
|
|
1926
|
+
(global.fetch as any) = jest.fn<any>().mockResolvedValueOnce({
|
|
1927
|
+
ok: true,
|
|
1928
|
+
status: 200,
|
|
1929
|
+
json: async () => ({
|
|
1930
|
+
data: [{ id: "model-dyn-1", name: "Dynamic Model 1" }],
|
|
1931
|
+
}),
|
|
1932
|
+
});
|
|
1933
|
+
|
|
1934
|
+
const providerWithDynamic = {
|
|
1935
|
+
id: "prov-test",
|
|
1936
|
+
name: "Test Provider",
|
|
1937
|
+
modelsUrl: "http://localhost/models",
|
|
1938
|
+
};
|
|
1939
|
+
|
|
1940
|
+
const models = await el._loadProviderModels(providerWithDynamic);
|
|
1941
|
+
expect(models).toHaveLength(1);
|
|
1942
|
+
expect(models[0].id).toBe("model-dyn-1");
|
|
1943
|
+
|
|
1944
|
+
document.body.removeChild(el);
|
|
1945
|
+
});
|
|
1452
1946
|
});
|
|
@@ -15,7 +15,14 @@ export class ShadowClawDialog extends HTMLElement {
|
|
|
15
15
|
|
|
16
16
|
close(returnValue?: string): void {
|
|
17
17
|
this.ensureDialog();
|
|
18
|
-
this._dialog?.close
|
|
18
|
+
if (typeof this._dialog?.close === "function") {
|
|
19
|
+
this._dialog.close(returnValue);
|
|
20
|
+
} else if (this._dialog) {
|
|
21
|
+
this._dialog.removeAttribute("open");
|
|
22
|
+
if (returnValue !== undefined) {
|
|
23
|
+
this._dialog.returnValue = returnValue;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
get dialog(): HTMLDialogElement | null {
|
|
@@ -67,7 +74,11 @@ export class ShadowClawDialog extends HTMLElement {
|
|
|
67
74
|
|
|
68
75
|
showModal(): void {
|
|
69
76
|
this.ensureDialog();
|
|
70
|
-
this._dialog?.showModal
|
|
77
|
+
if (typeof this._dialog?.showModal === "function") {
|
|
78
|
+
this._dialog.showModal();
|
|
79
|
+
} else if (this._dialog) {
|
|
80
|
+
this._dialog.setAttribute("open", "");
|
|
81
|
+
}
|
|
71
82
|
}
|
|
72
83
|
|
|
73
84
|
private syncDialogAttributes(): void {
|