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,153 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
import { DEFAULT_MAIN_GROUP_INDEX_CONTENT } from "./defaultIndexContent.mjs";
|
|
3
|
+
|
|
4
|
+
const mockGroupFileExists = jest.fn() as any;
|
|
5
|
+
const mockWriteGroupFile = jest.fn() as any;
|
|
6
|
+
const mockGetConfig = jest.fn() as any;
|
|
7
|
+
const mockSetConfig = jest.fn() as any;
|
|
8
|
+
|
|
9
|
+
jest.unstable_mockModule("./groupFileExists.js", () => ({
|
|
10
|
+
groupFileExists: mockGroupFileExists,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.unstable_mockModule("./writeGroupFile.js", () => ({
|
|
14
|
+
writeGroupFile: mockWriteGroupFile,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
jest.unstable_mockModule("../db/getConfig.js", () => ({
|
|
18
|
+
getConfig: mockGetConfig,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
jest.unstable_mockModule("../db/setConfig.js", () => ({
|
|
22
|
+
setConfig: mockSetConfig,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
ensureMainGroupIndex,
|
|
27
|
+
STATIC_MAIN_GROUP_INDEX_PATH,
|
|
28
|
+
resolveStaticMainGroupIndexUrl,
|
|
29
|
+
isMainGroupIndexSuppressed,
|
|
30
|
+
setMainGroupIndexSuppressed,
|
|
31
|
+
} = await import("./ensureMainGroupIndex.js");
|
|
32
|
+
|
|
33
|
+
const { DEFAULT_GROUP_ID } = await import("../config/config.js");
|
|
34
|
+
|
|
35
|
+
describe("ensureMainGroupIndex", () => {
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
jest.clearAllMocks();
|
|
38
|
+
(mockGroupFileExists as any).mockReset();
|
|
39
|
+
(mockWriteGroupFile as any).mockReset();
|
|
40
|
+
(mockGetConfig as any).mockReset();
|
|
41
|
+
(mockSetConfig as any).mockReset();
|
|
42
|
+
(mockGetConfig as any).mockResolvedValue(undefined);
|
|
43
|
+
(mockSetConfig as any).mockResolvedValue(undefined);
|
|
44
|
+
(globalThis as any).fetch = (jest.fn() as any).mockRejectedValue(
|
|
45
|
+
new Error("no static template"),
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("returns true immediately if index.html already exists", async () => {
|
|
50
|
+
mockGroupFileExists.mockResolvedValue(true);
|
|
51
|
+
|
|
52
|
+
const res = await ensureMainGroupIndex({} as any, "group-1");
|
|
53
|
+
expect(res).toBe(true);
|
|
54
|
+
expect(mockWriteGroupFile).not.toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("returns false if index.html is suppressed for DEFAULT_GROUP_ID", async () => {
|
|
58
|
+
mockGroupFileExists.mockResolvedValue(false);
|
|
59
|
+
mockGetConfig.mockResolvedValue(true);
|
|
60
|
+
|
|
61
|
+
const res = await ensureMainGroupIndex({} as any, DEFAULT_GROUP_ID);
|
|
62
|
+
expect(res).toBe(false);
|
|
63
|
+
expect(mockWriteGroupFile).not.toHaveBeenCalled();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("creates index.html with custom content if supplied", async () => {
|
|
67
|
+
mockGroupFileExists.mockResolvedValue(false);
|
|
68
|
+
mockWriteGroupFile.mockResolvedValue(undefined);
|
|
69
|
+
|
|
70
|
+
const res = await ensureMainGroupIndex(
|
|
71
|
+
{} as any,
|
|
72
|
+
"custom-group",
|
|
73
|
+
"<h1>Custom Title</h1>",
|
|
74
|
+
);
|
|
75
|
+
expect(res).toBe(true);
|
|
76
|
+
expect(mockWriteGroupFile).toHaveBeenCalledWith(
|
|
77
|
+
{},
|
|
78
|
+
"custom-group",
|
|
79
|
+
"index.html",
|
|
80
|
+
"<h1>Custom Title</h1>",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("fetches static template if available", async () => {
|
|
85
|
+
mockGroupFileExists.mockResolvedValue(false);
|
|
86
|
+
(globalThis as any).fetch = (jest.fn() as any).mockResolvedValue({
|
|
87
|
+
ok: true,
|
|
88
|
+
text: async () => "<html>Static Template</html>",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const res = await ensureMainGroupIndex({} as any, DEFAULT_GROUP_ID);
|
|
92
|
+
expect(res).toBe(true);
|
|
93
|
+
expect(mockWriteGroupFile).toHaveBeenCalledWith(
|
|
94
|
+
{},
|
|
95
|
+
DEFAULT_GROUP_ID,
|
|
96
|
+
"index.html",
|
|
97
|
+
"<html>Static Template</html>",
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("falls back to DEFAULT_MAIN_GROUP_INDEX_CONTENT when fetch fails", async () => {
|
|
102
|
+
mockGroupFileExists.mockResolvedValue(false);
|
|
103
|
+
mockWriteGroupFile.mockResolvedValue(undefined);
|
|
104
|
+
|
|
105
|
+
const res = await ensureMainGroupIndex({} as any, DEFAULT_GROUP_ID);
|
|
106
|
+
expect(res).toBe(true);
|
|
107
|
+
expect(mockWriteGroupFile).toHaveBeenCalledWith(
|
|
108
|
+
{},
|
|
109
|
+
DEFAULT_GROUP_ID,
|
|
110
|
+
"index.html",
|
|
111
|
+
DEFAULT_MAIN_GROUP_INDEX_CONTENT,
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("returns false if writeGroupFile throws an error", async () => {
|
|
116
|
+
mockGroupFileExists.mockResolvedValue(false);
|
|
117
|
+
mockWriteGroupFile.mockRejectedValue(new Error("Storage write failure"));
|
|
118
|
+
|
|
119
|
+
const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
120
|
+
const res = await ensureMainGroupIndex({} as any, DEFAULT_GROUP_ID);
|
|
121
|
+
expect(res).toBe(false);
|
|
122
|
+
warnSpy.mockRestore();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("tests isMainGroupIndexSuppressed and setMainGroupIndexSuppressed", async () => {
|
|
126
|
+
mockGetConfig.mockResolvedValueOnce("true").mockResolvedValueOnce(false);
|
|
127
|
+
|
|
128
|
+
expect(await isMainGroupIndexSuppressed({} as any)).toBe(true);
|
|
129
|
+
expect(await isMainGroupIndexSuppressed({} as any)).toBe(false);
|
|
130
|
+
|
|
131
|
+
await setMainGroupIndexSuppressed({} as any, true);
|
|
132
|
+
expect(mockSetConfig).toHaveBeenCalledWith(
|
|
133
|
+
{},
|
|
134
|
+
"main_group_index_suppressed",
|
|
135
|
+
true,
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("resolves static main group index url with and without window.location.origin", () => {
|
|
140
|
+
const url = resolveStaticMainGroupIndexUrl();
|
|
141
|
+
expect(url).toContain(STATIC_MAIN_GROUP_INDEX_PATH);
|
|
142
|
+
|
|
143
|
+
const originalLocation = window.location;
|
|
144
|
+
try {
|
|
145
|
+
delete (window as any).location;
|
|
146
|
+
expect(resolveStaticMainGroupIndexUrl()).toContain(
|
|
147
|
+
STATIC_MAIN_GROUP_INDEX_PATH,
|
|
148
|
+
);
|
|
149
|
+
} finally {
|
|
150
|
+
(window as any).location = originalLocation;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("./getGroupDir.js", () => ({
|
|
4
|
+
getGroupDir: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
jest.unstable_mockModule("./parsePath.js", () => ({
|
|
8
|
+
parsePath: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const { getGroupFile } = await import("./getGroupFile.js");
|
|
12
|
+
const { getGroupDir } = await import("./getGroupDir.js");
|
|
13
|
+
const { parsePath } = await import("./parsePath.js");
|
|
14
|
+
|
|
15
|
+
describe("getGroupFile", () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("retrieves native File object from nested directory structure", async () => {
|
|
21
|
+
const mockFile = new File(["test data"], "report.pdf", {
|
|
22
|
+
type: "application/pdf",
|
|
23
|
+
});
|
|
24
|
+
const fileHandle: any = {
|
|
25
|
+
getFile: (jest.fn() as any).mockResolvedValue(mockFile),
|
|
26
|
+
};
|
|
27
|
+
const subDir: any = {
|
|
28
|
+
getFileHandle: (jest.fn() as any).mockResolvedValue(fileHandle),
|
|
29
|
+
};
|
|
30
|
+
const rootDir: any = {
|
|
31
|
+
getDirectoryHandle: (jest.fn() as any).mockResolvedValue(subDir),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
(getGroupDir as any).mockResolvedValue(rootDir);
|
|
35
|
+
(parsePath as any).mockReturnValue({
|
|
36
|
+
dirs: ["docs"],
|
|
37
|
+
filename: "report.pdf",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const result = await getGroupFile({} as any, "group-1", "docs/report.pdf");
|
|
41
|
+
|
|
42
|
+
expect(getGroupDir).toHaveBeenCalledWith({}, "group-1");
|
|
43
|
+
expect(rootDir.getDirectoryHandle).toHaveBeenCalledWith("docs");
|
|
44
|
+
expect(subDir.getFileHandle).toHaveBeenCalledWith("report.pdf");
|
|
45
|
+
expect(fileHandle.getFile).toHaveBeenCalled();
|
|
46
|
+
expect(result).toBe(mockFile);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("retrieves native File from root workspace directory", async () => {
|
|
50
|
+
const mockFile = new File(["hello"], "README.md");
|
|
51
|
+
const fileHandle: any = {
|
|
52
|
+
getFile: (jest.fn() as any).mockResolvedValue(mockFile),
|
|
53
|
+
};
|
|
54
|
+
const rootDir: any = {
|
|
55
|
+
getFileHandle: (jest.fn() as any).mockResolvedValue(fileHandle),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
(getGroupDir as any).mockResolvedValue(rootDir);
|
|
59
|
+
(parsePath as any).mockReturnValue({ dirs: [], filename: "README.md" });
|
|
60
|
+
|
|
61
|
+
const result = await getGroupFile({} as any, "group-2", "README.md");
|
|
62
|
+
|
|
63
|
+
expect(rootDir.getFileHandle).toHaveBeenCalledWith("README.md");
|
|
64
|
+
expect(result).toBe(mockFile);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MemoryDirectoryHandle,
|
|
3
3
|
MemoryFileHandle,
|
|
4
|
+
MemorySyncAccessHandle,
|
|
4
5
|
getMemoryOpfsRoot,
|
|
6
|
+
isMemoryStorageFallbackActive,
|
|
5
7
|
resetMemoryOpfsRoot,
|
|
6
8
|
} from "./memoryStorage.js";
|
|
7
9
|
|
|
@@ -65,6 +67,41 @@ describe("memoryStorage", () => {
|
|
|
65
67
|
expect(await file2.text()).toBe("AB");
|
|
66
68
|
});
|
|
67
69
|
|
|
70
|
+
it("supports MemorySyncAccessHandle read, write, truncate, getSize, flush and close", async () => {
|
|
71
|
+
const fileHandle = new MemoryFileHandle("sync.dat");
|
|
72
|
+
const syncHandle: MemorySyncAccessHandle =
|
|
73
|
+
await fileHandle.createSyncAccessHandle();
|
|
74
|
+
|
|
75
|
+
expect(syncHandle.getSize()).toBe(0);
|
|
76
|
+
|
|
77
|
+
const writeData = new Uint8Array([10, 20, 30, 40]);
|
|
78
|
+
const written = syncHandle.write(writeData, { at: 0 });
|
|
79
|
+
expect(written).toBe(4);
|
|
80
|
+
expect(syncHandle.getSize()).toBe(4);
|
|
81
|
+
|
|
82
|
+
syncHandle.flush(); // No-op
|
|
83
|
+
|
|
84
|
+
const readBuffer = new Uint8Array(4);
|
|
85
|
+
const readBytes = syncHandle.read(readBuffer, { at: 0 });
|
|
86
|
+
expect(readBytes).toBe(4);
|
|
87
|
+
expect(Array.from(readBuffer)).toEqual([10, 20, 30, 40]);
|
|
88
|
+
|
|
89
|
+
// Read past end
|
|
90
|
+
const emptyBuffer = new Uint8Array(2);
|
|
91
|
+
expect(syncHandle.read(emptyBuffer, { at: 100 })).toBe(0);
|
|
92
|
+
|
|
93
|
+
// Truncate
|
|
94
|
+
syncHandle.truncate(2);
|
|
95
|
+
expect(syncHandle.getSize()).toBe(2);
|
|
96
|
+
|
|
97
|
+
// Close and verify operations throw
|
|
98
|
+
syncHandle.close();
|
|
99
|
+
expect(() => syncHandle.getSize()).toThrow();
|
|
100
|
+
expect(() => syncHandle.read(readBuffer)).toThrow();
|
|
101
|
+
expect(() => syncHandle.write(writeData)).toThrow();
|
|
102
|
+
expect(() => syncHandle.truncate(1)).toThrow();
|
|
103
|
+
});
|
|
104
|
+
|
|
68
105
|
it("handles permissions and equality", async () => {
|
|
69
106
|
const fileHandle = new MemoryFileHandle("test.txt");
|
|
70
107
|
expect(await fileHandle.queryPermission()).toBe("granted");
|
|
@@ -94,6 +131,14 @@ describe("memoryStorage", () => {
|
|
|
94
131
|
expect(existingFile).toBe(file);
|
|
95
132
|
});
|
|
96
133
|
|
|
134
|
+
it("handles special directory names (. and ..)", async () => {
|
|
135
|
+
const dir = new MemoryDirectoryHandle("root");
|
|
136
|
+
const current = await dir.getDirectoryHandle(".");
|
|
137
|
+
expect(current).toBe(dir);
|
|
138
|
+
|
|
139
|
+
await expect(dir.getDirectoryHandle("..")).rejects.toThrow();
|
|
140
|
+
});
|
|
141
|
+
|
|
97
142
|
it("throws NotFoundError when entry does not exist and create is false", async () => {
|
|
98
143
|
const dir = new MemoryDirectoryHandle("root");
|
|
99
144
|
await expect(dir.getDirectoryHandle("missing")).rejects.toThrow();
|
|
@@ -109,6 +154,28 @@ describe("memoryStorage", () => {
|
|
|
109
154
|
await expect(dir.getDirectoryHandle("file.txt")).rejects.toThrow();
|
|
110
155
|
});
|
|
111
156
|
|
|
157
|
+
it("resolves descendant handle paths", async () => {
|
|
158
|
+
const root = new MemoryDirectoryHandle("root");
|
|
159
|
+
const folderA = await root.getDirectoryHandle("folderA", {
|
|
160
|
+
create: true,
|
|
161
|
+
});
|
|
162
|
+
const folderB = await folderA.getDirectoryHandle("folderB", {
|
|
163
|
+
create: true,
|
|
164
|
+
});
|
|
165
|
+
const file = await folderB.getFileHandle("item.txt", { create: true });
|
|
166
|
+
|
|
167
|
+
expect(await root.resolve(root)).toEqual([]);
|
|
168
|
+
expect(await root.resolve(folderA)).toEqual(["folderA"]);
|
|
169
|
+
expect(await root.resolve(file)).toEqual([
|
|
170
|
+
"folderA",
|
|
171
|
+
"folderB",
|
|
172
|
+
"item.txt",
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
const unrelated = new MemoryFileHandle("unrelated.txt");
|
|
176
|
+
expect(await root.resolve(unrelated)).toBeNull();
|
|
177
|
+
});
|
|
178
|
+
|
|
112
179
|
it("iterates entries, keys, and values", async () => {
|
|
113
180
|
const dir = new MemoryDirectoryHandle("root");
|
|
114
181
|
await dir.getDirectoryHandle("b_dir", { create: true });
|
|
@@ -131,6 +198,13 @@ describe("memoryStorage", () => {
|
|
|
131
198
|
values.push(v.name);
|
|
132
199
|
}
|
|
133
200
|
expect(values).toEqual(["b_dir", "a_file.txt"]);
|
|
201
|
+
|
|
202
|
+
// async iterator fallback
|
|
203
|
+
const directEntries: string[] = [];
|
|
204
|
+
for await (const [name, handle] of dir) {
|
|
205
|
+
directEntries.push(`${name}:${handle.kind}`);
|
|
206
|
+
}
|
|
207
|
+
expect(directEntries).toEqual(["b_dir:directory", "a_file.txt:file"]);
|
|
134
208
|
});
|
|
135
209
|
|
|
136
210
|
it("removes files and empty/recursive directories", async () => {
|
|
@@ -149,12 +223,18 @@ describe("memoryStorage", () => {
|
|
|
149
223
|
});
|
|
150
224
|
});
|
|
151
225
|
|
|
152
|
-
describe("getMemoryOpfsRoot", () => {
|
|
153
|
-
it("returns a singleton OPFS root child", () => {
|
|
226
|
+
describe("getMemoryOpfsRoot & isMemoryStorageFallbackActive", () => {
|
|
227
|
+
it("returns a singleton OPFS root child and reports fallback active", () => {
|
|
228
|
+
expect(isMemoryStorageFallbackActive()).toBe(false);
|
|
154
229
|
const root1 = getMemoryOpfsRoot();
|
|
230
|
+
expect(isMemoryStorageFallbackActive()).toBe(true);
|
|
231
|
+
|
|
155
232
|
const root2 = getMemoryOpfsRoot();
|
|
156
233
|
expect(root1).toBe(root2);
|
|
157
234
|
expect(root1.name).toBeTruthy();
|
|
235
|
+
|
|
236
|
+
resetMemoryOpfsRoot();
|
|
237
|
+
expect(isMemoryStorageFallbackActive()).toBe(false);
|
|
158
238
|
});
|
|
159
239
|
});
|
|
160
240
|
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule("./getGroupDir.js", () => ({
|
|
4
|
+
getGroupDir: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
jest.unstable_mockModule("./parsePath.js", () => ({ parsePath: jest.fn() }));
|
|
8
|
+
|
|
9
|
+
jest.unstable_mockModule("./storage.js", () => ({
|
|
10
|
+
getOpfsRootDirName: jest.fn().mockReturnValue("shadowclaw"),
|
|
11
|
+
getStorageStatus: (jest.fn() as any).mockResolvedValue({
|
|
12
|
+
type: "opfs",
|
|
13
|
+
permission: "granted",
|
|
14
|
+
name: "OPFS",
|
|
15
|
+
}),
|
|
16
|
+
invalidateStorageRoot: jest.fn(),
|
|
17
|
+
isStaleHandleError: jest.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
jest.unstable_mockModule("./writeFileHandle.js", () => ({
|
|
21
|
+
writeFileHandle: (jest.fn() as any).mockResolvedValue(undefined),
|
|
22
|
+
writeOpfsPathViaWorker: (jest.fn() as any).mockResolvedValue(undefined),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const { writeGroupFileBytes } = await import("./writeGroupFileBytes.js");
|
|
26
|
+
const { getGroupDir } = await import("./getGroupDir.js");
|
|
27
|
+
const { parsePath } = await import("./parsePath.js");
|
|
28
|
+
const { getStorageStatus, invalidateStorageRoot, isStaleHandleError } =
|
|
29
|
+
await import("./storage.js");
|
|
30
|
+
const { writeFileHandle, writeOpfsPathViaWorker } =
|
|
31
|
+
await import("./writeFileHandle.js");
|
|
32
|
+
|
|
33
|
+
describe("writeGroupFileBytes", () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
(isStaleHandleError as any).mockReturnValue(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("writes binary Uint8Array content to target file", async () => {
|
|
40
|
+
const fileHandle: any = {};
|
|
41
|
+
const nested: any = {
|
|
42
|
+
getFileHandle: (jest.fn() as any).mockResolvedValue(fileHandle),
|
|
43
|
+
};
|
|
44
|
+
const root: any = {
|
|
45
|
+
getDirectoryHandle: (jest.fn() as any).mockResolvedValue(nested),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
(getGroupDir as any).mockResolvedValue(root);
|
|
49
|
+
(parsePath as any).mockReturnValue({
|
|
50
|
+
dirs: ["images"],
|
|
51
|
+
filename: "photo.png",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const content = new Uint8Array([1, 2, 3, 4]);
|
|
55
|
+
await writeGroupFileBytes({} as any, "g1", "images/photo.png", content);
|
|
56
|
+
|
|
57
|
+
expect(root.getDirectoryHandle).toHaveBeenCalledWith("images", {
|
|
58
|
+
create: true,
|
|
59
|
+
});
|
|
60
|
+
expect(nested.getFileHandle).toHaveBeenCalledWith("photo.png", {
|
|
61
|
+
create: true,
|
|
62
|
+
});
|
|
63
|
+
expect(writeFileHandle).toHaveBeenCalledWith(fileHandle, content);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("falls back to OPFS worker write when stream writing is unsupported in OPFS", async () => {
|
|
67
|
+
const fileHandle: any = {};
|
|
68
|
+
const nested: any = {
|
|
69
|
+
getFileHandle: (jest.fn() as any).mockResolvedValue(fileHandle),
|
|
70
|
+
};
|
|
71
|
+
const root: any = {
|
|
72
|
+
getDirectoryHandle: (jest.fn() as any).mockResolvedValue(nested),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
(getGroupDir as any).mockResolvedValue(root);
|
|
76
|
+
(parsePath as any).mockReturnValue({ dirs: ["sub"], filename: "bin.dat" });
|
|
77
|
+
|
|
78
|
+
(writeFileHandle as any).mockRejectedValueOnce(
|
|
79
|
+
new Error(
|
|
80
|
+
"Writable file streams are not supported by this browser/storage backend.",
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const content = new Uint8Array([10, 20]);
|
|
85
|
+
await writeGroupFileBytes(
|
|
86
|
+
{} as any,
|
|
87
|
+
"peer:abc:123",
|
|
88
|
+
"sub/bin.dat",
|
|
89
|
+
content,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(getStorageStatus).toHaveBeenCalled();
|
|
93
|
+
expect(writeOpfsPathViaWorker).toHaveBeenCalledWith(
|
|
94
|
+
["shadowclaw", "groups", "peer-abc-123", "sub", "bin.dat"],
|
|
95
|
+
content,
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("retries on stale handle error", async () => {
|
|
100
|
+
const fileHandle: any = {};
|
|
101
|
+
const root: any = {
|
|
102
|
+
getFileHandle: (jest.fn() as any).mockResolvedValue(fileHandle),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
(parsePath as any).mockReturnValue({ dirs: [], filename: "file.txt" });
|
|
106
|
+
(isStaleHandleError as any)
|
|
107
|
+
.mockReturnValueOnce(true)
|
|
108
|
+
.mockReturnValue(false);
|
|
109
|
+
|
|
110
|
+
(getGroupDir as any)
|
|
111
|
+
.mockRejectedValueOnce(new Error("Stale handle"))
|
|
112
|
+
.mockResolvedValueOnce(root);
|
|
113
|
+
|
|
114
|
+
const content = new Uint8Array([5, 6]);
|
|
115
|
+
await writeGroupFileBytes({} as any, "g1", "file.txt", content);
|
|
116
|
+
|
|
117
|
+
expect(invalidateStorageRoot).toHaveBeenCalledTimes(1);
|
|
118
|
+
expect(writeFileHandle).toHaveBeenCalledWith(fileHandle, content);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("throws error when non-OPFS storage backend fails with unsupported stream error", async () => {
|
|
122
|
+
const fileHandle: any = {};
|
|
123
|
+
const root: any = {
|
|
124
|
+
getFileHandle: (jest.fn() as any).mockResolvedValue(fileHandle),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
(getGroupDir as any).mockResolvedValue(root);
|
|
128
|
+
(parsePath as any).mockReturnValue({ dirs: [], filename: "file.txt" });
|
|
129
|
+
(getStorageStatus as any).mockResolvedValueOnce({
|
|
130
|
+
type: "local",
|
|
131
|
+
permission: "granted",
|
|
132
|
+
name: "Folder",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
(writeFileHandle as any).mockRejectedValueOnce(
|
|
136
|
+
new Error("Writable file streams are not supported"),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
await expect(
|
|
140
|
+
writeGroupFileBytes({} as any, "g1", "file.txt", new Uint8Array([1])),
|
|
141
|
+
).rejects.toThrow("Writable file streams are not supported");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -31,4 +31,24 @@ describe("ChatUiStore", () => {
|
|
|
31
31
|
|
|
32
32
|
(URL as any).revokeObjectURL = originalRevoke;
|
|
33
33
|
});
|
|
34
|
+
|
|
35
|
+
it("manages group scroll states and snapshots", () => {
|
|
36
|
+
const store = new ChatUiStore();
|
|
37
|
+
|
|
38
|
+
expect(store.getGroupScrollState("group-1")).toBeNull();
|
|
39
|
+
|
|
40
|
+
store.setGroupScrollState("group-1", 120, false);
|
|
41
|
+
expect(store.getGroupScrollState("group-1")).toEqual({
|
|
42
|
+
distanceFromBottom: 120,
|
|
43
|
+
nearBottom: false,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
store.setNearBottom(false);
|
|
47
|
+
expect(store.nearBottomSnapshot).toBe(false);
|
|
48
|
+
|
|
49
|
+
store.reset();
|
|
50
|
+
expect(store.getGroupScrollState("group-1")).toBeNull();
|
|
51
|
+
expect(store.isNearBottom).toBe(true);
|
|
52
|
+
expect(store.nearBottomSnapshot).toBe(true);
|
|
53
|
+
});
|
|
34
54
|
});
|
|
@@ -2887,5 +2887,139 @@ describe("OrchestratorStore", () => {
|
|
|
2887
2887
|
configElement.remove();
|
|
2888
2888
|
}
|
|
2889
2889
|
});
|
|
2890
|
+
|
|
2891
|
+
it("handles sendMessage with and without a2uiAction", async () => {
|
|
2892
|
+
const store = new OrchestratorStore();
|
|
2893
|
+
const mockSubmit = jest.fn();
|
|
2894
|
+
const orch: any = {
|
|
2895
|
+
browserChat: { submit: mockSubmit },
|
|
2896
|
+
events: createEvents(),
|
|
2897
|
+
};
|
|
2898
|
+
await store.init({} as any, orch);
|
|
2899
|
+
|
|
2900
|
+
store.sendMessage("plain message", []);
|
|
2901
|
+
expect(mockSubmit).toHaveBeenCalledWith(
|
|
2902
|
+
"plain message",
|
|
2903
|
+
DEFAULT_GROUP_ID,
|
|
2904
|
+
[],
|
|
2905
|
+
);
|
|
2906
|
+
|
|
2907
|
+
const action = {
|
|
2908
|
+
actionId: "btn-click",
|
|
2909
|
+
surfaceId: "surf-1",
|
|
2910
|
+
componentId: "btn-1",
|
|
2911
|
+
};
|
|
2912
|
+
store.sendMessage("action message", [], action as any);
|
|
2913
|
+
expect(mockSubmit).toHaveBeenCalledWith(
|
|
2914
|
+
"action message",
|
|
2915
|
+
DEFAULT_GROUP_ID,
|
|
2916
|
+
[],
|
|
2917
|
+
action,
|
|
2918
|
+
);
|
|
2919
|
+
});
|
|
2920
|
+
|
|
2921
|
+
it("manages remote agent status, typing status, and ready state", () => {
|
|
2922
|
+
const store = new OrchestratorStore();
|
|
2923
|
+
expect(store.ready).toBe(false);
|
|
2924
|
+
|
|
2925
|
+
store.setReady(true);
|
|
2926
|
+
expect(store.ready).toBe(true);
|
|
2927
|
+
|
|
2928
|
+
store.setRemoteAgentStatus("peer:agent-1", "running" as any);
|
|
2929
|
+
expect(store._remoteAgentStatusByGroup.get().get("peer:agent-1")).toBe(
|
|
2930
|
+
"running",
|
|
2931
|
+
);
|
|
2932
|
+
|
|
2933
|
+
store.setRemoteAgentTyping("peer:agent-1", true);
|
|
2934
|
+
expect(store._remoteAgentTypingByGroup.get().get("peer:agent-1")).toBe(
|
|
2935
|
+
true,
|
|
2936
|
+
);
|
|
2937
|
+
|
|
2938
|
+
store.setRemoteAgentTyping("peer:agent-1", false);
|
|
2939
|
+
expect(
|
|
2940
|
+
store._remoteAgentTypingByGroup.get().get("peer:agent-1"),
|
|
2941
|
+
).toBeUndefined();
|
|
2942
|
+
});
|
|
2943
|
+
|
|
2944
|
+
it("handles folder navigation and proxy/network settings", async () => {
|
|
2945
|
+
const store = new OrchestratorStore();
|
|
2946
|
+
const mockDb: any = {};
|
|
2947
|
+
const orch: any = {
|
|
2948
|
+
browserChat: { submit: jest.fn() },
|
|
2949
|
+
events: createEvents(),
|
|
2950
|
+
};
|
|
2951
|
+
await store.init(mockDb, orch);
|
|
2952
|
+
|
|
2953
|
+
// 1. Folder navigation
|
|
2954
|
+
await store.navigateIntoFolder(mockDb, "docs");
|
|
2955
|
+
expect(store.currentPath).toBe("docs");
|
|
2956
|
+
|
|
2957
|
+
await store.navigateIntoFolder(mockDb, "sub");
|
|
2958
|
+
expect(store.currentPath).toBe("docs/sub");
|
|
2959
|
+
|
|
2960
|
+
await store.navigateBackFolder(mockDb);
|
|
2961
|
+
expect(store.currentPath).toBe("docs");
|
|
2962
|
+
|
|
2963
|
+
await store.resetToRootFolder(mockDb);
|
|
2964
|
+
expect(store.currentPath).toBe(".");
|
|
2965
|
+
|
|
2966
|
+
// 2. Proxy & network settings
|
|
2967
|
+
await store.setGitProxyUrl(mockDb, "https://git-proxy.example.com");
|
|
2968
|
+
expect(store.gitProxyUrl).toBe("https://git-proxy.example.com");
|
|
2969
|
+
|
|
2970
|
+
await store.setProxyUrl(mockDb, "https://proxy.example.com");
|
|
2971
|
+
expect(store.proxyUrl).toBe("https://proxy.example.com");
|
|
2972
|
+
|
|
2973
|
+
await store.setUseProxy(mockDb, true);
|
|
2974
|
+
expect(store.useProxy).toBe(true);
|
|
2975
|
+
|
|
2976
|
+
await store.setVMBashFullInternetAccess(mockDb, true);
|
|
2977
|
+
expect(store.vmBashFullInternetAccess).toBe(true);
|
|
2978
|
+
});
|
|
2979
|
+
|
|
2980
|
+
it("handles task management helpers (clearAllTasks, restoreTasksFromBackup, toggleTask)", async () => {
|
|
2981
|
+
const store = new OrchestratorStore();
|
|
2982
|
+
const mockDb: any = {};
|
|
2983
|
+
const orch: any = {
|
|
2984
|
+
browserChat: { submit: jest.fn() },
|
|
2985
|
+
events: createEvents(),
|
|
2986
|
+
};
|
|
2987
|
+
await store.init(mockDb, orch);
|
|
2988
|
+
|
|
2989
|
+
const task: any = {
|
|
2990
|
+
id: "task-123",
|
|
2991
|
+
groupId: DEFAULT_GROUP_ID,
|
|
2992
|
+
name: "Test Task",
|
|
2993
|
+
enabled: true,
|
|
2994
|
+
};
|
|
2995
|
+
mockGetAllTasks.mockResolvedValue([task]);
|
|
2996
|
+
await store.loadTasks(mockDb);
|
|
2997
|
+
expect(store.tasks.length).toBe(1);
|
|
2998
|
+
|
|
2999
|
+
// 1. toggleTask
|
|
3000
|
+
mockGetAllTasks.mockResolvedValue([{ ...task, enabled: false }]);
|
|
3001
|
+
await store.toggleTask(mockDb, task, false);
|
|
3002
|
+
expect(store.tasks.find((t) => t.id === "task-123")?.enabled).toBe(false);
|
|
3003
|
+
|
|
3004
|
+
// 2. restoreTasksFromBackup
|
|
3005
|
+
mockGetAllTasks.mockResolvedValue([
|
|
3006
|
+
task,
|
|
3007
|
+
{
|
|
3008
|
+
id: "task-new",
|
|
3009
|
+
groupId: DEFAULT_GROUP_ID,
|
|
3010
|
+
name: "New Task",
|
|
3011
|
+
enabled: true,
|
|
3012
|
+
},
|
|
3013
|
+
]);
|
|
3014
|
+
await store.restoreTasksFromBackup(mockDb, [
|
|
3015
|
+
{ id: "task-new", name: "New Task", enabled: true } as any,
|
|
3016
|
+
]);
|
|
3017
|
+
expect(store.tasks.some((t) => t.id === "task-new")).toBe(true);
|
|
3018
|
+
|
|
3019
|
+
// 3. clearAllTasks
|
|
3020
|
+
mockGetAllTasks.mockResolvedValue([]);
|
|
3021
|
+
await store.clearAllTasks(mockDb);
|
|
3022
|
+
expect(store.tasks).toEqual([]);
|
|
3023
|
+
});
|
|
2890
3024
|
});
|
|
2891
3025
|
});
|