shadow-claw 1.23.3 → 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/assert-version-bump.mjs +60 -38
- package/bin/assert-version-bump.test.mjs +74 -11
- 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 +6 -6
- package/dist/public/{app-routes-B0ePT49v.js → app-routes-Bs-riIvr.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-storage/shadow-claw-storage.html +1 -1
- 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-files/shadow-claw-files.css +1 -1
- package/dist/public/components/shadow-claw-files/shadow-claw-files.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-B7kNU8AQ.js → configurePeerJs-CxpZeDJJ.js} +1 -1
- package/dist/public/{connections-xd_66YVU.js → connections-DvbgdkaB.js} +1 -1
- package/dist/public/{custom-element-security-CZ92anB8.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/architecture/storage.md +5 -2
- package/dist/public/docs/decisions/bundled-typescript-architecture.md +1 -1
- package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +6 -3
- package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
- package/dist/public/docs/example/article/index.html +2 -2
- 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 +19 -76
- package/dist/public/docs/skill-creator/index.html +2 -2
- 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-DkS5xFGX.js → e2e-bridge-IGWsRl4i.js} +1 -1
- package/dist/public/file-viewer-D0eflgt6.js +1 -0
- package/dist/public/files/main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
- package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
- package/dist/public/{git-BkjrsRXX.js → git-7Vq_SJSp.js} +2 -2
- package/dist/public/{iframe-sanitizer-L6Jyu__Z.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-BslPXAXS.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 +2 -2
- package/dist/public/main/memory/index.html +2 -2
- package/dist/public/{markdown-6MBrIo-S.js → markdown-BnpKxSxl.js} +1 -1
- package/dist/public/mcp-reconnect-9emsqGRe.js +1 -0
- package/dist/public/{memoryStorage-CjGseKI1.js → memoryStorage-C0KvLNUp.js} +1 -1
- package/dist/public/{orchestrator-B9KmIj8Q.js → orchestrator-3h9xxHUm.js} +676 -676
- package/dist/public/orchestrator-nJj59RqD.js +15 -0
- package/dist/public/pages/main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/{peerjs-Dv8JNm7v.js → peerjs-BP4laMXj.js} +1 -1
- package/dist/public/{push-client-Dqq06wbB.js → push-client-CrjEXK3U.js} +1 -1
- package/dist/public/service-worker/fetch-proxy.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-Bd1rfuVC.js → shadow-claw-CbwETBWf.js} +2 -2
- package/dist/public/{shadow-claw-accounts-DL2l3oM0.js → shadow-claw-accounts-B4sBTQEj.js} +3 -3
- package/dist/public/{shadow-claw-channels-BGXDsQ38.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
- package/dist/public/{shadow-claw-chat-NXg6zz_l.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-C8eqGpP9.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-Blf6yGVD.js +577 -0
- package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
- package/dist/public/{shadow-claw-git-C0ntz7aX.js → shadow-claw-git-v8S0JjuR.js} +3 -3
- package/dist/public/{shadow-claw-integrations-DCXV2r69.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
- package/dist/public/{shadow-claw-llm-CudgFQsS.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-B8RkpX28.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
- package/dist/public/{shadow-claw-networking-BLiRsrsK.js → shadow-claw-networking-qHbP95z9.js} +1 -1
- package/dist/public/{shadow-claw-notifications-CQhQcjtL.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
- package/dist/public/{shadow-claw-page-header-NeRD0tKU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
- package/dist/public/{shadow-claw-pages-DCHnFh6r.js → shadow-claw-pages-DFZHhzFZ.js} +3 -3
- package/dist/public/{shadow-claw-settings-VAfjKFQg.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
- package/dist/public/shadow-claw-storage-BFynbP0j.js +219 -0
- package/dist/public/{shadow-claw-task-server-Dk33N_1G.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
- package/dist/public/{shadow-claw-tasks-xA1VfmWN.js → shadow-claw-tasks-Caxc9atL.js} +4 -4
- package/dist/public/{shadow-claw-terminal-Du2r0ciq.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
- package/dist/public/{shadow-claw-tools-CZj4rq7l.js → shadow-claw-tools-BfJx3A9W.js} +2 -2
- package/dist/public/{shadow-claw-webvm-JYUkWICG.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
- package/dist/public/static-main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/static-main-manifest.json +1 -1
- package/dist/public/{syncWebMcpRegistration-DfpvASnh.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.html +17 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +120 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.ts +56 -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 +378 -0
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +11 -9
- package/src/components/shadow-claw-files/shadow-claw-files.css +41 -17
- package/src/components/shadow-claw-files/shadow-claw-files.html +35 -0
- package/src/components/shadow-claw-files/shadow-claw-files.test.ts +627 -1
- package/src/components/shadow-claw-files/shadow-claw-files.ts +170 -6
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.test.ts +31 -0
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.ts +2 -1
- package/src/components/shadow-claw-pages/utils/rewriteWorkspacePreviewHtml.test.ts +15 -0
- 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.test.ts +9 -0
- package/src/config/config.ts +15 -0
- package/src/core/app-routes.test.ts +56 -0
- package/src/core/app-routes.ts +8 -1
- 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/db/openDatabase.test.ts +10 -1
- package/src/db/openDatabase.ts +3 -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/migrateLegacyOpfs.test.ts +327 -0
- package/src/storage/migrateLegacyOpfs.ts +117 -0
- 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-AL77QAx4.js +0 -1
- package/dist/public/downloadGroupFile-BABv_1mJ.js +0 -1
- package/dist/public/file-viewer-CCSABMsE.js +0 -1
- package/dist/public/mcp-reconnect-DoicazQd.js +0 -1
- package/dist/public/orchestrator-4su7iyWj.js +0 -15
- package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
- package/dist/public/shadow-claw-file-viewer-9Qkwc30T.js +0 -577
- package/dist/public/shadow-claw-files-aDyq6Hv7.js +0 -781
- package/dist/public/shadow-claw-storage-Cf31np81.js +0 -202
|
@@ -74,21 +74,6 @@ class MockWorker {
|
|
|
74
74
|
terminate() {}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
// Add generic Web APIs for JSDOM from Node globals
|
|
78
|
-
globalThis.Response = global.Response;
|
|
79
|
-
globalThis.Request = global.Request;
|
|
80
|
-
globalThis.Headers = global.Headers;
|
|
81
|
-
(globalThis as any).__PRERENDER_MAIN_MEMORY__ = false;
|
|
82
|
-
|
|
83
|
-
if (typeof (globalThis as any).Worker === "undefined") {
|
|
84
|
-
(globalThis as any).Worker = MockWorker;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (typeof globalThis.structuredClone !== "function") {
|
|
88
|
-
(globalThis as any).structuredClone = <T>(value: T): T =>
|
|
89
|
-
deserialize(serialize(value));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
77
|
// Patch TextEncoder
|
|
93
78
|
if (typeof globalThis.TextEncoder === "undefined") {
|
|
94
79
|
(globalThis as any).TextEncoder = class TextEncoder {
|
|
@@ -101,10 +86,236 @@ if (typeof globalThis.TextEncoder === "undefined") {
|
|
|
101
86
|
// Patch TextDecoder
|
|
102
87
|
if (typeof globalThis.TextDecoder === "undefined") {
|
|
103
88
|
(globalThis as any).TextDecoder = class TextDecoder {
|
|
104
|
-
decode(buf
|
|
105
|
-
|
|
89
|
+
decode(buf?: Uint8Array | ArrayBuffer): string {
|
|
90
|
+
if (!buf) return "";
|
|
91
|
+
if (buf instanceof Uint8Array) {
|
|
92
|
+
return Buffer.from(buf).toString("utf-8");
|
|
93
|
+
}
|
|
94
|
+
return Buffer.from(new Uint8Array(buf)).toString("utf-8");
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Polyfill HTMLDialogElement in JSDOM
|
|
100
|
+
if (typeof globalThis.HTMLDialogElement !== "undefined") {
|
|
101
|
+
if (!globalThis.HTMLDialogElement.prototype.showModal) {
|
|
102
|
+
globalThis.HTMLDialogElement.prototype.showModal = function () {
|
|
103
|
+
this.setAttribute("open", "");
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (!globalThis.HTMLDialogElement.prototype.close) {
|
|
107
|
+
globalThis.HTMLDialogElement.prototype.close = function (
|
|
108
|
+
returnValue?: string,
|
|
109
|
+
) {
|
|
110
|
+
this.removeAttribute("open");
|
|
111
|
+
if (returnValue !== undefined) {
|
|
112
|
+
this.returnValue = returnValue;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
class MockHeaders {
|
|
119
|
+
private map = new Map<string, string>();
|
|
120
|
+
constructor(init?: any) {
|
|
121
|
+
if (init) {
|
|
122
|
+
if (typeof init.forEach === "function") {
|
|
123
|
+
init.forEach((v: string, k: string) =>
|
|
124
|
+
this.map.set(k.toLowerCase(), v),
|
|
125
|
+
);
|
|
126
|
+
} else if (Array.isArray(init)) {
|
|
127
|
+
for (const [k, v] of init) this.map.set(k.toLowerCase(), String(v));
|
|
128
|
+
} else if (typeof init === "object") {
|
|
129
|
+
for (const [k, v] of Object.entries(init)) {
|
|
130
|
+
if (v != null) this.map.set(k.toLowerCase(), String(v));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
get(name: string) {
|
|
136
|
+
return this.map.get(name.toLowerCase()) ?? null;
|
|
137
|
+
}
|
|
138
|
+
set(name: string, value: string) {
|
|
139
|
+
this.map.set(name.toLowerCase(), String(value));
|
|
140
|
+
}
|
|
141
|
+
has(name: string) {
|
|
142
|
+
return this.map.has(name.toLowerCase());
|
|
143
|
+
}
|
|
144
|
+
delete(name: string) {
|
|
145
|
+
this.map.delete(name.toLowerCase());
|
|
146
|
+
}
|
|
147
|
+
forEach(callback: (value: string, key: string) => void) {
|
|
148
|
+
this.map.forEach((v, k) => callback(v, k));
|
|
149
|
+
}
|
|
150
|
+
entries() {
|
|
151
|
+
return this.map.entries();
|
|
152
|
+
}
|
|
153
|
+
keys() {
|
|
154
|
+
return this.map.keys();
|
|
155
|
+
}
|
|
156
|
+
values() {
|
|
157
|
+
return this.map.values();
|
|
158
|
+
}
|
|
159
|
+
[Symbol.iterator]() {
|
|
160
|
+
return this.map.entries();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
class MockBlob {
|
|
165
|
+
private parts: any[];
|
|
166
|
+
public size: number;
|
|
167
|
+
public type: string;
|
|
168
|
+
constructor(parts?: any[], options?: { type?: string }) {
|
|
169
|
+
this.parts = parts || [];
|
|
170
|
+
this.type = options?.type || "";
|
|
171
|
+
this.size = this.parts.reduce(
|
|
172
|
+
(acc, p) =>
|
|
173
|
+
acc +
|
|
174
|
+
(p?.byteLength ||
|
|
175
|
+
p?.length ||
|
|
176
|
+
(typeof p === "string" ? Buffer.byteLength(p) : 0)),
|
|
177
|
+
0,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
async arrayBuffer(): Promise<ArrayBuffer> {
|
|
181
|
+
const combined = new Uint8Array(this.size);
|
|
182
|
+
let offset = 0;
|
|
183
|
+
for (const p of this.parts) {
|
|
184
|
+
if (p instanceof Uint8Array) {
|
|
185
|
+
combined.set(p, offset);
|
|
186
|
+
offset += p.byteLength;
|
|
187
|
+
} else if (p instanceof ArrayBuffer) {
|
|
188
|
+
combined.set(new Uint8Array(p), offset);
|
|
189
|
+
offset += p.byteLength;
|
|
190
|
+
} else if (typeof p === "string") {
|
|
191
|
+
const encoded = new (globalThis as any).TextEncoder().encode(p);
|
|
192
|
+
combined.set(encoded, offset);
|
|
193
|
+
offset += encoded.byteLength;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return combined.buffer;
|
|
197
|
+
}
|
|
198
|
+
async text(): Promise<string> {
|
|
199
|
+
const ab = await this.arrayBuffer();
|
|
200
|
+
return new (globalThis as any).TextDecoder().decode(ab);
|
|
201
|
+
}
|
|
202
|
+
stream() {
|
|
203
|
+
let resolved = false;
|
|
204
|
+
let bytes: Uint8Array | null = null;
|
|
205
|
+
const blobPromise = this.arrayBuffer().then((ab) => {
|
|
206
|
+
bytes = new Uint8Array(ab);
|
|
207
|
+
resolved = true;
|
|
208
|
+
});
|
|
209
|
+
return new ReadableStream<Uint8Array>({
|
|
210
|
+
async start(controller) {
|
|
211
|
+
if (!resolved) await blobPromise;
|
|
212
|
+
if (bytes && bytes.byteLength > 0) controller.enqueue(bytes);
|
|
213
|
+
controller.close();
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
class MockResponse {
|
|
220
|
+
body: any;
|
|
221
|
+
headers: any;
|
|
222
|
+
status: number;
|
|
223
|
+
statusText: string;
|
|
224
|
+
ok: boolean;
|
|
225
|
+
constructor(body?: any, init?: any) {
|
|
226
|
+
this.body = body;
|
|
227
|
+
this.status = init?.status ?? 200;
|
|
228
|
+
this.statusText = init?.statusText ?? (this.status === 200 ? "OK" : "");
|
|
229
|
+
this.ok = this.status >= 200 && this.status < 300;
|
|
230
|
+
const HeadersClass = (globalThis as any).Headers || MockHeaders;
|
|
231
|
+
this.headers =
|
|
232
|
+
init?.headers instanceof HeadersClass
|
|
233
|
+
? init.headers
|
|
234
|
+
: new HeadersClass(init?.headers);
|
|
235
|
+
}
|
|
236
|
+
async json() {
|
|
237
|
+
if (typeof this.body === "string") return JSON.parse(this.body);
|
|
238
|
+
if (this.body instanceof Uint8Array) {
|
|
239
|
+
return JSON.parse(Buffer.from(this.body).toString("utf-8"));
|
|
240
|
+
}
|
|
241
|
+
return this.body;
|
|
242
|
+
}
|
|
243
|
+
async text() {
|
|
244
|
+
if (typeof this.body === "string") return this.body;
|
|
245
|
+
if (this.body instanceof Uint8Array) {
|
|
246
|
+
return Buffer.from(this.body).toString("utf-8");
|
|
247
|
+
}
|
|
248
|
+
if (this.body && typeof this.body.text === "function") {
|
|
249
|
+
return this.body.text();
|
|
250
|
+
}
|
|
251
|
+
return String(this.body ?? "");
|
|
252
|
+
}
|
|
253
|
+
async arrayBuffer() {
|
|
254
|
+
if (this.body instanceof Uint8Array) {
|
|
255
|
+
return this.body.buffer.slice(
|
|
256
|
+
this.body.byteOffset,
|
|
257
|
+
this.body.byteOffset + this.body.byteLength,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (this.body instanceof ArrayBuffer) return this.body;
|
|
261
|
+
if (this.body && typeof this.body.arrayBuffer === "function") {
|
|
262
|
+
return this.body.arrayBuffer();
|
|
263
|
+
}
|
|
264
|
+
if (typeof this.body === "string") {
|
|
265
|
+
return new (globalThis as any).TextEncoder().encode(this.body).buffer;
|
|
266
|
+
}
|
|
267
|
+
return new ArrayBuffer(0);
|
|
268
|
+
}
|
|
269
|
+
async blob() {
|
|
270
|
+
if (this.body instanceof (globalThis as any).Blob) return this.body;
|
|
271
|
+
const ab = await this.arrayBuffer();
|
|
272
|
+
const BlobClass = (globalThis as any).Blob || MockBlob;
|
|
273
|
+
return new BlobClass([ab]);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (typeof (globalThis as any).Headers === "undefined") {
|
|
278
|
+
(globalThis as any).Headers = MockHeaders;
|
|
279
|
+
(global as any).Headers = MockHeaders;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (typeof (globalThis as any).Blob === "undefined") {
|
|
283
|
+
(globalThis as any).Blob = MockBlob;
|
|
284
|
+
(global as any).Blob = MockBlob;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (typeof (globalThis as any).Response === "undefined") {
|
|
288
|
+
(globalThis as any).Response = MockResponse;
|
|
289
|
+
(global as any).Response = MockResponse;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (typeof (globalThis as any).Request === "undefined") {
|
|
293
|
+
(globalThis as any).Request = class MockRequest {
|
|
294
|
+
url: string;
|
|
295
|
+
method: string;
|
|
296
|
+
headers: any;
|
|
297
|
+
constructor(input: any, init?: any) {
|
|
298
|
+
this.url = typeof input === "string" ? input : input?.url || "";
|
|
299
|
+
this.method = init?.method || "GET";
|
|
300
|
+
const HeadersClass = (globalThis as any).Headers || MockHeaders;
|
|
301
|
+
this.headers =
|
|
302
|
+
init?.headers instanceof HeadersClass
|
|
303
|
+
? init.headers
|
|
304
|
+
: new HeadersClass(init?.headers);
|
|
106
305
|
}
|
|
107
306
|
};
|
|
307
|
+
(global as any).Request = (globalThis as any).Request;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
(globalThis as any).__PRERENDER_MAIN_MEMORY__ = false;
|
|
311
|
+
|
|
312
|
+
if (typeof (globalThis as any).Worker === "undefined") {
|
|
313
|
+
(globalThis as any).Worker = MockWorker;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (typeof globalThis.structuredClone !== "function") {
|
|
317
|
+
(globalThis as any).structuredClone = <T>(value: T): T =>
|
|
318
|
+
deserialize(serialize(value));
|
|
108
319
|
}
|
|
109
320
|
|
|
110
321
|
// Implement a filesystem-aware fetch for component templates/styles
|
|
@@ -117,7 +328,7 @@ globalThis.fetch = jest.fn((url: string | URL | Request) => {
|
|
|
117
328
|
statusText: "OK",
|
|
118
329
|
text: () => Promise.resolve(""),
|
|
119
330
|
json: () => Promise.resolve({}),
|
|
120
|
-
blob: () => Promise.resolve(new Blob()),
|
|
331
|
+
blob: () => Promise.resolve(new (globalThis as any).Blob()),
|
|
121
332
|
arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
|
|
122
333
|
} as Response);
|
|
123
334
|
}
|
|
@@ -163,7 +374,7 @@ globalThis.fetch = jest.fn((url: string | URL | Request) => {
|
|
|
163
374
|
ok: true,
|
|
164
375
|
text: () => Promise.resolve(content),
|
|
165
376
|
json: () => Promise.resolve(JSON.parse(content)),
|
|
166
|
-
blob: () => Promise.resolve(new Blob([content])),
|
|
377
|
+
blob: () => Promise.resolve(new (globalThis as any).Blob([content])),
|
|
167
378
|
arrayBuffer: () => Promise.resolve(Buffer.from(content).buffer),
|
|
168
379
|
} as Response);
|
|
169
380
|
} catch (err) {
|
|
@@ -178,6 +389,66 @@ globalThis.fetch = jest.fn((url: string | URL | Request) => {
|
|
|
178
389
|
(globalThis as any).WritableStream = WritableStream;
|
|
179
390
|
(globalThis as any).AudioContext = MockAudioContext;
|
|
180
391
|
|
|
392
|
+
// Polyfill ResizeObserver for JSDOM
|
|
393
|
+
if (typeof globalThis.ResizeObserver === "undefined") {
|
|
394
|
+
(globalThis as any).ResizeObserver = class MockResizeObserver {
|
|
395
|
+
callback: ResizeObserverCallback;
|
|
396
|
+
constructor(callback: ResizeObserverCallback) {
|
|
397
|
+
this.callback = callback;
|
|
398
|
+
}
|
|
399
|
+
observe() {}
|
|
400
|
+
unobserve() {}
|
|
401
|
+
disconnect() {}
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const BaseMouseEvent: any =
|
|
406
|
+
typeof globalThis.MouseEvent !== "undefined"
|
|
407
|
+
? globalThis.MouseEvent
|
|
408
|
+
: typeof globalThis.Event !== "undefined"
|
|
409
|
+
? globalThis.Event
|
|
410
|
+
: class MockEvent {
|
|
411
|
+
type: string;
|
|
412
|
+
bubbles: boolean;
|
|
413
|
+
cancelable: boolean;
|
|
414
|
+
defaultPrevented = false;
|
|
415
|
+
constructor(type: string, init?: any) {
|
|
416
|
+
this.type = type;
|
|
417
|
+
this.bubbles = init?.bubbles ?? false;
|
|
418
|
+
this.cancelable = init?.cancelable ?? false;
|
|
419
|
+
}
|
|
420
|
+
preventDefault() {
|
|
421
|
+
this.defaultPrevented = true;
|
|
422
|
+
}
|
|
423
|
+
stopPropagation() {}
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
if (typeof globalThis.PointerEvent === "undefined") {
|
|
427
|
+
(globalThis as any).PointerEvent = class PointerEvent extends BaseMouseEvent {
|
|
428
|
+
pointerId: number;
|
|
429
|
+
pointerType: string;
|
|
430
|
+
clientX: number;
|
|
431
|
+
clientY: number;
|
|
432
|
+
constructor(type: string, init?: any) {
|
|
433
|
+
super(type, init);
|
|
434
|
+
this.pointerId = init?.pointerId ?? 0;
|
|
435
|
+
this.pointerType = init?.pointerType ?? "mouse";
|
|
436
|
+
this.clientX = init?.clientX ?? 0;
|
|
437
|
+
this.clientY = init?.clientY ?? 0;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (typeof globalThis.DragEvent === "undefined") {
|
|
443
|
+
(globalThis as any).DragEvent = class DragEvent extends BaseMouseEvent {
|
|
444
|
+
dataTransfer: any;
|
|
445
|
+
constructor(type: string, init?: any) {
|
|
446
|
+
super(type, init);
|
|
447
|
+
this.dataTransfer = init?.dataTransfer ?? null;
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
181
452
|
// Polyfill CSSStyleSheet and adoptedStyleSheets for JSDOM
|
|
182
453
|
if (typeof globalThis.CSSStyleSheet === "undefined") {
|
|
183
454
|
(globalThis as any).CSSStyleSheet = class MockCSSStyleSheet {
|
|
@@ -201,24 +472,26 @@ if (typeof globalThis.CSSStyleSheet === "undefined") {
|
|
|
201
472
|
}
|
|
202
473
|
}
|
|
203
474
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
475
|
+
if (typeof ShadowRoot !== "undefined") {
|
|
476
|
+
Object.defineProperty(ShadowRoot.prototype, "adoptedStyleSheets", {
|
|
477
|
+
configurable: true,
|
|
478
|
+
set(sheets: any[]) {
|
|
479
|
+
(this as any)._adoptedStyleSheets = sheets;
|
|
480
|
+
if (Array.isArray(sheets)) {
|
|
481
|
+
sheets.forEach((sheet) => {
|
|
482
|
+
if (sheet._css) {
|
|
483
|
+
const style = document.createElement("style");
|
|
484
|
+
style.textContent = sheet._css;
|
|
485
|
+
this.appendChild(style);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
get() {
|
|
491
|
+
return (this as any)._adoptedStyleSheets || [];
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
}
|
|
222
495
|
|
|
223
496
|
const originalConsoleError = console.error;
|
|
224
497
|
const originalConsoleWarn = console.warn;
|
|
@@ -243,11 +516,14 @@ const expectedLogs = [
|
|
|
243
516
|
"PeerJsChannel: connected to signaling server",
|
|
244
517
|
"PeerJsChannel: connection opened with",
|
|
245
518
|
"PeerJsChannel: rejecting connection from untrusted peer:",
|
|
519
|
+
"PeerJsChannel: connection closed with",
|
|
246
520
|
"[WebVM] Ignoring invalid VM boot host:",
|
|
247
521
|
"[WebVM] Ignoring invalid VM relay URL:",
|
|
248
|
-
"[WebVM
|
|
249
|
-
"[WebVM
|
|
250
|
-
"[WebVM
|
|
522
|
+
"[WebVM]",
|
|
523
|
+
"[WebVM:ui]",
|
|
524
|
+
"[WebVM boot]",
|
|
525
|
+
"[shadow-claw-a2ui] Failed to load workspace",
|
|
526
|
+
"[shadow-claw-a2ui] groupId not set, cannot resolve workspace files",
|
|
251
527
|
"Failed to fetch Vertex AI models dynamically:",
|
|
252
528
|
"Vertex AI proxy error:",
|
|
253
529
|
"Error: Not implemented: navigation (except hash changes)",
|
|
@@ -276,6 +552,18 @@ const expectedLogs = [
|
|
|
276
552
|
"[Security] Registration blocked:",
|
|
277
553
|
"[Security] Removing unapproved custom element:",
|
|
278
554
|
"Failed to retrieve local storage handle from DB:",
|
|
555
|
+
"fetch proxy: config updated",
|
|
556
|
+
"Error reaching huggingface.co:",
|
|
557
|
+
"Error reaching hf-mirror.com:",
|
|
558
|
+
"Hugging Face main domain unreachable",
|
|
559
|
+
"writePartialMeta error:",
|
|
560
|
+
"flushChunkToCache error:",
|
|
561
|
+
"[Proxy] Llamafile runtime:",
|
|
562
|
+
"[Proxy] Mesh LLM request for model:",
|
|
563
|
+
"[Proxy] Fetching Mesh LLM catalog...",
|
|
564
|
+
"Ollama invoke error:",
|
|
565
|
+
"Ollama models discovery error:",
|
|
566
|
+
"Failed to check Prompt API onboarding:",
|
|
279
567
|
];
|
|
280
568
|
|
|
281
569
|
function isExpectedLog(...args: any[]) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export class V86 {
|
|
2
|
+
constructor(options) {
|
|
3
|
+
this.options = options;
|
|
4
|
+
this.listeners = new Map();
|
|
5
|
+
this.serial0_send = (text) => {
|
|
6
|
+
if (text === "root\n") {
|
|
7
|
+
setTimeout(() => {
|
|
8
|
+
this.emitSerial("# ");
|
|
9
|
+
}, 10);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
this.destroy = () => Promise.resolve();
|
|
13
|
+
this.fs9p = {
|
|
14
|
+
read_dir: () => [],
|
|
15
|
+
SearchPath: () => ({ id: 1 }),
|
|
16
|
+
IsDirectory: () => true,
|
|
17
|
+
};
|
|
18
|
+
this.create_file = () => Promise.resolve();
|
|
19
|
+
this.read_file = () => Promise.resolve(new Uint8Array([1, 2, 3]));
|
|
20
|
+
|
|
21
|
+
// Auto-emit boot sequence
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
this.emitSerial("Booting Linux...\nlogin: ");
|
|
24
|
+
}, 10);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
add_listener(event, listener) {
|
|
28
|
+
if (!this.listeners.has(event)) {
|
|
29
|
+
this.listeners.set(event, new Set());
|
|
30
|
+
}
|
|
31
|
+
this.listeners.get(event).add(listener);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
remove_listener(event, listener) {
|
|
35
|
+
this.listeners.get(event)?.delete(listener);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
emitSerial(text) {
|
|
39
|
+
const serialListeners = this.listeners.get("serial0-output-byte");
|
|
40
|
+
if (!serialListeners) return;
|
|
41
|
+
for (const ch of text) {
|
|
42
|
+
for (const listener of serialListeners) {
|
|
43
|
+
listener(ch.charCodeAt(0));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default V86;
|
package/src/ui/a2ui/types.ts
CHANGED
|
@@ -423,7 +423,7 @@ export interface ColumnSpec extends ComponentCommon {
|
|
|
423
423
|
align?: AlignValue;
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
-
export type ButtonVariant = "default" | "primary" | "borderless";
|
|
426
|
+
export type ButtonVariant = "default" | "primary" | "secondary" | "borderless";
|
|
427
427
|
|
|
428
428
|
/**
|
|
429
429
|
* A2UI v1.0 spec §actions: server action event sent when a button is clicked.
|
|
@@ -571,6 +571,7 @@ export interface CheckBoxSpec extends ComponentCommon, CheckableComponent {
|
|
|
571
571
|
component: "CheckBox";
|
|
572
572
|
label: DynamicString;
|
|
573
573
|
value: DynamicBoolean;
|
|
574
|
+
action?: A2UIActionDescriptor;
|
|
574
575
|
}
|
|
575
576
|
|
|
576
577
|
export interface ChoicePickerOption {
|
|
@@ -592,14 +593,14 @@ export interface SliderSpec extends ComponentCommon, CheckableComponent {
|
|
|
592
593
|
component: "Slider";
|
|
593
594
|
label?: DynamicString;
|
|
594
595
|
min?: number;
|
|
595
|
-
max
|
|
596
|
+
max?: number;
|
|
596
597
|
value: DynamicNumber;
|
|
597
598
|
steps?: number;
|
|
598
599
|
}
|
|
599
600
|
|
|
600
601
|
export interface DateTimeInputSpec extends ComponentCommon, CheckableComponent {
|
|
601
602
|
component: "DateTimeInput";
|
|
602
|
-
value
|
|
603
|
+
value?: DynamicString;
|
|
603
604
|
enableDate?: boolean;
|
|
604
605
|
enableTime?: boolean;
|
|
605
606
|
min?: DynamicString;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
import { normaliseComponentsToMap } from "./normaliseComponentsToMap.js";
|
|
3
|
+
|
|
4
|
+
describe("normaliseComponentsToMap", () => {
|
|
5
|
+
it("normalises a flat list of component specs", () => {
|
|
6
|
+
const specs = [
|
|
7
|
+
{ id: "root", component: "Column", children: ["c1", "c2"] },
|
|
8
|
+
{ id: "c1", component: "Text", text: "Hello" },
|
|
9
|
+
{ id: "c2", component: "Button", text: "Submit" },
|
|
10
|
+
] as any;
|
|
11
|
+
|
|
12
|
+
const map = normaliseComponentsToMap(specs);
|
|
13
|
+
expect(map["root"]).toEqual(specs[0]);
|
|
14
|
+
expect(map["c1"]).toEqual(specs[1]);
|
|
15
|
+
expect(map["c2"]).toEqual(specs[2]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("walks nested children array objects", () => {
|
|
19
|
+
const specs = [
|
|
20
|
+
{
|
|
21
|
+
id: "root",
|
|
22
|
+
component: "Column",
|
|
23
|
+
children: [
|
|
24
|
+
{ id: "nested1", component: "Text", text: "Nested 1" },
|
|
25
|
+
{ id: "nested2", component: "Text", text: "Nested 2" },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
] as any;
|
|
29
|
+
|
|
30
|
+
const map = normaliseComponentsToMap(specs);
|
|
31
|
+
expect(map["root"]).toBeDefined();
|
|
32
|
+
expect(map["nested1"]).toEqual({
|
|
33
|
+
id: "nested1",
|
|
34
|
+
component: "Text",
|
|
35
|
+
text: "Nested 1",
|
|
36
|
+
});
|
|
37
|
+
expect(map["nested2"]).toEqual({
|
|
38
|
+
id: "nested2",
|
|
39
|
+
component: "Text",
|
|
40
|
+
text: "Nested 2",
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("does not walk children if children[0] is a template specification", () => {
|
|
45
|
+
const specs = [
|
|
46
|
+
{
|
|
47
|
+
id: "root",
|
|
48
|
+
component: "Column",
|
|
49
|
+
children: [{ path: "/items", componentId: "tmpl" }],
|
|
50
|
+
},
|
|
51
|
+
] as any;
|
|
52
|
+
|
|
53
|
+
const map = normaliseComponentsToMap(specs);
|
|
54
|
+
expect(map["root"]).toBeDefined();
|
|
55
|
+
expect(Object.keys(map)).toEqual(["root"]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("walks nested child object and nested tabs", () => {
|
|
59
|
+
const specs = [
|
|
60
|
+
{
|
|
61
|
+
id: "card1",
|
|
62
|
+
component: "Card",
|
|
63
|
+
child: { id: "cardInner", component: "Text", text: "Inside Card" },
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "tabs1",
|
|
67
|
+
component: "Tabs",
|
|
68
|
+
tabs: [
|
|
69
|
+
{ title: "Tab 1", child: { id: "tabContent1", component: "Text" } },
|
|
70
|
+
{ title: "Tab 2", child: { id: "tabContent2", component: "Text" } },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
] as any;
|
|
74
|
+
|
|
75
|
+
const map = normaliseComponentsToMap(specs);
|
|
76
|
+
expect(map["card1"]).toBeDefined();
|
|
77
|
+
expect(map["cardInner"]).toBeDefined();
|
|
78
|
+
expect(map["tabs1"]).toBeDefined();
|
|
79
|
+
expect(map["tabContent1"]).toBeDefined();
|
|
80
|
+
expect(map["tabContent2"]).toBeDefined();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("warns and skips components missing id field", () => {
|
|
84
|
+
const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
85
|
+
const specs = [
|
|
86
|
+
null,
|
|
87
|
+
"invalid-primitive" as any,
|
|
88
|
+
{ component: "Text", text: "No ID" },
|
|
89
|
+
{ customField: 123 },
|
|
90
|
+
] as any;
|
|
91
|
+
|
|
92
|
+
const map = normaliseComponentsToMap(specs);
|
|
93
|
+
expect(map).toEqual({});
|
|
94
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
95
|
+
expect.stringContaining(
|
|
96
|
+
"Component missing required 'id' field; skipped.",
|
|
97
|
+
),
|
|
98
|
+
expect.anything(),
|
|
99
|
+
);
|
|
100
|
+
warnSpy.mockRestore();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
import { resolveChildIds } from "./resolveChildIds.js";
|
|
3
|
+
|
|
4
|
+
describe("resolveChildIds", () => {
|
|
5
|
+
it("returns fixed string array as-is", () => {
|
|
6
|
+
const children = ["child1", "child2", "child3"];
|
|
7
|
+
expect(resolveChildIds(children, {})).toEqual([
|
|
8
|
+
"child1",
|
|
9
|
+
"child2",
|
|
10
|
+
"child3",
|
|
11
|
+
]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("handles template specification object and generates dynamic IDs", () => {
|
|
15
|
+
const template = { path: "/items", componentId: "item" };
|
|
16
|
+
const dataModel = { items: ["apple", "banana", "cherry"] };
|
|
17
|
+
expect(resolveChildIds(template, dataModel)).toEqual([
|
|
18
|
+
"item_0",
|
|
19
|
+
"item_1",
|
|
20
|
+
"item_2",
|
|
21
|
+
]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("handles array containing a single template object", () => {
|
|
25
|
+
const children = [{ path: "/users", componentId: "user_card" }] as any;
|
|
26
|
+
const dataModel = { users: [{ name: "Alice" }, { name: "Bob" }] };
|
|
27
|
+
expect(resolveChildIds(children, dataModel)).toEqual([
|
|
28
|
+
"user_card_0",
|
|
29
|
+
"user_card_1",
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("handles array containing nested component objects with IDs", () => {
|
|
34
|
+
const children = [
|
|
35
|
+
{ id: "comp1", component: "Text" },
|
|
36
|
+
{ id: "comp2", component: "Button" },
|
|
37
|
+
] as any;
|
|
38
|
+
expect(resolveChildIds(children, {})).toEqual(["comp1", "comp2"]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("returns empty array for nested component objects without IDs", () => {
|
|
42
|
+
const children = [{ component: "Text" }] as any;
|
|
43
|
+
expect(resolveChildIds(children, {})).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("warns and returns empty array if template path does not resolve to an array", () => {
|
|
47
|
+
const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
48
|
+
const template = { path: "/notAnArray", componentId: "item" };
|
|
49
|
+
const dataModel = { notAnArray: "string value" };
|
|
50
|
+
|
|
51
|
+
expect(resolveChildIds(template, dataModel)).toEqual([]);
|
|
52
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
53
|
+
expect.stringContaining(
|
|
54
|
+
'ChildList template path "/notAnArray" did not resolve to an array.',
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
warnSpy.mockRestore();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("handles empty array gracefully", () => {
|
|
61
|
+
expect(resolveChildIds([], {})).toEqual([]);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -9,7 +9,19 @@ describe("resolveDynamicNumber", () => {
|
|
|
9
9
|
expect(resolveDynamicNumber({ path: "/n" }, { n: 7 })).toBe(7);
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
-
it("
|
|
12
|
+
it("resolves a $dataModel reference", () => {
|
|
13
|
+
expect(resolveDynamicNumber({ $dataModel: "/count" }, { count: 99 })).toBe(
|
|
14
|
+
99,
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("returns 0 for null or undefined", () => {
|
|
13
19
|
expect(resolveDynamicNumber(null, {})).toBe(0);
|
|
20
|
+
expect(resolveDynamicNumber(undefined, {})).toBe(0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("returns 0 for invalid objects without path or $dataModel", () => {
|
|
24
|
+
expect(resolveDynamicNumber({} as any, {})).toBe(0);
|
|
25
|
+
expect(resolveDynamicNumber("not-a-number" as any, {})).toBe(0);
|
|
14
26
|
});
|
|
15
27
|
});
|