From 1464adb1d935fd02ae6189bb35f2ab9270a8563c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 25 Sep 2026 04:36:28 -0700 Subject: [PATCH 1/2] chore(tests): remove low-signal tests and consolidate the testing setup --- .agents/skills/cleanup/SKILL.md | 13 +- .agents/skills/ship/SKILL.md | 3 +- .agents/skills/test-audit/SKILL.md | 147 + .claude/rules/sim-testing.md | 353 +- .claude/skills/test-audit | 1 + .cursor/rules/sim-testing.mdc | 349 +- .github/workflows/publish-sim-cli.yml | 9 +- .github/workflows/publish-sim-setup.yml | 9 +- .github/workflows/test-build.yml | 275 +- .gitignore | 3 + CLAUDE.md | 50 +- apps/desktop/package.json | 2 +- .../src/main/account-data-generation.test.ts | 89 +- apps/desktop/src/main/app-routes.test.ts | 19 - .../desktop/src/main/atomic-json-file.test.ts | 20 +- .../src/main/browser-agent/cdp.test.ts | 674 ---- .../main/browser-agent/context-menu.test.ts | 192 - .../main/browser-agent/driver-profile.test.ts | 6 +- .../src/main/browser-agent/driver.test.ts | 2335 +------------ .../main/browser-agent/file-transfer.test.ts | 106 +- .../src/main/browser-agent/keyboard.test.ts | 166 - .../main/browser-agent/page-functions.test.ts | 1449 +------- .../src/main/browser-agent/panel.test.ts | 245 -- .../post-action-observation.test.ts | 55 - .../request-policy-network.test.ts | 1 - .../main/browser-agent/request-policy.test.ts | 1 - .../src/main/browser-agent/session.test.ts | 2714 +-------------- .../src/main/browser-agent/url-guard.test.ts | 75 - .../src/main/browser-credentials/fill.test.ts | 308 -- .../main/browser-credentials/os-auth.test.ts | 96 - .../main/browser-credentials/vault.test.ts | 142 +- .../browser-import/chromium-cookies.test.ts | 16 - .../browser-import/chromium-crypto.test.ts | 8 - .../browser-import/chromium-favicons.test.ts | 36 - .../browser-import/chromium-passwords.test.ts | 51 - .../browser-import/chromium-profiles.test.ts | 129 - .../chromium-site-names.test.ts | 166 - .../browser-import/cookie-translate.test.ts | 42 +- .../browser-import/import-service.test.ts | 704 +--- .../main/browser-search/suggestions.test.ts | 3 - .../src/main/browser-sites/directory.test.ts | 192 +- .../desktop/src/main/channel-identity.test.ts | 43 +- apps/desktop/src/main/client-info.test.ts | 30 - apps/desktop/src/main/config.test.ts | 213 +- apps/desktop/src/main/context-menu.test.ts | 53 - apps/desktop/src/main/csp.test.ts | 22 - .../main/desktop-chat-session-store.test.ts | 197 +- .../desktop/src/main/desktop-settings.test.ts | 284 -- apps/desktop/src/main/dialogs.test.ts | 62 +- apps/desktop/src/main/downloads.test.ts | 91 +- apps/desktop/src/main/handoff.test.ts | 208 +- apps/desktop/src/main/help-search.test.ts | 60 - apps/desktop/src/main/input-activity.test.ts | 48 - apps/desktop/src/main/ipc.test.ts | 1046 +----- apps/desktop/src/main/load-health.test.ts | 60 - apps/desktop/src/main/local-files.test.ts | 158 - .../main/local-filesystem-grant-store.test.ts | 61 +- .../desktop/src/main/local-filesystem.test.ts | 400 --- apps/desktop/src/main/local-pages.test.ts | 104 +- apps/desktop/src/main/menu.test.ts | 315 +- apps/desktop/src/main/navigation.test.ts | 154 - apps/desktop/src/main/observability.test.ts | 85 +- .../src/main/resource-shortcuts.test.ts | 17 - .../src/main/scoped-event-router.test.ts | 38 - apps/desktop/src/main/security-guards.test.ts | 20 - apps/desktop/src/main/server-window.test.ts | 149 - .../src/main/session-lifecycle.test.ts | 192 - apps/desktop/src/main/shell-theme.test.ts | 21 +- .../desktop/src/main/telemetry-policy.test.ts | 41 - apps/desktop/src/main/terminal-themes.test.ts | 69 - .../src/main/terminal/pending-runs.test.ts | 3 - .../src/main/terminal/process-cwd.test.ts | 25 - .../src/main/terminal/registry.test.ts | 279 -- .../src/main/terminal/selection.test.ts | 88 - .../desktop/src/main/terminal/service.test.ts | 338 -- .../desktop/src/main/terminal/session.test.ts | 61 - apps/desktop/src/main/terminal/tmux.test.ts | 80 +- apps/desktop/src/main/tray.test.ts | 261 +- apps/desktop/src/main/updater.test.ts | 649 +--- apps/desktop/src/main/user-agent.test.ts | 15 - apps/desktop/src/main/window.test.ts | 331 +- apps/desktop/src/main/windows.test.ts | 74 +- .../desktop/src/preload/browser/forms.test.ts | 92 +- apps/desktop/src/preload/index.test.ts | 68 - apps/desktop/src/renderer/shell.test.ts | 30 - apps/desktop/vitest.config.ts | 1 - .../docs-layout/docs-sidebar.test.tsx | 119 - .../docs-layout/sidebar-components.test.tsx | 221 -- .../components/ui/video-placeholder.test.tsx | 92 - .../workflow-preview/docs-block-tile.test.tsx | 42 - .../fit-view-after-init.test.tsx | 84 - .../workflow-preview/workflow-data.test.ts | 145 - .../workflow-preview.test.tsx | 244 -- apps/docs/lib/academy-captions.test.ts | 1 - apps/docs/lib/integration-navigation.test.ts | 69 +- apps/docs/lib/openapi-download.test.ts | 42 +- apps/docs/lib/shiki-theme.test.ts | 29 - apps/docs/package.json | 2 +- apps/realtime/package.json | 2 +- apps/realtime/src/access-revalidation.test.ts | 225 +- apps/realtime/src/database/operations.test.ts | 3 - apps/realtime/src/database/preflight.test.ts | 33 +- apps/realtime/src/handlers/connection.test.ts | 3 - .../src/handlers/file-doc-store.test.ts | 817 ----- .../handlers/file-doc.join-readiness.test.ts | 20 - .../handlers/file-doc.multireplica.test.ts | 3 - apps/realtime/src/handlers/file-doc.test.ts | 1191 +------ apps/realtime/src/handlers/operations.test.ts | 28 +- apps/realtime/src/handlers/presence.test.ts | 67 - apps/realtime/src/handlers/tables.test.ts | 259 -- apps/realtime/src/handlers/workflow.test.ts | 201 -- .../workspace-invalidation-room.test.ts | 109 +- apps/realtime/src/index.test.ts | 442 --- .../src/middleware/permissions.test.ts | 402 +-- .../realtime/src/rooms/memory-manager.test.ts | 72 - apps/realtime/src/routes/http.test.ts | 35 - apps/realtime/src/tests/socket-server.test.ts | 268 -- apps/realtime/vitest.config.ts | 1 - apps/sim/AGENTS.md | 4 +- apps/sim/app/(auth)/auth-redirect.test.ts | 79 +- .../components/auth-loading-skeleton.test.tsx | 54 - .../app/(auth)/components/auth-shell.test.tsx | 23 - .../oauth/consent/consent-view.test.tsx | 31 - .../app/(auth)/oauth/sign-in/route.test.ts | 72 - .../app/(auth)/signup/signup-form.test.tsx | 132 - .../sso/launch/[providerId]/route.test.ts | 38 - apps/sim/app/(auth)/verify/page.test.tsx | 53 - .../(auth)/verify/use-verification.test.tsx | 27 - .../message/components/file-download.test.tsx | 153 - .../chat/components/message/message.test.tsx | 194 +- .../chat/hooks/use-chat-streaming.test.tsx | 452 +-- .../[workflowId]/[executionId]/page.test.tsx | 12 - .../[executionId]/resume-page-client.test.tsx | 50 - .../comparisons/[provider]/page.test.tsx | 162 - .../agent-momentum/agent-momentum.test.tsx | 37 - .../app/(landing)/components/cta/cta.test.tsx | 50 - .../featured-customer.test.tsx | 545 --- .../features-rail/features-rail.test.tsx | 282 -- .../components/features/features.test.tsx | 31 - .../footer-wordmark-loop.test.tsx | 183 - .../hero-announcement-chip.test.tsx | 52 - .../hero-chat-loop/hero-chat-loop.test.tsx | 216 -- .../hero-platform-intro.test.tsx | 146 - .../hero-platform-loop-mount.test.tsx | 59 - .../hero-platform-loop.test.tsx | 324 -- .../hero-platform-stage.test.tsx | 34 - .../landing-cta-link.test.tsx | 31 - .../landing-shell/landing-shell.test.tsx | 219 -- .../components/mobile-nav/mobile-nav.test.tsx | 146 - .../nav-menu-logo-marquee.test.tsx | 60 - .../workflow-menu-preview.test.tsx | 135 - .../nav-menu-chip/constants.test.ts | 38 - .../nav-menu-chip/nav-menu-chip.test.tsx | 329 -- .../navbar-shell/navbar-shell.test.tsx | 304 -- .../governance-loop/governance-loop.test.tsx | 70 - .../platform-suite/platform-suite.test.tsx | 103 - .../composer-loop/composer-loop.test.tsx | 123 - .../composer-loop/demo-composer.test.tsx | 56 - .../composer-loop/demo-workflow-data.test.ts | 51 - .../product-demo-caption.test.tsx | 71 - .../components/cert-blocs/cert-blocs.test.tsx | 196 -- .../workspace-controls.test.tsx | 46 - .../components/security/security.test.tsx | 43 - .../logs-run-graph/logs-run-graph.test.tsx | 115 - .../product-window/product-window.test.tsx | 31 - .../responsive-design-stage.test.ts | 178 - .../solutions-structured-data.test.tsx | 73 - .../app/(landing)/content-routing.test.tsx | 7 +- .../customer-story-media.test.tsx | 40 - .../sim/app/(landing)/customers/page.test.tsx | 42 +- .../demo-scheduler/demo-scheduler.test.tsx | 119 +- .../components/legacy-inert-fallback.test.ts | 53 - .../hooks/use-preview-dialog-focus.test.tsx | 143 - .../knowledge-document-detail.test.tsx | 79 - apps/sim/app/(landing)/landing.test.tsx | 71 - apps/sim/app/(landing)/models/utils.test.ts | 77 - .../(landing)/x-page-view-tracker.test.tsx | 46 - .../api/mcp/route.test.ts | 1 - .../api/mcp/serve/[serverId]/route.test.ts | 1 - .../consent-preferences-trigger.test.tsx | 45 - .../_shell/consent/consent-provider.test.tsx | 58 - .../consent/consent-store-provider.test.tsx | 61 - ...oogle-analytics-page-view-tracker.test.tsx | 64 - .../desktop-title-bar-controller.test.tsx | 18 - .../_shell/desktop-title-bar-surfaces.test.ts | 392 --- apps/sim/app/_shell/desktop-title-bar.test.ts | 29 - .../app/_shell/paste-admission-guard.test.tsx | 72 - .../_shell/providers/get-query-client.test.ts | 26 - .../providers/session-provider.test.tsx | 95 +- .../_shell/providers/theme-provider.test.tsx | 144 - .../sim/app/_shell/public-env-script.test.tsx | 50 - apps/sim/app/access-requests/page.test.tsx | 148 - .../account/settings/[section]/page.test.tsx | 11 - .../billing/credit-usage/page.test.ts | 63 - .../app/api/audit-logs/export/route.test.ts | 74 - apps/sim/app/api/audit-logs/route.test.ts | 83 - apps/sim/app/api/auth/[...all]/route.test.ts | 147 - .../api/auth/forget-password/route.test.ts | 108 - .../auth/instagram/authorize/route.test.ts | 26 - .../api/auth/oauth/connections/route.test.ts | 191 - .../api/auth/oauth/credentials/route.test.ts | 88 - .../api/auth/oauth/disconnect/route.test.ts | 117 - .../app/api/auth/oauth/token/route.test.ts | 496 --- apps/sim/app/api/auth/oauth/utils.test.ts | 374 +- .../api/auth/oauth2/authorize/route.test.ts | 245 -- .../oauth2/callback/quickbooks/route.test.ts | 26 - .../oauth2/callback/shopify/route.test.ts | 4 - .../api/auth/oauth2/register/route.test.ts | 19 - .../app/api/auth/oauth2/revoke/route.test.ts | 27 - ....postgres.test.ts => route.integration.ts} | 49 +- .../app/api/auth/oauth2/token/route.test.ts | 121 - .../app/api/auth/reset-password/route.test.ts | 130 - .../api/auth/shopify/authorize/route.test.ts | 4 - .../sso/providers/[providerId]/route.test.ts | 110 +- .../app/api/auth/sso/providers/route.test.ts | 11 - .../app/api/auth/sso/register/route.test.ts | 274 -- .../app/api/auth/sso/resolve/route.test.ts | 35 - .../app/api/billing/invoices/route.test.ts | 26 - apps/sim/app/api/billing/route.test.ts | 62 - .../app/api/billing/switch-plan/route.test.ts | 4 - ...ntegration.test.ts => replay.live.test.ts} | 3 - .../app/api/billing/update-cost/route.test.ts | 120 - ...ntegration.test.ts => worker.live.test.ts} | 3 - .../app/api/blocks/visibility/route.test.ts | 32 - .../api/chat/[identifier]/otp/route.test.ts | 297 -- .../app/api/chat/[identifier]/route.test.ts | 462 +-- .../api/chat/[identifier]/sso/route.test.ts | 15 - .../chat/manage/[id]/password/route.test.ts | 49 - .../app/api/chat/manage/[id]/route.test.ts | 306 +- apps/sim/app/api/chat/route.test.ts | 115 - apps/sim/app/api/chat/utils.test.ts | 288 +- apps/sim/app/api/chat/validate/route.test.ts | 33 +- .../app/api/cli/auth/approve/route.test.ts | 163 - apps/sim/app/api/cli/auth/poll/route.test.ts | 84 - .../app/api/copilot/api-keys/route.test.ts | 304 +- .../copilot/api-keys/validate/route.test.ts | 133 - .../app/api/copilot/chat/abort/route.test.ts | 130 - .../app/api/copilot/chat/delete/route.test.ts | 184 - .../app/api/copilot/chat/steer/route.test.ts | 61 - .../app/api/copilot/chat/stop/route.test.ts | 51 - .../app/api/copilot/chat/stream/route.test.ts | 43 - apps/sim/app/api/copilot/chats/route.test.ts | 218 -- .../sim/app/api/copilot/confirm/route.test.ts | 396 +-- .../app/api/copilot/feedback/route.test.ts | 342 +- .../api/copilot/tool-permission/route.test.ts | 46 - .../api/copilot/tools/execute/route.test.ts | 64 - .../enroll/[token]/complete/route.test.ts | 19 - .../[token]/oauth/[optionId]/route.test.ts | 45 - .../credential-groups/oauth-callback.test.ts | 38 - .../oauth/[provider]/callback/route.test.ts | 59 - .../credentials/[id]/members/route.test.ts | 4 - .../app/api/credentials/[id]/route.test.ts | 4 - apps/sim/app/api/credentials/route.test.ts | 198 -- .../cleanup-stale-executions/route.test.ts | 122 +- .../cron/cleanup-table-row-ttl/route.test.ts | 91 - .../cron/knowledge-projection/route.test.ts | 86 - .../reconcile-billing-seats/route.test.ts | 92 - .../cron/renew-subscriptions/route.test.ts | 36 - .../route.test.ts | 68 - .../custom-blocks/[id]/usages/route.test.ts | 35 - .../api/desktop/auth/handoff/route.test.ts | 26 - .../api/desktop/tool/authorize/route.test.ts | 51 - .../app/api/desktop/tool/file/route.test.ts | 38 - .../api/desktop/update/download/route.test.ts | 36 - .../update/latest-mac.yml/route.test.ts | 23 - .../[id]/route.test.ts | 4 - apps/sim/app/api/files/authorization.test.ts | 118 - apps/sim/app/api/files/delete/route.test.ts | 89 - .../app/api/files/export/[id]/route.test.ts | 70 - .../public/[token]/content/route.test.ts | 33 - .../files/public/[token]/inline/route.test.ts | 49 - .../files/public/[token]/otp/route.test.ts | 69 - .../api/files/public/[token]/route.test.ts | 25 - .../files/public/[token]/sso/route.test.ts | 49 - .../api/files/serve/[...path]/route.test.ts | 321 -- .../app/api/files/uploads/finalizers.test.ts | 85 - apps/sim/app/api/files/uploads/route.test.ts | 108 - apps/sim/app/api/files/utils.test.ts | 301 -- .../api/folders/[id]/duplicate/route.test.ts | 18 - apps/sim/app/api/folders/[id]/route.test.ts | 382 -- .../sim/app/api/folders/reorder/route.test.ts | 20 - apps/sim/app/api/folders/route.test.ts | 354 -- .../api/guardrails/mask-batch/route.test.ts | 31 - .../api/guardrails/pii/validate/route.test.ts | 36 - apps/sim/app/api/health/route.test.ts | 17 - .../api/internal/file-doc/seed/route.test.ts | 32 - .../api/invitations/[id]/accept/route.test.ts | 85 - .../api/invitations/[id]/resend/route.test.ts | 131 - apps/sim/app/api/invitations/route.test.ts | 43 - apps/sim/app/api/jobs/[jobId]/route.test.ts | 102 - .../[documentId]/chunks/route.test.ts | 5 - .../api/knowledge/[id]/export/route.test.ts | 54 - .../[id]/tag-definitions/route.test.ts | 154 - .../connectors/directory-sync/route.test.ts | 27 - .../connectors/member-sync/route.test.ts | 11 - .../knowledge/connectors/sync/route.test.ts | 150 +- .../github/installations/route.test.ts | 37 - .../knowledge/github/setup/redirects.test.ts | 45 +- .../app/api/knowledge/migrated-routes.test.ts | 286 +- apps/sim/app/api/knowledge/route.test.ts | 227 -- .../app/api/knowledge/search/route.test.ts | 109 - .../app/api/knowledge/search/utils.test.ts | 404 +-- .../api/knowledge/secret-provenance.test.ts | 92 - .../sim-search/sources/route.test.ts | 183 +- .../slack/oauth/callback/route.test.ts | 2 - .../knowledge/slack/onboarding/route.test.ts | 38 - .../api/knowledge/slack/setup/route.test.ts | 92 - apps/sim/app/api/knowledge/utils.test.ts | 107 - apps/sim/app/api/link-preview/route.test.ts | 2 - apps/sim/app/api/logs/cleanup/route.test.ts | 72 - apps/sim/app/api/logs/export/route.test.ts | 49 - apps/sim/app/api/logs/stats/route.test.ts | 44 - .../api/mcp/capability-declarations.test.ts | 2 - apps/sim/app/api/mcp/copilot/route.test.ts | 62 - apps/sim/app/api/mcp/discover/route.test.ts | 30 - apps/sim/app/api/mcp/events/route.test.ts | 121 - .../app/api/mcp/oauth/callback/route.test.ts | 44 - .../sim/app/api/mcp/oauth/start/route.test.ts | 87 - .../api/mcp/serve/[serverId]/route.test.ts | 551 --- .../mcp/servers/[id]/refresh/route.test.ts | 37 - .../app/api/mcp/servers/[id]/route.test.ts | 4 - apps/sim/app/api/mcp/servers/route.test.ts | 62 +- .../mcp/servers/test-connection/route.test.ts | 16 - .../api/mcp/workflow-servers/route.test.ts | 80 - .../chats/[chatId]/fork/route.test.ts | 147 - .../chats/[chatId]/restore/route.test.ts | 32 - .../mothership/chats/[chatId]/route.test.ts | 117 - .../api/mothership/chats/read/route.test.ts | 38 - .../app/api/mothership/chats/route.test.ts | 83 - .../app/api/mothership/events/route.test.ts | 18 - .../app/api/mothership/execute/route.test.ts | 237 -- .../integrations/catalog/route.test.ts | 12 - .../api/mothership/runs/control/route.test.ts | 10 - .../oauth/route.test.ts | 74 - .../[id]/billing-summary/route.test.ts | 30 - .../slack-managed-users/route.test.ts | 20 - .../[id]/domains/[domainId]/route.test.ts | 27 - .../domains/[domainId]/verify/route.test.ts | 33 - .../organizations/[id]/domains/route.test.ts | 42 - .../[memberId]/usage-limit/route.test.ts | 58 - .../organizations/[id]/members/route.test.ts | 28 - .../permission-groups/[groupId]/route.test.ts | 42 +- .../organizations/[id]/roster/route.test.ts | 121 +- .../[id]/secret-source/route.test.ts | 20 +- .../[id]/session-policy/route.test.ts | 41 - .../[id]/usage/error-policy.test.ts | 1 - apps/sim/app/api/organizations/route.test.ts | 49 - .../api/permission-groups/user/route.test.ts | 60 - .../[resourceType]/[resourceId]/route.test.ts | 92 - apps/sim/app/api/pinned-items/route.test.ts | 116 - .../providers/baseten/models/route.test.ts | 143 - .../ollama-cloud/models/route.test.ts | 170 - .../api/providers/ollama/models/route.test.ts | 102 - .../embeddings/models/route.test.ts | 77 - .../providers/together/models/route.test.ts | 148 - .../api/providers/vllm/models/route.test.ts | 14 - .../app/api/public-api-route-handler.test.ts | 52 - .../[executionId]/[contextId]/route.test.ts | 56 +- .../[workflowId]/[executionId]/route.test.ts | 55 - apps/sim/app/api/resume/poll/route.test.ts | 137 - apps/sim/app/api/schedules/[id]/route.test.ts | 514 --- .../app/api/schedules/execute/route.test.ts | 333 +- apps/sim/app/api/schedules/route.test.ts | 83 - .../app/api/secrets/references/route.test.ts | 79 - apps/sim/app/api/secrets/usage/route.test.ts | 74 - .../app/api/selectors/execute/route.test.ts | 17 - .../allowed-integrations/route.test.ts | 4 - apps/sim/app/api/skills/route.test.ts | 132 +- apps/sim/app/api/speech/token/route.test.ts | 35 - .../api/table/[tableId]/columns/route.test.ts | 183 +- .../[tableId]/delete-async/route.test.ts | 93 +- .../table/[tableId]/dispatches/route.test.ts | 110 - .../api/table/[tableId]/export/route.test.ts | 12 - .../api/table/[tableId]/groups/route.test.ts | 21 - .../api/table/[tableId]/import/route.test.ts | 241 -- .../api/table/[tableId]/query/route.test.ts | 47 - .../api/table/[tableId]/restore/route.test.ts | 65 - .../sim/app/api/table/[tableId]/route.test.ts | 54 - .../enrichment/[groupId]/route.test.ts | 38 - .../[tableId]/rows/[rowId]/route.test.ts | 229 +- .../table/[tableId]/rows/find/route.test.ts | 75 - .../api/table/[tableId]/rows/route.test.ts | 106 +- .../table/[tableId]/rows/upsert/route.test.ts | 164 - .../sim/app/api/table/capability-gate.test.ts | 71 - .../executor-capability-exemption.test.ts | 3 - .../app/api/table/import-csv/route.test.ts | 77 +- apps/sim/app/api/table/route.test.ts | 91 +- .../api/table/row-secret-provenance.test.ts | 75 - .../sim/app/api/table/table-tool-auth.test.ts | 38 - .../api/table/table-transfer-routes.test.ts | 30 - apps/sim/app/api/table/utils.test.ts | 101 - apps/sim/app/api/tools/custom/route.test.ts | 152 +- apps/sim/app/api/usage/route.test.ts | 4 - .../app/api/users/me/deletion/route.test.ts | 3 - .../app/api/users/me/profile/route.test.ts | 67 - .../app/api/users/me/settings/route.test.ts | 12 - .../subscription/[id]/transfer/route.test.ts | 28 - .../api/users/me/usage-limits/route.test.ts | 35 - .../users/me/usage-logs/cursor-route.test.ts | 11 +- .../users/me/usage-logs/export/route.test.ts | 122 - .../app/api/users/me/usage-logs/route.test.ts | 114 - .../api/users/me/usage-logs/shared.test.ts | 23 - .../v1/admin/organizations/[id]/route.test.ts | 13 - .../workspaces/[id]/folders/route.test.ts | 17 - .../workspaces/[id]/import/route.test.ts | 22 - .../app/api/v1/audit-logs/[id]/route.test.ts | 23 - apps/sim/app/api/v1/audit-logs/auth.test.ts | 59 - apps/sim/app/api/v1/audit-logs/route.test.ts | 44 - apps/sim/app/api/v1/auth.test.ts | 10 +- apps/sim/app/api/v1/capability-gate.test.ts | 79 - .../sim/app/api/v1/copilot/chat/route.test.ts | 26 - .../app/api/v1/files/[fileId]/route.test.ts | 38 - .../v1/knowledge/[id]/documents/route.test.ts | 26 - .../app/api/v1/knowledge/search/route.test.ts | 132 - apps/sim/app/api/v1/knowledge/utils.test.ts | 5 +- .../executions/[executionId]/route.test.ts | 57 - apps/sim/app/api/v1/logs/projection.test.ts | 157 - apps/sim/app/api/v1/middleware.test.ts | 153 +- .../app/api/v1/tables/[tableId]/route.test.ts | 50 - .../app/api/v1/tables/capability-gate.test.ts | 18 - apps/sim/app/api/v1/tables/route.test.ts | 4 - .../v1/workflows/[id]/deploy/route.test.ts | 143 +- .../v1/workflows/[id]/export/route.test.ts | 61 - .../v1/workflows/[id]/rollback/route.test.ts | 95 - .../app/api/v1/workflows/import/route.test.ts | 220 -- .../app/api/v2/[[...segments]]/route.test.ts | 9 - .../app/api/v2/access-requests.routes.test.ts | 288 +- apps/sim/app/api/v2/audit-logs/route.test.ts | 75 - .../sim/app/api/v2/billing/logs/route.test.ts | 83 +- .../app/api/v2/billing/status/route.test.ts | 73 +- apps/sim/app/api/v2/blocks/route.test.ts | 127 +- .../app/api/v2/chat-deployments/route.test.ts | 63 - apps/sim/app/api/v2/chat/route.test.ts | 67 - .../app/api/v2/connector-types/route.test.ts | 91 - .../credentials/[credentialId]/route.test.ts | 102 +- .../v2/credentials/connections/route.test.ts | 89 - .../v2/credentials/providers/route.test.ts | 137 - apps/sim/app/api/v2/credentials/route.test.ts | 153 - .../custom-tools/[customToolId]/route.test.ts | 69 - .../sim/app/api/v2/custom-tools/route.test.ts | 102 +- .../v2/files/[fileId]/content/route.test.ts | 76 - .../v2/files/[fileId]/metadata/route.test.ts | 92 - .../v2/files/[fileId]/restore/route.test.ts | 153 - .../app/api/v2/files/[fileId]/route.test.ts | 109 +- .../api/v2/files/[fileId]/share/route.test.ts | 146 +- .../api/v2/files/[fileId]/text/route.test.ts | 117 - .../api/v2/files/[fileId]/unzip/route.test.ts | 102 +- .../versions/[version]/revert/route.test.ts | 21 - .../api/v2/files/bulk-delete/route.test.ts | 115 - .../api/v2/files/bulk-download/route.test.ts | 105 - .../v2/files/folders/restore/route.test.ts | 143 - .../app/api/v2/files/folders/route.test.ts | 181 +- apps/sim/app/api/v2/files/move/route.test.ts | 63 - apps/sim/app/api/v2/files/route.test.ts | 155 - .../v2/files/uploads/[uploadId]/route.test.ts | 97 +- .../app/api/v2/files/uploads/route.test.ts | 77 - apps/sim/app/api/v2/files/utils.test.ts | 9 +- .../connectors/route.test.ts | 384 -- .../chunks/[chunkId]/route.test.ts | 162 - .../[documentId]/chunks/route.test.ts | 110 - .../documents/[documentId]/route.test.ts | 45 - .../documents/collection.test.ts | 61 - .../from-workspace-files/route.test.ts | 69 - .../[knowledgeBaseId]/documents/route.test.ts | 55 - .../uploads/[uploadId]/complete/route.test.ts | 30 - .../documents/uploads/concealment.test.ts | 20 +- .../documents/uploads/control-routes.test.ts | 158 - .../documents/uploads/route.test.ts | 64 - .../[knowledgeBaseId]/export/route.test.ts | 87 - .../[knowledgeBaseId]/restore/route.test.ts | 50 - .../tags/[tagId]/route.test.ts | 28 - .../tags/next-slot/route.test.ts | 80 - .../[knowledgeBaseId]/tags/route.test.ts | 281 +- .../tags/usage/route.test.ts | 101 - apps/sim/app/api/v2/knowledge/route.test.ts | 172 - .../knowledge/search/route.provenance.test.ts | 5 - .../app/api/v2/knowledge/search/route.test.ts | 178 - apps/sim/app/api/v2/knowledge/utils.test.ts | 20 - apps/sim/app/api/v2/lib/response.test.ts | 32 - .../sim/app/api/v2/logs/[runId]/route.test.ts | 54 - apps/sim/app/api/v2/logs/route.test.ts | 261 +- apps/sim/app/api/v2/logs/stats/route.test.ts | 84 - .../mcp-servers/[mcpServerId]/route.test.ts | 79 +- .../[mcpServerId]/tools/route.test.ts | 65 - apps/sim/app/api/v2/mcp-servers/route.test.ts | 89 - apps/sim/app/api/v2/meta/route.test.ts | 114 - .../[invitationId]/workspaces/route.test.ts | 29 - .../permission-groups/route.test.ts | 69 +- .../[organizationId]/usage/route.test.ts | 14 - .../app/api/v2/organizations/route.test.ts | 9 - .../v2/sandboxes/[sandboxId]/route.test.ts | 106 +- apps/sim/app/api/v2/sandboxes/route.test.ts | 124 - .../app/api/v2/secrets/[name]/route.test.ts | 142 +- apps/sim/app/api/v2/secrets/route.test.ts | 50 - .../v2/skills/[skillId]/editors/route.test.ts | 75 +- .../app/api/v2/skills/[skillId]/route.test.ts | 83 +- apps/sim/app/api/v2/skills/route.test.ts | 108 +- .../[tableId]/cancel-runs/route.test.ts | 49 - .../v2/tables/[tableId]/columns/route.test.ts | 111 +- .../dispatches/[dispatchId]/route.test.ts | 119 +- .../tables/[tableId]/dispatches/route.test.ts | 118 +- .../v2/tables/[tableId]/exports/route.test.ts | 165 - .../v2/tables/[tableId]/groups/route.test.ts | 69 +- .../[tableId]/query/count/route.test.ts | 40 - .../v2/tables/[tableId]/query/route.test.ts | 59 - .../v2/tables/[tableId]/restore/route.test.ts | 47 - .../app/api/v2/tables/[tableId]/route.test.ts | 42 - .../enrichment/[groupId]/route.test.ts | 109 +- .../[tableId]/rows/[rowId]/route.test.ts | 41 +- .../[tableId]/rows/bulk-update/route.test.ts | 36 - .../v2/tables/[tableId]/rows/route.test.ts | 65 +- .../[tableId]/rows/search/route.test.ts | 23 - .../[tableId]/rows/upsert/route.test.ts | 44 - .../[tableId]/views/[viewId]/route.test.ts | 48 +- .../v2/tables/[tableId]/views/route.test.ts | 133 - .../v2/tables/folders/restore/route.test.ts | 83 - .../app/api/v2/tables/folders/route.test.ts | 153 - .../imports/[importId]/complete/route.test.ts | 108 - .../app/api/v2/tables/imports/route.test.ts | 163 - apps/sim/app/api/v2/tables/move/route.test.ts | 79 - apps/sim/app/api/v2/tables/presenters.test.ts | 4 - apps/sim/app/api/v2/tables/route.test.ts | 173 - .../v2/tools/[toolId]/execute/route.test.ts | 58 - apps/sim/app/api/v2/tools/route.test.ts | 73 +- .../parts/[partNumber]/route.test.ts | 18 - .../api/v2/uploads/[uploadId]/route.test.ts | 4 - .../[serverId]/route.test.ts | 51 - .../[serverId]/tools/route.test.ts | 84 - .../api/v2/workflow-mcp-servers/route.test.ts | 79 - .../[workflowId]/deploy/route.test.ts | 104 - .../[workflowId]/deployment/route.test.ts | 32 - .../deployments/chat/route.test.ts | 24 - .../[workflowId]/duplicate/route.test.ts | 59 - .../[workflowId]/execute/route.test.ts | 23 - .../[workflowId]/export/route.test.ts | 37 - .../[workflowId]/operations/route.test.ts | 73 - .../[workflowId]/restore/route.test.ts | 58 - .../[workflowId]/rollback/route.test.ts | 78 - .../v2/workflows/[workflowId]/route.test.ts | 85 +- .../runs/[runId]/cancel/route.test.ts | 24 - .../runs/[runId]/files/[fileId]/route.test.ts | 86 - .../runs/[runId]/resume/route.test.ts | 48 - .../[workflowId]/runs/[runId]/route.test.ts | 257 +- .../workflows/[workflowId]/runs/route.test.ts | 127 +- .../[workflowId]/state/route.test.ts | 124 - .../[workflowId]/variables/route.test.ts | 122 - .../versions/[version]/activate/route.test.ts | 22 - .../versions/[version]/revert/route.test.ts | 25 - .../versions/[version]/route.test.ts | 67 - .../[workflowId]/versions/route.test.ts | 50 - .../api/v2/workflows/folders/route.test.ts | 83 - .../app/api/v2/workflows/import/route.test.ts | 39 +- .../app/api/v2/workflows/move/route.test.ts | 47 - apps/sim/app/api/v2/workflows/route.test.ts | 175 - .../[workspaceId]/invitations/route.test.ts | 65 +- .../permission-config/route.test.ts | 23 +- .../v2/workspaces/[workspaceId]/route.test.ts | 21 - apps/sim/app/api/v2/workspaces/route.test.ts | 111 +- .../webhooks/[id]/reactivation-gate.test.ts | 4 - .../app/api/webhooks/agentmail/route.test.ts | 26 - .../api/webhooks/outbox/process/route.test.ts | 62 - .../webhooks/poll/[provider]/route.test.ts | 44 - .../quickbooks/[appKey]/route.test.ts | 24 - apps/sim/app/api/webhooks/route.test.ts | 241 -- .../slack/custom/[credentialId]/route.test.ts | 75 - apps/sim/app/api/webhooks/slack/route.test.ts | 43 - .../sim/app/api/webhooks/tiktok/route.test.ts | 58 - .../api/webhooks/trigger/[path]/route.test.ts | 612 +--- .../workflows/[id]/chat/status/route.test.ts | 43 - .../api/workflows/[id]/deployed/route.test.ts | 43 - .../[id]/deployments/[version]/route.test.ts | 66 - .../[id]/execute/response-block.test.ts | 228 +- .../[id]/execute/route.async.test.ts | 777 +---- .../[executionId]/cancel/route.test.ts | 100 - .../executions/[executionId]/route.test.ts | 14 - .../[executionId]/stream/route.test.ts | 107 - .../app/api/workflows/[id]/log/route.test.ts | 94 - apps/sim/app/api/workflows/[id]/route.test.ts | 75 - .../workflows/[id]/variables/route.test.ts | 264 +- apps/sim/app/api/workflows/middleware.test.ts | 52 - apps/sim/app/api/workflows/route.test.ts | 19 - .../api/workspace-events/poll/route.test.ts | 33 - .../[id]/api-keys/[keyId]/route.test.ts | 24 - .../workspaces/[id]/api-keys/route.test.ts | 4 - .../workspaces/[id]/byok-keys/route.test.ts | 149 +- .../[groupId]/access/route.test.ts | 113 +- .../[groupId]/enrollments/route.test.ts | 47 - .../credential-groups/ensure/route.test.ts | 76 - .../[id]/credential-groups/route.test.ts | 18 - .../[id]/credit-availability/route.test.ts | 14 - .../[id]/environment/capability-gate.test.ts | 69 +- .../workspaces/[id]/environment/route.test.ts | 33 - .../[id]/files/[fileId]/content/route.test.ts | 93 - .../files/[fileId]/csv-preview/route.test.ts | 16 - .../files/[fileId]/dimensions/route.test.ts | 96 - .../[id]/files/[fileId]/export/route.test.ts | 87 - .../[id]/files/[fileId]/extract/route.test.ts | 30 - .../[id]/files/[fileId]/route.test.ts | 91 - .../[id]/files/[fileId]/share/route.test.ts | 111 +- .../[id]/files/bulk-archive/route.test.ts | 79 - ...egration.test.ts => route-archive.test.ts} | 3 - .../[id]/files/download/route.test.ts | 130 - .../files/folders/[folderId]/route.test.ts | 171 - .../[id]/files/folders/route.test.ts | 127 - .../[id]/files/inline/route.test.ts | 45 - .../workspaces/[id]/files/move/route.test.ts | 99 - .../api/workspaces/[id]/files/route.test.ts | 72 +- .../fork/excluded-workflows/route.test.ts | 90 - .../[id]/fork/lineage/route.test.ts | 50 - .../[id]/fork/promote/route.test.ts | 36 - .../api/workspaces/[id]/fork/route.test.ts | 27 - .../[id]/host-context/route.test.ts | 22 - .../api/workspaces/[id]/inbox/route.test.ts | 56 - .../[id]/inbox/siblings.capability.test.ts | 4 - .../workspaces/[id]/permissions/route.test.ts | 279 -- .../[id]/sandboxes/[sandboxId]/route.test.ts | 3 - .../[id]/sandboxes/error-policy.test.ts | 51 +- .../workspaces/[id]/sandboxes/route.test.ts | 99 - .../workspaces/[id]/usage-gate/route.test.ts | 15 - .../api/workspaces/[id]/visit/route.test.ts | 21 - .../invitations/batch/route.test.ts | 94 - .../api/workspaces/invitations/route.test.ts | 151 - apps/sim/app/api/workspaces/route.test.ts | 37 +- apps/sim/app/cli/auth/cli-auth-view.test.tsx | 46 +- apps/sim/app/cli/auth/page.test.tsx | 16 - .../[token]/oauth-reconnect-link.test.tsx | 40 - .../enroll/[token]/oauth-toast.test.tsx | 74 - .../enroll/[token]/page.test.tsx | 139 - apps/sim/app/desktop/auth/page.test.tsx | 4 - apps/sim/app/desktop/auth/validation.test.ts | 3 - apps/sim/app/desktop/connect/page.test.tsx | 106 - .../app/desktop/connect/validation.test.ts | 26 - apps/sim/app/home/page.test.tsx | 18 +- apps/sim/app/invite/[id]/invite.test.tsx | 204 -- .../invite/components/status-card.test.tsx | 46 - .../app/knowledge/github/setup/setup.test.tsx | 118 - .../chats-section/chats-section.test.tsx | 374 -- .../organization-footer.test.tsx | 155 - .../organization-header.test.tsx | 235 -- .../workspace-list.test.tsx | 154 - .../workspaces-section.test.tsx | 272 -- .../use-organization-workspaces.test.tsx | 106 - .../components/composer/composer.test.tsx | 595 +--- .../home/organization-home.test.tsx | 828 +---- .../app/o/[organizationId]/home/page.test.tsx | 87 - .../disconnect-account-menu.test.tsx | 131 - .../integrations/integrations.test.tsx | 1 - .../live-member-integrations.test.tsx | 19 - .../integrations/page.test.tsx | 2 - .../app/o/[organizationId]/layout.test.tsx | 92 - .../app/o/[organizationId]/prefetch.test.ts | 70 +- .../search/search-results-view.test.tsx | 162 - .../o/[organizationId]/search/search.test.tsx | 353 -- .../settings/[section]/layout.test.tsx | 12 - .../settings/[section]/page.test.tsx | 35 - .../live-search-settings.test.tsx | 24 - ...rganization-integrations-settings.test.tsx | 1 - .../organization-integrations-setup.test.tsx | 414 --- .../organization-search-stats-period.test.tsx | 127 - .../organization-search-status.test.ts | 56 - .../organization-source-stats.test.tsx | 153 - .../integrations/search-source-setup.test.tsx | 501 +-- .../integrations/slack-account-setup.test.tsx | 1 - .../organization-recently-deleted.test.tsx | 178 - .../organization-search-mcp.test.tsx | 67 - .../organization-search-slack.test.tsx | 482 --- .../components/search-mcp-connection.test.tsx | 82 - .../providers/[connectorType]/page.test.tsx | 2 - .../[connectorType]/provider-detail.test.tsx | 790 ----- .../sources/[connectorId]/page.test.tsx | 2 - .../[connectorId]/source-detail.test.tsx | 388 --- .../settings/navigation.test.ts | 164 +- .../organization-settings-sidebar.test.tsx | 197 -- .../settings/settings-query-warmers.test.ts | 107 - .../chat-complete-handoff.test.tsx | 31 - .../oauth/credential-connected/page.test.tsx | 36 - apps/sim/app/seo.test.ts | 152 - apps/sim/app/sitemap.test.ts | 54 - .../connect/[token]/page.test.tsx | 12 - .../[token]/slack-search-onboarding.test.tsx | 24 - .../install/[teamId]/page.test.tsx | 13 - .../connect-oauth-modal.test.tsx | 581 ---- .../components/find-bar/find-bar.test.tsx | 329 -- .../folders/drag-move-pruning.test.ts | 29 - .../components/folders/flyout-entries.test.ts | 146 - .../folders/folder-search-scope.test.ts | 85 - .../components/folders/folders.test.ts | 298 +- .../components/folders/resource-sort.test.ts | 93 - .../folders/use-drag-teardown.test.tsx | 118 - .../folders/use-row-drag-ghost.test.tsx | 76 - .../folders/use-spring-loaded-folder.test.tsx | 235 -- .../folders/use-spring-navigation.test.tsx | 309 -- .../integration-tabs-header.test.tsx | 45 - .../invite-modal/invite-modal.test.tsx | 316 -- .../message-actions/message-actions.test.tsx | 124 - .../resource-options.test.tsx | 178 - .../resource/is-resource-list-empty.test.ts | 53 - .../selection-aware-context-menus.test.tsx | 256 -- .../resource/selection-label.test.ts | 69 - .../use-resource-row-selection.test.tsx | 173 - .../session-expired/session-expired.test.tsx | 131 - .../use-sidebar-peek.test.tsx | 455 --- .../file-viewer/document-table.test.ts | 179 - .../file-viewer/file-category.test.ts | 229 -- .../file-viewer/image-preview.test.tsx | 88 - .../file-viewer/mermaid-diagram.test.ts | 41 - .../block-image-paragraph.test.ts | 55 +- .../rich-markdown-editor/block-mover.test.ts | 90 - .../rich-markdown-editor/bullet-list.test.ts | 78 - .../rich-markdown-editor/chrome-scope.test.ts | 85 - .../code-highlight.test.ts | 37 +- .../code-languages.test.ts | 2 - .../collaboration/agent-stream-leader.test.ts | 29 +- ...apply-streamed-markdown.concurrent.test.ts | 28 - .../apply-streamed-markdown.test.ts | 65 +- .../collaboration/caret-presence.test.ts | 118 - .../collaboration/file-doc-provider.test.ts | 432 --- .../pending-update-journal.test.ts | 88 - .../collaboration/readiness.test.ts | 38 - .../detect-language.test.ts | 38 - .../dirty-on-open.test.ts | 15 - .../rich-markdown-editor/dirty-signal.test.ts | 17 - .../editor-lifecycle.test.tsx | 478 +-- .../embed/link-embed.test.ts | 28 - .../field-lifecycle.test.tsx | 72 +- .../field-upload.test.tsx | 88 - .../find/find-extension.test.ts | 155 +- .../find/find-matches.test.ts | 47 - .../heading-anchors.test.ts | 27 +- .../heading-image.test.ts | 186 +- .../image-collaboration.test.tsx | 214 +- .../image-drag-move.test.ts | 37 - .../image-input-rule.test.ts | 137 - .../rich-markdown-editor/image-paste.test.ts | 114 - .../image-resize.test.tsx | 67 - .../rich-markdown-editor/image-upload.test.ts | 153 - .../rich-markdown-editor/image.test.ts | 28 - .../rich-markdown-editor/keymap.test.ts | 394 +-- .../link-input-rule.test.ts | 23 - .../link-mark-precedence.test.ts | 217 -- .../list-collaboration.test.ts | 47 - .../list-input-rules.test.ts | 140 - .../rich-markdown-editor/list-item.test.ts | 36 - .../markdown-fidelity.test.ts | 6 - .../markdown-parse.test.ts | 134 - .../markdown-paste.test.ts | 291 +- .../markdown-storage.test.ts | 123 - .../mention/mention-chip.test.tsx | 82 - .../mention/mention-icon.test.ts | 32 - .../mention/mention-list.test.tsx | 198 -- .../mention/mention-node.test.ts | 22 - .../mention/sim-link.test.ts | 22 - .../menus/bubble-menu-collaboration.test.tsx | 37 - .../menus/editor-toolbar-integration.test.tsx | 574 --- .../menus/link-editing.test.ts | 64 +- .../menus/link-hover-card.test.tsx | 247 -- .../menus/suggestion-list.test.tsx | 113 - .../menus/table-menu.test.ts | 64 - .../menus/toolbar-button.test.tsx | 61 - .../menus/use-bubble-menu-floating.test.ts | 34 - .../menus/use-editor-toolbar.test.tsx | 123 - .../menus/use-suggestion-keyboard.test.tsx | 87 - .../rich-markdown-editor/ordered-list.test.ts | 106 - .../paste-admission.test.ts | 105 +- .../raw-markdown-snippet-view.test.ts | 56 - .../raw-markdown-snippet.test.ts | 140 - .../rich-markdown-field.test.tsx | 28 - .../round-trip-safety.test.ts | 170 - .../rich-markdown-editor/round-trip.test.ts | 232 -- .../slash-command/commands.test.ts | 95 - .../stream-settle-selection.test.ts | 17 - .../table-serialization.node.test.ts | 3 - .../title-heading.test.ts | 63 - .../file-viewer/text-editor-paste.test.ts | 46 - .../file-viewer/text-editor-state.test.ts | 308 -- .../file-viewer/text-editor-sync.test.tsx | 417 --- .../use-doc-preview-binary.test.ts | 142 - .../use-editable-file-conflicts.test.tsx | 132 +- .../use-editable-file-content.test.tsx | 168 - .../use-selection-copy-bridge.test.tsx | 91 - .../file-viewer/xlsx-preview-data.test.ts | 50 - .../share-modal/share-modal.test.tsx | 681 ---- .../files/untitled-title.test.ts | 40 +- .../components/chat-panel-layout.test.tsx | 127 - .../credits-chip/credits-chip.test.tsx | 146 - .../knowledge-search-results.test.tsx | 348 -- .../search-transitions.test.tsx | 295 -- .../agent-group/activity-layout.test.tsx | 149 - .../agent-group/activity-stream.test.tsx | 418 --- .../agent-group/agent-group.test.ts | 1305 +------ .../agent-group/browser-agent-icon.test.tsx | 222 -- .../agent-group/inline-markdown.test.tsx | 44 - .../agent-group/lane-activity.test.ts | 51 +- .../agent-group/search-activity.test.tsx | 366 -- .../agent-group/tool-activity-group.test.ts | 234 -- .../agent-group/tool-call-item.test.tsx | 332 -- .../chat-content/chat-content-images.test.tsx | 226 -- .../chat-content/chat-content.test.ts | 165 - .../chat-content/chat-presentation.test.tsx | 96 - .../chat-content/external-link.test.tsx | 82 - .../chat-content/highlighted-lines.test.tsx | 83 - .../chat-content/remark-plain-text.test.ts | 16 - .../message-sources/message-sources.test.tsx | 102 - .../components/question/question.test.ts | 265 +- .../source-chip/source-chip.test.tsx | 179 - .../personal-credential-card.test.tsx | 204 -- .../resource-tag-workspace.test.tsx | 139 - .../special-tags/special-tags.test.ts | 614 ---- .../special-tags/special-tags.test.tsx | 1723 +-------- .../usage-upgrade-display.test.tsx | 67 - .../watch-activity/watch-activity.test.tsx | 156 - .../message-content-overlay.test.tsx | 83 - .../message-content-spacing.test.tsx | 218 -- .../message-content-thinking.test.tsx | 674 ---- .../message-content/message-content.test.ts | 789 +---- .../message-content/resolve-citations.test.ts | 53 - .../components/message-content/utils.test.ts | 57 - .../mothership-chat/copyable-markdown.test.ts | 88 +- .../message-actions-visibility.test.ts | 40 - .../mothership-chat/mothership-chat.test.tsx | 279 -- .../mothership-chat/sizer-floor.test.ts | 46 - .../add-resource-dropdown.test.tsx | 131 - .../resource-folder-tree.test.ts | 72 - .../resource-from-item.test.ts | 30 - .../browser-session/browser-downloads.test.ts | 51 - .../browser-loading-bar.test.tsx | 59 - .../browser-page-issue.test.ts | 110 +- .../browser-panel-occlusion.test.ts | 318 -- .../browser-session-ui.test.tsx | 249 -- .../browser-session/browser-session.test.ts | 325 -- .../browser-session/url-suggestions.test.ts | 396 --- .../generic-resource-content.test.tsx | 29 - .../search-resource-content.test.tsx | 270 -- .../sources-resource-content.test.tsx | 104 - .../terminal-session/terminal-session.test.ts | 47 +- .../use-terminal-close-confirmation.test.tsx | 24 - .../resource-content.test.tsx | 395 --- .../resource-invalidation.test.ts | 132 - .../terminal-tab-icon.test.tsx | 96 - .../resource-tab-controls.test.ts | 24 - .../resource-tabs/resource-tabs.test.tsx | 206 -- .../resource-workspace-host.test.tsx | 119 - .../atlassian-source-setup-modal.test.tsx | 188 - .../suggested-actions.test.tsx | 119 - .../user-input/chip-selection.test.ts | 58 - .../attached-files-list.test.tsx | 99 - .../components/mic-button/mic-button.test.tsx | 112 - .../microphone-permission-help.test.tsx | 61 - .../plus-menu-dropdown.test.tsx | 430 --- .../resource-mention-items.test.ts | 193 -- .../prompt-editor/menu-cursor.test.tsx | 128 - .../prompt-editor/prompt-editor.test.tsx | 266 -- .../prompt-editor/use-prompt-editor.test.tsx | 444 +-- .../components/resource-context.test.ts | 47 - .../organization-skill-options.test.ts | 11 - .../mothership-context-event.test.ts | 45 - .../components/user-input/user-input.test.tsx | 240 -- .../[workspaceId]/home/hooks/chat-url.test.ts | 29 - .../preview/apply-file-preview-phase.test.ts | 115 - .../use-file-preview-sessions.test.tsx | 322 -- .../home/hooks/send-handoff.test.ts | 14 - .../stream/dispatch-stream-event.test.ts | 120 - .../stream/handle-complete-event.test.ts | 79 - .../stream/handle-resource-event.test.ts | 479 +-- .../hooks/stream/handle-session-event.test.ts | 14 - .../hooks/stream/handle-span-event.test.ts | 62 - .../hooks/stream/handle-title-event.test.ts | 37 - .../hooks/stream/handle-tool-event.test.ts | 247 -- .../home/hooks/stream/stream-context.test.ts | 121 +- .../hooks/stream/turn-model-serialize.test.ts | 367 -- .../home/hooks/stream/turn-model.test.ts | 291 -- .../hooks/use-browser-tab-resources.test.tsx | 269 -- .../home/hooks/use-chat.mount-send.test.tsx | 1142 +----- .../[workspaceId]/home/hooks/use-chat.test.ts | 136 - .../home/hooks/use-mothership-resize.test.tsx | 239 -- .../home/hooks/use-resource-panel.test.tsx | 123 - .../hooks/use-terminal-tab-resources.test.tsx | 176 - .../home/resolve-resource-ref.test.ts | 47 - .../home/resource-view-policy.test.ts | 175 +- .../use-workspace-invalidation-room.test.tsx | 31 - .../connect-service-account-modal.test.tsx | 173 - .../connect-slack-bot-modal.test.tsx | 150 - .../integrations/connect-route.test.ts | 39 - .../add-connector-modal.test.tsx | 1389 -------- .../connector-access-field.test.tsx | 325 -- .../connector-access.test.ts | 71 - .../components/connector-entitlements.test.ts | 13 - .../connector-selector-field.queries.test.tsx | 334 -- .../connector-selector-field.test.tsx | 143 - .../connector-selector-field.ui.test.tsx | 190 - .../connectors-section.test.tsx | 1293 ------- .../document-context-menu.test.tsx | 82 - .../connector-documents-tab.test.tsx | 324 -- .../connector-settings-fields.test.tsx | 666 ---- .../use-connector-settings-form.test.tsx | 224 -- .../knowledge/[id]/document-columns.test.ts | 14 - .../use-connector-config-fields.test.tsx | 236 -- .../[id]/hooks/use-connector-scope.test.ts | 19 - .../hooks/use-knowledge-upload.test.tsx | 26 - .../knowledge/knowledge.test.tsx | 280 -- .../knowledge/permissions.test.ts | 22 - .../workspace/[workspaceId]/layout.test.tsx | 5 - .../[workspaceId]/lib/prefetch.test.ts | 318 -- .../lib/seed-workspace-files.test.ts | 38 +- .../snapshot-boundary.test.tsx | 112 - .../log-row-context-menu.test.tsx | 154 - .../[workspaceId]/logs/error.test.tsx | 76 - .../[workspaceId]/logs/utils.test.ts | 43 +- .../[workspaceId]/prefetch-access.test.tsx | 115 - .../providers/custom-blocks-loader.test.tsx | 102 - .../global-commands-provider.test.tsx | 161 - .../providers/provider-models-loader.test.tsx | 141 - .../workspace-host-provider.test.tsx | 9 - .../workspace-permissions-provider.test.tsx | 123 - .../providers/workspace-scope-sync.test.tsx | 32 - .../utils/calendar-grid.test.ts | 104 - .../scheduled-tasks/utils/recurrence.test.ts | 69 - .../utils/schedule-events.test.ts | 126 - .../settings/[section]/layout.test.tsx | 42 - .../settings/[section]/page.test.tsx | 130 - .../settings/[section]/prefetch.test.ts | 60 - .../settings/[section]/settings.test.tsx | 65 - .../credit-usage/credit-usage-view.test.tsx | 150 - .../components/admin/add-user-modal.test.tsx | 346 -- .../create-api-key-modal.test.tsx | 112 - .../components/billing/billing.test.tsx | 525 --- .../components/browser/browser.test.tsx | 547 --- .../password-detail/password-detail.test.tsx | 75 - .../passwords-view/passwords-view.test.tsx | 110 - .../settings/components/byok/byok.test.tsx | 12 - .../components/cookie-preferences.test.tsx | 14 - .../general/components/privacy-view.test.tsx | 14 - .../components/general/general.test.tsx | 174 - .../general/timezone-picker.test.ts | 38 - .../inbox-enable-toggle.test.tsx | 150 - .../settings/components/inbox/inbox.test.tsx | 96 - .../mcp/refresh-action-state.test.ts | 61 - .../components/mcp/server-tools-label.test.ts | 34 - .../recently-deleted/recently-deleted.test.ts | 24 - .../row-actions-menu.test.tsx | 115 - .../components/sandbox-editor.test.tsx | 224 -- .../components/sandboxes/utils.test.ts | 113 - .../secret-value-field.test.tsx | 26 - .../components/self-host/self-host.test.tsx | 38 - .../team-management/team-management.test.tsx | 295 -- .../terminal/terminal-theme-picker.test.tsx | 128 - .../components/terminal/terminal.test.tsx | 295 -- .../[workspaceId]/settings/navigation.test.ts | 146 +- .../skills/components/utils.test.ts | 138 - .../column-type-limits.test.ts | 56 - .../column-types.test.ts | 43 - .../column-dropdown/column-dropdown.test.tsx | 89 - .../columns-menu/columns-menu.test.tsx | 69 - .../lock-settings-modal.test.tsx | 148 - .../components/row-modal/row-modal.test.tsx | 249 -- .../table-filter/table-filter.test.tsx | 215 -- .../table-grid/cells/cell-render.test.ts | 3 - .../cells/expanded-cell-popover.test.tsx | 148 - .../table-grid/cells/inline-editors.test.ts | 185 +- .../components/table-grid/table-paste.test.ts | 14 - .../components/table-grid/utils.test.ts | 157 +- .../components/views-menu/views-menu.test.tsx | 279 -- .../hooks/use-table-event-stream.test.ts | 127 - .../tables/[tableId]/hooks/use-table.test.ts | 126 - .../tables/[tableId]/utils.test.ts | 83 - .../tables/[tableId]/view-state.test.ts | 119 - .../upgrade/hooks/use-upgrade-state.test.tsx | 159 - .../components/chat/chat.test.tsx | 386 --- .../output-select/output-select.test.tsx | 321 -- .../chat/hooks/use-chat-file-upload.test.tsx | 105 - .../note-block/note-markdown-editor.test.tsx | 192 - .../hooks/use-context-management.test.tsx | 18 - .../hooks/use-file-attachments.test.tsx | 220 -- .../components/user-input/utils.test.ts | 49 +- .../components/chat/utils.test.ts | 53 - .../deploy/hooks/deploy-action-lock.test.ts | 3 - .../hooks/use-deploy-button-status.test.ts | 87 - .../deploy/hooks/use-deploy-readiness.test.ts | 26 - .../retry-settings/retry-settings.test.tsx | 121 - .../sub-block/components/code/code.test.tsx | 53 - .../combobox/missing-option.test.ts | 9 - .../microsoft-dataverse-policy.test.ts | 45 - .../components/dropdown/dropdown.test.tsx | 262 -- .../dropdown/stale-selections.test.ts | 13 - .../components/formatted-text.test.tsx | 52 - .../components/long-input/long-input.test.tsx | 27 - .../model-fallback-list.test.tsx | 92 - .../password-mask/password-mask.test.ts | 27 +- .../selector-combobox.test.tsx | 155 - .../starter/input-format-files.test.ts | 74 - .../sub-block/components/table/table.test.tsx | 20 - .../tool-input/param-dependents.test.ts | 53 +- .../components/tool-input/tool-input.test.ts | 469 +-- .../workflow-search-highlight.test.ts | 122 +- .../workspace-folder-selector.test.tsx | 72 - .../hooks/use-fetched-options.test.tsx | 152 - .../sub-block/sub-block-password.test.tsx | 14 - .../components/sub-block/sub-block.test.ts | 261 -- .../editor/components/sub-block/utils.test.ts | 29 - .../panel/components/toolbar/toolbar.test.tsx | 224 -- .../components/terminal-row-button.test.tsx | 110 - .../components/terminal/utils.test.ts | 479 +-- .../wand-prompt-bar/wand-prompt-bar.test.tsx | 103 - ...use-accessible-reference-prefixes.test.tsx | 67 - .../hooks/use-execution-handoff.test.ts | 71 - .../hooks/use-node-utilities.test.tsx | 22 - .../hooks/use-workflow-execution.test.tsx | 386 --- .../utils/block-ring-utils.test.ts | 44 - .../utils/node-position-utils.test.ts | 60 - .../[workflowId]/utils/run-from-block.test.ts | 21 - .../utils/workflow-attachment-upload.test.ts | 67 - .../utils/workflow-canvas-helpers.test.ts | 213 +- ...=> workflow-execution-utils-store.test.ts} | 2 - .../utils/workflow-execution-utils.test.ts | 815 +---- .../preview-workflow-cursor.test.tsx | 207 -- .../chat-navigation-link.test.tsx | 293 -- .../collapsed-sidebar-menu-rename.test.tsx | 401 --- .../collapsed-sidebar-menu.test.tsx | 152 - .../rail-resource-flyout.test.tsx | 133 - .../command-chrome/command-chrome.test.tsx | 123 - .../command-items/command-items.test.tsx | 79 - .../search-groups/search-groups.test.tsx | 154 - .../search-modal/search-modal.test.tsx | 933 ----- .../components/search-modal/utils.test.ts | 291 -- .../settings-query-warmers.test.ts | 104 - .../settings-sidebar.test.tsx | 335 -- .../sidebar-footer/sidebar-footer.test.tsx | 307 -- .../status-notice/status-notice.test.tsx | 105 - .../context-menu/context-menu.test.tsx | 315 -- .../view-invitations-modal.test.tsx | 75 - .../workspace-header.test.tsx | 426 --- .../sidebar/hooks/use-drag-drop.test.tsx | 379 -- .../hooks/use-flyout-inline-rename.test.tsx | 109 - .../sidebar/hooks/use-sidebar-resize.test.tsx | 73 - .../hooks/use-workflow-operations.test.tsx | 88 - .../hooks/use-workspace-management.test.tsx | 146 +- apps/sim/app/workspace/page.test.tsx | 96 - .../providers/socket-join-controller.test.ts | 73 - .../providers/socket-join-target.test.ts | 42 +- .../providers/socket-presence-merge.test.ts | 22 - .../async-execution-correlation.test.ts | 62 - .../async-preprocessing-correlation.test.ts | 198 -- .../sim/background/cleanup-logs-files.test.ts | 5 - apps/sim/background/cleanup-logs.test.ts | 13 +- .../background/cleanup-oauth-tokens.test.ts | 26 +- .../background/cleanup-soft-deletes.test.ts | 78 - ...nup-table-row-ttl-predicate.integration.ts | 141 + ...s => cleanup-table-row-ttl.integration.ts} | 35 +- .../background/cleanup-table-row-ttl.test.ts | 135 +- apps/sim/background/cleanup-tasks.test.ts | 10 - .../background/dispatch-cancel-guard.test.ts | 21 - .../background/drain-governed-subject.test.ts | 4 - .../enrichment-capability-subject.test.ts | 4 - ...knowledge-connector-directory-sync.test.ts | 36 +- .../knowledge-connector-member-sync.test.ts | 26 - .../knowledge-connector-sync.test.ts | 109 - .../background/knowledge-processing.test.ts | 125 - apps/sim/background/process-outbox.test.ts | 30 - .../quickbooks-webhook-ingress.test.ts | 21 - apps/sim/background/resume-execution.test.ts | 12 - .../resume-governed-subject.test.ts | 21 - .../sim/background/schedule-execution.test.ts | 18 - .../background/table-run-dispatcher.test.ts | 26 - apps/sim/background/webhook-execution.test.ts | 154 +- .../workflow-column-execution.test.ts | 66 - .../workflow-group-governed-subject.test.ts | 11 - .../workspace-file-search-dispatch.test.ts | 45 - .../workspace-file-search-index.test.ts | 82 - apps/sim/blocks/accent.test.ts | 71 - apps/sim/blocks/agent-evaluation.test.ts | 104 +- apps/sim/blocks/blocks.test.ts | 996 +----- apps/sim/blocks/blocks/agent.test.ts | 49 +- apps/sim/blocks/blocks/airtable.test.ts | 14 - apps/sim/blocks/blocks/api.test.ts | 24 - apps/sim/blocks/blocks/ashby.test.ts | 255 -- apps/sim/blocks/blocks/azure_devops.test.ts | 162 - .../blocks/binary-download-versioning.test.ts | 100 - apps/sim/blocks/blocks/calendly.test.ts | 105 - apps/sim/blocks/blocks/cbinsights.test.ts | 102 - apps/sim/blocks/blocks/cloudflare.test.ts | 201 -- apps/sim/blocks/blocks/cloudtrail.test.ts | 12 - apps/sim/blocks/blocks/confluence.test.ts | 4 - .../blocks/blocks/credential-group.test.ts | 31 - apps/sim/blocks/blocks/credential.test.ts | 44 - apps/sim/blocks/blocks/crowdstrike.test.ts | 111 - apps/sim/blocks/blocks/crunchbase.test.ts | 85 - apps/sim/blocks/blocks/datadog.test.ts | 109 - apps/sim/blocks/blocks/embeddings.test.ts | 270 +- apps/sim/blocks/blocks/file-folders.test.ts | 402 +-- apps/sim/blocks/blocks/file.test.ts | 177 - apps/sim/blocks/blocks/function.test.ts | 33 - apps/sim/blocks/blocks/gitlab.test.ts | 201 -- apps/sim/blocks/blocks/google_drive.test.ts | 19 - apps/sim/blocks/blocks/granola.test.ts | 161 - apps/sim/blocks/blocks/harmonic.test.ts | 259 +- apps/sim/blocks/blocks/iam.test.ts | 24 - apps/sim/blocks/blocks/instagram.test.ts | 33 - .../blocks/jira_service_management.test.ts | 31 - apps/sim/blocks/blocks/jotform.test.ts | 176 - apps/sim/blocks/blocks/jupyter.test.ts | 35 - apps/sim/blocks/blocks/knowledge.test.ts | 52 - apps/sim/blocks/blocks/lambda.test.ts | 211 +- apps/sim/blocks/blocks/logs.test.ts | 40 - apps/sim/blocks/blocks/managed_agent.test.ts | 132 - .../blocks/blocks/manageengine-sdp.test.ts | 58 +- apps/sim/blocks/blocks/mem0.test.ts | 20 - apps/sim/blocks/blocks/microsoft_ad.test.ts | 113 - .../blocks/microsoft_dynamics_365.test.ts | 188 +- apps/sim/blocks/blocks/mintlify.test.ts | 88 - apps/sim/blocks/blocks/modal.test.ts | 84 - apps/sim/blocks/blocks/mothership.test.ts | 39 - apps/sim/blocks/blocks/mssql.test.ts | 118 - apps/sim/blocks/blocks/note.test.ts | 22 +- apps/sim/blocks/blocks/okta.test.ts | 118 - apps/sim/blocks/blocks/outlook.test.ts | 73 - apps/sim/blocks/blocks/pi.test.ts | 331 -- apps/sim/blocks/blocks/quiver.test.ts | 34 - apps/sim/blocks/blocks/servicenow.test.ts | 63 - apps/sim/blocks/blocks/sftp.test.ts | 59 - .../sim/blocks/blocks/slack-resources.test.ts | 182 +- .../blocks/slack-workflow-operations.test.ts | 116 - apps/sim/blocks/blocks/slack.test.ts | 108 +- apps/sim/blocks/blocks/slack.ts | 4 - apps/sim/blocks/blocks/splunk.test.ts | 181 +- apps/sim/blocks/blocks/sqs.test.ts | 28 - apps/sim/blocks/blocks/ssh.test.ts | 39 - apps/sim/blocks/blocks/supabase.test.ts | 26 - apps/sim/blocks/blocks/table.test.ts | 3 - apps/sim/blocks/blocks/table_v2.test.ts | 52 - apps/sim/blocks/blocks/telegram.test.ts | 30 - apps/sim/blocks/blocks/tiktok.test.ts | 19 - apps/sim/blocks/brand-icon.test.tsx | 54 - apps/sim/blocks/custom/build-config.test.ts | 135 - .../blocks/custom/custom-block-icon.test.tsx | 49 - apps/sim/blocks/custom/overlay.test.ts | 41 - apps/sim/blocks/icon-color.test.ts | 43 - .../sim/blocks/integration-popularity.test.ts | 30 - apps/sim/blocks/password-masking.test.ts | 3 - apps/sim/blocks/pi-api-key-condition.test.ts | 16 - apps/sim/blocks/pi-model-options.test.ts | 11 - apps/sim/blocks/provider-credentials.test.ts | 34 - apps/sim/blocks/registry-lookup.test.ts | 8 - .../blocks/selector-backed-subblocks.test.ts | 21 - apps/sim/blocks/utils.test.ts | 202 -- apps/sim/blocks/visibility/client.test.ts | 36 +- .../blocks/visibility/registry-inert.test.ts | 7 - apps/sim/blocks/visibility/visibility.test.ts | 31 - .../agent-stream/agent-stream-chrome.test.tsx | 286 -- .../agent-stream/tool-call-lifecycle.test.ts | 29 - .../browser-import-dialog.test.tsx | 215 -- .../browser-import/import-modal.test.tsx | 212 -- .../emails/_styles/base.tokens.test.ts | 133 - apps/sim/components/emails/boundary.test.ts | 100 - .../emails/render-notifications.test.ts | 174 - .../components/icons/document-icons.test.ts | 22 - .../mcp/mcp-mode-switching.test.tsx | 19 - .../sim/components/mcp/mcp-selectors.test.tsx | 218 -- .../mcp/operation-policy-editor.test.tsx | 47 +- .../sim/components/mcp/target-options.test.ts | 7 - .../pii/custom-patterns-editor.test.tsx | 84 - .../secrets/secrets-editor.test.tsx | 23 - .../components/settings/navigation.test.ts | 444 --- .../prefetch-standalone-general.test.ts | 96 - .../settings/settings-header-order.test.ts | 141 - .../settings/settings-header-shell.test.tsx | 136 +- .../settings/settings-intent-link.test.tsx | 195 -- .../settings/settings-sidebar.test.tsx | 153 - ...standalone-settings-shell-seeding.test.tsx | 32 - .../standalone-settings-shell.test.ts | 33 - .../ui/generated-password-input.test.tsx | 43 - .../components/ui/thinking-loader.test.tsx | 143 - .../workspace-context-menu.test.tsx | 114 - apps/sim/connectors/all-selection.test.ts | 1 - apps/sim/connectors/asana/asana.test.ts | 358 +- apps/sim/connectors/ashby/ashby.test.ts | 24 - apps/sim/connectors/auth.test.ts | 30 +- .../connectors/bitbucket/bitbucket.test.ts | 510 +-- apps/sim/connectors/box/box.test.ts | 9 +- .../connectors/circleback/circleback.test.ts | 90 - apps/sim/connectors/coda/README.md | 4 +- apps/sim/connectors/coda/coda.live.test.ts | 3 +- apps/sim/connectors/coda/coda.test.ts | 139 +- apps/sim/connectors/coda/permissions.test.ts | 51 - .../connectors/confluence/attachments.test.ts | 297 +- .../connectors/confluence/bulk-spaces.test.ts | 95 - .../connectors/confluence/confluence.test.ts | 783 +---- .../connectors/confluence/permissions.test.ts | 575 --- .../connectors/confluence/preflight.test.ts | 77 - .../connectors/databricks/databricks.test.ts | 320 +- .../connectors/fireflies/fireflies.test.ts | 480 --- apps/sim/connectors/github/github.test.ts | 531 --- apps/sim/connectors/github/pacing.test.ts | 35 - apps/sim/connectors/github/rate-limits.md | 2 +- apps/sim/connectors/github/request.test.ts | 43 - apps/sim/connectors/gitlab/gitlab.test.ts | 186 - .../gitlab/permission-config/fields.test.tsx | 240 -- .../gitlab/permission-config/parser.test.ts | 1 - .../gitlab/permission-policy.test.ts | 2 - .../sim/connectors/gitlab/permissions.test.ts | 23 - .../connectors/gmail/company-crawl.test.ts | 283 -- apps/sim/connectors/gmail/gmail.test.ts | 638 ---- apps/sim/connectors/gmail/mailbox.test.ts | 33 +- .../google-calendar/company-crawl.test.ts | 267 -- .../google-calendar/google-calendar.test.ts | 258 -- .../google-chat/google-chat.test.ts | 177 - .../google-docs/google-docs.test.ts | 441 +-- .../google-drive/company-crawl.test.ts | 287 +- .../connectors/google-drive/directory.test.ts | 77 - .../google-drive/google-drive.test.ts | 629 +--- .../connectors/google-drive/shortcuts.test.ts | 186 - .../google-drive/workspace-drives.test.ts | 47 - .../google-meet/google-meet.test.ts | 50 - .../google-sheets/google-sheets.test.ts | 237 +- .../google-workspace/api-errors.test.ts | 114 - .../google-workspace/company-crawl.test.ts | 407 +-- apps/sim/connectors/granola/granola.test.ts | 68 - .../connectors/greenhouse/greenhouse.test.ts | 134 - .../connectors/incidentio/incidentio.test.ts | 183 +- apps/sim/connectors/intercom/intercom.test.ts | 165 - apps/sim/connectors/jira/jira.test.ts | 318 +- apps/sim/connectors/jsm/jsm.test.ts | 31 +- apps/sim/connectors/linear/linear.test.ts | 4 - apps/sim/connectors/listing-failures.test.ts | 14 - .../microsoft-teams/microsoft-teams.test.ts | 47 +- apps/sim/connectors/monday/monday.test.ts | 159 +- apps/sim/connectors/notion/notion.test.ts | 601 ---- apps/sim/connectors/onedrive/onedrive.test.ts | 287 +- apps/sim/connectors/outlook/outlook.test.ts | 258 -- .../permission-scoped-listing.test.ts | 58 - apps/sim/connectors/reddit/reddit.test.ts | 193 -- .../connectors/salesforce/salesforce.test.ts | 46 +- apps/sim/connectors/sentry/sentry.test.ts | 114 - .../connectors/servicenow/servicenow.test.ts | 186 +- apps/sim/connectors/sftp/sftp.test.ts | 35 - .../connectors/sharepoint/sharepoint.test.ts | 446 +-- apps/sim/connectors/slack/slack.test.ts | 309 -- apps/sim/connectors/typeform/typeform.test.ts | 129 - apps/sim/connectors/utils.test.ts | 1436 -------- apps/sim/connectors/webflow/webflow.test.ts | 134 +- .../connectors/wordpress/wordpress.test.ts | 149 - apps/sim/connectors/workday/workday.test.ts | 284 +- apps/sim/connectors/youtube/youtube.test.ts | 247 +- apps/sim/connectors/zendesk/zendesk.test.ts | 255 +- apps/sim/connectors/zoom/zoom.test.ts | 64 - .../components/access-control-layout.test.tsx | 100 - .../components/access-control.test.tsx | 180 - .../hooks/permission-groups.test.tsx | 47 +- .../utils/integration-allowlist-rows.test.ts | 2 - .../utils/permission-check.test.ts | 401 --- .../utils/permission-gate-subject.test.ts | 94 - apps/sim/ee/access-requests/README.md | 4 +- .../components/access-request-review.test.tsx | 181 - .../access-requests-settings.test.tsx | 311 -- .../organization-access-requests.test.tsx | 212 -- .../permission-access-boundary.test.tsx | 30 - .../components/policy-changes.test.ts | 69 - .../components/request-access-action.test.tsx | 242 -- .../components/search-params.test.ts | 26 - .../lib/application/authorization.test.ts | 64 - .../application/authorized-use-case.test.ts | 67 - ...w.postgres.test.ts => flow.integration.ts} | 17 +- .../lib/application/requests.test.ts | 250 +- .../lib/application/review.test.ts | 153 +- .../ee/access-requests/lib/catalog.test.ts | 48 - ...postgres.test.ts => impact.integration.ts} | 17 +- .../ee/access-requests/lib/navigation.test.ts | 129 - .../access-requests/lib/notifications.test.ts | 156 - ...gres.test.ts => repository.integration.ts} | 15 +- .../ee/access-requests/lib/schemas.test.ts | 66 - .../ee/access-requests/lib/settings.test.ts | 9 - .../ee/access-requests/lib/targets.test.ts | 53 - .../audit-logs/components/audit-logs.test.ts | 51 - .../ee/audit-logs/hooks/audit-logs.test.tsx | 196 -- .../credential-group-access.test.tsx | 377 -- ...edential-group-add-resource-modal.test.tsx | 174 - ...organization-account-invite-modal.test.tsx | 244 -- .../organization-account-people.test.tsx | 497 --- .../organization-account-providers.test.tsx | 314 -- ...nization-account-workspace-access.test.tsx | 90 - ...rganization-workspace-grant-modal.test.tsx | 87 - .../slack-managed-users-access.test.tsx | 758 ---- .../slack-managed-users-modal.test.ts | 21 - .../components/custom-blocks.test.tsx | 99 - .../components/data-drain-create.test.tsx | 315 -- .../components/data-drain-detail.test.tsx | 351 -- .../components/usage-consumers.test.ts | 15 - .../components/usage-consumers.tsx | 2 - .../components/usage-credits.test.tsx | 60 - .../hooks/use-usage-window.test.ts | 16 - apps/sim/ee/scim/TESTING.md | 15 +- .../ee/scim/components/scim-section.test.tsx | 143 - .../application/groups/manage-groups.test.ts | 14 - .../users/deprovision-user.test.ts | 43 - .../application/users/provision-user.test.ts | 253 -- .../lib/application/users/update-user.test.ts | 58 - apps/sim/ee/scim/lib/authenticate.test.ts | 35 - apps/sim/ee/scim/lib/entitlement.test.ts | 11 - .../lib/identity/account-identity.test.ts | 4 - .../ee/scim/lib/identity/resolve-user.test.ts | 22 +- ...t.ts => managed-membership.integration.ts} | 8 +- .../ee/scim/lib/managed-membership.test.ts | 24 - .../ee/scim/lib/projection/auto-map.test.ts | 11 - .../sim/ee/scim/lib/projection/grants.test.ts | 38 - .../lib/projection/reconcile-user.test.ts | 52 - .../ee/scim/lib/protocol/canonical.test.ts | 42 +- apps/sim/ee/scim/lib/protocol/filter.test.ts | 39 - .../ee/scim/lib/protocol/group-patch.test.ts | 27 - .../ee/scim/lib/protocol/resources.test.ts | 75 - .../ee/scim/lib/protocol/user-patch.test.ts | 37 - apps/sim/ee/scim/lib/reconcile/job.test.ts | 124 - .../session-policy-settings.test.tsx | 153 - .../components/require-sso-section.test.tsx | 159 - apps/sim/ee/sso/components/sso-form.test.tsx | 202 +- .../ee/sso/components/sso-settings.test.tsx | 505 --- apps/sim/ee/sso/hooks/sso.test.tsx | 122 - .../components/branding-provider.test.tsx | 101 - .../whitelabeling-settings.test.tsx | 94 - .../authorized-fork-use-case.test.ts | 40 +- .../application/content-outbox.test.ts | 4 - .../application/discovery.test.ts | 4 - .../application/preview-sync.test.ts | 115 - .../application/recovery-and-mappings.test.ts | 14 - ...stgres.test.ts => revision.integration.ts} | 17 +- .../application/revision.test.ts | 19 - .../fork-activity-panel.test.tsx | 289 -- .../fork-file-tree/fork-file-tree.test.ts | 3 - .../fork-sync/cleared-refs-list.test.ts | 70 - .../fork-sync/copy-reconciliation.test.ts | 82 - .../custom-block-input-control.test.ts | 26 - .../fork-sync/dependent-field-noun.test.ts | 34 - .../fork-sync/dependent-value.test.ts | 222 -- .../fork-sync/trigger-choices.test.ts | 31 - .../fork-sync/use-fork-sync.test.tsx | 197 -- .../lib/background-work/store.test.ts | 140 +- .../lib/copy/cleanup-failed.test.ts | 97 - .../lib/copy/content-copy-runner.test.ts | 84 - .../lib/copy/copy-chats.test.ts | 31 - .../lib/copy/copy-files.test.ts | 188 - .../lib/copy/copy-resources.test.ts | 926 +---- .../lib/copy/copy-workflows.test.ts | 234 -- .../lib/copy/progress.test.ts | 3 - .../lib/copy/storage-quota.test.ts | 85 - .../lib/copy/workflow-id-map.test.ts | 8 - .../lib/copy/workflow-mcp-attachments.test.ts | 31 - .../workspace-forking/lib/create-fork.test.ts | 54 - .../lib/lineage/unlink.test.ts | 16 - .../lib/mapping/block-map-store.test.ts | 7 - .../lib/mapping/cascade.test.ts | 43 - .../lib/mapping/dependent-value-store.test.ts | 67 - .../lib/mapping/mapping-service.test.ts | 250 -- .../lib/mapping/mapping-store.test.ts | 47 - .../lib/mapping/resources.test.ts | 59 - .../lib/promote/cleared-refs.test.ts | 636 ---- .../lib/promote/copy-unmapped.test.ts | 265 -- .../lib/promote/promote-plan.test.ts | 3 - .../lib/promote/promote.test.ts | 419 --- .../lib/promote/rollback.test.ts | 68 - .../lib/promote/sync-blockers.test.ts | 37 - .../lib/promote/trigger-urls.test.ts | 67 - .../lib/remap/block-identity.test.ts | 26 - .../lib/remap/remap-block-type.test.ts | 63 - .../lib/remap/remap-content-refs.test.ts | 57 - .../lib/remap/remap-table-groups.test.ts | 24 - .../company-domain/company-domain.test.ts | 44 - .../company-info/company-info.test.ts | 67 - .../email-verification.test.ts | 59 - .../linkedin-profile/linkedin-profile.test.ts | 53 - .../phone-number/phone-number.test.ts | 74 - apps/sim/enrichments/run.test.ts | 89 +- .../enrichments/work-email/work-email.test.ts | 121 - apps/sim/executor/dag/builder.test.ts | 45 - .../executor/dag/construction/edges.test.ts | 823 +---- .../executor/dag/construction/nodes.test.ts | 3 - .../execution/block-executor.retry.test.ts | 56 +- .../executor/execution/block-executor.test.ts | 730 +--- .../executor/execution/block-retry.test.ts | 53 - .../executor/execution/edge-manager.test.ts | 1718 +-------- apps/sim/executor/execution/engine.test.ts | 841 +---- .../execution/executor.run-from-block.test.ts | 3 - apps/sim/executor/execution/executor.test.ts | 224 +- .../executor/execution/failure-trace.test.ts | 3 - .../execution/resume-execution-order.test.ts | 15 - .../execution/snapshot-serializer.test.ts | 73 - apps/sim/executor/execution/snapshot.test.ts | 36 - apps/sim/executor/execution/state.test.ts | 29 - .../handlers/agent/agent-handler.test.ts | 3077 +---------------- ....test.ts => memory-harness.integration.ts} | 19 +- .../handlers/agent/memory.durability.test.ts | 191 +- .../executor/handlers/agent/memory.test.ts | 423 --- .../handlers/agent/skills-resolver.test.ts | 80 +- .../executor/handlers/api/api-handler.test.ts | 124 - .../condition/condition-handler.test.ts | 908 +---- .../credential-group-handler.test.ts | 1 - .../credential/credential-handler.test.ts | 111 - .../evaluator/evaluator-handler.test.ts | 455 --- .../function/function-handler.test.ts | 248 -- .../handlers/generic/file-write.test.ts | 4 - .../handlers/generic/generic-handler.test.ts | 333 -- .../generic/keyed-invocation-identity.test.ts | 8 +- .../notification-params.test.ts | 10 - .../mothership/mothership-handler.test.ts | 936 +---- .../pi/cloud/authoring/backend.test.ts | 599 ---- .../handlers/pi/cloud/babysit/backend.test.ts | 509 +-- .../handlers/pi/cloud/babysit/github.test.ts | 202 +- .../handlers/pi/cloud/babysit/round.test.ts | 3 - .../handlers/pi/cloud/github-pr.test.ts | 40 - .../handlers/pi/cloud/plan/backend.test.ts | 11 - .../handlers/pi/cloud/review/backend.test.ts | 269 -- .../handlers/pi/cloud/review/tools.test.ts | 73 - .../executor/handlers/pi/cloud/shared.test.ts | 29 +- .../executor/handlers/pi/core/events.test.ts | 77 - .../executor/handlers/pi/core/keys.test.ts | 154 +- .../executor/handlers/pi/core/pi-sdk.test.ts | 3 - .../handlers/pi/core/redaction.test.ts | 3 - .../handlers/pi/local/backend.test.ts | 11 - .../handlers/pi/local/sim-tools.test.ts | 214 -- .../handlers/pi/local/ssh-tools.test.ts | 43 +- .../executor/handlers/pi/pi-handler.test.ts | 520 --- .../pi/search/extension-source.test.ts | 213 -- .../handlers/pi/search/normalize.test.ts | 101 - .../handlers/pi/search/parity.test.ts | 2 - .../executor/handlers/pi/search/tool.test.ts | 208 -- .../handlers/router/router-handler.test.ts | 570 --- .../handlers/shared/response-format.test.ts | 9 +- .../handlers/trigger/trigger-handler.test.ts | 314 -- .../variables/variables-handler.test.ts | 45 - .../handlers/wait/wait-handler.test.ts | 161 - .../workflow/custom-block-tool-runner.test.ts | 70 +- .../workflow/workflow-handler.test.ts | 1067 +----- .../workflow/workflow-tool-runner.test.ts | 22 - apps/sim/executor/orchestrators/loop.test.ts | 170 - apps/sim/executor/orchestrators/node.test.ts | 208 -- .../executor/orchestrators/parallel.test.ts | 205 -- apps/sim/executor/utils.test.ts | 199 +- apps/sim/executor/utils/block-data.test.ts | 12 - .../executor/utils/block-reference.test.ts | 135 - .../utils/code-secret-references.test.ts | 3 - .../executor/utils/credential-token.test.ts | 83 - apps/sim/executor/utils/delegation.test.ts | 11 - .../executor/utils/errors.classify.test.ts | 58 - apps/sim/executor/utils/errors.test.ts | 23 - .../utils/file-tool-processor.aliases.test.ts | 3 - .../utils/file-tool-processor.context.test.ts | 3 - .../utils/file-tool-processor.test.ts | 59 - apps/sim/executor/utils/http.test.ts | 33 - .../executor/utils/iteration-context.test.ts | 232 -- .../utils/model-fallback-request.test.ts | 22 - apps/sim/executor/utils/output-filter.test.ts | 20 - .../executor/utils/parallel-expansion.test.ts | 305 -- .../reference-validation.differential.test.ts | 2 - ...resolved-secret-content-projection.test.ts | 251 -- .../resolved-secret-match-policy.test.ts | 3 - .../utils/resolved-secret-matcher.test.ts | 40 - ...resolved-secret-projection-refusal.test.ts | 153 +- .../resolved-secret-trace-registry.test.ts | 1206 +------ .../utils/run-from-block-overlay.test.ts | 16 - .../sim/executor/utils/run-from-block.test.ts | 900 +---- apps/sim/executor/utils/start-block.test.ts | 448 --- .../utils/subflow-node-id-codec.test.ts | 47 - apps/sim/executor/utils/subflow-utils.test.ts | 39 - .../utils/tool-binding-labels.test.ts | 107 - .../executor/utils/vertex-credential.test.ts | 33 - apps/sim/executor/variables/resolver.test.ts | 952 +---- .../variables/resolvers/block.test.ts | 725 ---- .../executor/variables/resolvers/env.test.ts | 136 - .../executor/variables/resolvers/loop.test.ts | 247 -- .../variables/resolvers/parallel.test.ts | 313 -- .../variables/resolvers/reference.test.ts | 146 - .../variables/resolvers/workflow.test.ts | 177 - .../hooks/mcp/use-mcp-oauth-popup.test.tsx | 109 - .../hooks/queries/access-requests.test.tsx | 321 -- apps/sim/hooks/queries/admin-users.test.ts | 68 - apps/sim/hooks/queries/api-keys.test.ts | 50 - apps/sim/hooks/queries/chats.test.tsx | 142 - .../hooks/queries/credential-groups.test.tsx | 31 - apps/sim/hooks/queries/credentials.test.ts | 98 - apps/sim/hooks/queries/deployments.test.ts | 75 - .../queries/dynamic-subblock-options.test.tsx | 147 - apps/sim/hooks/queries/environment.test.tsx | 38 - apps/sim/hooks/queries/folders.test.ts | 32 +- .../general-settings-timezone.test.tsx | 34 - .../hooks/queries/general-settings.test.tsx | 120 - .../github-search-installations.test.tsx | 114 - .../queries/github-search-setup.test.tsx | 54 - .../kb/connector-documents-progress.test.tsx | 278 -- .../queries/kb/connectors-cache.test.tsx | 329 +- apps/sim/hooks/queries/kb/connectors.test.ts | 360 -- apps/sim/hooks/queries/kb/knowledge.test.ts | 178 - .../kb/organization-search-overview.test.tsx | 133 - .../kb/search-source-progress.test.tsx | 222 -- apps/sim/hooks/queries/link-preview.test.tsx | 62 - apps/sim/hooks/queries/logs.test.tsx | 255 -- apps/sim/hooks/queries/mcp.test.tsx | 219 +- .../hooks/queries/mothership-chats.test.ts | 341 +- .../navigation-request-gating.test.tsx | 121 - .../sim/hooks/queries/oauth-provider.test.tsx | 102 +- .../microsoft-dataverse-connections.test.ts | 53 - .../queries/oauth/oauth-connections.test.tsx | 47 - .../queries/oauth/oauth-credentials.test.ts | 116 +- .../queries/organization-accounts.test.tsx | 361 +- apps/sim/hooks/queries/organization.test.tsx | 129 +- .../hooks/queries/resume-execution.test.ts | 9 - .../scoped-credentials-mutations.test.ts | 33 +- .../hooks/queries/scoped-credentials.test.ts | 29 - apps/sim/hooks/queries/selectors.test.tsx | 469 +-- apps/sim/hooks/queries/session.test.ts | 23 - apps/sim/hooks/queries/slack-search.test.tsx | 115 - apps/sim/hooks/queries/tables.test.ts | 303 -- apps/sim/hooks/queries/unsubscribe.test.tsx | 206 -- .../utils/fetch-workflow-envelope.test.ts | 51 - .../utils/find-workspace-file-by-src.test.ts | 19 - .../hooks/queries/utils/folder-tree.test.ts | 148 - .../utils/invalidate-workflow-lists.test.ts | 25 - .../queries/utils/optimistic-mutation.test.ts | 24 - .../utils/prefetch-query-on-intent.test.ts | 59 - .../reset-organization-search-access.test.ts | 1 - .../hooks/queries/utils/selector-keys.test.ts | 36 +- .../utils/table-rows-pagination.test.ts | 58 - .../queries/utils/workflow-cache.test.ts | 87 - apps/sim/hooks/queries/voice.test.tsx | 143 - .../queries/workflow-mcp-servers.test.tsx | 33 - .../queries/workflow-search-replace.test.ts | 59 +- .../queries/workspace-file-folders.test.ts | 55 - .../hooks/queries/workspace-files.test.tsx | 380 -- .../sim/hooks/queries/workspace-usage.test.ts | 83 - apps/sim/hooks/queries/workspace.test.tsx | 170 +- apps/sim/hooks/use-autosave.test.tsx | 667 ---- apps/sim/hooks/use-canvas-viewport.test.tsx | 87 - apps/sim/hooks/use-chat-input-focus.test.tsx | 108 - .../hooks/use-desktop-update-state.test.tsx | 19 - apps/sim/hooks/use-execution-stream.test.tsx | 65 - ...e-github-installation-setup-cache.test.tsx | 209 -- .../use-github-installation-setup.test.tsx | 94 - apps/sim/hooks/use-member-enrollment.test.tsx | 256 -- ...-mothership-chat-events-lifecycle.test.tsx | 23 - .../hooks/use-mothership-chat-events.test.ts | 280 -- .../sim/hooks/use-oauth-return-hooks.test.tsx | 173 - apps/sim/hooks/use-oauth-return.test.ts | 96 - apps/sim/hooks/use-permission-config.test.tsx | 43 - .../use-personal-source-account.test.tsx | 26 - .../use-resource-list-preferences.test.tsx | 200 -- apps/sim/hooks/use-scroll-anchor.test.ts | 92 - .../hooks/use-search-filter-value.test.tsx | 38 - ...use-search-integration-connection.test.tsx | 1 - .../sim/hooks/use-settings-navigation.test.ts | 79 - apps/sim/hooks/use-smooth-text.test.tsx | 213 -- apps/sim/hooks/use-stable-flag.test.ts | 162 - apps/sim/hooks/use-table-undo.test.ts | 41 - apps/sim/hooks/use-voice-input.test.tsx | 50 - apps/sim/lib/a2a/client.test.ts | 146 +- .../lib/admin/dashboard-credit-grant.test.ts | 5 - .../lib/admin/dashboard-organizations.test.ts | 169 - .../lib/admin/external-collaborators.test.ts | 19 - .../lib/admin/invitation-operation.test.ts | 4 - apps/sim/lib/admin/member-operation.test.ts | 160 - .../lib/admin/subscription-lifecycle.test.ts | 157 - apps/sim/lib/analytics/google.test.ts | 27 - .../organization-byok-keys.test.ts | 165 +- .../application/personal-api-keys.test.ts | 63 +- .../application/workspace-api-keys.test.ts | 54 +- .../application/workspace-byok-keys.test.ts | 24 +- apps/sim/lib/api-key/auth.test.ts | 196 +- apps/sim/lib/api-key/byok-entitlement.test.ts | 22 - apps/sim/lib/api-key/byok.test.ts | 353 +- apps/sim/lib/api-key/crypto.test.ts | 46 +- apps/sim/lib/api-key/service.test.ts | 46 - .../read-v2-api-capabilities.test.ts | 45 - apps/sim/lib/api/client-id.test.ts | 9 - apps/sim/lib/api/client/request.test.ts | 69 - .../lib/api/contracts/access-requests.test.ts | 149 - .../api/contracts/chat-output-config.test.ts | 35 - .../contracts/chats.include-thinking.test.ts | 86 - .../lib/api/contracts/chats.password.test.ts | 36 - apps/sim/lib/api/contracts/cleanup.test.ts | 22 - .../api/contracts/credential-groups.test.ts | 315 +- .../sim/lib/api/contracts/credentials.test.ts | 137 - .../lib/api/contracts/data-retention.test.ts | 149 +- .../sim/lib/api/contracts/deployments.test.ts | 41 - apps/sim/lib/api/contracts/folders.test.ts | 35 - apps/sim/lib/api/contracts/hotspots.test.ts | 58 - .../lib/api/contracts/knowledge/base.test.ts | 49 - .../contracts/knowledge/connectors.test.ts | 112 - .../api/contracts/knowledge/documents.test.ts | 190 +- .../knowledge/github-installations.test.ts | 48 - .../lib/api/contracts/knowledge/mcp.test.ts | 109 - .../knowledge/personal-source-setup.test.ts | 11 - .../api/contracts/knowledge/search.test.ts | 64 - .../api/contracts/knowledge/shared.test.ts | 26 - apps/sim/lib/api/contracts/memory.test.ts | 15 - .../mothership-assistant-tools.test.ts | 60 - .../api/contracts/mothership-chats.test.ts | 79 - .../mothership-management-tools.test.ts | 37 - .../api/contracts/oauth-connections.test.ts | 38 - .../contracts/organization-accounts.test.ts | 95 - .../contracts/organization-activity.test.ts | 29 - .../api/contracts/organization-usage.test.ts | 50 +- .../api/contracts/permission-groups.test.ts | 112 +- apps/sim/lib/api/contracts/primitives.test.ts | 3 - .../contracts/public-shares.password.test.ts | 3 - apps/sim/lib/api/contracts/sandboxes.test.ts | 168 - .../api/contracts/secret-mount-policy.test.ts | 32 - .../lib/api/contracts/subscription.test.ts | 138 - .../api/contracts/tables-predicate.test.ts | 173 - apps/sim/lib/api/contracts/tables.test.ts | 14 - .../contracts/tools/aws/iam-shared.test.ts | 17 +- .../aws/identity-center-list-accounts.test.ts | 48 - apps/sim/lib/api/contracts/tools/jsm.test.ts | 115 - .../lib/api/contracts/usage-limits.test.ts | 48 - .../api/contracts/v1/admin/dashboard.test.ts | 149 - .../contracts/v1/admin/global-work.test.ts | 28 - .../contracts/v1/knowledge/tag-filter.test.ts | 62 - .../contracts/v2/__tests__/contract-sweep.ts | 2 +- .../v2/__tests__/cross-cutting.test.ts | 187 - .../v2/__tests__/document-tag-slots.test.ts | 38 - .../v2/__tests__/files-attribution.test.ts | 22 - .../v2/__tests__/files-recursive.test.ts | 47 - .../v2/__tests__/files-search-scope.test.ts | 40 - .../contracts/v2/__tests__/knowledge.test.ts | 283 -- .../v2/__tests__/list-pagination.test.ts | 163 +- .../contracts/v2/__tests__/logs-stats.test.ts | 66 - .../v2/__tests__/pagination-limit.test.ts | 106 +- .../v2/__tests__/permission-groups.test.ts | 14 - .../v2/__tests__/query-declaration.test.ts | 24 - .../v2/__tests__/run-accounting.test.ts | 59 - .../v2/__tests__/schema-introspection.test.ts | 75 - .../v2/__tests__/schema-introspection.ts | 14 +- .../api/contracts/v2/__tests__/shared.test.ts | 51 - .../api/contracts/v2/__tests__/tables.test.ts | 695 +--- .../transport-neutral-descriptions.test.ts | 118 - .../contracts/v2/__tests__/uploads.test.ts | 39 - .../v2/__tests__/workflow-agent-tools.test.ts | 232 -- .../workflow-graph-variables.test.ts | 3 - .../v2/__tests__/workflow-graph.test.ts | 21 - apps/sim/lib/api/contracts/v2/billing.test.ts | 20 +- .../lib/api/contracts/v2/credentials.test.ts | 91 +- .../lib/api/contracts/v2/error-codes.test.ts | 25 +- .../api/contracts/v2/knowledge-chunks.test.ts | 20 - .../v2/knowledge-search-bounds.test.ts | 106 - .../api/contracts/v2/log-cost-bounds.test.ts | 96 - .../lib/api/contracts/v2/log-status.test.ts | 26 - .../v2/openapi/head-not-safe.test.ts | 89 - .../v2/required-field-messages.test.ts | 213 -- apps/sim/lib/api/contracts/v2/run-id.test.ts | 27 - apps/sim/lib/api/contracts/v2/secrets.test.ts | 109 +- .../v2/workflow-deployment-requests.test.ts | 29 - .../contracts/v2/workflow-run-status.test.ts | 58 - .../api/contracts/v2/workspace-fork.test.ts | 79 - apps/sim/lib/api/contracts/workflows.test.ts | 78 +- .../lib/api/contracts/workspace-fork.test.ts | 251 -- apps/sim/lib/api/contracts/workspaces.test.ts | 32 - apps/sim/lib/api/cursor-binding.test.ts | 7 - apps/sim/lib/api/list-convention.test.ts | 15 - apps/sim/lib/api/list-keyset-paging.test.ts | 3 - apps/sim/lib/api/list-query.test.ts | 43 - apps/sim/lib/api/mcp/catalog.test.ts | 47 - apps/sim/lib/api/mcp/dispatch.test.ts | 44 - apps/sim/lib/api/mcp/host-routing.test.ts | 7 - apps/sim/lib/api/mcp/route-handler.test.ts | 67 - .../lib/api/server/blank-query-values.test.ts | 19 - .../lib/api/server/nul-byte-boundary.test.ts | 3 - .../api/server/routes/copilot-request.test.ts | 2 - .../routes/copilot-route-inventory.test.ts | 12 - .../lib/api/server/routes/definition.test.ts | 93 - .../routes/in-process-transport.test.ts | 3 - .../server/routes/internal-json-route.test.ts | 270 +- .../lib/api/server/routes/request-id.test.ts | 23 - .../routes/resource-concealment.test.ts | 3 - .../lib/api/server/routes/scim-route.test.ts | 19 - .../api/server/routes/v2-api-key-auth.test.ts | 5 - .../api/server/routes/v2-binary-route.test.ts | 50 - .../routes/v2-body-lifecycle-route.test.ts | 5 - .../server/routes/v2-error-envelope.test.ts | 38 - .../api/server/routes/v2-json-route.test.ts | 8 - apps/sim/lib/api/server/validation.test.ts | 21 - apps/sim/lib/atlassian/assistant.test.ts | 4 - apps/sim/lib/atlassian/discovery.test.ts | 76 - apps/sim/lib/audio/extractor.test.ts | 42 - .../application/audit-log-use-cases.test.ts | 74 - apps/sim/lib/audit-logs/authorization.test.ts | 22 +- apps/sim/lib/audit-logs/query.test.ts | 73 +- apps/sim/lib/auth/access-control.test.ts | 4 - apps/sim/lib/auth/ban.test.ts | 5 - apps/sim/lib/auth/better-auth-error.test.ts | 9 +- apps/sim/lib/auth/connector-email.test.ts | 40 - .../lib/auth/connectors/managed-oauth.test.ts | 3 - apps/sim/lib/auth/constants.test.ts | 3 - apps/sim/lib/auth/credential-access.test.ts | 4 - apps/sim/lib/auth/hybrid.test.ts | 5 - apps/sim/lib/auth/internal-delegation.test.ts | 4 - apps/sim/lib/auth/internal.test.ts | 4 - apps/sim/lib/auth/oauth-access-token.test.ts | 4 - .../auth/oauth-authorization-error.test.ts | 3 - apps/sim/lib/auth/oauth-principal.test.ts | 3 - .../lib/auth/oauth-protocol-request.test.ts | 3 - .../auth/oauth-provider-adapter-guard.test.ts | 4 - ...> oauth-provider-lifecycle.integration.ts} | 14 +- .../lib/auth/oauth-provider-metadata.test.ts | 16 +- .../auth/oauth-provider-registration.test.ts | 1 - apps/sim/lib/auth/oauth-provider.test.ts | 3 - apps/sim/lib/auth/oauth-resource.test.ts | 14 - ...t.ts => oauth-token-family.integration.ts} | 12 +- apps/sim/lib/auth/oauth-token-family.test.ts | 4 - apps/sim/lib/auth/principal.test.ts | 3 - apps/sim/lib/auth/security-policy.test.ts | 5 - apps/sim/lib/auth/session-hooks.test.ts | 4 - apps/sim/lib/auth/session-policy.test.ts | 10 - apps/sim/lib/auth/session-response.test.ts | 15 - apps/sim/lib/auth/sign-out.test.ts | 12 - ...est.ts => sim-auth-adapter.integration.ts} | 10 +- .../sim/lib/auth/sim-auth-adapter.sql.test.ts | 3 - apps/sim/lib/auth/sim-auth-adapter.test.ts | 5 - apps/sim/lib/auth/sso-policy.test.ts | 4 - apps/sim/lib/auth/sso-trust.test.ts | 2 - ....test.ts => admit-sso-user.integration.ts} | 9 +- .../sso/application/admit-sso-user.test.ts | 4 - .../application/set-primary-provider.test.ts | 4 - .../sso/application/sso-requirement.test.ts | 4 - .../lib/auth/sso/domain-verification.test.ts | 26 +- .../lib/auth/sso/idp-initiated-login.test.ts | 3 - ...est.ts => primary-provider.integration.ts} | 16 +- .../sim/lib/auth/sso/primary-provider.test.ts | 13 - .../sim/lib/auth/stripe-adapter-guard.test.ts | 4 - .../application/billing-use-cases.test.ts | 266 -- ...anization-billing-summary-use-case.test.ts | 19 - .../get-organization-billing-summary.test.ts | 4 - .../organization-settings-actor.test.ts | 7 +- .../organization-usage/event-cursor.test.ts | 15 - .../get-organization-activity.test.ts | 20 - .../get-organization-usage-overview.test.ts | 24 - .../organization-usage-use-cases.test.ts | 42 - .../billing/application/usage-limits.test.ts | 57 +- apps/sim/lib/billing/authorization.test.ts | 45 - .../calculations/usage-monitor.test.ts | 167 - .../usage-reservation-env.test.ts | 4 - .../usage-reservation.integration.ts | 105 + .../calculations/usage-reservation.test.ts | 168 +- .../lib/billing/checkout-admission.test.ts | 14 - .../lib/billing/cleanup-dispatcher.test.ts | 100 +- .../lib/billing/concurrency-defaults.test.ts | 12 - apps/sim/lib/billing/core/access.test.ts | 60 +- .../core/account-billing-snapshot.test.ts | 42 - .../billing/core/billing-attribution.test.ts | 304 -- apps/sim/lib/billing/core/billing.test.ts | 5 - .../billing/core/inbox-entitlement.test.ts | 22 - apps/sim/lib/billing/core/ledger-read.test.ts | 35 - .../billing/core/limit-notifications.test.ts | 17 - ...s => organization-activity.integration.ts} | 15 +- ...anization-usage-pagination.integration.ts} | 5 +- .../billing/core/organization-usage.test.ts | 3 - .../lib/billing/core/payer-context.test.ts | 72 - apps/sim/lib/billing/core/plan.test.ts | 30 - .../core/reporting-usage-cache.test.ts | 12 - .../sim/lib/billing/core/subscription.test.ts | 196 -- ...=> usage-analytics-queries.integration.ts} | 7 +- .../lib/billing/core/usage-analytics.test.ts | 107 - .../lib/billing/core/usage-gate-cache.test.ts | 3 - ...tgres.test.ts => usage-log.integration.ts} | 11 +- apps/sim/lib/billing/core/usage-log.test.ts | 158 - .../billing/core/usage-segment-cache.test.ts | 4 - apps/sim/lib/billing/core/usage.test.ts | 151 - .../lib/billing/core/workspace-access.test.ts | 14 - .../core/workspace-billing-authority.test.ts | 6 +- .../billing/core/workspace-usage-gate.test.ts | 5 - .../lib/billing/credits/conversion.test.ts | 3 - .../billing/credits/weekly-refresh.test.ts | 57 - apps/sim/lib/billing/cycle-close.test.ts | 135 - .../billing/enterprise-credit-limits.test.ts | 3 - .../sim/lib/billing/enterprise-outbox.test.ts | 186 - .../billing/enterprise-owner-claim.test.ts | 103 - .../billing/enterprise-provisioning.test.ts | 987 +----- .../execution-timeout-defaults.test.ts | 7 - apps/sim/lib/billing/max-tier-parity.test.ts | 33 +- apps/sim/lib/billing/on-demand.test.ts | 3 - apps/sim/lib/billing/organization.test.ts | 54 - .../organizations/create-organization.test.ts | 86 - .../billing/organizations/lock-order.test.ts | 58 - ...s.test.ts => member-limits.integration.ts} | 31 +- .../organizations/member-limits.test.ts | 56 - .../membership-external-removal.test.ts | 50 - .../membership-reconciliation.test.ts | 24 +- .../pause-pro-for-coverage.test.ts | 23 - .../organizations/provision-seat.test.ts | 72 - .../billing/organizations/seat-drift.test.ts | 25 - .../lib/billing/organizations/seats.test.ts | 62 - apps/sim/lib/billing/plan-helpers.test.ts | 28 - apps/sim/lib/billing/retention.test.ts | 92 - apps/sim/lib/billing/sandbox-pricing.test.ts | 3 - apps/sim/lib/billing/storage/context.test.ts | 9 +- apps/sim/lib/billing/storage/limits.test.ts | 78 +- .../storage/payer-transfer-callsites.test.ts | 71 - .../billing/storage/payer-transfer.test.ts | 88 +- apps/sim/lib/billing/storage/tracking.test.ts | 85 - .../lib/billing/subscriptions/utils.test.ts | 29 - .../sim/lib/billing/threshold-billing.test.ts | 138 - apps/sim/lib/billing/types/index.test.ts | 17 - apps/sim/lib/billing/upgrade-reasons.test.ts | 48 - apps/sim/lib/billing/usage-sources.test.ts | 15 - .../validation/seat-management.test.ts | 41 - .../enterprise-reconciliation-lease.test.ts | 6 +- .../lib/billing/webhooks/enterprise.test.ts | 74 - .../sim/lib/billing/webhooks/invoices.test.ts | 49 - .../billing/webhooks/outbox-handlers.test.ts | 56 - .../webhooks/subscription-usage.test.ts | 16 - .../lib/billing/workspace-permissions.test.ts | 46 +- apps/sim/lib/branding/wordmark.test.ts | 51 - .../application/browser-file-transfer.test.ts | 4 - .../browser-agent/renderer-shortcuts.test.ts | 35 - apps/sim/lib/browser-agent/tab-label.test.ts | 60 - apps/sim/lib/browser-agent/transport.test.ts | 181 +- .../catalog/application/catalog-reads.test.ts | 348 +- .../application/list-registries.test.ts | 71 - .../catalog/application/operations.test.ts | 56 - .../catalog/projection/catalog-sweep.test.ts | 45 - .../projection/projection-invariants.test.ts | 120 +- .../sim/lib/catalog/registry-boundary.test.ts | 117 - apps/sim/lib/charts/spec.test.ts | 54 - apps/sim/lib/chat-deployments/urls.test.ts | 15 - apps/sim/lib/chunkers/chunk-limit.test.ts | 4 - apps/sim/lib/chunkers/docs-chunker.test.ts | 97 +- .../lib/chunkers/json-yaml-chunker.test.ts | 402 --- .../lib/chunkers/recursive-chunker.test.ts | 270 -- apps/sim/lib/chunkers/regex-chunker.test.ts | 228 -- .../sim/lib/chunkers/sentence-chunker.test.ts | 202 -- .../chunkers/structured-data-chunker.test.ts | 311 -- apps/sim/lib/chunkers/text-chunker.test.ts | 489 --- apps/sim/lib/chunkers/token-chunker.test.ts | 178 - apps/sim/lib/chunkers/utils.test.ts | 182 - apps/sim/lib/cleanup/batch-delete.test.ts | 50 +- apps/sim/lib/cli-auth/approval-store.test.ts | 93 +- apps/sim/lib/collab-doc/collab-state.test.ts | 53 - apps/sim/lib/collab-doc/converter.test.ts | 40 - apps/sim/lib/collab-doc/merge.test.ts | 19 - apps/sim/lib/collab-doc/persist.test.ts | 425 --- apps/sim/lib/collab-doc/seed.test.ts | 335 +- apps/sim/lib/colors/brightness.test.ts | 50 - apps/sim/lib/colors/convert.test.ts | 48 - .../concurrency/__tests__/leader-lock.test.ts | 56 - .../__tests__/singleflight.test.ts | 32 - apps/sim/lib/consent/scripts.test.ts | 49 +- apps/sim/lib/consent/storage.test.ts | 14 - apps/sim/lib/content/og-image.test.ts | 3 - apps/sim/lib/content/registry-factory.test.ts | 11 - apps/sim/lib/content/seo.test.ts | 129 - apps/sim/lib/core/admission/gate.test.ts | 3 - .../core/admission/transient-failure.test.ts | 10 - .../lib/core/application/audit-source.test.ts | 3 - .../authorized-workspace-use-case.test.ts | 38 - .../lib/core/application/bulk-items.test.ts | 4 - .../copilot-workspace-invocation.test.ts | 1 - .../application/environment-reference.test.ts | 3 - .../locked-credential-authorization.test.ts | 2 - .../application/oauth-authorization.test.ts | 3 - .../lib/core/application/operation.test.ts | 3 - .../organization-authorization.test.ts | 2 - .../workspace-authorization.test.ts | 107 - .../application/workspace-operation.test.ts | 3 - .../core/async-jobs/backends/database.test.ts | 103 +- .../async-jobs/backends/trigger-dev.test.ts | 143 - apps/sim/lib/core/async-jobs/region.test.ts | 9 +- apps/sim/lib/core/async-jobs/types.test.ts | 3 - .../lib/core/config/appconfig-rules.test.ts | 18 - apps/sim/lib/core/config/appconfig.test.ts | 43 - .../lib/core/config/block-visibility.test.ts | 20 - .../lib/core/config/cost-multiplier.test.ts | 3 - .../core/config/deployment-shape.dom.test.tsx | 7 - .../lib/core/config/deployment-shape.test.ts | 37 - .../config/enterprise-entitlements.test.ts | 3 - .../config/env-capabilities.server.test.ts | 11 - .../lib/core/config/env-capabilities.test.ts | 84 - apps/sim/lib/core/config/env-flags.test.ts | 3 - apps/sim/lib/core/config/env.dom.test.ts | 4 - apps/sim/lib/core/config/env.test.ts | 3 - .../sim/lib/core/config/feature-flags.test.ts | 8 - apps/sim/lib/core/config/redis-budget.test.ts | 3 - apps/sim/lib/core/config/redis.test.ts | 144 - apps/sim/lib/core/config/scim-rollout.test.ts | 3 - .../core/config/trigger-availability.test.ts | 3 - .../lib/core/config/trigger-runtime.test.ts | 13 - .../lib/core/errors/background-retry.test.ts | 3 - .../core/errors/database-query-error.test.ts | 3 - .../errors/retryable-infrastructure.test.ts | 4 - .../lib/core/execution-limits/types.test.ts | 3 - apps/sim/lib/core/idempotency/cleanup.test.ts | 4 - apps/sim/lib/core/idempotency/service.test.ts | 4 - .../lib/core/idempotency/transaction.test.ts | 2 - .../read-organization-network.test.ts | 2 - .../lib/core/network/config.server.test.ts | 1 - .../lib/core/network/context.server.test.ts | 1 - .../lib/core/network/gateway.server.test.ts | 1 - .../network/resource-scope.server.test.ts | 2 - apps/sim/lib/core/network/routing.test.ts | 1 - .../lib/core/network/transport.server.test.ts | 2 - apps/sim/lib/core/orchestration/types.test.ts | 16 - apps/sim/lib/core/outbox/enqueue.test.ts | 18 - apps/sim/lib/core/outbox/processor.test.ts | 16 - .../lib/core/outbox/service.integration.ts | 2 +- apps/sim/lib/core/outbox/service.test.ts | 135 - .../hosted-key-rate-limiter.test.ts | 199 -- .../rate-limiter/hosted-key/queue.test.ts | 131 - .../rate-limiter/provider-admission.test.ts | 3 - .../provider-capacity-state.test.ts | 3 - .../provider-capacity-store.test.ts | 3 - .../provider-capacity.integration.ts | 2 +- .../rate-limiter/provider-capacity.test.ts | 3 - .../core/rate-limiter/rate-limiter.test.ts | 183 +- ...est.ts => route-helpers-client-ip.test.ts} | 4 - .../core/rate-limiter/route-helpers.test.ts | 71 - .../storage/db-token-bucket.test.ts | 3 - .../core/rate-limiter/storage/factory.test.ts | 37 - apps/sim/lib/core/rate-limiter/types.test.ts | 3 - .../lib/core/redis/byte-budget.server.test.ts | 3 - apps/sim/lib/core/resource-scope.test.ts | 1 - .../core/security/csp-socket-fallback.test.ts | 13 +- apps/sim/lib/core/security/csp.test.ts | 250 +- apps/sim/lib/core/security/deployment.test.ts | 39 +- .../security/egress-end-to-end.server.test.ts | 2 - .../lib/core/security/egress/profiles.test.ts | 14 - apps/sim/lib/core/security/encryption.test.ts | 163 +- .../guarded-request-fetch.server.test.ts | 66 +- .../security/input-validation.server.test.ts | 9 +- .../core/security/input-validation.test.ts | 2128 ++---------- .../linear-regex.differential.test.ts | 10 - .../lib/core/security/linear-regex.test.ts | 68 - .../core/security/pinned-fetch.server.test.ts | 97 - .../pinned-redirect-replay.server.test.ts | 140 +- apps/sim/lib/core/security/redaction.test.ts | 896 +---- .../sim/lib/core/security/same-origin.test.ts | 27 +- ...ecure-fetch-connection-pool.server.test.ts | 28 - ...ecure-fetch-request-framing.server.test.ts | 30 +- .../secure-fetch-response-cap.server.test.ts | 48 - .../core/security/ssrf-guarded-lookup.test.ts | 80 +- apps/sim/lib/core/security/url-safety.test.ts | 21 +- apps/sim/lib/core/utils.test.ts | 131 +- apps/sim/lib/core/utils/arrays.test.ts | 13 - apps/sim/lib/core/utils/background.test.ts | 12 - apps/sim/lib/core/utils/bounded-json.test.ts | 1 - .../lib/core/utils/browser-storage.test.ts | 41 - apps/sim/lib/core/utils/csv.test.ts | 12 - apps/sim/lib/core/utils/deadline.test.ts | 1 - .../sim/lib/core/utils/fetch-deadline.test.ts | 3 - .../core/utils/internal-api-base-url.test.ts | 2 - apps/sim/lib/core/utils/multipart.test.ts | 3 - apps/sim/lib/core/utils/node-stream.test.ts | 8 - apps/sim/lib/core/utils/records.test.ts | 24 +- .../core/utils/request-attribution.test.ts | 9 - .../lib/core/utils/response-format.test.ts | 3 - apps/sim/lib/core/utils/restore-name.test.ts | 11 - .../sim/lib/core/utils/separator-keys.test.ts | 3 - apps/sim/lib/core/utils/sse.test.ts | 290 +- apps/sim/lib/core/utils/stream-limits.test.ts | 28 - apps/sim/lib/core/utils/timezone.test.ts | 36 - apps/sim/lib/core/utils/urls.test.ts | 33 - apps/sim/lib/core/utils/uuid-v5.test.ts | 3 - .../lib/core/utils/with-route-handler.test.ts | 12 +- .../application/authorization.test.ts | 5 - .../configure-organization-mcp.test.ts | 2 - .../application/create-invite-link.test.ts | 41 - .../application/enrollment-auth.test.ts | 2 - .../application/list-credentials.test.ts | 92 - .../application/list-mcp-connections.test.ts | 65 +- .../application/manage-access.test.ts | 77 +- .../application/manage-enrollments.test.ts | 4 - .../application/manage-groups.test.ts | 37 - .../application/organization-access.test.ts | 20 - .../organization-account-indexing.test.ts | 50 +- .../organization-account-management.test.ts | 53 - .../organization-settings-delegation.test.ts | 2 - .../personal-organization-accounts.test.ts | 11 - .../application/public-enrollment.test.ts | 4 - .../application/send-invite.test.ts | 23 - .../application/slack-managed-users.test.ts | 4 - .../workflow-access-policy.test.ts | 112 - .../workspace-access-policy.test.ts | 1 - .../workspace-organization-accounts.test.ts | 11 - .../credential-groups/availability.test.ts | 20 +- .../lib/credential-groups/credentials.test.ts | 104 +- .../enrollment-links.test.ts | 43 +- .../lib/credential-groups/enrollments.test.ts | 12 - .../lib/credential-groups/indexing.test.ts | 23 - .../managed-mcp-connectors.test.ts | 19 +- .../credential-groups/mcp-connections.test.ts | 6 +- .../credential-groups/mcp-oauth-state.test.ts | 67 +- .../lib/credential-groups/oauth-state.test.ts | 107 - apps/sim/lib/credential-groups/oauth.test.ts | 4 - .../organization-account-options.test.ts | 97 +- .../organization-provider-setup.test.ts | 2 - .../organization-setup.test.ts | 4 +- .../provider-configuration.test.ts | 1 - .../provider-registry.test.ts | 64 - .../lib/credential-groups/rate-limit.test.ts | 9 +- .../scoped-availability.test.ts | 2 - .../search-connection-completion.test.ts | 9 - .../self-enrollment-oauth.test.ts | 25 - .../credential-groups/self-enrollment.test.ts | 2 - .../sim/lib/credential-groups/service.test.ts | 84 +- .../slack-managed-users.test.ts | 130 - .../credential-groups/slack-provider.test.ts | 15 +- .../standard-oauth-provider.test.ts | 184 +- .../sim/lib/credential-groups/trigger.test.ts | 37 +- .../credentials/__tests__/deletion.test.ts | 44 - .../__tests__/webhook-deactivation.test.ts | 18 - apps/sim/lib/credentials/access.test.ts | 99 +- .../credentials/api/route-policies.test.ts | 28 - .../authorized-credential-use-case.test.ts | 42 +- .../application/capability-gate.test.ts | 46 - .../complete-quickbooks-connection.test.ts | 27 - .../application/connection-target.test.ts | 18 - .../copilot-managed-oauth-delegation.test.ts | 3 - .../create-credential-connection.test.ts | 22 - .../application/credential-crud.test.ts | 164 +- .../delete-many-credentials.test.ts | 8 - .../discover-managed-mcp-tools.test.ts | 24 - .../launch-credential-connection.test.ts | 10 - .../list-credential-providers.test.ts | 43 - .../list-workspace-credentials.test.ts | 65 +- .../managed-oauth-delegation.test.ts | 12 - .../application/membership-projection.test.ts | 24 - .../application/oauth-accounts.test.ts | 217 +- .../application/operations.test.ts | 37 +- .../organization-credentials.test.ts | 247 +- .../application/personal-connection.test.ts | 62 - .../application/personal-credentials.test.ts | 101 +- .../prepare-credential-connection.test.ts | 60 - .../application/provider-catalog.test.ts | 179 +- .../resolve-managed-oauth-token.test.ts | 43 +- ...esolve-organization-personal-token.test.ts | 42 +- .../resolve-personal-token.test.ts | 31 +- .../application/save-credential-draft.test.ts | 11 - .../application/service-account.test.ts | 191 - .../workspace-account-visibility.test.ts | 14 +- .../atlassian-service-account.test.ts | 13 +- .../descriptors.test.ts | 39 - .../minters/box.test.ts | 147 - .../minters/netsuite.test.ts | 127 +- .../minters/salesforce.test.ts | 288 +- .../minters/zoho-desk.test.ts | 243 +- .../minters/zoom.test.ts | 107 - .../client-credential-accounts/server.test.ts | 15 - .../sim/lib/credentials/connect-draft.test.ts | 44 - apps/sim/lib/credentials/deletion.test.ts | 43 +- apps/sim/lib/credentials/display-name.test.ts | 14 +- apps/sim/lib/credentials/draft-hooks.test.ts | 5 - .../lib/credentials/draft-processor.test.ts | 50 +- apps/sim/lib/credentials/environment.test.ts | 73 - .../credentials/gitlab-personal-token.test.ts | 2 - .../credentials/managed-mcp-runtime.test.ts | 2 - apps/sim/lib/credentials/managed-mcp.test.ts | 4 - .../sim/lib/credentials/managed-oauth.test.ts | 144 +- apps/sim/lib/credentials/members.test.ts | 47 +- .../credentials/oauth-chat-attempt.test.ts | 97 +- .../credentials/orchestration/index.test.ts | 347 +- .../credentials/organization-draft.test.ts | 8 - .../credentials/organization-managed.test.ts | 20 +- .../lib/credentials/personal-tokens.test.ts | 53 - apps/sim/lib/credentials/personal.test.ts | 2 - apps/sim/lib/credentials/queries.test.ts | 62 +- .../sim/lib/credentials/secret-values.test.ts | 174 +- .../service-account-provider-ids.test.ts | 51 +- .../service-account-secret.test.ts | 99 - .../descriptors.test.ts | 60 - .../token-service-accounts/errors.test.ts | 54 +- .../validators/airtable.test.ts | 93 - .../validators/asana.test.ts | 107 - .../validators/attio.test.ts | 52 +- .../validators/calcom.test.ts | 95 - .../validators/coda.test.ts | 97 - .../validators/harmonic.test.ts | 68 +- .../validators/hubspot.test.ts | 59 - .../validators/linear.test.ts | 82 +- .../validators/monday.test.ts | 77 +- .../validators/notion.test.ts | 104 - .../validators/pipedrive.test.ts | 144 - .../validators/shopify.test.ts | 71 +- .../validators/snowflake.test.ts | 28 +- .../validators/trello.test.ts | 62 +- .../validators/wealthbox.test.ts | 37 +- .../validators/webflow.test.ts | 105 - .../application/use-cases.test.ts | 15 - apps/sim/lib/custom-tools/schema.test.ts | 21 +- .../data-drains/application/routes.test.ts | 47 - .../data-drains/application/use-cases.test.ts | 51 - .../destinations/azure_blob.test.ts | 94 +- .../data-drains/destinations/bigquery.test.ts | 69 - .../data-drains/destinations/datadog.test.ts | 68 - .../lib/data-drains/destinations/gcs.test.ts | 73 +- .../lib/data-drains/destinations/s3.test.ts | 55 +- .../destinations/snowflake.test.ts | 65 - .../data-drains/destinations/webhook.test.ts | 4 - apps/sim/lib/data-drains/dispatcher.test.ts | 62 +- apps/sim/lib/data-drains/service.test.ts | 17 - .../lib/data-drains/sources/cursor.test.ts | 11 - apps/sim/lib/db/read-retry.test.ts | 1 - apps/sim/lib/db/transaction.test.ts | 17 +- apps/sim/lib/desktop/appearance.test.ts | 139 - apps/sim/lib/desktop/index.test.ts | 21 - apps/sim/lib/desktop/min-version.test.ts | 12 - .../lib/desktop/preferences-loading.test.ts | 3 - apps/sim/lib/desktop/update-feed.test.ts | 54 - apps/sim/lib/embeddings/catalog.test.ts | 84 - apps/sim/lib/embeddings/client.test.ts | 987 ------ .../embeddings/configuration-error.test.ts | 19 - .../lib/embeddings/error-diagnostics.test.ts | 46 - .../knowledge-embedding-family.test.ts | 17 - .../ollama-model-catalog.server.test.ts | 71 - .../openrouter-model-catalog.server.test.ts | 19 - .../lib/embeddings/providers/openai.test.ts | 60 - .../embeddings/providers/providers.test.ts | 171 - apps/sim/lib/embeddings/quota-circuit.test.ts | 16 - apps/sim/lib/embeddings/rate-limit.test.ts | 46 - apps/sim/lib/environment/utils.test.ts | 322 -- .../lib/events/rotating-event-source.test.ts | 4 - apps/sim/lib/events/sse-endpoint.test.ts | 23 - .../execution/__tests__/call-chain.test.ts | 63 - .../cancel-workflow-execution.test.ts | 5 - apps/sim/lib/execution/cancellation.test.ts | 21 - .../code-placeholders/compiler.test.ts | 3 - .../scan.differential.test.ts | 2 - ...urable-secret-provenance-telemetry.test.ts | 4 +- .../durable-secret-provenance.test.ts | 9 +- apps/sim/lib/execution/event-buffer.test.ts | 4 - .../execution-deadline-header.test.ts | 3 - .../lib/execution/execution-signal.test.ts | 4 - apps/sim/lib/execution/files.test.ts | 2 - .../isolated-vm-worker-hardening.test.ts | 2 - apps/sim/lib/execution/isolated-vm.test.ts | 9 +- .../execution/model-input-provenance.test.ts | 3 - .../mounted-file-secret-provenance.test.ts | 4 - apps/sim/lib/execution/payloads/cache.test.ts | 3 - .../payloads/file-secret-provenance.test.ts | 2 - .../lib/execution/payloads/hydration.test.ts | 9 +- .../payloads/large-array-manifest.test.ts | 4 - .../payloads/large-value-metadata.test.ts | 5 - .../payloads/live-paused-statuses-sql.test.ts | 4 - .../payloads/materialization.server.test.ts | 4 - .../payloads/prune-metadata-sql.test.ts | 4 - .../lib/execution/payloads/serializer.test.ts | 10 - apps/sim/lib/execution/payloads/store.test.ts | 4 - apps/sim/lib/execution/preprocessing.test.ts | 12 - .../preprocessing.webhook-correlation.test.ts | 4 - .../execution/private-tool-metadata.test.ts | 3 - .../lib/execution/provenance-budget.test.ts | 3 - .../remote-sandbox/cli-tools-boundary.test.ts | 3 - .../remote-sandbox/cli-tools.test.ts | 59 - .../remote-sandbox/conformance.test.ts | 9 - .../remote-sandbox/e2b-session.test.ts | 3 - .../e2b-system-packages.test.ts | 4 - .../remote-sandbox/entitlement.test.ts | 9 - .../fellows-digest.acceptance.test.ts | 4 - ....test.ts => google-cloud-cli.live.test.ts} | 3 - .../remote-sandbox/image-registry.test.ts | 8 - ...smoke.test.ts => managed-cli.live.test.ts} | 3 - .../remote-sandbox/pi-lifetime.test.ts | 3 - ...est.ts => pi-sandbox-billing.live.test.ts} | 2 - .../execution/remote-sandbox/resolve.test.ts | 3 - ...e.test.ts => sandbox-billing.live.test.ts} | 2 - .../remote-sandbox/sandbox-encoding.test.ts | 3 - ...oke.test.ts => sandbox-files.live.test.ts} | 2 - .../remote-sandbox/sandbox-spec.test.ts | 3 - .../remote-sandbox/session-cli.test.ts | 1 - .../session-file-provenance.test.ts | 1 - .../session-file-snapshot.test.ts | 2 - .../session-file-uploads.test.ts | 1 - .../remote-sandbox/session-files.test.ts | 3 - .../remote-sandbox/session-lock.test.ts | 2 - .../remote-sandbox/session-sandbox.test.ts | 5 - .../remote-sandbox/wand-enricher.test.ts | 4 - .../workspace-sandboxes.test.ts | 3 - .../sandbox/brokers/workspace-file.test.ts | 9 +- .../execution/sandbox/bundles/verify.test.ts | 3 - ...smoke.test.ts => sim-helpers.live.test.ts} | 2 - .../lib/file-parsers/complete-text.test.ts | 80 - .../file-parsers/csv-preview-slice.test.ts | 47 +- apps/sim/lib/file-parsers/doc-parser.test.ts | 115 +- apps/sim/lib/file-parsers/errors.test.ts | 42 +- apps/sim/lib/file-parsers/html-parser.test.ts | 105 - apps/sim/lib/file-parsers/index.test.ts | 320 -- apps/sim/lib/file-parsers/json-parser.test.ts | 51 - apps/sim/lib/file-parsers/odf-text.test.ts | 98 - .../file-parsers/officeparser-module.test.ts | 33 +- .../lib/file-parsers/ooxml-encryption.test.ts | 8 - .../file-parsers/ooxml-presentation.test.ts | 152 +- .../file-parsers/ooxml-preview-guard.test.ts | 16 - .../lib/file-parsers/parser-formats.test.ts | 153 +- .../lib/file-parsers/pdf-furniture.test.ts | 28 - apps/sim/lib/file-parsers/pdf-layout.test.ts | 183 - apps/sim/lib/file-parsers/pdf-lines.test.ts | 41 - .../pdf-parser-cancellation.test.ts | 132 +- .../file-parsers/pdf-parser-structure.test.ts | 41 +- apps/sim/lib/file-parsers/pdf-parser.test.ts | 68 +- .../sim/lib/file-parsers/pdfjs-server.test.ts | 28 +- apps/sim/lib/file-parsers/pptx-parser.test.ts | 9 - apps/sim/lib/file-parsers/registry.test.ts | 40 +- .../file-parsers/sheet-display-text.test.ts | 56 - apps/sim/lib/file-parsers/sniff.test.ts | 218 +- apps/sim/lib/file-parsers/utils.test.ts | 69 - .../file-parsers/xlsx-preview-bound.test.ts | 58 - apps/sim/lib/file-parsers/yaml-limits.test.ts | 14 - apps/sim/lib/file-parsers/yaml-parser.test.ts | 59 - apps/sim/lib/file-parsers/zip-guard.test.ts | 40 - .../folders/application-folder-caps.test.ts | 37 - apps/sim/lib/folders/bulk.test.ts | 16 - apps/sim/lib/folders/cascade.test.ts | 267 +- apps/sim/lib/folders/naming.test.ts | 29 - apps/sim/lib/folders/orchestration.test.ts | 505 --- apps/sim/lib/folders/queries.test.ts | 243 +- apps/sim/lib/folders/scope.test.ts | 23 - apps/sim/lib/folders/selection.test.ts | 22 +- apps/sim/lib/folders/subtree.test.ts | 76 - .../application/execute-chat-function.test.ts | 2 - .../application/execute-function.test.ts | 4 - .../execute-request.test.ts | 500 +-- .../function-execution/sandbox-mounts.test.ts | 3 - apps/sim/lib/global-work/summary.test.ts | 36 - apps/sim/lib/guardrails/mask-client.test.ts | 58 - apps/sim/lib/guardrails/pii-entities.test.ts | 57 - .../guardrails/validate_hallucination.test.ts | 17 - apps/sim/lib/guardrails/validate_pii.test.ts | 40 - .../sim/lib/guardrails/validate_regex.test.ts | 23 - .../lib/guardrails/validation-client.test.ts | 32 - apps/sim/lib/imap/connection.server.test.ts | 22 - .../integrations/availability.server.test.ts | 3 - .../integrations/credential-display.test.ts | 149 +- .../credential-visibility.server.test.ts | 5 - .../integrations/metadata-boundary.test.ts | 31 - .../oauth-service-availability.server.test.ts | 3 - .../lib/integrations/oauth-service.test.ts | 73 - .../sim/lib/internal/a2a/execute-tool.test.ts | 135 - apps/sim/lib/internal/a2a/operations.test.ts | 28 - apps/sim/lib/internal/agiloft/client.test.ts | 32 - .../lib/internal/agiloft/execute-tool.test.ts | 170 - .../lib/internal/agiloft/file-input.test.ts | 119 - .../lib/internal/agiloft/operations.test.ts | 19 - .../sim/lib/internal/agiloft/protocol.test.ts | 2 - apps/sim/lib/internal/agiloft/urls.test.ts | 153 - .../internal/appconfig/execute-tool.test.ts | 160 - .../lib/internal/appconfig/operations.test.ts | 123 - apps/sim/lib/internal/asana/client.test.ts | 118 - .../lib/internal/asana/execute-tool.test.ts | 231 -- .../sim/lib/internal/asana/operations.test.ts | 74 - .../sim/lib/internal/ashby/operations.test.ts | 40 - .../lib/internal/athena/execute-tool.test.ts | 267 -- .../lib/internal/athena/operations.test.ts | 187 +- .../azure-data-explorer/client.test.ts | 15 - apps/sim/lib/internal/box/client.test.ts | 71 - .../sim/lib/internal/box/execute-tool.test.ts | 37 - apps/sim/lib/internal/box/operations.test.ts | 50 - apps/sim/lib/internal/brex/client.test.ts | 45 +- .../lib/internal/brex/execute-tool.test.ts | 78 +- apps/sim/lib/internal/brex/operations.test.ts | 74 +- .../browser-use/operations/run-task.test.ts | 155 - .../lib/internal/buffer/execute-tool.test.ts | 91 - .../lib/internal/buffer/operations.test.ts | 38 - .../lib/internal/clickhouse/client.test.ts | 58 +- .../internal/clickhouse/execute-tool.test.ts | 202 -- .../internal/clickhouse/operations.test.ts | 118 +- apps/sim/lib/internal/clickup/client.test.ts | 71 - .../lib/internal/clickup/operations.test.ts | 4 - .../cloudformation/execute-tool.test.ts | 173 - .../cloudformation/operations.test.ts | 143 - .../internal/cloudtrail/execute-tool.test.ts | 128 +- .../internal/cloudtrail/operations.test.ts | 135 +- .../internal/cloudwatch/execute-tool.test.ts | 193 -- .../internal/cloudwatch/operations.test.ts | 171 - .../codepipeline/execute-tool.test.ts | 160 - .../internal/codepipeline/operations.test.ts | 96 - .../lib/internal/confluence/client.test.ts | 118 - .../internal/confluence/execute-tool.test.ts | 438 --- .../internal/confluence/operations.test.ts | 112 - .../lib/internal/crowdstrike/client.test.ts | 91 - .../internal/crowdstrike/execute-tool.test.ts | 161 - .../internal/crowdstrike/operations.test.ts | 52 - .../lib/internal/cursor/execute-tool.test.ts | 92 - .../lib/internal/cursor/operations.test.ts | 26 - .../read-available-by-id-or-title.test.ts | 53 - .../lib/internal/daytona/execute-tool.test.ts | 90 - .../lib/internal/daytona/operations.test.ts | 62 - .../lib/internal/deployments/client.test.ts | 100 +- .../internal/deployments/execute-tool.test.ts | 67 - .../internal/deployments/operations.test.ts | 149 - apps/sim/lib/internal/discord/client.test.ts | 27 - .../lib/internal/discord/operations.test.ts | 77 - apps/sim/lib/internal/docusign/client.test.ts | 91 - .../internal/docusign/execute-tool.test.ts | 134 - .../lib/internal/docusign/operations.test.ts | 66 - apps/sim/lib/internal/dropbox/client.test.ts | 55 - .../lib/internal/dropbox/execute-tool.test.ts | 20 - .../lib/internal/dropbox/operations.test.ts | 59 - apps/sim/lib/internal/dynamodb/client.test.ts | 24 - .../internal/dynamodb/execute-tool.test.ts | 157 - .../lib/internal/dynamodb/operations.test.ts | 104 - .../lib/internal/elevenlabs/client.test.ts | 151 - .../internal/elevenlabs/execute-tool.test.ts | 51 - .../internal/elevenlabs/operations.test.ts | 31 +- .../internal/embeddings/execute-tool.test.ts | 80 +- .../internal/embeddings/operations.test.ts | 3 - .../internal/enrichment/execute-tool.test.ts | 4 - apps/sim/lib/internal/extend/client.test.ts | 81 - .../lib/internal/extend/execute-tool.test.ts | 38 - .../lib/internal/extend/operations.test.ts | 73 - .../lib/internal/file/execute-tool.test.ts | 238 -- .../file/materialization-context.test.ts | 16 - .../file/operations.provenance.test.ts | 5 - apps/sim/lib/internal/file/operations.test.ts | 926 +---- apps/sim/lib/internal/file/parser.test.ts | 759 ---- .../internal/firecrawl/execute-tool.test.ts | 71 - .../lib/internal/firecrawl/operations.test.ts | 35 - .../internal/fireflies/execute-tool.test.ts | 76 - .../lib/internal/fireflies/operations.test.ts | 40 - .../sim/lib/internal/function/execute.test.ts | 4 - .../lib/internal/github/execute-tool.test.ts | 76 - .../lib/internal/github/operations.test.ts | 63 - .../lib/internal/gmail/execute-tool.test.ts | 183 - .../sim/lib/internal/gmail/operations.test.ts | 158 - .../lib/internal/google-drive/client.test.ts | 21 +- .../google-drive/execute-tool.test.ts | 183 - .../internal/google-drive/file-input.test.ts | 49 - .../internal/google-drive/operations.test.ts | 137 +- .../google-slides/execute-tool.test.ts | 26 - .../internal/google-slides/operations.test.ts | 4 - .../google-vault/execute-tool.test.ts | 76 - .../internal/google-vault/operations.test.ts | 4 - apps/sim/lib/internal/grafana/client.test.ts | 20 - .../lib/internal/grafana/execute-tool.test.ts | 112 - .../lib/internal/grafana/operations.test.ts | 50 +- .../internal/guardrails/execute-tool.test.ts | 34 - .../internal/guardrails/operations.test.ts | 90 - apps/sim/lib/internal/iam/client.test.ts | 84 +- .../sim/lib/internal/iam/execute-tool.test.ts | 247 -- apps/sim/lib/internal/iam/operations.test.ts | 79 - .../identity-center/concurrency.test.ts | 3 - .../identity-center/execute-tool.test.ts | 253 -- .../identity-center/operations.test.ts | 71 - .../identity-center/partition.test.ts | 3 - apps/sim/lib/internal/image/fetch.test.ts | 40 - .../sim/lib/internal/image/operations.test.ts | 4 - apps/sim/lib/internal/image/schema.test.ts | 22 - .../internal/instagram/execute-tool.test.ts | 191 - .../lib/internal/instagram/operations.test.ts | 152 - .../lib/internal/instagram/publishing.test.ts | 108 - apps/sim/lib/internal/jira/client.test.ts | 76 - .../lib/internal/jira/execute-tool.test.ts | 69 - apps/sim/lib/internal/jira/operations.test.ts | 85 +- apps/sim/lib/internal/jsm/client.test.ts | 67 - .../sim/lib/internal/jsm/execute-tool.test.ts | 364 -- apps/sim/lib/internal/jsm/operations.test.ts | 26 +- apps/sim/lib/internal/jupyter/client.test.ts | 71 - .../lib/internal/jupyter/execute-tool.test.ts | 120 +- .../lib/internal/jupyter/file-input.test.ts | 93 - .../lib/internal/jupyter/get-content.test.ts | 121 +- .../lib/internal/jupyter/operations.test.ts | 198 -- .../sim/lib/internal/jupyter/protocol.test.ts | 49 - .../internal/knowledge/execute-tool.test.ts | 79 +- .../lib/internal/knowledge/operations.test.ts | 32 +- apps/sim/lib/internal/lambda/client.test.ts | 308 -- .../lib/internal/lambda/execute-tool.test.ts | 1069 ------ .../lib/internal/lambda/operations.test.ts | 447 --- .../lambda/projection-contract.test.ts | 47 - .../lib/internal/latex/execute-tool.test.ts | 4 - .../sim/lib/internal/latex/operations.test.ts | 4 - .../sim/lib/internal/llm/execute-tool.test.ts | 32 - apps/sim/lib/internal/llm/operations.test.ts | 59 - .../lib/internal/logs/execute-tool.test.ts | 185 +- apps/sim/lib/internal/logs/operations.test.ts | 14 - .../mail/attachment-materialization.test.ts | 42 +- .../lib/internal/mail/execute-tools.test.ts | 102 - .../sim/lib/internal/mcp/execute-tool.test.ts | 167 - .../lib/internal/mcp/list-operations.test.ts | 23 - .../lib/internal/memory/execute-tool.test.ts | 95 - .../lib/internal/memory/operations.test.ts | 5 - .../lib/internal/memory/provenance.test.ts | 9 - .../add-user-app-role-assignment.test.ts | 19 - .../microsoft-teams/execute-tool.test.ts | 110 - .../microsoft-teams/operations.test.ts | 177 - .../microsoft-teams/tool-config.test.ts | 38 - .../internal/microsoft-word/client.test.ts | 19 - .../microsoft-word/execute-tool.test.ts | 124 - .../microsoft-word/operations.test.ts | 104 - .../sim/lib/internal/mistral/capacity.test.ts | 3 - apps/sim/lib/internal/mistral/client.test.ts | 4 - .../mistral/error-diagnostics.test.ts | 1 - .../lib/internal/mistral/execute-tool.test.ts | 43 - .../lib/internal/mistral/operations.test.ts | 4 - .../internal/mistral/page-count-real.test.ts | 3 - .../lib/internal/mistral/page-count.test.ts | 4 - apps/sim/lib/internal/mongodb/client.test.ts | 46 - .../lib/internal/mongodb/execute-tool.test.ts | 140 - .../internal/mongodb/input-validation.test.ts | 3 - .../internal/mongodb/introspection.test.ts | 58 - .../lib/internal/mongodb/operations.test.ts | 232 -- .../lib/internal/mssql/execute-tool.test.ts | 139 - .../sim/lib/internal/mssql/operations.test.ts | 144 - apps/sim/lib/internal/mssql/query.test.ts | 272 -- apps/sim/lib/internal/mysql/client.test.ts | 75 - .../lib/internal/mysql/execute-tool.test.ts | 137 - .../sim/lib/internal/mysql/operations.test.ts | 117 - apps/sim/lib/internal/mysql/queries.test.ts | 85 +- .../lib/internal/neo4j/execute-tool.test.ts | 107 - .../sim/lib/internal/neo4j/operations.test.ts | 22 - .../internal/onedrive/execute-tool.test.ts | 79 - .../lib/internal/onedrive/operations.test.ts | 182 +- .../lib/internal/onedrive/tool-config.test.ts | 21 - .../lib/internal/onepassword/client.test.ts | 58 +- .../internal/onepassword/execute-tool.test.ts | 186 - .../internal/onepassword/json-patch.test.ts | 3 - .../internal/onepassword/operations.test.ts | 151 +- apps/sim/lib/internal/outlook/client.test.ts | 192 - .../lib/internal/outlook/execute-tool.test.ts | 234 -- .../lib/internal/outlook/operations.test.ts | 333 +- .../lib/internal/persona/execute-tool.test.ts | 4 - .../lib/internal/persona/operations.test.ts | 27 - .../lib/internal/pipedrive/operations.test.ts | 15 - .../lib/internal/postgresql/client.test.ts | 36 +- .../internal/postgresql/execute-tool.test.ts | 142 - .../internal/postgresql/operations.test.ts | 68 +- .../lib/internal/postgresql/queries.test.ts | 67 - .../lib/internal/principals/executor.test.ts | 17 - .../lib/internal/pulse/execute-tool.test.ts | 31 - .../sim/lib/internal/pulse/operations.test.ts | 63 - .../quickbooks/contract-param-parity.test.ts | 3 - .../internal/quickbooks/execute-tool.test.ts | 201 -- .../internal/quickbooks/operations.test.ts | 27 - .../quickbooks/provider-operations.test.ts | 86 +- apps/sim/lib/internal/quiver/client.test.ts | 71 - .../lib/internal/quiver/execute-tool.test.ts | 98 - .../lib/internal/quiver/operations.test.ts | 142 - apps/sim/lib/internal/rds/client.test.ts | 3 - .../sim/lib/internal/rds/execute-tool.test.ts | 135 - apps/sim/lib/internal/rds/operations.test.ts | 128 - apps/sim/lib/internal/redis/client.test.ts | 72 - .../lib/internal/redis/execute-tool.test.ts | 158 - .../sim/lib/internal/redis/operations.test.ts | 110 - .../lib/internal/reducto/execute-tool.test.ts | 38 - .../lib/internal/reducto/operations.test.ts | 55 - apps/sim/lib/internal/resend/resend.test.ts | 71 - apps/sim/lib/internal/s3/execute-tool.test.ts | 156 - apps/sim/lib/internal/s3/operations.test.ts | 193 +- .../sim/lib/internal/sailpoint/client.test.ts | 27 - .../internal/sailpoint/execute-tool.test.ts | 708 ---- .../operations/update-custom-field.test.ts | 55 - .../lib/internal/sap-concur/client.test.ts | 393 +-- .../internal/sap-concur/execute-tool.test.ts | 126 +- .../internal/sap-concur/operations.test.ts | 104 +- .../internal/sap-concur/response-body.test.ts | 30 - .../lib/internal/sap-s4hana/client.test.ts | 66 +- .../internal/sap-s4hana/execute-tool.test.ts | 94 - .../internal/sap-s4hana/operations.test.ts | 29 +- .../lib/internal/sap-s4hana/schema.test.ts | 20 - .../lib/internal/search/operations.test.ts | 60 - .../secrets-manager/execute-tool.test.ts | 152 - .../secrets-manager/operations.test.ts | 63 - .../lib/internal/sendgrid/operations.test.ts | 51 - .../sim/lib/internal/ses/execute-tool.test.ts | 173 - apps/sim/lib/internal/ses/operations.test.ts | 107 - apps/sim/lib/internal/sftp/client.test.ts | 43 - .../lib/internal/sftp/execute-tool.test.ts | 122 - apps/sim/lib/internal/sftp/operations.test.ts | 126 +- .../lib/internal/sharepoint/client.test.ts | 32 - .../internal/sharepoint/execute-tool.test.ts | 83 +- .../internal/sharepoint/operations.test.ts | 54 - .../lib/internal/slack/execute-tool.test.ts | 87 - .../sim/lib/internal/slack/file-input.test.ts | 33 - apps/sim/lib/internal/slack/oauth.test.ts | 4 - .../sim/lib/internal/slack/operations.test.ts | 211 +- .../lib/internal/slack/search-client.test.ts | 1 - .../sim/lib/internal/sms/execute-tool.test.ts | 50 - apps/sim/lib/internal/smtp/client.test.ts | 47 - apps/sim/lib/internal/smtp/operations.test.ts | 31 - .../sim/lib/internal/sqs/execute-tool.test.ts | 237 -- apps/sim/lib/internal/sqs/operations.test.ts | 94 - .../lib/internal/square/execute-tool.test.ts | 79 - .../lib/internal/square/operations.test.ts | 50 - .../sim/lib/internal/ssh/execute-tool.test.ts | 168 - apps/sim/lib/internal/ssh/operations.test.ts | 146 - apps/sim/lib/internal/ssm/client.test.ts | 171 +- .../sim/lib/internal/ssm/execute-tool.test.ts | 217 +- .../sim/lib/internal/stagehand/client.test.ts | 22 - .../internal/stagehand/execute-tool.test.ts | 50 - .../stagehand/schema-conversion.test.ts | 3 - .../sim/lib/internal/sts/execute-tool.test.ts | 117 - .../sim/lib/internal/stt/execute-tool.test.ts | 322 -- .../internal/supabase/execute-tool.test.ts | 76 - .../lib/internal/supabase/operations.test.ts | 61 - .../operations/storage-update-bucket.test.ts | 56 - .../lib/internal/table/execute-tool.test.ts | 224 -- .../sim/lib/internal/table/operations.test.ts | 23 - .../lib/internal/table/read-schema.test.ts | 43 - .../internal/telegram/execute-tool.test.ts | 5 - .../lib/internal/telegram/operations.test.ts | 57 +- .../internal/textract/document-input.test.ts | 10 - apps/sim/lib/internal/textract/errors.test.ts | 38 - .../internal/textract/execute-tool.test.ts | 146 - .../lib/internal/textract/operations.test.ts | 28 - .../lib/internal/textract/poll-job.test.ts | 33 - .../internal/thinking/execute-tool.test.ts | 41 - .../lib/internal/tiktok/execute-tool.test.ts | 68 - .../lib/internal/tiktok/operations.test.ts | 4 - apps/sim/lib/internal/tiktok/upload.test.ts | 4 - .../internal/tool-operations/execute.test.ts | 57 +- .../file-result.server.test.ts | 138 - .../tool-operations/registry.server.test.ts | 3 - apps/sim/lib/internal/tts/client.test.ts | 99 +- .../sim/lib/internal/tts/execute-tool.test.ts | 157 - apps/sim/lib/internal/tts/operations.test.ts | 99 +- .../twilio-voice/execute-tool.test.ts | 40 - .../internal/twilio-voice/operations.test.ts | 5 - .../internal/uptimerobot/execute-tool.test.ts | 107 - .../internal/uptimerobot/operations.test.ts | 110 - apps/sim/lib/internal/vanta/client.test.ts | 28 - .../lib/internal/vanta/execute-tool.test.ts | 74 - .../sim/lib/internal/vanta/file-input.test.ts | 39 - .../sim/lib/internal/vanta/input-size.test.ts | 3 - .../sim/lib/internal/vanta/operations.test.ts | 162 +- apps/sim/lib/internal/video/client.test.ts | 79 - .../lib/internal/video/execute-tool.test.ts | 110 - .../sim/lib/internal/video/operations.test.ts | 59 - apps/sim/lib/internal/vision/client.test.ts | 124 - .../lib/internal/vision/execute-tool.test.ts | 72 - .../lib/internal/vision/operations.test.ts | 66 - .../internal/whatsapp/execute-tool.test.ts | 91 - .../lib/internal/whatsapp/operations.test.ts | 18 - apps/sim/lib/internal/whatsapp/upload.test.ts | 41 - .../internal/windchill/execute-tool.test.ts | 94 - .../lib/internal/windchill/operations.test.ts | 269 -- .../internal/wordpress/execute-tool.test.ts | 62 - apps/sim/lib/internal/workday/client.test.ts | 66 - .../lib/internal/workday/execute-tool.test.ts | 203 -- .../lib/internal/workday/operations.test.ts | 252 +- .../workflows/read-definition.test.ts | 9 +- .../workflows/read-tool-enrichment.test.ts | 9 +- .../internal/zoho-desk/execute-tool.test.ts | 80 - .../lib/internal/zoho-desk/operations.test.ts | 91 +- .../lib/internal/zoom/execute-tool.test.ts | 55 - apps/sim/lib/internal/zoom/operations.test.ts | 82 +- .../lib/internal/zoominfo/operations.test.ts | 69 - .../list-workspace-invitations.test.ts | 13 - .../application/manage-invitation.test.ts | 65 +- .../invitations/application/mutations.test.ts | 24 - .../application/send-invitation-batch.test.ts | 252 -- apps/sim/lib/invitations/core.test.ts | 1077 +----- apps/sim/lib/invitations/direct-grant.test.ts | 183 +- .../lib/invitations/grant-revocation.test.ts | 4 - .../lib/invitations/import-boundary.test.ts | 3 - .../organization-invitations.test.ts | 42 - .../sim/lib/invitations/resend-policy.test.ts | 1 - apps/sim/lib/invitations/send-resend.test.ts | 12 - apps/sim/lib/invitations/send.test.ts | 68 +- .../invitations/workspace-invitations.test.ts | 555 --- .../__integration__/coda-live-fixture.test.ts | 1 - .../__integration__/coda-live.integration.ts | 10 +- .../github-member.integration.ts | 2 +- .../gitlab-federated.integration.ts | 2 +- .../__integration__/migration-fixture.ts | 4 +- .../provider-admission.integration.ts | 2 +- .../seed-source-access-fixture.ts | 2 +- .../lib/knowledge/__integration__/setup.ts | 49 - .../storage-accounting.integration.ts | 2 +- .../lib/knowledge/access/availability.test.ts | 58 - .../access/confluence-permissions.test.ts | 3 - .../knowledge/access/confluence-site.test.ts | 234 +- .../access/drive-permissions.test.ts | 46 - .../access/github-installation.test.ts | 107 +- .../access/group-membership.integration.ts | 4 +- ...tgres.test.ts => predicate.integration.ts} | 14 +- .../lib/knowledge/access/predicate.test.ts | 44 - apps/sim/lib/knowledge/access/scope.test.ts | 379 -- apps/sim/lib/knowledge/access/tokens.test.ts | 67 - .../lib/knowledge/api/internal-route.test.ts | 64 - .../lib/knowledge/api/route-policies.test.ts | 29 - .../application/add-workspace-files.test.ts | 206 -- .../application/authorization.test.ts | 4 - .../authorized-knowledge-use-case.test.ts | 13 - .../lib/knowledge/application/billing.test.ts | 4 - .../lib/knowledge/application/bulk.test.ts | 81 - .../application/chat-citations.test.ts | 13 - .../lib/knowledge/application/chat.test.ts | 241 -- .../lib/knowledge/application/chunks.test.ts | 106 +- ...onnect-personal-search-integration.test.ts | 2 - .../application/connector-access.test.ts | 346 -- .../application/connector-credential.test.ts | 2 - .../knowledge/application/connectors.test.ts | 941 +---- .../knowledge/application/contexts.test.ts | 187 - .../knowledge/application/documents.test.ts | 535 +-- .../lib/knowledge/application/exports.test.ts | 80 - .../application/folder-paths.test.ts | 41 - .../lib/knowledge/application/folders.test.ts | 80 +- .../github-installation-source.test.ts | 13 - ...ts => github-installations.integration.ts} | 7 +- .../application/github-installations.test.ts | 84 - .../application/github-setup.test.ts | 267 +- .../application/knowledge-bases.test.ts | 410 --- .../knowledge/application/operations.test.ts | 239 -- .../organization-search-overview.test.ts | 152 - .../organization-search-stats.test.ts | 13 - .../personal-search-account.test.ts | 20 - .../personal-search-integrations.test.ts | 2 - .../application/personal-source-setup.test.ts | 258 -- .../read-original-document.test.ts | 39 - .../application/read-search-document.test.ts | 35 - ...es.test.ts => search-index.integration.ts} | 3 +- .../application/search-integrations.test.ts | 2 - .../search-source-overview.test.ts | 158 - .../application/search-sources.test.ts | 483 +-- .../lib/knowledge/application/search.test.ts | 430 +-- .../knowledge/application/sim-search.test.ts | 304 +- .../slack-search/assistant.test.ts | 89 - .../slack-search/authorization.test.ts | 9 - .../application/slack-search/commands.test.ts | 18 - .../application/slack-search/home.test.ts | 60 - .../application/slack-search/identity.test.ts | 2 - .../application/slack-search/ingress.test.ts | 12 - .../slack-search/installations.test.ts | 63 - .../slack-search/lifecycle.test.ts | 23 - .../application/slack-search/mention.test.ts | 2 - .../slack-search/onboarding.test.ts | 180 - .../slack-search/process-message.test.ts | 118 - .../application/slack-search/setup.test.ts | 478 +-- .../application/slack-search/stop.test.ts | 2 - .../application/slack-search/turns.test.ts | 21 - .../lib/knowledge/application/tags.test.ts | 300 -- .../application/upload-sessions.test.ts | 359 +- .../workspace-search.activity.test.ts | 44 - .../application/workspace-search.test.ts | 10 - .../lib/knowledge/chunks/keyset-sql.test.ts | 4 - apps/sim/lib/knowledge/chunks/service.test.ts | 55 - .../knowledge/connectors/access-modes.test.ts | 3 - .../knowledge/connectors/access-token.test.ts | 261 -- .../connectors/connector-error.test.ts | 97 - .../connectors/credential-recovery.test.ts | 12 +- .../lib/knowledge/connectors/deletion.test.ts | 54 +- .../knowledge/connectors/detachment.test.ts | 53 - .../connectors/directory-queue.test.ts | 4 - ...t.ts => external-directory.integration.ts} | 3 +- .../connectors/external-group-sync.test.ts | 367 -- .../google-company-scheduler.test.ts | 302 -- .../connectors/indexing-policy.test.ts | 1 - .../connectors/listing-checkpoint.test.ts | 331 -- .../connectors/manual-sync-cooldown.test.ts | 12 - .../connectors/member-access.test.ts | 339 +- .../connectors/member-observations.test.ts | 575 +-- .../connectors/member-provisioning.test.ts | 227 +- .../knowledge/connectors/member-queue.test.ts | 231 -- ...er-sync-engine-content-credential.test.ts} | 4 - .../connectors/member-sync-engine.test.ts | 117 +- .../connectors/mirrored-access.test.ts | 22 +- .../connectors/mirrored-acls.test.ts | 43 - .../organization-account-indexing.test.ts | 23 - .../lib/knowledge/connectors/queue.test.ts | 313 -- .../connectors/source-modified-at.test.ts | 39 - ...st.ts => sync-content-pass.integration.ts} | 10 +- .../connectors/sync-content-pass.test.ts | 516 +-- .../connectors/sync-database-retry.test.ts | 114 +- .../knowledge/connectors/sync-engine.test.ts | 1748 +--------- .../knowledge/connectors/sync-limits.test.ts | 3 - .../connectors/sync-log-retention.test.ts | 14 - ...est.ts => sync-persistence.integration.ts} | 9 +- .../connectors/sync-persistence.test.ts | 368 +- .../connectors/sync-primitives.test.ts | 200 -- .../connectors/viewer-source-accounts.test.ts | 26 - apps/sim/lib/knowledge/constants.test.ts | 67 +- .../documents/deferred-retry-check.test.ts | 41 - .../documents/document-indexing-usage.test.ts | 50 - .../document-processing-error.test.ts | 46 - .../document-processing-source.test.ts | 952 +---- .../document-processor-chunk-limit.test.ts | 10 +- ...cument-processor-secret-provenance.test.ts | 78 - .../document-secret-provenance-update.test.ts | 6 +- .../documents/embedding-checkpoints.test.ts | 4 - .../knowledge/documents/lock-order.test.ts | 5 +- .../documents/ocr-checkpoints.test.ts | 181 - .../lib/knowledge/documents/ocr-recovery.md | 2 +- .../documents/ocr-request-policy.test.ts | 45 - .../documents/ocr-source-validation.test.ts | 42 - .../documents/parser-extension.test.ts | 17 - .../documents/pdf-ocr-chunking.test.ts | 37 - .../documents/pdf-ocr-triage.test.ts | 405 +-- .../documents/pdf-text-layer.test.ts | 13 - .../documents/processing-claim.test.ts | 54 +- .../processing-continuation-dispatch.test.ts | 9 +- .../documents/processing-dispatch.test.ts | 9 +- .../documents/processing-lane.test.ts | 29 +- .../processing-outbox-handler.test.ts | 44 - .../documents/processing-payload.test.ts | 33 - .../processing-provider-continuation.test.ts | 98 - .../processing-provider-deferral.test.ts | 1 - .../documents/processing-queue.test.ts | 479 --- ...rocessing-recovery-queue-transport.test.ts | 1 - .../processing-recovery-queue.test.ts | 134 +- .../documents/processing-status.test.ts | 24 - .../documents/retry-processing-grace.test.ts | 223 +- .../documents/secure-fetch.server.test.ts | 25 - .../lib/knowledge/documents/service.test.ts | 22 - .../documents/storage-billing.test.ts | 352 +- .../documents/storage-cleanup.test.ts | 59 - .../documents/storage-upload.test.ts | 23 - .../stored-artifact-extension.test.ts | 67 +- .../knowledge/documents/tag-filter.test.ts | 149 - .../documents/unreadable-document.test.ts | 32 - .../sim/lib/knowledge/documents/utils.test.ts | 543 +-- .../workspace-source-provenance.test.ts | 208 -- .../embeddings-configuration.test.ts | 67 - apps/sim/lib/knowledge/embeddings.test.ts | 4 - apps/sim/lib/knowledge/folders.test.ts | 73 - .../lib/knowledge/github-setup-urls.test.ts | 25 - apps/sim/lib/knowledge/mcp/activity.test.ts | 24 - .../lib/knowledge/mcp/oauth-metadata.test.ts | 6 - .../lib/knowledge/mcp/route-handler.test.ts | 2 - .../lib/knowledge/mcp/server.protocol.test.ts | 2 - apps/sim/lib/knowledge/mcp/server.test.ts | 357 +- .../knowledge/model-input-provenance.test.ts | 66 - .../orchestration/connector-access.test.ts | 448 +-- .../orchestration/connectors.test.ts | 984 +----- .../knowledge/orchestration/documents.test.ts | 326 +- .../orchestration/knowledge-bases.test.ts | 150 - .../knowledge/orchestration/restore.test.ts | 91 - .../projection/enqueue-inline.test.ts | 26 +- .../lib/knowledge/projection/enqueue.test.ts | 47 - apps/sim/lib/knowledge/projection/run.test.ts | 48 - apps/sim/lib/knowledge/read-access.test.ts | 49 +- apps/sim/lib/knowledge/reranker.test.ts | 94 - .../sim/lib/knowledge/search/activity.test.ts | 30 - apps/sim/lib/knowledge/search/author.test.ts | 23 - apps/sim/lib/knowledge/search/budget.test.ts | 1 - .../sim/lib/knowledge/search/citation.test.ts | 14 - .../search/connection-target.test.ts | 39 +- .../sim/lib/knowledge/search/defaults.test.ts | 28 +- .../lib/knowledge/search/diagnostics.test.ts | 119 +- apps/sim/lib/knowledge/search/filters.test.ts | 17 - apps/sim/lib/knowledge/search/prewarm.test.ts | 97 +- apps/sim/lib/knowledge/search/queries.test.ts | 1227 +------ apps/sim/lib/knowledge/search/recency.test.ts | 27 +- apps/sim/lib/knowledge/search/snippet.test.ts | 67 - .../search/source-vector-indexes.test.ts | 21 - apps/sim/lib/knowledge/search/stats.test.ts | 34 - .../search/tin-keyword-readiness.test.ts | 3 - .../lib/knowledge/search/tin-keyword.test.ts | 19 - .../lib/knowledge/search/tin-query.test.ts | 17 - .../lib/knowledge/secret-provenance.test.ts | 100 +- apps/sim/lib/knowledge/service.test.ts | 301 -- .../knowledge/tags/filter-resolution.test.ts | 64 - .../tags/secret-provenance-delete.test.ts | 36 +- apps/sim/lib/knowledge/tags/service.test.ts | 230 -- apps/sim/lib/knowledge/tags/utils.test.ts | 45 - .../sim/lib/knowledge/transfer/bundle.test.ts | 108 - .../knowledge/transfer/export-archive.test.ts | 110 - .../sim/lib/knowledge/upload-metadata.test.ts | 82 +- apps/sim/lib/knowledge/vector-columns.test.ts | 42 - .../lib/link-preview/fetch-preview.test.ts | 82 - apps/sim/lib/logs/api/route-policies.test.ts | 19 - .../lib/logs/application/list-logs.test.ts | 32 - .../log-analytics-use-cases.test.ts | 57 - .../lib/logs/application/operations.test.ts | 4 - .../application/public-log-projection.test.ts | 101 - .../application/public-log-use-cases.test.ts | 242 -- .../read-execution-snapshot.test.ts | 35 - .../logs/application/read-log-detail.test.ts | 31 - apps/sim/lib/logs/cost-ledger.test.ts | 3 - apps/sim/lib/logs/execution-origin.test.ts | 4 - .../lib/logs/execution/cancellation.test.ts | 18 - apps/sim/lib/logs/execution/duration.test.ts | 19 - .../logs/execution/functional-outputs.test.ts | 3 - .../execution/hydrate-child-traces.test.ts | 114 - apps/sim/lib/logs/execution/logger.test.ts | 342 -- .../logs/execution/logging-factory.test.ts | 349 +- .../logs/execution/logging-session.test.ts | 501 --- .../logs/execution/pii-large-values.test.ts | 145 - .../lib/logs/execution/pii-redaction.test.ts | 44 - .../logs/execution/progress-markers.test.ts | 76 - .../logs/execution/snapshot/service.test.ts | 229 +- .../execution/trace-secret-projection.test.ts | 411 +-- .../execution/trace-spans/trace-spans.test.ts | 1354 +------- .../execution/trace-store-storage.test.ts | 4 - .../lib/logs/execution/trace-store.test.ts | 317 -- apps/sim/lib/logs/fetch-log-detail.test.ts | 88 - apps/sim/lib/logs/filters.test.ts | 16 - apps/sim/lib/logs/folder-scope.test.ts | 3 - apps/sim/lib/logs/list-logs.test.ts | 85 - apps/sim/lib/logs/log-views.test.ts | 62 - apps/sim/lib/logs/public-queries.test.ts | 66 - apps/sim/lib/logs/query-parser.test.ts | 423 --- apps/sim/lib/logs/search-suggestions.test.ts | 378 -- apps/sim/lib/logs/sort-cursor.test.ts | 3 - apps/sim/lib/logs/stats.test.ts | 76 - .../lib/managed-agents/run-session.test.ts | 4 - .../lib/managed-agents/session-client.test.ts | 386 +-- .../lib/managed-agents/wire-shapes.test.ts | 2 - .../application/execute-managed-tool.test.ts | 73 - .../lib/mcp/application/execute-tool.test.ts | 193 -- .../application/managed-connections.test.ts | 24 - .../mcp/application/operation-access.test.ts | 24 - .../lib/mcp/application/operations.test.ts | 30 - .../sim/lib/mcp/application/use-cases.test.ts | 175 - .../application/workflow-deployments.test.ts | 69 - apps/sim/lib/mcp/client.test.ts | 190 - apps/sim/lib/mcp/connection-manager.test.ts | 81 - apps/sim/lib/mcp/connection-pool.test.ts | 40 - apps/sim/lib/mcp/domain-check.test.ts | 295 -- apps/sim/lib/mcp/error-diagnostics.test.ts | 4 - apps/sim/lib/mcp/middleware.test.ts | 10 - apps/sim/lib/mcp/oauth/auth.test.ts | 31 - .../lib/mcp/oauth/coordinated-fetch.test.ts | 77 - apps/sim/lib/mcp/oauth/probe.test.ts | 19 - apps/sim/lib/mcp/oauth/revoke.test.ts | 26 - apps/sim/lib/mcp/oauth/storage.test.ts | 33 +- apps/sim/lib/mcp/operation-policy.test.ts | 1 - .../orchestration/server-lifecycle.test.ts | 219 -- .../workflow-mcp-lifecycle.test.ts | 4 - apps/sim/lib/mcp/pinned-fetch.test.ts | 146 - apps/sim/lib/mcp/pubsub.test.ts | 90 - apps/sim/lib/mcp/resolve-config.test.ts | 4 - apps/sim/lib/mcp/server-locks.test.ts | 3 - apps/sim/lib/mcp/service-pool.test.ts | 166 - apps/sim/lib/mcp/service.test.ts | 250 -- apps/sim/lib/mcp/shared.test.ts | 3 - apps/sim/lib/mcp/storage/memory-cache.test.ts | 282 -- apps/sim/lib/mcp/tool-schema.test.ts | 1 - apps/sim/lib/mcp/tool-validation.test.ts | 183 - apps/sim/lib/mcp/types.test.ts | 247 -- apps/sim/lib/mcp/utils.test.ts | 326 -- apps/sim/lib/mcp/utils.ts | 36 - apps/sim/lib/mcp/workflow-mcp-sync.test.ts | 41 - apps/sim/lib/mcp/workflow-tool-schema.test.ts | 1 - .../lib/media/ffmpeg-schema-parity.test.ts | 7 - apps/sim/lib/media/ffmpeg.test.ts | 3 - .../sim/lib/memory/agent-turn-session.test.ts | 172 - .../memory/application/agent-turns.test.ts | 16 - .../lib/memory/application/operations.test.ts | 21 - .../lib/memory/application/retrieval.test.ts | 12 - .../lib/memory/application/use-cases.test.ts | 5 - apps/sim/lib/memory/artifacts.test.ts | 78 - apps/sim/lib/memory/checkpoint-codec.test.ts | 1 - apps/sim/lib/memory/context-policy.test.ts | 21 - apps/sim/lib/memory/context-tokens.test.ts | 7 +- ...t.ts => conversation-store.integration.ts} | 15 +- .../sim/lib/memory/conversation-store.test.ts | 4 - apps/sim/lib/memory/execution-record.test.ts | 1 - apps/sim/lib/memory/history-group.test.ts | 1 - apps/sim/lib/memory/history-window.test.ts | 20 - ...t.ts => message-provenance.integration.ts} | 8 +- .../sim/lib/memory/message-provenance.test.ts | 96 - apps/sim/lib/memory/read-budget.test.ts | 2 - apps/sim/lib/memory/replay-provenance.test.ts | 2 - apps/sim/lib/memory/retrieval-prefix.test.ts | 6 +- apps/sim/lib/memory/retrieval-tool.test.ts | 4 - apps/sim/lib/memory/retrieval.test.ts | 95 - apps/sim/lib/memory/secret-provenance.test.ts | 48 +- ...s.test.ts => summary-store.integration.ts} | 8 +- apps/sim/lib/memory/turn-journal.test.ts | 1 - apps/sim/lib/memory/turn-state.test.ts | 96 - apps/sim/lib/messaging/email/ehlo.test.ts | 29 - .../lib/messaging/email/free-email.test.ts | 23 - apps/sim/lib/messaging/email/mailer.test.ts | 151 +- .../messaging/email/providers/gmail.test.ts | 78 - .../lib/messaging/email/unsubscribe.test.ts | 378 +- apps/sim/lib/messaging/email/unsubscribe.ts | 12 - apps/sim/lib/messaging/email/utils.test.ts | 59 +- .../messaging/email/validation.server.test.ts | 29 +- .../lib/messaging/email/validation.test.ts | 87 - .../lib/messaging/email/verification.test.ts | 11 - .../microsoft-word/document.server.test.ts | 179 - apps/sim/lib/model-router/resolve.test.ts | 179 - .../agent-cli/csv-workflow-execution.test.ts | 3 - .../lib/mothership/agent-cli/curation.test.ts | 11 - .../lib/mothership/agent-cli/engines.test.ts | 17 - .../agent-cli/engines/docs-search.test.ts | 48 - .../agent-cli/engines/file-read.test.ts | 4 - .../agent-cli/engines/file-view.test.ts | 6 +- .../agent-cli/engines/lint-scope.test.ts | 16 +- .../agent-cli/engines/universal-grep.test.ts | 11 +- .../agent-cli/engines/workflow-api.test.ts | 1 - .../agent-cli/engines/workflow-inputs.test.ts | 5 +- .../agent-cli/file-provenance.test.ts | 2 - .../file-round-trip-provenance.test.ts | 2 - .../agent-cli/file-upload-origin.test.ts | 3 - .../agent-cli/file-upload-transport.test.ts | 1 - .../agent-cli/knowledge-curation.test.ts | 7 - .../agent-cli/resource-effects.test.ts | 73 +- .../agent-cli/resource-owner.test.ts | 2 - .../agent-cli/run-cli-files.test.ts | 1 - .../lib/mothership/agent-cli/run-cli.test.ts | 6 +- ...es.test.ts => saved-run-read.live.test.ts} | 2 - .../agent-cli/scope-routing.test.ts | 2 - .../agent-cli/scoped-fork-transport.test.ts | 2 - .../lib/mothership/agent-cli/services.test.ts | 2 - .../sim/lib/mothership/agent-cli/sink.test.ts | 1 - .../agent-cli/table-read-transport.test.ts | 2 - .../workbench-file-provenance.test.ts | 2 - .../application/application-adapter.test.ts | 3 - .../authorize-chat-callback.test.ts | 1 - .../lib/mothership/application/error.test.ts | 3 - .../execute-custom-tool-use-case.test.ts | 3 - .../application/execute-file-use-case.test.ts | 3 - ...ecute-organization-secret-use-case.test.ts | 1 - .../execute-sandbox-use-case.test.ts | 3 - .../execute-table-use-case.test.ts | 4 - .../execute-workflow-use-case.test.ts | 3 - .../load-search-integrations.test.ts | 4 - ...esolve-organization-personal-token.test.ts | 1 - .../application/settings-context.test.ts | 1 - .../application/table-commands.test.ts | 183 - .../application/workspace-target.test.ts | 2 - .../assistant/connected-account-tool.test.ts | 1 - .../mothership/assistant/tool-policy.test.ts | 3 - ... => browser-download-claim.integration.ts} | 8 +- .../mothership/async-runs/lifecycle.test.ts | 37 +- .../mothership/async-runs/repository.test.ts | 310 +- ...s.test.ts => tool-identity.integration.ts} | 25 +- .../auth/application-delegation.test.ts | 3 - .../mothership/auth/file-delegation.test.ts | 3 - apps/sim/lib/mothership/auth/internal.test.ts | 1 - .../lib/mothership/auth/permissions.test.ts | 40 +- .../mothership/auth/table-delegation.test.ts | 3 - ...s.test.ts => service-store.integration.ts} | 4 +- .../lib/mothership/block-visibility.test.ts | 1 - apps/sim/lib/mothership/chat-status.test.ts | 7 +- .../chat/application/admit-turn.test.ts | 2 - .../chat/application/change-resources.test.ts | 2 - .../application/inline-images-scratch.test.ts | 2 - .../chat/application/inline-images.test.ts | 2 - .../chat/application/read-attachment.test.ts | 2 - .../application/read-sandbox-file.test.ts | 2 - .../chat/application/use-cases.test.ts | 2 - .../mothership/chat/assistant-images.test.ts | 2 - .../chat/attachment-preview.test.ts | 3 - .../chat/child-failure-transcript.test.ts | 3 - .../lib/mothership/chat/delegation.test.ts | 1 - .../mothership/chat/display-message.test.ts | 4 - .../chat/effective-transcript.test.ts | 8 +- .../mothership/chat/fork-chat-files.test.ts | 5 - .../chat/fork-inline-images.test.ts | 1 - .../lib/mothership/chat/fork-worker.test.ts | 1 - .../sim/lib/mothership/chat/lifecycle.test.ts | 38 - .../mothership/chat/messages-store.test.ts | 52 +- .../chat/organization-chats.test.ts | 4 - apps/sim/lib/mothership/chat/payload.test.ts | 56 - .../mothership/chat/persisted-message.test.ts | 4 - apps/sim/lib/mothership/chat/post.test.ts | 169 - .../process-contents-log-projection.test.ts | 3 - .../mothership/chat/process-contents.test.ts | 34 - .../chat/retrieval-citations.test.ts | 1 - .../chat/selection-clipboard.test.ts | 3 - .../mothership/chat/selection-context.test.ts | 32 - .../mothership/chat/sim-key-redaction.test.ts | 4 - .../mothership/chat/stream-liveness.test.ts | 4 - .../lib/mothership/chat/task-replay.test.ts | 3 - .../mothership/chat/terminal-state.test.ts | 5 - .../lib/mothership/chat/title-context.test.ts | 1 - .../mothership/chat/upload-context.test.ts | 52 - .../chat/workspace-inventory.test.ts | 3 - .../lib/mothership/conversation-id.test.ts | 3 - .../lib/mothership/docs/docs-corpus.test.ts | 9 - .../sim/lib/mothership/docs/docs-path.test.ts | 3 - .../lib/mothership/docs/docs-search.test.ts | 29 - .../mothership/environment-context.test.ts | 3 - apps/sim/lib/mothership/events.test.ts | 41 - apps/sim/lib/mothership/feature-flags.test.ts | 2 - .../mothership/inbox/agentmail-client.test.ts | 3 - .../mothership/inbox/cleanup-outbox.test.ts | 5 - .../sim/lib/mothership/inbox/executor.test.ts | 4 - .../lib/mothership/inbox/lifecycle.test.ts | 4 - .../integration-tool-projection.test.ts | 3 - .../integration-tools-invariants.test.ts | 3 - .../lib/mothership/integration-tools.test.ts | 3 - .../application/catalog-registry.test.ts | 1 - .../integrations/application/catalog.test.ts | 2 - apps/sim/lib/mothership/mcp-tools.test.ts | 5 - .../mothership/observability/timing.test.ts | 134 - .../tool-confirm/tool-confirm.test.ts | 5 - .../application/controls-boundary.test.ts | 2 - .../request/application/read-control.test.ts | 2 - .../application/recover-stream.test.ts | 1 - .../mothership/request/context/result.test.ts | 3 - .../lib/mothership/request/go/fetch.test.ts | 79 - .../request/go/file-preview-adapter.test.ts | 5 - .../lib/mothership/request/go/stream.test.ts | 455 +-- .../request/go/tool-call-identity.test.ts | 3 - .../request/handlers/handlers.test.ts | 503 +-- .../request/handlers/task-run-event.test.ts | 3 - .../request/lifecycle/admission.test.ts | 2 - .../request/lifecycle/headless.test.ts | 83 - .../request/lifecycle/recovery-config.test.ts | 1 - .../mothership/request/lifecycle/run.test.ts | 46 - .../request/lifecycle/start.test.ts | 41 - .../lib/mothership/request/metrics.test.ts | 71 +- .../mothership/request/session/abort.test.ts | 6 - .../request/session/buffer.redis.test.ts | 1 - .../mothership/request/session/buffer.test.ts | 4 - .../request/session/contract.test.ts | 120 - .../mothership/request/session/event.test.ts | 4 - .../request/session/explicit-abort.test.ts | 4 - .../session/file-preview-session.test.ts | 42 - .../request/session/recovery.test.ts | 4 - .../mothership/request/session/writer.test.ts | 5 - .../request/tool-call-state.test.ts | 4 - .../mothership/request/tools/billing.test.ts | 9 +- .../mothership/request/tools/client.test.ts | 6 - .../request/tools/enrich-error.test.ts | 30 - .../request/tools/execution-lifetime.test.ts | 2 - .../mothership/request/tools/executor.test.ts | 189 - .../mothership/request/tools/files.test.ts | 48 - .../request/tools/permission.test.ts | 32 - .../tools/resolved-secret-result.test.ts | 3 - .../request/tools/resources.test.ts | 65 +- .../mothership/request/tools/tables.test.ts | 64 - .../tools/workflow-client-fallback.test.ts | 13 - .../request/tools/workflow-context.test.ts | 26 - .../client-persistence-queue.test.ts | 9 +- .../mothership/resources/extraction.test.ts | 75 +- .../mothership/resources/persistence.test.ts | 9 +- .../lib/mothership/resources/search.test.ts | 1 - .../lib/mothership/resources/types.test.ts | 18 - .../lib/mothership/server/agent-url.test.ts | 1 - .../tasks/application/tasks.test.ts | 26 +- apps/sim/lib/mothership/tasks/wake.test.ts | 4 - .../tool-executor/assistant-routing.test.ts | 17 - .../mothership/tool-executor/executor.test.ts | 70 +- .../mothership/tool-executor/router.test.ts | 7 +- .../settings-entry-boundary.test.ts | 1 - .../client/browser-tool-execution.test.ts | 196 +- .../tools/client/completion.test.ts | 3 - .../tools/client/hidden-tools.test.ts | 23 - .../tools/client/local-filesystem.test.ts | 1 - .../tools/client/read-block.test.ts | 45 - .../tools/client/resource-display.test.tsx | 325 -- .../tools/client/run-tool-execution.test.ts | 57 - .../tools/client/store-utils.test.ts | 271 -- .../client/terminal-tool-execution.test.ts | 1 - .../lib/mothership/tools/descriptions.test.ts | 131 - .../function-execute-file-mounts.test.ts | 1 - .../function-execute-provenance.test.ts | 1 - .../handlers/function-execute-session.test.ts | 4 - .../function-execute-table-mounts.test.ts | 1 - .../mothership/tools/handlers/oauth.test.ts | 4 - .../tools/handlers/sim-cli-bridge.test.ts | 5 - .../tools/handlers/workflow/mutations.test.ts | 161 - .../workflow/withheld-run-result.test.ts | 3 - ....test.ts => hosted-workbench.live.test.ts} | 1 - .../tools/organization-secret-mount.test.ts | 1 - .../lib/mothership/tools/permissions.test.ts | 23 +- .../registry/server-tool-adapter.test.ts | 4 - .../tools/sandbox-resource-transport.test.ts | 2 - .../tools/sandbox-resources.test.ts | 2 - .../mothership/tools/sandbox-session.test.ts | 1 - .../secret-mount-materializer.server.test.ts | 4 - .../server/docs/search-docs-dispatch.test.ts | 39 - .../tools/server/docs/search-docs.test.ts | 45 - .../tools/server/files/doc-compile.test.ts | 8 - .../server/files/doc-compiled-store.test.ts | 4 - .../tools/server/files/doc-servable.test.ts | 44 - .../server/files/file-edit-roundtrip.test.ts | 5 +- .../server/files/file-intent-store.test.ts | 15 - .../tools/server/files/file-preview.test.ts | 3 - .../tools/server/generated-schema.test.ts | 3 - .../tools/server/image/generate-image.test.ts | 3 - .../server/knowledge/workspace-search.test.ts | 2 - .../tools/server/media/ffmpeg.test.ts | 5 - .../server/media/model-boundaries.test.ts | 4 - .../tools/server/model-input.test.ts | 4 - .../tools/server/open-resource.test.ts | 2 - .../tools/server/search-sources.test.ts | 2 - .../tools/server/settings-collections.test.ts | 1 - .../server/settings-custom-blocks.test.ts | 2 - .../settings-management-boundary.test.ts | 1 - .../mothership/tools/server/settings.test.ts | 1 - .../tools/server/user/get-credentials.test.ts | 3 - .../tools/server/workspaces.test.ts | 2 - .../tools/streaming-cli-name.test.ts | 57 - .../mothership/tools/tool-activity.test.ts | 187 +- .../lib/mothership/tools/tool-display.test.ts | 1143 ------ .../mothership/tools/workflow-tools.test.ts | 4 - .../lib/mothership/transport/control.test.ts | 1 - .../lib/mothership/transport/receiver.test.ts | 1 - .../lib/mothership/vfs/document-style.test.ts | 9 +- .../sim/lib/mothership/vfs/operations.test.ts | 9 - .../sim/lib/mothership/vfs/path-utils.test.ts | 3 - .../mothership/vfs/resource-writer.test.ts | 1 - .../navigation/organization-rollout.test.ts | 1 - apps/sim/lib/navigation/paths.test.ts | 39 - .../lib/navigation/resolve-app-entry.test.ts | 35 - .../oauth/__tests__/terminal-errors.test.ts | 94 +- apps/sim/lib/oauth/atlassian-token.test.ts | 3 +- apps/sim/lib/oauth/credential-service.test.ts | 107 +- .../github-installation-credential.test.ts | 7 +- .../sim/lib/oauth/github-installation.test.ts | 77 +- .../sim/lib/oauth/github-repositories.test.ts | 69 +- apps/sim/lib/oauth/github-repository.test.ts | 10 - .../google-service-account-transport.test.ts | 17 +- .../sim/lib/oauth/microsoft-dataverse.test.ts | 47 +- apps/sim/lib/oauth/microsoft.test.ts | 29 +- apps/sim/lib/oauth/monday.test.ts | 4 - apps/sim/lib/oauth/oauth.test.ts | 621 +--- .../oauth/quickbooks-client-config.test.ts | 27 +- apps/sim/lib/oauth/quickbooks-state.test.ts | 3 - apps/sim/lib/oauth/quickbooks.test.ts | 58 - apps/sim/lib/oauth/shopify-state.test.ts | 3 - apps/sim/lib/oauth/slack.test.ts | 7 - apps/sim/lib/oauth/token-resolution.test.ts | 237 +- apps/sim/lib/oauth/utils.test.ts | 810 +---- apps/sim/lib/og/cover-image.test.tsx | 58 - .../application/use-cases.test.ts | 31 - .../organization-secrets/repository.test.ts | 20 +- .../application/configuration.test.ts | 92 - .../application/member-removal.test.ts | 28 - .../application/member-role.test.ts | 68 +- .../application/security-settings.test.ts | 25 - .../application/settings.test.ts | 29 - .../application/use-cases.test.ts | 98 +- .../lib/organizations/instance-org.test.ts | 109 - .../lib/organizations/member-manager.test.ts | 12 - .../organizations/members/lifecycle.test.ts | 39 +- .../organizations/resource-cleanup.test.ts | 71 - .../lib/organizations/settings-access.test.ts | 23 +- apps/sim/lib/organizations/surface.test.ts | 82 - .../application/group-membership.test.ts | 138 - .../application/management.test.ts | 112 +- .../application/read-user-config.test.ts | 49 - .../application/use-cases.test.ts | 70 +- .../permission-groups/block-access.test.ts | 25 +- .../permission-groups/capabilities.test.ts | 17 - .../config-scope.server.test.ts | 22 - .../lib/permission-groups/features.test.ts | 73 - apps/sim/lib/permission-groups/fields.test.ts | 45 - .../permission-groups/group-manager.test.ts | 29 - .../integration-allowlist.test.ts | 2 - .../permission-groups/member-manager.test.ts | 73 +- .../permission-groups/model-access.test.ts | 9 - .../lib/permission-groups/mutation.test.ts | 2 - .../operation-access.test.ts | 53 - .../permission-groups/resolve.server.test.ts | 15 - apps/sim/lib/pinned-items/resources.test.ts | 45 - .../platform-context-use-cases.test.ts | 4 - .../lib/postcss/hairline-border-width.test.ts | 21 - apps/sim/lib/posthog/client-disabled.test.ts | 3 - apps/sim/lib/posthog/client.test.ts | 3 - apps/sim/lib/posthog/exception-filter.test.ts | 35 - apps/sim/lib/posthog/server.test.ts | 89 - .../pptx-renderer/parser/zip-parser.test.ts | 19 - ...test.ts => echarts-runtime-render.test.ts} | 3 - .../renderer/echarts-runtime.test.ts | 100 - .../lib/pptx-renderer/sim-pptx-viewer.test.ts | 10 +- .../pptx-renderer/utils/font-stack.test.ts | 20 - apps/sim/lib/public-shares/rate-limit.test.ts | 4 - apps/sim/lib/realtime/event-log.test.ts | 48 - apps/sim/lib/realtime/notify.test.ts | 38 - .../conditions/registry.test.ts | 65 - .../lib/resource-policies/evaluator.test.ts | 14 - .../principals/registry.test.ts | 71 - .../lib/resource-policies/repository.test.ts | 39 +- apps/sim/lib/resources/urls.test.ts | 28 +- .../sandboxes/application/operations.test.ts | 4 - .../sandboxes/application/use-cases.test.ts | 15 - .../lib/secrets/application/use-cases.test.ts | 335 -- apps/sim/lib/secrets/references/scan.test.ts | 6 +- apps/sim/lib/secrets/usage/record.test.ts | 31 - .../application/execute-selector.test.ts | 234 -- .../selectors/client/execute-selector.test.ts | 9 +- apps/sim/lib/selectors/manifest.test.ts | 29 - .../lib/selectors/server/credentials.test.ts | 7 - .../server/integration-access.test.ts | 3 - .../sim/lib/selectors/server/internal.test.ts | 52 - .../selectors/server/option-budget.test.ts | 3 - .../server/providers/atlassian.test.ts | 32 - .../server/providers/bigquery.test.ts | 82 - .../server/providers/bitbucket.test.ts | 25 - .../server/providers/clickup.test.ts | 4 - .../server/providers/cloudwatch.test.ts | 18 +- .../selectors/server/providers/coda.test.ts | 67 - .../server/providers/confluence.test.ts | 173 +- .../providers/credential-bundle.test.ts | 35 +- .../selectors/server/providers/github.test.ts | 2 - .../selectors/server/providers/google.test.ts | 116 - .../server/providers/harmonic.test.ts | 4 - .../server/providers/hubspot.test.ts | 67 - .../selectors/server/providers/imap.test.ts | 25 - .../selectors/server/providers/jira.test.ts | 4 - .../selectors/server/providers/jsm.test.ts | 4 - .../selectors/server/providers/linear.test.ts | 55 - .../server/providers/managed-agent.test.ts | 48 - .../selectors/server/providers/mcp.test.ts | 4 +- .../server/providers/microsoft.test.ts | 59 - .../selectors/server/providers/monday.test.ts | 17 - .../server/providers/netsuite.test.ts | 7 +- .../selectors/server/providers/notion.test.ts | 4 - .../server/providers/pipedrive.test.ts | 4 - .../server/providers/provider-http.test.ts | 4 - .../server/providers/sharepoint.test.ts | 24 - .../selectors/server/providers/slack.test.ts | 50 - .../server/providers/snowflake.test.ts | 39 - .../server/providers/webflow.test.ts | 36 - .../server/providers/zoho-desk.test.ts | 17 - .../selectors/server/providers/zoom.test.ts | 19 - .../lib/selectors/server/references.test.ts | 38 - .../sim/lib/selectors/server/sanitize.test.ts | 48 - .../organization-section-access.test.ts | 57 - .../workspace-section-access.test.ts | 171 - apps/sim/lib/sim-search/connectors.test.ts | 218 -- apps/sim/lib/sim-search/live/accounts.test.ts | 2 - .../lib/sim-search/live/application.test.ts | 503 --- apps/sim/lib/sim-search/live/coda-mcp.test.ts | 116 +- .../lib/sim-search/live/coda-service.test.ts | 14 - apps/sim/lib/sim-search/live/coda-uri.test.ts | 10 - apps/sim/lib/sim-search/live/coda.test.ts | 1 - apps/sim/lib/sim-search/live/dates.test.ts | 169 +- .../sim-search/live/github-service.test.ts | 45 - .../lib/sim-search/live/gitlab-admin.test.ts | 35 +- .../sim-search/live/google-service.test.ts | 212 +- apps/sim/lib/sim-search/live/http.test.ts | 55 +- .../lib/sim-search/live/mcp-accounts.test.ts | 2 - apps/sim/lib/sim-search/live/pages.test.ts | 10 +- apps/sim/lib/sim-search/live/policy.test.ts | 43 +- .../sim-search/live/provider-catalog.test.ts | 50 - .../sim/lib/sim-search/live/providers.test.ts | 639 +--- .../lib/sim-search/live/scoped-search.test.ts | 69 +- .../sim-search/live/service-session.test.ts | 65 - .../sim-search/live/service-sources.test.ts | 3 - .../lib/sim-search/live/slack-format.test.ts | 160 +- .../lib/sim-search/live/source-policy.test.ts | 41 - .../sim-search/live/source-settings.test.ts | 62 - apps/sim/lib/sim-search/live/text.test.ts | 46 - apps/sim/lib/sim-search/search-params.test.ts | 9 - .../lib/sim-search/setup-navigation.test.ts | 20 - .../lib/sim-search/source-identity.test.ts | 115 - apps/sim/lib/skills/access.test.ts | 4 - .../application/editor-use-cases.test.ts | 4 - .../lib/skills/application/operations.test.ts | 4 - .../lib/skills/application/use-cases.test.ts | 4 - .../orchestration/skill-lifecycle.test.ts | 4 - .../slack-search/app-configuration.test.ts | 2 - .../lib/slack-search/assistant-stream.test.ts | 743 ---- apps/sim/lib/slack-search/connections.test.ts | 24 - .../sim/lib/slack-search/conversation.test.ts | 1 - apps/sim/lib/slack-search/dispatcher.test.ts | 57 - apps/sim/lib/slack-search/home.test.ts | 49 +- apps/sim/lib/slack-search/manifest.test.ts | 129 - apps/sim/lib/slack-search/messages.test.ts | 25 - apps/sim/lib/slack-search/oauth-state.test.ts | 7 - .../lib/slack-search/onboarding-state.test.ts | 13 - apps/sim/lib/slack-search/onboarding.test.ts | 13 - .../slack-search/public-install-auth.test.ts | 2 - apps/sim/lib/slack-search/queue.test.ts | 13 - apps/sim/lib/slack-search/shared-app.test.ts | 30 - apps/sim/lib/slack-search/types.test.ts | 46 - apps/sim/lib/status-page.test.ts | 56 - .../__tests__/bulk-update-concurrency.test.ts | 4 - .../lib/table/__tests__/cell-format.test.ts | 80 - .../table/__tests__/column-conversion.test.ts | 62 - .../lib/table/__tests__/column-keys.test.ts | 46 - .../__tests__/column-type-registry.test.ts | 25 - apps/sim/lib/table/__tests__/currency.test.ts | 78 - .../table/__tests__/find-row-matches.test.ts | 36 - .../lib/table/__tests__/lock-order.test.ts | 3 - .../lib/table/__tests__/name-pattern.test.ts | 3 - .../lib/table/__tests__/select-values.test.ts | 26 +- .../service-filter-threading.test.ts | 249 +- apps/sim/lib/table/__tests__/sql.test.ts | 329 -- apps/sim/lib/table/__tests__/trigger.test.ts | 27 - .../lib/table/__tests__/update-row.test.ts | 240 -- .../lib/table/__tests__/validation.test.ts | 305 -- apps/sim/lib/table/admission-retry.test.ts | 4 - apps/sim/lib/table/api/route-policies.test.ts | 37 - .../table/application/authorization.test.ts | 5 - apps/sim/lib/table/application/bulk.test.ts | 429 --- .../sim/lib/table/application/columns.test.ts | 42 - .../sim/lib/table/application/context.test.ts | 190 - .../application/copilot-bulk-rows.test.ts | 14 - .../copilot-table-lifecycle.test.ts | 38 - .../sim/lib/table/application/exports.test.ts | 69 +- .../table/application/folder-paths.test.ts | 12 - .../sim/lib/table/application/folders.test.ts | 7 - apps/sim/lib/table/application/groups.test.ts | 697 ---- .../sim/lib/table/application/imports.test.ts | 242 -- .../lib/table/application/operations.test.ts | 48 - .../application/row-secret-provenance.test.ts | 22 - apps/sim/lib/table/application/rows.test.ts | 997 +----- apps/sim/lib/table/application/runs.test.ts | 171 - apps/sim/lib/table/application/tables.test.ts | 112 +- .../workspace-file-imports.test.ts | 53 - .../table/backfill-governed-subject.test.ts | 5 - apps/sim/lib/table/backfill-runner.test.ts | 4 - apps/sim/lib/table/billing.test.ts | 27 - apps/sim/lib/table/cascade-lock.test.ts | 4 - apps/sim/lib/table/cell-write.test.ts | 30 - .../column-types/extension-points.test.ts | 3 - apps/sim/lib/table/column-types/ttl.test.ts | 3 - apps/sim/lib/table/columns/memory.test.ts | 4 - .../lib/table/columns/option-locks.test.ts | 8 +- .../sim/lib/table/columns/retype-cell.test.ts | 15 - apps/sim/lib/table/columns/ttl-limit.test.ts | 22 +- .../table/columns/workflow-references.test.ts | 21 +- apps/sim/lib/table/constants.test.ts | 27 - apps/sim/lib/table/dates.test.ts | 50 - apps/sim/lib/table/delete-runner.test.ts | 116 - apps/sim/lib/table/deps.test.ts | 58 - .../lib/table/dispatch-concurrency.test.ts | 65 - .../table/dispatch-governed-subject.test.ts | 4 - apps/sim/lib/table/dispatcher.test.ts | 56 +- apps/sim/lib/table/events.attribution.test.ts | 125 - apps/sim/lib/table/events.test.ts | 45 +- apps/sim/lib/table/export-format.test.ts | 65 - apps/sim/lib/table/export-format.ts | 7 - apps/sim/lib/table/export-runner.test.ts | 13 - apps/sim/lib/table/ids.test.ts | 15 - apps/sim/lib/table/import-runner.test.ts | 52 - apps/sim/lib/table/import.test.ts | 194 -- apps/sim/lib/table/jobs/service.test.ts | 68 +- apps/sim/lib/table/llm/enrichment.test.ts | 190 +- apps/sim/lib/table/mutation-locks.test.ts | 18 - .../lib/table/orchestration/columns.test.ts | 78 - .../orchestration/import-resource.test.ts | 73 - .../lib/table/orchestration/import.test.ts | 80 - .../lib/table/orchestration/tables.test.ts | 45 +- .../table/prestamp-governed-subject.test.ts | 4 - .../__tests__/cell-filter.test.ts | 28 - .../__tests__/converters.test.ts | 136 +- .../query-builder/__tests__/predicate.test.ts | 24 - .../query-builder/__tests__/validate.test.ts | 67 +- .../resume-context-governed-subject.test.ts | 16 +- .../bounded-fetch-projection.test.ts | 3 - .../bulk-update-patch-validation.test.ts | 3 - .../lib/table/rows/__tests__/cursor.test.ts | 3 - .../rows/__tests__/ordering-anchor.test.ts | 54 +- .../rows/__tests__/ordering-delete.test.ts | 4 - apps/sim/lib/table/rows/cursor.test.ts | 88 - apps/sim/lib/table/rows/executions.test.ts | 56 - ...st.ts => secret-provenance.integration.ts} | 27 +- .../lib/table/rows/secret-provenance.test.ts | 253 -- .../table/run-column-governed-subject.test.ts | 4 - .../table/secret-provenance-selection.test.ts | 3 - apps/sim/lib/table/select-options.test.ts | 25 - apps/sim/lib/table/select-values.test.ts | 39 - apps/sim/lib/table/service.test.ts | 142 - apps/sim/lib/table/snapshot-cache.test.ts | 19 - .../lib/table/stale-dispatch-recovery.test.ts | 48 - apps/sim/lib/table/ttl-availability.test.ts | 24 +- apps/sim/lib/table/update-runner.test.ts | 85 +- apps/sim/lib/table/validation.test.ts | 75 - apps/sim/lib/table/views/service.test.ts | 349 -- .../lib/table/workflow-cell-result.test.ts | 4 - apps/sim/lib/table/workflow-columns.test.ts | 94 - .../table/workflow-group-cancellation.test.ts | 91 - .../lib/table/workflow-groups/service.test.ts | 34 - apps/sim/lib/telemetry/browser.test.ts | 70 - apps/sim/lib/terminal/resource-id.test.ts | 3 - apps/sim/lib/terminal/tab-label.test.ts | 3 - apps/sim/lib/terminal/transport.test.ts | 69 +- .../application/execute-tool.test.ts | 229 -- apps/sim/lib/tools/falai-pricing.test.ts | 3 - apps/sim/lib/uploads/archive.test.ts | 205 -- apps/sim/lib/uploads/client/download.test.ts | 50 - apps/sim/lib/uploads/client/logo-file.test.ts | 30 - .../lib/uploads/client/session-upload.test.ts | 100 +- .../lib/uploads/client/upload-session.test.ts | 71 - .../copilot/copilot-file-manager.test.ts | 2 - .../execution/execution-file-manager.test.ts | 15 - .../uploads/contexts/execution/utils.test.ts | 3 - .../application.test.ts | 53 - .../application.integration.ts | 3 +- .../organization-logo/application.test.ts | 50 - .../workspace/fetch-external-url.test.ts | 136 - .../workspace/track-chat-upload.test.ts | 251 +- .../workspace-file-folder-manager.test.ts | 130 - .../workspace-file-live-doc-outbox.test.ts | 11 - .../workspace-file-manager-download.test.ts | 29 - .../workspace-file-manager-errors.test.ts | 42 +- .../workspace-file-manager-page.test.ts | 2 - .../workspace/workspace-file-manager.test.ts | 4 - .../workspace/workspace-file-query.test.ts | 82 - .../workspace-file-reference.test.ts | 50 - .../workspace-file-secret-provenance.test.ts | 747 +--- .../workspace-file-storage-accounting.test.ts | 402 --- .../workspace-file-storage-billing.test.ts | 4 - ...kspace-file-storage-cleanup-outbox.test.ts | 38 - apps/sim/lib/uploads/core/errors.test.ts | 10 - .../core/knowledge-upload-cleanup.test.ts | 4 - .../lib/uploads/core/storage-client.test.ts | 16 - apps/sim/lib/uploads/core/storage-key.test.ts | 4 - .../core/storage-service-create-only.test.ts | 1 - ...age-service.blob-connection-string.test.ts | 2 - .../storage-service.local-download.test.ts | 24 - .../core/storage-service.local.test.ts | 41 +- .../lib/uploads/core/storage-service.test.ts | 91 +- .../lib/uploads/providers/blob/client.test.ts | 311 -- .../google-cloud-storage/client.test.ts | 375 +- .../lib/uploads/providers/s3/client.test.ts | 372 -- .../server/embedded-image-refs.test.ts | 3 - .../uploads/server/heic-pixel-guard.test.ts | 11 - apps/sim/lib/uploads/server/heic.test.ts | 19 - .../uploads/server/image-derivative.test.ts | 18 - .../lib/uploads/server/inline-image.test.ts | 25 - .../uploads/server/markdown-export.test.ts | 34 - apps/sim/lib/uploads/server/metadata.test.ts | 148 - .../upload-session/application.test.ts | 152 +- .../uploads/upload-session/cleanup.test.ts | 3 - .../uploads/upload-session/provider.test.ts | 216 -- .../uploads/upload-session/service.test.ts | 426 --- ... workspace-file-provenance.integration.ts} | 11 +- .../workspace-file-provenance.test.ts | 1 - .../utils/attachment-download-budget.test.ts | 13 - .../uploads/utils/embedded-image-ref.test.ts | 5 - .../uploads/utils/file-utils.server.test.ts | 41 - apps/sim/lib/uploads/utils/file-utils.test.ts | 147 - .../sim/lib/uploads/utils/model-input.test.ts | 101 +- .../utils/user-file-base64.server.test.ts | 251 -- apps/sim/lib/uploads/utils/validation.test.ts | 60 +- apps/sim/lib/uploads/zip-entry-path.test.ts | 43 - .../account-deletion-attachments.test.ts | 61 - ...count-deletion-cancel-announcement.test.ts | 4 - .../account-deletion-dispatch-cancel.test.ts | 4 - .../account-deletion-marker-cancel.test.ts | 20 - apps/sim/lib/users/account-deletion.test.ts | 57 - .../users/application/authorized-apps.test.ts | 70 +- .../users/application/delete-account.test.ts | 4 - .../preferences-authorization.test.ts | 26 - .../lib/users/application/preferences.test.ts | 70 +- .../application/read-current-user.test.ts | 41 +- apps/sim/lib/users/queries.test.ts | 23 +- apps/sim/lib/wand/strip-code-fences.test.ts | 55 +- .../lib/webhooks/delivery-predicate.test.ts | 55 - apps/sim/lib/webhooks/deploy.test.ts | 300 -- apps/sim/lib/webhooks/deployed-urls.test.ts | 4 - apps/sim/lib/webhooks/env-resolver.test.ts | 77 - .../lib/webhooks/execution-principal.test.ts | 3 - apps/sim/lib/webhooks/path-claims.test.ts | 3 - .../lib/webhooks/pending-verification.test.ts | 64 - .../lib/webhooks/polling/canonical.test.ts | 16 - .../lib/webhooks/polling/config-sql.test.ts | 4 - apps/sim/lib/webhooks/polling/hubspot.test.ts | 36 - apps/sim/lib/webhooks/polling/imap.test.ts | 4 - apps/sim/lib/webhooks/polling/rss.test.ts | 4 - apps/sim/lib/webhooks/polling/utils.test.ts | 111 +- apps/sim/lib/webhooks/processor.test.ts | 190 - .../webhooks/provider-subscriptions.test.ts | 51 - apps/sim/lib/webhooks/providers/ashby.test.ts | 151 +- .../lib/webhooks/providers/bitbucket.test.ts | 444 --- apps/sim/lib/webhooks/providers/clerk.test.ts | 82 - .../lib/webhooks/providers/clickup.test.ts | 214 -- .../lib/webhooks/providers/emailbison.test.ts | 103 - .../lib/webhooks/providers/generic.test.ts | 89 - .../sim/lib/webhooks/providers/github.test.ts | 163 - .../sim/lib/webhooks/providers/gitlab.test.ts | 98 - apps/sim/lib/webhooks/providers/gmail.test.ts | 27 - apps/sim/lib/webhooks/providers/gong.test.ts | 61 - apps/sim/lib/webhooks/providers/grain.test.ts | 119 - .../lib/webhooks/providers/granola.test.ts | 219 -- apps/sim/lib/webhooks/providers/imap.test.ts | 32 - .../lib/webhooks/providers/incidentio.test.ts | 163 - .../lib/webhooks/providers/instantly.test.ts | 285 +- apps/sim/lib/webhooks/providers/jira.test.ts | 117 - .../lib/webhooks/providers/jotform.test.ts | 142 - .../sim/lib/webhooks/providers/linear.test.ts | 76 - apps/sim/lib/webhooks/providers/loops.test.ts | 100 - .../providers/microsoft-teams.test.ts | 92 +- .../lib/webhooks/providers/quickbooks.test.ts | 47 +- .../sim/lib/webhooks/providers/resend.test.ts | 45 - .../lib/webhooks/providers/revenuecat.test.ts | 169 +- .../sim/lib/webhooks/providers/rootly.test.ts | 228 +- .../lib/webhooks/providers/salesforce.test.ts | 44 - .../lib/webhooks/providers/sendblue.test.ts | 63 - .../sim/lib/webhooks/providers/sentry.test.ts | 116 - apps/sim/lib/webhooks/providers/slack.test.ts | 282 -- .../sim/lib/webhooks/providers/stripe.test.ts | 78 - .../sim/lib/webhooks/providers/tiktok.test.ts | 160 - .../webhooks/providers/twilio-voice.test.ts | 125 - .../sim/lib/webhooks/providers/twilio.test.ts | 99 - .../sim/lib/webhooks/providers/vercel.test.ts | 16 - .../lib/webhooks/providers/whatsapp.test.ts | 63 - .../lib/webhooks/providers/zendesk.test.ts | 164 - .../lib/webhooks/providers/zoho-desk.test.ts | 157 +- apps/sim/lib/webhooks/providers/zoom.test.ts | 20 - .../webhooks/quickbooks-credentials.test.ts | 32 - .../webhooks/registration-identity.test.ts | 3 - .../registration-reconciliation.test.ts | 3 - .../lib/webhooks/registration-service.test.ts | 9 +- .../lib/webhooks/registration-store.test.ts | 16 - apps/sim/lib/webhooks/slack-agent-api.test.ts | 119 - .../lib/webhooks/slack-custom-ingress.test.ts | 16 - apps/sim/lib/webhooks/slack-dispatch.test.ts | 28 - .../webhooks/slack-execution-stream.test.ts | 466 --- .../lib/webhooks/slack-stream-config.test.ts | 58 - apps/sim/lib/webhooks/utils.server.test.ts | 18 +- .../lib/workflows/api/route-policies.test.ts | 19 +- .../apply-workflow-operations.test.ts | 102 - .../application/authorization.test.ts | 4 - .../lib/workflows/application/context.test.ts | 39 - .../download-workflow-run-file.test.ts | 30 - .../execute-manual-workflow.test.ts | 67 - .../application/execute-workflow.test.ts | 20 - .../application/import-export.test.ts | 93 - .../inspect-workflow-tools.test.ts | 80 - .../application/list-workflow-runs.test.ts | 19 - .../application/move-workflows-bulk.test.ts | 18 - .../workflows/application/operations.test.ts | 80 - .../read-paused-workflow-execution.test.ts | 40 - .../read-workflow-copilot-metadata.test.ts | 40 - .../read-workflow-definition.test.ts | 19 - .../read-workflow-deployment-overview.test.ts | 4 - .../application/read-workflow-graph.test.ts | 56 - .../read-workflow-lint-agent-tools.test.ts | 23 - .../read-workflow-lint-refs.test.ts | 79 - .../read-workflow-lint-tables.test.ts | 87 - .../application/read-workflow-run.test.ts | 33 - .../replace-workflow-state.test.ts | 178 - .../resolve-workflow-outputs.test.ts | 17 - .../application/restore-workflow.test.ts | 40 - .../run-workflow-from-copilot.test.ts | 179 - .../application/transition-result.test.ts | 7 - .../update-workflow-content.test.ts | 73 - .../application/workflow-crud.test.ts | 101 - .../workflow-custom-block-authoring.test.ts | 36 - .../application/workflow-deployments.test.ts | 162 - .../application/workflow-folders.test.ts | 77 - .../application/workflow-run-control.test.ts | 30 - .../application/workflow-runs.test.ts | 99 - .../application/workflow-vfs.test.ts | 55 +- .../workflows/autolayout/change-set.test.ts | 54 - .../sim/lib/workflows/autolayout/core.test.ts | 3 - .../lib/workflows/autolayout/targeted.test.ts | 154 - .../lib/workflows/autolayout/utils.test.ts | 130 - .../blocks/canvas-card-fields.test.ts | 13 - .../blocks/canvas-presentation.test.ts | 40 - .../blocks/canvas-sentence-imperative.test.ts | 33 - .../blocks/canvas-sentence-noun.test.ts | 46 - .../blocks/canvas-sentence-validation.test.ts | 221 -- .../blocks/canvas-sentence-view.test.tsx | 32 - .../workflows/blocks/canvas-sentence.test.ts | 167 - .../lib/workflows/blocks/code-preview.test.ts | 47 - .../blocks/deterministic-dimensions.test.ts | 43 - .../workflows/blocks/fallback-models.test.ts | 152 - .../provider-config-round-trip.test.ts | 2 - .../workflows/canonical/reported-bug.test.ts | 2 - .../lib/workflows/comparison/compare.test.ts | 2289 +----------- .../comparison/format-description.test.ts | 592 ---- .../workflows/comparison/normalize.test.ts | 440 --- apps/sim/lib/workflows/conditions.test.ts | 3 - .../credentials/credential-extractor.test.ts | 44 - .../application/settings.test.ts | 78 +- .../custom-blocks/child-execution.test.ts | 70 - .../custom-blocks/operations.test.ts | 23 - .../custom-tools/available-lookup.test.ts | 9 +- .../workflows/custom-tools/operations.test.ts | 19 +- .../workflows/deployment-lifecycle.test.ts | 3 - .../lib/workflows/deployment-outbox.test.ts | 49 - .../lib/workflows/diff/diff-engine.test.ts | 42 +- .../workflows/dynamic-handle-topology.test.ts | 8 - .../lib/workflows/editing/builders.test.ts | 74 - .../workflows/editing/dangling-refs.test.ts | 69 - .../lib/workflows/editing/lint-report.test.ts | 20 - apps/sim/lib/workflows/editing/lint.test.ts | 95 - .../lib/workflows/editing/operations.test.ts | 255 -- .../editing/sandbox-projection.test.ts | 2 - .../editing/selector-validator.test.ts | 4 - .../lib/workflows/editing/validation.test.ts | 612 ---- .../executor/enqueue-execution.test.ts | 60 - .../executor/execute-service.test.ts | 8 - .../executor/execute-workflow.test.ts | 154 - .../workflows/executor/execution-core.test.ts | 509 --- .../executor/execution-id-claim.test.ts | 15 - .../executor/execution-queries.test.ts | 56 +- .../executor/execution-run-files.test.ts | 41 +- .../executor/execution-state.test.ts | 62 - .../execution-status-projection.test.ts | 13 - .../executor/execution-status.test.ts | 55 - .../human-in-the-loop-manager.test.ts | 143 - .../executor/input-secret-provenance.test.ts | 5 - .../workflows/executor/resume-policy.test.ts | 17 - .../executor/start-run-identity.test.ts | 9 +- apps/sim/lib/workflows/input-format.test.ts | 167 - apps/sim/lib/workflows/lifecycle.test.ts | 4 - .../migrations/mcp-operation-controls.test.ts | 1 - .../migrations/subblock-migrations.test.ts | 294 -- .../whatsapp-interactive-type.test.ts | 97 - .../sim/lib/workflows/notes/add-image.test.ts | 11 - .../operations/export-workflow.test.ts | 5 - .../import-export-roundtrip.test.ts | 12 - .../operations/import-export.test.ts | 73 - .../operations/import-workflow.test.ts | 13 - .../orchestration/chat-deploy.test.ts | 14 - .../workflows/orchestration/deploy.test.ts | 64 - .../orchestration/workflow-lifecycle.test.ts | 23 - .../persistence/block-access-guard.test.ts | 12 - .../custom-tools-persistence.test.ts | 27 - .../persistence/deployment-operations.test.ts | 43 - .../workflows/persistence/duplicate.test.ts | 122 - .../persist-block-access-gate.test.ts | 30 - .../persistence/prepare-state.test.ts | 11 - .../persistence/remap-internal-ids.test.ts | 141 - .../replace-normalized-state.test.ts | 87 - .../persistence/save-normalized-state.test.ts | 14 - .../save-workflow-normalized-state.test.ts | 72 - .../lib/workflows/persistence/utils.test.ts | 715 +--- .../references/binding-credentials.test.ts | 2 - .../references/custom-block-reconfigs.test.ts | 46 - .../references/dependent-reconfigs.test.ts | 277 -- .../references/import-configuration.test.ts | 10 - .../workflows/references/import-plan.test.ts | 33 - .../lib/workflows/references/manifest.test.ts | 1 - .../references/preview-limits.test.ts | 1 - .../workflows/references/remap-files.test.ts | 14 - .../references/remap-references.test.ts | 508 --- .../references/selector-values.test.ts | 2 - .../sanitization/json-sanitizer.test.ts | 133 - .../workflows/sanitization/references.test.ts | 29 - .../workflows/sanitization/subblocks.test.ts | 23 - .../lib/workflows/schedules/deploy.test.ts | 480 +-- .../schedules/disable-notifications.test.ts | 56 - .../sim/lib/workflows/schedules/retry.test.ts | 40 - .../sim/lib/workflows/schedules/utils.test.ts | 455 --- .../indexer-selector-context.test.ts | 14 - .../workflows/search-replace/indexer.test.ts | 1451 +------- .../search-replace/replacements.test.ts | 570 --- .../search-replace/resources/registry.test.ts | 17 - .../resources/resolvers.test.ts | 52 - .../workflows/search-replace/state.test.ts | 3 - .../search-replace/subflow-fields.test.ts | 3 - .../workflows/skills/builtin-skills.test.ts | 34 - .../lib/workflows/skills/operations.test.ts | 38 - .../streaming/agent-stream-protocol.test.ts | 3 - .../forward-agent-stream-events.test.ts | 18 - .../streaming/streaming-principal.test.ts | 3 - .../lib/workflows/streaming/streaming.test.ts | 321 -- .../lib/workflows/subblocks/context.test.ts | 169 - .../workflows/subblocks/dependencies.test.ts | 41 - .../lib/workflows/subblocks/display.test.ts | 246 -- .../workflows/subblocks/visibility.test.ts | 216 -- .../workflows/tool-input/authoring.test.ts | 1 - .../tool-input/param-visibility.test.ts | 14 - .../tool-input/synthetic-subblocks.test.ts | 40 +- .../tool-input/usage-control.test.ts | 11 - .../workflows/triggers/run-options.test.ts | 48 - apps/sim/lib/workflows/utils.test.ts | 93 +- .../sim/lib/workflows/variables/parse.test.ts | 26 - .../variables/variable-manager.test.ts | 124 - apps/sim/lib/workspace-events/emitter.test.ts | 101 - .../lib/workspace-events/no-activity.test.ts | 73 - apps/sim/lib/workspace-events/payload.test.ts | 87 +- apps/sim/lib/workspace-events/rules.test.ts | 66 +- apps/sim/lib/workspace-events/state.test.ts | 48 - .../workspace-events/subscriptions.test.ts | 24 - .../api/internal-error-policies.test.ts | 10 - .../api/internal-presenters.test.ts | 6 - .../api/route-policies.test.ts | 19 - .../archive-workspace-file-items.test.ts | 51 - .../application/authorization.test.ts | 20 - .../application/delegated-principal.test.ts | 3 - .../application/delete-workspace-file.test.ts | 85 - .../download-workspace-file-items.test.ts | 77 - .../download-workspace-file.test.ts | 82 +- .../edit-workspace-file-content.test.ts | 47 - .../export-workspace-file-snapshot.test.ts | 68 - .../extract-workspace-file.test.ts | 89 - .../application/file-versions.test.ts | 165 - .../application/list-workspace-files.test.ts | 42 - .../move-workspace-file-items.test.ts | 47 - .../application/operations.test.ts | 9 - ...tored-workspace-file-record-by-key.test.ts | 30 - .../read-workspace-file-artifact.test.ts | 21 - ...read-workspace-file-content-by-key.test.ts | 4 - .../read-workspace-file-content.test.ts | 30 - .../read-workspace-file-metadata.test.ts | 101 +- .../read-workspace-file-record.test.ts | 4 - .../read-workspace-file-text.test.ts | 259 -- .../read-workspace-inline-file.test.ts | 4 - .../application/rename-workspace-file.test.ts | 67 - .../resolve-workspace-file-reference.test.ts | 116 +- .../restore-workspace-file.test.ts | 41 - .../search-workspace-file-content.test.ts | 20 - .../application/share-workspace-file.test.ts | 39 - .../update-workspace-file-content.test.ts | 14 - .../update-workspace-file-dimensions.test.ts | 4 - .../workspace-file-folders.test.ts | 228 -- .../artifact-stylesheet.test.ts | 291 -- .../workspace-files/directory-listing.test.ts | 76 - .../lib/workspace-files/edit-content.test.ts | 30 - .../folder-path-selection.test.ts | 79 - .../orchestration/create.test.ts | 89 - .../file-folder-lifecycle.test.ts | 145 +- .../page-compile-limits.test.ts | 38 - .../lib/workspace-files/page-compile.test.ts | 212 -- .../page-document.server.test.ts | 28 +- .../workspace-files/page-source-embed.test.ts | 7 - .../workspace-files/page-tabs-rail.test.ts | 191 - .../prepare-image-for-vision.test.ts | 5 +- apps/sim/lib/workspace-files/queries.test.ts | 51 - .../render-document-for-vision.test.ts | 5 +- apps/sim/lib/workspace-files/search/README.md | 2 +- .../workspace-files/search/admission.test.ts | 8 - .../search/chunks.integration.ts | 4 +- .../search/dispatcher.integration.ts | 4 +- .../workspace-files/search/dispatcher.test.ts | 26 - .../search/enqueue-dispatch.test.ts | 4 - .../workspace-files/search/extract.test.ts | 45 - .../search/index-batches.test.ts | 1 - .../search/index-plan.integration.ts | 7 +- .../workspace-files/search/indexing.test.ts | 54 +- .../lib/workspace-files/search/regex.test.ts | 7 - .../workspace-files/search/repository.test.ts | 15 - .../sim/lib/workspace-files/selection.test.ts | 3 - .../lib/workspace-files/shell-layout.test.ts | 57 - .../lib/workspaces/__integration__/README.md | 2 +- .../admin-move-source-impact.test.ts | 65 +- apps/sim/lib/workspaces/admin-move.test.ts | 578 ---- .../application/inbox-settings.test.ts | 61 +- .../list-organization-workspaces.test.ts | 60 - .../list-public-workspaces.test.ts | 85 - .../application/manage-permissions.test.ts | 25 +- .../public-workspace-reads.test.ts | 29 - .../record-workspace-visit.test.ts | 33 +- .../application/remove-member.test.ts | 73 - .../application/workspace-context.test.ts | 52 +- apps/sim/lib/workspaces/colors.test.ts | 19 +- apps/sim/lib/workspaces/create.test.ts | 89 +- apps/sim/lib/workspaces/host-context.test.ts | 64 - .../invitation-migration-plan.test.ts | 3 - apps/sim/lib/workspaces/lifecycle.test.ts | 4 - .../lib/workspaces/operations/refresh.test.ts | 14 +- ...=> organization-workspaces.integration.ts} | 13 +- .../organization-workspaces.test.ts | 131 - .../lib/workspaces/permissions/utils.test.ts | 698 +--- apps/sim/lib/workspaces/policy.test.ts | 426 --- .../sim/lib/workspaces/public-queries.test.ts | 52 +- apps/sim/lib/workspaces/sharing.test.ts | 25 - apps/sim/lib/workspaces/utils.test.ts | 125 - apps/sim/lib/workspaces/visits.test.ts | 31 +- apps/sim/next.config.test.ts | 3 - apps/sim/package.json | 4 +- .../__fixtures__/anthropic/fixtures.test.ts | 13 - .../sim/providers/agent-events-smokes.test.ts | 67 - .../providers/anthropic/core.request.test.ts | 3 - .../anthropic/core.streaming.test.ts | 3 - .../providers/anthropic/core.thinking.test.ts | 2 - .../anthropic/request-history.test.ts | 3 - .../anthropic/streaming-tool-loop.test.ts | 3 - apps/sim/providers/anthropic/usage.test.ts | 3 - apps/sim/providers/anthropic/utils.test.ts | 2 - apps/sim/providers/attachments.test.ts | 3 - .../providers/azure-anthropic/index.test.ts | 4 - apps/sim/providers/azure-openai/index.test.ts | 4 - apps/sim/providers/baseten/index.test.ts | 25 - apps/sim/providers/bedrock/index.test.ts | 23 - .../providers/bedrock/request-history.test.ts | 1 - .../bedrock/streaming-tool-loop.test.ts | 4 - .../providers/bedrock/utils.stream.test.ts | 3 - apps/sim/providers/bedrock/utils.test.ts | 3 - apps/sim/providers/client-cache.test.ts | 3 - .../conversation-attachments.test.ts | 1 - .../conversation-continuation.test.ts | 2 - .../conversation-generation-coverage.test.ts | 1 - .../providers/conversation-generation.test.ts | 1 - .../providers/conversation-history.test.ts | 1 - apps/sim/providers/conversation-model.test.ts | 1 - .../conversation-prompt-identity.test.ts | 1 - apps/sim/providers/conversation-smoke.test.ts | 1 - .../providers/conversation-summary.test.ts | 4 - apps/sim/providers/cost-policy.test.ts | 3 - apps/sim/providers/custom-block-tool.test.ts | 9 +- apps/sim/providers/deepseek/index.test.ts | 3 - .../file-attachments-authorization.test.ts | 3 - .../providers/file-attachments.server.test.ts | 4 - apps/sim/providers/fireworks/index.test.ts | 19 - apps/sim/providers/fireworks/utils.test.ts | 3 - apps/sim/providers/gemini/core.cost.test.ts | 4 - .../sim/providers/gemini/core.request.test.ts | 4 - .../providers/gemini/core.streaming.test.ts | 9 +- .../gemini/streaming-tool-loop.test.ts | 4 - apps/sim/providers/gemini/usage.test.ts | 11 - .../sim/providers/google/utils.stream.test.ts | 3 - apps/sim/providers/google/utils.test.ts | 214 -- apps/sim/providers/groq/index.test.ts | 3 - apps/sim/providers/history-adapters.test.ts | 1 - apps/sim/providers/index.test.ts | 16 - apps/sim/providers/litellm/index.test.ts | 4 - apps/sim/providers/mistral/index.test.ts | 4 - apps/sim/providers/models.test.ts | 302 -- apps/sim/providers/models.ts | 32 - apps/sim/providers/ollama-cloud/index.test.ts | 285 +- apps/sim/providers/ollama/index.test.ts | 4 - .../openai-compat/stream-events.test.ts | 3 - .../openai-compat/streaming-tool-loop.test.ts | 4 - .../providers/openai/core.cache-key.test.ts | 3 - .../providers/openai/core.reasoning.test.ts | 3 - .../openai/core.response-status.test.ts | 3 - .../openai/core.transport-phase.test.ts | 3 - apps/sim/providers/openai/usage.test.ts | 3 - .../sim/providers/openai/utils.stream.test.ts | 3 - apps/sim/providers/openai/utils.test.ts | 3 - apps/sim/providers/openrouter/index.test.ts | 26 - apps/sim/providers/openrouter/utils.test.ts | 1 - apps/sim/providers/pricing.test.ts | 3 - apps/sim/providers/runtime-context.test.ts | 9 +- .../providers/settled-tool-streams.test.ts | 4 - .../providers/specialist-reasoning.test.ts | 3 - apps/sim/providers/stream-events.test.ts | 3 - apps/sim/providers/stream-pump.test.ts | 3 - .../sim/providers/streaming-execution.test.ts | 3 - .../streaming-tool-loop-shared.test.ts | 3 - apps/sim/providers/together/index.test.ts | 30 - apps/sim/providers/tool-binding.test.ts | 9 - apps/sim/providers/tool-identity.test.ts | 3 - .../sim/providers/tool-schema-adapter.test.ts | 114 - apps/sim/providers/transport.test.ts | 2 - apps/sim/providers/typesafe/typesafe.test.ts | 1 - apps/sim/providers/utils.test.ts | 784 ----- apps/sim/providers/utils.ts | 29 - apps/sim/providers/vertex/index.test.ts | 4 - apps/sim/providers/vllm/index.test.ts | 4 - apps/sim/providers/xai/index.test.ts | 13 - apps/sim/proxy.test.ts | 102 - ...ackfill-table-workflow-deployments.test.ts | 182 +- apps/sim/scripts/backfill-trace-spans.test.ts | 228 -- .../scripts/block-registry-snapshot.test.ts | 1 - apps/sim/scripts/create-oauth-client.test.ts | 23 +- apps/sim/scripts/function-e2b-release.test.ts | 16 - .../scripts/function-sandbox-packages.test.ts | 26 - .../function-sandbox-parity-manifest.test.ts | 3 - apps/sim/scripts/pi-sandbox-packages.test.ts | 3 - apps/sim/serializer/checkbox-list.test.ts | 2 - .../serializer/custom-block-lifecycle.test.ts | 6 +- apps/sim/serializer/field-analysis.test.ts | 75 - apps/sim/serializer/index.test.ts | 426 --- apps/sim/serializer/mcp-operations.test.ts | 1 - apps/sim/serializer/private-inputs.test.ts | 5 - .../serializer/tests/dual-validation.test.ts | 379 -- .../tests/serializer.extended.test.ts | 1078 +----- apps/sim/stores/browser-session/store.test.ts | 292 -- apps/sim/stores/connector-setup/store.test.ts | 23 - .../sim/stores/copilot-terminal/store.test.ts | 128 - .../sim/stores/execution/store-hooks.test.tsx | 56 - apps/sim/stores/execution/store.test.ts | 520 +-- apps/sim/stores/file-viewer/store.test.ts | 28 - apps/sim/stores/index.test.ts | 1 - apps/sim/stores/modals/search/store.test.ts | 153 - .../stores/mothership-drafts/store.test.ts | 16 - .../sim/stores/mothership-queue/store.test.ts | 143 - apps/sim/stores/operation-queue/store.test.ts | 283 -- .../organization-chat-mode/store.test.ts | 17 - apps/sim/stores/panel/editor/store.test.ts | 59 - apps/sim/stores/reset-all-stores.test.ts | 4 - .../resource-list-preferences/store.test.ts | 104 - apps/sim/stores/settings/dirty/store.test.ts | 16 - apps/sim/stores/sidebar/store.test.ts | 98 +- apps/sim/stores/table/store.test.ts | 63 - apps/sim/stores/table/view-pin/store.test.ts | 42 - .../stores/terminal/console/storage.test.ts | 37 - .../sim/stores/terminal/console/store.test.ts | 199 -- .../sim/stores/terminal/console/utils.test.ts | 77 +- apps/sim/stores/undo-redo/store.test.ts | 411 --- apps/sim/stores/undo-redo/utils.test.ts | 282 +- .../stores/user-data-reset-registry.test.ts | 18 - apps/sim/stores/workflow-diff/store.test.ts | 224 -- apps/sim/stores/workflow-diff/utils.test.ts | 3 - .../stores/workflows/registry/store.test.ts | 90 - .../stores/workflows/registry/utils.test.ts | 121 - .../stores/workflows/sync-local-draft.test.ts | 121 - apps/sim/stores/workflows/utils.test.ts | 563 +-- .../workflow/edge-validation.test.ts | 29 - .../stores/workflows/workflow/store.test.ts | 963 +----- .../stores/workflows/workflow/utils.test.ts | 73 - apps/sim/tools/a2a/send_message.test.ts | 4 - .../tools/agiloft/retrieve_attachment.test.ts | 23 - apps/sim/tools/airtable/airtable.test.ts | 3 - apps/sim/tools/asana/operations.test.ts | 215 -- apps/sim/tools/ashby/ashby.live.test.ts | 4 +- apps/sim/tools/ashby/ashby.test.ts | 3 - apps/sim/tools/ashby/utils.test.ts | 3 - apps/sim/tools/assistant-execution.test.ts | 4 - .../tools/azure_data_explorer/utils.test.ts | 3 - .../tools/azure_devops/azure-devops.test.ts | 237 -- apps/sim/tools/binary-downloads.test.ts | 1 - apps/sim/tools/bitbucket/pipelines.test.ts | 9 +- .../sim/tools/bitbucket/pull-requests.test.ts | 3 - .../tools/bitbucket/repository-source.test.ts | 57 - apps/sim/tools/bitbucket/utils.server.test.ts | 4 - apps/sim/tools/bitbucket/utils.test.ts | 3 - apps/sim/tools/brex/idempotency.test.ts | 3 - apps/sim/tools/brex/utils.test.ts | 3 - apps/sim/tools/buffer/operation-input.test.ts | 16 - apps/sim/tools/calendly/calendly.test.ts | 3 - apps/sim/tools/cbinsights/cbinsights.test.ts | 3 - apps/sim/tools/circleback/circleback.test.ts | 2 - apps/sim/tools/cloudflare/cloudflare.test.ts | 114 - .../cloudtrail/contract-validation.test.ts | 191 - .../tools/cloudtrail/operation-input.test.ts | 76 - apps/sim/tools/cloudwatch/listing.test.ts | 4 - apps/sim/tools/coda/coda.live.test.ts | 6 +- apps/sim/tools/coda/coda.test.ts | 74 +- apps/sim/tools/context_dev/hosting.test.ts | 15 - .../sim/tools/crowdstrike/crowdstrike.test.ts | 215 -- apps/sim/tools/crunchbase/crunchbase.test.ts | 3 - .../tools/cursor/download_artifact.test.ts | 33 - apps/sim/tools/cursor/operation-input.test.ts | 15 - apps/sim/tools/datadog/datadog.test.ts | 47 - apps/sim/tools/datagma-hosting.test.ts | 17 - .../sim/tools/daytona/operation-input.test.ts | 12 - .../tools/daytona/sandbox_path_safety.test.ts | 2 - .../tools/deployments/operation-input.test.ts | 43 - .../tools/docusign/download_document.test.ts | 51 - apps/sim/tools/dropcontact-hosting.test.ts | 10 - apps/sim/tools/dynatrace/dynatrace.test.ts | 26 - .../elasticsearch/cluster_health.test.ts | 3 - .../sim/tools/elasticsearch/responses.test.ts | 25 - apps/sim/tools/elasticsearch/search.test.ts | 3 - apps/sim/tools/elasticsearch/utils.test.ts | 3 - apps/sim/tools/elevenlabs/operation.test.ts | 38 - apps/sim/tools/embeddings/factory.test.ts | 11 - apps/sim/tools/enrichment-hosting.test.ts | 20 - apps/sim/tools/enrow-hosting.test.ts | 15 - apps/sim/tools/enrow/find_email.test.ts | 13 +- apps/sim/tools/exa/exa.test.ts | 53 - apps/sim/tools/falai-hosting.test.ts | 3 - .../file-message-operation-security.test.ts | 4 - .../sim/tools/file-message-operations.test.ts | 4 - .../file-message-provider-clients.test.ts | 4 - apps/sim/tools/file/append.test.ts | 3 - apps/sim/tools/file/compress.test.ts | 120 - apps/sim/tools/file/edit.test.ts | 3 - apps/sim/tools/file/parser.test.ts | 16 +- apps/sim/tools/file/provenance.test.ts | 55 - apps/sim/tools/file/search.test.ts | 41 - apps/sim/tools/firecrawl/hosting.test.ts | 13 - apps/sim/tools/firecrawl/model-input.test.ts | 3 - apps/sim/tools/function/execute.test.ts | 49 - ...ranch_protection_and_workflow_runs.test.ts | 64 +- apps/sim/tools/github/comment.test.ts | 15 - .../sim/tools/github/create_pr_review.test.ts | 3 - apps/sim/tools/github/job_logs.test.ts | 3 - .../tools/github/list_review_threads.test.ts | 3 - apps/sim/tools/github/pr.test.ts | 3 - .../tools/github/reply_review_thread.test.ts | 3 - .../github/resolve_review_thread.test.ts | 3 - apps/sim/tools/github/review-schema.test.ts | 4 - .../tools/github/status_check_rollup.test.ts | 3 - apps/sim/tools/github/utils.server.test.ts | 2 - apps/sim/tools/gitlab/access.test.ts | 3 - apps/sim/tools/gitlab/groups.test.ts | 56 - apps/sim/tools/gitlab/utils.test.ts | 3 - apps/sim/tools/gmail/read.test.ts | 4 - apps/sim/tools/gmail/utils.test.ts | 3 - apps/sim/tools/google_appsheet/utils.test.ts | 3 - .../sim/tools/google_calendar/respond.test.ts | 3 - .../google_drive/operation-input.test.ts | 37 - apps/sim/tools/google_sheets/filter.test.ts | 3 - apps/sim/tools/google_sheets/read.test.ts | 3 - .../google_slides/export_presentation.test.ts | 52 - apps/sim/tools/grafana/operation.test.ts | 18 - apps/sim/tools/granola/granola.test.ts | 2 - apps/sim/tools/guardrails/validate.test.ts | 14 - apps/sim/tools/harmonic/harmonic.test.ts | 108 - apps/sim/tools/http/request.test.ts | 152 - apps/sim/tools/hunter/hunter.test.ts | 3 - apps/sim/tools/icypeas-hosting.test.ts | 12 - .../identity_center/describe_user.test.ts | 23 - apps/sim/tools/incidentio/alerts_list.test.ts | 3 - .../sim/tools/incidentio/output_drift.test.ts | 311 -- apps/sim/tools/index.test.ts | 157 - .../get_conversation_messages.test.ts | 3 - .../instagram/get_publishing_limit.test.ts | 3 - apps/sim/tools/instagram/utils.test.ts | 3 - .../tools/jira/attachment-downloads.test.ts | 4 - apps/sim/tools/jira/operation-input.test.ts | 44 - apps/sim/tools/jira/utils.test.ts | 3 - apps/sim/tools/jotform/jotform.test.ts | 3 - .../tools/jupyter/content-transforms.test.ts | 164 - apps/sim/tools/knowledge/knowledge.test.ts | 2 - .../tools/knowledge/secret-provenance.test.ts | 3 - apps/sim/tools/lambda/supplied.test.ts | 27 - apps/sim/tools/langsmith/langsmith.test.ts | 166 - apps/sim/tools/leadmagic-hosting.test.ts | 27 - apps/sim/tools/linear/utils.test.ts | 3 - apps/sim/tools/linkedin/share_post.test.ts | 4 - apps/sim/tools/llm/chat.test.ts | 36 - apps/sim/tools/logfire/get_token_info.test.ts | 18 - apps/sim/tools/logfire/get_trace.test.ts | 3 - apps/sim/tools/logfire/query.test.ts | 15 - apps/sim/tools/logfire/search_records.test.ts | 9 - apps/sim/tools/logfire/utils.test.ts | 3 - apps/sim/tools/logs/operation-input.test.ts | 18 - .../tools/mail-submissions-operation.test.ts | 13 - .../tools/managed_agent/get_session.test.ts | 3 - .../tools/managed_agent/list_events.test.ts | 3 - .../tools/managed_agent/normalizers.test.ts | 3 - .../respond_tool_confirmation.test.ts | 4 - .../tools/managed_agent/run_session.test.ts | 4 - .../managed_agent/update_session.test.ts | 3 - .../manageengine_sdp/entity-builders.test.ts | 3 - apps/sim/tools/manageengine_sdp/utils.test.ts | 3 - apps/sim/tools/mem0/add_memories.test.ts | 8 - apps/sim/tools/mem0/get_memories.test.ts | 15 - apps/sim/tools/mem0/search_memories.test.ts | 8 - apps/sim/tools/memory/get.test.ts | 20 - apps/sim/tools/memory/operation-input.test.ts | 41 - apps/sim/tools/metadata.test.ts | 3 - .../add_user_app_role_assignment.test.ts | 3 - .../tools/microsoft_ad/advanced_query.test.ts | 2 - .../tools/microsoft_ad/descriptions.test.ts | 36 - .../microsoft_ad/list_user_devices.test.ts | 3 - .../tools/microsoft_ad/output_schema.test.ts | 110 - .../dynamics_crm.test.ts | 25 - apps/sim/tools/microsoft_excel/utils.test.ts | 3 - .../attachment-downloads.test.ts | 4 - apps/sim/tools/modal/call_function.test.ts | 3 - apps/sim/tools/modal/chat_completion.test.ts | 3 - apps/sim/tools/modal/utils.test.ts | 3 - apps/sim/tools/model-input-selectors.test.ts | 3 - .../tools/nested-model-input-adapters.test.ts | 3 - apps/sim/tools/netsuite/netsuite.test.ts | 332 -- apps/sim/tools/netsuite/utils.test.ts | 3 - apps/sim/tools/normalize.test.ts | 3 - apps/sim/tools/notion/responses.test.ts | 3 - apps/sim/tools/okta/tools.test.ts | 41 - .../tools/okta/user_path_descriptions.test.ts | 106 - apps/sim/tools/okta/utils.test.ts | 3 - .../tools/onepassword/get_item_file.test.ts | 26 - .../opaque-model-input-selectors.test.ts | 3 - apps/sim/tools/operation-input.test.ts | 3 - apps/sim/tools/operation-model-input.test.ts | 3 - .../outlook/calendar-idempotency.test.ts | 3 - apps/sim/tools/outlook/calendar-tools.test.ts | 42 - apps/sim/tools/outlook/calendar-utils.test.ts | 3 - apps/sim/tools/outlook/get_attachment.test.ts | 30 - apps/sim/tools/outlook/read.test.ts | 4 - apps/sim/tools/parallel/parallel.test.ts | 37 - apps/sim/tools/param-shape.test.ts | 3 - apps/sim/tools/params-resolver.test.ts | 3 - apps/sim/tools/params.test.ts | 39 - apps/sim/tools/pipedrive/utils.test.ts | 3 - apps/sim/tools/pitchbook/pitchbook.test.ts | 49 - apps/sim/tools/qdrant/search_vector.test.ts | 3 - .../sim/tools/quickbooks/api_accuracy.test.ts | 3 - apps/sim/tools/quickbooks/block.test.ts | 46 - apps/sim/tools/quickbooks/documents.test.ts | 3 - apps/sim/tools/quickbooks/fault.test.ts | 3 - apps/sim/tools/quickbooks/full_update.test.ts | 3 - apps/sim/tools/quickbooks/reports.test.ts | 3 - .../quickbooks/void_bill_payment.test.ts | 20 - apps/sim/tools/quiver/operation.test.ts | 76 - apps/sim/tools/rabbitmq/rabbitmq.test.ts | 3 - apps/sim/tools/sailpoint/definitions.test.ts | 143 - apps/sim/tools/salesforce/utils.test.ts | 3 - .../tools/sap_s4hana/operation-input.test.ts | 75 - apps/sim/tools/schema-enrichers.test.ts | 5 - apps/sim/tools/semrush/semrush.test.ts | 116 - apps/sim/tools/serper/search.test.ts | 10 - apps/sim/tools/servicenow/servicenow.test.ts | 188 +- apps/sim/tools/sftp/download.test.ts | 67 - apps/sim/tools/slack/agent_sessions.test.ts | 3 - apps/sim/tools/slack/canvas.test.ts | 1 - apps/sim/tools/slack/list_channels.test.ts | 11 - apps/sim/tools/slack/oauth-scopes.test.ts | 3 - apps/sim/tools/slack/operations.test.ts | 55 - .../tools/slack/workflow-operations.test.ts | 1 - apps/sim/tools/slack_lists/operations.test.ts | 1 - apps/sim/tools/snowflake/utils.test.ts | 294 -- apps/sim/tools/splunk/errors.test.ts | 45 - apps/sim/tools/splunk/requests.test.ts | 50 - apps/sim/tools/splunk/utils.test.ts | 3 - apps/sim/tools/sqs/contract-guards.test.ts | 47 - apps/sim/tools/square/idempotency.test.ts | 3 - apps/sim/tools/ssh/download_file.test.ts | 47 - apps/sim/tools/ssh/operation-input.test.ts | 83 - apps/sim/tools/ssm/cancel_command.test.ts | 38 - apps/sim/tools/stripe/idempotency.test.ts | 3 - apps/sim/tools/stt/model-input.test.ts | 61 - apps/sim/tools/stt/operation.test.ts | 75 - apps/sim/tools/supabase/utils.test.ts | 3 - apps/sim/tools/table/catalog.test.ts | 24 - apps/sim/tools/table/compatibility.test.ts | 42 - apps/sim/tools/table/query_rows_v2.test.ts | 3 - apps/sim/tools/table/response.test.ts | 1 - apps/sim/tools/textract/textract.test.ts | 3 - apps/sim/tools/tiktok/api-schemas.test.ts | 49 - apps/sim/tools/tiktok/utils.test.ts | 3 - apps/sim/tools/tinyfish/tinyfish.test.ts | 51 - apps/sim/tools/tts/operations.test.ts | 58 - apps/sim/tools/typeform/files.test.ts | 24 - .../tools/uptimerobot/operation-input.test.ts | 36 - apps/sim/tools/url-path.test.ts | 3 - apps/sim/tools/utils.test.ts | 121 +- apps/sim/tools/vanta/operation-input.test.ts | 49 - .../vercel/edge_config_path_safety.test.ts | 2 - apps/sim/tools/video/operation.test.ts | 51 +- apps/sim/tools/vision/operation.test.ts | 66 - apps/sim/tools/webflow/webflow.test.ts | 3 - apps/sim/tools/whatsapp/whatsapp.test.ts | 68 - apps/sim/tools/windchill/registry.test.ts | 15 - apps/sim/tools/windchill/windchill.test.ts | 207 +- .../tools/wordpress/operation-input.test.ts | 12 - apps/sim/tools/workflow/executor.test.ts | 14 - apps/sim/tools/zoho_desk/list_tickets.test.ts | 3 - .../tools/zoho_desk/operation-input.test.ts | 12 - .../sim/tools/zoho_desk/update_ticket.test.ts | 3 - apps/sim/tools/zoho_desk/utils.test.ts | 3 - apps/sim/tools/zoom/operation-input.test.ts | 12 - apps/sim/triggers/bitbucket/utils.test.ts | 198 -- apps/sim/triggers/constants.test.ts | 26 - .../triggers/credential-group/event.test.ts | 22 - apps/sim/triggers/generic/webhook.test.ts | 78 - apps/sim/triggers/greenhouse/utils.test.ts | 36 - apps/sim/triggers/linear/utils.test.ts | 8 - .../triggers/quickbooks/quickbooks.test.ts | 82 - apps/sim/triggers/sim/workspace-event.test.ts | 252 +- apps/sim/triggers/slack/capabilities.test.ts | 63 - apps/sim/triggers/slack/oauth.test.ts | 70 - apps/sim/triggers/table/poller.test.ts | 26 - apps/sim/triggers/vercel/utils.test.ts | 12 - apps/sim/triggers/webhook-url.test.ts | 53 +- apps/sim/vitest.config.ts | 55 +- ...n.setup.ts => vitest.integration.setup.ts} | 44 +- apps/sim/vitest.setup.ts | 2 +- .../vitest.workflows-integration.config.ts | 17 - bun.lock | 131 +- package.json | 7 +- packages/audit/package.json | 2 +- packages/audit/src/log.test.ts | 373 +- packages/audit/src/updated-fields.test.ts | 35 - packages/audit/vitest.config.ts | 2 - packages/db/connection-url.test.ts | 17 - ... embedding-width-migration.integration.ts} | 6 +- packages/db/knowledge-projection.test.ts | 54 - ...mber-sync-status-migration.integration.ts} | 4 +- ...anization-search-migration.integration.ts} | 9 +- packages/db/package.json | 2 +- ...-access-requests-migration.integration.ts} | 9 +- ...rations-paused-billing-attribution.test.ts | 81 +- ...cript-migrations-provenance-repair.test.ts | 42 - ...04_backfill_fork_kb_file_ownership.test.ts | 53 - ...epair_unknown_table_row_provenance.test.ts | 52 - ...credential_group_resource_policies.test.ts | 105 +- ...cy_knowledge_connector_credentials.test.ts | 3 - ..._knowledge_base_workspaces.integration.ts} | 6 +- ...l_legacy_knowledge_base_workspaces.test.ts | 21 - ...quire_knowledge_base_owner.integration.ts} | 6 +- ...16_backfill_search_vectors.integration.ts} | 4 +- .../0016_backfill_search_vectors.test.ts | 19 - ...pace_file_content_revision.integration.ts} | 4 +- ...019_tin_keyword_projection.integration.ts} | 4 +- .../0019_tin_keyword_projection.test.ts | 23 - ...embedding_search_connector.integration.ts} | 4 +- .../0021_embedding_search_connector.test.ts | 41 +- packages/db/script-migrations/index.test.ts | 3 - .../db/scripts/backfill-api-key-hash.test.ts | 34 +- ...or-sync-schedule-precision.integration.ts} | 0 ...ase-failure-classification.integration.ts} | 0 .../db/scripts/lock-timeout-retry.test.ts | 39 - .../scripts/migrate-slack-custom-bots.test.ts | 271 -- packages/db/scripts/migration-fixture.ts | 4 +- ...h.postgres.test.ts => push.integration.ts} | 2 +- packages/db/scripts/push.test.ts | 62 - ...test.ts => retired-columns.integration.ts} | 0 packages/db/testing/test-infrastructure.ts | 50 + packages/db/timestamps.test.ts | 37 - packages/db/tx-tripwire.test.ts | 71 - packages/db/vitest.config.ts | 29 +- packages/db/vitest.integration.setup.ts | 5 + packages/emcn/package.json | 2 +- .../src/components/avatar/avatar.test.tsx | 127 - .../bulk-action-button.test.tsx | 156 - .../calendar/calendar-interaction.test.tsx | 46 - .../src/components/calendar/calendar.test.ts | 81 +- .../charts/chart-accessibility.test.tsx | 34 - .../components/charts/chart-geometry.test.ts | 99 +- .../components/charts/chart-layout.test.tsx | 474 --- .../components/charts/chart-tooltip.test.ts | 110 - .../chip-button-group.test.tsx | 157 - .../chip-dropdown/chip-dropdown.test.tsx | 140 - .../chip-emails-input.test.tsx | 36 - .../components/chip-input/chip-input.test.tsx | 59 - .../components/chip-modal/chip-modal.test.tsx | 909 ----- .../chip-select/chip-select.dom.test.tsx | 60 - .../chip-select/chip-select.test.ts | 23 +- .../chip-switch/chip-switch.test.tsx | 172 - .../emcn/src/components/code/code.test.tsx | 62 - .../components/combobox/combobox.dom.test.tsx | 403 --- .../composer-action-button.test.tsx | 122 - .../details-panel/details-panel.test.tsx | 60 - .../dropdown-menu/dropdown-menu.test.tsx | 273 -- .../src/components/lightbox/lightbox.test.tsx | 142 - .../modal/modal-interactions.test.tsx | 301 -- .../emcn/src/components/modal/modal.test.tsx | 122 +- .../overflow-text/overflow-text.test.tsx | 302 -- .../tab-strip/tab-strip.dom.test.tsx | 551 --- .../components/tab-strip/tab-strip.test.ts | 61 +- .../components/tag-input/tag-input.test.tsx | 40 - .../emcn/src/components/toast/toast.test.tsx | 144 - .../src/components/tooltip/tooltip.test.tsx | 136 - .../upload-preview-button.test.tsx | 82 - .../src/hooks/use-copy-to-clipboard.test.ts | 27 - .../emcn/src/hooks/use-scroll-edges.test.tsx | 157 - packages/emcn/src/lib/cn.test.ts | 81 - .../emcn/src/lib/preview-wheel-zoom.test.ts | 136 - packages/emcn/vitest.config.ts | 2 - packages/logger/package.json | 2 +- packages/logger/src/index.test.ts | 291 -- packages/logger/src/request-context.test.ts | 33 - packages/logger/vitest.config.ts | 2 - packages/realtime-protocol/package.json | 2 +- packages/realtime-protocol/src/rooms.test.ts | 73 +- packages/realtime-protocol/vitest.config.ts | 2 - packages/runtime-secrets/package.json | 2 +- packages/runtime-secrets/src/index.test.ts | 38 - packages/runtime-secrets/vitest.config.ts | 7 - packages/security/package.json | 2 +- packages/security/src/compare.test.ts | 22 - packages/security/src/dns.test.ts | 60 +- packages/security/src/egress.test.ts | 248 -- packages/security/src/encryption.test.ts | 32 - packages/security/src/hash.test.ts | 12 - packages/security/src/hmac.test.ts | 31 - packages/security/src/hostnames.test.ts | 21 +- packages/security/src/ip.test.ts | 31 - packages/security/src/ssrf.test.ts | 17 - packages/security/src/tokens.test.ts | 11 - packages/security/vitest.config.ts | 2 - packages/sim-cli/package.json | 2 +- packages/sim-cli/src/auth/device-flow.test.ts | 152 - packages/sim-cli/src/auth/oauth-flow.test.ts | 129 +- .../sim-cli/src/auth/refresh.process.test.ts | 1 - packages/sim-cli/src/auth/refresh.test.ts | 18 - packages/sim-cli/src/commands/auth.test.ts | 790 ----- .../sim-cli/src/commands/configure.test.ts | 154 - .../sim-cli/src/commands/credentials.test.ts | 158 - .../src/commands/protocol/chat.test.ts | 108 +- .../src/commands/protocol/files-get.test.ts | 219 +- .../commands/protocol/files-upload.test.ts | 28 +- .../knowledge-document-upload.test.ts | 99 - .../protocol/knowledge-export.test.ts | 127 +- .../src/commands/protocol/logs-follow.test.ts | 322 +- .../protocol/resource-directory.test.ts | 148 - .../commands/protocol/tables-import.test.ts | 124 +- .../protocol/workflow-run-follow.test.ts | 384 +- .../protocol/workflow-run-get.test.ts | 51 - .../protocol/workflow-run-wait.test.ts | 130 - .../protocol/workspace-operation-wait.test.ts | 80 +- packages/sim-cli/src/commands/secrets.test.ts | 139 - .../sim-cli/src/commands/telemetry.test.ts | 23 - packages/sim-cli/src/commands/update.test.ts | 52 - packages/sim-cli/src/config/ini.test.ts | 154 +- packages/sim-cli/src/config/profile.test.ts | 399 --- .../sim-cli/src/contract/commands.test.ts | 519 +-- .../sim-cli/src/contract/reference.test.ts | 125 - packages/sim-cli/src/embed-output.test.ts | 80 +- .../sim-cli/src/embed-presentation.test.ts | 38 +- packages/sim-cli/src/embed.test.ts | 145 - packages/sim-cli/src/http/client.test.ts | 712 +--- packages/sim-cli/src/http/environment.test.ts | 52 - packages/sim-cli/src/output/render.test.ts | 225 +- packages/sim-cli/src/program.test.ts | 122 - packages/sim-cli/src/runtime/build.test.ts | 1880 +--------- .../src/runtime/discriminated-body.test.ts | 44 - .../src/runtime/embedded-file-args.test.ts | 54 - packages/sim-cli/src/runtime/execute.test.ts | 365 +- packages/sim-cli/src/runtime/naming.test.ts | 62 - packages/sim-cli/src/runtime/options.test.ts | 135 - packages/sim-cli/src/runtime/request.test.ts | 446 --- packages/sim-cli/src/runtime/result.test.ts | 401 +-- .../sim-cli/src/telemetry/client-info.test.ts | 10 - .../src/telemetry/coding-agent.test.ts | 57 - .../sim-cli/src/telemetry/invocation.test.ts | 121 - packages/sim-cli/src/telemetry/policy.test.ts | 24 - packages/sim-cli/src/telemetry/state.test.ts | 61 +- .../sim-cli/src/telemetry/transport.test.ts | 34 - .../sim-cli/src/terminal/secret-input.test.ts | 40 - .../src/transfer/embedded-files.test.ts | 55 - .../src/transfer/streaming-upload.test.ts | 81 - .../sim-cli/src/update/check.process.test.ts | 193 +- packages/sim-cli/src/update/check.test.ts | 284 +- .../src/update/install.process.test.ts | 54 - packages/sim-cli/src/update/install.test.ts | 285 +- packages/sim-cli/vitest.config.ts | 1 - packages/sim-setup/package.json | 2 +- packages/sim-setup/src/arguments.test.ts | 76 +- .../sim-setup/src/capability-config.test.ts | 43 +- .../sim-setup/src/capability-setup.test.ts | 24 - .../sim-setup/src/capability-status.test.ts | 260 +- packages/sim-setup/src/checks.test.ts | 12 - packages/sim-setup/src/cli-auth.test.ts | 67 +- packages/sim-setup/src/compose-asset.test.ts | 40 - .../sim-setup/src/compose-database.test.ts | 64 +- .../sim-setup/src/compose-project.test.ts | 19 +- .../src/configuration-sources.test.ts | 386 --- packages/sim-setup/src/context.test.ts | 65 - packages/sim-setup/src/desktop.test.ts | 133 +- packages/sim-setup/src/env-files.test.ts | 36 +- .../sim-setup/src/feature-setup-chat.test.ts | 17 - packages/sim-setup/src/feature-setup.test.ts | 78 +- packages/sim-setup/src/lifecycle.test.ts | 59 +- packages/sim-setup/src/setup-status.test.ts | 80 - packages/sim-setup/src/steps.test.ts | 166 +- packages/sim-setup/vitest.config.ts | 7 - packages/terminal-protocol/package.json | 2 +- packages/terminal-protocol/src/index.test.ts | 22 - packages/terminal-protocol/vitest.config.ts | 2 - packages/testing/package.json | 15 +- .../src/assertions/execution.assertions.ts | 159 - packages/testing/src/assertions/index.ts | 67 +- .../src/assertions/permission.assertions.ts | 126 - .../src/assertions/workflow.assertions.ts | 220 -- .../testing/src/builders/execution.builder.ts | 224 -- packages/testing/src/builders/index.ts | 17 +- .../src/builders/tool-tester.builder.ts | 8 +- packages/testing/src/deferred.ts | 19 + .../testing/src/factories/block.factory.ts | 55 - packages/testing/src/factories/dag.factory.ts | 192 - .../testing/src/factories/edge.factory.ts | 41 +- .../src/factories/execution.factory.ts | 54 +- .../src/factories/executor-context.factory.ts | 81 +- packages/testing/src/factories/index.ts | 140 +- .../src/factories/permission.factory.ts | 279 +- .../src/factories/serialized-block.factory.ts | 114 +- .../src/factories/table.factory.test.ts | 12 - .../testing/src/factories/table.factory.ts | 71 +- .../src/factories/tool-responses.factory.ts | 289 -- .../src/factories/undo-redo.factory.ts | 238 -- .../testing/src/factories/user.factory.ts | 115 - .../testing/src/factories/workflow.factory.ts | 299 +- packages/testing/src/index.ts | 32 +- .../src/mocks/auth-oauth-utils.mock.ts | 2 +- packages/testing/src/mocks/blocks.mock.ts | 4 +- .../testing/src/mocks/database.mock.test.ts | 214 -- packages/testing/src/mocks/database.mock.ts | 2 +- packages/testing/src/mocks/env-flags.mock.ts | 2 +- packages/testing/src/mocks/env.mock.test.ts | 75 - packages/testing/src/mocks/env.mock.ts | 12 +- .../src/mocks/environment-utils.mock.test.ts | 69 - packages/testing/src/mocks/fetch.mock.ts | 56 - packages/testing/src/mocks/index.ts | 106 +- packages/testing/src/mocks/logger.mock.ts | 25 - packages/testing/src/mocks/mcp-oauth.mock.ts | 2 +- .../testing/src/mocks/posthog-server.mock.ts | 22 +- .../src/mocks/redis-config.mock.test.ts | 41 - packages/testing/src/mocks/redis.mock.test.ts | 90 - packages/testing/src/mocks/redis.mock.ts | 18 - packages/testing/src/mocks/request.mock.ts | 19 - packages/testing/src/mocks/socket.mock.ts | 180 - packages/testing/src/mocks/storage.mock.ts | 18 - packages/testing/src/mocks/stripe.mock.ts | 28 +- packages/testing/src/mocks/urls.mock.test.ts | 66 - packages/testing/src/mocks/urls.mock.ts | 4 +- .../testing/src/mocks/workflow-authz.mock.ts | 6 +- .../src/mocks/workflows-orchestration.mock.ts | 57 - packages/testing/src/setup/global.setup.ts | 74 - packages/testing/src/setup/vitest.setup.ts | 40 - packages/testing/src/types/index.ts | 96 - packages/testing/tsconfig.json | 2 + packages/ts-sdk/package.json | 2 +- packages/ts-sdk/src/index.test.ts | 576 +-- packages/ts-sdk/vitest.config.ts | 2 - packages/utils/package.json | 2 +- packages/utils/src/client-info.test.ts | 117 +- packages/utils/src/coerce.test.ts | 49 - packages/utils/src/color.test.ts | 57 - packages/utils/src/errors.test.ts | 197 +- packages/utils/src/formatting.test.ts | 170 +- packages/utils/src/formatting.ts | 50 - .../utils/src/fractional-indexing.test.ts | 2 - packages/utils/src/helpers.test.ts | 64 +- packages/utils/src/id.test.ts | 53 +- packages/utils/src/index.ts | 3 - packages/utils/src/media-embed.test.ts | 76 - packages/utils/src/object.test.ts | 111 +- packages/utils/src/paste.test.ts | 65 +- packages/utils/src/random.test.ts | 17 - packages/utils/src/sandbox-references.test.ts | 45 - packages/utils/src/sso-domain.test.ts | 21 - packages/utils/src/string.test.ts | 223 -- .../utils/src/workflow-references.test.ts | 34 - packages/utils/vitest.config.ts | 2 - packages/workflow-persistence/package.json | 2 +- .../workflow-persistence/src/load.test.ts | 62 +- .../src/subblocks.test.ts | 46 +- .../workflow-persistence/vitest.config.ts | 2 - packages/workflow-renderer/package.json | 2 +- .../src/block-tile-view.test.tsx | 69 - .../src/canvas-layers.test.ts | 105 - .../edge/workflow-edge-view-mount.test.tsx | 229 -- .../src/formatted-text.test.tsx | 100 +- .../src/lib/overflow-span-mount.test.tsx | 230 -- .../src/lib/tile-icon-color.test.ts | 13 - .../workflow-renderer/src/node-order.test.ts | 30 - .../src/note/note-card-actions.test.tsx | 245 -- .../src/note/note-markdown-render.test.tsx | 139 - .../src/note/note-search-highlight.test.tsx | 329 +- .../src/use-canvas-color-mode.test.tsx | 62 - .../use-action-menu-swell.test.tsx | 162 - ...workflow-block-border-connectable.test.tsx | 206 -- .../workflow-block-border-mount.test.tsx | 1214 +------ .../workflow-block-view-interaction.test.tsx | 427 --- packages/workflow-renderer/vitest.config.ts | 4 - patches/README.md | 15 +- scripts/bump-npm-package-versions.test.ts | 18 - ...heck-actorless-executor-operations.test.ts | 39 - scripts/check-application-graph.test.ts | 72 +- scripts/check-capability-subject.test.ts | 84 - scripts/check-db-audit-candidates.test.ts | 12 - scripts/check-icon-path-precision.test.ts | 35 - scripts/check-pending-drop-tables.test.ts | 26 +- ...check-permission-group-enforcement.test.ts | 183 - scripts/check-principal-kind-parity.test.ts | 37 +- scripts/check-route-verbs.test.ts | 8 - scripts/check-tool-registry-boundary.test.ts | 25 - scripts/check-tool-request-boundary.test.ts | 735 +--- scripts/create-single-release.test.ts | 26 - scripts/desktop-prune-workflow.test.ts | 45 - scripts/dev-deployment-workflow.test.ts | 32 - .../request-factory.ts | 6 - scripts/format-generated-source.test.ts | 35 - scripts/generate-block-successors.test.ts | 43 +- scripts/generate-cli-docs.test.ts | 85 +- scripts/generate-docs.test.ts | 712 +--- scripts/generate-image-manifest.test.ts | 71 +- scripts/generate-v2-cli-api.test.ts | 117 +- scripts/generate-v2-mcp-operations.test.ts | 46 +- scripts/migrations-workflow.test.ts | 7 - scripts/openapi/docs-redirects.test.ts | 10 - scripts/openapi/documents.test.ts | 385 +-- scripts/openapi/generator.test.ts | 416 +-- scripts/openapi/vitest.config.ts | 1 - ...-knowledge-acls.ts => test-integration.ts} | 71 +- scripts/test-workflow-sync.ts | 106 - scripts/vitest.config.ts | 4 - turbo.json | 1 - 4419 files changed, 4384 insertions(+), 438522 deletions(-) create mode 100644 .agents/skills/test-audit/SKILL.md create mode 120000 .claude/skills/test-audit delete mode 100644 apps/desktop/src/main/app-routes.test.ts delete mode 100644 apps/desktop/src/main/desktop-settings.test.ts delete mode 100644 apps/desktop/src/main/help-search.test.ts delete mode 100644 apps/desktop/src/main/resource-shortcuts.test.ts delete mode 100644 apps/desktop/src/main/terminal-themes.test.ts delete mode 100644 apps/desktop/src/preload/index.test.ts delete mode 100644 apps/docs/components/docs-layout/docs-sidebar.test.tsx delete mode 100644 apps/docs/components/docs-layout/sidebar-components.test.tsx delete mode 100644 apps/docs/components/ui/video-placeholder.test.tsx delete mode 100644 apps/docs/components/workflow-preview/docs-block-tile.test.tsx delete mode 100644 apps/docs/components/workflow-preview/fit-view-after-init.test.tsx delete mode 100644 apps/docs/components/workflow-preview/workflow-data.test.ts delete mode 100644 apps/docs/components/workflow-preview/workflow-preview.test.tsx delete mode 100644 apps/docs/lib/shiki-theme.test.ts delete mode 100644 apps/realtime/src/index.test.ts delete mode 100644 apps/realtime/src/tests/socket-server.test.ts delete mode 100644 apps/sim/app/(auth)/components/auth-loading-skeleton.test.tsx delete mode 100644 apps/sim/app/(auth)/components/auth-shell.test.tsx delete mode 100644 apps/sim/app/(auth)/signup/signup-form.test.tsx delete mode 100644 apps/sim/app/(auth)/verify/page.test.tsx delete mode 100644 apps/sim/app/(landing)/comparisons/[provider]/page.test.tsx delete mode 100644 apps/sim/app/(landing)/components/agent-momentum/agent-momentum.test.tsx delete mode 100644 apps/sim/app/(landing)/components/cta/cta.test.tsx delete mode 100644 apps/sim/app/(landing)/components/featured-customer/featured-customer.test.tsx delete mode 100644 apps/sim/app/(landing)/components/features/components/features-rail/features-rail.test.tsx delete mode 100644 apps/sim/app/(landing)/components/features/features.test.tsx delete mode 100644 apps/sim/app/(landing)/components/footer/components/footer-wordmark-loop/footer-wordmark-loop.test.tsx delete mode 100644 apps/sim/app/(landing)/components/hero/components/hero-announcement-chip/hero-announcement-chip.test.tsx delete mode 100644 apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.test.tsx delete mode 100644 apps/sim/app/(landing)/components/hero/components/hero-platform-intro/hero-platform-intro.test.tsx delete mode 100644 apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop-mount.test.tsx delete mode 100644 apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.test.tsx delete mode 100644 apps/sim/app/(landing)/components/hero/components/hero-platform-stage/hero-platform-stage.test.tsx delete mode 100644 apps/sim/app/(landing)/components/landing-shell/landing-shell.test.tsx delete mode 100644 apps/sim/app/(landing)/components/navbar/components/mobile-nav/mobile-nav.test.tsx delete mode 100644 apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-logo-marquee/nav-menu-logo-marquee.test.tsx delete mode 100644 apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/workflow-menu-preview/workflow-menu-preview.test.tsx delete mode 100644 apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/constants.test.ts delete mode 100644 apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/nav-menu-chip.test.tsx delete mode 100644 apps/sim/app/(landing)/components/navbar/components/navbar-shell/navbar-shell.test.tsx delete mode 100644 apps/sim/app/(landing)/components/platform-suite/components/governance-loop/governance-loop.test.tsx delete mode 100644 apps/sim/app/(landing)/components/platform-suite/platform-suite.test.tsx delete mode 100644 apps/sim/app/(landing)/components/product-demo/components/composer-loop/composer-loop.test.tsx delete mode 100644 apps/sim/app/(landing)/components/product-demo/components/composer-loop/demo-composer.test.tsx delete mode 100644 apps/sim/app/(landing)/components/product-demo/components/composer-loop/demo-workflow-data.test.ts delete mode 100644 apps/sim/app/(landing)/components/product-demo/components/product-demo-caption/product-demo-caption.test.tsx delete mode 100644 apps/sim/app/(landing)/components/security/components/cert-blocs/cert-blocs.test.tsx delete mode 100644 apps/sim/app/(landing)/components/security/components/workspace-controls/workspace-controls.test.tsx delete mode 100644 apps/sim/app/(landing)/components/security/security.test.tsx delete mode 100644 apps/sim/app/(landing)/components/shared/logs-run-graph/logs-run-graph.test.tsx delete mode 100644 apps/sim/app/(landing)/components/shared/product-window/product-window.test.tsx delete mode 100644 apps/sim/app/(landing)/components/shared/responsive-design-stage/responsive-design-stage.test.ts delete mode 100644 apps/sim/app/(landing)/components/solutions-page/components/solutions-structured-data/solutions-structured-data.test.tsx delete mode 100644 apps/sim/app/(landing)/customers/components/customer-story-media/customer-story-media.test.tsx delete mode 100644 apps/sim/app/(landing)/demo/components/legacy-inert-fallback.test.ts delete mode 100644 apps/sim/app/(landing)/hooks/use-preview-dialog-focus.test.tsx delete mode 100644 apps/sim/app/(landing)/knowledge/components/knowledge-sources-preview/components/knowledge-document-detail/knowledge-document-detail.test.tsx delete mode 100644 apps/sim/app/(landing)/landing.test.tsx delete mode 100644 apps/sim/app/(landing)/models/utils.test.ts delete mode 100644 apps/sim/app/(landing)/x-page-view-tracker.test.tsx delete mode 100644 apps/sim/app/_shell/consent/consent-preferences-trigger.test.tsx delete mode 100644 apps/sim/app/_shell/consent/consent-provider.test.tsx delete mode 100644 apps/sim/app/_shell/consent/consent-store-provider.test.tsx delete mode 100644 apps/sim/app/_shell/consent/google-analytics-page-view-tracker.test.tsx delete mode 100644 apps/sim/app/_shell/desktop-title-bar-surfaces.test.ts delete mode 100644 apps/sim/app/_shell/desktop-title-bar.test.ts delete mode 100644 apps/sim/app/_shell/providers/get-query-client.test.ts delete mode 100644 apps/sim/app/_shell/providers/theme-provider.test.tsx delete mode 100644 apps/sim/app/account/settings/billing/credit-usage/page.test.ts delete mode 100644 apps/sim/app/api/audit-logs/route.test.ts delete mode 100644 apps/sim/app/api/auth/oauth/connections/route.test.ts delete mode 100644 apps/sim/app/api/auth/oauth/disconnect/route.test.ts rename apps/sim/app/api/auth/oauth2/token/{route.postgres.test.ts => route.integration.ts} (96%) rename apps/sim/app/api/billing/update-cost/{replay.integration.test.ts => replay.live.test.ts} (99%) rename apps/sim/app/api/billing/update-cost/{worker.integration.test.ts => worker.live.test.ts} (99%) delete mode 100644 apps/sim/app/api/copilot/chat/delete/route.test.ts delete mode 100644 apps/sim/app/api/copilot/chats/route.test.ts delete mode 100644 apps/sim/app/api/cron/knowledge-projection/route.test.ts delete mode 100644 apps/sim/app/api/cron/reconcile-billing-seats/route.test.ts delete mode 100644 apps/sim/app/api/cron/workspace-file-search-dispatch/route.test.ts delete mode 100644 apps/sim/app/api/health/route.test.ts delete mode 100644 apps/sim/app/api/invitations/[id]/accept/route.test.ts delete mode 100644 apps/sim/app/api/jobs/[jobId]/route.test.ts delete mode 100644 apps/sim/app/api/knowledge/[id]/tag-definitions/route.test.ts delete mode 100644 apps/sim/app/api/knowledge/route.test.ts delete mode 100644 apps/sim/app/api/knowledge/search/route.test.ts delete mode 100644 apps/sim/app/api/knowledge/slack/setup/route.test.ts delete mode 100644 apps/sim/app/api/logs/cleanup/route.test.ts delete mode 100644 apps/sim/app/api/mcp/copilot/route.test.ts delete mode 100644 apps/sim/app/api/mcp/events/route.test.ts delete mode 100644 apps/sim/app/api/mcp/workflow-servers/route.test.ts delete mode 100644 apps/sim/app/api/pinned-items/[resourceType]/[resourceId]/route.test.ts delete mode 100644 apps/sim/app/api/providers/openrouter/embeddings/models/route.test.ts delete mode 100644 apps/sim/app/api/secrets/usage/route.test.ts delete mode 100644 apps/sim/app/api/table/[tableId]/dispatches/route.test.ts delete mode 100644 apps/sim/app/api/table/[tableId]/rows/upsert/route.test.ts delete mode 100644 apps/sim/app/api/table/table-tool-auth.test.ts delete mode 100644 apps/sim/app/api/users/me/profile/route.test.ts delete mode 100644 apps/sim/app/api/v1/copilot/chat/route.test.ts delete mode 100644 apps/sim/app/api/v2/credentials/connections/route.test.ts delete mode 100644 apps/sim/app/api/v2/credentials/providers/route.test.ts delete mode 100644 apps/sim/app/api/v2/files/[fileId]/restore/route.test.ts delete mode 100644 apps/sim/app/api/v2/files/bulk-delete/route.test.ts delete mode 100644 apps/sim/app/api/v2/files/folders/restore/route.test.ts delete mode 100644 apps/sim/app/api/v2/knowledge/[knowledgeBaseId]/connectors/route.test.ts delete mode 100644 apps/sim/app/api/v2/knowledge/[knowledgeBaseId]/documents/[documentId]/chunks/[chunkId]/route.test.ts delete mode 100644 apps/sim/app/api/v2/knowledge/[knowledgeBaseId]/documents/uploads/control-routes.test.ts delete mode 100644 apps/sim/app/api/v2/knowledge/[knowledgeBaseId]/tags/next-slot/route.test.ts delete mode 100644 apps/sim/app/api/v2/knowledge/[knowledgeBaseId]/tags/usage/route.test.ts delete mode 100644 apps/sim/app/api/v2/meta/route.test.ts delete mode 100644 apps/sim/app/api/v2/tables/[tableId]/exports/route.test.ts delete mode 100644 apps/sim/app/api/v2/tables/[tableId]/views/route.test.ts delete mode 100644 apps/sim/app/api/v2/tables/folders/route.test.ts delete mode 100644 apps/sim/app/api/v2/tables/imports/[importId]/complete/route.test.ts delete mode 100644 apps/sim/app/api/v2/tables/imports/route.test.ts delete mode 100644 apps/sim/app/api/v2/workflows/[workflowId]/deploy/route.test.ts delete mode 100644 apps/sim/app/api/v2/workflows/[workflowId]/rollback/route.test.ts delete mode 100644 apps/sim/app/api/v2/workflows/[workflowId]/variables/route.test.ts delete mode 100644 apps/sim/app/api/v2/workflows/folders/route.test.ts delete mode 100644 apps/sim/app/api/webhooks/outbox/process/route.test.ts delete mode 100644 apps/sim/app/api/workflows/[id]/executions/[executionId]/cancel/route.test.ts delete mode 100644 apps/sim/app/api/workspaces/[id]/credential-groups/ensure/route.test.ts delete mode 100644 apps/sim/app/api/workspaces/[id]/files/[fileId]/dimensions/route.test.ts delete mode 100644 apps/sim/app/api/workspaces/[id]/files/bulk-archive/route.test.ts rename apps/sim/app/api/workspaces/[id]/files/download/{route.integration.test.ts => route-archive.test.ts} (99%) delete mode 100644 apps/sim/app/api/workspaces/[id]/files/download/route.test.ts delete mode 100644 apps/sim/app/api/workspaces/[id]/files/folders/[folderId]/route.test.ts delete mode 100644 apps/sim/app/api/workspaces/[id]/files/folders/route.test.ts delete mode 100644 apps/sim/app/api/workspaces/[id]/files/move/route.test.ts delete mode 100644 apps/sim/app/api/workspaces/[id]/sandboxes/route.test.ts delete mode 100644 apps/sim/app/api/workspaces/invitations/batch/route.test.ts delete mode 100644 apps/sim/app/credential-groups/enroll/[token]/oauth-reconnect-link.test.tsx delete mode 100644 apps/sim/app/credential-groups/enroll/[token]/oauth-toast.test.tsx delete mode 100644 apps/sim/app/invite/components/status-card.test.tsx delete mode 100644 apps/sim/app/knowledge/github/setup/setup.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/components/organization-sidebar/components/chats-section/chats-section.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/components/organization-sidebar/components/organization-footer/organization-footer.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/components/organization-sidebar/components/organization-header/organization-header.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/components/organization-sidebar/components/workspaces-section/workspace-list.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/components/organization-sidebar/components/workspaces-section/workspaces-section.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/components/organization-sidebar/hooks/use-organization-workspaces.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/integrations/disconnect-account-menu.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/search/search-results-view.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/search/search.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/settings/components/integrations/organization-integrations-setup.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/settings/components/integrations/organization-search-stats-period.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/settings/components/integrations/organization-search-status.test.ts delete mode 100644 apps/sim/app/o/[organizationId]/settings/components/integrations/organization-source-stats.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/settings/components/organization-recently-deleted.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/settings/components/organization-search-mcp.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/settings/components/organization-search-slack.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/settings/integrations/providers/[connectorType]/provider-detail.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/settings/organization-settings-sidebar.test.tsx delete mode 100644 apps/sim/app/o/[organizationId]/settings/settings-query-warmers.test.ts delete mode 100644 apps/sim/app/oauth/credential-connected/page.test.tsx delete mode 100644 apps/sim/app/seo.test.ts delete mode 100644 apps/sim/app/sitemap.test.ts delete mode 100644 apps/sim/app/slack-search/connect/[token]/slack-search-onboarding.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/connect-oauth-modal/connect-oauth-modal.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/find-bar/find-bar.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/folders/use-drag-teardown.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/folders/use-row-drag-ghost.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/folders/use-spring-loaded-folder.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/folders/use-spring-navigation.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/integration-tabs-header/integration-tabs-header.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/invite-modal/invite-modal.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options/resource-options.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/resource/is-resource-list-empty.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/resource/selection-aware-context-menus.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/resource/selection-label.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/resource/use-resource-row-selection.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/session-expired/session-expired.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/components/workspace-chrome/use-sidebar-peek.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/document-table.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/image-preview.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/chrome-scope.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/collaboration/caret-presence.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/collaboration/readiness.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/detect-language.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/link-mark-precedence.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-icon.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-list.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/sim-link.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/editor-toolbar-integration.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/link-hover-card.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/suggestion-list.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/toolbar-button.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/use-bubble-menu-floating.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/use-editor-toolbar.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/use-suggestion-keyboard.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/slash-command/commands.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/title-heading.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor-sync.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/use-editable-file-content.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/use-selection-copy-bridge.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/share-modal/share-modal.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/chat-panel-layout.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/credits-chip/credits-chip.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/knowledge-search-results/knowledge-search-results.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/activity-layout.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/activity-stream.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/search-activity.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-activity-group.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content-images.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-presentation.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/highlighted-lines.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/message-sources/message-sources.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-chip.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/resource-tag-workspace.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/usage-upgrade-display.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/watch-activity/watch-activity.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content-overlay.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content-spacing.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content-thinking.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/message-actions-visibility.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown/add-resource-dropdown.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown/resource-from-item.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-downloads.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-loading-bar.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-session-ui.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/generic-resource-content/generic-resource-content.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/search-resource-content.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/sources-resource-content.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry/resource-invalidation.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry/terminal-tab-icon.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tab-controls.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/resource-workspace-host.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/search-sources/atlassian-source-setup-modal.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/suggested-actions/suggested-actions.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/attached-files-list/attached-files-list.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/mic-button/mic-button.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/microphone-permission-help/microphone-permission-help.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/plus-menu-dropdown/plus-menu-dropdown.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/plus-menu-dropdown/resource-mention-items.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/prompt-editor/menu-cursor.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/prompt-editor/prompt-editor.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/user-input/user-input.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/hooks/chat-url.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/hooks/stream/dispatch-stream-event.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-title-event.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/hooks/use-browser-tab-resources.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/hooks/use-mothership-resize.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/hooks/use-resource-panel.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/home/hooks/use-terminal-tab-resources.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/connect-service-account-modal.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/integrations/connect-route.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-access-field/connector-access-field.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-access-field/connector-access.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.queries.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.ui.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/document-context-menu/document-context-menu.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/connector-documents-tab.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/connector-settings-fields.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/knowledge/[id]/document-columns.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/knowledge/permissions.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/snapshot-boundary.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/logs/components/log-row-context-menu/log-row-context-menu.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/logs/error.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/providers/custom-blocks-loader.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/providers/provider-models-loader.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/[section]/layout.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/[section]/prefetch.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/billing/credit-usage/credit-usage-view.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/admin/add-user-modal.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/api-keys/components/create-api-key-modal/create-api-key-modal.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/billing/billing.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/browser/browser.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/general/general.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/inbox/components/inbox-enable-toggle/inbox-enable-toggle.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/inbox/inbox.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/mcp/refresh-action-state.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/mcp/server-tools-label.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/recently-deleted/recently-deleted.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/row-actions-menu/row-actions-menu.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/sandboxes/components/sandbox-editor.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/self-host/self-host.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/team-management/team-management.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/terminal/terminal-theme-picker.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/terminal/terminal.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-type-limits.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-types.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-dropdown/column-dropdown.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/columns-menu/columns-menu.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/lock-settings-modal/lock-settings-modal.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/expanded-cell-popover.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/views-menu/views-menu.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/upgrade/hooks/use-upgrade-state.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/hooks/use-chat-file-upload.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-markdown-editor.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/hooks/use-file-attachments.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/chat/utils.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/retry-settings/retry-settings.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/dropdown/dropdown.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workspace-folder-selector/workspace-folder-selector.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-fetched-options.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/components/terminal-row-button.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-execution-handoff.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/block-ring-utils.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/node-position-utils.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-attachment-upload.test.ts rename apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/{workflow-execution-utils.integration.test.ts => workflow-execution-utils-store.test.ts} (99%) delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/preview-workflow-cursor.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/chat-navigation-link/chat-navigation-link.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/collapsed-sidebar-menu/collapsed-sidebar-menu-rename.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/collapsed-sidebar-menu/collapsed-sidebar-menu.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/rail-resource-flyout/rail-resource-flyout.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-chrome/command-chrome.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items/command-items.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/search-groups/search-groups.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-query-warmers.test.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-footer/sidebar-footer.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/status-notice/status-notice.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-drag-drop.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-flyout-inline-rename.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-sidebar-resize.test.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-workflow-operations.test.tsx delete mode 100644 apps/sim/app/workspace/page.test.tsx create mode 100644 apps/sim/background/cleanup-table-row-ttl-predicate.integration.ts rename apps/sim/background/{cleanup-table-row-ttl.integration.test.ts => cleanup-table-row-ttl.integration.ts} (96%) delete mode 100644 apps/sim/background/process-outbox.test.ts delete mode 100644 apps/sim/background/table-run-dispatcher.test.ts delete mode 100644 apps/sim/background/workspace-file-search-dispatch.test.ts delete mode 100644 apps/sim/background/workspace-file-search-index.test.ts delete mode 100644 apps/sim/blocks/accent.test.ts delete mode 100644 apps/sim/blocks/blocks/api.test.ts delete mode 100644 apps/sim/blocks/blocks/azure_devops.test.ts delete mode 100644 apps/sim/blocks/blocks/binary-download-versioning.test.ts delete mode 100644 apps/sim/blocks/blocks/calendly.test.ts delete mode 100644 apps/sim/blocks/blocks/credential-group.test.ts delete mode 100644 apps/sim/blocks/blocks/credential.test.ts delete mode 100644 apps/sim/blocks/blocks/function.test.ts delete mode 100644 apps/sim/blocks/blocks/granola.test.ts delete mode 100644 apps/sim/blocks/blocks/jupyter.test.ts delete mode 100644 apps/sim/blocks/blocks/knowledge.test.ts delete mode 100644 apps/sim/blocks/blocks/mothership.test.ts delete mode 100644 apps/sim/blocks/blocks/pi.test.ts delete mode 100644 apps/sim/blocks/blocks/quiver.test.ts delete mode 100644 apps/sim/blocks/blocks/sftp.test.ts delete mode 100644 apps/sim/blocks/blocks/ssh.test.ts delete mode 100644 apps/sim/blocks/brand-icon.test.tsx delete mode 100644 apps/sim/blocks/custom/custom-block-icon.test.tsx delete mode 100644 apps/sim/blocks/custom/overlay.test.ts delete mode 100644 apps/sim/blocks/icon-color.test.ts delete mode 100644 apps/sim/blocks/integration-popularity.test.ts delete mode 100644 apps/sim/blocks/provider-credentials.test.ts delete mode 100644 apps/sim/components/agent-stream/agent-stream-chrome.test.tsx delete mode 100644 apps/sim/components/agent-stream/tool-call-lifecycle.test.ts delete mode 100644 apps/sim/components/browser-import/browser-import-dialog.test.tsx delete mode 100644 apps/sim/components/browser-import/import-modal.test.tsx delete mode 100644 apps/sim/components/emails/_styles/base.tokens.test.ts delete mode 100644 apps/sim/components/emails/boundary.test.ts delete mode 100644 apps/sim/components/emails/render-notifications.test.ts delete mode 100644 apps/sim/components/icons/document-icons.test.ts delete mode 100644 apps/sim/components/mcp/mcp-selectors.test.tsx delete mode 100644 apps/sim/components/pii/custom-patterns-editor.test.tsx delete mode 100644 apps/sim/components/settings/prefetch-standalone-general.test.ts delete mode 100644 apps/sim/components/settings/settings-header-order.test.ts delete mode 100644 apps/sim/components/settings/settings-intent-link.test.tsx delete mode 100644 apps/sim/components/settings/settings-sidebar.test.tsx delete mode 100644 apps/sim/components/settings/standalone-settings-shell.test.ts delete mode 100644 apps/sim/components/ui/thinking-loader.test.tsx delete mode 100644 apps/sim/components/workspaces/workspace-context-menu.test.tsx delete mode 100644 apps/sim/connectors/gitlab/permission-config/fields.test.tsx delete mode 100644 apps/sim/ee/access-control/components/access-control-layout.test.tsx delete mode 100644 apps/sim/ee/access-control/components/access-control.test.tsx delete mode 100644 apps/sim/ee/access-requests/components/access-request-review.test.tsx delete mode 100644 apps/sim/ee/access-requests/components/access-requests-settings.test.tsx delete mode 100644 apps/sim/ee/access-requests/components/organization-access-requests.test.tsx delete mode 100644 apps/sim/ee/access-requests/components/request-access-action.test.tsx delete mode 100644 apps/sim/ee/access-requests/components/search-params.test.ts rename apps/sim/ee/access-requests/lib/application/{flow.postgres.test.ts => flow.integration.ts} (98%) rename apps/sim/ee/access-requests/lib/{impact.postgres.test.ts => impact.integration.ts} (96%) delete mode 100644 apps/sim/ee/access-requests/lib/navigation.test.ts rename apps/sim/ee/access-requests/lib/{repository.postgres.test.ts => repository.integration.ts} (93%) delete mode 100644 apps/sim/ee/access-requests/lib/schemas.test.ts delete mode 100644 apps/sim/ee/audit-logs/components/audit-logs.test.ts delete mode 100644 apps/sim/ee/audit-logs/hooks/audit-logs.test.tsx delete mode 100644 apps/sim/ee/credential-groups/components/credential-group-access.test.tsx delete mode 100644 apps/sim/ee/credential-groups/components/credential-group-add-resource-modal.test.tsx delete mode 100644 apps/sim/ee/credential-groups/components/organization-account-invite-modal.test.tsx delete mode 100644 apps/sim/ee/credential-groups/components/organization-account-people.test.tsx delete mode 100644 apps/sim/ee/credential-groups/components/slack-managed-users-access.test.tsx delete mode 100644 apps/sim/ee/credential-groups/components/slack-managed-users-modal.test.ts delete mode 100644 apps/sim/ee/custom-blocks/components/custom-blocks.test.tsx delete mode 100644 apps/sim/ee/data-drains/components/data-drain-create.test.tsx delete mode 100644 apps/sim/ee/data-drains/components/data-drain-detail.test.tsx delete mode 100644 apps/sim/ee/organization-usage/components/usage-consumers.test.ts delete mode 100644 apps/sim/ee/organization-usage/components/usage-credits.test.tsx delete mode 100644 apps/sim/ee/scim/components/scim-section.test.tsx rename apps/sim/ee/scim/lib/{managed-membership.postgres.test.ts => managed-membership.integration.ts} (97%) delete mode 100644 apps/sim/ee/session-policy/components/session-policy-settings.test.tsx delete mode 100644 apps/sim/ee/sso/components/require-sso-section.test.tsx delete mode 100644 apps/sim/ee/sso/hooks/sso.test.tsx delete mode 100644 apps/sim/ee/whitelabeling/components/branding-provider.test.tsx delete mode 100644 apps/sim/ee/whitelabeling/components/whitelabeling-settings.test.tsx rename apps/sim/ee/workspace-forking/application/{revision.postgres.test.ts => revision.integration.ts} (94%) delete mode 100644 apps/sim/ee/workspace-forking/components/fork-activity-panel/fork-activity-panel.test.tsx delete mode 100644 apps/sim/ee/workspace-forking/components/fork-sync/dependent-field-noun.test.ts delete mode 100644 apps/sim/enrichments/company-domain/company-domain.test.ts delete mode 100644 apps/sim/enrichments/company-info/company-info.test.ts delete mode 100644 apps/sim/enrichments/email-verification/email-verification.test.ts rename apps/sim/executor/handlers/agent/{memory-harness.postgres.test.ts => memory-harness.integration.ts} (98%) delete mode 100644 apps/sim/executor/handlers/trigger/trigger-handler.test.ts delete mode 100644 apps/sim/executor/utils/http.test.ts delete mode 100644 apps/sim/hooks/queries/access-requests.test.tsx delete mode 100644 apps/sim/hooks/queries/api-keys.test.ts delete mode 100644 apps/sim/hooks/queries/chats.test.tsx delete mode 100644 apps/sim/hooks/queries/credentials.test.ts delete mode 100644 apps/sim/hooks/queries/deployments.test.ts delete mode 100644 apps/sim/hooks/queries/dynamic-subblock-options.test.tsx delete mode 100644 apps/sim/hooks/queries/general-settings.test.tsx delete mode 100644 apps/sim/hooks/queries/github-search-installations.test.tsx delete mode 100644 apps/sim/hooks/queries/kb/connector-documents-progress.test.tsx delete mode 100644 apps/sim/hooks/queries/kb/organization-search-overview.test.tsx delete mode 100644 apps/sim/hooks/queries/kb/search-source-progress.test.tsx delete mode 100644 apps/sim/hooks/queries/link-preview.test.tsx delete mode 100644 apps/sim/hooks/queries/navigation-request-gating.test.tsx delete mode 100644 apps/sim/hooks/queries/slack-search.test.tsx delete mode 100644 apps/sim/hooks/queries/unsubscribe.test.tsx delete mode 100644 apps/sim/hooks/queries/utils/fetch-workflow-envelope.test.ts delete mode 100644 apps/sim/hooks/queries/utils/invalidate-workflow-lists.test.ts delete mode 100644 apps/sim/hooks/queries/utils/optimistic-mutation.test.ts delete mode 100644 apps/sim/hooks/queries/utils/workflow-cache.test.ts delete mode 100644 apps/sim/hooks/queries/voice.test.tsx delete mode 100644 apps/sim/hooks/queries/workspace-file-folders.test.ts delete mode 100644 apps/sim/hooks/queries/workspace-usage.test.ts delete mode 100644 apps/sim/hooks/use-canvas-viewport.test.tsx delete mode 100644 apps/sim/hooks/use-chat-input-focus.test.tsx delete mode 100644 apps/sim/hooks/use-github-installation-setup-cache.test.tsx delete mode 100644 apps/sim/hooks/use-smooth-text.test.tsx delete mode 100644 apps/sim/hooks/use-stable-flag.test.ts delete mode 100644 apps/sim/lib/analytics/google.test.ts delete mode 100644 apps/sim/lib/api/contracts/access-requests.test.ts delete mode 100644 apps/sim/lib/api/contracts/chat-output-config.test.ts delete mode 100644 apps/sim/lib/api/contracts/chats.include-thinking.test.ts delete mode 100644 apps/sim/lib/api/contracts/cleanup.test.ts delete mode 100644 apps/sim/lib/api/contracts/credentials.test.ts delete mode 100644 apps/sim/lib/api/contracts/deployments.test.ts delete mode 100644 apps/sim/lib/api/contracts/folders.test.ts delete mode 100644 apps/sim/lib/api/contracts/hotspots.test.ts delete mode 100644 apps/sim/lib/api/contracts/knowledge/connectors.test.ts delete mode 100644 apps/sim/lib/api/contracts/knowledge/github-installations.test.ts delete mode 100644 apps/sim/lib/api/contracts/knowledge/mcp.test.ts delete mode 100644 apps/sim/lib/api/contracts/knowledge/search.test.ts delete mode 100644 apps/sim/lib/api/contracts/memory.test.ts delete mode 100644 apps/sim/lib/api/contracts/mothership-chats.test.ts delete mode 100644 apps/sim/lib/api/contracts/organization-accounts.test.ts delete mode 100644 apps/sim/lib/api/contracts/organization-activity.test.ts delete mode 100644 apps/sim/lib/api/contracts/sandboxes.test.ts delete mode 100644 apps/sim/lib/api/contracts/secret-mount-policy.test.ts delete mode 100644 apps/sim/lib/api/contracts/subscription.test.ts delete mode 100644 apps/sim/lib/api/contracts/tools/aws/identity-center-list-accounts.test.ts delete mode 100644 apps/sim/lib/api/contracts/tools/jsm.test.ts delete mode 100644 apps/sim/lib/api/contracts/usage-limits.test.ts delete mode 100644 apps/sim/lib/api/contracts/v1/admin/global-work.test.ts delete mode 100644 apps/sim/lib/api/contracts/v1/knowledge/tag-filter.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/__tests__/document-tag-slots.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/__tests__/files-attribution.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/__tests__/files-recursive.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/__tests__/knowledge.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/__tests__/run-accounting.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/__tests__/schema-introspection.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/__tests__/transport-neutral-descriptions.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/__tests__/uploads.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/__tests__/workflow-agent-tools.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/knowledge-chunks.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/knowledge-search-bounds.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/log-cost-bounds.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/log-status.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/openapi/head-not-safe.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/required-field-messages.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/run-id.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/workflow-run-status.test.ts delete mode 100644 apps/sim/lib/api/contracts/v2/workspace-fork.test.ts delete mode 100644 apps/sim/lib/api/contracts/workspace-fork.test.ts delete mode 100644 apps/sim/lib/api/contracts/workspaces.test.ts delete mode 100644 apps/sim/lib/api/server/routes/v2-error-envelope.test.ts rename apps/sim/lib/auth/{oauth-provider-lifecycle.postgres.test.ts => oauth-provider-lifecycle.integration.ts} (98%) rename apps/sim/lib/auth/{oauth-token-family.postgres.test.ts => oauth-token-family.integration.ts} (96%) rename apps/sim/lib/auth/{sim-auth-adapter.postgres.test.ts => sim-auth-adapter.integration.ts} (94%) rename apps/sim/lib/auth/sso/application/{admit-sso-user.postgres.test.ts => admit-sso-user.integration.ts} (96%) rename apps/sim/lib/auth/sso/{primary-provider.postgres.test.ts => primary-provider.integration.ts} (98%) create mode 100644 apps/sim/lib/billing/calculations/usage-reservation.integration.ts delete mode 100644 apps/sim/lib/billing/core/ledger-read.test.ts rename apps/sim/lib/billing/core/{organization-activity.postgres.test.ts => organization-activity.integration.ts} (97%) rename apps/sim/lib/billing/core/{organization-usage-pagination.postgres.test.ts => organization-usage-pagination.integration.ts} (96%) delete mode 100644 apps/sim/lib/billing/core/payer-context.test.ts rename apps/sim/lib/billing/core/{usage-analytics-queries.postgres.test.ts => usage-analytics-queries.integration.ts} (95%) rename apps/sim/lib/billing/core/{usage-log.postgres.test.ts => usage-log.integration.ts} (98%) delete mode 100644 apps/sim/lib/billing/organizations/create-organization.test.ts rename apps/sim/lib/billing/organizations/{member-limits.postgres.test.ts => member-limits.integration.ts} (88%) delete mode 100644 apps/sim/lib/billing/plan-helpers.test.ts delete mode 100644 apps/sim/lib/billing/storage/payer-transfer-callsites.test.ts delete mode 100644 apps/sim/lib/billing/upgrade-reasons.test.ts delete mode 100644 apps/sim/lib/branding/wordmark.test.ts delete mode 100644 apps/sim/lib/browser-agent/renderer-shortcuts.test.ts delete mode 100644 apps/sim/lib/browser-agent/tab-label.test.ts delete mode 100644 apps/sim/lib/catalog/registry-boundary.test.ts delete mode 100644 apps/sim/lib/colors/brightness.test.ts delete mode 100644 apps/sim/lib/colors/convert.test.ts delete mode 100644 apps/sim/lib/content/seo.test.ts rename apps/sim/lib/core/rate-limiter/{route-helpers.integration.test.ts => route-helpers-client-ip.test.ts} (95%) delete mode 100644 apps/sim/lib/core/utils/arrays.test.ts delete mode 100644 apps/sim/lib/credential-groups/indexing.test.ts delete mode 100644 apps/sim/lib/credentials/token-service-accounts/descriptors.test.ts delete mode 100644 apps/sim/lib/credentials/token-service-accounts/validators/airtable.test.ts delete mode 100644 apps/sim/lib/credentials/token-service-accounts/validators/asana.test.ts delete mode 100644 apps/sim/lib/credentials/token-service-accounts/validators/calcom.test.ts delete mode 100644 apps/sim/lib/credentials/token-service-accounts/validators/coda.test.ts delete mode 100644 apps/sim/lib/credentials/token-service-accounts/validators/notion.test.ts delete mode 100644 apps/sim/lib/credentials/token-service-accounts/validators/pipedrive.test.ts delete mode 100644 apps/sim/lib/credentials/token-service-accounts/validators/webflow.test.ts delete mode 100644 apps/sim/lib/desktop/appearance.test.ts rename apps/sim/lib/execution/remote-sandbox/{google-cloud-cli.smoke.test.ts => google-cloud-cli.live.test.ts} (98%) rename apps/sim/lib/execution/remote-sandbox/{managed-cli.smoke.test.ts => managed-cli.live.test.ts} (98%) rename apps/sim/lib/execution/remote-sandbox/{pi-sandbox-billing.smoke.test.ts => pi-sandbox-billing.live.test.ts} (99%) rename apps/sim/lib/execution/remote-sandbox/{sandbox-billing.smoke.test.ts => sandbox-billing.live.test.ts} (99%) rename apps/sim/lib/execution/remote-sandbox/{sandbox-files.smoke.test.ts => sandbox-files.live.test.ts} (99%) rename apps/sim/lib/execution/{sim-helpers.smoke.test.ts => sim-helpers.live.test.ts} (99%) delete mode 100644 apps/sim/lib/file-parsers/index.test.ts delete mode 100644 apps/sim/lib/integrations/metadata-boundary.test.ts delete mode 100644 apps/sim/lib/internal/a2a/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/appconfig/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/appconfig/operations.test.ts delete mode 100644 apps/sim/lib/internal/asana/client.test.ts delete mode 100644 apps/sim/lib/internal/asana/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/athena/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/box/client.test.ts delete mode 100644 apps/sim/lib/internal/buffer/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/clickhouse/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/clickup/client.test.ts delete mode 100644 apps/sim/lib/internal/cloudformation/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/cloudformation/operations.test.ts delete mode 100644 apps/sim/lib/internal/cloudwatch/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/cloudwatch/operations.test.ts delete mode 100644 apps/sim/lib/internal/codepipeline/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/codepipeline/operations.test.ts delete mode 100644 apps/sim/lib/internal/confluence/client.test.ts delete mode 100644 apps/sim/lib/internal/confluence/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/crowdstrike/client.test.ts delete mode 100644 apps/sim/lib/internal/crowdstrike/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/cursor/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/daytona/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/daytona/operations.test.ts delete mode 100644 apps/sim/lib/internal/deployments/operations.test.ts delete mode 100644 apps/sim/lib/internal/discord/client.test.ts delete mode 100644 apps/sim/lib/internal/docusign/client.test.ts delete mode 100644 apps/sim/lib/internal/docusign/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/dropbox/client.test.ts delete mode 100644 apps/sim/lib/internal/dynamodb/client.test.ts delete mode 100644 apps/sim/lib/internal/dynamodb/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/dynamodb/operations.test.ts delete mode 100644 apps/sim/lib/internal/elevenlabs/client.test.ts delete mode 100644 apps/sim/lib/internal/extend/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/extend/operations.test.ts delete mode 100644 apps/sim/lib/internal/firecrawl/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/fireflies/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/github/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/github/operations.test.ts delete mode 100644 apps/sim/lib/internal/gmail/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/google-drive/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/google-vault/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/grafana/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/iam/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/iam/operations.test.ts delete mode 100644 apps/sim/lib/internal/identity-center/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/identity-center/operations.test.ts delete mode 100644 apps/sim/lib/internal/jsm/client.test.ts delete mode 100644 apps/sim/lib/internal/jsm/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/jupyter/operations.test.ts delete mode 100644 apps/sim/lib/internal/lambda/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/mail/execute-tools.test.ts delete mode 100644 apps/sim/lib/internal/microsoft-teams/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/microsoft-teams/operations.test.ts delete mode 100644 apps/sim/lib/internal/microsoft-teams/tool-config.test.ts delete mode 100644 apps/sim/lib/internal/mistral/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/mongodb/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/mongodb/introspection.test.ts delete mode 100644 apps/sim/lib/internal/mongodb/operations.test.ts delete mode 100644 apps/sim/lib/internal/mssql/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/mssql/operations.test.ts delete mode 100644 apps/sim/lib/internal/mysql/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/mysql/operations.test.ts delete mode 100644 apps/sim/lib/internal/neo4j/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/onedrive/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/onedrive/tool-config.test.ts delete mode 100644 apps/sim/lib/internal/onepassword/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/outlook/client.test.ts delete mode 100644 apps/sim/lib/internal/outlook/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/postgresql/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/pulse/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/pulse/operations.test.ts delete mode 100644 apps/sim/lib/internal/quiver/client.test.ts delete mode 100644 apps/sim/lib/internal/rds/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/rds/operations.test.ts delete mode 100644 apps/sim/lib/internal/redis/client.test.ts delete mode 100644 apps/sim/lib/internal/redis/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/reducto/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/reducto/operations.test.ts delete mode 100644 apps/sim/lib/internal/resend/resend.test.ts delete mode 100644 apps/sim/lib/internal/s3/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/sap-s4hana/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/search/operations.test.ts delete mode 100644 apps/sim/lib/internal/secrets-manager/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/secrets-manager/operations.test.ts delete mode 100644 apps/sim/lib/internal/ses/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/ses/operations.test.ts delete mode 100644 apps/sim/lib/internal/sftp/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/sms/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/smtp/client.test.ts delete mode 100644 apps/sim/lib/internal/sqs/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/square/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/ssh/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/ssh/operations.test.ts delete mode 100644 apps/sim/lib/internal/sts/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/supabase/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/textract/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/thinking/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/tiktok/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/tts/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/twilio-voice/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/uptimerobot/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/uptimerobot/operations.test.ts delete mode 100644 apps/sim/lib/internal/video/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/whatsapp/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/wordpress/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/workday/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/zoho-desk/execute-tool.test.ts delete mode 100644 apps/sim/lib/internal/zoom/execute-tool.test.ts delete mode 100644 apps/sim/lib/knowledge/__integration__/setup.ts rename apps/sim/lib/knowledge/access/{predicate.postgres.test.ts => predicate.integration.ts} (99%) delete mode 100644 apps/sim/lib/knowledge/application/folder-paths.test.ts rename apps/sim/lib/knowledge/application/{github-installations.postgres.test.ts => github-installations.integration.ts} (97%) rename apps/sim/lib/knowledge/application/{search-index.postgres.test.ts => search-index.integration.ts} (98%) delete mode 100644 apps/sim/lib/knowledge/application/search-source-overview.test.ts rename apps/sim/lib/knowledge/connectors/{external-directory.postgres.test.ts => external-directory.integration.ts} (94%) rename apps/sim/lib/knowledge/connectors/{member-sync-engine.integration.test.ts => member-sync-engine-content-credential.test.ts} (99%) rename apps/sim/lib/knowledge/connectors/{sync-content-pass.postgres.test.ts => sync-content-pass.integration.ts} (98%) rename apps/sim/lib/knowledge/connectors/{sync-persistence.postgres.test.ts => sync-persistence.integration.ts} (98%) delete mode 100644 apps/sim/lib/knowledge/documents/ocr-request-policy.test.ts delete mode 100644 apps/sim/lib/knowledge/orchestration/restore.test.ts delete mode 100644 apps/sim/lib/knowledge/search/author.test.ts delete mode 100644 apps/sim/lib/mcp/pubsub.test.ts delete mode 100644 apps/sim/lib/mcp/types.test.ts delete mode 100644 apps/sim/lib/memory/application/operations.test.ts rename apps/sim/lib/memory/{conversation-store.postgres.test.ts => conversation-store.integration.ts} (98%) rename apps/sim/lib/memory/{message-provenance.postgres.test.ts => message-provenance.integration.ts} (97%) rename apps/sim/lib/memory/{summary-store.postgres.test.ts => summary-store.integration.ts} (98%) delete mode 100644 apps/sim/lib/mothership/agent-cli/engines/docs-search.test.ts rename apps/sim/lib/mothership/agent-cli/{saved-run-read.postgres.test.ts => saved-run-read.live.test.ts} (99%) delete mode 100644 apps/sim/lib/mothership/application/table-commands.test.ts rename apps/sim/lib/mothership/async-runs/{browser-download-claim.postgres.test.ts => browser-download-claim.integration.ts} (95%) rename apps/sim/lib/mothership/async-runs/{tool-identity.postgres.test.ts => tool-identity.integration.ts} (92%) rename apps/sim/lib/mothership/billing/{service-store.postgres.test.ts => service-store.integration.ts} (97%) delete mode 100644 apps/sim/lib/mothership/chat/upload-context.test.ts delete mode 100644 apps/sim/lib/mothership/events.test.ts delete mode 100644 apps/sim/lib/mothership/request/go/fetch.test.ts delete mode 100644 apps/sim/lib/mothership/request/session/file-preview-session.test.ts delete mode 100644 apps/sim/lib/mothership/request/tools/enrich-error.test.ts delete mode 100644 apps/sim/lib/mothership/tool-executor/assistant-routing.test.ts delete mode 100644 apps/sim/lib/mothership/tools/client/hidden-tools.test.ts delete mode 100644 apps/sim/lib/mothership/tools/client/read-block.test.ts delete mode 100644 apps/sim/lib/mothership/tools/client/resource-display.test.tsx delete mode 100644 apps/sim/lib/mothership/tools/client/store-utils.test.ts delete mode 100644 apps/sim/lib/mothership/tools/descriptions.test.ts rename apps/sim/lib/mothership/tools/{hosted-workbench.smoke.test.ts => hosted-workbench.live.test.ts} (99%) delete mode 100644 apps/sim/lib/mothership/tools/server/docs/search-docs-dispatch.test.ts delete mode 100644 apps/sim/lib/mothership/tools/streaming-cli-name.test.ts delete mode 100644 apps/sim/lib/mothership/tools/tool-display.test.ts delete mode 100644 apps/sim/lib/navigation/paths.test.ts rename apps/sim/lib/pptx-renderer/renderer/{echarts-runtime.smoke.test.ts => echarts-runtime-render.test.ts} (96%) delete mode 100644 apps/sim/lib/pptx-renderer/renderer/echarts-runtime.test.ts delete mode 100644 apps/sim/lib/resource-policies/conditions/registry.test.ts delete mode 100644 apps/sim/lib/resource-policies/principals/registry.test.ts delete mode 100644 apps/sim/lib/selectors/server/providers/managed-agent.test.ts delete mode 100644 apps/sim/lib/sim-search/live/source-settings.test.ts delete mode 100644 apps/sim/lib/sim-search/setup-navigation.test.ts delete mode 100644 apps/sim/lib/slack-search/dispatcher.test.ts delete mode 100644 apps/sim/lib/status-page.test.ts delete mode 100644 apps/sim/lib/table/dispatch-concurrency.test.ts delete mode 100644 apps/sim/lib/table/events.attribution.test.ts delete mode 100644 apps/sim/lib/table/export-format.test.ts delete mode 100644 apps/sim/lib/table/ids.test.ts delete mode 100644 apps/sim/lib/table/query-builder/__tests__/predicate.test.ts rename apps/sim/lib/table/rows/{secret-provenance.postgres.test.ts => secret-provenance.integration.ts} (97%) delete mode 100644 apps/sim/lib/uploads/client/logo-file.test.ts rename apps/sim/lib/uploads/upload-session/{workspace-file-provenance.postgres.test.ts => workspace-file-provenance.integration.ts} (95%) delete mode 100644 apps/sim/lib/webhooks/delivery-predicate.test.ts delete mode 100644 apps/sim/lib/webhooks/providers/resend.test.ts delete mode 100644 apps/sim/lib/workflows/blocks/code-preview.test.ts delete mode 100644 apps/sim/lib/workflows/executor/enqueue-execution.test.ts delete mode 100644 apps/sim/lib/workflows/schedules/retry.test.ts delete mode 100644 apps/sim/lib/workflows/skills/builtin-skills.test.ts delete mode 100644 apps/sim/lib/workspace-events/state.test.ts delete mode 100644 apps/sim/lib/workspace-files/application/delete-workspace-file.test.ts delete mode 100644 apps/sim/lib/workspace-files/page-tabs-rail.test.ts delete mode 100644 apps/sim/lib/workspace-files/shell-layout.test.ts rename apps/sim/lib/workspaces/{organization-workspaces.postgres.test.ts => organization-workspaces.integration.ts} (96%) delete mode 100644 apps/sim/providers/agent-events-smokes.test.ts delete mode 100644 apps/sim/providers/tool-schema-adapter.test.ts delete mode 100644 apps/sim/serializer/tests/dual-validation.test.ts delete mode 100644 apps/sim/stores/execution/store-hooks.test.tsx delete mode 100644 apps/sim/stores/file-viewer/store.test.ts delete mode 100644 apps/sim/stores/modals/search/store.test.ts delete mode 100644 apps/sim/stores/panel/editor/store.test.ts delete mode 100644 apps/sim/stores/workflow-diff/store.test.ts delete mode 100644 apps/sim/stores/workflows/registry/utils.test.ts delete mode 100644 apps/sim/tools/agiloft/retrieve_attachment.test.ts delete mode 100644 apps/sim/tools/asana/operations.test.ts delete mode 100644 apps/sim/tools/buffer/operation-input.test.ts delete mode 100644 apps/sim/tools/cloudtrail/contract-validation.test.ts delete mode 100644 apps/sim/tools/cloudtrail/operation-input.test.ts delete mode 100644 apps/sim/tools/cursor/download_artifact.test.ts delete mode 100644 apps/sim/tools/cursor/operation-input.test.ts delete mode 100644 apps/sim/tools/daytona/operation-input.test.ts delete mode 100644 apps/sim/tools/deployments/operation-input.test.ts delete mode 100644 apps/sim/tools/docusign/download_document.test.ts delete mode 100644 apps/sim/tools/elevenlabs/operation.test.ts delete mode 100644 apps/sim/tools/file/compress.test.ts delete mode 100644 apps/sim/tools/file/provenance.test.ts delete mode 100644 apps/sim/tools/google_drive/operation-input.test.ts delete mode 100644 apps/sim/tools/grafana/operation.test.ts delete mode 100644 apps/sim/tools/incidentio/output_drift.test.ts delete mode 100644 apps/sim/tools/jira/operation-input.test.ts delete mode 100644 apps/sim/tools/jupyter/content-transforms.test.ts delete mode 100644 apps/sim/tools/memory/operation-input.test.ts delete mode 100644 apps/sim/tools/microsoft_ad/descriptions.test.ts delete mode 100644 apps/sim/tools/microsoft_ad/output_schema.test.ts delete mode 100644 apps/sim/tools/okta/user_path_descriptions.test.ts delete mode 100644 apps/sim/tools/onepassword/get_item_file.test.ts delete mode 100644 apps/sim/tools/outlook/get_attachment.test.ts delete mode 100644 apps/sim/tools/quiver/operation.test.ts delete mode 100644 apps/sim/tools/sailpoint/definitions.test.ts delete mode 100644 apps/sim/tools/sap_s4hana/operation-input.test.ts delete mode 100644 apps/sim/tools/sftp/download.test.ts delete mode 100644 apps/sim/tools/slack/operations.test.ts delete mode 100644 apps/sim/tools/ssh/download_file.test.ts delete mode 100644 apps/sim/tools/ssh/operation-input.test.ts delete mode 100644 apps/sim/tools/ssm/cancel_command.test.ts delete mode 100644 apps/sim/tools/stt/model-input.test.ts delete mode 100644 apps/sim/tools/stt/operation.test.ts delete mode 100644 apps/sim/tools/table/compatibility.test.ts delete mode 100644 apps/sim/tools/tiktok/api-schemas.test.ts delete mode 100644 apps/sim/tools/tts/operations.test.ts delete mode 100644 apps/sim/tools/uptimerobot/operation-input.test.ts delete mode 100644 apps/sim/tools/vanta/operation-input.test.ts delete mode 100644 apps/sim/tools/vision/operation.test.ts delete mode 100644 apps/sim/tools/windchill/registry.test.ts delete mode 100644 apps/sim/tools/wordpress/operation-input.test.ts delete mode 100644 apps/sim/tools/zoho_desk/operation-input.test.ts delete mode 100644 apps/sim/tools/zoom/operation-input.test.ts delete mode 100644 apps/sim/triggers/bitbucket/utils.test.ts delete mode 100644 apps/sim/triggers/generic/webhook.test.ts delete mode 100644 apps/sim/triggers/table/poller.test.ts rename apps/sim/{vitest.workflows-integration.setup.ts => vitest.integration.setup.ts} (55%) delete mode 100644 apps/sim/vitest.workflows-integration.config.ts rename packages/db/{embedding-width-migration.postgres.test.ts => embedding-width-migration.integration.ts} (98%) rename packages/db/{member-sync-status-migration.postgres.test.ts => member-sync-status-migration.integration.ts} (96%) rename packages/db/{organization-search-migration.postgres.test.ts => organization-search-migration.integration.ts} (98%) rename packages/db/{permission-access-requests-migration.postgres.test.ts => permission-access-requests-migration.integration.ts} (94%) rename packages/db/script-migrations/{0013_backfill_legacy_knowledge_base_workspaces.postgres.test.ts => 0013_backfill_legacy_knowledge_base_workspaces.integration.ts} (99%) rename packages/db/script-migrations/{0014_require_knowledge_base_owner.postgres.test.ts => 0014_require_knowledge_base_owner.integration.ts} (98%) rename packages/db/script-migrations/{0016_backfill_search_vectors.postgres.test.ts => 0016_backfill_search_vectors.integration.ts} (99%) rename packages/db/script-migrations/{0018_repair_workspace_file_content_revision.postgres.test.ts => 0018_repair_workspace_file_content_revision.integration.ts} (99%) rename packages/db/script-migrations/{0019_tin_keyword_projection.postgres.test.ts => 0019_tin_keyword_projection.integration.ts} (98%) rename packages/db/script-migrations/{0021_embedding_search_connector.postgres.test.ts => 0021_embedding_search_connector.integration.ts} (99%) rename packages/db/scripts/{connector-sync-schedule-precision.postgres.test.ts => connector-sync-schedule-precision.integration.ts} (100%) rename packages/db/scripts/{database-failure-classification.postgres.test.ts => database-failure-classification.integration.ts} (100%) rename packages/db/scripts/{push.postgres.test.ts => push.integration.ts} (99%) rename packages/db/scripts/{retired-columns.postgres.test.ts => retired-columns.integration.ts} (100%) create mode 100644 packages/db/testing/test-infrastructure.ts create mode 100644 packages/db/vitest.integration.setup.ts delete mode 100644 packages/emcn/src/components/avatar/avatar.test.tsx delete mode 100644 packages/emcn/src/components/bulk-action-button/bulk-action-button.test.tsx delete mode 100644 packages/emcn/src/components/calendar/calendar-interaction.test.tsx delete mode 100644 packages/emcn/src/components/charts/chart-accessibility.test.tsx delete mode 100644 packages/emcn/src/components/charts/chart-layout.test.tsx delete mode 100644 packages/emcn/src/components/charts/chart-tooltip.test.ts delete mode 100644 packages/emcn/src/components/chip-button-group/chip-button-group.test.tsx delete mode 100644 packages/emcn/src/components/chip-dropdown/chip-dropdown.test.tsx delete mode 100644 packages/emcn/src/components/chip-emails-input/chip-emails-input.test.tsx delete mode 100644 packages/emcn/src/components/chip-input/chip-input.test.tsx delete mode 100644 packages/emcn/src/components/chip-modal/chip-modal.test.tsx delete mode 100644 packages/emcn/src/components/chip-select/chip-select.dom.test.tsx delete mode 100644 packages/emcn/src/components/chip-switch/chip-switch.test.tsx delete mode 100644 packages/emcn/src/components/code/code.test.tsx delete mode 100644 packages/emcn/src/components/combobox/combobox.dom.test.tsx delete mode 100644 packages/emcn/src/components/composer-action-button/composer-action-button.test.tsx delete mode 100644 packages/emcn/src/components/details-panel/details-panel.test.tsx delete mode 100644 packages/emcn/src/components/dropdown-menu/dropdown-menu.test.tsx delete mode 100644 packages/emcn/src/components/lightbox/lightbox.test.tsx delete mode 100644 packages/emcn/src/components/modal/modal-interactions.test.tsx delete mode 100644 packages/emcn/src/components/overflow-text/overflow-text.test.tsx delete mode 100644 packages/emcn/src/components/tab-strip/tab-strip.dom.test.tsx delete mode 100644 packages/emcn/src/components/tag-input/tag-input.test.tsx delete mode 100644 packages/emcn/src/components/toast/toast.test.tsx delete mode 100644 packages/emcn/src/components/tooltip/tooltip.test.tsx delete mode 100644 packages/emcn/src/components/upload-preview-button/upload-preview-button.test.tsx delete mode 100644 packages/emcn/src/hooks/use-scroll-edges.test.tsx delete mode 100644 packages/emcn/src/lib/preview-wheel-zoom.test.ts delete mode 100644 packages/runtime-secrets/vitest.config.ts delete mode 100644 packages/sim-cli/src/commands/update.test.ts delete mode 100644 packages/sim-setup/src/capability-setup.test.ts delete mode 100644 packages/sim-setup/vitest.config.ts delete mode 100644 packages/testing/src/assertions/execution.assertions.ts delete mode 100644 packages/testing/src/builders/execution.builder.ts create mode 100644 packages/testing/src/deferred.ts delete mode 100644 packages/testing/src/factories/dag.factory.ts delete mode 100644 packages/testing/src/factories/table.factory.test.ts delete mode 100644 packages/testing/src/factories/user.factory.ts delete mode 100644 packages/testing/src/mocks/database.mock.test.ts delete mode 100644 packages/testing/src/mocks/env.mock.test.ts delete mode 100644 packages/testing/src/mocks/environment-utils.mock.test.ts delete mode 100644 packages/testing/src/mocks/redis-config.mock.test.ts delete mode 100644 packages/testing/src/mocks/redis.mock.test.ts delete mode 100644 packages/testing/src/mocks/socket.mock.ts delete mode 100644 packages/testing/src/mocks/urls.mock.test.ts delete mode 100644 packages/testing/src/mocks/workflows-orchestration.mock.ts delete mode 100644 packages/testing/src/setup/global.setup.ts delete mode 100644 packages/testing/src/setup/vitest.setup.ts delete mode 100644 packages/utils/src/coerce.test.ts delete mode 100644 packages/utils/src/color.test.ts delete mode 100644 packages/workflow-renderer/src/block-tile-view.test.tsx delete mode 100644 packages/workflow-renderer/src/canvas-layers.test.ts delete mode 100644 packages/workflow-renderer/src/edge/workflow-edge-view-mount.test.tsx delete mode 100644 packages/workflow-renderer/src/lib/overflow-span-mount.test.tsx delete mode 100644 packages/workflow-renderer/src/lib/tile-icon-color.test.ts delete mode 100644 packages/workflow-renderer/src/note/note-card-actions.test.tsx delete mode 100644 packages/workflow-renderer/src/note/note-markdown-render.test.tsx delete mode 100644 packages/workflow-renderer/src/use-canvas-color-mode.test.tsx delete mode 100644 packages/workflow-renderer/src/workflow-block/use-action-menu-swell.test.tsx delete mode 100644 packages/workflow-renderer/src/workflow-block/workflow-block-border-connectable.test.tsx delete mode 100644 packages/workflow-renderer/src/workflow-block/workflow-block-view-interaction.test.tsx delete mode 100644 scripts/fixtures/check-tool-request-boundary/request-factory.ts delete mode 100644 scripts/format-generated-source.test.ts rename scripts/{test-knowledge-acls.ts => test-integration.ts} (72%) delete mode 100644 scripts/test-workflow-sync.ts diff --git a/.agents/skills/cleanup/SKILL.md b/.agents/skills/cleanup/SKILL.md index 4a286d701ed..fa5f5140634 100644 --- a/.agents/skills/cleanup/SKILL.md +++ b/.agents/skills/cleanup/SKILL.md @@ -1,6 +1,6 @@ --- name: cleanup -description: Run all code quality skills — effects, memo, callbacks, state, React Query, emcn design review, url-state, and comments — analyzing in parallel, then applying fixes sequentially +description: Run all code quality skills — effects, memo, callbacks, state, React Query, emcn design review, url-state, comments, and test-audit — analyzing in parallel, then applying fixes sequentially argument-hint: "[scope] [fix=true|false]" --- @@ -16,9 +16,9 @@ User arguments: $ARGUMENTS Parse `$ARGUMENTS` into `scope` and `fix`: extract the `fix=true|false` token wherever it appears in the string and strip it from `scope`; defaults are the current changes and `fix=true`. `fix` is consumed by Step 3 only — the passes below always run `fix=false`. -Spawn all eight passes concurrently as subagents in a **single message** (multiple Agent tool calls). Each runs its skill on the parsed `scope` with `fix=false` — analysis and proposals ONLY, no edits. Instruct each agent to return its findings as a structured list: for every proposed change, the file path, line range, a one-line description of the change, and the exact before/after so the orchestrator can apply it without re-deriving. +Spawn all nine passes concurrently as subagents in a **single message** (multiple Agent tool calls). Each runs its skill on the parsed `scope` with `fix=false` — analysis and proposals ONLY, no edits. Instruct each agent to return its findings as a structured list: for every proposed change, the file path, line range, a one-line description of the change, and the exact before/after so the orchestrator can apply it without re-deriving. -Run these eight in parallel on the parsed `scope`: +Run these nine in parallel on the parsed `scope`: 1. `/you-might-not-need-an-effect fix=false` 2. `/you-might-not-need-a-memo fix=false` @@ -28,10 +28,11 @@ Run these eight in parallel on the parsed `scope`: 6. `/emcn-design-review fix=false` 7. `/you-might-not-need-url-state fix=false` 8. `/you-might-not-need-a-comment fix=false` +9. `/test-audit audit ` — read-only; only when the scope adds or changes test files (`*.test.ts(x)`, `*.integration.ts`, `e2e/**`). It applies the authoring gate to every new or changed test and proposes deleting the ones that fail it. ## Step 2 — Converge -Collect all findings into one list, **keeping each proposal tagged with the pass that produced it** — do NOT collapse a file's proposals into a single unlabeled patch, because Step 3 applies in pass order and needs those labels. Detect overlaps where two passes touch the same region (common: a state pass and an effect pass on the same block, or a memo and callback pass on the same component). Reconcile only genuine same-region conflicts, and drop proposals a sibling pass has made moot; a reconciled change inherits the pass label of whichever of its passes comes first in the Step 3 dependency order (effects → state → memo → callback → React Query → url-state → emcn → comments), so it is applied at the earliest safe point. Non-overlapping proposals stay as-is with their own labels. The output is a per-pass list of surviving changes, not a per-file patch. +Collect all findings into one list, **keeping each proposal tagged with the pass that produced it** — do NOT collapse a file's proposals into a single unlabeled patch, because Step 3 applies in pass order and needs those labels. Detect overlaps where two passes touch the same region (common: a state pass and an effect pass on the same block, or a memo and callback pass on the same component). Reconcile only genuine same-region conflicts, and drop proposals a sibling pass has made moot; a reconciled change inherits the pass label of whichever of its passes comes first in the Step 3 dependency order (effects → state → memo → callback → React Query → url-state → emcn → comments → tests), so it is applied at the earliest safe point. Non-overlapping proposals stay as-is with their own labels. The output is a per-pass list of surviving changes, not a per-file patch. ## Step 3 — Sequential apply @@ -39,7 +40,7 @@ If `fix=false`, skip this step — just report the proposals from Step 2. Otherwise apply the surviving changes yourself (in the main context, not delegated), iterating **pass by pass** in this dependency order so earlier structural changes settle before later passes build on them: -1. effects → 2. state → 3. memo → 4. callback → 5. React Query → 6. url-state → 7. emcn design → 8. comments +1. effects → 2. state → 3. memo → 4. callback → 5. React Query → 6. url-state → 7. emcn design → 8. comments → 9. tests For each pass in turn, apply all of that pass's changes, then move to the next pass. A file touched by several passes is therefore edited once per pass, in this order — not once as a merged patch. This is what makes the ordering real: a single merged-per-file patch would collapse all passes into one edit and lose it. @@ -55,7 +56,7 @@ After all edits, run `bun run lint:check` (it runs `turbo run lint:check` across ## Step 4 — Summary -Output a summary across all eight passes: what each found, what was applied vs. skipped-as-redundant, and any proposals that need a human decision. +Output a summary across all passes that ran: what each found, what was applied vs. skipped-as-redundant, and any proposals that need a human decision. ## Boundary findings diff --git a/.agents/skills/ship/SKILL.md b/.agents/skills/ship/SKILL.md index 943ea393f87..8ae857812a3 100644 --- a/.agents/skills/ship/SKILL.md +++ b/.agents/skills/ship/SKILL.md @@ -32,6 +32,7 @@ When the user runs `/ship`: - Keep it concise 4. **Run the cleanup pass** — only if the diff modifies UI code (any `.tsx` file, or anything under `apps/sim/components/`, `apps/sim/hooks/`, or `apps/sim/stores/`): `/cleanup` - `/cleanup` fans out the React/UI passes (effects, memo, callbacks, state, React Query, emcn, url-state) plus the comment pass; skip it when no UI was touched. When it runs, it applies fixes so they land in this commit. + - If the diff adds or changes tests (`*.test.ts(x)`, `*.integration.ts`, `e2e/**`) and `/cleanup` did not run, run `/test-audit audit ` on its own. Every new or changed test must pass the authoring gate; delete the ones that don't rather than shipping them. 5. **Run migration safety** — only if the diff touches `packages/db/migrations/**` or `packages/db/schema.ts`: - Run `/db-migrate` to review the migration for zero-downtime safety (expand/contract phasing, backward-compatibility with the deployed app version). - `bun run check:migrations origin/staging` must pass (staging is the PR base). Do not silence a flagged statement with a `-- migration-safe:` annotation unless `/db-migrate` confirmed the old code no longer depends on it; otherwise split the destructive change into a later deploy. @@ -143,7 +144,7 @@ Tested manually (or describe testing) ## Checklist - [x] Code follows project style guidelines - [x] Self-reviewed my changes -- [ ] Tests added/updated and passing +- [ ] Tests added/updated and passing (new tests pass the `test-audit` authoring gate) - [x] No new warnings introduced - [x] I confirm that I have read and agree to the terms outlined in the [Contributor License Agreement (CLA)](./CONTRIBUTING.md#contributor-license-agreement-cla) ``` diff --git a/.agents/skills/test-audit/SKILL.md b/.agents/skills/test-audit/SKILL.md new file mode 100644 index 00000000000..fec5aa97ad7 --- /dev/null +++ b/.agents/skills/test-audit/SKILL.md @@ -0,0 +1,147 @@ +--- +name: test-audit +description: Invoke whenever writing, changing, reviewing, or sweeping tests. Authoring gate for new tests, plus an audit workflow for low-value, implementation-coupled, or duplicative tests and the test-only production seams they demand. +argument-hint: "[author | audit | campaign ]" +--- + +# Test Audit + +Three modes, one value bar. **Authoring** gates every new or changed test at write time. +**Audit** runs a focused sweep of existing tests that re-assert source, duplicate stronger proof, +couple to implementation, or keep test-only production seams alive. **Campaign** prunes one +subsystem's whole test surface in parallel lanes. Optimize for confidence, not deletion count — +but a test that cannot name the bug it catches is cost, not coverage. + +Read `.claude/rules/sim-testing.md` first: it defines the test layers, file naming, and the +mechanics (global mocks, `@sim/testing`, performance rules). + +## The three rules + +1. **Never write unit tests after you write code.** A test written to describe code that already + exists restates the implementation, passes on the first run, and proves nothing. If the change + needs proof, prove it end to end. +2. **Prefer E2E tests; use them to verify complex features.** Exercise the real boundary — real + Postgres/Redis (`*.integration.ts`), a running app over real HTTP, or the packaged desktop app + (Playwright). Every E2E run ends with a **verifiable, repeatable artifact**: a JSON report of + checks with pass/fail and durations, an HTTP status log, a trace, or a screenshot, written to a + path the caller controls (`_REPORT_PATH`) and uploaded by CI on failure. + `apps/sim/scripts/test-scim-e2e.ts` is the reference shape. +3. **If you must test a system in isolation, write the failure modes down first, then write the + code.** List every way the unit can fail (bad input, boundary, concurrency, partial failure, + permission denial, resource cap). Each listed mode becomes one test that fails before the code + exists. A mode you cannot list is not a test you should write. + +## Authoring gate + +Before adding any test, answer all four. A missing answer means do not add it. + +1. What observable behavior, invariant, or independent contract does it protect? +2. What credible regression makes it fail? +3. Why does existing coverage not already catch that failure? Type-check, `next build`, + `bun run check:audits`, and the integration/E2E suites are coverage too. Each contract has one + primary owner at the strongest boundary; another layer needs its own distinct risk. Prefer + extending an existing table-driven case over a near-duplicate test. +4. Does it need a production seam (export, flag, wrapper, injection hook) that no production + caller needs? If yes, test at the real boundary instead. + +Then check it against every junk pattern below. A match fails the gate unless the retention bar +names the contract it independently guards. A test that would break under behavior-preserving +refactoring asserts implementation, not behavior. + +**Regression tests** must fail on the pre-fix code for the intended reason. Revert each guard of +the fix separately and watch the test named for that guard go red, then restore. A regression +test that never demonstrably failed proves the mock, not the fix. One regression at the owner +boundary covers the bug; do not replay it at every layer it crosses. + +## Junk patterns + +- assertion-free or `toBeDefined()`-only tests; "renders without crashing"; +- restating declarations: block/tool/trigger/provider config (subBlock ids, params, outputs, URL + templates, header maps), constant tables, registries, enums, export lists — type-check and + `check:audits` own these; +- route/handler tests that mock every collaborator and assert `toHaveBeenCalledWith` on the mocks, + or re-assert a mock's canned return; +- mocks that implement the asserted behavior, or one mock standing in for different APIs; +- Zod contract tests proving a schema accepts a valid object or rejects an obviously invalid one; +- React tests of text, class names, aria presence, snapshots, "calls onClick"; +- hook tests asserting query keys or fetch URLs; store tests of trivial setters; +- tests of test infrastructure (mocks, factories, builders testing themselves); +- source-text or import greps (`readFileSync(src)` + `toContain`); +- expected values produced by the helper under test; +- duplicate invocations of the same contract, or provider-local replays of a shared helper; +- fixtures that supply the ordering, receipt, or callback the owner should produce; +- negative controls that pass for an unrelated reason (a different guard short-circuits first); +- names or fixtures that promise more than the input exercises; +- dead production code or exports whose only callers are tests. + +## Retention bar + +Keep a test when it independently enforces one of: + +- **security** — authn/authz denial, tenant/workspace isolation, SSRF/URL validation, secret + redaction, encryption, signature verification, path traversal, injection, rate limits; +- **money and data integrity** — billing/usage math, metering, quotas, idempotency, migrations, + persistence semantics, concurrency/locking/leases, outbox, retries; +- **executor semantics** — DAG traversal, loops/parallels, conditions/routers, reference + resolution, streaming, pause/resume, run-from-block, cancellation; +- **real algorithms with edge cases** — chunkers, parsers, query builders, cron, diff/merge, + pagination, encoding, date math, ranking; +- **cross-process wire contracts** — realtime protocol, desktop bridge/IPC, CLI/SDK wire, provider + webhooks, MCP — that type-check cannot see; +- **a regression with a credible repeat**, shown red on the pre-fix code. + +Also keep call ordering when order is observable, and a source inspection when it is the cheapest +independent guard of a user-facing byte, key, or path. A retained test that fails on the baseline +is a possible product bug: reproduce it and fix the owner rather than deleting it. Static or slow +is not a deletion reason. + +## Audit mode + +Keep discovery read-only and report evidence before editing. Before judging a candidate, read the +complete test and its production owner, callers, sibling implementations, overlapping tests, CI +routing (`.github/workflows/test-build.yml` names DB suites by path), and relevant history +(`git log --format='%h %s' -5 -- `). + +Record for every deletion candidate: the test and location; the failure it can actually detect; +non-test callers of the seam it covers; the stronger remaining proof (or why none is needed); the +production or test-support code its deletion unlocks; and the focused validation command. + +**Edit shape.** One coherent owner-boundary batch per PR. When pruning inside a file, also delete +now-unused imports, mocks, fixtures, and helpers. Delete test-only exports and dead production +paths instead of preserving aliases (`rg -n '' --glob '!**/*.test.*'` must show no other +reference, including string and dynamic-import references; never delete route files, registry +entries, or generated files). Prefer net-negative production LOC. Do not add replacement tests +that restate the same implementation. + +## Campaign mode + +For a whole subsystem or the whole repo: + +1. Partition test files into lanes of ~150–350 files by owning directory, and list the protected + set (every `*.integration.ts`, `*.postgres.test.ts`, `__integration__/**`, `apps/desktop/e2e/**`, + and every path named in `.github/workflows/*.yml`). +2. Give each lane its own git worktree and branch (`git worktree add -b `, + then `bun install --frozen-lockfile` inside it). Lanes never share a checkout, never symlink + `node_modules`, and never use `git stash` — the stash is shared across worktrees. +3. Each lane commits once and writes a report: counts, categories removed with examples, notable + keeps and why, production seams removed with grep evidence, and the commands it ran. +4. Merge lane branches, then sweep orphaned shared test support (`packages/testing/**`, fixtures, + helpers) that no remaining test imports. + +## Validation + +Never edit source or tests while Vitest is running in the same checkout. + +1. Run the touched and sibling test files. From `apps/sim`: + `../../node_modules/.bin/vitest run ` (never `bunx vitest`, which fetches a different + Vitest). Other workspaces: run from the workspace directory. Never pipe the runner through + `grep`/`tail` where the pipe hides its exit code. +2. If production code changed: `bun run type-check` in that workspace. +3. `bun run check:audits` from the repo root (some audits list test files by path). +4. `bun run lint`, then `git diff --check`. +5. Report `git diff --shortstat` with production and test changes counted separately. + +## Handoff + +Report the categories removed, production simplifications, retained false positives and why they +stay, the validation actually run, production vs test LOC, and named follow-ups. diff --git a/.claude/rules/sim-testing.md b/.claude/rules/sim-testing.md index 13c9044e594..3434dc273f9 100644 --- a/.claude/rules/sim-testing.md +++ b/.claude/rules/sim-testing.md @@ -1,258 +1,127 @@ --- -description: Testing patterns with Vitest and @sim/testing +description: Test layers, naming, and Vitest mechanics for Sim paths: - - "apps/sim/**/*.test.ts" - - "apps/sim/**/*.test.tsx" + - "**/*.test.ts" + - "**/*.test.tsx" + - "**/*.integration.ts" + - "**/e2e/**" --- -# Testing Patterns +# Testing -Use Vitest. Test files: `feature.ts` → `feature.test.ts` +Before writing or changing any test, run the `test-audit` skill's authoring gate. The short +version: never write unit tests after the code; prefer E2E tests at the real boundary and end +them with a verifiable artifact; if you must test in isolation, write the failure modes down +first. A test that cannot name the bug it catches does not get written. -## Global Mocks (vitest.setup.ts) +## Layers -These modules are mocked globally — do NOT re-mock them in test files unless you need to override behavior: +What already catches bugs, in the order to reach for it: -- `@sim/db` → `databaseMock` -- `@sim/db/schema` → `schemaMock` -- `drizzle-orm` → `drizzleOrmMock` -- `@sim/logger` → `loggerMock` -- `@/lib/auth` → `authMock` -- `@/lib/auth/hybrid` → `hybridAuthMock` (with default session-delegating behavior) -- `@/lib/core/utils/request` → `requestUtilsMock` -- `@/stores/console/store`, `@/stores/terminal`, `@/stores/execution/store` -- `@/blocks/registry` -- `@trigger.dev/sdk` -- `@sim/platform-authz/workflow` → `workflowAuthzMock` - -## Structure - -```typescript -/** - * @vitest-environment node - */ -import { createMockRequest } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const { mockGetSession } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), -})) - -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) - -import { GET, POST } from '@/app/api/my-route/route' - -describe('my route', () => { - beforeEach(() => { - vi.clearAllMocks() - mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) - }) - - it('returns data', async () => { - const req = createMockRequest('GET') - const res = await GET(req) - expect(res.status).toBe(200) - }) -}) -``` - -## Performance Rules (Critical) - -### NEVER use `vi.resetModules()` + `vi.doMock()` + `await import()` - -This is the #1 cause of slow tests. It forces complete module re-evaluation per test. - -```typescript -// BAD — forces module re-evaluation every test (~50-100ms each) -beforeEach(() => { - vi.resetModules() - vi.doMock('@/lib/auth', () => ({ getSession: vi.fn() })) -}) -it('test', async () => { - const { GET } = await import('./route') // slow dynamic import -}) - -// GOOD — module loaded once, mocks reconfigured per test (~1ms each) -const { mockGetSession } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), -})) -vi.mock('@/lib/auth', () => ({ getSession: mockGetSession })) -import { GET } from '@/app/api/my-route/route' - -beforeEach(() => { vi.clearAllMocks() }) -it('test', () => { - mockGetSession.mockResolvedValue({ user: { id: '1' } }) -}) -``` - -**Only exception:** Singleton modules that cache state at module scope (e.g., Redis clients, connection pools). These genuinely need `vi.resetModules()` + dynamic import to get a fresh instance per test. - -### NEVER use `vi.importActual()` - -This defeats the purpose of mocking by loading the real module and all its dependencies. - -```typescript -// BAD — loads real module + all transitive deps -vi.mock('@/lib/workspaces/utils', async () => { - const actual = await vi.importActual('@/lib/workspaces/utils') - return { ...actual, myFn: vi.fn() } -}) - -// GOOD — mock everything, only implement what tests need -vi.mock('@/lib/workspaces/utils', () => ({ - myFn: vi.fn(), - otherFn: vi.fn(), -})) -``` - -### Mock heavy transitive dependencies - -If a module under test imports `@/blocks` (200+ files), `@/tools/registry`, or other heavy modules, mock them: - -```typescript -vi.mock('@/blocks', () => ({ - getBlock: () => null, - getAllBlocks: () => ({}), - getAllBlockTypes: () => [], - registry: {}, -})) -``` - -### Use `@vitest-environment node` unless DOM is needed - -Only use `@vitest-environment jsdom` if the test uses `window`, `document`, `FormData`, or other browser APIs. Node environment is significantly faster. - -### Avoid real timers in tests - -```typescript -// BAD -await new Promise(r => setTimeout(r, 500)) - -// GOOD — use minimal delays or fake timers -await new Promise(r => setTimeout(r, 1)) -// or -vi.useFakeTimers() -``` - -## Centralized Mocks (prefer over local declarations) - -`@sim/testing` exports ready-to-use mock modules for common dependencies. Import and pass directly to `vi.mock()` — no `vi.hoisted()` boilerplate needed. Each paired `*MockFns` object exposes the underlying `vi.fn()`s for per-test overrides. - -| Module mocked | Import | Factory form | +| Layer | What it proves | Where | |---|---|---| -| `@/app/api/auth/oauth/utils` | `authOAuthUtilsMock`, `authOAuthUtilsMockFns` | `vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock)` | -| `@/app/api/knowledge/utils` | `knowledgeApiUtilsMock`, `knowledgeApiUtilsMockFns` | `vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock)` | -| `@/app/api/workflows/utils` | `workflowsApiUtilsMock`, `workflowsApiUtilsMockFns` | `vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock)` | -| `@sim/audit` | `auditMock`, `auditMockFns` | `vi.mock('@sim/audit', () => auditMock)` | -| `@/lib/auth` | `authMock`, `authMockFns` | `vi.mock('@/lib/auth', () => authMock)` | -| `@/lib/auth/hybrid` | `hybridAuthMock`, `hybridAuthMockFns` | `vi.mock('@/lib/auth/hybrid', () => hybridAuthMock)` | -| `@/lib/copilot/request/http` | `copilotHttpMock`, `copilotHttpMockFns` | `vi.mock('@/lib/copilot/request/http', () => copilotHttpMock)` | -| `@/lib/core/config/env` | `envMock`, `createEnvMock(overrides)` | `vi.mock('@/lib/core/config/env', () => envMock)` | -| `@/lib/core/config/env-flags` | `featureFlagsMock` | `vi.mock('@/lib/core/config/env-flags', () => featureFlagsMock)` | -| `@/lib/core/config/redis` | `redisConfigMock`, `redisConfigMockFns` | `vi.mock('@/lib/core/config/redis', () => redisConfigMock)` | -| `@/lib/core/security/encryption` | `encryptionMock`, `encryptionMockFns` | `vi.mock('@/lib/core/security/encryption', () => encryptionMock)` | -| `@/lib/core/security/input-validation.server` | `inputValidationMock`, `inputValidationMockFns` | `vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock)` | -| `@/lib/core/utils/request` | `requestUtilsMock`, `requestUtilsMockFns` | `vi.mock('@/lib/core/utils/request', () => requestUtilsMock)` | -| `@/lib/core/utils/urls` | `urlsMock`, `urlsMockFns` | `vi.mock('@/lib/core/utils/urls', () => urlsMock)` | -| `@/lib/execution/preprocessing` | `executionPreprocessingMock`, `executionPreprocessingMockFns` | `vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock)` | -| `@/lib/logs/execution/logging-session` | `loggingSessionMock`, `loggingSessionMockFns`, `LoggingSessionMock` | `vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock)` | -| `@/lib/workflows/orchestration` | `workflowsOrchestrationMock`, `workflowsOrchestrationMockFns` | `vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock)` | -| `@/lib/workflows/persistence/utils` | `workflowsPersistenceUtilsMock`, `workflowsPersistenceUtilsMockFns` | `vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock)` | -| `@/lib/workflows/utils` | `workflowsUtilsMock`, `workflowsUtilsMockFns` | `vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock)` | -| `@/lib/workspaces/permissions/utils` | `permissionsMock`, `permissionsMockFns` | `vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock)` | -| `@sim/db/schema` | `schemaMock` | `vi.mock('@sim/db/schema', () => schemaMock)` | - -### Auth mocking (API routes) +| Type-check, `next build` | shapes, imports, wiring | `bunx turbo run type-check`, CI build | +| Repo audits | registry consistency, API contract boundaries, tool/block/icon invariants, migrations | `bun run check:audits` | +| E2E / integration | real Postgres/Redis, real HTTP, packaged desktop app | `*.integration.ts`, `apps/sim/scripts/test-*-e2e.ts`, `apps/desktop/e2e/*.spec.ts` | +| Unit | one isolated unit's listed failure modes | `*.test.ts(x)` | + +A unit test is justified only for a failure the layers above cannot see: security boundaries, +billing math, executor semantics, parsers and algorithms with edge cases, cross-process wire +contracts, and demonstrated regressions. Never restate declarations (block/tool/provider config, +registries, constants, schemas accepting valid input), assert that mocks were called, check +rendered text or class names, or test mocks and factories themselves. + +## Naming + +| Suffix | Needs | Run with | In CI | +|--------|-------|----------|-------| +| `*.test.ts(x)` | nothing; global mocks from `vitest.setup.ts` | `vitest run` | Lint and Test job | +| `*.integration.ts` | real PostgreSQL (`TEST_DATABASE_URL`), optionally Redis (`TEST_REDIS_URL`) | `vitest run --mode integration` | `PostgreSQL integration` job, by glob | +| `*.live.test.ts` | provider APIs, hosted sandboxes, local runtimes, or sibling checkouts | `vitest run --mode live ` (apps/sim) | never | +| `apps/desktop/e2e/*.spec.ts` | the packaged Electron app | Playwright | desktop E2E workflow | +| `apps/sim/scripts/test-*-e2e.ts` | a running app over HTTP | `bun run test::e2e` | PostgreSQL integration job | + +- A unit test lives next to its source: `feature.ts` → `feature.test.ts`. No network, no database, + no real timers. +- Integration mode installs **no** global mocks: `vitest.integration.setup.ts` validates the env + contract, scrubs application env, and sets fixture env. Declare every fixture the suite needs with + `vi.mock` in the file. +- `TEST_DATABASE_URL` must be loopback and its database name must contain a `test` segment + (`sim_test`); `TEST_REDIS_URL` must be loopback. `packages/db/testing/test-infrastructure.ts` owns + those checks. Isolate with a unique schema or generated IDs, and clean up in `afterAll`. +- Integration files run one at a time against one shared database. A new `*.integration.ts` is + picked up by CI with no workflow change, and the run writes `test-results/integration.json`, which + CI uploads. Never add a passing suite to the quarantine list in `apps/sim/vitest.config.ts`. +- `bun run test:integration` starts disposable Postgres and Redis containers, provisions the schema, + and runs both workspaces; pass filenames to narrow the `apps/sim` run. + +Name the `describe`/`it` for the behavior and the condition (`it('rejects a token issued for +another workspace')`), never the implementation (`it('calls verifyToken')`). + +## E2E artifacts + +Every E2E or integration run ends with an artifact a reviewer can inspect and re-run: a JSON +report of each check (name, status, duration, error), an HTTP status log, a Playwright trace, or a +screenshot. Write it to a caller-supplied path (`_REPORT_PATH`) and have CI upload it. +`apps/sim/scripts/test-scim-e2e.ts` is the reference: it asserts its environment is loopback and +disposable, seeds with SQL, exercises the real HTTP boundary, cleans up its fixtures, and writes +the report. + +## Unit test mechanics + +### Global mocks (`apps/sim/vitest.setup.ts`) + +Mocked for every unit test file — do not re-mock unless you override behavior: `@sim/db`, +`@sim/db/schema`, `drizzle-orm`, `@sim/logger`, `@sim/platform-authz/workflow`, `@/lib/auth`, +`@/lib/auth/hybrid`, `@/lib/core/utils/request`, `@/lib/core/config/env`, +`@/lib/core/config/env-flags`, `@/lib/core/utils/urls`, `@/lib/core/config/redis`, +`@/lib/environment/utils`, the console/terminal/execution stores, `@/blocks/registry`, and +`@trigger.dev/sdk`. + +### Structure ```typescript -import { authMock, authMockFns } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@/lib/auth', () => authMock) - +import { authMockFns, createMockRequest } from '@sim/testing' +import { describe, expect, it } from 'vitest' import { GET } from '@/app/api/my-route/route' -beforeEach(() => { - vi.clearAllMocks() - authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) -}) -``` - -Only define a local `vi.mock('@/lib/auth', ...)` if the module under test consumes exports outside the centralized shape (e.g., `auth.api.verifyOneTimeToken`, `auth.api.resetPassword`). - -### Hybrid auth mocking - -```typescript -import { hybridAuthMock, hybridAuthMockFns } from '@sim/testing' - -vi.mock('@/lib/auth/hybrid', () => hybridAuthMock) - -// In tests: -hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ - success: true, userId: 'user-1', authType: 'session', -}) -``` - -### Database chain mocking - -Use the centralized `dbChainMock` + `dbChainMockFns` helpers — no `vi.hoisted()` or chain-wiring boilerplate needed. - -```typescript -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' - -vi.mock('@sim/db', () => dbChainMock) -// Spread for custom exports: vi.mock('@sim/db', () => ({ ...dbChainMock, myTable: {...} })) - -beforeEach(() => { - vi.clearAllMocks() - resetDbChainMock() // only needed if tests use permanent (non-`Once`) overrides -}) - -it('reads a row', async () => { - dbChainMockFns.limit.mockResolvedValueOnce([{ id: '1', name: 'test' }]) - // exercise code that hits db.select().from().where().limit() - expect(dbChainMockFns.where).toHaveBeenCalled() +describe('GET /api/my-route', () => { + it('returns 401 without a session', async () => { + authMockFns.mockGetSession.mockResolvedValue(null) + const res = await GET(createMockRequest('GET')) + expect(res.status).toBe(401) + }) }) ``` -**Default chains supported:** -- `select()/selectDistinct()/selectDistinctOn() → from() → where()/innerJoin()/leftJoin() → where() → limit()/orderBy()/returning()/groupBy()/for()` -- `insert() → values() → returning()/onConflictDoUpdate()/onConflictDoNothing()` -- `update() → set() → where() → limit()/orderBy()/returning()/for()` -- `delete() → where() → limit()/orderBy()/returning()/for()` -- `db.execute()` resolves `[]` -- `db.transaction(cb)` calls cb with `dbChainMock.db` - -`.for('update')` (Postgres row-level locking) is supported on `where` builders. It returns a thenable with `.limit` / `.orderBy` / `.returning` / `.groupBy` attached, so both `await .where().for('update')` (terminal) and `await .where().for('update').limit(1)` (chained) work. Override the terminal result with `dbChainMockFns.for.mockResolvedValueOnce([...])`; for the chained form, mock the downstream terminal (e.g. `dbChainMockFns.limit.mockResolvedValueOnce([...])`). - -All terminals default to `Promise.resolve([])`. Override per-test with `dbChainMockFns..mockResolvedValueOnce(...)`. Use `resetDbChainMock()` in `beforeEach` only when tests replace wiring with `.mockReturnValue` / `.mockResolvedValue` (permanent); tests using only `...Once` variants don't need it. - -## @sim/testing Package - -Always prefer over local test data. - -| Category | Utilities | -|----------|-----------| -| **Module mocks** | See "Centralized Mocks" table above | -| **Logger helpers** | `loggerMock`, `createMockLogger()`, `getLoggerCalls()`, `clearLoggerMocks()` | -| **Database helpers** | `databaseMock`, `drizzleOrmMock`, `createMockDb()`, `createMockSql()`, `createMockSqlOperators()` | -| **Fetch helpers** | `setupGlobalFetchMock()`, `createMockFetch()`, `createMockResponse()`, `mockFetchError()` | -| **Factories** | `createSession()`, `createWorkflowRecord()`, `createBlock()`, `createExecutionContext()` | -| **Builders** | `WorkflowBuilder`, `ExecutionContextBuilder` | -| **Assertions** | `expectWorkflowAccessGranted()`, `expectBlockExecuted()` | -| **Requests** | `createMockRequest()`, `createMockFormDataRequest()` | - -## Rules Summary - -1. `@vitest-environment node` unless DOM is required -2. Prefer centralized mocks from `@sim/testing` (see table above) over local `vi.hoisted()` + `vi.mock()` boilerplate -3. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports -4. `vi.mock()` calls before importing mocked modules -5. `beforeEach(() => vi.clearAllMocks())` to reset state — no redundant `afterEach` -6. No `vi.importActual()` — mock everything explicitly -7. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them -8. Use absolute imports in test files -9. Avoid real timers — use 1ms delays or `vi.useFakeTimers()` +Prefer the centralized mocks in `@sim/testing` (`authMock`/`authMockFns`, `hybridAuthMock`, +`dbChainMock`/`dbChainMockFns`, `envMock`, `redisConfigMock`, `encryptionMock`, …) over local +`vi.hoisted()` + `vi.mock()` boilerplate; import the mock file directly +(`@sim/testing/mocks/.mock`) when you only need one. Mocks with per-test knobs export a paired +`*MockFns` object for overrides; the rest expose their `vi.fn()`s directly on the mock. + +### Performance rules + +The suite's wall time is bound by the single Vite server thread that serves every module fetch +and `vi.mock` resolve, so the lever is fewer modules and fewer mocks per file, not more workers. + +1. `vi.hoisted()` + `vi.mock()` + static imports. Never `vi.resetModules()` + `vi.doMock()` + + dynamic `import()` — it re-evaluates the module graph per test. The only exception is a module + that caches a singleton at module scope. +2. Never `vi.importActual()` — it loads the real module and its whole graph. Mock explicitly. +3. Mock heavy graphs you do not need: `@/blocks`, `@/tools/registry`, `@/triggers/registry`, + `@/tools/generated/*`. +4. Node environment is the default — never write `@vitest-environment node`. Add + `/** @vitest-environment jsdom */` only when the test needs + `window`, `document`, or other DOM APIs. +5. No real timers: `vi.useFakeTimers()` or a 1ms delay. +6. Vitest clears mock call history before every test (`clearMocks` is on by default), so never + add `vi.clearAllMocks()` to a `beforeEach`. Reset implementations explicitly only where a test + installs a permanent one (`mockReturnValue`, not `mockReturnValueOnce`). +7. Absolute imports only. + +### Running + +From `apps/sim`: `../../node_modules/.bin/vitest run `. Other workspaces: run from the +workspace directory. `bunx vitest` fetches a different Vitest and fails to load the config. +Never pipe the runner through `grep`/`tail` in a script where the pipe hides its exit code. diff --git a/.claude/skills/test-audit b/.claude/skills/test-audit new file mode 120000 index 00000000000..7fb9bcd8b32 --- /dev/null +++ b/.claude/skills/test-audit @@ -0,0 +1 @@ +../../.agents/skills/test-audit \ No newline at end of file diff --git a/.cursor/rules/sim-testing.mdc b/.cursor/rules/sim-testing.mdc index 2ce946647a7..b9ede0d1b85 100644 --- a/.cursor/rules/sim-testing.mdc +++ b/.cursor/rules/sim-testing.mdc @@ -1,258 +1,125 @@ --- -description: "Testing patterns with Vitest and @sim/testing" -globs: ["apps/sim/**/*.test.ts","apps/sim/**/*.test.tsx"] +description: "Test layers, naming, and Vitest mechanics for Sim" +globs: ["**/*.test.ts","**/*.test.tsx","**/*.integration.ts","**/e2e/**"] --- -# Testing Patterns +# Testing -Use Vitest. Test files: `feature.ts` → `feature.test.ts` +Before writing or changing any test, run the `test-audit` skill's authoring gate. The short +version: never write unit tests after the code; prefer E2E tests at the real boundary and end +them with a verifiable artifact; if you must test in isolation, write the failure modes down +first. A test that cannot name the bug it catches does not get written. -## Global Mocks (vitest.setup.ts) +## Layers -These modules are mocked globally — do NOT re-mock them in test files unless you need to override behavior: +What already catches bugs, in the order to reach for it: -- `@sim/db` → `databaseMock` -- `@sim/db/schema` → `schemaMock` -- `drizzle-orm` → `drizzleOrmMock` -- `@sim/logger` → `loggerMock` -- `@/lib/auth` → `authMock` -- `@/lib/auth/hybrid` → `hybridAuthMock` (with default session-delegating behavior) -- `@/lib/core/utils/request` → `requestUtilsMock` -- `@/stores/console/store`, `@/stores/terminal`, `@/stores/execution/store` -- `@/blocks/registry` -- `@trigger.dev/sdk` -- `@sim/platform-authz/workflow` → `workflowAuthzMock` - -## Structure - -```typescript -/** - * @vitest-environment node - */ -import { createMockRequest } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const { mockGetSession } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), -})) - -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) - -import { GET, POST } from '@/app/api/my-route/route' - -describe('my route', () => { - beforeEach(() => { - vi.clearAllMocks() - mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) - }) - - it('returns data', async () => { - const req = createMockRequest('GET') - const res = await GET(req) - expect(res.status).toBe(200) - }) -}) -``` - -## Performance Rules (Critical) - -### NEVER use `vi.resetModules()` + `vi.doMock()` + `await import()` - -This is the #1 cause of slow tests. It forces complete module re-evaluation per test. - -```typescript -// BAD — forces module re-evaluation every test (~50-100ms each) -beforeEach(() => { - vi.resetModules() - vi.doMock('@/lib/auth', () => ({ getSession: vi.fn() })) -}) -it('test', async () => { - const { GET } = await import('./route') // slow dynamic import -}) - -// GOOD — module loaded once, mocks reconfigured per test (~1ms each) -const { mockGetSession } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), -})) -vi.mock('@/lib/auth', () => ({ getSession: mockGetSession })) -import { GET } from '@/app/api/my-route/route' - -beforeEach(() => { vi.clearAllMocks() }) -it('test', () => { - mockGetSession.mockResolvedValue({ user: { id: '1' } }) -}) -``` - -**Only exception:** Singleton modules that cache state at module scope (e.g., Redis clients, connection pools). These genuinely need `vi.resetModules()` + dynamic import to get a fresh instance per test. - -### NEVER use `vi.importActual()` - -This defeats the purpose of mocking by loading the real module and all its dependencies. - -```typescript -// BAD — loads real module + all transitive deps -vi.mock('@/lib/workspaces/utils', async () => { - const actual = await vi.importActual('@/lib/workspaces/utils') - return { ...actual, myFn: vi.fn() } -}) - -// GOOD — mock everything, only implement what tests need -vi.mock('@/lib/workspaces/utils', () => ({ - myFn: vi.fn(), - otherFn: vi.fn(), -})) -``` - -### Mock heavy transitive dependencies - -If a module under test imports `@/blocks` (200+ files), `@/tools/registry`, or other heavy modules, mock them: - -```typescript -vi.mock('@/blocks', () => ({ - getBlock: () => null, - getAllBlocks: () => ({}), - getAllBlockTypes: () => [], - registry: {}, -})) -``` - -### Use `@vitest-environment node` unless DOM is needed - -Only use `@vitest-environment jsdom` if the test uses `window`, `document`, `FormData`, or other browser APIs. Node environment is significantly faster. - -### Avoid real timers in tests - -```typescript -// BAD -await new Promise(r => setTimeout(r, 500)) - -// GOOD — use minimal delays or fake timers -await new Promise(r => setTimeout(r, 1)) -// or -vi.useFakeTimers() -``` - -## Centralized Mocks (prefer over local declarations) - -`@sim/testing` exports ready-to-use mock modules for common dependencies. Import and pass directly to `vi.mock()` — no `vi.hoisted()` boilerplate needed. Each paired `*MockFns` object exposes the underlying `vi.fn()`s for per-test overrides. - -| Module mocked | Import | Factory form | +| Layer | What it proves | Where | |---|---|---| -| `@/app/api/auth/oauth/utils` | `authOAuthUtilsMock`, `authOAuthUtilsMockFns` | `vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock)` | -| `@/app/api/knowledge/utils` | `knowledgeApiUtilsMock`, `knowledgeApiUtilsMockFns` | `vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock)` | -| `@/app/api/workflows/utils` | `workflowsApiUtilsMock`, `workflowsApiUtilsMockFns` | `vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock)` | -| `@sim/audit` | `auditMock`, `auditMockFns` | `vi.mock('@sim/audit', () => auditMock)` | -| `@/lib/auth` | `authMock`, `authMockFns` | `vi.mock('@/lib/auth', () => authMock)` | -| `@/lib/auth/hybrid` | `hybridAuthMock`, `hybridAuthMockFns` | `vi.mock('@/lib/auth/hybrid', () => hybridAuthMock)` | -| `@/lib/copilot/request/http` | `copilotHttpMock`, `copilotHttpMockFns` | `vi.mock('@/lib/copilot/request/http', () => copilotHttpMock)` | -| `@/lib/core/config/env` | `envMock`, `createEnvMock(overrides)` | `vi.mock('@/lib/core/config/env', () => envMock)` | -| `@/lib/core/config/env-flags` | `featureFlagsMock` | `vi.mock('@/lib/core/config/env-flags', () => featureFlagsMock)` | -| `@/lib/core/config/redis` | `redisConfigMock`, `redisConfigMockFns` | `vi.mock('@/lib/core/config/redis', () => redisConfigMock)` | -| `@/lib/core/security/encryption` | `encryptionMock`, `encryptionMockFns` | `vi.mock('@/lib/core/security/encryption', () => encryptionMock)` | -| `@/lib/core/security/input-validation.server` | `inputValidationMock`, `inputValidationMockFns` | `vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock)` | -| `@/lib/core/utils/request` | `requestUtilsMock`, `requestUtilsMockFns` | `vi.mock('@/lib/core/utils/request', () => requestUtilsMock)` | -| `@/lib/core/utils/urls` | `urlsMock`, `urlsMockFns` | `vi.mock('@/lib/core/utils/urls', () => urlsMock)` | -| `@/lib/execution/preprocessing` | `executionPreprocessingMock`, `executionPreprocessingMockFns` | `vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock)` | -| `@/lib/logs/execution/logging-session` | `loggingSessionMock`, `loggingSessionMockFns`, `LoggingSessionMock` | `vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock)` | -| `@/lib/workflows/orchestration` | `workflowsOrchestrationMock`, `workflowsOrchestrationMockFns` | `vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock)` | -| `@/lib/workflows/persistence/utils` | `workflowsPersistenceUtilsMock`, `workflowsPersistenceUtilsMockFns` | `vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock)` | -| `@/lib/workflows/utils` | `workflowsUtilsMock`, `workflowsUtilsMockFns` | `vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock)` | -| `@/lib/workspaces/permissions/utils` | `permissionsMock`, `permissionsMockFns` | `vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock)` | -| `@sim/db/schema` | `schemaMock` | `vi.mock('@sim/db/schema', () => schemaMock)` | - -### Auth mocking (API routes) +| Type-check, `next build` | shapes, imports, wiring | `bunx turbo run type-check`, CI build | +| Repo audits | registry consistency, API contract boundaries, tool/block/icon invariants, migrations | `bun run check:audits` | +| E2E / integration | real Postgres/Redis, real HTTP, packaged desktop app | `*.integration.ts`, `apps/sim/scripts/test-*-e2e.ts`, `apps/desktop/e2e/*.spec.ts` | +| Unit | one isolated unit's listed failure modes | `*.test.ts(x)` | + +A unit test is justified only for a failure the layers above cannot see: security boundaries, +billing math, executor semantics, parsers and algorithms with edge cases, cross-process wire +contracts, and demonstrated regressions. Never restate declarations (block/tool/provider config, +registries, constants, schemas accepting valid input), assert that mocks were called, check +rendered text or class names, or test mocks and factories themselves. + +## Naming + +| Suffix | Needs | Run with | In CI | +|--------|-------|----------|-------| +| `*.test.ts(x)` | nothing; global mocks from `vitest.setup.ts` | `vitest run` | Lint and Test job | +| `*.integration.ts` | real PostgreSQL (`TEST_DATABASE_URL`), optionally Redis (`TEST_REDIS_URL`) | `vitest run --mode integration` | `PostgreSQL integration` job, by glob | +| `*.live.test.ts` | provider APIs, hosted sandboxes, local runtimes, or sibling checkouts | `vitest run --mode live ` (apps/sim) | never | +| `apps/desktop/e2e/*.spec.ts` | the packaged Electron app | Playwright | desktop E2E workflow | +| `apps/sim/scripts/test-*-e2e.ts` | a running app over HTTP | `bun run test::e2e` | PostgreSQL integration job | + +- A unit test lives next to its source: `feature.ts` → `feature.test.ts`. No network, no database, + no real timers. +- Integration mode installs **no** global mocks: `vitest.integration.setup.ts` validates the env + contract, scrubs application env, and sets fixture env. Declare every fixture the suite needs with + `vi.mock` in the file. +- `TEST_DATABASE_URL` must be loopback and its database name must contain a `test` segment + (`sim_test`); `TEST_REDIS_URL` must be loopback. `packages/db/testing/test-infrastructure.ts` owns + those checks. Isolate with a unique schema or generated IDs, and clean up in `afterAll`. +- Integration files run one at a time against one shared database. A new `*.integration.ts` is + picked up by CI with no workflow change, and the run writes `test-results/integration.json`, which + CI uploads. Never add a passing suite to the quarantine list in `apps/sim/vitest.config.ts`. +- `bun run test:integration` starts disposable Postgres and Redis containers, provisions the schema, + and runs both workspaces; pass filenames to narrow the `apps/sim` run. + +Name the `describe`/`it` for the behavior and the condition (`it('rejects a token issued for +another workspace')`), never the implementation (`it('calls verifyToken')`). + +## E2E artifacts + +Every E2E or integration run ends with an artifact a reviewer can inspect and re-run: a JSON +report of each check (name, status, duration, error), an HTTP status log, a Playwright trace, or a +screenshot. Write it to a caller-supplied path (`_REPORT_PATH`) and have CI upload it. +`apps/sim/scripts/test-scim-e2e.ts` is the reference: it asserts its environment is loopback and +disposable, seeds with SQL, exercises the real HTTP boundary, cleans up its fixtures, and writes +the report. + +## Unit test mechanics + +### Global mocks (`apps/sim/vitest.setup.ts`) + +Mocked for every unit test file — do not re-mock unless you override behavior: `@sim/db`, +`@sim/db/schema`, `drizzle-orm`, `@sim/logger`, `@sim/platform-authz/workflow`, `@/lib/auth`, +`@/lib/auth/hybrid`, `@/lib/core/utils/request`, `@/lib/core/config/env`, +`@/lib/core/config/env-flags`, `@/lib/core/utils/urls`, `@/lib/core/config/redis`, +`@/lib/environment/utils`, the console/terminal/execution stores, `@/blocks/registry`, and +`@trigger.dev/sdk`. + +### Structure ```typescript -import { authMock, authMockFns } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@/lib/auth', () => authMock) - +import { authMockFns, createMockRequest } from '@sim/testing' +import { describe, expect, it } from 'vitest' import { GET } from '@/app/api/my-route/route' -beforeEach(() => { - vi.clearAllMocks() - authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) -}) -``` - -Only define a local `vi.mock('@/lib/auth', ...)` if the module under test consumes exports outside the centralized shape (e.g., `auth.api.verifyOneTimeToken`, `auth.api.resetPassword`). - -### Hybrid auth mocking - -```typescript -import { hybridAuthMock, hybridAuthMockFns } from '@sim/testing' - -vi.mock('@/lib/auth/hybrid', () => hybridAuthMock) - -// In tests: -hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ - success: true, userId: 'user-1', authType: 'session', -}) -``` - -### Database chain mocking - -Use the centralized `dbChainMock` + `dbChainMockFns` helpers — no `vi.hoisted()` or chain-wiring boilerplate needed. - -```typescript -import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' - -vi.mock('@sim/db', () => dbChainMock) -// Spread for custom exports: vi.mock('@sim/db', () => ({ ...dbChainMock, myTable: {...} })) - -beforeEach(() => { - vi.clearAllMocks() - resetDbChainMock() // only needed if tests use permanent (non-`Once`) overrides -}) - -it('reads a row', async () => { - dbChainMockFns.limit.mockResolvedValueOnce([{ id: '1', name: 'test' }]) - // exercise code that hits db.select().from().where().limit() - expect(dbChainMockFns.where).toHaveBeenCalled() +describe('GET /api/my-route', () => { + it('returns 401 without a session', async () => { + authMockFns.mockGetSession.mockResolvedValue(null) + const res = await GET(createMockRequest('GET')) + expect(res.status).toBe(401) + }) }) ``` -**Default chains supported:** -- `select()/selectDistinct()/selectDistinctOn() → from() → where()/innerJoin()/leftJoin() → where() → limit()/orderBy()/returning()/groupBy()/for()` -- `insert() → values() → returning()/onConflictDoUpdate()/onConflictDoNothing()` -- `update() → set() → where() → limit()/orderBy()/returning()/for()` -- `delete() → where() → limit()/orderBy()/returning()/for()` -- `db.execute()` resolves `[]` -- `db.transaction(cb)` calls cb with `dbChainMock.db` - -`.for('update')` (Postgres row-level locking) is supported on `where` builders. It returns a thenable with `.limit` / `.orderBy` / `.returning` / `.groupBy` attached, so both `await .where().for('update')` (terminal) and `await .where().for('update').limit(1)` (chained) work. Override the terminal result with `dbChainMockFns.for.mockResolvedValueOnce([...])`; for the chained form, mock the downstream terminal (e.g. `dbChainMockFns.limit.mockResolvedValueOnce([...])`). - -All terminals default to `Promise.resolve([])`. Override per-test with `dbChainMockFns..mockResolvedValueOnce(...)`. Use `resetDbChainMock()` in `beforeEach` only when tests replace wiring with `.mockReturnValue` / `.mockResolvedValue` (permanent); tests using only `...Once` variants don't need it. - -## @sim/testing Package - -Always prefer over local test data. - -| Category | Utilities | -|----------|-----------| -| **Module mocks** | See "Centralized Mocks" table above | -| **Logger helpers** | `loggerMock`, `createMockLogger()`, `getLoggerCalls()`, `clearLoggerMocks()` | -| **Database helpers** | `databaseMock`, `drizzleOrmMock`, `createMockDb()`, `createMockSql()`, `createMockSqlOperators()` | -| **Fetch helpers** | `setupGlobalFetchMock()`, `createMockFetch()`, `createMockResponse()`, `mockFetchError()` | -| **Factories** | `createSession()`, `createWorkflowRecord()`, `createBlock()`, `createExecutionContext()` | -| **Builders** | `WorkflowBuilder`, `ExecutionContextBuilder` | -| **Assertions** | `expectWorkflowAccessGranted()`, `expectBlockExecuted()` | -| **Requests** | `createMockRequest()`, `createMockFormDataRequest()` | - -## Rules Summary - -1. `@vitest-environment node` unless DOM is required -2. Prefer centralized mocks from `@sim/testing` (see table above) over local `vi.hoisted()` + `vi.mock()` boilerplate -3. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports -4. `vi.mock()` calls before importing mocked modules -5. `beforeEach(() => vi.clearAllMocks())` to reset state — no redundant `afterEach` -6. No `vi.importActual()` — mock everything explicitly -7. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them -8. Use absolute imports in test files -9. Avoid real timers — use 1ms delays or `vi.useFakeTimers()` +Prefer the centralized mocks in `@sim/testing` (`authMock`/`authMockFns`, `hybridAuthMock`, +`dbChainMock`/`dbChainMockFns`, `envMock`, `redisConfigMock`, `encryptionMock`, …) over local +`vi.hoisted()` + `vi.mock()` boilerplate; import the mock file directly +(`@sim/testing/mocks/.mock`) when you only need one. Mocks with per-test knobs export a paired +`*MockFns` object for overrides; the rest expose their `vi.fn()`s directly on the mock. + +### Performance rules + +The suite's wall time is bound by the single Vite server thread that serves every module fetch +and `vi.mock` resolve, so the lever is fewer modules and fewer mocks per file, not more workers. + +1. `vi.hoisted()` + `vi.mock()` + static imports. Never `vi.resetModules()` + `vi.doMock()` + + dynamic `import()` — it re-evaluates the module graph per test. The only exception is a module + that caches a singleton at module scope. +2. Never `vi.importActual()` — it loads the real module and its whole graph. Mock explicitly. +3. Mock heavy graphs you do not need: `@/blocks`, `@/tools/registry`, `@/triggers/registry`, + `@/tools/generated/*`. +4. Node environment is the default — never write `@vitest-environment node`. Add + `/** @vitest-environment jsdom */` only when the test needs + `window`, `document`, or other DOM APIs. +5. No real timers: `vi.useFakeTimers()` or a 1ms delay. +6. Vitest clears mock call history before every test (`clearMocks` is on by default), so never + add `vi.clearAllMocks()` to a `beforeEach`. Reset implementations explicitly only where a test + installs a permanent one (`mockReturnValue`, not `mockReturnValueOnce`). +7. Absolute imports only. + +### Running + +From `apps/sim`: `../../node_modules/.bin/vitest run `. Other workspaces: run from the +workspace directory. `bunx vitest` fetches a different Vitest and fails to load the config. +Never pipe the runner through `grep`/`tail` in a script where the pipe hides its exit code. diff --git a/.github/workflows/publish-sim-cli.yml b/.github/workflows/publish-sim-cli.yml index 546190bcb62..3c20290b06a 100644 --- a/.github/workflows/publish-sim-cli.yml +++ b/.github/workflows/publish-sim-cli.yml @@ -31,10 +31,12 @@ jobs: with: bun-version: 1.4.1 + # Vitest 5 requires Node 22.12+; the published bundle is still + # smoke-tested on Node 20 below, its minimum supported runtime. - name: Setup Node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: - node-version: '20' + node-version: '22' - name: Cache Bun dependencies uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 @@ -120,6 +122,11 @@ jobs: echo "tag=$TAG" } >> "$GITHUB_OUTPUT" + - name: Setup Node 20 for the bundle smoke test + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: '20' + - name: Smoke-test packed Node bundle working-directory: packages/sim-cli run: | diff --git a/.github/workflows/publish-sim-setup.yml b/.github/workflows/publish-sim-setup.yml index 19b667c17b6..fdf99ceb8fa 100644 --- a/.github/workflows/publish-sim-setup.yml +++ b/.github/workflows/publish-sim-setup.yml @@ -31,10 +31,12 @@ jobs: with: bun-version: 1.4.1 + # Vitest 5 requires Node 22.12+; the published bundle is still + # smoke-tested on Node 20 below, its minimum supported runtime. - name: Setup Node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: - node-version: '20' + node-version: '22' - name: Cache Bun dependencies uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 @@ -117,6 +119,11 @@ jobs: echo "tag=$TAG" } >> "$GITHUB_OUTPUT" + - name: Setup Node 20 for the bundle smoke test + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: '20' + - name: Smoke-test packed Node bundle working-directory: packages/sim-setup run: | diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index aafa67327d2..7be5eb1002d 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -9,9 +9,12 @@ permissions: jobs: oauth-postgres: + # Runs the real-infrastructure test layer: every `*.integration.ts` in packages/db and + # apps/sim, discovered by glob (`vitest run --mode integration`), against the database each + # provisioning path produces. A new integration suite needs no workflow change. name: PostgreSQL integration (${{ matrix.provision }}) runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }} - timeout-minutes: 15 + timeout-minutes: 25 strategy: fail-fast: false matrix: @@ -31,11 +34,11 @@ jobs: env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - POSTGRES_DB: sim_auth_scim + POSTGRES_DB: sim_test ports: - 5432:5432 options: >- - --health-cmd "pg_isready -U postgres -d sim_auth_scim" + --health-cmd "pg_isready -U postgres -d sim_test" --health-interval 5s --health-timeout 5s --health-retries 10 @@ -44,17 +47,18 @@ jobs: env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - POSTGRES_DB: sim_billing_test + POSTGRES_DB: sim_test ports: - 5433:5432 options: >- - --health-cmd "pg_isready -U postgres -d sim_billing_test" + --health-cmd "pg_isready -U postgres -d sim_test" --health-interval 5s --health-timeout 5s --health-retries 10 env: - DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - OAUTH_TOKEN_FAMILY_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim + TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_test + TEST_REDIS_URL: redis://127.0.0.1:6379 + DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_test BETTER_AUTH_SECRET: oauth-postgres-ci-secret-at-least-32-characters NEXT_PUBLIC_APP_URL: https://test.sim.ai ENCRYPTION_KEY: '0000000000000000000000000000000000000000000000000000000000000000' @@ -83,12 +87,6 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile --ignore-scripts - - name: Verify direct schema push compatibility - working-directory: packages/db - env: - DB_PUSH_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/postgres - run: bunx vitest run scripts/push.postgres.test.ts - - name: Provision a fresh database through the supported command working-directory: packages/db run: | @@ -100,84 +98,36 @@ jobs: working-directory: packages/db run: bun run db:migrate - - name: Verify schema contract migrations in PostgreSQL + - name: Run packages/db integration tests working-directory: packages/db - env: - MIGRATION_CONTRACT_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - run: >- - bunx vitest run - scripts/retired-columns.postgres.test.ts - scripts/connector-sync-schedule-precision.postgres.test.ts - scripts/database-failure-classification.postgres.test.ts + run: bunx vitest run --mode integration - - name: Verify OAuth lifecycle and SCIM membership guards in PostgreSQL - working-directory: apps/sim - # These suites share a schema and install triggers; parallel files can deadlock DDL against cleanup. - run: >- - bunx vitest run --no-file-parallelism - lib/auth/oauth-token-family.postgres.test.ts - lib/auth/oauth-provider-lifecycle.postgres.test.ts - app/api/auth/oauth2/token/route.postgres.test.ts - lib/auth/sim-auth-adapter.test.ts - lib/auth/sim-auth-adapter.postgres.test.ts - ee/scim/lib/managed-membership.postgres.test.ts - lib/auth/sso/application/admit-sso-user.postgres.test.ts - lib/auth/sso/primary-provider.postgres.test.ts - - - name: Verify billing and organization activity in PostgreSQL + - name: Run apps/sim integration tests working-directory: apps/sim + # A non-UTC process zone keeps timestamp-without-time-zone handling honest. env: - BILLING_USAGE_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - BILLING_USAGE_TEST_REDIS_URL: redis://127.0.0.1:6379 - run: >- - bunx vitest run - lib/billing/core/usage-log.postgres.test.ts - lib/billing/core/organization-activity.postgres.test.ts - lib/billing/core/usage-analytics-queries.postgres.test.ts - lib/billing/core/organization-usage-pagination.postgres.test.ts - lib/billing/organizations/member-limits.postgres.test.ts - lib/workspaces/organization-workspaces.postgres.test.ts - lib/billing/calculations/usage-reservation.test.ts - - - name: Verify access request flows, pagination, and impact in PostgreSQL - working-directory: apps/sim - env: - ACCESS_REQUESTS_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_access_requests_test - run: | - bun -e 'import postgres from "postgres"; const sql = postgres(process.env.DATABASE_URL); await sql.unsafe("CREATE DATABASE sim_access_requests_test"); await sql.end()' - bunx vitest run ee/access-requests/lib/repository.postgres.test.ts ee/access-requests/lib/impact.postgres.test.ts ee/access-requests/lib/application/flow.postgres.test.ts - - - name: Verify fork previews ignore execution file history in PostgreSQL - working-directory: apps/sim - env: - FORK_REVISION_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - run: bunx vitest run ee/workspace-forking/application/revision.postgres.test.ts + TZ: America/Los_Angeles + run: bunx vitest run --mode integration - name: Verify cumulative billing timeout recovery on PostgreSQL 16 if: matrix.provision == 'push' working-directory: apps/sim env: - BILLING_USAGE_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5433/sim_billing_test - run: bunx vitest run lib/billing/core/usage-log.postgres.test.ts - - - name: Verify file search dispatch deadlines on PostgreSQL 17 - working-directory: apps/sim - env: - TZ: America/Los_Angeles - KNOWLEDGE_ACL_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - run: bunx vitest run --mode integration lib/workspace-files/search/dispatcher.integration.ts - - - name: Verify workspace file version history on PostgreSQL 17 - working-directory: apps/sim - env: - KNOWLEDGE_ACL_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - run: bunx vitest run --mode integration lib/uploads/contexts/workspace/__integration__/file-versions.integration.ts + TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5433/sim_test + run: >- + bunx vitest run --mode integration lib/billing/core/usage-log.integration.ts + --outputFile.json=test-results/integration-pg16.json - - name: Verify file search trigram estimate against pg_trgm - working-directory: apps/sim - env: - KNOWLEDGE_ACL_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - run: bunx vitest run --mode integration lib/workspace-files/search/index-plan.integration.ts + - name: Upload integration test reports + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: integration-reports-${{ matrix.provision }} + path: | + packages/db/test-results/*.json + apps/sim/test-results/*.json + if-no-files-found: warn + retention-days: 14 - name: Verify SCIM and administration over real HTTP working-directory: apps/sim @@ -239,95 +189,6 @@ jobs: if-no-files-found: ignore retention-days: 7 - - name: Verify durable provenance, concurrent memory writes, and browser download admission - working-directory: apps/sim - env: - BROWSER_FILE_TRANSFER_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - TABLE_PROVENANCE_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - MEMORY_PROVENANCE_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - AGENT_MEMORY_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - run: >- - bunx vitest run - lib/mothership/async-runs/browser-download-claim.postgres.test.ts - lib/table/rows/secret-provenance.postgres.test.ts - lib/memory/message-provenance.postgres.test.ts - lib/memory/conversation-store.postgres.test.ts - lib/memory/summary-store.postgres.test.ts - executor/handlers/agent/memory-harness.postgres.test.ts - - - name: Verify Search vector projection upgrade in PostgreSQL - working-directory: packages/db - env: - KNOWLEDGE_ACL_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - run: >- - bunx vitest run - script-migrations/0016_backfill_search_vectors.postgres.test.ts - script-migrations/0018_repair_workspace_file_content_revision.postgres.test.ts - script-migrations/0019_tin_keyword_projection.postgres.test.ts - member-sync-status-migration.postgres.test.ts - - - name: Verify Search progress, pagination, and outbox scheduling in PostgreSQL - working-directory: apps/sim - env: - KNOWLEDGE_ACL_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - run: >- - bunx vitest run --mode integration - lib/knowledge/__integration__/search-source-progress.integration.ts - lib/knowledge/__integration__/organization-search-overview.integration.ts - lib/knowledge/__integration__/search-source-pagination.integration.ts - lib/knowledge/__integration__/search-reference-batching.integration.ts - lib/knowledge/__integration__/embedding-insert-batches.integration.ts - lib/knowledge/__integration__/processing-lock-scope.integration.ts - lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts - lib/knowledge/__integration__/connector-deferral.integration.ts - lib/knowledge/__integration__/stored-document-recovery.integration.ts - lib/knowledge/__integration__/connector-partition-work.integration.ts - lib/knowledge/__integration__/user-document-visibility.integration.ts - lib/knowledge/__integration__/listing-continuation.integration.ts - lib/knowledge/__integration__/member-scope-renewal.integration.ts - lib/knowledge/__integration__/member-document-lifecycle.integration.ts - lib/knowledge/__integration__/connector-lease-pages.integration.ts - lib/knowledge/__integration__/slack-empty-threads.integration.ts - lib/knowledge/__integration__/kb-block-search.integration.ts - lib/knowledge/__integration__/gitlab-workspace.integration.ts - lib/knowledge/__integration__/unfilled-projection-source.integration.ts - lib/knowledge/__integration__/knowledge-projection.integration.ts - lib/knowledge/__integration__/async-projection-processing.integration.ts - lib/knowledge/__integration__/purged-detach-reservation.integration.ts - lib/core/outbox/service.integration.ts - lib/knowledge/__integration__/connector-upload.integration.ts - lib/uploads/contexts/organization-logo/application.integration.ts - - - name: Verify Confluence identity and directory sync in PostgreSQL - working-directory: apps/sim - env: - KNOWLEDGE_ACL_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - run: >- - bunx vitest run --mode integration - lib/knowledge/__integration__/confluence-identity.integration.ts - lib/knowledge/__integration__/directory-sync.integration.ts - - - name: Verify Confluence audience migrations and permission queries in PostgreSQL - working-directory: apps/sim - env: - KNOWLEDGE_ACL_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_acl_test - run: | - bun -e 'import postgres from "postgres"; const sql = postgres(process.env.DATABASE_URL); await sql`CREATE DATABASE sim_acl_test`; await sql.end()' - bunx vitest run --mode integration lib/knowledge/access/group-membership.integration.ts - bunx vitest run \ - lib/knowledge/access/predicate.postgres.test.ts \ - lib/knowledge/connectors/external-directory.postgres.test.ts \ - lib/knowledge/connectors/sync-persistence.postgres.test.ts \ - lib/knowledge/connectors/sync-content-pass.postgres.test.ts - - - name: Verify the projection source and ACL trigger and backfill in PostgreSQL - working-directory: packages/db - env: - KNOWLEDGE_ACL_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_acl_test - run: | - bun -e 'import postgres from "postgres"; const sql = postgres(process.env.DATABASE_URL); const [row] = await sql`SELECT 1 FROM pg_database WHERE datname = ${"sim_acl_test"}`; if (!row) await sql`CREATE DATABASE sim_acl_test`; await sql.end()' - bunx vitest run script-migrations/0021_embedding_search_connector.postgres.test.ts - test-build: name: Lint and Test runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }} @@ -499,12 +360,7 @@ jobs: run: command -v rg || (sudo apt-get update && sudo apt-get install -y ripgrep) # Runs the setup CLI's Bun tests plus each workspace's Vitest suite, - # without `--coverage`. See the Codecov note below. - # - # apps/sim runs only its first shard here; `test-shard` below runs the - # others. That suite is bound by the single Vite server thread that feeds - # every worker — wall time is flat from 4 to 13 workers — so a bigger - # runner buys nothing and each extra runner takes a proportional slice. + # without `--coverage`. - name: Run tests env: NODE_OPTIONS: '--no-warnings --max-old-space-size=8192' @@ -512,7 +368,6 @@ jobs: DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/simstudio' ENCRYPTION_KEY: '0000000000000000000000000000000000000000000000000000000000000000' # dummy key for CI only TURBO_CACHE_DIR: .turbo - SIM_TEST_SHARD: 1/3 run: bun run test - name: Check schema and migrations are in sync @@ -528,72 +383,6 @@ jobs: fi echo "✅ Schema and migrations are in sync" - # The remaining shards of apps/sim's Vitest suite. Everything else — lint, - # the audits, type-check, the other workspaces' suites — lives in - # `test-build` with shard 1; these jobs exist only because that suite cannot - # go faster on one machine (see the "Run tests" note there). Three shards - # put each runner at roughly the fixed cost of checkout + install. The Turbo - # cache disk gets its own key so the shards' entries do not evict each other. - test-shard: - name: Test (shard ${{ matrix.shard }}) - runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }} - timeout-minutes: 15 - strategy: - fail-fast: false - matrix: - shard: [2, 3] - - steps: - - name: Checkout code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - - - name: Setup Bun - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - bun-version: 1.4.1 - - - name: Setup Node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 - with: - node-version: 24 - - - name: Mount Bun cache - uses: ./.github/actions/cache-mount - with: - provider: ${{ vars.CI_PROVIDER }} - key: ${{ github.repository }}-bun-cache-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }} - path: ~/.bun/install/cache - - - name: Mount node_modules - uses: ./.github/actions/cache-mount - with: - provider: ${{ vars.CI_PROVIDER }} - key: ${{ github.repository }}-node-modules-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-${{ hashFiles('bun.lock') }} - path: ./node_modules - - - name: Mount Turbo cache - uses: ./.github/actions/cache-mount - with: - provider: ${{ vars.CI_PROVIDER }} - key: ${{ github.repository }}-turbo-cache-shard-${{ matrix.shard }}-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }} - path: ./.turbo - - - name: Install dependencies - run: bun install --frozen-lockfile --ignore-scripts - - - name: Install ripgrep - run: command -v rg || (sudo apt-get update && sudo apt-get install -y ripgrep) - - - name: Run tests (apps/sim shard ${{ matrix.shard }}/3) - env: - NODE_OPTIONS: '--no-warnings --max-old-space-size=8192' - NEXT_PUBLIC_APP_URL: 'https://www.sim.ai' - DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/simstudio' - ENCRYPTION_KEY: '0000000000000000000000000000000000000000000000000000000000000000' # dummy key for CI only - TURBO_CACHE_DIR: .turbo - SIM_TEST_SHARD: ${{ matrix.shard }}/3 - run: bunx turbo run test --filter=@sim/app - # Next.js production build, in parallel with lint + tests. Sticky disks are # cloned from the last committed snapshot per job and committed last-writer- # wins, so concurrent mounts are safe. The bun/node_modules disks are shared diff --git a/.gitignore b/.gitignore index 6bbecf8f295..920df52ee2c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ package-lock.json # testing /coverage /apps/**/coverage +.vitest/ +/apps/*/test-results +/packages/*/test-results # next.js /.next/ diff --git a/CLAUDE.md b/CLAUDE.md index 29ca6ff4ad1..1f3e36e6453 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -434,51 +434,15 @@ Principles when building or migrating shared UI: ## Testing -Use Vitest. Test files: `feature.ts` → `feature.test.ts`. See `.claude/rules/sim-testing.md` for full details. +Most unit tests in a codebase like this restate the code they test. They pass on the first run, break on every refactor, and catch nothing that type-check, `next build`, `bun run check:audits`, or a real end-to-end run would miss. Test for confidence, not coverage. -### Global Mocks (vitest.setup.ts) +- **Never write unit tests after you write code.** A test written to describe code that already exists restates the implementation and proves nothing. If the change needs proof, prove it end to end. +- **Highly prefer E2E tests as the sole testing mechanism.** Use them to verify complex features work, against the real boundary: real Postgres/Redis (`*.integration.ts`), the running app over real HTTP (`apps/sim/scripts/test-*-e2e.ts`), or the packaged desktop app (`apps/desktop/e2e`, Playwright). At the end of an E2E test, produce a verifiable and repeatable artifact — a JSON report of each check with status and duration, an HTTP status log, a trace, or a screenshot — written to a caller-supplied `_REPORT_PATH` and uploaded by CI on failure. `apps/sim/scripts/test-scim-e2e.ts` is the reference. +- **If you must test a system in isolation, first write down all the ways it could fail, then write the code.** Each failure mode (bad input, boundary, concurrency, partial failure, permission denial, resource cap) becomes one test that fails before the code exists. +- A regression test must fail on the pre-fix code. Revert each guard of the fix and watch its test go red before you trust it. +- Never write tests that restate declarations (block/tool/provider config, registries, constants, schemas accepting valid input), assert that mocks were called, check rendered text or class names, or test mocks and factories themselves. -`@sim/db`, `@sim/db/schema`, `drizzle-orm`, `@sim/logger`, `@sim/platform-authz/workflow`, `@/blocks/registry`, `@/lib/auth`, `@/lib/auth/hybrid`, `@/lib/core/utils/request`, `@trigger.dev/sdk`, and store mocks are provided globally. Do NOT re-mock them unless overriding behavior. (The `vi.mock('@/lib/auth', ...)` in the example below is an override of the global mock so `getSession` can be controlled per-test.) - -### Standard Test Pattern - -```typescript -/** - * @vitest-environment node - */ -import { createMockRequest } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const { mockGetSession } = vi.hoisted(() => ({ - mockGetSession: vi.fn(), -})) - -vi.mock('@/lib/auth', () => ({ - auth: { api: { getSession: vi.fn() } }, - getSession: mockGetSession, -})) - -import { GET } from '@/app/api/my-route/route' - -describe('my route', () => { - beforeEach(() => { - vi.clearAllMocks() - mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) - }) - it('returns data', async () => { ... }) -}) -``` - -### Performance Rules - -- **NEVER** use `vi.resetModules()` + `vi.doMock()` + `await import()` — use `vi.hoisted()` + `vi.mock()` + static imports -- **NEVER** use `vi.importActual()` — mock everything explicitly -- **NEVER** use `mockAuth()`, `mockConsoleLogger()`, `setupCommonApiMocks()` from `@sim/testing` — they use `vi.doMock()` internally -- **Mock heavy deps** (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them -- **Use `@vitest-environment node`** unless DOM APIs are needed (`window`, `document`, `FormData`) -- **Avoid real timers** — use 1ms delays or `vi.useFakeTimers()` - -Use `@sim/testing` mocks/factories over local test data. +Use the `test-audit` skill whenever you write, change, review, or sweep tests — it holds the authoring gate, the junk patterns, and the retention bar. Test layers, file naming, and Vitest mechanics (global mocks, `@sim/testing`, performance rules) are in `.claude/rules/sim-testing.md`. ## Caching diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 206f288200a..2ce2040a709 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -63,6 +63,6 @@ "react": "19.2.4", "react-dom": "19.2.4", "typescript": "^7.0.2", - "vitest": "^4.1.0" + "vitest": "^5.0.1" } } diff --git a/apps/desktop/src/main/account-data-generation.test.ts b/apps/desktop/src/main/account-data-generation.test.ts index 8062a0d2fa8..23ebebce55f 100644 --- a/apps/desktop/src/main/account-data-generation.test.ts +++ b/apps/desktop/src/main/account-data-generation.test.ts @@ -1,11 +1,4 @@ -import { - existsSync, - mkdirSync, - mkdtempSync, - readFileSync, - unlinkSync, - writeFileSync, -} from 'node:fs' +import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs' import { rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' @@ -20,7 +13,6 @@ import { initializeAccountDataRecovery, invalidateAccountDataOperations, isAccountDataTeardownRequired, - prepareAccountDataTeardownForQuit, retryAccountDataTeardown, runAccountDataMutation, waitForAccountDataMutations, @@ -56,22 +48,6 @@ describe('account data generation', () => { expect(isAccountDataTeardownRequired()).toBe(true) }) - it('fails closed and retries marker persistence before quit', () => { - const blockedParent = join(directory, 'blocked') - markerPath = join(blockedParent, 'teardown-required.json') - initializeAccountDataRecovery(markerPath) - writeFileSync(blockedParent, 'not a directory') - - expect(beginAccountDataTeardown('account', ORIGIN)).toBe(false) - expect(isAccountDataTeardownRequired()).toBe(false) - expect(prepareAccountDataTeardownForQuit()).toBe(true) - - unlinkSync(blockedParent) - mkdirSync(blockedParent) - expect(beginAccountDataTeardown('account', ORIGIN)).toBe(true) - expect(existsSync(markerPath)).toBe(true) - }) - it('does not erase data when the recovery marker cannot be written', async () => { const blockedParent = join(directory, 'blocked') markerPath = join(blockedParent, 'teardown-required.json') @@ -109,32 +85,6 @@ describe('account data generation', () => { expect(getAccountDataTeardownKind()).toBeNull() }) - it('keeps recovery gated until a retry clears every account store', async () => { - beginAccountDataTeardown('account', ORIGIN) - const failedClear = vi.fn(async () => { - throw new Error('keychain unavailable') - }) - const successfulClear = vi.fn(async () => {}) - - await expect( - retryAccountDataTeardown([ - { label: 'browser profile', clear: failedClear }, - { label: 'local filesystem grants', clear: successfulClear }, - ]) - ).resolves.toEqual(['browser profile']) - expect(existsSync(markerPath)).toBe(true) - expect(isAccountDataTeardownRequired()).toBe(true) - - await expect( - retryAccountDataTeardown([ - { label: 'browser profile', clear: successfulClear }, - { label: 'local filesystem grants', clear: successfulClear }, - ]) - ).resolves.toEqual([]) - expect(existsSync(markerPath)).toBe(false) - expect(isAccountDataTeardownRequired()).toBe(false) - }) - it('never downgrades or clears an account recovery marker for a server switch', () => { beginAccountDataTeardown('account', ORIGIN) beginAccountDataTeardown('deployment', ORIGIN) @@ -160,43 +110,6 @@ describe('account data generation', () => { }) }) - it('keeps deployment recovery armed when the server configuration commit fails', () => { - beginAccountDataTeardown('deployment', ORIGIN) - - expect(completeDeploymentScopedTeardown(() => false)).toBe(false) - expect(existsSync(markerPath)).toBe(true) - expect(isAccountDataTeardownRequired()).toBe(true) - }) - - it('keeps deployment recovery armed when the server configuration commit throws', () => { - beginAccountDataTeardown('deployment', ORIGIN) - - expect(() => - completeDeploymentScopedTeardown(() => { - throw new Error('disk unavailable') - }) - ).toThrow('disk unavailable') - expect(existsSync(markerPath)).toBe(true) - expect(isAccountDataTeardownRequired()).toBe(true) - }) - - it('reports successful completion of a deployment-scoped teardown', () => { - beginAccountDataTeardown('deployment', ORIGIN) - - expect(completeDeploymentScopedTeardown(() => true)).toBe(true) - expect(isAccountDataTeardownRequired()).toBe(false) - }) - - it('treats an unknown marker version as an untrusted account teardown', () => { - writeFileSync(markerPath, '{"version":3,"kind":"deployment","origin":"https://old.example"}') - - initializeAccountDataRecovery(markerPath) - - expect(getAccountDataTeardownKind()).toBe('account') - expect(getAccountDataTeardownOrigin()).toBeNull() - expect(prepareAccountDataTeardownForQuit()).toBe(false) - }) - it('waits for an admitted commit before teardown can clear its store', async () => { let releaseMutation: (() => void) | undefined const mutation = new Promise((resolve) => { diff --git a/apps/desktop/src/main/app-routes.test.ts b/apps/desktop/src/main/app-routes.test.ts deleted file mode 100644 index 6816c745736..00000000000 --- a/apps/desktop/src/main/app-routes.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { newChatRoute, settingsRoute } from '@/main/app-routes' - -describe('app routes', () => { - it('derives the new-chat route from the last workspace route', () => { - expect(newChatRoute('/workspace/ws1/w/wf2')).toBe('/workspace/ws1/home') - expect(newChatRoute('/workspace/ws1/home?resource=r1')).toBe('/workspace/ws1/home') - expect(newChatRoute('/account')).toBe('/home') - expect(newChatRoute(undefined)).toBe('/home') - expect(newChatRoute('//evil.example')).toBe('/home') - }) - - it('derives the settings route from the last workspace route', () => { - expect(settingsRoute('/workspace/ws1/w/wf2')).toBe('/workspace/ws1/settings/desktop') - expect(settingsRoute('/account')).toBe('/home') - expect(settingsRoute(undefined)).toBe('/home') - expect(settingsRoute('//evil.example')).toBe('/home') - }) -}) diff --git a/apps/desktop/src/main/atomic-json-file.test.ts b/apps/desktop/src/main/atomic-json-file.test.ts index fdb1fce2c08..b95236083ab 100644 --- a/apps/desktop/src/main/atomic-json-file.test.ts +++ b/apps/desktop/src/main/atomic-json-file.test.ts @@ -1,4 +1,4 @@ -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it } from 'vitest' @@ -19,14 +19,6 @@ describe('bounded file reads', () => { rmSync(directory, { recursive: true, force: true }) }) - it('reads the file through its opened handle', async () => { - const filePath = join(directory, 'store.json') - writeFileSync(filePath, 'bounded payload') - - await expect(readFileWithinLimit(filePath, 15)).resolves.toEqual(Buffer.from('bounded payload')) - expect(readFileWithinLimitSync(filePath, 15)).toEqual(Buffer.from('bounded payload')) - }) - it('rejects a file larger than the configured limit', async () => { const filePath = join(directory, 'store.json') writeFileSync(filePath, 'too large') @@ -34,14 +26,4 @@ describe('bounded file reads', () => { await expect(readFileWithinLimit(filePath, 8)).rejects.toBeInstanceOf(FileResourceLimitError) expect(() => readFileWithinLimitSync(filePath, 8)).toThrow(FileResourceLimitError) }) - - it('rejects non-file handles', async () => { - const childDirectory = join(directory, 'store') - mkdirSync(childDirectory) - - await expect(readFileWithinLimit(childDirectory, 100)).rejects.toBeInstanceOf( - FileResourceLimitError - ) - expect(() => readFileWithinLimitSync(childDirectory, 100)).toThrow(FileResourceLimitError) - }) }) diff --git a/apps/desktop/src/main/browser-agent/cdp.test.ts b/apps/desktop/src/main/browser-agent/cdp.test.ts index 4b24e3484df..821f9b956d4 100644 --- a/apps/desktop/src/main/browser-agent/cdp.test.ts +++ b/apps/desktop/src/main/browser-agent/cdp.test.ts @@ -14,17 +14,13 @@ import { import { captureScreenshot, clickAt, - consumeAgentContextMenu, dragPointer, ensureInstrumented, evaluateInIsolatedFrame, insertText, - movePointer, PRIMARY_CLICK, - pointerPathSteps, releaseFileInput, resolveFileInput, - setColorScheme, setFileInputFiles, } from '@/main/browser-agent/cdp' @@ -132,39 +128,6 @@ describe('browser-agent CDP instrumentation', () => { }) }) - it('accepts an OOPIF beforeunload dialog on the flattened child session', async () => { - const contents = new WebContentsView().webContents - const onDialog = vi.fn() - await ensureInstrumented(contents, { onDialog, dialogResponse: () => null }) - const listener = vi - .mocked(contents.debugger.on) - .mock.calls.find(([event]) => event === 'message')?.[1] as - | ((event: unknown, method: string, params: unknown, sessionId?: string) => void) - | undefined - expect(listener).toBeTypeOf('function') - vi.mocked(contents.debugger.sendCommand).mockClear() - - listener?.( - {}, - 'Page.javascriptDialogOpening', - { type: 'beforeunload', message: 'Leave this page?' }, - 'child-session' - ) - await vi.waitFor(() => expect(onDialog).toHaveBeenCalled()) - - expect(contents.debugger.sendCommand).toHaveBeenCalledWith( - 'Page.handleJavaScriptDialog', - { accept: true }, - 'child-session' - ) - expect(onDialog).toHaveBeenCalledWith({ - type: 'beforeunload', - message: 'Leave this page?', - handled: true, - accepted: true, - }) - }) - it('answers dialogs with the running action requested response', async () => { const contents = new WebContentsView().webContents const onDialog = vi.fn() @@ -194,141 +157,6 @@ describe('browser-agent CDP instrumentation', () => { }) }) - it('reports an OOPIF dialog as unhandled when child and root commands fail', async () => { - const contents = new WebContentsView().webContents - const onDialog = vi.fn() - await ensureInstrumented(contents, { onDialog, dialogResponse: () => null }) - const listener = vi - .mocked(contents.debugger.on) - .mock.calls.find(([event]) => event === 'message')?.[1] as - | ((event: unknown, method: string, params: unknown, sessionId?: string) => void) - | undefined - expect(listener).toBeTypeOf('function') - vi.mocked(contents.debugger.sendCommand).mockClear() - vi.mocked(contents.debugger.sendCommand).mockRejectedValue(new Error('dialog target closed')) - - listener?.( - {}, - 'Page.javascriptDialogOpening', - { type: 'confirm', message: 'Continue?' }, - 'child-session' - ) - await vi.waitFor(() => expect(onDialog).toHaveBeenCalled()) - - expect(vi.mocked(contents.debugger.sendCommand).mock.calls).toEqual([ - ['Page.handleJavaScriptDialog', { accept: false }, 'child-session'], - ['Page.handleJavaScriptDialog', { accept: false }], - ]) - expect(onDialog).toHaveBeenCalledWith({ - type: 'confirm', - message: 'Continue?', - handled: false, - accepted: false, - }) - }) - - it('clicks through Chromium trusted mouse input', async () => { - const contents = new WebContentsView().webContents - - await clickAt(contents, 120, 240) - - expect(vi.mocked(contents.debugger.sendCommand).mock.calls).toEqual([ - ['Input.dispatchMouseEvent', { type: 'mouseMoved', x: 120, y: 240, button: 'none' }], - [ - 'Input.dispatchMouseEvent', - { - type: 'mousePressed', - x: 120, - y: 240, - button: 'left', - buttons: 1, - modifiers: 0, - clickCount: 1, - }, - ], - [ - 'Input.dispatchMouseEvent', - { - type: 'mouseReleased', - x: 120, - y: 240, - button: 'left', - buttons: 0, - modifiers: 0, - clickCount: 1, - }, - ], - ]) - }) - - it('stops a pointer route as soon as it is aborted', async () => { - const contents = new WebContentsView().webContents - const moves = () => - vi - .mocked(contents.debugger.sendCommand) - .mock.calls.filter(([method]) => String(method).startsWith('Input.dispatchMouseEvent')) - const aborted = new AbortController() - aborted.abort() - await expect( - movePointer(contents, { via: [], durationMs: null }, { x: 5, y: 5 }, aborted.signal) - ).rejects.toMatchObject({ name: 'AbortError' }) - expect(moves()).toHaveLength(0) - - vi.useFakeTimers() - try { - const controller = new AbortController() - const route = movePointer( - contents, - { via: [{ x: 0, y: 0 }], durationMs: 5_000 }, - { x: 500, y: 0 }, - controller.signal - ) - const settled = expect(route).rejects.toMatchObject({ name: 'AbortError' }) - await vi.advanceTimersByTimeAsync(100) - const sentBeforeAbort = moves().length - controller.abort() - await settled - expect(moves()).toHaveLength(sentBeforeAbort) - } finally { - vi.useRealTimers() - } - }) - - it('sends nothing for an already-aborted drag and cancels one aborted while it settles', async () => { - const contents = new WebContentsView().webContents - const mouse = () => - vi - .mocked(contents.debugger.sendCommand) - .mock.calls.filter(([method]) => method === 'Input.dispatchMouseEvent') - .map(([, params]) => toRecord(params).type) - const aborted = new AbortController() - aborted.abort() - await expect( - dragPointer(contents, { x: 0, y: 0 }, { x: 50, y: 0 }, undefined, aborted.signal) - ).rejects.toMatchObject({ name: 'AbortError' }) - expect(contents.debugger.sendCommand).not.toHaveBeenCalled() - - vi.useFakeTimers() - try { - const controller = new AbortController() - const drag = dragPointer( - contents, - { x: 0, y: 0 }, - { x: 50, y: 0 }, - undefined, - controller.signal - ) - const settled = expect(drag).rejects.toMatchObject({ name: 'AbortError' }) - // The default route takes 13 moves 20 ms apart, then a 120 ms settle hold. - await vi.advanceTimersByTimeAsync(300) - controller.abort() - await settled - expect(mouse().at(-1)).toBe('mouseReleased') - } finally { - vi.useRealTimers() - } - }) - it('releases the button when a timed drag is aborted mid-route', async () => { const contents = new WebContentsView().webContents const types = () => @@ -357,55 +185,6 @@ describe('browser-agent CDP instrumentation', () => { } }) - it('keeps the default drag pace and lands exactly on every via point', () => { - const direct = pointerPathSteps({ x: 0, y: 0 }, { via: [], durationMs: null }, { x: 120, y: 0 }) - expect(direct.stepDelayMs).toBe(20) - expect(direct.points).toHaveLength(12) - expect(direct.points[0]).toEqual({ x: 10, y: 0 }) - expect(direct.points[11]).toEqual({ x: 120, y: 0 }) - - const routed = pointerPathSteps( - { x: 0, y: 0 }, - { via: [{ x: 100, y: 0 }], durationMs: 800 }, - { x: 100, y: 300 } - ) - expect(routed.points).toContainEqual({ x: 100, y: 0 }) - expect(routed.points[routed.points.length - 1]).toEqual({ x: 100, y: 300 }) - expect(routed.points.length * routed.stepDelayMs).toBeCloseTo(800) - // The longer second segment gets about three times the steps of the first. - const corner = routed.points.findIndex((point) => point.x === 100 && point.y === 0) - expect(routed.points.length - 1 - corner).toBeGreaterThan(corner * 2) - }) - - it('holds the button down for holdMs before releasing it', async () => { - const contents = new WebContentsView().webContents - const types = () => - vi.mocked(contents.debugger.sendCommand).mock.calls.map(([, params]) => toRecord(params).type) - vi.useFakeTimers() - try { - const click = clickAt(contents, 5, 6, false, { ...PRIMARY_CLICK, holdMs: 1500 }) - await vi.advanceTimersByTimeAsync(1000) - expect(types()).toEqual(['mousePressed']) - - await vi.advanceTimersByTimeAsync(500) - await click - expect(types()).toEqual(['mousePressed', 'mouseReleased']) - } finally { - vi.useRealTimers() - } - }) - - it('presses nothing when its click was aborted before dispatch', async () => { - const contents = new WebContentsView().webContents - const controller = new AbortController() - controller.abort() - - await expect( - clickAt(contents, 5, 6, true, PRIMARY_CLICK, controller.signal) - ).rejects.toMatchObject({ name: 'AbortError' }) - expect(contents.debugger.sendCommand).not.toHaveBeenCalled() - }) - it('releases a held button as soon as its click is aborted', async () => { const contents = new WebContentsView().webContents const types = () => @@ -426,28 +205,6 @@ describe('browser-agent CDP instrumentation', () => { } }) - it('keeps a held right-click marked as the agent context menu until release', async () => { - const contents = new WebContentsView().webContents - vi.useFakeTimers() - try { - const rightHold = { ...PRIMARY_CLICK, button: 'right' as const, holdMs: 1500 } - await Promise.all([ - clickAt(contents, 5, 6, false, rightHold), - vi.advanceTimersByTimeAsync(1500), - ]) - expect(consumeAgentContextMenu(contents)).toBe(true) - - const click = clickAt(contents, 5, 6, false, rightHold) - await vi.advanceTimersByTimeAsync(0) - expect(consumeAgentContextMenu(contents)).toBe(true) - await vi.advanceTimersByTimeAsync(1500) - await click - expect(consumeAgentContextMenu(contents)).toBe(false) - } finally { - vi.useRealTimers() - } - }) - it('releases the mouse after a partial click failure', async () => { const contents = new WebContentsView().webContents vi.mocked(contents.debugger.sendCommand) @@ -472,44 +229,6 @@ describe('browser-agent CDP instrumentation', () => { ]) }) - it('best-effort releases the mouse when the press response is lost', async () => { - const contents = new WebContentsView().webContents - vi.mocked(contents.debugger.sendCommand) - .mockResolvedValueOnce({}) - .mockRejectedValueOnce(new Error('mouse press response lost')) - .mockRejectedValueOnce(new Error('cleanup unavailable')) - - await expect(clickAt(contents, 36, 48)).rejects.toThrow('mouse press response lost') - - expect(vi.mocked(contents.debugger.sendCommand).mock.calls).toEqual([ - ['Input.dispatchMouseEvent', { type: 'mouseMoved', x: 36, y: 48, button: 'none' }], - [ - 'Input.dispatchMouseEvent', - { - type: 'mousePressed', - x: 36, - y: 48, - button: 'left', - buttons: 1, - modifiers: 0, - clickCount: 1, - }, - ], - [ - 'Input.dispatchMouseEvent', - { - type: 'mouseReleased', - x: 36, - y: 48, - button: 'left', - buttons: 0, - modifiers: 0, - clickCount: 1, - }, - ], - ]) - }) - it('times out a hung press and sends cleanup before the tool watchdog can release', async () => { vi.useFakeTimers() try { @@ -909,19 +628,6 @@ describe('browser-agent file input handles', () => { ).toHaveLength(1) }) - it('supports a same-origin child document and XHTML input captured by its parent world', async () => { - const { contents, frame, input } = await fileInputFixture() - input.tagName = 'input' - const handle = await resolveFileInput(contents, frame, 'captureSameOriginChildInput()') - try { - await expect(setFileInputFiles(contents, handle, ['/staged/a.pdf'])).resolves.toEqual({ - files: [{ name: 'a.pdf', size: 12 }], - }) - } finally { - await releaseFileInput(contents, handle) - } - }) - it.each(['evaluation', 'metadata'] as const)('releases handles when %s fails', async (phase) => { const { contents, frame, input, behavior, send } = await fileInputFixture() if (phase === 'evaluation') behavior.rejectEvaluation = true @@ -934,64 +640,6 @@ describe('browser-agent file input handles', () => { expect(released).toEqual(phase === 'evaluation' ? ['exception'] : ['exception', 'wrapper']) }) - it('releases the transient node when cancellation arrives during validation', async () => { - const { contents, frame, behavior, send } = await fileInputFixture() - const handle = await resolveFileInput(contents, frame, 'captureUploadInput(4)') - const controller = new AbortController() - const onDispatch = vi.fn() - behavior.afterInputValidation = () => controller.abort() - try { - await expect( - setFileInputFiles(contents, handle, ['/staged/a.pdf'], controller.signal, onDispatch) - ).rejects.toThrow() - expect(send.mock.calls.some(([method]) => method === 'DOM.setFileInputFiles')).toBe(false) - expect(onDispatch).not.toHaveBeenCalled() - } finally { - await releaseFileInput(contents, handle) - } - expect(send).toHaveBeenCalledWith('Runtime.releaseObject', { objectId: 'original-input' }) - }) - - it('releases the transient node when Chromium rejects the file assignment', async () => { - const { contents, frame, behavior, send } = await fileInputFixture(true) - const handle = await resolveFileInput(contents, frame, 'captureUploadInput(4)') - behavior.rejectSet = true - const onDispatch = vi.fn() - try { - await expect( - setFileInputFiles(contents, handle, ['/staged/a.pdf'], undefined, onDispatch) - ).rejects.toThrow('disappeared') - expect(onDispatch.mock.calls).toEqual([['pending']]) - } finally { - await releaseFileInput(contents, handle) - } - expect(send).toHaveBeenCalledWith( - 'Runtime.releaseObject', - { objectId: 'original-input' }, - 'child-session' - ) - expect(send).toHaveBeenCalledWith( - 'Runtime.releaseObject', - { objectId: 'wrapper' }, - 'child-session' - ) - }) - - it('reads the original input even when its change handler removes it', async () => { - const { contents, frame, input, behavior } = await fileInputFixture() - const handle = await resolveFileInput(contents, frame, 'captureUploadInput(4)') - behavior.afterSet = () => { - input.isConnected = false - } - try { - await expect(setFileInputFiles(contents, handle, ['/staged/a.pdf'])).resolves.toEqual({ - files: [{ name: 'a.pdf', size: 12 }], - }) - } finally { - await releaseFileInput(contents, handle) - } - }) - it('reports pending dispatch while acknowledgement is held, then acknowledges before readback', async () => { const { contents, frame, behavior, send } = await fileInputFixture() const handle = await resolveFileInput(contents, frame, 'captureUploadInput(4)') @@ -1025,51 +673,6 @@ describe('browser-agent file input handles', () => { await releaseFileInput(contents, handle) } }) - - it('reports readback failure separately once Chromium has acknowledged the upload', async () => { - const { contents, frame, behavior, send } = await fileInputFixture() - const handle = await resolveFileInput(contents, frame, 'captureUploadInput(4)') - behavior.rejectReadback = true - try { - await expect(setFileInputFiles(contents, handle, ['/staged/a.pdf'])).resolves.toEqual({ - readbackError: 'Execution context was destroyed', - }) - } finally { - await releaseFileInput(contents, handle) - } - expect(send.mock.calls.filter(([method]) => method === 'DOM.setFileInputFiles')).toHaveLength(1) - expect(send).toHaveBeenCalledWith('Runtime.releaseObject', { objectId: 'original-input' }) - }) -}) - -describe('browser-agent CDP theme', () => { - it('emulates explicit light and dark preferences', async () => { - const contents = new WebContentsView().webContents - - await setColorScheme(contents, 'dark') - await setColorScheme(contents, 'light') - - expect(vi.mocked(contents.debugger.sendCommand).mock.calls).toEqual([ - [ - 'Emulation.setEmulatedMedia', - { features: [{ name: 'prefers-color-scheme', value: 'dark' }] }, - ], - [ - 'Emulation.setEmulatedMedia', - { features: [{ name: 'prefers-color-scheme', value: 'light' }] }, - ], - ]) - }) - - it('clears the override for the system preference', async () => { - const contents = new WebContentsView().webContents - - await setColorScheme(contents, 'system') - - expect(contents.debugger.sendCommand).toHaveBeenCalledWith('Emulation.setEmulatedMedia', { - features: [], - }) - }) }) /** @@ -1122,43 +725,6 @@ describe('browser-agent screenshot capture', () => { ) }) - it('crops the decoded image in memory without sending a CDP clip', async () => { - const { contents, cropped, image } = captureFixture({ width: 4096, height: 2048 }) - - const shot = await captureScreenshot(contents, { x: 100, y: 50, width: 200, height: 100 }) - - expect(contents.capturePage).toHaveBeenCalledWith(undefined, { stayHidden: true }) - expect(contents.debugger.sendCommand).not.toHaveBeenCalledWith( - 'Page.captureScreenshot', - expect.anything() - ) - expect(image.crop).toHaveBeenCalledWith({ x: 200, y: 100, width: 400, height: 200 }) - expect(cropped.resize).not.toHaveBeenCalled() - expect(shot).toEqual({ - dataUrl: `data:image/jpeg;base64,${Buffer.from('cropped').toString('base64')}`, - scale: 2, - viewport: { width: 2048, height: 1024 }, - imageSize: { width: 400, height: 200 }, - clip: { x: 100, y: 50, width: 200, height: 100 }, - }) - }) - - it('reports the actual CSS crop after rounding a narrow fractional element to pixels', async () => { - const { contents, cropped, image } = captureFixture({ width: 4096, height: 2048 }) - cropped.getSize.mockReturnValue({ width: 3, height: 201 }) - - const shot = await captureScreenshot(contents, { x: 0.1, y: 0.2, width: 1.1, height: 100 }) - - expect(image.crop).toHaveBeenCalledWith({ x: 0, y: 0, width: 3, height: 201 }) - expect(shot).toMatchObject({ - clip: { x: 0, y: 0, width: 1.5, height: 100.5 }, - imageSize: { width: 3, height: 201 }, - scale: 2, - }) - expect(100 / shot.scale).toBe(50) - expect(cropped.resize).not.toHaveBeenCalled() - }) - it.each([ { requested: { x: -10, y: -20, width: 30, height: 40 }, @@ -1187,46 +753,6 @@ describe('browser-agent screenshot capture', () => { } ) - /** - * A 2048px CSS viewport bounded to 1024px is scale 0.5, and the capture - * arrives at device resolution (4096px on a 2x display). The resize is what - * lands the image on the CSS-relative size the coordinate contract - * (cssX = imageX / scale) assumes. - */ - it('downscales the returned image to the CSS-relative size', async () => { - const { contents, resized, image } = captureFixture({ width: 4096, height: 2048 }) - - const shot = await captureScreenshot(contents) - - expect(image.resize).toHaveBeenCalledWith({ width: 1024, height: 512, quality: 'good' }) - expect(resized.toJPEG).toHaveBeenCalled() - expect(shot).toEqual({ - dataUrl: `data:image/jpeg;base64,${Buffer.from('resized').toString('base64')}`, - scale: 0.5, - viewport: { width: 2048, height: 1024 }, - imageSize: { width: 1024, height: 512 }, - }) - }) - - it('skips resizing when the capture already matches the target size', async () => { - const { contents, image } = captureFixture({ width: 1024, height: 512 }) - - const shot = await captureScreenshot(contents) - - expect(image.resize).not.toHaveBeenCalled() - expect(shot).toEqual({ - dataUrl: 'data:image/jpeg;base64,c2lt', - scale: 0.5, - viewport: { width: 2048, height: 1024 }, - imageSize: { width: 1024, height: 512 }, - }) - }) - - it('rejects an empty native capture', async () => { - const { contents } = captureFixture(null) - await expect(captureScreenshot(contents)).rejects.toThrow('empty image') - }) - describe('stalled native capture recovery', () => { beforeEach(() => vi.useFakeTimers()) afterEach(() => vi.useRealTimers()) @@ -1293,151 +819,6 @@ describe('browser-agent screenshot capture', () => { expect(contents.endFrameSubscription).toHaveBeenCalledTimes(2) expect(vi.getTimerCount()).toBe(0) }) - - it('ignores a timed-out frame callback while a later subscription is active', async () => { - const { contents, image } = captureFixture({ width: 1024, height: 512 }, 'fresh') - const stale = captureFixture({ width: 1024, height: 512 }, 'stale').image - vi.mocked(contents.capturePage).mockReturnValue(new Promise(() => {})) - const frames = observeFrames(contents) - const failed = expect(captureScreenshot(contents)).rejects.toThrow('frame capture timed out') - await vi.advanceTimersByTimeAsync(10_000) - await failed - - const recovered = captureScreenshot(contents) - const settled = vi.fn() - void recovered.then(settled) - await vi.advanceTimersByTimeAsync(0) - frames[0](stale) - await vi.advanceTimersByTimeAsync(0) - expect(settled).not.toHaveBeenCalled() - expect(contents.endFrameSubscription).toHaveBeenCalledOnce() - frames[1](image) - await expect(recovered).resolves.toMatchObject({ - dataUrl: `data:image/jpeg;base64,${Buffer.from('fresh').toString('base64')}`, - }) - expect(contents.endFrameSubscription).toHaveBeenCalledTimes(2) - }) - - it.each(['resolve', 'reject'] as const)( - 'ignores a late native %s and resumes native captures afterward', - async (outcome) => { - const { contents, image } = captureFixture({ width: 1024, height: 512 }, 'current') - const stale = captureFixture({ width: 1024, height: 512 }, 'stale').image - let settleNative: () => void = () => {} - vi.mocked(contents.capturePage).mockImplementationOnce( - () => - new Promise((resolve, reject) => { - settleNative = () => - outcome === 'resolve' ? resolve(stale) : reject(new Error('late failure')) - }) - ) - const frames = observeFrames(contents) - const capture = captureScreenshot(contents) - const settled = vi.fn() - void capture.then(settled) - await vi.advanceTimersByTimeAsync(5_000) - settleNative() - await vi.advanceTimersByTimeAsync(0) - expect(settled).not.toHaveBeenCalled() - expect(contents.endFrameSubscription).not.toHaveBeenCalled() - frames[0](image) - await expect(capture).resolves.toMatchObject({ - dataUrl: `data:image/jpeg;base64,${Buffer.from('current').toString('base64')}`, - }) - await expect(captureScreenshot(contents)).resolves.toMatchObject({ - dataUrl: `data:image/jpeg;base64,${Buffer.from('current').toString('base64')}`, - }) - expect(contents.capturePage).toHaveBeenCalledTimes(2) - expect(contents.beginFrameSubscription).toHaveBeenCalledOnce() - expect(vi.getTimerCount()).toBe(0) - } - ) - - it('rejects concurrent captures without replacing the active subscription or blocking another tab', async () => { - const { contents, image } = captureFixture({ width: 1024, height: 512 }) - const other = captureFixture({ width: 1024, height: 512 }) - vi.mocked(contents.capturePage).mockReturnValue(new Promise(() => {})) - const frames = observeFrames(contents) - const capture = captureScreenshot(contents) - await vi.advanceTimersByTimeAsync(0) - await expect(captureScreenshot(contents)).rejects.toThrow('already in progress') - expect(contents.capturePage).toHaveBeenCalledOnce() - await vi.advanceTimersByTimeAsync(5_000) - await expect(captureScreenshot(contents)).rejects.toThrow('already in progress') - expect(contents.beginFrameSubscription).toHaveBeenCalledOnce() - expect(contents.endFrameSubscription).not.toHaveBeenCalled() - await expect(captureScreenshot(other.contents)).resolves.toMatchObject({ - imageSize: { width: 1024, height: 512 }, - }) - frames[0](image) - await capture - expect(contents.endFrameSubscription).toHaveBeenCalledOnce() - }) - - it.each(['cancel', 'destroy'] as const)( - 'releases frame resources on %s and ignores a subsequent frame', - async (reason) => { - const { contents, image } = captureFixture({ width: 1024, height: 512 }) - vi.mocked(contents.capturePage).mockReturnValue(new Promise(() => {})) - const frames = observeFrames(contents) - const controller = new AbortController() - const removeAbort = vi.spyOn(controller.signal, 'removeEventListener') - const failed = expect( - captureScreenshot(contents, undefined, controller.signal) - ).rejects.toThrow(reason === 'cancel' ? 'cancelled' : 'tab was closed') - await vi.advanceTimersByTimeAsync(5_000) - const destroyed = vi - .mocked(contents.once) - .mock.calls.filter(([event]) => String(event) === 'destroyed') - .at(-1)?.[1] as unknown as (() => void) | undefined - expect(destroyed).toBeDefined() - if (reason === 'cancel') controller.abort() - else { - vi.mocked(contents.isDestroyed).mockReturnValue(true) - destroyed?.() - } - await failed - expect(contents.removeListener).toHaveBeenCalledWith('destroyed', destroyed) - expect(removeAbort).toHaveBeenCalledTimes(2) - expect(contents.endFrameSubscription).toHaveBeenCalledTimes(reason === 'cancel' ? 1 : 0) - frames[0](image) - await vi.advanceTimersByTimeAsync(0) - expect(contents.endFrameSubscription).toHaveBeenCalledTimes(reason === 'cancel' ? 1 : 0) - expect(vi.getTimerCount()).toBe(0) - if (reason === 'cancel') { - const recovered = captureScreenshot(contents) - await vi.advanceTimersByTimeAsync(0) - frames[1](image) - await recovered - expect(contents.capturePage).toHaveBeenCalledOnce() - expect(contents.endFrameSubscription).toHaveBeenCalledTimes(2) - } - } - ) - - it.each(['beginFrameSubscription', 'invalidate'] as const)( - 'cleans up a synchronous %s failure and permits another frame attempt', - async (method) => { - const { contents, image } = captureFixture({ width: 1024, height: 512 }) - vi.mocked(contents.capturePage).mockReturnValue(new Promise(() => {})) - const frames = observeFrames(contents) - vi.mocked(contents[method]).mockImplementationOnce(() => { - throw new Error('frame setup failed') - }) - const failed = expect(captureScreenshot(contents)).rejects.toThrow('frame setup failed') - await vi.advanceTimersByTimeAsync(5_000) - await failed - expect(contents.endFrameSubscription).toHaveBeenCalledOnce() - expect(vi.getTimerCount()).toBe(0) - - const recovered = captureScreenshot(contents) - await vi.advanceTimersByTimeAsync(0) - frames.at(-1)?.(image) - await expect(recovered).resolves.toMatchObject({ imageSize: { width: 1024, height: 512 } }) - expect(contents.capturePage).toHaveBeenCalledOnce() - expect(contents.endFrameSubscription).toHaveBeenCalledTimes(2) - } - ) }) it.each(['cancel', 'destroy'] as const)( @@ -1469,21 +850,6 @@ describe('browser-agent screenshot capture', () => { } ) - it('does not start capture after cancellation or keep a synchronous failure pending', async () => { - const { contents } = captureFixture({ width: 1024, height: 512 }) - const controller = new AbortController() - controller.abort() - await expect(captureScreenshot(contents, undefined, controller.signal)).rejects.toThrow() - expect(contents.capturePage).not.toHaveBeenCalled() - vi.mocked(contents.capturePage).mockImplementationOnce(() => { - throw new Error('native failure') - }) - await expect(captureScreenshot(contents)).rejects.toThrow('native failure') - await expect(captureScreenshot(contents)).resolves.toMatchObject({ - imageSize: { width: 1024, height: 512 }, - }) - }) - it('does not expose deprecated device-pixel metrics as a CSS viewport', async () => { const { contents } = captureFixture({ width: 1024, height: 512 }) vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { @@ -1499,46 +865,6 @@ describe('browser-agent screenshot capture', () => { expect(shot.imageSize).toEqual({ width: 1024, height: 512 }) }) - it('refuses element cropping without verified CSS viewport metrics', async () => { - const { contents } = captureFixture({ width: 1024, height: 512 }) - vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { - if (method === 'Page.getLayoutMetrics') { - return Promise.resolve({ layoutViewport: { clientWidth: 2048, clientHeight: 1024 } }) - } - return Promise.resolve(undefined) - }) - - await expect( - captureScreenshot(contents, { x: 10, y: 10, width: 100, height: 50 }) - ).rejects.toThrow(/CSS viewport/) - expect(contents.debugger.sendCommand).not.toHaveBeenCalledWith( - 'Page.captureScreenshot', - expect.anything() - ) - }) - - it('accepts stable finite scroll offsets around the capture', async () => { - const { contents } = captureFixture({ width: 1024, height: 512 }) - vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { - if (method === 'Page.getLayoutMetrics') { - return Promise.resolve({ - cssLayoutViewport: { - clientWidth: 2048, - clientHeight: 1024, - pageX: 12, - pageY: 34, - }, - }) - } - return Promise.resolve(undefined) - }) - - await expect(captureScreenshot(contents)).resolves.toMatchObject({ - viewport: { width: 2048, height: 1024 }, - imageSize: { width: 1024, height: 512 }, - }) - }) - it.each([ [ 'dimensions', diff --git a/apps/desktop/src/main/browser-agent/context-menu.test.ts b/apps/desktop/src/main/browser-agent/context-menu.test.ts index 0ab0c6cd395..44058879f89 100644 --- a/apps/desktop/src/main/browser-agent/context-menu.test.ts +++ b/apps/desktop/src/main/browser-agent/context-menu.test.ts @@ -64,49 +64,6 @@ const item = (template: MenuItemConstructorOptions[], label: string) => template.find((entry) => entry.label === label) describe('buildAgentContextMenuTemplate', () => { - it('always offers navigation and zoom, whatever was clicked', () => { - const template = buildAgentContextMenuTemplate(params(), page(), handlers()) - - // Unlike the main window's menu, an empty template is not an option here: - // the page has no menu of its own to fall back to. - expect(labels(template)).toEqual([ - 'Back', - 'Forward', - 'Reload', - 'Zoom In', - 'Zoom Out', - 'Actual Size (100%)', - ]) - }) - - it('offers clipboard items only where the click can use them', () => { - expect(labels(buildAgentContextMenuTemplate(params(), page(), handlers()))).not.toContain( - 'Copy' - ) - - const withSelection = buildAgentContextMenuTemplate( - params({ selectionText: ' hello ' }), - page(), - handlers() - ) - expect(labels(withSelection)).toContain('Copy') - - const inField = buildAgentContextMenuTemplate( - params({ isEditable: true, editFlags: { ...EDIT_FLAGS, canPaste: true } }), - page(), - handlers() - ) - expect(labels(inField)).toContain('Paste') - - // A read-only field can report canPaste; both signals have to agree. - const readOnly = buildAgentContextMenuTemplate( - params({ isEditable: false, editFlags: { ...EDIT_FLAGS, canPaste: true } }), - page(), - handlers() - ) - expect(labels(readOnly)).not.toContain('Paste') - }) - it('puts Add to chat first and preserves the exact nonblank selection', () => { const handled = handlers() const template = buildAgentContextMenuTemplate( @@ -122,95 +79,6 @@ describe('buildAgentContextMenuTemplate', () => { labels(buildAgentContextMenuTemplate(params({ selectionText: ' \n ' }), page(), handlers())) ).not.toContain('Add to chat') }) - - it('offers link items for http(s) targets only', () => { - const handled = handlers() - const template = buildAgentContextMenuTemplate( - params({ linkURL: 'https://example.com/docs' }), - page(), - handled - ) - expect(labels(template)).toContain('Open Link in New Tab') - - item(template, 'Open Link in New Tab')?.click?.({} as never, undefined as never, {} as never) - expect(handled.openTab).toHaveBeenCalledWith('https://example.com/docs') - - // The actions open a tab or copy an address; neither means anything for a - // script or mail target, so the menu must not offer them. - for (const linkURL of ['javascript:alert(1)', 'mailto:a@b.com', 'file:///etc/passwd']) { - const other = buildAgentContextMenuTemplate(params({ linkURL }), page(), handlers()) - expect(labels(other)).not.toContain('Open Link in New Tab') - expect(labels(other)).not.toContain('Copy Link Address') - } - }) - - it('disables navigation the page cannot do', () => { - const template = buildAgentContextMenuTemplate( - params(), - page({ canGoBack: false, canGoForward: false }), - handlers() - ) - - expect(item(template, 'Back')?.enabled).toBe(false) - expect(item(template, 'Forward')?.enabled).toBe(false) - expect(item(template, 'Reload')?.enabled).toBeUndefined() - }) - - it('reports the current zoom and disables the ends of the ladder', () => { - // Two rungs up from the baseline, reported against the baseline rather than - // against Chromium's native scale (where this factor would read 110%). - const twoUp = steppedZoomFactor(steppedZoomFactor(BASE_ZOOM_FACTOR, 1), 1) - const stepped = buildAgentContextMenuTemplate(params(), page({ zoomFactor: twoUp }), handlers()) - expect(item(stepped, 'Actual Size (121%)')?.enabled).toBe(true) - - const atMax = buildAgentContextMenuTemplate(params(), page({ zoomFactor: 3 }), handlers()) - expect(item(atMax, 'Zoom In')?.enabled).toBe(false) - expect(item(atMax, 'Zoom Out')?.enabled).toBe(true) - - const atMin = buildAgentContextMenuTemplate(params(), page({ zoomFactor: 0.5 }), handlers()) - expect(item(atMin, 'Zoom Out')?.enabled).toBe(false) - - // Nothing to reset to at 100%. - expect( - item(buildAgentContextMenuTemplate(params(), page(), handlers()), 'Actual Size (100%)') - ?.enabled - ).toBe(false) - }) - - it('resets to the configured default, undoing accumulated drift', () => { - const handled = handlers() - // Three rungs of float multiplication up, so the factor no longer sits on a - // clean value — reset has to restore the baseline exactly, not step back. - const drifted = [1, 1, 1].reduce((factor) => steppedZoomFactor(factor, 1), BASE_ZOOM_FACTOR) - const configuredDefault = BASE_ZOOM_FACTOR * 1.25 - const template = buildAgentContextMenuTemplate( - params(), - page({ zoomFactor: drifted, defaultZoomFactor: configuredDefault }), - handled - ) - - item(template, 'Actual Size (133%)')?.click?.({} as never, undefined as never, {} as never) - - expect(handled.setZoomFactor).toHaveBeenCalledWith(configuredDefault) - }) - - it('never leaves a separator with nothing above it', () => { - for (const p of [ - params(), - params({ selectionText: 'hi' }), - params({ linkURL: 'https://example.com' }), - params({ isEditable: true, editFlags: { ...EDIT_FLAGS, canPaste: true } }), - ]) { - const template = buildAgentContextMenuTemplate(p, page(), handlers()) - expect(template[0].type).not.toBe('separator') - expect(template[template.length - 1].type).not.toBe('separator') - expect( - template.some( - (entry, index) => entry.type === 'separator' && template[index - 1]?.type === 'separator' - ) - ).toBe(false) - } - }) }) describe('attachAgentContextMenu', () => { @@ -260,66 +128,13 @@ describe('attachAgentContextMenu', () => { expect(Menu.buildFromTemplate).toHaveBeenCalledTimes(1) } ) - - it('pops a menu built from the page that was right-clicked', () => { - const contents = new WebContentsView().webContents - vi.mocked(contents.navigationHistory.canGoBack).mockReturnValue(true) - attachAgentContextMenu(contents, { - addToChat: vi.fn(), - openTab: vi.fn(), - defaultZoomFactor: () => BASE_ZOOM_FACTOR, - }) - - const listeners = vi.mocked(contents.on).mock.calls as unknown as [ - string, - ContextMenuListener, - ][] - const onContextMenu = listeners.find(([event]) => event === 'context-menu')?.[1] - expect(onContextMenu).toBeDefined() - onContextMenu?.({}, params()) - - const template = vi.mocked(Menu.buildFromTemplate).mock.calls.at(-1)?.[0] as - | MenuItemConstructorOptions[] - | undefined - // The template is read off the live page, not a snapshot of it. - expect(item(template ?? [], 'Back')?.enabled).toBe(true) - expect(item(template ?? [], 'Forward')?.enabled).toBe(false) - }) }) describe('steppedZoomFactor', () => { - it('steps up and down from the current factor', () => { - expect(steppedZoomFactor(1, 1)).toBe(1.1) - expect(steppedZoomFactor(1, -1)).toBe(1 / 1.1) - }) - it('clamps at both ends so a step never runs away', () => { expect(steppedZoomFactor(3, 1)).toBe(3) expect(steppedZoomFactor(0.5, -1)).toBe(0.5) }) - - it('treats a nonsense factor as the baseline', () => { - // One rung up from the baseline is Chromium's native 1.0. - expect(steppedZoomFactor(Number.NaN, 1)).toBe(1) - expect(steppedZoomFactor(0, 1)).toBe(1) - }) -}) - -describe('BASE_ZOOM_FACTOR', () => { - it('renders a rung below native but reads as 100%', () => { - expect(BASE_ZOOM_FACTOR).toBeCloseTo(0.909, 3) - expect(zoomPercentOf(BASE_ZOOM_FACTOR)).toBe(100) - }) - - it('keeps the ladder landing exactly on Chromium native one step up', () => { - expect(steppedZoomFactor(BASE_ZOOM_FACTOR, 1)).toBe(1) - expect(zoomPercentOf(1)).toBe(110) - }) - - it('stays inside the ladder, so the page menu can still step both ways', () => { - expect(steppedZoomFactor(BASE_ZOOM_FACTOR, 1)).not.toBe(BASE_ZOOM_FACTOR) - expect(steppedZoomFactor(BASE_ZOOM_FACTOR, -1)).not.toBe(BASE_ZOOM_FACTOR) - }) }) describe('zoomPercentOf', () => { @@ -328,11 +143,4 @@ describe('zoomPercentOf', () => { expect(zoomPercentOf(BASE_ZOOM_FACTOR)).toBe(100) expect(zoomPercentOf(steppedZoomFactor(BASE_ZOOM_FACTOR, 1))).toBe(110) }) - - it('still reads 100% after a round trip up and back down', () => { - // The ladder is float arithmetic, so the reset item's `!== 100` guard has to - // survive a step that does not return bit-identically to the baseline. - const roundTripped = steppedZoomFactor(steppedZoomFactor(BASE_ZOOM_FACTOR, 1), -1) - expect(zoomPercentOf(roundTripped)).toBe(100) - }) }) diff --git a/apps/desktop/src/main/browser-agent/driver-profile.test.ts b/apps/desktop/src/main/browser-agent/driver-profile.test.ts index 90d5dab667d..6103f01de82 100644 --- a/apps/desktop/src/main/browser-agent/driver-profile.test.ts +++ b/apps/desktop/src/main/browser-agent/driver-profile.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { describe, expect, it, vi } from 'vitest' vi.mock('electron', () => import('@/test/electron-mock')) @@ -29,10 +29,6 @@ import { import type { ConfigStore } from '@/main/config' describe('clearBrowserProfile', () => { - beforeEach(() => { - vi.clearAllMocks() - }) - it('requires settings erasure for sign-out but lets explicit server repair replace it', async () => { const config = { get: vi.fn(() => undefined), diff --git a/apps/desktop/src/main/browser-agent/driver.test.ts b/apps/desktop/src/main/browser-agent/driver.test.ts index fa402b3ef61..5baaed87f00 100644 --- a/apps/desktop/src/main/browser-agent/driver.test.ts +++ b/apps/desktop/src/main/browser-agent/driver.test.ts @@ -1,6 +1,6 @@ import { BROWSER_TOOL_QUEUE_WAIT_TIMEOUT_MS } from '@sim/browser-protocol' import { toRecord } from '@sim/utils/object' -import type { MenuItemConstructorOptions, WebContents } from 'electron' +import type { WebContents } from 'electron' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('electron', () => import('@/test/electron-mock')) @@ -15,11 +15,10 @@ vi.mock('@/main/browser-agent/file-transfer', () => ({ discardStagedUploads: vi.fn(async () => {}), })) -import { BrowserWindow, Menu, type nativeImage } from 'electron' +import { BrowserWindow, type nativeImage } from 'electron' import * as cdp from '@/main/browser-agent/cdp' import * as driverModule from '@/main/browser-agent/driver' import * as session from '@/main/browser-agent/session' -import { fillCoordinator } from '@/main/browser-credentials' import type { BrowserSessionSnapshot } from '@/main/desktop-chat-session-store' type DriverModule = typeof import('@/main/browser-agent/driver') @@ -99,53 +98,6 @@ describe('executeTool', () => { expect(prepare).not.toHaveBeenCalled() }) - it('reports missing required parameters by name', async () => { - const result = await driver.executeTool('chat-test', 'browser_navigate', {}) - expect(result.ok).toBe(false) - expect(result.error).toMatch(/Missing required parameter "url"/) - }) - - it('loads SSRF-checked agent navigation destinations', async () => { - const navigations = [ - ['browser_navigate', 'http://127.0.0.1:4011/navigate'], - ['browser_open_url', 'http://127.0.0.1:4012/open'], - ['browser_open_tab', 'http://127.0.0.1:4013/tab'], - ] as const - - for (const [tool, url] of navigations) { - await expect(driver.executeTool('chat-test', tool, { url })).resolves.toMatchObject({ - ok: true, - }) - const contents = session.requireAutomationTab().view.webContents - expect(contents.loadURL).toHaveBeenCalledWith(url) - } - }) - - it('keeps the 400ms hydration grace without rediscovering a completed load', async () => { - await driver.executeTool('chat-test', 'browser_open_tab', {}) - const contents = session.requireTab().view.webContents - vi.useFakeTimers() - try { - let settled = false - const navigation = driver.executeTool('chat-test', 'browser_navigate', { - url: 'http://127.0.0.1/loaded', - }) - void navigation.then(() => { - settled = true - }) - await vi.advanceTimersByTimeAsync(0) - expect(contents.loadURL).toHaveBeenCalledWith('http://127.0.0.1/loaded') - - await vi.advanceTimersByTimeAsync(399) - expect(settled).toBe(false) - await vi.advanceTimersByTimeAsync(1) - expect(settled).toBe(true) - await expect(navigation).resolves.toMatchObject({ ok: true }) - } finally { - vi.useRealTimers() - } - }) - it('still waits for a replacement load after loadURL has resolved', async () => { await driver.executeTool('chat-test', 'browser_open_tab', {}) const contents = session.requireTab().view.webContents @@ -203,32 +155,6 @@ describe('executeTool', () => { } }) - it('accepts ERR_ABORTED only when a replacement navigation changed the URL', async () => { - vi.useFakeTimers() - try { - await driver.executeTool('chat-test', 'browser_open_tab', {}) - const contents = session.requireTab().view.webContents - let currentUrl = 'http://127.0.0.1/old' - vi.mocked(contents.getURL).mockImplementation(() => currentUrl) - vi.mocked(contents.loadURL).mockImplementation(async () => { - currentUrl = 'http://127.0.0.1/replacement' - throw Object.assign(new Error('net::ERR_ABORTED'), { code: 'ERR_ABORTED' }) - }) - - const navigation = driver.executeTool('chat-test', 'browser_navigate', { - url: 'http://127.0.0.1/new', - }) - await vi.advanceTimersByTimeAsync(1_000) - - await expect(navigation).resolves.toMatchObject({ - ok: true, - result: { url: 'http://127.0.0.1/replacement' }, - }) - } finally { - vi.useRealTimers() - } - }) - it('reports non-abort navigation failures instead of treating dispatch as success', async () => { await driver.executeTool('chat-test', 'browser_open_tab', {}) const contents = session.requireTab().view.webContents @@ -246,40 +172,6 @@ describe('executeTool', () => { }) }) - it('serializes tool calls: a queued failure never rejects the next call', async () => { - const first = await driver.executeTool('chat-test', 'browser_snapshot', {}) - expect(first.ok).toBe(false) - const second = await driver.executeTool('chat-test', 'browser_list_tabs', {}) - // list_tabs works without a session (empty list). - expect(second.ok).toBe(true) - expect(second.result).toMatchObject({ tabs: [] }) - }) - - it('lists safe download metadata without opening a page', async () => { - const result = await driver.executeTool('chat-test', 'browser_list_downloads', {}) - - expect(result).toEqual({ - ok: true, - result: { scopeId: 'chat-test', downloads: [] }, - }) - expect(session.peekTabsState().tabs).toEqual([]) - }) - - it('reloads the active tab and waits for its load boundary', async () => { - await driver.executeTool('chat-test', 'browser_open_tab', {}) - const contents = session.requireTab().view.webContents - vi.useFakeTimers() - try { - const reload = driver.executeTool('chat-test', 'browser_reload', {}) - await vi.advanceTimersByTimeAsync(500) - - await expect(reload).resolves.toMatchObject({ ok: true }) - expect(contents.reload).toHaveBeenCalledOnce() - } finally { - vi.useRealTimers() - } - }) - it('uses the shared failed-page recovery path when reloading', async () => { await driver.executeTool('chat-test', 'browser_open_tab', {}) const tab = session.requireTab() @@ -411,73 +303,6 @@ describe('executeTool', () => { } }) - it('does not let a detached takeover poll touch a disposed scope', async () => { - await driver.executeTool('chat-test', 'browser_open_tab', {}) - vi.useFakeTimers() - const hasSession = vi.spyOn(session, 'hasSession') - try { - const takeover = driver.executeTool( - 'chat-test', - 'browser_request_takeover', - { reason: 'Please finish in the browser' }, - 'tool-disposed-takeover' - ) - await vi.advanceTimersByTimeAsync(0) - - driver.disposeBrowserScope('chat-test') - hasSession.mockClear() - await expect(takeover).resolves.toMatchObject({ - ok: false, - error: expect.stringContaining('cancelled'), - }) - await vi.advanceTimersByTimeAsync(1_500) - - expect(hasSession).not.toHaveBeenCalled() - } finally { - hasSession.mockRestore() - vi.useRealTimers() - } - }) - - it('does not let a detached text wait touch a disposed scope', async () => { - await driver.executeTool('chat-test', 'browser_open_tab', {}) - const contents = session.requireTab().view.webContents - vi.mocked(contents.getURL).mockReturnValue('https://example.com/') - let resolvePageProbe: (value: boolean) => void = () => {} - vi.mocked(contents.executeJavaScript).mockImplementation( - () => - new Promise((resolve) => { - resolvePageProbe = resolve - }) - ) - vi.useFakeTimers() - const automationTab = vi.spyOn(session, 'automationTab') - try { - const waiting = driver.executeTool( - 'chat-test', - 'browser_wait_for', - { text: 'ready', timeoutMs: 120_000 }, - 'tool-disposed-wait' - ) - await vi.advanceTimersByTimeAsync(0) - expect(contents.executeJavaScript).toHaveBeenCalled() - - driver.disposeBrowserScope('chat-test') - automationTab.mockClear() - await expect(waiting).resolves.toMatchObject({ - ok: false, - error: expect.stringContaining('cancelled'), - }) - resolvePageProbe(false) - await vi.advanceTimersByTimeAsync(300) - - expect(automationTab).not.toHaveBeenCalled() - } finally { - automationTab.mockRestore() - vi.useRealTimers() - } - }) - it('does not let a detached screenshot verification touch a disposed scope', async () => { await driver.executeTool('chat-test', 'browser_open_tab', {}) const contents = session.requireTab().view.webContents @@ -581,50 +406,6 @@ describe('executeTool', () => { expect(session.withBrowserScope('chat-test', () => session.peekTabsState()).tabs).toEqual([]) }) - it('captures a missing scope without materializing driver state', async () => { - const boundary = driver.captureBrowserToolQueueBoundary('chat-not-yet-active') - expect(boundary).not.toBeNull() - if (!boundary) throw new Error('Expected browser tool authorization admission') - - expect(boundary).toMatchObject({ - scopeId: 'chat-not-yet-active', - generation: null, - cancellationEpoch: null, - }) - - driver.activateBrowserScope('chat-not-yet-active') - await expect( - driver.executeTool( - 'chat-not-yet-active', - 'browser_list_tabs', - {}, - 'tool-authorized-before-activation', - boundary - ) - ).resolves.toMatchObject({ ok: true }) - }) - - it('rejects a missing-scope authorization after process-wide browser teardown', async () => { - const boundary = driver.captureBrowserToolQueueBoundary('chat-not-yet-active') - expect(boundary).not.toBeNull() - if (!boundary) throw new Error('Expected browser tool authorization admission') - driver.closeBrowserSession() - driver.activateBrowserScope('chat-not-yet-active') - - await expect( - driver.executeTool( - 'chat-not-yet-active', - 'browser_list_tabs', - {}, - 'tool-authorized-before-global-close', - boundary - ) - ).resolves.toMatchObject({ - ok: false, - error: expect.stringContaining('cancelled before it started'), - }) - }) - it('rejects a first-use authorization after its scope is disposed and reopened', async () => { const boundary = driver.captureBrowserToolQueueBoundary('chat-first-use-disposed') expect(boundary).not.toBeNull() @@ -650,31 +431,6 @@ describe('executeTool', () => { ).toEqual([]) }) - it('rejects a first-use authorization after its scope is suspended and reopened', async () => { - const boundary = driver.captureBrowserToolQueueBoundary('chat-first-use-suspended') - expect(boundary).not.toBeNull() - if (!boundary) throw new Error('Expected browser tool authorization admission') - - expect(driver.suspendBrowserScope('chat-first-use-suspended')).toBe(true) - driver.activateBrowserScope('chat-first-use-suspended') - - await expect( - driver.executeTool( - 'chat-first-use-suspended', - 'browser_open_tab', - {}, - 'tool-authorized-before-first-use-suspension', - boundary - ) - ).resolves.toMatchObject({ - ok: false, - error: expect.stringContaining('cancelled before it started'), - }) - expect( - session.withBrowserScope('chat-first-use-suspended', () => session.peekTabsState()).tabs - ).toEqual([]) - }) - it('cancels a provisional first-use authorization when its durable scope is disposed', async () => { const boundary = driver.captureBrowserToolQueueBoundary('pending:first-use-disposed') expect(boundary).not.toBeNull() @@ -719,28 +475,6 @@ describe('executeTool', () => { ).resolves.toMatchObject({ ok: true }) }) - it('rejects an existing-scope authorization after disposal and recreation', async () => { - const boundary = driver.captureBrowserToolQueueBoundary('chat-test') - expect(boundary).not.toBeNull() - if (!boundary) throw new Error('Expected browser tool authorization admission') - - driver.disposeBrowserScope('chat-test') - driver.activateBrowserScope('chat-test') - - await expect( - driver.executeTool( - 'chat-test', - 'browser_list_tabs', - {}, - 'tool-authorized-before-scope-disposal', - boundary - ) - ).resolves.toMatchObject({ - ok: false, - error: expect.stringContaining('cancelled before it started'), - }) - }) - it('bounds pending authorizations without materializing their scopes', () => { const boundaries = capturePendingAuthorizations(driver, 'chat-pending-authorization') @@ -766,32 +500,6 @@ describe('executeTool', () => { if (replacement) driver.releaseBrowserToolQueueBoundary(replacement) }) - it('retains disposed-scope authorization admissions until their fetches settle', () => { - const boundaries = capturePendingAuthorizations(driver, 'chat-disposed-authorizations') - - driver.disposeBrowserScope('chat-disposed-authorizations') - expect(boundaries.every((boundary) => boundary.cancelled)).toBe(true) - expect(driver.captureBrowserToolQueueBoundary('chat-disposed-authorizations')).toBeNull() - - releasePendingAuthorizations(driver, boundaries) - const replacement = driver.captureBrowserToolQueueBoundary('chat-disposed-authorizations') - expect(replacement).not.toBeNull() - if (replacement) driver.releaseBrowserToolQueueBoundary(replacement) - }) - - it('retains suspended-scope authorization admissions until their fetches settle', () => { - const boundaries = capturePendingAuthorizations(driver, 'chat-suspended-authorizations') - - expect(driver.suspendBrowserScope('chat-suspended-authorizations')).toBe(true) - expect(boundaries.every((boundary) => boundary.cancelled)).toBe(true) - expect(driver.captureBrowserToolQueueBoundary('chat-suspended-authorizations')).toBeNull() - - releasePendingAuthorizations(driver, boundaries) - const replacement = driver.captureBrowserToolQueueBoundary('chat-suspended-authorizations') - expect(replacement).not.toBeNull() - if (replacement) driver.releaseBrowserToolQueueBoundary(replacement) - }) - it('retains process-wide authorization admissions across driver reinitialization', () => { const boundaries = ['chat-auth-a', 'chat-auth-b', 'chat-auth-c', 'chat-auth-d'].flatMap( (scopeId) => capturePendingAuthorizations(driver, scopeId) @@ -829,31 +537,6 @@ describe('executeTool', () => { }) }) - it('settles native automation activity immediately when an active tool is cancelled', async () => { - await driver.executeTool('chat-test', 'browser_open_tab', {}) - vi.useFakeTimers() - try { - const waiting = driver.executeTool( - 'chat-test', - 'browser_wait_for', - { timeoutMs: 120_000 }, - 'tool-waiting' - ) - await vi.advanceTimersByTimeAsync(0) - expect(session.getTabsState().automationActive).toBe(true) - - expect(driver.cancelActiveTool('chat-test')).toBe(true) - await vi.advanceTimersByTimeAsync(0) - expect(session.getTabsState().automationActive).toBe(false) - await expect(waiting).resolves.toMatchObject({ - ok: false, - error: expect.stringContaining('cancelled'), - }) - } finally { - vi.useRealTimers() - } - }) - it('cancels queued pre-boundary tools while allowing later browser work', async () => { await driver.executeTool('chat-test', 'browser_open_tab', {}) vi.useFakeTimers() @@ -913,33 +596,6 @@ describe('executeTool', () => { } }) - it('returns a free-text takeover instruction to the browser agent', async () => { - await driver.executeTool('chat-test', 'browser_open_tab', {}) - vi.useFakeTimers() - try { - const takeover = driver.executeTool('chat-test', 'browser_request_takeover', { - reason: 'Please pick a match in the draw', - }) - await vi.advanceTimersByTimeAsync(0) - - await driver.handlePanelAction('chat-test', { - action: 'takeover-done', - takeoverResponse: 'Open the second match', - }) - await vi.advanceTimersByTimeAsync(1_500) - - await expect(takeover).resolves.toMatchObject({ - ok: true, - result: { - completed: true, - userInstruction: 'Open the second match', - }, - }) - } finally { - vi.useRealTimers() - } - }) - it('publishes a settled tab when the main frame finishes before subresources', async () => { const onPageState = vi.fn() const onTabsState = vi.fn() @@ -1033,22 +689,6 @@ describe('executeTool', () => { expect(contents.loadURL).toHaveBeenCalledWith(failedUrl) }) - it('forces fill availability to replay on scope activation and tab switches', async () => { - const refreshAvailability = vi - .spyOn(fillCoordinator()!, 'refreshAvailability') - .mockResolvedValue() - - driver.activateBrowserScope('chat-with-login') - expect(refreshAvailability).toHaveBeenCalledWith(true) - - await driver.executeTool('chat-with-login', 'browser_open_tab', {}) - await driver.executeTool('chat-with-login', 'browser_open_tab', {}) - refreshAvailability.mockClear() - session.switchTab('1') - - expect(refreshAvailability).toHaveBeenCalledWith(true) - }) - it('keeps target-blank initiation user-owned while automation is active', async () => { driver = freshDriver() await driver.executeTool('chat-test', 'browser_open_tab', {}) @@ -1090,142 +730,7 @@ describe('executeTool', () => { expect(popup.loadURL).toHaveBeenCalledWith('https://agent-popup.example/') }) - it('keeps context-menu new tabs user-owned while automation is active', async () => { - driver = freshDriver() - await driver.executeTool('chat-test', 'browser_open_tab', {}) - const source = session.requireTab().view.webContents - session.setAutomationActive(true) - vi.mocked(Menu.buildFromTemplate).mockClear() - const contextMenu = (source.on as unknown as ReturnType).mock.calls.find( - ([eventName]) => eventName === 'context-menu' - )?.[1] as (event: unknown, params: unknown) => void - contextMenu( - {}, - { - selectionText: '', - linkURL: 'https://context-link.example/', - isEditable: false, - editFlags: { canPaste: false }, - } - ) - const template = vi.mocked(Menu.buildFromTemplate).mock.calls.at(-1)?.[0] as - | MenuItemConstructorOptions[] - | undefined - template - ?.find((item) => item.label === 'Open Link in New Tab') - ?.click?.({} as never, undefined as never, {} as never) - - const popup = session.activeTab()?.view.webContents - if (!popup) throw new Error('Expected context-menu tab') - expect(session.automationTab()?.view.webContents).toBe(source) - expect(popup.loadURL).toHaveBeenCalledWith('https://context-link.example/') - }) - - it('builds the native toolbar menu and routes renderer-owned actions back to its chat', async () => { - await driver.executeTool('chat-test', 'browser_open_tab', {}) - const win = new BrowserWindow() - vi.mocked(Menu.buildFromTemplate).mockClear() - - expect(driver.showToolbarMenu('chat-test', win, { x: 20, y: 30 })).toBe(true) - const template = vi.mocked(Menu.buildFromTemplate).mock.calls[0]?.[0] as - | MenuItemConstructorOptions[] - | undefined - const labels = template?.filter((item) => item.type !== 'separator').map((item) => item.label) - expect(labels).toEqual([ - 'Find in Page', - 'Zoom (110%)', - 'Fill Saved Password', - 'Passwords', - 'Import Passwords', - 'Browser Settings', - ]) - - const settings = template?.find((item) => item.label === 'Browser Settings') - const openSettings = settings?.click as (() => void) | undefined - openSettings?.() - expect(win.webContents.send).toHaveBeenCalledWith( - 'browser-agent:toolbar-command', - 'browser-settings', - 'chat-test' - ) - }) - - it('routes an exact renderer media decision through the scoped session boundary', async () => { - const respond = vi.spyOn(session, 'respondToMediaPermission').mockResolvedValue() - - await driver.handlePanelAction('chat-test', { - action: 'respond-media-permission', - requestId: 'request-1', - allowed: true, - }) - await driver.handlePanelAction('chat-test', { - action: 'respond-media-permission', - requestId: 'request-2', - }) - - expect(respond).toHaveBeenCalledOnce() - expect(respond).toHaveBeenCalledWith('request-1', true) - }) - - it('ignores retired site decisions without changing tab ownership', async () => { - const claim = vi.spyOn(session, 'claimActiveTabForUser') - - await driver.handlePanelAction('chat-test', { - action: 'respond-site-permission', - requestId: 'request-1', - allowed: true, - }) - await driver.handlePanelAction('chat-test', { - action: 'respond-site-permission', - requestId: 'request-2', - }) - - expect(claim).not.toHaveBeenCalled() - }) - - it('loads the exact URL entered through the user omnibox', async () => { - await driver.executeTool('chat-test', 'browser_open_tab', {}) - const contents = session.requireTab().view.webContents - - await driver.handlePanelAction('chat-test', { - action: 'navigate', - url: 'https://docs.example/private?token=secret', - }) - - expect(contents.loadURL).toHaveBeenCalledWith('https://docs.example/private?token=secret') - }) - - it('waits for a selected restored tab before reporting it ready to the model', async () => { - await driver.executeTool('chat-test', 'browser_open_tab', {}) - const tab = session.requireTab() - let releaseRestore = () => {} - const wait = vi.spyOn(session, 'waitForPendingTabRestore').mockImplementation( - () => - new Promise((resolve) => { - releaseRestore = () => resolve(true) - }) - ) - let settled = false - const switched = driver - .executeTool('chat-test', 'browser_switch_tab', { tabId: tab.id }) - .then((result) => { - settled = true - return result - }) - - await Promise.resolve() - expect(settled).toBe(false) - expect(wait).toHaveBeenCalledWith(tab) - - releaseRestore() - await expect(switched).resolves.toMatchObject({ - ok: true, - result: { tabId: tab.id }, - }) - wait.mockRestore() - }) - - it('does not report a timed-out restored tab as ready to the model', async () => { + it('does not report a timed-out restored tab as ready to the model', async () => { await driver.executeTool('chat-test', 'browser_open_tab', {}) const tab = session.requireTab() const wait = vi.spyOn(session, 'waitForPendingTabRestore').mockResolvedValue(false) @@ -1240,33 +745,6 @@ describe('executeTool', () => { wait.mockRestore() }) - it('allows a fifty-second restored-tab consent and load without duplicating the tab', async () => { - vi.useFakeTimers() - try { - await driver.executeTool('chat-test', 'browser_open_tab', {}) - const tab = session.requireTab() - const wait = vi.spyOn(session, 'waitForPendingTabRestore').mockImplementation( - () => - new Promise((resolve) => { - setTimeout(() => resolve(true), 50_000) - }) - ) - - const switched = driver.executeTool('chat-test', 'browser_switch_tab', { tabId: tab.id }) - await vi.advanceTimersByTimeAsync(50_000) - - await expect(switched).resolves.toMatchObject({ - ok: true, - result: { tabId: tab.id }, - }) - expect(session.listTabs()).toHaveLength(1) - expect(session.requireTab()).toBe(tab) - wait.mockRestore() - } finally { - vi.useRealTimers() - } - }) - it('keeps tool queues and tab state isolated by chat scope', async () => { await driver.executeTool('chat-a', 'browser_open_tab', {}) await driver.executeTool('chat-a', 'browser_open_tab', {}) @@ -1300,18 +778,6 @@ describe('executeTool', () => { expect(driver.migrateBrowserScope('pending:other-chat', 'chat-occupied')).toBe(false) }) - it('keeps a durable destination adoptable after an empty restore', async () => { - await driver.executeTool('pending:new-chat', 'browser_open_tab', {}) - driver.activateBrowserScope('chat-real') - expect(driver.restoreBrowserScope('chat-real')).toMatchObject({ tabs: [] }) - - expect(driver.migrateBrowserScope('pending:new-chat', 'chat-real')).toBe(true) - await expect(driver.executeTool('chat-real', 'browser_list_tabs', {})).resolves.toMatchObject({ - ok: true, - result: { scopeId: 'chat-real', tabs: [{ tabId: '1' }] }, - }) - }) - it('cancels only the replaced destination authorizations during migration', async () => { await driver.executeTool('pending:new-chat', 'browser_open_tab', {}) driver.activateBrowserScope('chat-real') @@ -1359,57 +825,6 @@ describe('executeTool', () => { ).resolves.toMatchObject({ ok: true }) }) - it('retains replaced destination admissions until their authorization fetches settle', async () => { - await driver.executeTool('pending:new-chat', 'browser_open_tab', {}) - driver.activateBrowserScope('chat-real') - const sourceBoundary = driver.captureBrowserToolQueueBoundary('pending:new-chat') - const destinationBoundaries = capturePendingAuthorizations( - driver, - 'chat-real', - driver.BROWSER_TOOL_ADMISSION_LIMITS.perScope - 1 - ) - expect(sourceBoundary).not.toBeNull() - if (!sourceBoundary) throw new Error('Expected source authorization admission') - - expect(driver.migrateBrowserScope('pending:new-chat', 'chat-real')).toBe(true) - - expect(destinationBoundaries.every((boundary) => boundary.cancelled)).toBe(true) - expect(sourceBoundary.cancelled).toBe(false) - expect(driver.captureBrowserToolQueueBoundary('chat-real')).toBeNull() - - releasePendingAuthorizations(driver, destinationBoundaries) - await expect( - driver.executeTool( - 'chat-real', - 'browser_list_tabs', - {}, - 'tool-source-after-destination-settlement', - sourceBoundary - ) - ).resolves.toMatchObject({ ok: true }) - const replacement = driver.captureBrowserToolQueueBoundary('chat-real') - expect(replacement).not.toBeNull() - if (replacement) driver.releaseBrowserToolQueueBoundary(replacement) - }) - - it('keeps migrated source admissions charged to the durable scope after disposal', () => { - driver.activateBrowserScope('pending:new-chat') - const sourceBoundaries = capturePendingAuthorizations(driver, 'pending:new-chat') - - expect(driver.migrateBrowserScope('pending:new-chat', 'chat-real')).toBe(true) - expect(sourceBoundaries.every((boundary) => boundary.scopeId === 'chat-real')).toBe(true) - - driver.disposeBrowserScope('chat-real') - driver.activateBrowserScope('chat-real') - expect(sourceBoundaries.every((boundary) => boundary.cancelled)).toBe(true) - expect(driver.captureBrowserToolQueueBoundary('chat-real')).toBeNull() - - releasePendingAuthorizations(driver, sourceBoundaries) - const replacement = driver.captureBrowserToolQueueBoundary('chat-real') - expect(replacement).not.toBeNull() - if (replacement) driver.releaseBrowserToolQueueBoundary(replacement) - }) - it('retains a migrated provisional alias for callbacks until durable disposal', async () => { await driver.executeTool('pending:new-chat', 'browser_open_tab', {}) const tab = session.withBrowserScope('pending:new-chat', () => session.requireTab()) @@ -1639,61 +1054,6 @@ describe('executeTool', () => { } }) - it('bounds process-wide queues across scopes and recovers capacity on disposal', async () => { - vi.useFakeTimers() - const scopes = Array.from( - { - length: - driver.BROWSER_TOOL_ADMISSION_LIMITS.process / - driver.BROWSER_TOOL_ADMISSION_LIMITS.perScope, - }, - (_, index) => `chat-admission-${index}` - ) - const executions: Array> = [] - try { - for (const scopeId of scopes) { - await driver.executeTool(scopeId, 'browser_open_tab', {}) - const contents = session.withBrowserScope( - scopeId, - () => session.requireTab().view.webContents - ) - vi.mocked(contents.getURL).mockReturnValue('https://example.com/') - vi.mocked(contents.executeJavaScript).mockImplementation(() => new Promise(() => {})) - executions.push( - driver.executeTool(scopeId, 'browser_snapshot', {}, `held-process-${scopeId}`) - ) - await vi.advanceTimersByTimeAsync(0) - for (let index = 1; index < driver.BROWSER_TOOL_ADMISSION_LIMITS.perScope; index++) { - executions.push( - driver.executeTool( - scopeId, - 'browser_list_tabs', - {}, - `queued-process-${scopeId}-${index}` - ) - ) - } - } - - await expect( - driver.executeTool('chat-process-overflow', 'browser_list_tabs', {}, 'process-overflow') - ).resolves.toEqual({ - ok: false, - error: - 'Sim already has too many browser actions queued. Wait for earlier actions to finish.', - }) - - driver.disposeBrowserScope(scopes[0]) - await expect( - driver.executeTool('chat-process-recovered', 'browser_list_tabs', {}, 'process-recovered') - ).resolves.toMatchObject({ ok: true }) - } finally { - for (const scopeId of scopes) driver.disposeBrowserScope(scopeId) - await Promise.allSettled(executions) - vi.useRealTimers() - } - }) - it('sanitizes hostile tab titles before returning them across the tool boundary', async () => { await driver.executeTool('chat-test', 'browser_open_tab', {}) const contents = session.requireTab().view.webContents @@ -2210,22 +1570,6 @@ describe('credential protection', () => { } ) - it.each([ - { value: 'a', values: ['b'] }, - { values: [1] }, - { values: Array.from({ length: 101 }, () => 'a') }, - {}, - ])('rejects invalid selection arguments before dispatch', async (params) => { - const contents = await openPage() - vi.mocked(contents.executeJavaScript).mockClear() - const result = await driver.executeTool('chat-test', 'browser_select_option', { - elementId: 0, - ...params, - }) - expect(result.ok).toBe(false) - expect(contents.executeJavaScript).not.toHaveBeenCalled() - }) - const formFields = [ { elementId: 1, kind: 'select', value: 'first' }, { elementId: 2, kind: 'select', value: 'second' }, @@ -2251,22 +1595,6 @@ describe('credential protection', () => { }) }) - it.each([ - { fields: [] }, - { - fields: Array.from({ length: 9 }, (_, elementId) => ({ elementId, kind: 'text', text: 'x' })), - }, - { fields: [formFields[0], { ...formFields[1], submit: true }] }, - { fields: [formFields[0], formFields[0]] }, - { fields: [{ elementId: 0, kind: 'text', text: 'x'.repeat(4097) }] }, - ])('validates the entire bounded form payload before writing', async (params) => { - const form = await openForm() - expect(await driver.executeTool('chat-test', 'browser_fill_form', params)).toMatchObject({ - ok: false, - }) - expect(form.writes).toEqual([]) - }) - it('preflights later secret fields before changing earlier fields', async () => { const form = await openForm({ refuseAt: 1 }) const result = await driver.executeTool('chat-test', 'browser_fill_form', { @@ -2316,17 +1644,6 @@ describe('credential protection', () => { }) }) - it('stops before the next field after same-document navigation', async () => { - const form: Awaited> = await openForm({ - afterWrite: () => vi.mocked(form.contents.getURL).mockReturnValue('https://example.com/next'), - }) - const result = await driver.executeTool('chat-test', 'browser_fill_form', { - fields: formFields, - }) - expect(form.writes).toEqual([0]) - expect(result).toMatchObject({ ok: true, result: { completed: false, doNotRetry: true } }) - }) - it('detects a later field changing an earlier completed field', async () => { const form: Awaited> = await openForm({ afterWrite: (index) => { @@ -2426,34 +1743,6 @@ describe('credential protection', () => { expect(cdpCalls(contents, 'Input.dispatchKeyEvent').length).toBeGreaterThan(0) }) - it('repeats a keystroke with a trusted down/up pair per press', async () => { - const contents = await openPage() - respondWith(contents, { activeElementSecrecy: 'safe', readActiveElementState: {} }) - - const result = await driver.executeTool('chat-test', 'browser_press_key', { - key: 'ArrowRight', - repeat: 3, - }) - - expect(result).toMatchObject({ ok: true, result: { pressed: 'ArrowRight', repeat: 3 } }) - const downs = cdpCalls(contents, 'Input.dispatchKeyEvent').filter( - ([, event]) => (event as { type?: string }).type === 'rawKeyDown' - ) - expect(downs).toHaveLength(3) - }) - - it('rejects an out-of-range repeat before dispatch', async () => { - const contents = await openPage() - - const result = await driver.executeTool('chat-test', 'browser_press_key', { - key: 'Tab', - repeat: 51, - }) - - expect(result).toMatchObject({ ok: false, error: expect.stringContaining('repeat must') }) - expect(cdpCalls(contents, 'Input.dispatchKeyEvent')).toHaveLength(0) - }) - it('stops repeating once focus reaches a password field', async () => { const contents = await openPage() let secrecyChecks = 0 @@ -2476,16 +1765,6 @@ describe('credential protection', () => { expect(downs).toHaveLength(1) }) - it('sends the keystroke when nothing sensitive is focused', async () => { - const contents = await openPage() - respondWith(contents, { activeElementSecrecy: 'safe', readActiveElementState: {} }) - - const result = await driver.executeTool('chat-test', 'browser_press_key', { key: 'a' }) - - expect(result.ok).toBe(true) - expect(cdpCalls(contents, 'Input.dispatchKeyEvent').length).toBeGreaterThan(0) - }) - it.each(['cancelled', 'timed out'] as const)( 'retains a completed action when its observation is %s', async (stop) => { @@ -2555,38 +1834,6 @@ describe('credential protection', () => { } ) - it('reports when a platform-mismatched shortcut produces no observable effect', async () => { - const contents = await openPage() - respondWith(contents, { - activeElementSecrecy: 'safe', - readActiveElementState: { - activeElement: 'body', - selectedChars: 0, - valueLength: 0, - valuePreview: '', - }, - readPageActionState: { - url: 'https://example.com/login', - title: 'Example', - focus: 'body', - mutationRevision: 0, - dialogs: [], - scroll: [0], - }, - }) - - const result = await driver.executeTool('chat-test', 'browser_press_key', { key: 'Control+K' }) - - expect(result).toMatchObject({ - ok: true, - result: { - pressed: 'Control+K', - effectObserved: false, - note: expect.stringContaining('No strong observable page change'), - }, - }) - }) - it('aborts a type when focus moves to a password field before the insert', async () => { const contents = await openPage() let focusReads = 0 @@ -2652,29 +1899,6 @@ describe('credential protection', () => { expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(0) }) - it('warns when acknowledged text produces no observable field change', async () => { - const contents = await openPage() - respondWith(contents, { - focusElementForTyping: { focused: true, kind: 'input', x: 24, y: 48 }, - activeElementSecrecy: 'safe', - readActiveElementState: { activeElement: 'input', valueLength: 7 }, - }) - - const result = await driver.executeTool('chat-test', 'browser_type', { - elementId: 0, - text: 'hunter2', - }) - - expect(result.ok).toBe(true) - expect(result).toMatchObject({ - result: { - effectObserved: false, - note: expect.stringContaining('field readback did not change'), - }, - }) - expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(1) - }) - it('sets structured input values without dispatching text or select-all keystrokes', async () => { const contents = await openPage() respondWith(contents, { @@ -2713,130 +1937,12 @@ describe('credential protection', () => { expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(0) }) - it('reports an interrupted structured write as uncertain without replaying it', async () => { + it('confirms typing only after the field readback changes', async () => { const contents = await openPage() - let writes = 0 - vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { - if (isPageCall(expression, 'focusElementForTyping')) - return Promise.resolve({ focused: true, valueInput: true, x: 24, y: 48 }) - if (isPageCall(expression, 'setFocusedInputValue')) { - writes++ - return Promise.reject(new Error('Execution context was destroyed')) - } - return Promise.resolve({}) - }) - const result = await driver.executeTool('chat-test', 'browser_type', { - elementId: 0, - text: '2026-09-15', - }) - expect(result).toMatchObject({ - ok: false, - error: expect.stringContaining('may have reached the field and was not retried'), - }) - expect(writes).toBe(1) - expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(0) - expect( - vi - .mocked(contents.executeJavaScript) - .mock.calls.some(([expression]) => isPageCall(String(expression), 'typeIntoElement')) - ).toBe(false) - }) - - it('refuses a field whose input mode changes before dispatch', async () => { - const contents = await openPage() - let reads = 0 - vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { - if (isPageCall(expression, 'focusElementForTyping')) - return Promise.resolve({ focused: true, valueInput: ++reads === 1, x: 24, y: 48 }) - return Promise.resolve({}) - }) - const result = await driver.executeTool('chat-test', 'browser_type', { - elementId: 0, - text: '2026-09-15', - }) - expect(result).toMatchObject({ - ok: false, - error: expect.stringContaining('field type changed'), - }) - expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(0) - expect( - vi - .mocked(contents.executeJavaScript) - .mock.calls.some(([expression]) => isPageCall(String(expression), 'setFocusedInputValue')) - ).toBe(false) - }) - - it('accepts empty text and sends it through native insertion to clear a field', async () => { - const contents = await openPage() - respondWith(contents, { - focusElementForTyping: { focused: true, kind: 'input', x: 24, y: 48 }, - readActiveElementState: { activeElement: 'input', valueLength: 0, valuePreview: '' }, - readPageActionState: {}, - }) - - const result = await driver.executeTool('chat-test', 'browser_type', { elementId: 0, text: '' }) - - expect(result).toMatchObject({ ok: true, result: { dispatched: true, trusted: true } }) - expect(cdpCalls(contents, 'Input.insertText')).toEqual([['Input.insertText', { text: '' }]]) - }) - - it.each([{}, { text: undefined }, { text: null }, { text: 7 }, { text: false }])( - 'rejects missing or nonstring text before native input', - async (params) => { - const contents = await openPage() - const result = await driver.executeTool('chat-test', 'browser_type', { - elementId: 0, - ...params, - }) - - expect(result).toMatchObject({ ok: false, error: expect.stringContaining('text') }) - expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(0) - expect(cdpCalls(contents, 'Input.dispatchKeyEvent')).toHaveLength(0) - } - ) - - it('types through a focused combobox suggestions popup without pointer probing', async () => { - const contents = await openPage() - respondWith(contents, { - focusElementForTyping: { - focused: true, - kind: 'input', - x: 24, - y: 48, - coveredByRelatedPopup: true, - }, - readActiveElementState: { activeElement: 'input', valueLength: 0 }, - readPageActionState: { - url: 'https://example.com/login', - title: 'Compose', - focus: 'input:combobox:::To:', - mutationRevision: 0, - dialogs: [], - popups: ['Contact list'], - scroll: [0], - }, - }) - - const result = await driver.executeTool('chat-test', 'browser_type', { - elementId: 0, - text: 'Mondu', - }) - - expect(result).toMatchObject({ ok: true, result: { dispatched: true, trusted: true } }) - expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(1) - expect( - vi - .mocked(contents.executeJavaScript) - .mock.calls.some(([expression]) => isPageCall(String(expression), 'clickElement')) - ).toBe(false) - }) - - it('confirms typing only after the field readback changes', async () => { - const contents = await openPage() - let inserted = false - const observedInsertionStates: boolean[] = [] - vi.mocked(contents.debugger.sendCommand).mockImplementation((method) => { - if (method === 'Input.insertText') inserted = true + let inserted = false + const observedInsertionStates: boolean[] = [] + vi.mocked(contents.debugger.sendCommand).mockImplementation((method) => { + if (method === 'Input.insertText') inserted = true return Promise.resolve({}) }) vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { @@ -2893,41 +1999,6 @@ describe('credential protection', () => { } ) - it('still allows select-all, which carries no clipboard content', async () => { - const contents = await openPage() - respondWith(contents, { activeElementSecrecy: 'safe', readActiveElementState: {} }) - - const result = await driver.executeTool('chat-test', 'browser_press_key', { key: 'Cmd+A' }) - - expect(result.ok).toBe(true) - }) - - it('surfaces the page-side refusal for element-targeted actions', async () => { - const contents = await openPage() - respondWith(contents, { clickElement: { error: 'password' } }) - - const result = await driver.executeTool('chat-test', 'browser_click', { elementId: 0 }) - - expect(result.ok).toBe(false) - expect(result.error).toMatch(/Refusing to act on a password field/) - }) - - it('guides typing through owned suggestions without dispatching a pointer click', async () => { - const contents = await openPage() - respondWith(contents, { - clickElement: { error: 'suggestions-open', blocker: 'Contact list' }, - }) - - const result = await driver.executeTool('chat-test', 'browser_click', { elementId: 0 }) - - expect(result).toMatchObject({ - ok: false, - error: expect.stringContaining('Use browser_type on the same element'), - }) - expect(result.error).toContain('do not dismiss the popup') - expect(cdpCalls(contents, 'Input.dispatchMouseEvent')).toHaveLength(0) - }) - it('uses trusted CDP mouse input for element clicks', async () => { const contents = await openPage() respondWith(contents, { @@ -2957,41 +2028,6 @@ describe('credential protection', () => { expect(cdpCalls(contents, 'Input.dispatchMouseEvent')).toHaveLength(3) }) - it('returns the actual inner-container movement from browser_scroll', async () => { - const contents = await openPage() - respondWith(contents, { - scrollPage: { - direction: 'up', - requestedAmount: 500, - target: 'Message history', - targetSource: 'viewport-center', - movedBy: -500, - scrollTop: 1_000, - scrollHeight: 4_000, - clientHeight: 800, - atTop: false, - atBottom: false, - }, - }) - - const result = await driver.executeTool('chat-test', 'browser_scroll', { - direction: 'up', - amount: 500, - }) - - expect(result).toMatchObject({ - ok: true, - result: { - target: 'Message history', - targetSource: 'viewport-center', - movedBy: -500, - scrollTop: 1_000, - atTop: false, - atBottom: false, - }, - }) - }) - it('rejects an unsupported browser_scroll direction instead of treating it as down', async () => { const contents = await openPage() @@ -3010,35 +2046,6 @@ describe('credential protection', () => { ).toBe(false) }) - it.each(['left', 'right'])( - 'accepts browser_scroll %s and returns horizontal movement', - async (direction) => { - const contents = await openPage() - const movedBy = direction === 'left' ? -100 : 100 - respondWith(contents, { - scrollPage: { - target: 'Table columns', - targetSource: 'viewport-center', - movedBy, - scrollLeft: 300, - scrollWidth: 1_000, - clientWidth: 200, - atLeft: false, - atRight: false, - atTop: true, - atBottom: true, - }, - }) - - expect( - await driver.executeTool('chat-test', 'browser_scroll', { direction, amount: 100 }) - ).toMatchObject({ - ok: true, - result: { movedBy, scrollLeft: 300, atLeft: false, atRight: false }, - }) - } - ) - it('confirms a click when the requested target changes semantic state', async () => { const contents = await openPage() let actionReads = 0 @@ -3070,48 +2077,6 @@ describe('credential protection', () => { }) }) - it('confirms a panel close when the clicked target semantically disappears', async () => { - const contents = await openPage() - let actionReads = 0 - vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { - if (isPageCall(expression, 'clickElement')) { - return Promise.resolve({ dispatched: false, x: 24, y: 48, element: 'Close thread' }) - } - if (isPageCall(expression, 'readActiveElementState')) return Promise.resolve({}) - if (isPageCall(expression, 'readPageActionState')) { - actionReads++ - return Promise.resolve({ - url: 'https://example.com/thread', - title: 'Thread', - focus: 'body', - mutationRevision: actionReads === 1 ? 0 : 2, - dialogs: [], - popups: [], - scroll: [0], - targetState: - actionReads === 1 - ? { present: true, rendered: true } - : { present: false, rendered: false }, - }) - } - return Promise.resolve(undefined) - }) - - const result = await driver.executeTool('chat-test', 'browser_click', { elementId: 0 }) - - expect(result).toMatchObject({ - ok: true, - result: { - effectObserved: true, - possibleEffectObserved: true, - effect: { targetChanged: true }, - }, - }) - expect(result).not.toMatchObject({ - result: { note: expect.stringContaining('background DOM/title churn') }, - }) - }) - it('reports failed submit dispatch separately from a completed text write', async () => { const contents = await openPage() respondWith(contents, { @@ -3237,43 +2202,6 @@ describe('credential protection', () => { expect(mainFrame.executeJavaScript).not.toHaveBeenCalled() }) - // A dialog that was ALREADY open before the click is not obstructing the - // navigation it survived — reporting it made every SPA route change under a - // persistent role=dialog (cookie banner, side drawer, picker) read as a - // failed click. Only a dialog that arrives with the navigation obstructs it. - // targetChanged can only fire when pageActionState was given an elementId. - // Tools without one listed it in their effect formula for a long time, where - // it was silently always false — coverage that read as real. This pins the - // dependency so the next tool that adds the term has to earn it. - it('cannot observe a target change for a tool that passes no elementId', async () => { - const contents = await openPage() - let actionReads = 0 - vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { - if (isPageCall(expression, 'readActiveElementState')) return Promise.resolve({}) - if (isPageCall(expression, 'readPageActionState')) { - actionReads++ - // No targetState in either sample: that is what a call without an - // elementId returns. - return Promise.resolve({ - url: 'https://example.com/a', - title: 'A', - focus: 'body', - mutationRevision: actionReads === 1 ? 0 : 3, - dialogs: [], - popups: [], - scroll: [0], - }) - } - return Promise.resolve(undefined) - }) - - const result = await driver.executeTool('chat-test', 'browser_press_key', { key: 'Enter' }) - - expect(result).toMatchObject({ ok: true }) - const effect = (result as { result?: { effect?: Record } }).result?.effect - expect(effect?.targetChanged).toBe(false) - }) - // The click-that-navigates race from the field: "Begin Assessment" submits a // form, the navigation tears the origin document down, and the CDP dispatch // rejects mid-press. The press already reached the page — the navigation IS @@ -3320,47 +2248,6 @@ describe('credential protection', () => { }) }) - it('ignores a dialog that was already open before the click', async () => { - const contents = await openPage() - let actionReads = 0 - vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { - if (isPageCall(expression, 'clickElement')) { - return Promise.resolve({ dispatched: false, x: 24, y: 48, element: 'Search result' }) - } - if (isPageCall(expression, 'readActiveElementState')) return Promise.resolve({}) - if (isPageCall(expression, 'readPageActionState')) { - actionReads++ - return Promise.resolve( - actionReads === 1 - ? { - url: 'https://example.com/search', - title: 'Search', - focus: 'body', - mutationRevision: 0, - dialogs: ['Search'], - scroll: [0], - } - : { - url: 'https://example.com/channel/eng-bugs', - title: 'eng-bugs', - focus: 'body', - mutationRevision: 1, - dialogs: ['Search'], - scroll: [0], - } - ) - } - return Promise.resolve(undefined) - }) - - const result = await driver.executeTool('chat-test', 'browser_click', { elementId: 0 }) - - expect(result).toMatchObject({ - ok: true, - result: { effectObserved: true, obstructedAfterNavigation: false, dialogs: ['Search'] }, - }) - }) - it('reports navigation obstructed by a dialog that opened with it', async () => { const contents = await openPage() let actionReads = 0 @@ -3405,58 +2292,6 @@ describe('credential protection', () => { }) }) - it('surfaces a CDP dialog notice on the next tool result exactly once', async () => { - const contents = await openPage() - const listener = vi - .mocked(contents.debugger.on) - .mock.calls.find(([event]) => event === 'message')?.[1] as - | ((event: unknown, method: string, params: unknown, sessionId?: string) => void) - | undefined - expect(listener).toBeTypeOf('function') - - listener?.({}, 'Page.javascriptDialogOpening', { type: 'alert', message: 'Heads up' }) - await vi.waitFor(() => - expect(contents.debugger.sendCommand).toHaveBeenCalledWith('Page.handleJavaScriptDialog', { - accept: false, - }) - ) - - const first = await driver.executeTool('chat-test', 'browser_list_tabs', {}) - const second = await driver.executeTool('chat-test', 'browser_list_tabs', {}) - - expect(first).toMatchObject({ - ok: true, - result: { - notices: [expect.stringContaining('alert dialog ("Heads up") which was dismissed')], - }, - }) - expect(second).not.toMatchObject({ result: { notices: expect.anything() } }) - }) - - it('runs batched actions in order and returns each result', async () => { - const contents = await openPage() - respondWith(contents, {}) - - const result = await driver.executeTool('chat-test', 'browser_batch', { - actions: [ - { tool: 'browser_click', args: { elementId: 0 } }, - { tool: 'browser_click', args: { elementId: 0 } }, - ], - }) - - expect(result).toMatchObject({ - ok: true, - result: { - completed: true, - completedCount: 2, - results: [ - { index: 0, tool: 'browser_click', result: { dispatched: true } }, - { index: 1, tool: 'browser_click', result: { dispatched: true } }, - ], - }, - }) - }) - it('stops a batch at the first failed action and keeps earlier results', async () => { const contents = await openPage() vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { @@ -3513,44 +2348,16 @@ describe('credential protection', () => { }) }) - it('stops a batch after an action changes the URL within the document', async () => { + it('reports a batch cancelled during its first action as an unknown outcome', async () => { const contents = await openPage() respondWith(contents, {}) - const url = contents.getURL() const sendCommand = vi.mocked(contents.debugger.sendCommand) const dispatch = sendCommand.getMockImplementation() - sendCommand.mockImplementation((method, params) => { - if (method === 'Input.dispatchMouseEvent' && toRecord(params).type === 'mouseReleased') { - vi.mocked(contents.getURL).mockReturnValue(`${url}#next`) - emitContentsEvent(contents, 'did-navigate-in-page') - } - return dispatch?.(method, params) ?? Promise.resolve(undefined) - }) - - const result = await driver.executeTool('chat-test', 'browser_batch', { - actions: [ - { tool: 'browser_click', args: { elementId: 0 } }, - { tool: 'browser_click', args: { elementId: 0 } }, - ], - }) - - expect(mousePresses(contents)).toBe(1) - expect(result).toMatchObject({ - ok: true, - result: { completed: false, completedCount: 1, stoppedIndex: 1, stoppedBy: 'page-change' }, - }) - }) - - it('reports a batch cancelled during its first action as an unknown outcome', async () => { - const contents = await openPage() - respondWith(contents, {}) - const sendCommand = vi.mocked(contents.debugger.sendCommand) - const dispatch = sendCommand.getMockImplementation() - sendCommand.mockImplementation((method, params) => - method === 'Input.dispatchKeyEvent' - ? new Promise(() => {}) - : (dispatch?.(method, params) ?? Promise.resolve(undefined)) - ) + sendCommand.mockImplementation((method, params) => + method === 'Input.dispatchKeyEvent' + ? new Promise(() => {}) + : (dispatch?.(method, params) ?? Promise.resolve(undefined)) + ) const pending = driver.executeTool( 'chat-test', @@ -3572,65 +2379,6 @@ describe('credential protection', () => { }) }) - it('reports a batch cancelled after an action ran as an unknown outcome', async () => { - const contents = await openPage() - respondWith(contents, {}) - const sendCommand = vi.mocked(contents.debugger.sendCommand) - const dispatch = sendCommand.getMockImplementation() - sendCommand.mockImplementation((method, params) => - method === 'Input.dispatchKeyEvent' - ? new Promise(() => {}) - : (dispatch?.(method, params) ?? Promise.resolve(undefined)) - ) - - const pending = driver.executeTool( - 'chat-test', - 'browser_batch', - { - actions: [ - { tool: 'browser_click', args: { elementId: 0 } }, - { tool: 'browser_press_key', args: { key: 'Enter' } }, - ], - }, - 'batch-call' - ) - await vi.waitFor(() => expect(cdpCalls(contents, 'Input.dispatchKeyEvent')).toHaveLength(1)) - driver.cancelTool('chat-test', 'batch-call') - - await expect(pending).resolves.toMatchObject({ - ok: true, - result: { outcomeUnknown: true, doNotRetry: true }, - }) - }) - - it('reports a press-and-hold cancelled mid-hold as an unknown outcome', async () => { - const contents = await openPage() - respondWith(contents, {}) - - const pending = driver.executeTool( - 'chat-test', - 'browser_click', - { elementId: 0, holdMs: 5_000 }, - 'hold-call' - ) - await vi.waitFor(() => - expect( - cdpCalls(contents, 'Input.dispatchMouseEvent').some( - ([, params]) => toRecord(params).type === 'mousePressed' - ) - ).toBe(true) - ) - driver.cancelTool('chat-test', 'hold-call') - - await expect(pending).resolves.toMatchObject({ - ok: true, - result: { outcomeUnknown: true, doNotRetry: true }, - }) - expect( - cdpCalls(contents, 'Input.dispatchMouseEvent').map(([, params]) => toRecord(params).type) - ).toContain('mouseReleased') - }) - it('rejects batches that name non-action tools or observe per action', async () => { await openPage() @@ -3683,33 +2431,6 @@ describe('credential protection', () => { expect(retried).toMatchObject({ ok: true, result: { dispatched: true } }) }) - it('names the overlay and nested controls a refused click can use next', async () => { - const contents = await openPage() - respondWith(contents, { - clickElement: { - error: 'obstructed', - blocker: 'We use cookies', - blockerControls: [{ id: 4, name: 'Accept all' }], - }, - }) - const covered = await driver.executeTool('chat-test', 'browser_click', { elementId: 0 }) - respondWith(contents, { - clickElement: { error: 'nested-control', blocker: 'Delete channel', controlId: 3 }, - }) - const nested = await driver.executeTool('chat-test', 'browser_click', { elementId: 0 }) - - expect(covered).toEqual({ - ok: false, - error: expect.stringContaining( - '[ref=4] "Accept all". Dismiss it with one of those, then retry the same id.' - ), - }) - expect(nested).toEqual({ - ok: false, - error: expect.stringContaining('Delete channel [ref=3]'), - }) - }) - it('invalidates element ids when the active tab changes', async () => { await openPage() await driver.executeTool('chat-test', 'browser_open_tab', {}) @@ -3758,61 +2479,6 @@ describe('credential protection', () => { }) }) - it('tolerates same-document URL churn during a keypress', async () => { - const contents = await openPage() - let urlReads = 0 - vi.mocked(contents.getURL).mockImplementation(() => - ++urlReads === 1 ? 'https://example.com/channel-a' : 'https://example.com/channel-b' - ) - respondWith(contents, { - activeElementSecrecy: 'safe', - readActiveElementState: {}, - readPageActionState: { - url: 'https://example.com/channel-a', - title: 'Example', - focus: 'body', - mutationRevision: 0, - dialogs: [], - scroll: [0], - }, - }) - - const result = await driver.executeTool('chat-test', 'browser_press_key', { key: 'Escape' }) - - expect(result.ok).toBe(true) - expect(cdpCalls(contents, 'Input.dispatchKeyEvent').length).toBeGreaterThan(0) - }) - - it('aborts a coordinate hover when a cross-document navigation lands mid-flight', async () => { - const contents = await openPage() - let navigated = false - vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { - if (isPageCall(expression, 'describePointTarget')) return Promise.resolve({ found: true }) - if (isPageCall(expression, 'readActiveElementState')) return Promise.resolve({}) - if (isPageCall(expression, 'readPageActionState')) { - if (!navigated) { - navigated = true - emitContentsEvent(contents, 'did-navigate') - } - return Promise.resolve({ - url: 'https://example.com/login', - title: 'Example', - focus: 'body', - mutationRevision: 0, - dialogs: [], - scroll: [0], - }) - } - return Promise.resolve(undefined) - }) - - const result = await driver.executeTool('chat-test', 'browser_hover', { x: 40, y: 50 }) - - expect(result.ok).toBe(false) - expect(result.error).toMatch(/active tab or page changed/) - expect(cdpCalls(contents, 'Input.dispatchMouseEvent')).toHaveLength(0) - }) - it('aborts a keypress when a cross-document navigation lands mid-flight', async () => { const contents = await openPage() let navigated = false @@ -3843,58 +2509,6 @@ describe('credential protection', () => { expect(cdpCalls(contents, 'Input.dispatchKeyEvent')).toHaveLength(0) }) - it('waits for a late-mounting editor before typing', async () => { - const contents = await openPage() - let focusReads = 0 - vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { - if (isPageCall(expression, 'focusElementForTyping')) { - focusReads++ - return Promise.resolve( - focusReads === 1 - ? { error: 'not-editable' } - : { focused: true, kind: 'contenteditable', x: 24, y: 48 } - ) - } - if (isPageCall(expression, 'activeElementSecrecy')) return Promise.resolve('safe') - if (isPageCall(expression, 'readActiveElementState')) { - return Promise.resolve({ activeElement: 'div', valueLength: 5 }) - } - return Promise.resolve(undefined) - }) - - const result = await driver.executeTool('chat-test', 'browser_type', { - elementId: 0, - text: 'hello', - }) - - expect(result.ok).toBe(true) - expect(focusReads).toBeGreaterThan(1) - expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(1) - }) - - it('reprobes a transiently stale click target before giving up', async () => { - const contents = await openPage() - let clickReads = 0 - vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { - if (isPageCall(expression, 'clickElement')) { - clickReads++ - return Promise.resolve( - clickReads === 1 - ? { error: 'stale' } - : { dispatched: false, x: 24, y: 48, element: 'Channel row' } - ) - } - if (isPageCall(expression, 'readActiveElementState')) return Promise.resolve({}) - if (isPageCall(expression, 'readPageActionState')) return Promise.resolve({}) - return Promise.resolve(undefined) - }) - - const result = await driver.executeTool('chat-test', 'browser_click', { elementId: 0 }) - - expect(result).toMatchObject({ ok: true, result: { dispatched: true } }) - expect(clickReads).toBeGreaterThan(1) - }) - it('clicks a coordinate point with native input and reports the target', async () => { const contents = await openPage() respondWith(contents, { @@ -3927,73 +2541,6 @@ describe('credential protection', () => { expect(presses).toHaveLength(1) }) - it('double-clicks a coordinate point as a rising clickCount sequence', async () => { - const contents = await openPage() - respondWith(contents, { - describePointTarget: { found: true, element: 'canvas', editable: false }, - readActiveElementState: {}, - readPageActionState: {}, - }) - - const result = await driver.executeTool('chat-test', 'browser_click_at', { - x: 10, - y: 20, - clickCount: 2, - }) - - expect(result).toMatchObject({ ok: true, result: { clickCount: 2 } }) - const counts = cdpCalls(contents, 'Input.dispatchMouseEvent') - .filter(([, event]) => (event as { type?: string }).type === 'mousePressed') - .map(([, event]) => (event as { clickCount?: number }).clickCount) - expect(counts).toEqual([1, 2]) - }) - - it('dispatches right-clicks and modifier clicks through the same trusted gesture', async () => { - const contents = await openPage() - respondWith(contents, { - describePointTarget: { found: true, element: 'row "notes.pdf"', editable: false }, - readActiveElementState: {}, - readPageActionState: {}, - }) - - const result = await driver.executeTool('chat-test', 'browser_click_at', { - x: 10, - y: 20, - button: 'right', - modifiers: ['Shift'], - }) - - expect(result).toMatchObject({ ok: true, result: { dispatched: true } }) - const presses = cdpCalls(contents, 'Input.dispatchMouseEvent').filter( - ([, event]) => (event as { type?: string }).type === 'mousePressed' - ) - expect(presses.map(([, event]) => event)).toEqual([ - expect.objectContaining({ button: 'right', buttons: 2, modifiers: 8, clickCount: 1 }), - ]) - }) - - it('rejects unknown click buttons and modifiers before dispatch', async () => { - const contents = await openPage() - - const badButton = await driver.executeTool('chat-test', 'browser_click_at', { - x: 10, - y: 20, - button: 'back', - }) - const badModifier = await driver.executeTool('chat-test', 'browser_click_at', { - x: 10, - y: 20, - modifiers: ['Hyper'], - }) - - expect(badButton).toMatchObject({ ok: false, error: expect.stringContaining('button must be') }) - expect(badModifier).toMatchObject({ - ok: false, - error: expect.stringContaining('Unrecognized modifier'), - }) - expect(cdpCalls(contents, 'Input.dispatchMouseEvent')).toHaveLength(0) - }) - it('answers a dialog opened by an action with that action dialog response only', async () => { const contents = await openPage() respondWith(contents, { @@ -4070,19 +2617,6 @@ describe('credential protection', () => { ]) }) - it('rejects a malformed dialog response before dispatch', async () => { - const contents = await openPage() - - const result = await driver.executeTool('chat-test', 'browser_click_at', { - x: 10, - y: 20, - dialog: { accept: 'yes' }, - }) - - expect(result).toMatchObject({ ok: false, error: expect.stringContaining('dialog must be') }) - expect(cdpCalls(contents, 'Input.dispatchMouseEvent')).toHaveLength(0) - }) - describe('file uploads', () => { afterEach(() => vi.restoreAllMocks()) @@ -4127,28 +2661,6 @@ describe('credential protection', () => { expect(resolve).toHaveBeenCalledTimes(1) }) - it('refuses several files for a single-file input and releases its handle without staging', async () => { - const contents = await openPage() - const input = { objectId: 'isolated-input', multiple: false } - vi.spyOn(cdp, 'resolveFileInput').mockResolvedValue(input) - const release = vi.spyOn(cdp, 'releaseFileInput').mockResolvedValue() - stageUploadFiles.mockClear() - - const result = await driver.executeTool( - 'chat-test', - 'browser_upload_file', - { elementId: 0, paths: ['files/a.pdf', 'files/b.pdf'] }, - 'call-single' - ) - - expect(result).toMatchObject({ - ok: false, - error: expect.stringContaining('accepts one file'), - }) - expect(stageUploadFiles).not.toHaveBeenCalled() - expect(release).toHaveBeenCalledWith(contents, input) - }) - it.each(['staging', 'attachment'])( 'releases the pinned input after %s fails before dispatch', async (failure) => { @@ -4180,164 +2692,6 @@ describe('credential protection', () => { } ) - it('reports an acknowledged upload with unavailable readback without inviting a retry', async () => { - const contents = await openPage() - const input = { objectId: 'isolated-input', multiple: false } - vi.spyOn(cdp, 'resolveFileInput').mockResolvedValue(input) - vi.spyOn(cdp, 'setFileInputFiles').mockResolvedValue({ - readbackError: 'Execution context destroyed', - }) - const release = vi.spyOn(cdp, 'releaseFileInput').mockResolvedValue() - stageUploadFiles.mockResolvedValue(['/staged/a.pdf']) - - const result = await driver.executeTool( - 'chat-test', - 'browser_upload_file', - { elementId: 0, paths: ['files/a.pdf'] }, - 'call-navigated' - ) - - expect(result).toMatchObject({ - ok: true, - result: { - dispatched: true, - observation: { ok: false, doNotRetry: true, error: 'Execution context destroyed' }, - }, - }) - expect(release).toHaveBeenCalledWith(contents, input) - }) - - it.each(['cancelled', 'timed out'] as const)( - 'does not retry a dispatched upload when acknowledgement is %s', - async (stop) => { - const contents = await openPage() - const input = { objectId: 'isolated-input', multiple: false } - vi.spyOn(cdp, 'resolveFileInput').mockResolvedValue(input) - stageUploadFiles.mockResolvedValue(['/staged/a.pdf']) - let acknowledge: () => void = () => {} - const acknowledgement = new Promise((resolve) => { - acknowledge = resolve - }) - const send = vi.mocked(contents.debugger.sendCommand) - send.mockImplementation(async (method, params) => { - if (method === 'Runtime.callFunctionOn') { - const mode = (params?.arguments as Array<{ value: string }>)[0].value - return mode === 'input' - ? { result: { objectId: 'original-input' } } - : { result: { value: { files: [{ name: 'a.pdf', size: 3 }] } } } - } - if (method === 'DOM.setFileInputFiles') await acknowledgement - return {} - }) - vi.useFakeTimers() - try { - const pending = driver.executeTool( - 'chat-test', - 'browser_upload_file', - { elementId: 0, paths: ['files/a.pdf'] }, - 'unacknowledged-upload' - ) - await vi.advanceTimersByTimeAsync(200) - expect(cdpCalls(contents, 'DOM.setFileInputFiles')).toHaveLength(1) - expect(cdpCalls(contents, 'Runtime.releaseObject')).toHaveLength(0) - - if (stop === 'cancelled') driver.cancelTool('chat-test', 'unacknowledged-upload') - else - await vi.advanceTimersByTimeAsync( - driver.browserToolWatchdogMs('browser_upload_file', {})! - ) - - await expect(pending).resolves.toMatchObject({ - ok: true, - result: { - outcomeUnknown: true, - doNotRetry: true, - }, - }) - await expect( - driver.executeTool('chat-test', 'browser_list_tabs', {}, 'after-unacknowledged-upload') - ).resolves.toMatchObject({ ok: true }) - } finally { - acknowledge() - await vi.advanceTimersByTimeAsync(200) - vi.useRealTimers() - } - expect(cdpCalls(contents, 'DOM.setFileInputFiles')).toHaveLength(1) - expect(cdpCalls(contents, 'Runtime.releaseObject').map(([, params]) => params)).toEqual([ - { objectId: 'original-input' }, - { objectId: 'isolated-input' }, - ]) - } - ) - - it.each(['cancelled', 'timed out'] as const)( - 'retains an acknowledged upload when readback is %s and releases its handle when readback settles', - async (stop) => { - const contents = await openPage() - respondWith(contents, { readPageActionState: {} }) - const input = { objectId: 'isolated-input', multiple: false } - vi.spyOn(cdp, 'resolveFileInput').mockResolvedValue(input) - let releaseReadback: (value: { files: Array<{ name: string; size: number }> }) => void = - () => {} - const readback = new Promise<{ files: Array<{ name: string; size: number }> }>( - (resolve) => { - releaseReadback = resolve - } - ) - const setFiles = vi - .spyOn(cdp, 'setFileInputFiles') - .mockImplementation(async (_contents, _handle, _files, _signal, onDispatch) => { - onDispatch?.('pending') - onDispatch?.('acknowledged') - return readback - }) - const release = vi.spyOn(cdp, 'releaseFileInput').mockResolvedValue() - stageUploadFiles.mockResolvedValue(['/staged/a.pdf']) - vi.useFakeTimers() - try { - const timersBefore = vi.getTimerCount() - const pending = driver.executeTool( - 'chat-test', - 'browser_upload_file', - { elementId: 0, paths: ['files/a.pdf'] }, - 'interrupted-upload' - ) - await vi.advanceTimersByTimeAsync(200) - expect(setFiles).toHaveBeenCalledTimes(1) - expect(release).not.toHaveBeenCalled() - const queued = driver.executeTool('chat-test', 'browser_list_tabs', {}, 'after-upload') - - if (stop === 'cancelled') driver.cancelTool('chat-test', 'interrupted-upload') - else - await vi.advanceTimersByTimeAsync( - driver.browserToolWatchdogMs('browser_upload_file', {})! - ) - - await expect(pending).resolves.toMatchObject({ - ok: true, - result: { - dispatched: true, - observation: { - ok: false, - doNotRetry: true, - note: expect.stringContaining('The action was dispatched'), - }, - }, - }) - await expect(queued).resolves.toMatchObject({ ok: true }) - expect(vi.getTimerCount()).toBe(timersBefore) - expect(setFiles).toHaveBeenCalledTimes(1) - expect(release).not.toHaveBeenCalled() - } finally { - releaseReadback({ files: [{ name: 'a.pdf', size: 3 }] }) - await vi.advanceTimersByTimeAsync(200) - vi.useRealTimers() - } - expect(release).toHaveBeenCalledExactlyOnceWith(contents, input) - expect(setFiles).toHaveBeenCalledTimes(1) - } - ) - it.each(['cancelled', 'timed out'] as const)( 'reports an unconfirmed upload when its acknowledgment is %s without replaying it or affecting queued work', async (stop) => { @@ -4490,306 +2844,68 @@ describe('credential protection', () => { expect(cdpCalls(contents, 'Input.dispatchMouseEvent')).toHaveLength(0) }) - it('rejects a coordinate click outside the viewport with mapping guidance', async () => { - const contents = await openPage() - respondWith(contents, { - describePointTarget: { error: 'outside-viewport' }, - }) - - const result = await driver.executeTool('chat-test', 'browser_click_at', { x: 9999, y: 5 }) - - expect(result.ok).toBe(false) - expect(result.error).toMatch(/X\/Y coordinate mapping and crop origin/) - }) - - it('inserts text into the focused editable at the caret', async () => { - const contents = await openPage() - respondWith(contents, { - activeElementSecrecy: 'safe', - describeFocusedEditable: { editable: true, kind: 'contenteditable' }, - readActiveElementState: { activeElement: 'div', valueLength: 12 }, - readPageActionState: {}, - }) - - const result = await driver.executeTool('chat-test', 'browser_insert_text', { - text: 'hello world', - }) - - expect(result).toMatchObject({ - ok: true, - result: { dispatched: true, trusted: true, kind: 'contenteditable', insertedChars: 11 }, - }) - expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(1) - }) - - it('reports top-page effects observed after inserting text in a child frame', async () => { - const contents = await openPage() - const mainFrame = { - frameTreeNodeId: 1, - detached: false, - isDestroyed: vi.fn(() => false), - origin: 'https://example.com', - parent: null, - framesInSubtree: [] as unknown[], - } - const childFrame = { - frameTreeNodeId: 2, - detached: false, - isDestroyed: vi.fn(() => false), - origin: 'https://mail-widget.example', - parent: mainFrame, - url: 'https://mail-widget.example/compose', - } - mainFrame.framesInSubtree = [mainFrame, childFrame] - Object.defineProperty(contents, 'mainFrame', { configurable: true, value: mainFrame }) - Object.defineProperty(contents, 'focusedFrame', { configurable: true, value: childFrame }) - let topPageReads = 0 - vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { - if (isPageCall(expression, 'readPageActionState')) { - topPageReads++ - return Promise.resolve({ - url: - topPageReads === 1 ? 'https://example.com/compose' : 'https://example.com/message/sent', - title: 'Mail', - focus: 'iframe', - mutationRevision: topPageReads, - dialogs: [], - scroll: [0], - }) - } - return Promise.resolve(undefined) - }) - const isolatedFrameEval = vi - .spyOn(cdp, 'evaluateInIsolatedFrame') - .mockImplementation((_contents, frame, expression) => { - if ((frame as unknown) === mainFrame) return contents.executeJavaScript(expression) - if (isPageCall(expression, 'activeElementSecrecy')) return Promise.resolve('safe') - if (isPageCall(expression, 'describeFocusedEditable')) { - return Promise.resolve({ editable: true, kind: 'input' }) - } - if (isPageCall(expression, 'readActiveElementState')) { - return Promise.resolve({ activeElement: 'input', valueLength: 4 }) - } - if (isPageCall(expression, 'readPageActionState')) { - return Promise.resolve({ - url: 'https://mail-widget.example/compose', - title: 'Compose', - focus: 'input', - mutationRevision: 0, - dialogs: [], - scroll: [0], - }) - } - return Promise.resolve(undefined) - }) - - try { - const result = await driver.executeTool('chat-test', 'browser_insert_text', { text: 'sent' }) - - expect(result.ok, result.error).toBe(true) - expect(result).toMatchObject({ - ok: true, - result: { - effectObserved: true, - possibleEffectObserved: true, - effect: { urlChanged: true }, - }, - }) - } finally { - isolatedFrameEval.mockRestore() - } - }) - - it('refuses insertion when nothing editable holds focus', async () => { - const contents = await openPage() - respondWith(contents, { - activeElementSecrecy: 'safe', - describeFocusedEditable: { editable: false, reason: 'none' }, - }) - - const result = await driver.executeTool('chat-test', 'browser_insert_text', { text: 'x' }) - - expect(result.ok).toBe(false) - expect(result.error).toMatch(/No element is focused/) - expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(0) - }) - - it('refuses insertion while a password field holds focus', async () => { - const contents = await openPage() - respondWith(contents, { activeElementSecrecy: 'secret' }) - - const result = await driver.executeTool('chat-test', 'browser_insert_text', { text: 'x' }) - - expect(result.ok).toBe(false) - expect(result.error).toMatch(/Refusing to act on a password field/) - expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(0) - }) - - it('drags between coordinate points through the trusted pointer pipeline', async () => { - const contents = await openPage() - respondWith(contents, { - describePointTarget: { found: true, element: 'div "Card"' }, - readActiveElementState: {}, - readPageActionState: {}, - }) - - const result = await driver.executeTool('chat-test', 'browser_drag', { - fromX: 40, - fromY: 50, - toX: 200, - toY: 260, - }) - - expect(result).toMatchObject({ - ok: true, - result: { dispatched: true, trusted: true, from: { x: 40, y: 50 }, to: { x: 200, y: 260 } }, - }) - const events = cdpCalls(contents, 'Input.dispatchMouseEvent').map( - ([, event]) => (event as { type?: string }).type - ) - expect(events[0]).toBe('mouseMoved') - expect(events).toContain('mousePressed') - expect(events[events.length - 1]).toBe('mouseReleased') - expect(cdpCalls(contents, 'Input.setInterceptDrags').length).toBeGreaterThan(0) - }) - - it('drags through via points at the requested pace', async () => { - const contents = await openPage() - respondWith(contents, { - describePointTarget: { found: true, element: 'div "Card"' }, - readActiveElementState: {}, - readPageActionState: {}, - }) - - const result = await driver.executeTool('chat-test', 'browser_drag', { - fromX: 40, - fromY: 50, - toX: 200, - toY: 260, - via: [{ x: 300, y: 50 }], - durationMs: 320, - }) - - expect(result).toMatchObject({ ok: true, result: { dispatched: true } }) - const moves = cdpCalls(contents, 'Input.dispatchMouseEvent') - .map(([, event]) => event as { type?: string; x?: number; y?: number; buttons?: number }) - .filter((event) => event.type === 'mouseMoved' && event.buttons === 1) - expect(moves.some((event) => event.x === 300 && event.y === 50)).toBe(true) - expect(moves.length).toBeGreaterThanOrEqual(20) - }) - - it('moves the pointer along a coordinate hover path with no button pressed', async () => { - const contents = await openPage() - respondWith(contents, { - describePointTarget: { found: true, element: 'canvas "Field"' }, - readActiveElementState: {}, - readPageActionState: {}, - }) - - const result = await driver.executeTool('chat-test', 'browser_hover', { - x: 400, - y: 300, - via: [ - { x: 100, y: 300 }, - { x: 250, y: 150 }, - ], - }) - - expect(result).toMatchObject({ ok: true, result: { hovered: true, x: 400, y: 300 } }) - const events = cdpCalls(contents, 'Input.dispatchMouseEvent').map( - ([, event]) => event as { type?: string; x?: number; y?: number; button?: string } - ) - expect(events.every((event) => event.type === 'mouseMoved' && event.button === 'none')).toBe( - true - ) - expect(events[0]).toMatchObject({ x: 100, y: 300 }) - expect(events.some((event) => event.x === 250 && event.y === 150)).toBe(true) - expect(events[events.length - 1]).toMatchObject({ x: 400, y: 300 }) - }) - - it('rejects pointer paths where they cannot apply', async () => { - await openPage() + it('refuses insertion while a password field holds focus', async () => { + const contents = await openPage() + respondWith(contents, { activeElementSecrecy: 'secret' }) - const elementHover = await driver.executeTool('chat-test', 'browser_hover', { - elementId: 0, - via: [{ x: 1, y: 2 }], - }) - const unroutedPace = await driver.executeTool('chat-test', 'browser_hover', { - x: 10, - y: 10, - durationMs: 800, - }) - const badPoint = await driver.executeTool('chat-test', 'browser_hover', { - x: 10, - y: 10, - via: [{ x: 'left', y: 2 }], - }) - const tooLong = await driver.executeTool('chat-test', 'browser_drag', { - fromX: 1, - fromY: 1, - toX: 50, - toY: 50, - durationMs: 10_001, - }) - const batched = await driver.executeTool('chat-test', 'browser_batch', { - actions: [ - { tool: 'browser_click', args: { elementId: 0 } }, - { tool: 'browser_hover', args: { x: 5, y: 5, durationMs: 500 } }, - ], - }) + const result = await driver.executeTool('chat-test', 'browser_insert_text', { text: 'x' }) - expect(elementHover).toMatchObject({ - ok: false, - error: expect.stringContaining('coordinate hover'), - }) - expect(unroutedPace).toMatchObject({ - ok: false, - error: expect.stringContaining('paces a hover route'), - }) - expect(badPoint).toMatchObject({ ok: false, error: expect.stringContaining('via point') }) - expect(tooLong).toMatchObject({ ok: false, error: expect.stringContaining('durationMs') }) - expect(batched).toMatchObject({ - ok: false, - error: expect.stringContaining('timed pointer path'), - }) + expect(result.ok).toBe(false) + expect(result.error).toMatch(/Refusing to act on a password field/) + expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(0) }) - it('drags from a snapshot element to a coordinate target', async () => { + it('drags between coordinate points through the trusted pointer pipeline', async () => { const contents = await openPage() respondWith(contents, { - clickElement: { dispatched: false, x: 24, y: 48, element: 'Card "Ship it"' }, - describePointTarget: { found: true, element: 'section "Done"' }, + describePointTarget: { found: true, element: 'div "Card"' }, readActiveElementState: {}, readPageActionState: {}, }) const result = await driver.executeTool('chat-test', 'browser_drag', { - fromElementId: 0, - toX: 300, - toY: 60, + fromX: 40, + fromY: 50, + toX: 200, + toY: 260, }) expect(result).toMatchObject({ ok: true, - result: { dispatched: true, from: { x: 24, y: 48, element: 'Card "Ship it"' } }, + result: { dispatched: true, trusted: true, from: { x: 40, y: 50 }, to: { x: 200, y: 260 } }, }) + const events = cdpCalls(contents, 'Input.dispatchMouseEvent').map( + ([, event]) => (event as { type?: string }).type + ) + expect(events[0]).toBe('mouseMoved') + expect(events).toContain('mousePressed') + expect(events[events.length - 1]).toBe('mouseReleased') + expect(cdpCalls(contents, 'Input.setInterceptDrags').length).toBeGreaterThan(0) }) - it('rejects a drag whose endpoints are the same point', async () => { + it('drags through via points at the requested pace', async () => { const contents = await openPage() respondWith(contents, { - describePointTarget: { found: true, element: 'div' }, + describePointTarget: { found: true, element: 'div "Card"' }, + readActiveElementState: {}, + readPageActionState: {}, }) const result = await driver.executeTool('chat-test', 'browser_drag', { - fromX: 10, - fromY: 10, - toX: 10, - toY: 10, + fromX: 40, + fromY: 50, + toX: 200, + toY: 260, + via: [{ x: 300, y: 50 }], + durationMs: 320, }) - expect(result.ok).toBe(false) - expect(result.error).toMatch(/same point/) + expect(result).toMatchObject({ ok: true, result: { dispatched: true } }) + const moves = cdpCalls(contents, 'Input.dispatchMouseEvent') + .map(([, event]) => event as { type?: string; x?: number; y?: number; buttons?: number }) + .filter((event) => event.type === 'mouseMoved' && event.buttons === 1) + expect(moves.some((event) => event.x === 300 && event.y === 50)).toBe(true) + expect(moves.length).toBeGreaterThanOrEqual(20) }) it('finds only fresh ref-bearing snapshot lines with literal text matching', async () => { @@ -4819,24 +2935,6 @@ describe('credential protection', () => { }) }) - it.each([ - ['browser_snapshot', 'true'], - ['browser_find', 'false'], - ] as const)( - 'passes an absent scope as null to the serialized %s page call', - async (tool, markNew) => { - const contents = await openPage() - vi.mocked(contents.executeJavaScript).mockClear() - await driver.executeTool('chat-test', tool, { query: 'Continue' }) - const expressions = vi - .mocked(contents.executeJavaScript) - .mock.calls.map(([expression]) => expression) - .filter((expression) => isPageCall(expression, 'collectSnapshot')) - expect(expressions).toHaveLength(1) - expect(expressions[0]).toContain(`.apply(null, [1,null,${markNew}])`) - } - ) - it.each([ ['browser_snapshot', 'true'], ['browser_find', 'false'], @@ -4872,65 +2970,6 @@ describe('credential protection', () => { } ) - it.each([ - [1, false, 1], - [100, false, 50], - [100, true, 50], - ])( - 'bounds find results for maxResults=%s and snapshot truncation=%s', - async (maxResults, truncated, expectedCount) => { - const contents = await openPage() - const ids = Array.from({ length: 60 }, (_, index) => index + 1) - respondWith(contents, { - collectSnapshot: { - url: 'https://example.com/login', - title: 'Example', - outline: ids.map((id) => `- button "Continue ${id}" [ref=${id}]`).join('\n'), - truncated, - refIds: ids, - refLineIndexes: Object.fromEntries(ids.map((id, index) => [id, index])), - nextElementId: 61, - }, - }) - const response = await driver.executeTool('chat-test', 'browser_find', { - query: 'Continue', - maxResults, - }) - expect(response).toMatchObject({ ok: true, result: { totalMatches: 60, truncated: true } }) - expect((response.result as { matches: unknown[] }).matches).toHaveLength(expectedCount) - } - ) - - it('reports incomplete search coverage even when no ref matches', async () => { - const contents = await openPage() - respondWith(contents, { - collectSnapshot: { - url: 'https://example.com/login', - title: 'Example', - outline: '- button "Other" [ref=1]', - truncated: true, - refIds: [1], - refLineIndexes: { 1: 0 }, - nextElementId: 2, - }, - }) - const response = await driver.executeTool('chat-test', 'browser_find', { query: 'Missing' }) - expect(response).toMatchObject({ - ok: true, - result: { matches: [], totalMatches: 0, truncated: true }, - }) - }) - - it('rejects oversized search text before taking a snapshot', async () => { - const contents = await openPage() - vi.mocked(contents.executeJavaScript).mockClear() - const response = await driver.executeTool('chat-test', 'browser_find', { - query: 'x'.repeat(4097), - }) - expect(response).toMatchObject({ ok: false }) - expect(contents.executeJavaScript).not.toHaveBeenCalled() - }) - it.each([ { kind: 'input:checkbox', checked: true, disabled: false, readOnly: false }, { kind: 'input:radio', checked: false, disabled: false, readOnly: false }, @@ -5019,27 +3058,6 @@ describe('credential protection', () => { } ) - it('waits for URL and semantic element state together', async () => { - const contents = await openPage() - respondWith(contents, { - readPageActionState: { - targetState: { present: true, rendered: true, disabled: false }, - }, - }) - - const result = await driver.executeTool('chat-test', 'browser_wait_for', { - urlContains: '/login', - elementId: 0, - state: 'enabled', - timeoutMs: 1_000, - }) - - expect(result).toMatchObject({ - ok: true, - result: { found: true, matched: ['url', 'element'] }, - }) - }) - it.each([true, false])( 'polls delayed checkable state without redispatching input (updates=%s)', async (updates) => { @@ -5098,93 +3116,6 @@ describe('credential protection', () => { } ) - it('rejects navigation while inspecting an element wait condition', async () => { - const contents = await openPage() - vi.mocked(contents.executeJavaScript).mockImplementation(async () => { - vi.mocked(contents.getURL).mockReturnValue('https://example.com/next') - return { targetState: { present: false, rendered: false } } - }) - const response = await driver.executeTool('chat-test', 'browser_wait_for', { - elementId: 0, - state: 'detached', - timeoutMs: 1000, - }) - expect(response).toMatchObject({ ok: false }) - expect(response.error).toContain('page changed') - }) - - it.each([ - ['detached', { present: true, rendered: false }], - ['collapsed', { present: true, rendered: true }], - ['expanded', { present: true, rendered: true }], - ['unchecked', { present: true, rendered: true, checked: 'mixed' }], - ['unchecked', { present: true, rendered: true }], - ])('does not satisfy %s from an incompatible element state', async (state, targetState) => { - const contents = await openPage() - respondWith(contents, { readPageActionState: { targetState } }) - vi.useFakeTimers() - try { - const response = driver.executeTool('chat-test', 'browser_wait_for', { - elementId: 0, - state, - timeoutMs: 100, - }) - await vi.advanceTimersByTimeAsync(1000) - await expect(response).resolves.toMatchObject({ ok: true, result: { found: false } }) - } finally { - vi.useRealTimers() - } - }) - - it.each([ - ['expanded', { open: true }], - ['collapsed', { open: false }], - ['expanded', { ariaExpanded: 'true' }], - ['collapsed', { ariaExpanded: 'false' }], - ])('waits for %s on native and ARIA disclosure controls', async (state, semanticState) => { - const contents = await openPage() - respondWith(contents, { - readPageActionState: { targetState: { present: true, rendered: true, ...semanticState } }, - }) - await expect( - driver.executeTool('chat-test', 'browser_wait_for', { - elementId: 0, - state, - timeoutMs: 100, - }) - ).resolves.toMatchObject({ ok: true, result: { found: true } }) - }) - - it.each(['x', 'y', 'width', 'height', 'detached'])( - 'rejects an element screenshot when %s changes during capture', - async (change) => { - const contents = await openPage() - let captured = false - vi.mocked(contents.executeJavaScript).mockImplementation(async (expression: string) => { - if (!isPageCall(expression, 'getElementScreenshotRect')) return undefined - if (captured && change === 'detached') return { error: 'stale' } - return { x: 20, y: 30, width: 200, height: 100, ...(captured ? { [change]: 50 } : {}) } - }) - const capture = vi.spyOn(cdp, 'captureScreenshot').mockImplementation(async () => { - captured = true - return { - dataUrl: 'data:image/jpeg;base64,c2lt', - scale: 1, - viewport: { width: 800, height: 600 }, - imageSize: { width: 200, height: 100 }, - } - }) - try { - const result = await driver.executeTool('chat-test', 'browser_screenshot', { elementId: 0 }) - expect(result.ok).toBe(false) - expect(result.error).toMatch(change === 'detached' ? /stale/ : /element moved/) - expect(capture).toHaveBeenCalledTimes(1) - } finally { - capture.mockRestore() - } - } - ) - it('crops an element screenshot without changing the live viewport', async () => { const contents = await openPage() respondWith(contents, { @@ -5221,63 +3152,6 @@ describe('credential protection', () => { } }) - it('returns the encoded crop geometry while checking the original element bounds for movement', async () => { - const contents = await openPage() - const measuredClip = { x: 0.1, y: 0.2, width: 1.1, height: 100 } - const capturedClip = { x: 0, y: 0, width: 1.5, height: 100.5 } - respondWith(contents, { - getElementScreenshotRect: { ...measuredClip, element: 'div', refRecovered: false }, - }) - const capture = vi.spyOn(cdp, 'captureScreenshot').mockResolvedValue({ - dataUrl: 'data:image/jpeg;base64,c2lt', - scale: 2, - viewport: { width: 800, height: 600 }, - imageSize: { width: 3, height: 201 }, - clip: capturedClip, - }) - - try { - const result = await driver.executeTool('chat-test', 'browser_screenshot', { elementId: 0 }) - - expect(capture).toHaveBeenCalledWith(contents, measuredClip, expect.any(AbortSignal)) - expect(result).toMatchObject({ - ok: true, - result: { - element: 'div', - clip: capturedClip, - scale: 2, - imageSize: { width: 3, height: 201 }, - }, - }) - expect( - vi - .mocked(contents.executeJavaScript) - .mock.calls.filter(([expression]) => isPageCall(expression, 'getElementScreenshotRect')) - ).toHaveLength(2) - } finally { - capture.mockRestore() - } - }) - - it('rejects navigation during an element screenshot measurement', async () => { - const contents = await openPage() - vi.mocked(contents.executeJavaScript).mockImplementation(async (expression: string) => { - if (isPageCall(expression, 'getElementScreenshotRect')) { - vi.mocked(contents.getURL).mockReturnValue('https://example.com/next') - return { x: 20, y: 30, width: 200, height: 100 } - } - }) - const capture = vi.spyOn(cdp, 'captureScreenshot') - try { - const result = await driver.executeTool('chat-test', 'browser_screenshot', { elementId: 0 }) - expect(result).toMatchObject({ ok: false }) - expect(result.error).toMatch(/page changed/) - expect(capture).not.toHaveBeenCalled() - } finally { - capture.mockRestore() - } - }) - it('zooms by a standard step and invalidates existing element refs', async () => { const contents = await openPage() @@ -5374,79 +3248,6 @@ describe('credential protection', () => { ).toBe(true) }) - it('accepts stable truncated page identity with deprecated device metrics', async () => { - const contents = await openPage() - const fullUrl = `https://example.com/${'u'.repeat(5000)}` - const fullTitle = `Example ${'t'.repeat(600)}` - vi.mocked(contents.getURL).mockReturnValue(fullUrl) - vi.mocked(contents.getTitle).mockReturnValue(fullTitle) - mockScreenshotImage(contents, { width: 1024, height: 512 }) - vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { - if (method === 'Page.getLayoutMetrics') { - return Promise.resolve({ layoutViewport: { clientWidth: 2048, clientHeight: 1024 } }) - } - return Promise.resolve(undefined) - }) - respondWith(contents, { - getViewportInfo: { - url: fullUrl.slice(0, 4096), - title: fullTitle.slice(0, 500), - width: 1024, - height: 512, - }, - }) - - const result = await driver.executeTool('chat-test', 'browser_screenshot', {}) - - expect(result).toMatchObject({ - ok: true, - result: { - scale: 1, - viewport: { - url: fullUrl.slice(0, 4096), - title: fullTitle.slice(0, 500), - width: 1024, - height: 512, - }, - }, - }) - }) - - it('rejects an empty screenshot instead of returning an unverified scale', async () => { - const contents = await openPage() - mockScreenshotImage(contents, null) - vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { - if (method === 'Page.getLayoutMetrics') { - return Promise.resolve({ - cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024 }, - }) - } - return Promise.resolve(undefined) - }) - - const result = await driver.executeTool('chat-test', 'browser_screenshot', {}) - - expect(result.ok).toBe(false) - expect(result.error).toMatch(/empty image/) - }) - - it('rejects a screenshot when no CSS viewport can be established', async () => { - const contents = await openPage() - mockScreenshotImage(contents, { width: 1024, height: 512 }) - vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { - if (method === 'Page.getLayoutMetrics') { - return Promise.resolve({ layoutViewport: { clientWidth: 2048, clientHeight: 1024 } }) - } - return Promise.resolve(undefined) - }) - respondWith(contents, { getViewportInfo: null }) - - const result = await driver.executeTool('chat-test', 'browser_screenshot', {}) - - expect(result.ok).toBe(false) - expect(result.error).toMatch(/verify the page viewport/) - }) - it('rejects coordinate mapping when the viewport changes during capture', async () => { const contents = await openPage() mockScreenshotImage(contents, { width: 1024, height: 256 }) @@ -5494,38 +3295,4 @@ describe('credential protection', () => { expect(result.ok).toBe(false) expect(result.error).toMatch(/page changed while its screenshot was being captured/) }) - - it.each(['url', 'title'] as const)( - 'rejects a screenshot when the page %s changes during capture', - async (identityField) => { - const contents = await openPage() - mockScreenshotImage(contents, { width: 1024, height: 512 }) - const initialUrl = contents.getURL() - const initialTitle = contents.getTitle() - let currentUrl = initialUrl - let currentTitle = initialTitle - vi.mocked(contents.getURL).mockImplementation(() => currentUrl) - vi.mocked(contents.getTitle).mockImplementation(() => currentTitle) - vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { - if (method === 'Page.getLayoutMetrics') { - return Promise.resolve({ - cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024 }, - }) - } - return Promise.resolve(undefined) - }) - - const image = await contents.capturePage() - vi.mocked(contents.capturePage).mockImplementation(async () => { - if (identityField === 'url') currentUrl = 'https://example.com/changed' - else currentTitle = 'Changed title' - return image - }) - - const result = await driver.executeTool('chat-test', 'browser_screenshot', {}) - - expect(result.ok).toBe(false) - expect(result.error).toMatch(/page changed while its screenshot was being captured/) - } - ) }) diff --git a/apps/desktop/src/main/browser-agent/file-transfer.test.ts b/apps/desktop/src/main/browser-agent/file-transfer.test.ts index 16c648da340..bc4569f37c6 100644 --- a/apps/desktop/src/main/browser-agent/file-transfer.test.ts +++ b/apps/desktop/src/main/browser-agent/file-transfer.test.ts @@ -8,11 +8,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('electron', () => import('@/test/electron-mock')) import { app, type Session } from 'electron' -import { - discardStagedUploads, - saveDownloadToWorkspace, - stageUploadFiles, -} from '@/main/browser-agent/file-transfer' +import { stageUploadFiles } from '@/main/browser-agent/file-transfer' import { LocalFilesystemService } from '@/main/local-filesystem' let temp: string @@ -90,21 +86,6 @@ describe('stageUploadFiles', () => { expect(staged.endsWith('/evil')).toBe(true) }) - it('reports an app refusal and discards partial staging', async () => { - const fetch = vi.fn(async () => Response.json({ error: 'File not found' }, { status: 404 })) - - await expect( - stageUploadFiles({ - scopeId: 'chat-1', - toolCallId: 'call-2', - paths: ['files/missing.pdf'], - appSession: appSession(fetch), - localFiles: undefined, - signal, - }) - ).rejects.toThrow('Could not read that workspace file (File not found).') - }) - it('refuses a workspace file over the transfer ceiling', async () => { const oversized = new Uint8Array(BROWSER_FILE_TRANSFER_MAX_BYTES + 1) const fetch = vi.fn(async () => new Response(oversized)) @@ -174,35 +155,6 @@ describe('stageUploadFiles', () => { } }) - it('closes a local handle and discards staging when the upload is cancelled', async () => { - const local = join(temp, 'cancelled.txt') - writeFileSync(local, 'local bytes') - const handle = await open(local, 'r') - const controller = new AbortController() - controller.abort() - - try { - await expect( - stageUploadFiles({ - scopeId: 'chat-cancelled', - toolCallId: 'call-cancelled', - paths: ['user-local/Docs--m1/cancelled.txt'], - appSession: undefined, - localFiles: { - resolveGrantedFile: async () => ({ handle, name: 'cancelled.txt', size: 11 }), - }, - signal: controller.signal, - }) - ).rejects.toThrow(/aborted/) - expect(handle.fd).toBe(-1) - expect(existsSync(join(temp, 'sim-browser-uploads/chat-cancelled/call-cancelled'))).toBe( - false - ) - } finally { - await handle.close() - } - }) - it.runIf(process.platform !== 'win32')( 'preserves a granted POSIX backslash filename when staging', async () => { @@ -247,60 +199,4 @@ describe('stageUploadFiles', () => { }) ).rejects.toThrow('Local folders are unavailable') }) - - it('discards a scope staging directory', async () => { - const fetch = vi.fn(async () => new Response('x')) - const [staged] = await stageUploadFiles({ - scopeId: 'chat-9', - toolCallId: 'call-9', - paths: ['files/a.txt'], - appSession: appSession(fetch), - localFiles: undefined, - signal, - }) - - await discardStagedUploads('chat-9') - - expect(() => readFileSync(staged)).toThrow() - }) -}) - -describe('saveDownloadToWorkspace', () => { - it('stores the download under its claimed call and returns the workspace path', async () => { - const file = join(temp, 'report.csv') - writeFileSync(file, 'a,b\n1,2\n') - const fetch = vi.fn(async () => - Response.json({ path: 'files/report.csv', name: 'report.csv', size: 8 }) - ) - - const saved = await saveDownloadToWorkspace({ - appSession: appSession(fetch), - toolCallId: 'call-5', - filePath: file, - filename: 'report.csv', - signal, - }) - - expect(saved).toEqual({ path: 'files/report.csv', name: 'report.csv', size: 8 }) - const [url, init] = fetch.mock.calls[0] as unknown as [string, RequestInit] - expect(url).toBe('https://sim.test/api/desktop/tool/file?toolCallId=call-5&name=report.csv') - expect(init.method).toBe('PUT') - expect(await new Response(init.body).text()).toBe('a,b\n1,2\n') - }) - - it('surfaces the app error message', async () => { - const file = join(temp, 'report.csv') - writeFileSync(file, 'x') - const fetch = vi.fn(async () => Response.json({ error: 'Storage limit' }, { status: 402 })) - - await expect( - saveDownloadToWorkspace({ - appSession: appSession(fetch), - toolCallId: 'call-6', - filePath: file, - filename: 'report.csv', - signal, - }) - ).rejects.toThrow('Could not save the download (Storage limit).') - }) }) diff --git a/apps/desktop/src/main/browser-agent/keyboard.test.ts b/apps/desktop/src/main/browser-agent/keyboard.test.ts index e71a9514b17..18c98710645 100644 --- a/apps/desktop/src/main/browser-agent/keyboard.test.ts +++ b/apps/desktop/src/main/browser-agent/keyboard.test.ts @@ -1,4 +1,3 @@ -import { toRecord } from '@sim/utils/object' import { describe, expect, it, vi } from 'vitest' vi.mock('electron', () => import('@/test/electron-mock')) @@ -6,12 +5,10 @@ vi.mock('electron', () => import('@/test/electron-mock')) import { WebContentsView } from 'electron' import { buildKeyDispatchPlan, - cdpModifiers, dispatchKeyCombo, KeyDispatchError, modifierKeyEvents, parseKeyCombo, - parseModifiers, } from '@/main/browser-agent/keyboard' describe('parseKeyCombo', () => { @@ -38,26 +35,6 @@ describe('parseKeyCombo', () => { expect(parseKeyCombo('ControlOrMeta+K', 'darwin')).toMatchObject({ meta: true }) }) - it('parses function keys and Insert without inserting text', () => { - expect(parseKeyCombo('F2')).toMatchObject({ key: 'F2', code: 'F2', keyCode: 113 }) - expect(parseKeyCombo('Shift+F12')).toMatchObject({ key: 'F12', keyCode: 123, shift: true }) - expect(parseKeyCombo('Insert')).toMatchObject({ key: 'Insert', keyCode: 45 }) - const [down] = buildKeyDispatchPlan(parseKeyCombo('F2'), 'linux') - expect(down).toMatchObject({ type: 'rawKeyDown', key: 'F2' }) - expect(down.text).toBeUndefined() - }) - - it('parses modifier lists shared with pointer input', () => { - expect(parseModifiers(['Shift', 'Mod'], 'darwin')).toEqual({ - ctrl: false, - meta: true, - shift: true, - alt: false, - }) - expect(cdpModifiers(parseModifiers(['Mod', 'Alt'], 'linux'))).toBe(3) - expect(() => parseModifiers(['Hyper'])).toThrow(/Unrecognized modifier/) - }) - it('rejects unknown keys and modifiers', () => { expect(() => parseKeyCombo('Hyper+X')).toThrow(/Unrecognized modifier/) expect(() => parseKeyCombo('NotAKey')).toThrow(/Unrecognized key/) @@ -104,12 +81,6 @@ describe('buildKeyDispatchPlan', () => { expect(down).toMatchObject({ type: 'keyDown', text: '\r', windowsVirtualKeyCode: 13 }) }) - it('sends editing keys as rawKeyDown without text', () => { - const [down] = buildKeyDispatchPlan(parseKeyCombo('Backspace'), 'linux') - expect(down.type).toBe('rawKeyDown') - expect(down.text).toBeUndefined() - }) - it('maps Cmd shortcuts to Blink editing commands on macOS only', () => { const combo = parseKeyCombo('Cmd+A') const [macDown] = buildKeyDispatchPlan(combo, 'darwin') @@ -130,33 +101,6 @@ describe('buildKeyDispatchPlan', () => { expect(linuxDown.commands).toBeUndefined() }) - it('does not rewrite non-editing Control combos on macOS', () => { - const [down] = buildKeyDispatchPlan(parseKeyCombo('Control+K'), 'darwin') - expect(down.modifiers).toBe(2) - expect(down.commands).toBeUndefined() - }) - - it('uses the Chromium punctuation descriptor for Cmd+,', () => { - const [down, up] = buildKeyDispatchPlan(parseKeyCombo('Cmd+,'), 'darwin') - - expect(down).toMatchObject({ - type: 'rawKeyDown', - key: ',', - code: 'Comma', - windowsVirtualKeyCode: 188, - modifiers: 4, - }) - expect(down).not.toHaveProperty('nativeVirtualKeyCode') - expect(up).not.toHaveProperty('nativeVirtualKeyCode') - }) - - it('maps Cmd+Shift+Z to redo and Cmd+Z to undo on macOS', () => { - const [redo] = buildKeyDispatchPlan(parseKeyCombo('Cmd+Shift+Z'), 'darwin') - expect(redo.commands).toEqual(['redo']) - const [undo] = buildKeyDispatchPlan(parseKeyCombo('Cmd+Z'), 'darwin') - expect(undo.commands).toEqual(['undo']) - }) - it('encodes the CDP modifier bitmask (Alt=1 Ctrl=2 Meta=4 Shift=8)', () => { const [down] = buildKeyDispatchPlan(parseKeyCombo('Control+Shift+K'), 'linux') expect(down.modifiers).toBe(2 | 8) @@ -164,15 +108,6 @@ describe('buildKeyDispatchPlan', () => { expect(down.type).toBe('rawKeyDown') expect(down.text).toBeUndefined() }) - - it('sends the shifted character as text and keeps Alt-printable combos non-textual', () => { - const [shifted] = buildKeyDispatchPlan(parseKeyCombo('Shift+1'), 'linux') - expect(shifted).toMatchObject({ key: '!', code: 'Digit1', text: '!', modifiers: 8 }) - - const [alt] = buildKeyDispatchPlan(parseKeyCombo('Alt+a'), 'linux') - expect(alt).toMatchObject({ type: 'rawKeyDown', key: 'a', modifiers: 1 }) - expect(alt.text).toBeUndefined() - }) }) describe('modifierKeyEvents', () => { @@ -198,37 +133,6 @@ describe('modifierKeyEvents', () => { expect.objectContaining({ type: 'keyUp', key: 'Control', modifiers: 0 }), ]) }) - - it('presses a bare modifier as its own key with its flag set', () => { - const combo = parseKeyCombo('Control', 'linux') - const [down, up] = buildKeyDispatchPlan(combo, 'linux') - - expect(down).toMatchObject({ - type: 'rawKeyDown', - key: 'Control', - code: 'ControlLeft', - modifiers: 2, - }) - expect(up).toMatchObject({ type: 'keyUp', key: 'Control', modifiers: 0 }) - expect(modifierKeyEvents(combo, 'linux')).toEqual({ downs: [], ups: [] }) - }) - - it('treats modifier aliases like their canonical key', () => { - for (const [alias, key, flag] of [ - ['Ctrl', 'Control', 2], - ['Option', 'Alt', 1], - ['Cmd', 'Meta', 4], - ['Command', 'Meta', 4], - ] as const) { - const [down, up] = buildKeyDispatchPlan(parseKeyCombo(alias, 'linux'), 'linux') - expect(down).toMatchObject({ key, modifiers: flag }) - expect(up).toMatchObject({ type: 'keyUp', key, modifiers: 0 }) - } - }) - - it('sends no extra events for a key without modifiers', () => { - expect(modifierKeyEvents(parseKeyCombo('a', 'linux'), 'linux')).toEqual({ downs: [], ups: [] }) - }) }) describe('dispatchKeyCombo', () => { @@ -312,74 +216,4 @@ describe('dispatchKeyCombo', () => { expect.objectContaining({ type: 'keyUp', key: 'Enter' }), ]) }) - - it('releases only the chord keys whose press was attempted', async () => { - const contents = new WebContentsView().webContents - vi.mocked(contents.debugger.sendCommand) - .mockResolvedValueOnce({}) - .mockRejectedValueOnce(new Error('shift-down response lost')) - - await expect(dispatchKeyCombo(contents, parseKeyCombo('Ctrl+Shift+K'))).rejects.toMatchObject({ - name: KeyDispatchError.name, - keyDownDispatched: true, - }) - - const keys = vi - .mocked(contents.debugger.sendCommand) - .mock.calls.map(([, params]) => `${toRecord(params).type} ${toRecord(params).key}`) - expect(keys).toEqual(['rawKeyDown Control', 'rawKeyDown Shift', 'keyUp Shift', 'keyUp Control']) - - const early = new WebContentsView().webContents - vi.mocked(early.debugger.sendCommand).mockRejectedValueOnce(new Error('control-down lost')) - await expect(dispatchKeyCombo(early, parseKeyCombo('Ctrl+Shift+K'))).rejects.toMatchObject({ - name: KeyDispatchError.name, - }) - expect( - vi - .mocked(early.debugger.sendCommand) - .mock.calls.map(([, params]) => `${toRecord(params).type} ${toRecord(params).key}`) - ).toEqual(['rawKeyDown Control', 'keyUp Control']) - }) - - it('does not turn menu-restoration cleanup failure into a duplicate key retry signal', async () => { - const contents = new WebContentsView().webContents - vi.mocked(contents.setIgnoreMenuShortcuts).mockImplementation((ignored: boolean) => { - if (!ignored) throw new Error('menu cleanup failed') - }) - - await expect(dispatchKeyCombo(contents, parseKeyCombo('Cmd+A'))).resolves.toBeUndefined() - expect(contents.debugger.sendCommand).toHaveBeenCalledTimes(4) - }) - - it('keeps the application menu isolated until overlapping dispatches finish', async () => { - const contents = new WebContentsView().webContents - let releaseFirstDown: (() => void) | undefined - vi.mocked(contents.debugger.sendCommand).mockImplementationOnce( - () => - new Promise((resolve) => { - releaseFirstDown = () => resolve({}) - }) - ) - - const first = dispatchKeyCombo(contents, parseKeyCombo('Cmd+A')) - await Promise.resolve() - const second = dispatchKeyCombo(contents, parseKeyCombo('Cmd+Z')) - await second - - expect(contents.setIgnoreMenuShortcuts).toHaveBeenCalledTimes(1) - expect(contents.setIgnoreMenuShortcuts).toHaveBeenCalledWith(true) - - releaseFirstDown?.() - await first - - expect(contents.setIgnoreMenuShortcuts).toHaveBeenNthCalledWith(2, false) - }) - - it('does not change application-menu handling for ordinary page keys', async () => { - const contents = new WebContentsView().webContents - - await dispatchKeyCombo(contents, parseKeyCombo('Enter')) - - expect(contents.setIgnoreMenuShortcuts).not.toHaveBeenCalled() - }) }) diff --git a/apps/desktop/src/main/browser-agent/page-functions.test.ts b/apps/desktop/src/main/browser-agent/page-functions.test.ts index 3a10100b097..c9b6ef73e33 100644 --- a/apps/desktop/src/main/browser-agent/page-functions.test.ts +++ b/apps/desktop/src/main/browser-agent/page-functions.test.ts @@ -138,15 +138,6 @@ afterEach(() => { }) describe('conditional click scrolling', () => { - it('leaves a reachable target in place', () => { - const target = visible(document.createElement('button')) - document.body.append(target) - register(target) - target.scrollIntoView = vi.fn() - expect(runSerialized(clickElement, [0, false])).toMatchObject({ x: 50, y: 10 }) - expect(target.scrollIntoView).not.toHaveBeenCalled() - }) - it('rechecks the hit target after scrolling past a sticky obstruction', () => { const target = visible(document.createElement('button')) const obstruction = visible(document.createElement('div')) @@ -164,28 +155,6 @@ describe('conditional click scrolling', () => { expect(target.scrollIntoView).toHaveBeenCalledOnce() }) - it('reveals a parent control when only its nested button is initially reachable', () => { - const card = visible(document.createElement('div')) - card.setAttribute('role', 'button') - const nested = visible(document.createElement('button')) - card.append(nested) - document.body.append(card) - register(card) - let scrolled = false - card.scrollIntoView = vi.fn(() => { - scrolled = true - }) - const nestedClick = vi.fn() - nested.addEventListener('click', nestedClick) - Object.defineProperty(document, 'elementFromPoint', { - configurable: true, - value: () => (scrolled ? card : nested), - }) - expect(runSerialized(clickElement, [0, false])).toMatchObject({ x: 50, y: 10 }) - expect(card.scrollIntoView).toHaveBeenCalledOnce() - expect(nestedClick).not.toHaveBeenCalled() - }) - it('rejects a target removed by scrolling without dispatching input', () => { const target = visible(document.createElement('button')) const obstruction = visible(document.createElement('div')) @@ -307,42 +276,6 @@ describe('secret-field detection', () => { expect(focusElementForTyping(0)).toEqual({ error: 'password' }) }) - it('still allows ordinary fields and controls', () => { - document.body.innerHTML = - '' - const [text, , email] = Array.from(document.body.querySelectorAll('input, button')).map(visible) - const button = visible(document.querySelector('button') as HTMLButtonElement) - register(text, button, email) - - expect(typeIntoElement(0, 'search terms', false)).toMatchObject({ - dispatched: true, - }) - expect(clickElement(1)).toMatchObject({ dispatched: true }) - expect(focusElementForTyping(2)).toMatchObject({ focused: true }) - }) - - it('refuses readonly, disabled, and non-text fields for typing', () => { - document.body.innerHTML = ` - - - - ` - const fields = Array.from(document.querySelectorAll('input, textarea')).map((element) => - visible(element as HTMLElement) - ) - register(...fields) - - expect(focusElementForTyping(0)).toEqual({ error: 'readonly' }) - expect(typeIntoElement(0, 'change', false)).toEqual({ error: 'readonly' }) - expect(focusElementForTyping(1)).toEqual({ error: 'disabled' }) - expect(typeIntoElement(1, 'change', false)).toEqual({ error: 'disabled' }) - expect(focusElementForTyping(2)).toMatchObject({ error: 'not-editable', elementTag: 'input' }) - expect(typeIntoElement(2, 'change', false)).toMatchObject({ - error: 'not-editable', - elementTag: 'input', - }) - }) - it('detects a password field reached through a same-origin iframe', () => { // `instanceof HTMLInputElement` is realm-bound and returns false for nodes // owned by a frame, which is why detection matches on tagName instead. @@ -401,41 +334,6 @@ describe('combobox typing surfaces', () => { expect(input.value).toBe('Mondu') }) - it('keeps pointer clicks blocked with typing guidance when suggestions own the surface', () => { - const { option } = composeField() - Object.defineProperty(document, 'elementFromPoint', { - configurable: true, - value: () => option, - }) - expect(focusElementForTyping(0)).toMatchObject({ focused: true }) - - expect(clickElement(0, false)).toMatchObject({ - error: 'suggestions-open', - blocker: 'Mondu', - }) - }) - - it('does not give suggestions guidance when any click point has an unrelated blocker', () => { - const { option } = composeField() - const overlay = visible(document.createElement('div')) - overlay.setAttribute('aria-label', 'Unrelated overlay') - document.body.append(overlay) - Object.defineProperty(document, 'elementFromPoint', { - configurable: true, - value: (x: number) => (x > 70 ? overlay : option), - }) - expect(focusElementForTyping(0)).toEqual({ - error: 'obstructed', - blocker: 'Mondu', - blockerControls: [], - }) - - expect(clickElement(0, false)).toMatchObject({ - error: 'obstructed', - blocker: 'Mondu', - }) - }) - it('refuses mixed or unrelated blockers instead of treating them as suggestions', () => { const { option } = composeField() const overlay = visible(document.createElement('div')) @@ -505,72 +403,6 @@ describe('elements inside a same-origin iframe', () => { expect(field.value).toBe('hello') }) - it('focuses a framed input for native typing', () => { - const inner = framedBody('') - register(visible(inner.querySelector('input') as HTMLInputElement)) - - expect(focusElementForTyping(0)).toMatchObject({ - focused: true, - kind: 'input', - }) - }) - - it('selects an option in a framed select', () => { - const inner = framedBody( - '' - ) - const select = inner.querySelector('select') as HTMLSelectElement - register(select) - - expect(selectOptionInElement(0, 'B')).toMatchObject({ selected: 'B' }) - expect(select.value).toBe('b') - }) - - it('does not programmatically mutate disabled selects or options', () => { - const inner = framedBody(` - - - `) - const [disabledSelect, optionDisabled] = Array.from( - inner.querySelectorAll('select') - ) as HTMLSelectElement[] - register(disabledSelect, optionDisabled) - - expect(selectOptionInElement(0, 'A')).toEqual({ error: 'disabled' }) - expect(selectOptionInElement(1, 'B')).toEqual({ error: 'disabled' }) - expect(optionDisabled.value).toBe('') - }) - - it('focuses a framed element when clicking it', () => { - const inner = framedBody('') - const button = visible(inner.querySelector('button') as HTMLButtonElement) - register(button) - let focused = false - button.addEventListener('focus', () => { - focused = true - }) - - expect(clickElement(0)).toMatchObject({ dispatched: true }) - expect(focused).toBe(true) - }) - - it('does not synthesize Space after focusing a framed text input', () => { - const inner = framedBody('') - const [text, checkbox] = Array.from(inner.querySelectorAll('input')) as HTMLInputElement[] - visible(text) - visible(checkbox) - register(text, checkbox) - - expect(clickElement(0, false, true)).toMatchObject({ - dispatched: false, - activationKey: undefined, - }) - expect(clickElement(1, false, true)).toMatchObject({ - dispatched: false, - activationKey: 'Space', - }) - }) - it('still refuses a framed password field', () => { const inner = framedBody('') register(inner.querySelector('input') as HTMLInputElement) @@ -621,76 +453,6 @@ describe('collectSnapshot', () => { expect(outline).not.toContain('hunter2') }) - it('still reports ordinary input values', () => { - document.body.innerHTML = '' - visible(document.querySelector('input') as HTMLInputElement) - - expect(outlineOf(collectSnapshot())).toContain('value="tokyo"') - }) - - it('exposes roleless delegated React rows instead of dropping their text', () => { - document.body.innerHTML = '
eng-bugs
' - const row = visible(document.querySelector('div') as HTMLDivElement) - visible(document.querySelector('span') as HTMLSpanElement) - let clicked = false - row.addEventListener('click', () => { - clicked = true - }) - - const outline = outlineOf(collectSnapshot()) - const ref = refFor(outline, 'eng-bugs') - - expect(outline).toContain('clickable "eng-bugs"') - expect(clickElement(ref)).toMatchObject({ dispatched: true }) - expect(clicked).toBe(true) - }) - - it('names the covering dialog controls so the agent can dismiss it and retry', () => { - document.body.innerHTML = ` - -

We use cookies

- -
` - for (const element of Array.from(document.body.querySelectorAll('*'))) visible(element) - const outline = outlineOf(collectSnapshot()) - const ref = refFor(outline, 'Checkout') - const notice = document.querySelector('p') as HTMLParagraphElement - Object.defineProperty(document, 'elementFromPoint', { configurable: true, value: () => notice }) - - expect(clickElement(ref, false)).toEqual({ - error: 'obstructed', - blocker: 'We use cookies', - blockerControls: [ - { id: refFor(outline, 'Accept all'), name: 'Accept all' }, - { id: refFor(outline, 'Close notice'), name: 'Close notice' }, - ], - }) - }) - - it('names the controls of an overlay built from a web component', () => { - document.body.innerHTML = ` - - ` - const host = document.getElementById('banner') as HTMLDivElement - const shadow = host.attachShadow({ mode: 'open' }) - shadow.innerHTML = '

We use cookies

' - for (const element of [ - ...Array.from(document.body.querySelectorAll('*')), - ...Array.from(shadow.querySelectorAll('*')), - ]) { - visible(element) - } - const outline = outlineOf(collectSnapshot()) - const ref = refFor(outline, 'Checkout') - const notice = shadow.querySelector('p') as HTMLParagraphElement - Object.defineProperty(document, 'elementFromPoint', { configurable: true, value: () => notice }) - - expect(clickElement(ref, false)).toMatchObject({ - error: 'obstructed', - blockerControls: [{ id: refFor(outline, 'Close banner'), name: 'Close banner' }], - }) - }) - it('refuses a coordinate click when an overlay owns every hit point', () => { document.body.innerHTML = '
' @@ -710,86 +472,6 @@ describe('collectSnapshot', () => { }) }) - it('refuses a parent click when a nested independent control owns the hit point', () => { - document.body.innerHTML = ` -
- -
- ` - const card = visible(document.querySelector('[role="button"]') as HTMLDivElement) - const nestedButton = visible(document.querySelector('button') as HTMLButtonElement) - const ref = refFor(outlineOf(collectSnapshot()), 'Channel card') - Object.defineProperty(document, 'elementFromPoint', { - configurable: true, - value: () => nestedButton, - }) - - expect(card.contains(nestedButton)).toBe(true) - // Nothing is covering the card — its own button owns the point. Reporting - // this as an obstruction told the agent to close an overlay that does not - // exist; the recovery is to target the nested control instead. - expect(clickElement(ref, false)).toEqual({ - error: 'nested-control', - blocker: 'Delete channel', - controlId: 1, - }) - }) - - it('names an emoji gridcell from descendant image metadata', () => { - document.body.innerHTML = '
party parrot
' - visible(document.querySelector('[role="gridcell"]') as HTMLDivElement) - - expect(outlineOf(collectSnapshot())).toContain('gridcell "party parrot"') - }) - - it('names an emoji gridcell from Slack-style data metadata', () => { - document.body.innerHTML = - '
' - visible(document.querySelector('[role="gridcell"]') as HTMLDivElement) - - expect(outlineOf(collectSnapshot())).toContain('gridcell "party-parrot"') - }) - - it('reports native and ARIA control state in the snapshot', () => { - document.body.innerHTML = ` - - - - -
-
Activity
- -
- ` - for (const element of document.body.children) visible(element as HTMLElement) - document.querySelector('[aria-label="Partial selection"]')!.indeterminate = - true - const outline = outlineOf(collectSnapshot()) - - expect(outline).toMatch(/checkbox "Email alerts" \[ref=\d+\] unchecked/) - expect(outline).toMatch(/radio "Weekly" \[ref=\d+\] checked/) - expect(outline).toMatch(/checkbox "Partial selection" \[ref=\d+\] mixed/) - expect(outline).toMatch(/button "Filters" \[ref=\d+\] aria-expanded=false aria-pressed=true/) - expect(outline).toMatch(/switch "Dark mode" \[ref=\d+\] aria-checked=mixed/) - expect(outline).toMatch(/tab "Activity" \[ref=\d+\] aria-selected=true/) - expect(outline).toMatch(/textbox "Notes" \[ref=\d+\] readonly required/) - expect(outline).toMatch(/textbox "Summary" \[ref=\d+\] aria-readonly aria-required/) - }) - - it('does not duplicate every descendant of an inherited pointer target', () => { - document.body.innerHTML = ` -
- eng-bugs -
- ` - for (const element of document.querySelectorAll('*')) visible(element) - - const outline = outlineOf(collectSnapshot()) - - expect(outline.match(/clickable /g)).toHaveLength(1) - expect(outline).toContain('clickable "eng-bugs#"') - }) - it('escapes labels that try to forge snapshot ref syntax', () => { document.body.innerHTML = "" visible(document.querySelector('button') as HTMLButtonElement) @@ -844,23 +526,6 @@ describe('collectSnapshot', () => { expect(lines[0]).not.toContain('[ref=999]') }) - it('shares the text budget across inline fragments and leaves room for later controls', () => { - document.body.innerHTML = `${Array.from( - { length: 650 }, - (_, index) => `

Before ${index} inline ${index} after ${index}

` - ).join( - '' - )}${Array.from({ length: 100 }, (_, index) => ``).join('')}` - for (const element of document.querySelectorAll('*')) visible(element) - - const snapshot = collectSnapshot() as { outline: string; truncated: boolean } - expect(snapshot.truncated).toBe(true) - expect(snapshot.outline.match(/^- text /gm)).toHaveLength(120) - expect(snapshot.outline.match(/^- button /gm)).toHaveLength(100) - expect(snapshot.outline).toMatch(/button "Action 99" \[ref=\d+\]/) - expect(snapshot.outline).toMatch(/textbox "Final field" \[ref=\d+\]/) - }) - it('indexes only refs that were emitted before snapshot line truncation', () => { document.body.innerHTML = `${Array.from( { length: 599 }, @@ -901,41 +566,6 @@ describe('collectSnapshot', () => { expect(clickElement(ref)).toEqual({ error: 'file-input' }) }) - it('pins the file input behind a drop zone, label, or the input itself', () => { - document.body.innerHTML = `
Drop files
- -
` - register( - document.getElementById('zone') as HTMLElement, - document.getElementById('label') as HTMLElement, - document.getElementById('labelled') as HTMLElement, - document.getElementById('two') as HTMLElement - ) - - expect(resolveFileInputTarget(0)).toEqual({ - input: document.getElementById('hidden'), - document, - }) - expect(resolveFileInputTarget(1)).toEqual({ - input: document.getElementById('labelled'), - document, - }) - expect(resolveFileInputTarget(2)).toEqual({ - input: document.getElementById('labelled'), - document, - }) - expect(() => resolveFileInputTarget(3)).toThrow('multiple file inputs') - expect(document.querySelector('[data-sim-agent-upload]')).toBeNull() - }) - - it('reports an element with no nearby file input', () => { - document.body.innerHTML = - '
' - register(document.getElementById('b') as HTMLElement) - - expect(() => resolveFileInputTarget(0)).toThrow('no nearby file input') - }) - it.each([ ['disabled input', ''], ['disabled fieldset', '
'], @@ -960,57 +590,6 @@ describe('collectSnapshot', () => { expect(input.hasAttribute('data-sim-agent-upload')).toBe(false) }) - it('allows the first legend exemption in a disabled fieldset', () => { - document.body.innerHTML = - '
' - const input = document.querySelector('input') as HTMLInputElement - register(document.querySelector('label') as HTMLLabelElement) - - expect(runSerialized(resolveFileInputTarget, [0])).toEqual({ input, document }) - }) - - it('pins the original input inside an open shadow root without modifying the DOM', () => { - const host = document.createElement('div') - document.body.append(host) - const input = document.createElement('input') - input.type = 'file' - host.attachShadow({ mode: 'open' }).append(input) - register(host) - - expect(runSerialized(resolveFileInputTarget, [0])).toEqual({ input, document }) - expect(input.hasAttribute('data-sim-agent-upload')).toBe(false) - }) - - it('captures the actual owner document for an input reached through a same-origin frame', () => { - const frame = document.createElement('iframe') - document.body.append(frame) - const childDocument = frame.contentDocument as Document - childDocument.body.innerHTML = '' - const input = childDocument.querySelector('input') as HTMLInputElement - register(input) - - const captured = resolveFileInputTarget(0) - expect(captured).toEqual({ input, document: childDocument }) - document.body.append(input) - expect(captured.document).toBe(childDocument) - expect(captured.input.ownerDocument).toBe(document) - }) - - it('climbs out of a shadow root inside a same-origin frame to find the file input', () => { - const frame = document.createElement('iframe') - document.body.append(frame) - const childDocument = frame.contentDocument as Document - childDocument.body.innerHTML = '
' - const host = childDocument.getElementById('host') as HTMLElement - const button = childDocument.createElement('button') - host.attachShadow({ mode: 'open' }).append(button) - const input = childDocument.querySelector('input') as HTMLInputElement - register(button) - - expect(button.getRootNode()).not.toBeInstanceOf(ShadowRoot) - expect(resolveFileInputTarget(0)).toEqual({ input, document: childDocument }) - }) - it('refuses a disconnected or stale upload reference', () => { const input = document.createElement('input') input.type = 'file' @@ -1037,122 +616,6 @@ describe('collectSnapshot', () => { expect(readSelectElementState(0)).toMatchObject({ values: [] }) }) - it('captures requested labels before event handlers replace a duplicate-value option', () => { - document.body.innerHTML = - '' - const select = document.querySelector('select') as HTMLSelectElement - register(select) - select.addEventListener('change', () => { - select.options[1].selected = false - select.options[2].selected = true - select.options[1].label = 'Rewritten' - }) - expect(selectOptionInElement(0, ['Fixed', 'Wanted'])).toMatchObject({ - values: ['fixed', 'shared'], - labels: ['Fixed', 'Wanted'], - }) - expect(readSelectElementState(0)).toMatchObject({ - values: ['fixed', 'shared'], - labels: ['Fixed', 'Other'], - }) - }) - - it('does not use multiple-selection arguments on a single-selection dropdown', () => { - document.body.innerHTML = - '' - const select = document.querySelector('select') as HTMLSelectElement - register(select) - expect(selectOptionInElement(0, ['B'])).toHaveProperty('error') - expect(select.value).toBe('a') - expect(selectOptionInElement(0, 'B')).toMatchObject({ value: 'b' }) - }) - - it('keeps plain visible leaf text available as an actionable ref', () => { - document.body.innerHTML = '
announce
' - visible(document.querySelector('span') as HTMLSpanElement) - - expect(outlineOf(collectSnapshot())).toContain('text "announce" [ref=') - }) - - it('preserves mixed inline text in reading order without duplicating control labels', () => { - document.body.innerHTML = - '
Type "hello" in upper case.
' - for (const el of document.querySelectorAll('body, div, strong, button, span, b')) visible(el) - const outline = outlineOf(collectSnapshot()) - const labels = Array.from(outline.matchAll(/- text ("(?:[^"\\]|\\.)*")/g), (match) => - JSON.parse(match[1]) - ) - expect(labels).toEqual(['Type "', 'hello', '" in upper case.']) - expect(outline).toContain('button "Save draft"') - expect(outline).not.toContain('Hidden') - }) - - it('does not emit stale textarea defaults after the current value changes', () => { - document.body.innerHTML = '' - const input = visible(document.querySelector('textarea') as HTMLTextAreaElement) - input.value = 'Current draft' - expect(outlineOf(collectSnapshot())).not.toContain('Old draft') - input.value = '' - expect(outlineOf(collectSnapshot())).not.toContain('Old draft') - }) - - it('preserves direct text in open shadow roots and respects hidden hosts', () => { - document.body.innerHTML = '
' - const host = visible(document.querySelector('div') as HTMLDivElement) - const shadow = host.attachShadow({ mode: 'open' }) - shadow.innerHTML = 'Before middle after' - visible(shadow.querySelector('strong') as HTMLElement) - const outline = outlineOf(collectSnapshot()) - expect(outline.indexOf('text "Before"')).toBeLessThan(outline.indexOf('text "middle"')) - expect(outline.indexOf('text "middle"')).toBeLessThan(outline.indexOf('text "after"')) - host.hidden = true - expect(outlineOf(collectSnapshot())).not.toContain('Before') - }) - - it('gives interactive headings actionable refs while preserving static headings', () => { - document.body.innerHTML = - '

Overview

' - for (const el of document.querySelectorAll('h3, h2')) visible(el) - const clicked = vi.fn() - document.querySelector('h3')?.addEventListener('click', clicked) - const outline = outlineOf(collectSnapshot()) - expect(outline).toContain('tab "Details"') - expect(outline).toContain('aria-expanded=false') - expect(outline).toContain('heading "Overview" (h2)') - expect(clickElement(refFor(outline, 'Details'))).toMatchObject({ dispatched: true }) - expect(clicked).toHaveBeenCalledOnce() - }) - - it('exposes structured input types and multiple-selection controls', () => { - document.body.innerHTML = - '' - for (const el of document.querySelectorAll('input, select')) visible(el) - const outline = outlineOf(collectSnapshot()) - expect(outline).toContain('type="date"') - expect(outline).toMatch(/combobox "Countries" \[ref=\d+\] multiple/) - }) - - it('retains sender and timestamp text omitted from a row accessibility label', () => { - document.body.innerHTML = ` -
- Sid Studio - Quarterly plan - 11:42 AM - -
- ` - visible(document.querySelector('[role="link"]') as HTMLDivElement) - for (const child of document.querySelectorAll('span')) visible(child) - - const outline = outlineOf(collectSnapshot()) - - expect(outline).toContain('link "Quarterly plan Updated forecast"') - expect(outline).toContain('text "Sid Studio"') - expect(outline).toContain('text "11:42 AM"') - expect(outline).toContain('text "Has attachment"') - expect(outline).not.toContain('text "Quarterly plan"') - }) - it('recovers a ref when React uniquely replaces the same logical element', () => { document.body.innerHTML = '' const original = visible(document.querySelector('button') as HTMLButtonElement) @@ -1171,24 +634,6 @@ describe('collectSnapshot', () => { expect(clicked).toBe(true) }) - it('recovers from a connected but collapsed node to its unique visible replacement', () => { - document.body.innerHTML = - '
' - const original = visible(document.querySelector('[role="combobox"]') as HTMLDivElement) - visible(document.querySelector('input') as HTMLInputElement) - const ref = refFor(outlineOf(collectSnapshot()), 'To:') - original.getBoundingClientRect = () => - ({ width: 0, height: 0, top: 0, left: 0, right: 0, bottom: 0 }) as DOMRect - const replacement = visible(original.cloneNode(true) as HTMLDivElement) - visible(replacement.querySelector('input') as HTMLInputElement) - document.body.append(replacement) - - expect(focusElementForTyping(ref)).toMatchObject({ - focused: true, - refRecovered: true, - }) - }) - it('does not guess between visible replacements for a collapsed connected ref', () => { document.body.innerHTML = '
' @@ -1206,40 +651,8 @@ describe('collectSnapshot', () => { expect(focusElementForTyping(ref)).toMatchObject({ error: 'stale' }) }) - it('keeps a connected ref usable after a same-document URL change', () => { - document.body.innerHTML = '' - const button = visible(document.querySelector('button') as HTMLButtonElement) - const ref = refFor(outlineOf(collectSnapshot()), 'Send') - let clicked = false - button.addEventListener('click', () => { - clicked = true - }) - - window.history.pushState({}, '', '/client/T123/C456') - - expect(clickElement(ref)).toMatchObject({ dispatched: true, refRecovered: false }) - expect(clicked).toBe(true) - }) - - it('recovers a replaced ref after a same-document URL change', () => { - document.body.innerHTML = '' - const original = visible(document.querySelector('button') as HTMLButtonElement) - const ref = refFor(outlineOf(collectSnapshot()), 'Messages') - const replacement = visible(original.cloneNode(true) as HTMLButtonElement) - let clicked = false - replacement.addEventListener('click', () => { - clicked = true - }) - - window.history.pushState({}, '', '/client/T123/C456') - original.replaceWith(replacement) - - expect(clickElement(ref)).toMatchObject({ dispatched: true, refRecovered: true }) - expect(clicked).toBe(true) - }) - - it('refuses to recover a ref when replacement is ambiguous', () => { - document.body.innerHTML = '' + it('refuses to recover a ref when replacement is ambiguous', () => { + document.body.innerHTML = '' const original = visible(document.querySelector('button') as HTMLButtonElement) const ref = refFor(outlineOf(collectSnapshot()), 'Close') const first = visible(original.cloneNode(true) as HTMLButtonElement) @@ -1260,20 +673,6 @@ describe('collectSnapshot', () => { expect(clickElement(ref)).toMatchObject({ error: 'stale' }) }) - it('invalidates a generic connected row action when its surrounding item is recycled', () => { - document.body.innerHTML = ` -
eng-bugs
- ` - for (const element of document.querySelectorAll('*')) visible(element as HTMLElement) - const button = document.querySelector('button') as HTMLButtonElement - const ref = refFor(outlineOf(collectSnapshot()), 'More actions') - - ;(document.querySelector('span') as HTMLSpanElement).textContent = 'random' - - expect(button.isConnected).toBe(true) - expect(clickElement(ref)).toMatchObject({ error: 'stale' }) - }) - it('never recycles numeric refs across snapshots', () => { document.body.innerHTML = '' visible(document.querySelector('button') as HTMLButtonElement) @@ -1284,129 +683,9 @@ describe('collectSnapshot', () => { expect(clickElement(firstRef)).toMatchObject({ error: 'stale' }) expect(clickElement(secondRef)).toMatchObject({ dispatched: true }) }) - - // The exact shape of the reported failure: hovering a Slack message mounts an - // action bar, but it is a role="toolbar"/"group" — none of the three roles the - // popup scan used to match. The hover therefore observed no popup change, no - // target change, and so no effect at all, and the agent concluded hovering - // did not work and fell back to clicking pixels off screenshots. - it('sees a row action bar that mounts on hover', () => { - document.body.innerHTML = '
Hello
' - visible(document.querySelector('[data-testid="message"]') as HTMLElement) - - const before = readPageActionState(true) as { popups: string[] } - expect(before.popups).toEqual([]) - - const toolbar = visible(document.createElement('div')) - toolbar.setAttribute('role', 'toolbar') - toolbar.setAttribute('aria-label', 'Message shortcuts') - document.body.append(toolbar) - - const after = readPageActionState(false) as { popups: string[] } - expect(after.popups).toEqual(['Message shortcuts']) - expect(after.popups).not.toEqual(before.popups) - }) - - it.each([ - { role: 'dialog', field: 'dialogs' }, - { role: 'toolbar', field: 'popups' }, - ] as const)( - 'reports truncation when visible $field exceed the summary limit', - ({ role, field }) => { - for (let index = 0; index < 10; index++) { - const element = visible(document.createElement('div')) - element.setAttribute('role', role) - element.setAttribute('aria-label', `Existing ${index}`) - document.body.append(element) - } - const before = readPageActionState() as { - dialogs: string[] - popups: string[] - observationTruncated: boolean - } - expect(before[field]).toHaveLength(10) - expect(before.observationTruncated).toBe(false) - - const additional = visible(document.createElement('div')) - additional.setAttribute('role', role === 'toolbar' ? 'listbox' : role) - additional.setAttribute('aria-label', 'New overlay') - document.body.append(additional) - expect(readPageActionState()).toMatchObject({ - [field]: before[field], - observationTruncated: true, - }) - - additional.setAttribute('aria-hidden', 'true') - expect(readPageActionState()).toMatchObject({ - [field]: before[field], - observationTruncated: false, - }) - } - ) - - it('reports a targeted control semantic disappearance after its panel closes', () => { - document.body.innerHTML = ` - - ` - const panel = document.querySelector('aside') as HTMLElement - visible(panel) - visible(document.querySelector('button') as HTMLButtonElement) - const ref = refFor(outlineOf(collectSnapshot()), 'Close thread') - - const before = readPageActionState(true, ref) as { - targetState: { present: boolean; rendered: boolean } - } - panel.remove() - const composer = visible(document.createElement('textarea')) - composer.setAttribute('aria-label', 'Message') - document.body.append(composer) - const after = readPageActionState(false, ref) as { - targetState: { present: boolean; rendered: boolean } - } - - expect(before.targetState).toMatchObject({ present: true, rendered: true }) - expect(after.targetState).toEqual({ present: false, rendered: false }) - }) - - it('keeps semantic target presence through a unique React replacement', () => { - document.body.innerHTML = - '' - const original = visible(document.querySelector('button') as HTMLButtonElement) - const ref = refFor(outlineOf(collectSnapshot()), 'Close thread') - const before = readPageActionState(true, ref) as { targetState: unknown } - const replacement = visible(original.cloneNode(true) as HTMLButtonElement) - original.replaceWith(replacement) - const after = readPageActionState(false, ref) as { targetState: unknown } - - expect(after.targetState).toEqual(before.targetState) - }) }) describe('semantic control state', () => { - it('scopes a fresh snapshot to one card without reading sibling geometry', () => { - document.body.innerHTML = - '
' - for (const element of document.querySelectorAll('*')) visible(element) - const full = outlineOf(collectSnapshot()) - const oldRootRef = refFor(full, 'Selected card') - const outside = document.querySelector('body > button')! - const outsideGeometry = vi.spyOn(outside, 'getBoundingClientRect') - - const scoped = runSerialized(collectSnapshot, [100, oldRootRef]) as { - scoped: boolean - outline: string - refIds: number[] - } - expect(scoped.scoped).toBe(true) - expect(scoped.outline).toContain('Selected card') - expect(scoped.outline).toContain('Save card') - expect(scoped.outline).not.toContain('Outside card') - expect(scoped.outline).not.toContain('private') - expect(scoped.refIds.every((id) => id >= 100)).toBe(true) - expect(window.__simAgentResolveElement?.(oldRootRef)).toBeNull() - expect(outsideGeometry).not.toHaveBeenCalled() - }) - it('rejects stale or framed snapshot roots without replacing the page registry', () => { const button = document.createElement('button') document.body.append(visible(button)) @@ -1423,39 +702,6 @@ describe('semantic control state', () => { expect(collectSnapshot(10, 0)).toEqual({ error: 'framed-snapshot' }) }) - it.each(['detached', 'hidden', 'renamed'])( - 'rejects a %s root before scoped capture without adopting a lookalike', - (change) => { - document.body.innerHTML = - '
' - for (const element of document.querySelectorAll('*')) visible(element) - const root = document.querySelector('#card')! - const rootRef = refFor(outlineOf(collectSnapshot()), 'Selected card') - const replacement = root.cloneNode(true) as HTMLElement - for (const element of [replacement, ...replacement.querySelectorAll('*')]) visible(element) - if (change === 'detached') root.replaceWith(replacement) - else { - root.after(replacement) - if (change === 'hidden') root.setAttribute('hidden', '') - else root.setAttribute('aria-label', 'Different card') - } - - expect(runSerialized(collectSnapshot, [100, rootRef])).toMatchObject({ error: 'stale' }) - expect(window.__simAgentElements?.[rootRef]).toBe(root) - expect(runSerialized(collectSnapshot, [100, rootRef])).toMatchObject({ error: 'stale' }) - } - ) - - it('marks unreadable scoped frame content truncated', () => { - const root = visible(document.createElement('div')) - const frame = visible(document.createElement('iframe')) - root.append(frame) - document.body.append(root) - register(root) - Object.defineProperty(frame, 'contentDocument', { value: null }) - expect(collectSnapshot(10, 0)).toMatchObject({ scoped: true, truncated: true }) - }) - it('does not recover scoped refs into a different card after the original closes', () => { document.body.innerHTML = '
' @@ -1471,98 +717,6 @@ describe('semantic control state', () => { expect(window.__simAgentStaleReason).toContain('scoped snapshot root') }) - it('keeps scoped snapshots bounded when a selected container is very large', () => { - const root = document.createElement('div') - root.tabIndex = 0 - root.setAttribute('aria-label', 'Large card') - document.body.append(visible(root)) - register(root) - for (let index = 0; index < 400; index++) { - const button = visible(document.createElement('button')) - button.textContent = `Action ${index}` - root.append(button) - } - const scoped = collectSnapshot(10, 0) as { refIds: number[]; truncated: boolean } - expect(scoped.refIds).toHaveLength(300) - expect(scoped.truncated).toBe(true) - }) - - it('distinguishes hidden registered nodes from detached nodes without action recovery', () => { - const button = visible(document.createElement('button')) - document.body.append(button) - register(button) - window.__simAgentResolveElement = vi.fn(() => null) - button.style.display = 'none' - - expect(readPageActionState(false, 0, 'registered')).toMatchObject({ - targetState: { present: true, rendered: false }, - }) - button.remove() - expect(readPageActionState(false, 0, 'registered')).toMatchObject({ - targetState: { present: false, rendered: false }, - }) - expect(window.__simAgentResolveElement).not.toHaveBeenCalled() - }) - - it('does not report a text input as an unchecked control', () => { - const input = visible(document.createElement('input')) - document.body.append(input) - register(input) - - expect(readPageActionState(false, 0, 'registered')).toMatchObject({ - targetState: { present: true, checked: undefined }, - }) - expect(readPageActionState(false, 1, 'registered')).toEqual({ error: 'stale' }) - }) - - it.each([true, false])( - 'reports native disclosure state as %s without inventing it on other elements', - (open) => { - document.body.innerHTML = - '
Details
' - const details = document.querySelector('details')! - const dialog = document.querySelector('dialog')! - details.open = open - dialog.open = open - const elements = [ - details, - document.querySelector('summary')!, - dialog, - document.querySelector('button')!, - ] - register(...elements.map(visible)) - for (let id = 0; id < elements.length; id++) { - expect(runSerialized(readPageActionState, [false, id, 'registered'])).toMatchObject({ - targetState: { open: id === 3 ? undefined : open }, - }) - } - } - ) - - it.each(['checkbox', 'radio'])('reads native %s state with XHTML tag casing', (type) => { - const input = visible(document.createElement('input')) - input.type = type - input.checked = true - Object.defineProperty(input, 'tagName', { value: 'input' }) - document.body.append(input) - register(input) - - expect(runSerialized(readPageActionState, [false, 0, 'registered'])).toMatchObject({ - targetState: { checked: true }, - }) - }) - - it('preserves native and ARIA mixed states instead of reporting unchecked', () => { - document.body.innerHTML = - '
' - const checkbox = visible(document.querySelector('input') as HTMLInputElement) - checkbox.indeterminate = true - register(checkbox, visible(document.querySelector('div') as HTMLDivElement)) - - expect(readCheckableElementState(0)).toMatchObject({ checked: 'mixed' }) - expect(readCheckableElementState(1)).toMatchObject({ checked: 'mixed' }) - }) - it('honors disabled fieldsets and ARIA-disabled ancestors', () => { document.body.innerHTML = '
' @@ -1575,48 +729,6 @@ describe('semantic control state', () => { expect(readCheckableElementState(1)).toMatchObject({ disabled: true }) }) - it.each([true, false])( - 'reads ARIA-disabled=%s across shadow boundaries for waits and checked state', - (disabled) => { - const host = visible(document.createElement('div')) - host.setAttribute('aria-disabled', String(disabled)) - const nestedHost = visible(document.createElement('div')) - host.attachShadow({ mode: 'open' }).append(nestedHost) - const checkbox = visible(document.createElement('input')) - checkbox.type = 'checkbox' - nestedHost.attachShadow({ mode: 'open' }).append(checkbox) - document.body.append(host) - register(checkbox) - - expect(runSerialized(readCheckableElementState, [0])).toMatchObject({ disabled }) - expect(runSerialized(readPageActionState, [false, 0, 'registered'])).toMatchObject({ - targetState: { disabled }, - }) - } - ) - - it('reads native and ARIA checkable controls without mutating them', () => { - document.body.innerHTML = ` - - - ` - const checkbox = visible(document.querySelector('input') as HTMLInputElement) - const toggle = visible(document.querySelector('button') as HTMLButtonElement) - register(checkbox, toggle) - - expect(readCheckableElementState(0)).toMatchObject({ - checked: true, - disabled: false, - kind: 'input:checkbox', - }) - expect(readCheckableElementState(1)).toMatchObject({ - checked: false, - disabled: true, - kind: 'role:switch', - }) - expect(checkbox.checked).toBe(true) - }) - it('returns a viewport-clamped element screenshot rectangle', () => { const button = visible(document.createElement('button')) button.scrollIntoView = vi.fn() @@ -1656,29 +768,6 @@ describe('semantic control state', () => { expect(runSerialized(getElementScreenshotRect, [ref])).toMatchObject({ error: 'stale' }) expect(window.__simAgentElements?.[ref]).toBe(button) }) - - it('rejects same-origin frame crops rather than using frame-local coordinates', () => { - const frame = document.createElement('iframe') - document.body.append(frame) - const button = frame.contentDocument!.createElement('button') - frame.contentDocument!.body.append(button) - register(visible(button)) - - expect(getElementScreenshotRect(0)).toEqual({ error: 'framed-screenshot' }) - expect(readPageActionState(false, 0, 'registered')).toEqual({ error: 'framed-wait' }) - }) - - it('does not scroll an offscreen element into view for a screenshot', () => { - const button = visible(document.createElement('button')) - button.scrollIntoView = vi.fn() - document.body.append(button) - button.getBoundingClientRect = () => - ({ left: 0, right: 20, top: 5000, bottom: 5020, width: 20, height: 20 }) as DOMRect - register(button) - - expect(getElementScreenshotRect(0)).toEqual({ error: 'not-visible' }) - expect(button.scrollIntoView).not.toHaveBeenCalled() - }) }) describe('scrollPage', () => { @@ -1753,115 +842,6 @@ describe('scrollPage', () => { expect(scroller.scrollTop).toBe(300) }) - it('uses viewport width for the default horizontal distance', () => { - const { scroller, child } = makeHorizontalScroller() - Object.defineProperty(scroller, 'scrollWidth', { configurable: true, value: 10_000 }) - register(child) - - expect(scrollPage('right', undefined, 0)).toMatchObject({ - movedBy: Math.round(window.innerWidth * 0.85), - }) - }) - - it('skips a vertical-only descendant when targeting a horizontal ancestor', () => { - const { scroller, child } = makeHorizontalScroller(200) - child.style.overflowY = 'auto' - Object.defineProperties(child, { - clientHeight: { configurable: true, value: 50 }, - scrollHeight: { configurable: true, value: 500 }, - scrollTop: { configurable: true, writable: true, value: 100 }, - }) - register(child) - - expect(scrollPage('left', 75, 0)).toMatchObject({ - target: 'Message history', - targetSource: 'element', - movedBy: -75, - scrollLeft: 125, - }) - expect(scroller.scrollTop).toBe(300) - expect(child.scrollTop).toBe(100) - }) - - it('keeps a centered horizontal pane at its boundary instead of scrolling another pane', () => { - const { scroller, child } = makeHorizontalScroller(800) - const other = visible(document.createElement('div')) - other.style.overflowX = 'auto' - other.setAttribute('aria-label', 'Unrelated pane') - Object.defineProperties(other, { - clientWidth: { configurable: true, value: 200 }, - scrollWidth: { configurable: true, value: 1_000 }, - }) - document.body.prepend(other) - Object.defineProperty(document, 'elementsFromPoint', { - configurable: true, - value: () => [child, scroller], - }) - - expect(scrollPage('right', 100)).toMatchObject({ - target: 'Message history', - targetSource: 'viewport-center-boundary', - movedBy: 0, - atRight: true, - }) - expect(other.scrollLeft).toBe(0) - }) - - it.each([ - { direction: 'left', before: 0, after: -100, movedBy: -100, atLeft: false, atRight: false }, - { direction: 'left', before: -750, after: -800, movedBy: -50, atLeft: true, atRight: false }, - { direction: 'left', before: -800, after: -800, movedBy: 0, atLeft: true, atRight: false }, - { direction: 'right', before: -50, after: 0, movedBy: 50, atLeft: false, atRight: true }, - { direction: 'right', before: 0, after: 0, movedBy: 0, atLeft: false, atRight: true }, - ])('scrolls RTL $direction from $before with physical boundaries', (test) => { - const { child } = makeHorizontalScroller(test.before, true) - register(child) - - expect(scrollPage(test.direction, 100, 0)).toMatchObject({ - scrollLeft: test.after, - movedBy: test.movedBy, - atLeft: test.atLeft, - atRight: test.atRight, - }) - }) - - it('scrolls the document root containing an explicit same-origin iframe ref', () => { - document.body.innerHTML = '' - const frame = visible(document.querySelector('iframe') as HTMLIFrameElement) - const frameDocument = frame.contentDocument as Document - const frameWindow = frame.contentWindow as Window - frameDocument.body.innerHTML = '
wide table
' - const child = visible(frameDocument.body.firstElementChild as HTMLDivElement) - const root = visible(frameDocument.documentElement) - Object.defineProperties(root, { - clientWidth: { configurable: true, value: 200 }, - scrollWidth: { configurable: true, value: 1_000 }, - scrollLeft: { configurable: true, writable: true, value: 0 }, - }) - Object.defineProperty(frameWindow, 'scrollX', { configurable: true, writable: true, value: 0 }) - Object.defineProperty(frameWindow, 'scrollBy', { - configurable: true, - value: ({ left }: ScrollToOptions) => { - root.scrollLeft = Math.max(0, Math.min(800, root.scrollLeft + (left || 0))) - Object.defineProperty(frameWindow, 'scrollX', { - configurable: true, - value: root.scrollLeft, - }) - }, - }) - register(child) - - expect(scrollPage('right', 100, 0)).toMatchObject({ - target: 'html', - targetSource: 'element', - scrollLeft: 100, - movedBy: 100, - atLeft: false, - atRight: false, - windowScrollX: 0, - }) - }) - it('scrolls the movable internal container under the viewport center', () => { const { scroller, child } = makeScroller(600) Object.defineProperty(document, 'elementsFromPoint', { @@ -1879,20 +859,6 @@ describe('scrollPage', () => { }) }) - it('targets the nearest scrollable ancestor of an explicit ref', () => { - const { scroller, child } = makeScroller(0) - const ref = refFor(outlineOf(collectSnapshot()), 'message') - - expect(scrollPage('down', 125, ref)).toMatchObject({ - target: 'Message history', - targetSource: 'element', - scrollTop: 125, - movedBy: 125, - }) - expect(child.textContent).toBe('message') - expect(scroller.scrollTop).toBe(125) - }) - it('walks past an immovable nearest scroller to a movable ancestor for an explicit ref', () => { document.body.innerHTML = `
@@ -1933,45 +899,6 @@ describe('scrollPage', () => { expect(outer.scrollTop).toBe(400) }) - it('skips an immovable focused sidebar for the movable centered history pane', () => { - document.body.innerHTML = ` - -
message
- ` - const sidebar = visible(document.querySelector('#sidebar') as HTMLDivElement) - const history = visible(document.querySelector('#history') as HTMLDivElement) - const message = visible(history.firstElementChild as HTMLDivElement) - for (const [element, scrollTop] of [ - [sidebar, 0], - [history, 600], - ] as const) { - Object.defineProperties(element, { - clientHeight: { configurable: true, value: 200 }, - scrollHeight: { configurable: true, value: 1_000 }, - scrollTop: { configurable: true, writable: true, value: scrollTop }, - }) - Object.defineProperty(element, 'scrollBy', { - configurable: true, - value: ({ top }: ScrollToOptions) => { - element.scrollTop = Math.max(0, Math.min(800, element.scrollTop + (top || 0))) - }, - }) - } - setActiveElement(document, sidebar) - Object.defineProperty(document, 'elementsFromPoint', { - configurable: true, - value: () => [message, history], - }) - - expect(scrollPage('up', 100)).toMatchObject({ - target: 'Message history', - targetSource: 'viewport-center', - movedBy: -100, - }) - expect(sidebar.scrollTop).toBe(0) - expect(history.scrollTop).toBe(500) - }) - it('keeps a centered pane at its boundary instead of scrolling another pane', () => { const { scroller: history, child: message } = makeScroller(800) const sidebar = visible(document.createElement('div')) @@ -2041,47 +968,6 @@ describe('readChildFrameElementState', () => { frameName: 'apps', }) }) - - it('hit-tests a frame against its shadow root instead of the outer document', () => { - const host = document.createElement('div') - document.body.append(host) - const shadow = host.attachShadow({ mode: 'open' }) - const frame = document.createElement('iframe') - frame.name = 'apps' - shadow.append(frame) - visible(frame) - Object.defineProperty(shadow, 'elementFromPoint', { - configurable: true, - value: () => frame, - }) - Object.defineProperty(document, 'elementFromPoint', { - configurable: true, - value: () => host, - }) - - expect(readChildFrameElementState('apps', '', '', 0)).toMatchObject({ - known: true, - visible: true, - frameName: 'apps', - }) - }) - - it('uses WindowProxy identity to distinguish duplicate frame metadata', () => { - document.body.innerHTML = ` - - - ` - const frames = Array.from(document.querySelectorAll('iframe')) as HTMLIFrameElement[] - frames.forEach(visible) - Object.defineProperty(document, 'elementFromPoint', { - configurable: true, - value: () => frames[1], - }) - - expect( - readChildFrameElementState('apps', 'https://example.com/widget', 'https://example.com', 1) - ).toMatchObject({ known: true, visible: true, frameName: 'apps' }) - }) }) describe('readActiveElementState', () => { @@ -2127,31 +1013,6 @@ describe('readActiveElementState', () => { redacted: true, }) }) - - it('reports ordinary fields in full', () => { - document.body.innerHTML = '' - setActiveElement(document, document.querySelector('input')) - - expect(readActiveElementState()).toMatchObject({ - activeElement: 'input', - valueLength: 5, - valuePreview: 'tokyo', - }) - }) - - it('descends into a same-origin frame rather than reporting the frame', () => { - const frame = document.createElement('iframe') - document.body.append(frame) - const inner = frame.contentDocument as Document - inner.body.innerHTML = '' - setActiveElement(inner, inner.querySelector('input')) - setActiveElement(document, frame) - - expect(readActiveElementState()).toMatchObject({ - activeElement: 'password-field', - redacted: true, - }) - }) }) describe('XHTML lower-case tagName', () => { @@ -2173,51 +1034,9 @@ describe('XHTML lower-case tagName', () => { expect(typeIntoElement(0, 'hunter2', false)).toEqual({ error: 'password' }) expect(input.value).toBe('') }) - - it('still withholds the value of a lower-case-tagName credential field', () => { - const input = lowerCaseTagInput( - '' - ) - visible(input) - - const outline = outlineOf(collectSnapshot()) - - expect(outline).not.toContain('hunter2') - }) }) describe('activeElementSecrecy', () => { - it('reports safe for an ordinary field', () => { - document.body.innerHTML = '' - setActiveElement(document, document.querySelector('input')) - - expect(activeElementSecrecy()).toBe('safe') - }) - - it('distinguishes a different focused element from an invalid target ref', () => { - document.body.innerHTML = ` - - - ` - const expected = visible(document.querySelectorAll('input')[0]) - const other = visible(document.querySelectorAll('input')[1]) - const snapshot = collectSnapshot() as { refIds: number[] } - const expectedRef = snapshot.refIds[0] - setActiveElement(document, other) - - expect(activeElementSecrecy(expectedRef)).toBe('different') - expect(activeElementSecrecy(Number.MAX_SAFE_INTEGER)).toBe('stale') - - setActiveElement(document, expected) - expect(activeElementSecrecy(expectedRef)).toBe('safe') - }) - - it('reports safe when nothing is focused', () => { - setActiveElement(document, document.body) - - expect(activeElementSecrecy()).toBe('safe') - }) - it('reports secret for a focused password field', () => { document.body.innerHTML = '' setActiveElement(document, document.querySelector('input')) @@ -2262,44 +1081,6 @@ describe('activeElementSecrecy', () => { expect(host.shadowRoot).toBeNull() expect(activeElementSecrecy()).toBe('opaque') }) - - it('reports opaque for a closed shadow root on a custom element', () => { - const host = document.createElement('my-login') - document.body.append(host) - host.attachShadow({ mode: 'closed' }).innerHTML = '' - setActiveElement(document, host) - - expect(activeElementSecrecy()).toBe('opaque') - }) - - it('still reports safe for a focused element that is focusable in its own right', () => { - // The false-positive guard: a div the page made focusable is focused - // itself, not hiding a shadow tree, so keystrokes are not refused. - document.body.innerHTML = '
menu
' - setActiveElement(document, document.querySelector('div')) - - expect(activeElementSecrecy()).toBe('safe') - }) - - it('still reports safe for a focused contenteditable', () => { - document.body.innerHTML = '
note
' - const editable = document.querySelector('div') as HTMLElement - Object.defineProperty(editable, 'isContentEditable', { get: () => true }) - setActiveElement(document, editable) - - expect(activeElementSecrecy()).toBe('safe') - }) - - it('descends into a same-origin frame instead of calling it opaque', () => { - const frame = document.createElement('iframe') - document.body.append(frame) - const inner = frame.contentDocument as Document - inner.body.innerHTML = '' - setActiveElement(inner, inner.querySelector('input')) - setActiveElement(document, frame) - - expect(activeElementSecrecy()).toBe('safe') - }) }) describe('pressKeyOnPage', () => { @@ -2311,19 +1092,6 @@ describe('pressKeyOnPage', () => { error: 'password', }) }) - - it('delivers keystrokes to ordinary fields', () => { - document.body.innerHTML = '' - const input = document.querySelector('input') as HTMLInputElement - setActiveElement(document, input) - const seen: string[] = [] - input.addEventListener('keydown', (event) => seen.push(event.key)) - - expect(pressKeyOnPage('a', 'KeyA', 65, false, false, false, false)).toMatchObject({ - pressed: 'a', - }) - expect(seen).toEqual(['a']) - }) }) describe('describePointTarget', () => { @@ -2334,20 +1102,6 @@ describe('describePointTarget', () => { }) } - it('describes the element at a viewport point', () => { - document.body.innerHTML = '' - pointAt(document.querySelector('button')) - - expect(describePointTarget(10, 10)).toMatchObject({ - found: true, - tag: 'button', - element: 'button "Send message"', - editable: false, - fileInput: false, - secret: false, - }) - }) - it('flags file inputs and password fields at the point', () => { document.body.innerHTML = '' pointAt(document.querySelector('input')) @@ -2361,78 +1115,14 @@ describe('describePointTarget', () => { editable: true, }) }) - - it('rejects points outside the viewport', () => { - expect(describePointTarget(-5, 10)).toEqual({ error: 'outside-viewport' }) - expect(describePointTarget(10, window.innerHeight + 5)).toEqual({ - error: 'outside-viewport', - }) - }) }) describe('describeFocusedEditable', () => { - it('reports no focus when the body holds focus', () => { - setActiveElement(document, document.body) - expect(describeFocusedEditable()).toEqual({ editable: false, reason: 'none' }) - }) - - // The bug this pins: focus inside a same-origin frame surfaces on the outer - // document as the FRAME element, which is not an input, not contentEditable, - // not a canvas, and carries no textbox role — so a composer that press-key - // typed into fine was reported `not-editable` and insert_text refused. The - // descent here must match activeElementReadback's exactly. - it('descends a same-origin frame to the editable that really holds focus', () => { - document.body.innerHTML = '' - const frame = document.createElement('iframe') - document.body.append(frame) - const inner = frame.contentDocument as Document - inner.body.innerHTML = '
composer
' - const composer = inner.querySelector('div') as HTMLElement - Object.defineProperty(composer, 'isContentEditable', { value: true, configurable: true }) - setActiveElement(inner, composer) - setActiveElement(document, frame) - - expect(describeFocusedEditable()).toEqual({ editable: true, kind: 'contenteditable' }) - }) - - it('names the focused element when it refuses, so the agent can recover', () => { - document.body.innerHTML = '
Send
' - setActiveElement(document, document.querySelector('div')) - - expect(describeFocusedEditable()).toEqual({ - editable: false, - reason: 'not-editable', - focusedTag: 'div', - focusedRole: 'button', - contentEditable: 'unset', - }) - }) - - it('reports a writable input as insertable', () => { - document.body.innerHTML = '' - setActiveElement(document, document.querySelector('input')) - expect(describeFocusedEditable()).toEqual({ editable: true, kind: 'input:text' }) - }) - it('reports a read-only input as not insertable', () => { document.body.innerHTML = '' setActiveElement(document, document.querySelector('input')) expect(describeFocusedEditable()).toEqual({ editable: false, reason: 'readonly' }) }) - - it('reports a focused contenteditable editor as insertable', () => { - document.body.innerHTML = '
' - const editor = document.querySelector('div') as HTMLElement - Object.defineProperty(editor, 'isContentEditable', { get: () => true }) - setActiveElement(document, editor) - expect(describeFocusedEditable()).toEqual({ editable: true, kind: 'contenteditable' }) - }) - - it('treats a focused canvas editor surface as insertable', () => { - document.body.innerHTML = '' - setActiveElement(document, document.querySelector('canvas')) - expect(describeFocusedEditable()).toEqual({ editable: true, kind: 'canvas' }) - }) }) describe('setFocusedInputValue', () => { @@ -2460,38 +1150,6 @@ describe('setFocusedInputValue', () => { }) } - it('accepts native datetime normalization and bypasses an overridden value setter', () => { - document.body.innerHTML = '' - const input = document.querySelector('input') as HTMLInputElement - register(input) - input.focus() - const setter = vi.fn() - Object.defineProperty(input, 'value', { - configurable: true, - get() { - return Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.get?.call(this) - }, - set: setter, - }) - expect(setFocusedInputValue(0, '2026-09-15T15:48:00')).toEqual({ dispatched: true }) - expect(input.value).toBe('2026-09-15T15:48') - expect(setter).not.toHaveBeenCalled() - }) - - it('does not write to a newly focused input inside a registered container', () => { - document.body.innerHTML = '
' - const container = visible(document.querySelector('div') as HTMLDivElement) - visible(document.querySelector('input') as HTMLInputElement) - register(container) - expect(focusElementForTyping(0)).toMatchObject({ valueInput: true }) - const other = document.createElement('input') - other.type = 'date' - container.append(other) - other.focus() - expect(setFocusedInputValue(0, '2026-09-15')).toHaveProperty('error') - expect(other.value).toBe('') - }) - it('rejects malformed values before changing the field or emitting events', () => { document.body.innerHTML = '' const input = document.querySelector('input') as HTMLInputElement @@ -2558,30 +1216,6 @@ describe('modal hidden together with its own app root', () => { ) }) - it('keeps a portaled modal scoped exactly as before', () => { - document.body.innerHTML = ` - -
` - showAll() - - const outline = outlineOf(collectSnapshot()) - - expect(outline).toContain('Send') - expect(outline).not.toContain('Compose') - }) - - it('keeps an aria-hidden region hidden when no modal is open', () => { - document.body.innerHTML = ` - - ` - showAll() - - const outline = outlineOf(collectSnapshot()) - - expect(outline).toContain('Shown action') - expect(outline).not.toContain('Hidden action') - }) - it('exposes only the topmost of stacked disablePortal modals', () => { document.body.innerHTML = ` - - ) - ) - - const homeControls = host?.querySelectorAll( - '[data-copy="home"] :is(button, input)' - ) - expect(homeControls).toHaveLength(2) - for (const control of homeControls ?? []) expect(control.tabIndex).toBe(0) - - const cloneControls = host?.querySelectorAll( - ':is([data-copy="lead"], [data-copy="tail"]) :is(button, input)' - ) - expect(cloneControls).toHaveLength(4) - for (const control of cloneControls ?? []) expect(control.tabIndex).toBe(-1) - - const cloneButton = host?.querySelector('[data-copy="tail"] button') - expect(cloneButton?.disabled).toBe(false) - act(() => cloneButton?.click()) - expect(activations).toBe(1) - }) - - it('drags with the mouse, scrolling by the pointer delta and swallowing the click', () => { - const rail = mount() - const link = rail.querySelector('[data-copy="home"] a') - if (!link) throw new Error('no home link') - /** jsdom has no PointerEvent; a MouseEvent carrying the pointer fields is what the handlers read. */ - const pointer = (type: string, clientX: number) => { - const event = new MouseEvent(type, { bubbles: true, button: 0, clientX }) - Object.defineProperties(event, { pointerType: { value: 'mouse' }, pointerId: { value: 1 } }) - return event - } - - rail.scrollLeft = SET - link.dispatchEvent(pointer('pointerdown', 300)) - link.dispatchEvent(pointer('pointermove', 303)) - expect(rail.scrollLeft).toBe(SET) - link.dispatchEvent(pointer('pointermove', 260)) - expect(rail.scrollLeft).toBe(SET + 40) - expect(rail.dataset.dragging).toBe('') - link.dispatchEvent(pointer('pointerup', 260)) - expect(rail.dataset.dragging).toBeUndefined() - - const click = new MouseEvent('click', { bubbles: true, cancelable: true, detail: 1 }) - link.dispatchEvent(click) - expect(click.defaultPrevented).toBe(true) - - let swallowed: boolean | null = null - link.addEventListener( - 'click', - (event) => { - swallowed = event.defaultPrevented - event.preventDefault() - }, - { once: true } - ) - link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })) - expect(swallowed).toBe(false) - }) - - it.each(['pointercancel', 'pointerup'])( - 'allows keyboard activation after a drag ends with %s', - (endEvent) => { - const rail = mount() - const link = rail.querySelector('[data-copy="home"] a') - if (!link) throw new Error('no home link') - for (const [type, clientX] of [ - ['pointerdown', 300], - ['pointermove', 260], - [endEvent, 260], - ] as const) { - const event = new MouseEvent(type, { bubbles: true, button: 0, clientX }) - Object.defineProperties(event, { pointerType: { value: 'mouse' }, pointerId: { value: 1 } }) - link.dispatchEvent(event) - } - expect(rail.dataset.dragging).toBeUndefined() - - let activated = false - link.addEventListener('click', (event) => { - activated = true - event.preventDefault() - }) - link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, detail: 0 })) - expect(activated).toBe(true) - } - ) - - it('folds the position back into the middle copy as the user scrolls past it', () => { - const rail = mount() - - expect(scrollTo(rail, SET + 200)).toBe(SET + 200) - expect(scrollTo(rail, SET * 1.5 + 100)).toBe(SET * 0.5 + 100) - expect(scrollTo(rail, SET * 0.5 - 60)).toBe(SET * 1.5 - 60) - }) -}) diff --git a/apps/sim/app/(landing)/components/features/features.test.tsx b/apps/sim/app/(landing)/components/features/features.test.tsx deleted file mode 100644 index 29d3ae24ad2..00000000000 --- a/apps/sim/app/(landing)/components/features/features.test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @vitest-environment node - */ -import { renderToStaticMarkup } from 'react-dom/server' -import { describe, expect, it } from 'vitest' -import { Features } from '@/app/(landing)/components/features/features' - -const MODULES = [ - { title: 'CLI', href: 'https://docs.sim.ai/cli' }, - { title: 'Workflows', href: '/workflows' }, - { title: 'Knowledge Base', href: '/knowledge' }, - { title: 'Tables', href: '/tables' }, - { title: 'Files', href: '/files' }, - { title: 'Logs', href: '/logs' }, -] as const - -describe('Features', () => { - it('renders every core Sim module as a crawlable tall card', () => { - const html = renderToStaticMarkup() - - expect(html).toContain('Everything AI agents need to do real work') - expect(html).toContain('aspect-[5/6]') - expect(html).toContain('rounded-[12px]') - expect(html).toContain('overflow-x-auto') - - for (const module of MODULES) { - expect(html).toContain(`>${module.title}`) - expect(html).toContain(`href="${module.href}"`) - } - }) -}) diff --git a/apps/sim/app/(landing)/components/footer/components/footer-wordmark-loop/footer-wordmark-loop.test.tsx b/apps/sim/app/(landing)/components/footer/components/footer-wordmark-loop/footer-wordmark-loop.test.tsx deleted file mode 100644 index e54d3302f51..00000000000 --- a/apps/sim/app/(landing)/components/footer/components/footer-wordmark-loop/footer-wordmark-loop.test.tsx +++ /dev/null @@ -1,183 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { act } from 'react' -import { WORDMARK_PATHS } from '@sim/emcn' -import { createRoot, type Root } from 'react-dom/client' -import { renderToStaticMarkup } from 'react-dom/server' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { FooterWordmarkLoop } from '@/app/(landing)/components/footer/components/footer-wordmark-loop/footer-wordmark-loop' - -const SHAPES = ['metaballs', 'relay', 'compass', 'corners', 'burst', 'squeeze', 'thinking'] as const - -/** One full pass of the master film, in ms. */ -const CYCLE_MS = 17_100 - -let pending: FrameRequestCallback[] = [] -let clock = 0 -let reducedMotion = false -let onMotionPreference: (() => void) | undefined -let root: Root | null = null -let host: HTMLDivElement | null = null - -/** - * Drives the captured frame callbacks to `ms` on the loop's own clock in 50ms - * steps - under the loop's 100ms per-frame cap, so no choreography is skipped. - */ -function advanceTo(ms: number): void { - while (clock < ms) { - clock = Math.min(clock + 50, ms) - const frame = pending.shift() - if (!frame) throw new Error('the loop stopped requesting frames') - const now = clock - act(() => frame(now)) - } -} - -function attr(selector: string, name: string): string | null { - return host?.querySelector(selector)?.getAttribute(name) ?? null -} - -beforeEach(() => { - ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true - pending = [] - clock = 0 - reducedMotion = false - onMotionPreference = undefined - const stubs = { - requestAnimationFrame: (cb: FrameRequestCallback) => pending.push(cb), - cancelAnimationFrame: () => { - pending = [] - }, - matchMedia: () => ({ - get matches() { - return reducedMotion - }, - addEventListener: (_type: string, listener: () => void) => { - onMotionPreference = listener - }, - removeEventListener: () => { - onMotionPreference = undefined - }, - }), - } - for (const [name, value] of Object.entries(stubs)) { - vi.stubGlobal(name, value) - Object.assign(window, { [name]: value }) - } - host = document.createElement('div') - document.body.append(host) - root = createRoot(host) - act(() => root?.render()) - const anchor = pending.shift() - if (!anchor) throw new Error('the loop never started') - act(() => anchor(0)) -}) - -afterEach(() => { - act(() => root?.unmount()) - root = null - host?.remove() - host = null - vi.unstubAllGlobals() -}) - -describe('FooterWordmarkLoop', () => { - it('server-renders the crisp wordmark as the resting frame', () => { - const html = renderToStaticMarkup() - - expect(html).toContain('aria-hidden="true"') - expect(html).toContain('data-stage="wm" opacity="1"') - expect(html).toContain('data-stage="orb" opacity="0"') - expect(html).toContain('stdDeviation="0"') - expect(html).toMatch(/filter="url\(#fwl-goo-/) - expect(html).toContain('values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"') - expect(html).not.toContain(' { - expect(attr('[data-stage="wm"]', 'opacity')).toBe('1.0000') - expect(attr('[data-goo]', 'stdDeviation')).toBe('0.000') - expect(attr('[data-goo-matrix]', 'values')).toMatch(/1\.000 -?0\.000$/) - - advanceTo(2700) - expect(attr('[data-stage="wm"]', 'opacity')).toBe('0.0000') - expect(attr('[data-stage="orb"]', 'opacity')).toBe('1.0000') - expect(attr('[data-goo]', 'stdDeviation')).toBe('5.000') - expect(attr('[data-goo-matrix]', 'values')).toMatch(/40\.000 -19\.000$/) - - advanceTo(3900) - expect(attr('[data-stage="metaballs"]', 'opacity')).toBe('1.0000') - expect(attr('[data-stage="orb"]', 'opacity')).toBe('0.0000') - expect(attr('[data-anim="metaballsA"]', 'transform')).not.toBe('translate(0.000 0.000)') - - advanceTo(7000) - expect(attr('[data-stage="compass"]', 'opacity')).toBe('1.0000') - expect(attr('[data-stage="metaballs"]', 'opacity')).toBe('0.0000') - - advanceTo(9700) - expect(attr('[data-stage="burst"]', 'opacity')).toBe('1.0000') - - advanceTo(16000) - expect(attr('[data-stage="wm"]', 'opacity')).toBe('1.0000') - expect(attr('[data-stage="thinking"]', 'opacity')).toBe('0.0000') - expect(attr('[data-goo]', 'stdDeviation')).toBe('0.000') - expect(attr('[data-goo-matrix]', 'values')).toMatch(/1\.000 -?0\.000$/) - - advanceTo(CYCLE_MS + 2700) - expect(attr('[data-stage="orb"]', 'opacity')).toBe('1.0000') - expect(attr('[data-stage="wm"]', 'opacity')).toBe('0.0000') - }) - - it('returns to an identity filter when reduced motion is enabled mid-morph', () => { - advanceTo(2700) - expect(attr('[data-goo-matrix]', 'values')).toMatch(/40\.000 -19\.000$/) - - reducedMotion = true - act(() => onMotionPreference?.()) - - expect(pending).toHaveLength(0) - expect(attr('[data-stage="wm"]', 'opacity')).toBe('1.0000') - expect(attr('[data-stage="orb"]', 'opacity')).toBe('0.0000') - expect(attr('[data-goo-matrix]', 'values')).toMatch(/1\.000 -?0\.000$/) - expect(attr('[data-goo]', 'stdDeviation')).toBe('0.000') - }) - - it('eases the same filter to identity at both wordmark boundaries', () => { - advanceTo(1300) - expect(attr('[data-goo]', 'stdDeviation')).toBe('0.000') - expect(attr('[data-goo-matrix]', 'values')).toMatch(/1\.000 -?0\.000$/) - - advanceTo(1301) - expect(Number(attr('[data-goo]', 'stdDeviation'))).toBeLessThan(0.001) - expect(attr('[data-goo-matrix]', 'values')).toMatch(/1\.000 -?0\.000$/) - - advanceTo(1800) - expect(attr('[data-goo-matrix]', 'values')).toMatch(/40\.000 -19\.000$/) - - advanceTo(2500) - expect(attr('[data-goo]', 'stdDeviation')).toBe('5.000') - - advanceTo(15199) - expect(Number(attr('[data-goo]', 'stdDeviation'))).toBeLessThan(0.001) - expect(attr('[data-goo-matrix]', 'values')).toMatch(/1\.000 -?0\.000$/) - - advanceTo(15200) - expect(attr('[data-goo]', 'stdDeviation')).toBe('0.000') - expect(attr('[data-goo-matrix]', 'values')).toMatch(/1\.000 -?0\.000$/) - expect(host?.querySelector('feComposite')).toBeNull() - }) - - it('stops requesting frames on unmount', () => { - advanceTo(500) - act(() => root?.unmount()) - root = null - expect(pending).toHaveLength(0) - }) -}) diff --git a/apps/sim/app/(landing)/components/hero/components/hero-announcement-chip/hero-announcement-chip.test.tsx b/apps/sim/app/(landing)/components/hero/components/hero-announcement-chip/hero-announcement-chip.test.tsx deleted file mode 100644 index 87f01b75722..00000000000 --- a/apps/sim/app/(landing)/components/hero/components/hero-announcement-chip/hero-announcement-chip.test.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @vitest-environment node - */ -import type { ComponentType, ReactNode } from 'react' -import { renderToStaticMarkup } from 'react-dom/server' -import { describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/emcn', () => ({ - cn: (...values: Array) => values.filter(Boolean).join(' '), - ChipLink: ({ - children, - href, - leftAdornment, - rightIcon: RightIcon, - variant, - }: { - children: ReactNode - href: string - leftAdornment?: ReactNode - rightIcon?: ComponentType - variant?: string - }) => ( - - {leftAdornment} - {children} - {RightIcon ? : null} - - ), - ChipTag: ({ children, variant }: { children: ReactNode; variant?: string }) => ( - {children} - ), -})) - -vi.mock('@/app/(landing)/components/chevron-arrow', () => ({ - ChevronArrow: () => , -})) - -import { HeroAnnouncementChip } from '@/app/(landing)/components/hero/components/hero-announcement-chip/hero-announcement-chip' - -describe('HeroAnnouncementChip', () => { - it('announces GPT-6 Astra and links to Start building', () => { - const markup = renderToStaticMarkup() - - expect(markup).toContain('data-variant="outline"') - expect(markup).toContain('data-tag-variant="gray"') - expect(markup).toContain('href="/signup"') - expect(markup).toContain('New') - expect(markup).toContain('Use GPT-6 Astra') - expect(markup).toContain('Now available') - expect(markup).toContain('data-chevron-arrow') - }) -}) diff --git a/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.test.tsx b/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.test.tsx deleted file mode 100644 index a4c13692555..00000000000 --- a/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.test.tsx +++ /dev/null @@ -1,216 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { act, type ReactNode } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { - HeroChatLoop, - type HeroChatPhase, -} from '@/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop' -import type { AgentGroupItem } from '@/app/workspace/[workspaceId]/home/components/message-content/components' -import { ToolCallStatus } from '@/app/workspace/[workspaceId]/home/types' - -interface AgentGroupProps { - agentName: string - agentLabel: string - items: AgentGroupItem[] - isStreaming?: boolean - isLaneOpen?: boolean -} - -const { renderAgentGroup, renderPendingIndicator } = vi.hoisted(() => ({ - renderAgentGroup: vi.fn<(props: AgentGroupProps) => void>(), - renderPendingIndicator: vi.fn<(props: { label: string }) => void>(), -})) - -vi.mock('@sim/emcn', () => ({ - cn: (...values: Array) => values.filter(Boolean).join(' '), - Button: ({ children }: { children: ReactNode }) => , - Tooltip: { - Root: ({ children }: { children: ReactNode }) => children, - Trigger: ({ children }: { children: ReactNode }) => children, - Content: () => null, - }, -})) -vi.mock('@sim/emcn/icons', () => ({ - Workflow: () => null, - Mic: () => null, - Paperclip: () => null, - Plus: () => null, - Slash: () => null, - X: () => null, -})) -vi.mock('@/app/(landing)/components/hero/components/hero-chat-welcome', () => ({ - HeroChatWelcome: () => null, -})) -vi.mock('@/app/(landing)/components/hero/components/hero-platform-loop/sidebar-hotspots', () => ({ - HERO_TOOLTIP_OFFSET: 8, -})) -vi.mock( - '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/agent-group-view', - () => ({ - AgentGroupView: (props: AgentGroupProps) => { - renderAgentGroup(props) - return
{props.agentLabel}
- }, - }) -) -vi.mock('@/app/(landing)/components/hero/components/hero-chat-loop/hero-tool-call-item', () => ({ - HeroToolCallItem: () => null, -})) -vi.mock( - '@/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/pending-tag-indicator', - () => ({ - PendingTagIndicator: (props: { label: string }) => { - renderPendingIndicator(props) - return {props.label} - }, - }) -) -vi.mock( - '@/app/workspace/[workspaceId]/home/components/message-content/components/question', - () => ({ - parseQuestionAnswerMessage: () => undefined, - QuestionDisplay: () =>

What would you like to do next?

, - }) -) -vi.mock( - '@/app/workspace/[workspaceId]/home/components/user-input/components/send-button/send-button', - () => ({ SendButton: () => null }) -) - -let host: HTMLDivElement -let root: Root - -beforeEach(() => { - ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true - vi.useFakeTimers() - vi.clearAllMocks() - vi.stubGlobal('matchMedia', () => ({ - matches: true, - addEventListener: () => {}, - removeEventListener: () => {}, - })) - host = document.createElement('div') - document.body.append(host) - root = createRoot(host) -}) - -afterEach(() => { - act(() => root.unmount()) - host.remove() - vi.useRealTimers() - vi.unstubAllGlobals() -}) - -function renderPhase(phase: HeroChatPhase) { - act(() => { - root.render( - - ) - }) -} - -function hasExecutingTool(items: AgentGroupItem[]): boolean { - return items.some((item) => item.type === 'tool' && item.data.status === ToolCallStatus.executing) -} - -describe('HeroChatLoop production thinking handoff', () => { - it('shows the submitted prompt before assistant activity begins', () => { - renderPhase('user') - - expect(host.textContent).toContain('Enrich new leads and post to #sales.') - expect(host.querySelector('output')).toBeNull() - expect(renderAgentGroup).not.toHaveBeenCalled() - expect(renderPendingIndicator).not.toHaveBeenCalled() - }) - - it.each([ - ['thinking', 'Thinking'], - ['dispatching', 'Dispatching…'], - ] as const)('shows the production activity indicator alone during %s', (phase, label) => { - renderPhase(phase) - - expect(host.querySelector('[data-chat-phase]')?.getAttribute('data-chat-phase')).toBe(phase) - expect(host.textContent).toContain('Enrich new leads and post to #sales.') - expect(host.querySelector('output')?.textContent).toBe(label) - expect(renderPendingIndicator).toHaveBeenLastCalledWith({ label }) - expect(renderAgentGroup).not.toHaveBeenCalled() - expect(host.textContent).not.toContain('The workflow is ready') - }) - - it('keeps the activity indicator mounted while thinking hands off to dispatching', () => { - renderPhase('thinking') - const indicator = host.querySelector('output') - expect(indicator).not.toBeNull() - - renderPhase('dispatching') - - expect(host.querySelector('output')).toBe(indicator) - expect(indicator?.textContent).toBe('Dispatching…') - expect(renderAgentGroup).not.toHaveBeenCalled() - }) - - it('replaces the turn loader with Workflow Agent activity only once building begins', () => { - renderPhase('thinking') - renderPhase('dispatching') - renderPhase('building') - - expect(host.querySelector('output')).toBeNull() - expect(host.querySelectorAll('section')).toHaveLength(1) - const props = renderAgentGroup.mock.lastCall?.[0] - expect(props).toMatchObject({ - agentName: 'workflow', - agentLabel: 'Workflow Agent', - isStreaming: true, - isLaneOpen: true, - }) - expect(props && hasExecutingTool(props.items)).toBe(true) - expect(host.textContent).not.toContain('The workflow is ready') - }) - - it('shows completed agent groups and the response after building finishes', () => { - renderPhase('reply') - - expect(host.querySelector('output')).toBeNull() - expect(Array.from(host.querySelectorAll('section'), (group) => group.textContent)).toEqual([ - 'Workflow Agent', - 'Sim', - ]) - const groups = renderAgentGroup.mock.calls.map(([props]) => props) - expect(groups.every((group) => !group.isStreaming && !hasExecutingTool(group.items))).toBe(true) - expect(host.textContent).toContain('The workflow is ready to test with a sample lead.') - expect(host.textContent).toContain('What would you like to do next?') - }) - it('reveals the reply before showing follow-up actions when motion is enabled', () => { - vi.stubGlobal('matchMedia', () => ({ - matches: false, - addEventListener: () => {}, - removeEventListener: () => {}, - })) - renderPhase('reply') - expect(host.textContent).not.toContain('The workflow is ready') - expect(host.textContent).not.toContain('What would you like to do next?') - - act(() => vi.advanceTimersByTime(110)) - expect(host.textContent).toContain('The workflow') - expect(host.textContent).not.toContain('The workflow is ready to test with a sample lead.') - - act(() => vi.advanceTimersByTime(1000)) - expect(host.textContent).toContain('The workflow is ready to test with a sample lead.') - expect(host.textContent).toContain('What would you like to do next?') - }) -}) diff --git a/apps/sim/app/(landing)/components/hero/components/hero-platform-intro/hero-platform-intro.test.tsx b/apps/sim/app/(landing)/components/hero/components/hero-platform-intro/hero-platform-intro.test.tsx deleted file mode 100644 index b3709462f40..00000000000 --- a/apps/sim/app/(landing)/components/hero/components/hero-platform-intro/hero-platform-intro.test.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { act } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { HeroPlatformIntro } from '@/app/(landing)/components/hero/components/hero-platform-intro/hero-platform-intro' - -let root: Root -let host: HTMLDivElement -let enterViewport: () => void -let motionChange: () => void -let reducedMotion: boolean -const onComplete = vi.fn() -const onClick = vi.fn() -const disconnect = vi.fn() -const removeMotionListener = vi.fn() - -beforeEach(() => { - ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true - reducedMotion = false - vi.clearAllMocks() - vi.stubGlobal('matchMedia', () => ({ - get matches() { - return reducedMotion - }, - addEventListener: (_event: string, callback: () => void) => { - motionChange = callback - }, - removeEventListener: removeMotionListener, - })) - vi.stubGlobal( - 'IntersectionObserver', - class { - constructor(callback: IntersectionObserverCallback) { - enterViewport = () => - callback( - [{ isIntersecting: true } as IntersectionObserverEntry], - this as IntersectionObserver - ) - } - observe = vi.fn() - disconnect = disconnect - } - ) - host = document.createElement('div') - document.body.append(host) - root = createRoot(host) -}) - -afterEach(() => { - act(() => root.unmount()) - host.remove() - vi.restoreAllMocks() - vi.unstubAllGlobals() -}) - -function mount() { - act(() => { - root.render( - - - - ) - }) -} - -describe('HeroPlatformIntro', () => { - it('exposes usable content immediately and starts the exchange only once visible', () => { - mount() - expect(host.querySelector('[inert], [aria-hidden="true"], .opacity-0, svg')).toBeNull() - act(() => host.querySelector('button')?.click()) - expect(onClick).toHaveBeenCalledOnce() - expect(onComplete).not.toHaveBeenCalled() - - act(() => enterViewport()) - expect(onComplete).toHaveBeenCalledExactlyOnceWith(false) - expect(disconnect).toHaveBeenCalled() - act(() => enterViewport()) - expect(onComplete).toHaveBeenCalledOnce() - }) - - it.each(['pointerdown', 'focusin'])('preserves visitor control after early %s', (eventName) => { - mount() - act(() => host.querySelector('button')?.dispatchEvent(new Event(eventName, { bubbles: true }))) - expect(disconnect).toHaveBeenCalled() - act(() => enterViewport()) - act(() => { - reducedMotion = true - motionChange() - }) - expect(onComplete).not.toHaveBeenCalled() - }) - - it('does not wait for the painting to decode', () => { - const stage = document.createElement('div') - stage.dataset.previewStage = '' - host.before(stage) - stage.append(host) - const background = document.createElement('img') - background.dataset.previewBackground = '' - Object.defineProperty(background, 'complete', { value: false }) - background.decode = vi.fn() - stage.append(background) - - mount() - act(() => enterViewport()) - expect(background.decode).not.toHaveBeenCalled() - expect(onComplete).toHaveBeenCalledExactlyOnceWith(false) - stage.before(host) - stage.remove() - }) - - it('shows the completed preview immediately with reduced motion', () => { - reducedMotion = true - mount() - expect(onComplete).toHaveBeenCalledExactlyOnceWith(true) - }) - - it('settles when reduced motion is enabled before the preview enters view', () => { - mount() - act(() => { - reducedMotion = true - motionChange() - }) - act(() => enterViewport()) - expect(onComplete).toHaveBeenCalledExactlyOnceWith(true) - }) - - it('disconnects on unmount without starting the exchange from a queued callback', () => { - mount() - act(() => root.unmount()) - act(() => enterViewport()) - expect(onComplete).not.toHaveBeenCalled() - expect(disconnect).toHaveBeenCalled() - expect(removeMotionListener).toHaveBeenCalledWith('change', motionChange) - }) - - it('starts without waiting when IntersectionObserver is unavailable', () => { - vi.stubGlobal('IntersectionObserver', undefined) - mount() - expect(onComplete).toHaveBeenCalledExactlyOnceWith(false) - }) -}) diff --git a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop-mount.test.tsx b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop-mount.test.tsx deleted file mode 100644 index a3148ada9fa..00000000000 --- a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop-mount.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { act } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { renderToStaticMarkup } from 'react-dom/server' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { HeroPlatformLoopMount } from '@/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop-mount' - -vi.mock('next/dynamic', () => ({ - default: () => () =>
Desktop demo
, -})) - -let host: HTMLDivElement -let root: Root -let desktopMedia: EventTarget & { matches: boolean } - -beforeEach(() => { - vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) - desktopMedia = Object.assign(new EventTarget(), { matches: false }) - vi.stubGlobal( - 'matchMedia', - vi.fn(() => desktopMedia) - ) - host = document.createElement('div') - document.body.append(host) - root = createRoot(host) -}) - -afterEach(() => { - act(() => root.unmount()) - host.remove() - vi.unstubAllGlobals() -}) - -describe('HeroPlatformLoopMount', () => { - it('leaves the desktop demo unmounted on mobile and responds to breakpoint changes', () => { - act(() => root.render()) - expect(host.textContent).toBe('') - - act(() => { - desktopMedia.matches = true - desktopMedia.dispatchEvent(new Event('change')) - }) - expect(host.textContent).toBe('Desktop demo') - - act(() => { - desktopMedia.matches = false - desktopMedia.dispatchEvent(new Event('change')) - }) - expect(host.textContent).toBe('') - }) - - it('keeps the server render empty even when the client viewport is desktop', () => { - desktopMedia.matches = true - expect(renderToStaticMarkup()).toBe('') - expect(window.matchMedia).not.toHaveBeenCalled() - }) -}) diff --git a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.test.tsx b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.test.tsx deleted file mode 100644 index 64c296187a5..00000000000 --- a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.test.tsx +++ /dev/null @@ -1,324 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { act, type ReactNode } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { HeroPlatformLoop } from '@/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop' -import { DEFAULT_USER_MESSAGE } from '@/app/(landing)/components/hero/components/hero-platform-loop/preview-chat-content' - -interface ChatProps { - phase: string - composerValue: string - userMessage: string - showWelcome: boolean - isSending: boolean - onStopGeneration: () => void - onComposerFocus: () => void - onComposerValueChange: (value: string) => void - onSubmit: () => void -} - -let chat: ChatProps -let completeIntro: (reducedMotion: boolean) => void -let builtCount: number -let runWorkflow: () => void - -vi.mock('@sim/emcn', () => ({ - cn: (...values: Array) => values.filter(Boolean).join(' '), - Button: ({ children }: { children: ReactNode }) => , - Tooltip: { - Root: ({ children }: { children: ReactNode }) => children, - Trigger: ({ children }: { children: ReactNode }) => children, - Content: () => null, - }, -})) -vi.mock('@sim/emcn/icons', () => ({ PanelLeft: () => null })) -vi.mock('@/hooks/use-drag-resize', () => ({ - useDragResize: () => ({ handlePointerDown: vi.fn() }), -})) -vi.mock('@/app/(landing)/components/shared/hero-loop-shell', () => ({ - HeroLoopShell: ({ children }: { children: ReactNode }) => children, -})) -vi.mock('@/app/(landing)/components/hero/components/hero-platform-intro', () => ({ - HeroPlatformIntro: ({ - children, - onComplete, - }: { - children: ReactNode - onComplete: typeof completeIntro - }) => { - completeIntro = onComplete - return children - }, -})) -vi.mock('@/app/(landing)/components/hero/components/hero-chat-loop', () => ({ - HeroChatLoop: (props: ChatProps) => { - chat = props - return null - }, -})) -vi.mock( - '@/app/(landing)/components/hero/components/hero-platform-loop/hero-resource-panel', - () => ({ - HeroResourcePanel: (props: { builtCount: number; onRunWorkflow: () => void }) => { - builtCount = props.builtCount - runWorkflow = props.onRunWorkflow - return null - }, - }) -) -vi.mock('@/app/(landing)/components/hero/components/hero-platform-loop/stage-data', () => ({ - STAGE_BLOCKS: Array.from({ length: 6 }, (_, id) => ({ id })), -})) - -let root: Root -let host: HTMLDivElement -let reducedMotion: boolean -let motionListeners: Set<() => void> - -beforeEach(() => { - ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true - vi.useFakeTimers({ - toFake: ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'performance'], - }) - reducedMotion = false - motionListeners = new Set() - vi.stubGlobal('matchMedia', () => ({ - get matches() { - return reducedMotion - }, - addEventListener: (_event: string, callback: () => void) => motionListeners.add(callback), - removeEventListener: (_event: string, callback: () => void) => motionListeners.delete(callback), - })) - host = document.createElement('div') - document.body.append(host) - root = createRoot(host) - act(() => root.render()) -}) - -afterEach(() => { - act(() => root.unmount()) - host.remove() - vi.useRealTimers() - vi.unstubAllGlobals() -}) - -function advance(ms: number) { - act(() => vi.advanceTimersByTime(ms)) -} - -function panel() { - return host.querySelector('[data-resource-open]') -} - -describe('HeroPlatformLoop opening', () => { - it('thinks and dispatches before showing agent activity, opening the workflow, and building it', () => { - expect(chat.phase).toBe('idle') - expect(chat.showWelcome).toBe(true) - expect(panel()?.getAttribute('data-resource-open')).toBe('false') - expect(panel()?.hasAttribute('inert')).toBe(true) - advance(10_000) - expect(chat.composerValue).toBe('') - - act(() => completeIntro(false)) - advance(1_000) - expect(chat.phase).toBe('compose') - expect(chat.composerValue.length).toBeGreaterThan(0) - expect(DEFAULT_USER_MESSAGE.startsWith(chat.composerValue)).toBe(true) - expect(panel()?.getAttribute('data-resource-open')).toBe('false') - - advance(3_000) - expect(chat.phase).toBe('user') - expect(chat.composerValue).toBe('') - expect(chat.userMessage).toBe(DEFAULT_USER_MESSAGE) - advance(319) - expect(chat.phase).toBe('user') - advance(1) - expect(chat.phase).toBe('thinking') - expect(chat.isSending).toBe(true) - advance(3_549) - expect(chat.phase).toBe('thinking') - expect(builtCount).toBe(0) - expect(panel()?.getAttribute('data-resource-open')).toBe('false') - advance(1) - expect(chat.phase).toBe('dispatching') - advance(650) - expect(chat.phase).toBe('building') - expect(chat.isSending).toBe(true) - expect(panel()?.getAttribute('data-resource-open')).toBe('false') - advance(600) - expect(panel()?.getAttribute('data-resource-open')).toBe('true') - expect(panel()?.hasAttribute('inert')).toBe(false) - expect(builtCount).toBe(0) - advance(550) - expect(builtCount).toBe(1) - advance(4_000) - expect(builtCount).toBe(6) - expect(chat.phase).toBe('reply') - expect(chat.isSending).toBe(false) - }) - - it('lets a visitor take over typing and submit their own prompt', () => { - act(() => completeIntro(false)) - advance(1_000) - act(() => chat.onComposerFocus()) - const pausedPrompt = chat.composerValue - advance(10_000) - expect(chat.phase).toBe('idle') - expect(chat.composerValue).toBe(pausedPrompt) - - act(() => chat.onComposerValueChange('Build a pipeline report')) - advance(10_000) - expect(chat.composerValue).toBe('Build a pipeline report') - expect(panel()?.getAttribute('data-resource-open')).toBe('false') - act(() => chat.onSubmit()) - expect(chat.phase).toBe('user') - expect(chat.userMessage).toBe('Build a pipeline report') - advance(1_200) - expect(chat.phase).toBe('thinking') - expect(panel()?.getAttribute('data-resource-open')).toBe('false') - advance(3_920) - expect(panel()?.getAttribute('data-resource-open')).toBe('true') - }) - - it('cancels automatic typing when a suggested prompt replaces the composer value', () => { - act(() => completeIntro(false)) - advance(1_000) - act(() => chat.onComposerValueChange('Post deal alerts to #sales')) - advance(10_000) - expect(chat.phase).toBe('idle') - expect(chat.composerValue).toBe('Post deal alerts to #sales') - expect(panel()?.getAttribute('data-resource-open')).toBe('false') - }) - - it('shows the finished preview without typing when the entrance reports reduced motion', () => { - act(() => completeIntro(true)) - expect(chat.phase).toBe('reply') - expect(chat.composerValue).toBe('') - expect(builtCount).toBe(6) - expect(panel()?.getAttribute('data-resource-open')).toBe('true') - expect(vi.getTimerCount()).toBe(0) - }) - - it('settles the preview when reduced motion is enabled during typing', () => { - act(() => completeIntro(false)) - advance(1_000) - act(() => { - reducedMotion = true - motionListeners.forEach((listener) => listener()) - }) - expect(chat.phase).toBe('reply') - expect(chat.composerValue).toBe('') - expect(builtCount).toBe(6) - expect(panel()?.getAttribute('data-resource-open')).toBe('true') - expect(vi.getTimerCount()).toBe(0) - expect(motionListeners.size).toBe(0) - }) - - it('cancels the pending thinking and build sequence when generation is stopped', () => { - act(() => chat.onComposerValueChange('Build a pipeline report')) - act(() => chat.onSubmit()) - advance(1_000) - expect(chat.phase).toBe('thinking') - act(() => chat.onStopGeneration()) - expect(chat.isSending).toBe(false) - expect(vi.getTimerCount()).toBe(0) - advance(10_000) - expect(chat.phase).toBe('reply') - expect(builtCount).toBe(6) - }) - - it('restarts thinking for a new submission without an old timer opening the workflow', () => { - act(() => chat.onComposerValueChange('Build a report')) - act(() => chat.onSubmit()) - advance(1_000) - act(() => chat.onComposerValueChange('Enrich new leads')) - act(() => chat.onSubmit()) - advance(3_000) - expect(chat.userMessage).toBe('Enrich new leads') - expect(chat.phase).toBe('thinking') - expect(builtCount).toBe(0) - expect(panel()?.getAttribute('data-resource-open')).toBe('false') - advance(1_520) - expect(chat.phase).toBe('building') - }) - - it('settles and clears delayed work when reduced motion is enabled during thinking', () => { - act(() => chat.onComposerValueChange('Build a report')) - act(() => chat.onSubmit()) - advance(1_000) - act(() => { - reducedMotion = true - motionListeners.forEach((listener) => listener()) - }) - expect(chat.phase).toBe('reply') - expect(chat.isSending).toBe(false) - expect(builtCount).toBe(6) - expect(panel()?.getAttribute('data-resource-open')).toBe('true') - expect(vi.getTimerCount()).toBe(0) - }) - - it('cleans up automatic typing and motion listeners on unmount', () => { - act(() => completeIntro(false)) - advance(1_000) - act(() => root.unmount()) - expect(vi.getTimerCount()).toBe(0) - expect(motionListeners.size).toBe(0) - }) - - it('cancels a pending rerun before thinking through a new submission', () => { - act(() => completeIntro(true)) - act(() => runWorkflow()) - advance(400) - expect(builtCount).toBe(2) - act(() => chat.onComposerValueChange('Build a new report')) - act(() => chat.onSubmit()) - advance(2_000) - expect(chat.phase).toBe('thinking') - expect(builtCount).toBe(0) - expect(panel()?.getAttribute('data-resource-open')).toBe('false') - }) - - it('cancels pending chat build timers when the visitor runs the workflow', () => { - act(() => chat.onComposerValueChange('Build a report')) - act(() => chat.onSubmit()) - advance(1_000) - act(() => runWorkflow()) - advance(5_000) - expect(chat.phase).toBe('reply') - expect(chat.isSending).toBe(false) - expect(builtCount).toBe(6) - }) - - it('cancels a pending rerun when generation is stopped', () => { - act(() => completeIntro(true)) - act(() => runWorkflow()) - advance(400) - act(() => chat.onStopGeneration()) - expect(vi.getTimerCount()).toBe(0) - advance(1_000) - expect(builtCount).toBe(6) - }) - - it('settles reruns immediately when motion is reduced', () => { - reducedMotion = true - act(() => completeIntro(true)) - act(() => runWorkflow()) - expect(builtCount).toBe(6) - expect(vi.getTimerCount()).toBe(0) - }) - - it('settles an active rerun when reduced motion is enabled', () => { - act(() => completeIntro(true)) - act(() => runWorkflow()) - advance(400) - expect(builtCount).toBe(2) - act(() => { - reducedMotion = true - motionListeners.forEach((listener) => listener()) - }) - expect(builtCount).toBe(6) - expect(vi.getTimerCount()).toBe(0) - }) -}) diff --git a/apps/sim/app/(landing)/components/hero/components/hero-platform-stage/hero-platform-stage.test.tsx b/apps/sim/app/(landing)/components/hero/components/hero-platform-stage/hero-platform-stage.test.tsx deleted file mode 100644 index 11e5eb66f3f..00000000000 --- a/apps/sim/app/(landing)/components/hero/components/hero-platform-stage/hero-platform-stage.test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/** @vitest-environment node */ -import { renderToStaticMarkup } from 'react-dom/server' -import { describe, expect, it, vi } from 'vitest' -import { HERO_ARTWORK } from '@/app/(landing)/components/hero/components/hero-platform-stage/hero-artwork.generated' -import { HeroPlatformStage } from '@/app/(landing)/components/hero/components/hero-platform-stage/hero-platform-stage' - -vi.mock('@sim/emcn', async (importOriginal) => ({ - ...(await importOriginal()), - cn: (...values: string[]) => values.join(' '), -})) -vi.mock('@/app/(landing)/components/hero/components/hero-platform-loop', () => ({ - HeroPlatformLoopMount: () => null, -})) -vi.mock( - '@/app/(landing)/components/hero/components/hero-platform-stage/mobile-hero-workflow', - () => ({ - MobileHeroWorkflow: () => null, - }) -) - -describe('HeroPlatformStage loading', () => { - it('preloads only the desktop AVIF selection and keeps the hidden image lazy on mobile', () => { - const html = renderToStaticMarkup() - const preloads = html.match(/]*rel="preload"[^>]*>/g) ?? [] - expect(preloads).toHaveLength(1) - expect(preloads[0]).toContain('media="(min-width: 1024px)"') - expect(preloads[0]).toContain('type="image/avif"') - expect(preloads[0]).toContain(`imageSrcSet="${HERO_ARTWORK.avifSrcSet}"`) - expect(html).toContain(`srcSet="${HERO_ARTWORK.avifSrcSet}"`) - expect(html).toContain(`srcSet="${HERO_ARTWORK.webpSrcSet}"`) - expect(html).toContain('loading="lazy"') - expect(html).not.toContain('/_next/image') - }) -}) diff --git a/apps/sim/app/(landing)/components/landing-cta-link/landing-cta-link.test.tsx b/apps/sim/app/(landing)/components/landing-cta-link/landing-cta-link.test.tsx index 65c866c8a59..726d56cba28 100644 --- a/apps/sim/app/(landing)/components/landing-cta-link/landing-cta-link.test.tsx +++ b/apps/sim/app/(landing)/components/landing-cta-link/landing-cta-link.test.tsx @@ -15,7 +15,6 @@ let root: Root let host: HTMLDivElement beforeEach(() => { - vi.clearAllMocks() vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) host = document.createElement('div') document.body.append(host) @@ -47,34 +46,4 @@ describe('LandingCtaLink', () => { expect(internal.hasAttribute('target')).toBe(false) expect(internal.hasAttribute('rel')).toBe(false) }) - - it('reports a tracked click with the href as its destination', () => { - const onClick = vi.fn((event: React.MouseEvent) => event.preventDefault()) - const anchor = renderLink( - - Start building - - ) - act(() => anchor.click()) - expect(onClick).toHaveBeenCalledOnce() - expect(mockCaptureClientEvent).toHaveBeenCalledExactlyOnceWith('landing_cta_clicked', { - label: 'Start building', - section: 'footer_cta', - destination: '/signup', - }) - }) - - it('stays silent without a tracking request', () => { - const anchor = renderLink( - event.preventDefault()}> - Sign up - - ) - act(() => anchor.click()) - expect(mockCaptureClientEvent).not.toHaveBeenCalled() - }) }) diff --git a/apps/sim/app/(landing)/components/landing-shell/landing-shell.test.tsx b/apps/sim/app/(landing)/components/landing-shell/landing-shell.test.tsx deleted file mode 100644 index dba693aa8e2..00000000000 --- a/apps/sim/app/(landing)/components/landing-shell/landing-shell.test.tsx +++ /dev/null @@ -1,219 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { readdirSync, readFileSync } from 'node:fs' -import { dirname, join, resolve } from 'node:path' -import { fileURLToPath } from 'node:url' -import { type AnchorHTMLAttributes, act, type ReactNode } from 'react' -import ts from '@typescript/typescript6' -import { createRoot, type Root } from 'react-dom/client' -import { renderToStaticMarkup } from 'react-dom/server' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { FOOTER_ARTWORK } from '@/app/(landing)/components/cta/footer-artwork.generated' -import { LandingShell } from '@/app/(landing)/components/landing-shell/landing-shell' - -interface TestLinkProps extends AnchorHTMLAttributes { - href: string -} - -const { setTheme } = vi.hoisted(() => ({ setTheme: vi.fn() })) - -vi.mock('@sim/emcn', async (importOriginal) => ({ - ...(await importOriginal()), - cn: (...values: unknown[]) => values.flat().filter(Boolean).join(' '), - ChipLink: ({ href, children, className }: TestLinkProps) => ( - - {children} - - ), -})) -vi.mock('@sim/emcn/icons', () => ({ Moon: () => null, Sun: () => null })) -vi.mock('next/link', () => ({ - default: ({ href, children, ...props }: TestLinkProps) => ( - - {children} - - ), -})) -vi.mock('next/image', () => ({ - default: ({ src, alt }: { src: string; alt: string }) => {alt}, -})) -vi.mock('next-themes', () => ({ - useTheme: () => ({ resolvedTheme: 'light', setTheme }), -})) -vi.mock('@/lib/core/config/env-flags', () => ({ isHosted: true })) -vi.mock('@/lib/github/stars', () => ({ getGitHubStars: vi.fn().mockResolvedValue(29_500) })) -vi.mock('@/app/(landing)/components/navbar/navbar', () => ({ - Navbar: () =>
} />)) -}) - -afterEach(() => { - act(() => root.unmount()) - host.remove() - vi.unstubAllGlobals() -}) - -function element(selector: string): HTMLElement { - const result = host.querySelector(selector) - if (!result) throw new Error(`Missing navigation element: ${selector}`) - return result -} - -function hover(target: HTMLElement) { - act(() => { - target.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })) - }) -} - -function expectSelected(href: string, kind: string) { - const panel = element('#primary-navigation-mega-menu') - expect(panel.getAttribute('aria-hidden')).toBe('false') - const activeLinks = panel.querySelectorAll('a[data-active="true"]') - expect(activeLinks).toHaveLength(1) - expect(activeLinks[0].getAttribute('href')).toBe(href) - expect(panel.querySelector('output')?.textContent).toBe(kind) -} - -describe('NavMenuCluster feature selection', () => { - it('mounts the preview on first opening and preserves it during the exit transition', () => { - expect(host.querySelector('output')).toBeNull() - hover(element('#nav-platform-menu-trigger')) - expect(host.querySelector('output')).not.toBeNull() - act(() => { - document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })) - }) - expect(element('#primary-navigation-mega-menu').getAttribute('aria-hidden')).toBe('true') - expect(host.querySelector('output')).not.toBeNull() - }) - - it('prefetches destinations only while their menu is open', () => { - const overview = element('a[href="/platform"]') - const customers = element('#nav-customers-menu a[href="/customers/rivian"]') - expect(overview.dataset.prefetch).toBe('disabled') - expect(customers.dataset.prefetch).toBe('disabled') - - hover(element('#nav-platform-menu-trigger')) - expect(overview.dataset.prefetch).toBe('auto') - expect(customers.dataset.prefetch).toBe('disabled') - - hover(element('#nav-customers-menu-trigger')) - expect(overview.dataset.prefetch).toBe('disabled') - expect(customers.dataset.prefetch).toBe('auto') - - act(() => { - customers.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })) - }) - expect(customers.dataset.prefetch).toBe('disabled') - }) - - it('lets Tab enter Platform links, return to its trigger, and continue to Customers', () => { - const platform = element('#nav-platform-menu-trigger') - act(() => platform.focus()) - act(() => { - platform.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true })) - }) - const overview = element('a[href="/platform"]') - expect(document.activeElement).toBe(overview) - expectSelected('/platform', 'overview') - - act(() => { - overview.dispatchEvent( - new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true, bubbles: true }) - ) - }) - expect(document.activeElement).toBe(platform) - - const logs = element('a[href="/logs"]') - act(() => logs.focus()) - act(() => { - logs.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true })) - }) - expect(document.activeElement).toBe(element('#nav-customers-menu-trigger')) - expect(element('#nav-customers-menu').getAttribute('aria-hidden')).toBe('false') - }) - - it('supports arrow entry and Escape returns focus without reopening the menu', () => { - const platform = element('#nav-platform-menu-trigger') - act(() => platform.focus()) - act(() => { - platform.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true })) - }) - expect(document.activeElement).toBe(element('a[href="/logs"]')) - - act(() => { - document.activeElement?.dispatchEvent( - new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }) - ) - }) - expect(document.activeElement).toBe(platform) - expect(platform.getAttribute('aria-expanded')).toBe('false') - - act(() => platform.click()) - expect(document.activeElement).toBe(element('a[href="/platform"]')) - }) - - it('releases the desktop scroll lock when resized below the visible breakpoint', () => { - hover(element('#nav-platform-menu-trigger')) - setMenuOpen.mockClear() - act(() => { - desktopMedia.matches = false - desktopMedia.dispatchEvent(new Event('change')) - }) - expect(element('#nav-platform-menu-trigger').getAttribute('aria-expanded')).toBe('false') - expect(setMenuOpen).toHaveBeenCalledExactlyOnceWith('desktop', false) - }) - - it('reports opening and pointer-exit closure during the event before effects flush', () => { - const platform = element('#nav-platform-menu-trigger') - - act(() => { - platform.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })) - expect(setMenuOpen).toHaveBeenCalledExactlyOnceWith('desktop', true) - }) - - setMenuOpen.mockClear() - - act(() => { - platform.dispatchEvent( - new MouseEvent('mouseout', { bubbles: true, relatedTarget: document.body }) - ) - expect(setMenuOpen).toHaveBeenCalledExactlyOnceWith('desktop', false) - }) - }) - - it('stays open while the pointer crosses the header corridor into a menu link', () => { - const platform = element('#nav-platform-menu-trigger') - hover(platform) - const bridge = element('[data-navigation-hover-bridge]') - const overview = element('a[href="/platform"]') - setMenuOpen.mockClear() - - act(() => { - platform.dispatchEvent(new MouseEvent('mouseout', { bubbles: true, relatedTarget: bridge })) - bridge.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, relatedTarget: platform })) - bridge.dispatchEvent(new MouseEvent('mouseout', { bubbles: true, relatedTarget: overview })) - overview.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, relatedTarget: bridge })) - }) - - expectSelected('/platform', 'overview') - expect(setMenuOpen).not.toHaveBeenCalled() - - act(() => { - overview.dispatchEvent( - new MouseEvent('mouseout', { bubbles: true, relatedTarget: document.body }) - ) - }) - - expect(platform.getAttribute('aria-expanded')).toBe('false') - expect(host.querySelector('[data-navigation-hover-bridge]')).toBeNull() - expect(setMenuOpen).toHaveBeenCalledExactlyOnceWith('desktop', false) - }) - - it('only bridges the full header for an open surface menu', () => { - expect(host.querySelector('[data-navigation-hover-bridge]')).toBeNull() - - hover(element('#nav-platform-menu-trigger')) - expect(host.querySelector('[data-navigation-hover-bridge]')).not.toBeNull() - - hover(element('#nav-customers-menu-trigger')) - expect(host.querySelector('[data-navigation-hover-bridge]')).toBeNull() - expect(element('#nav-customers-menu').getAttribute('aria-hidden')).toBe('false') - - hover(element('#nav-resources-menu-trigger')) - expect(host.querySelector('[data-navigation-hover-bridge]')).not.toBeNull() - }) - - it('opens on Overview and keeps the selected feature highlighted while viewing its preview', () => { - hover(element('#nav-platform-menu-trigger')) - expectSelected('/platform', 'overview') - - const workflows = element('a[href="/workflows"]') - hover(workflows) - expectSelected('/workflows', 'workflows') - - act(() => { - workflows.dispatchEvent( - new MouseEvent('mouseout', { - bubbles: true, - relatedTarget: element('output'), - }) - ) - }) - expectSelected('/workflows', 'workflows') - }) - - it('returns to Overview when the pointer returns to the open Platform trigger', () => { - const platform = element('#nav-platform-menu-trigger') - hover(platform) - hover(element('a[href="/tables"]')) - expectSelected('/tables', 'tables') - - hover(platform) - expect(platform.getAttribute('aria-expanded')).toBe('true') - expectSelected('/platform', 'overview') - }) - - it('starts with Overview again after closing and reopening Platform', () => { - const platform = element('#nav-platform-menu-trigger') - hover(platform) - const logs = element('a[href="/logs"]') - hover(logs) - expectSelected('/logs', 'logs') - - act(() => { - logs.dispatchEvent( - new MouseEvent('mouseout', { bubbles: true, relatedTarget: document.body }) - ) - }) - expect(platform.getAttribute('aria-expanded')).toBe('false') - expect(element('#primary-navigation-mega-menu').getAttribute('aria-hidden')).toBe('true') - - hover(platform) - expectSelected('/platform', 'overview') - }) - - it('keeps keyboard selection and previews together when returning to a trigger or changing menus', () => { - const platform = element('#nav-platform-menu-trigger') - act(() => platform.focus()) - expectSelected('/platform', 'overview') - - act(() => element('a[href="/knowledge"]').focus()) - expectSelected('/knowledge', 'knowledge') - - act(() => platform.focus()) - expectSelected('/platform', 'overview') - - act(() => element('#nav-resources-menu-trigger').focus()) - expect(platform.getAttribute('aria-expanded')).toBe('false') - expectSelected('https://docs.sim.ai', 'docs') - - act(() => platform.focus()) - expectSelected('/platform', 'overview') - }) -}) diff --git a/apps/sim/app/(landing)/components/navbar/components/navbar-shell/navbar-shell.test.tsx b/apps/sim/app/(landing)/components/navbar/components/navbar-shell/navbar-shell.test.tsx deleted file mode 100644 index 4160191677d..00000000000 --- a/apps/sim/app/(landing)/components/navbar/components/navbar-shell/navbar-shell.test.tsx +++ /dev/null @@ -1,304 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { act } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { - NavbarShell, - useNavbarFrost, -} from '@/app/(landing)/components/navbar/components/navbar-shell/navbar-shell' - -vi.mock('@sim/emcn', () => ({ - cn: (...values: Array) => values.filter(Boolean).join(' '), -})) - -let resizeObservers: ControlledResizeObserver[] -let intersectionObservers: ControlledIntersectionObserver[] - -class ControlledResizeObserver implements ResizeObserver { - observe = vi.fn<(target: Element) => void>() - unobserve = vi.fn<(target: Element) => void>() - disconnect = vi.fn() - - constructor(private readonly callback: ResizeObserverCallback) { - resizeObservers.push(this) - } - - resize(target: Element) { - const entry: ResizeObserverEntry = { - target, - contentRect: target.getBoundingClientRect(), - borderBoxSize: [], - contentBoxSize: [], - devicePixelContentBoxSize: [], - } - this.callback([entry], this) - } -} - -class ControlledIntersectionObserver { - observe = vi.fn<(target: Element) => void>() - disconnect = vi.fn() - - constructor( - readonly callback: IntersectionObserverCallback, - readonly options?: IntersectionObserverInit - ) { - intersectionObservers.push(this) - } -} - -function MenuControls() { - const frost = useNavbarFrost() - if (!frost) throw new Error('Menu controls require the actual navbar context') - - return ( - <> - - - - - - ) -} - -let host: HTMLDivElement -let root: Root -let mounted: boolean -let headerHeight: number - -function header(): HTMLElement { - const element = host.querySelector('[data-landing-header]') - if (!element) throw new Error('Missing landing header') - return element -} - -function click(label: string) { - const button = Array.from(host.querySelectorAll('button')).find( - (candidate) => candidate.textContent === label - ) - if (!button) throw new Error(`Missing menu control: ${label}`) - act(() => button.dispatchEvent(new MouseEvent('click', { bubbles: true }))) -} - -function announcement(): HTMLElement { - const element = host.querySelector('[data-test-announcement]')?.parentElement - if (!element) throw new Error('Missing announcement') - return element -} - -function scrollTo(position: number) { - act(() => { - host.scrollTop = position - host.dispatchEvent(new Event('scroll')) - }) -} - -function unmount() { - act(() => root.unmount()) - mounted = false -} - -beforeEach(() => { - resizeObservers = [] - intersectionObservers = [] - headerHeight = 104 - vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) - vi.stubGlobal('ResizeObserver', ControlledResizeObserver) - vi.stubGlobal('IntersectionObserver', ControlledIntersectionObserver) - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(function () { - const height = this.tagName === 'HEADER' ? headerHeight : 32 - return new DOMRect(0, 0, 1440, height) - }) - vi.spyOn(HTMLElement.prototype, 'offsetHeight', 'get').mockReturnValue(32) - - host = document.createElement('div') - host.style.overflowY = 'scroll' - host.style.paddingRight = '12px' - host.style.scrollPaddingTop = '8px' - host.scrollTop = 320 - Object.defineProperties(host, { - offsetWidth: { value: 1440 }, - clientWidth: { value: 1420 }, - scrollHeight: { value: 2000 }, - clientHeight: { value: 800 }, - }) - document.body.append(host) - root = createRoot(host) - mounted = true - act(() => { - root.render( - - Read update - - } - > - - - ) - }) -}) - -afterEach(() => { - if (mounted) unmount() - host.remove() - vi.restoreAllMocks() - vi.unstubAllGlobals() -}) - -describe('NavbarShell menu positioning and scroll containment', () => { - it('publishes the current header height before a resize and updates it when header content changes', () => { - expect(header().style.getPropertyValue('--landing-header-height')).toBe( - 'calc(104px - var(--landing-announcement-offset, 0px))' - ) - expect(host.style.scrollPaddingTop).toBe('104px') - expect(resizeObservers).toHaveLength(1) - expect(resizeObservers[0].observe).toHaveBeenCalledWith(header()) - - headerHeight = 76 - act(() => resizeObservers[0].resize(header())) - - expect(header().style.getPropertyValue('--landing-header-height')).toBe( - 'calc(76px - var(--landing-announcement-offset, 0px))' - ) - expect(host.style.scrollPaddingTop).toBe('76px') - expect(host.scrollTop).toBe(320) - }) - - it('disconnects header and scroll-sentinel observations when the shell unmounts', () => { - expect(resizeObservers).toHaveLength(1) - expect(intersectionObservers).toHaveLength(1) - expect(intersectionObservers[0].options?.root).toBe(host) - - unmount() - - expect(resizeObservers[0].disconnect).toHaveBeenCalledOnce() - expect(intersectionObservers[0].disconnect).toHaveBeenCalledOnce() - expect(host.style.scrollPaddingTop).toBe('8px') - }) - - it('locks its actual parent scroll port and restores existing styles without changing its position', () => { - const bodyOverflow = document.body.style.overflowY - const bodyPadding = document.body.style.paddingRight - - click('Open desktop') - - expect(host.style.overflowY).toBe('hidden') - expect(host.style.paddingRight).toBe('20px') - expect(host.scrollTop).toBe(320) - expect(document.documentElement.style.overscrollBehaviorY).toBe('none') - expect(document.body.style.overflowY).toBe(bodyOverflow) - expect(document.body.style.paddingRight).toBe(bodyPadding) - - click('Close desktop') - - expect(host.style.overflowY).toBe('scroll') - expect(host.style.paddingRight).toBe('12px') - expect(host.scrollTop).toBe(320) - expect(document.documentElement.style.overscrollBehaviorY).toBe('') - }) - - it('keeps the scroll port locked until desktop and mobile menus have both closed', () => { - click('Open desktop') - click('Open mobile') - click('Close desktop') - - expect(host.style.overflowY).toBe('hidden') - expect(host.style.paddingRight).toBe('20px') - - click('Close mobile') - - expect(host.style.overflowY).toBe('scroll') - expect(host.style.paddingRight).toBe('12px') - }) -}) - -describe('NavbarShell announcement scroll behavior', () => { - it('hides on downward scroll and restores on upward scroll without changing the scroll position', () => { - expect(announcement().hasAttribute('inert')).toBe(false) - - scrollTo(400) - - expect(announcement().hasAttribute('inert')).toBe(true) - expect(announcement().getAttribute('aria-hidden')).toBe('true') - expect(host.style.scrollPaddingTop).toBe('104px') - expect(host.scrollTop).toBe(400) - - scrollTo(380) - - expect(announcement().hasAttribute('inert')).toBe(false) - expect(host.style.scrollPaddingTop).toBe('104px') - expect(host.scrollTop).toBe(380) - }) - - it('ignores small direction changes but accumulates slow scrolling', () => { - scrollTo(324) - expect(announcement().hasAttribute('inert')).toBe(false) - scrollTo(329) - expect(announcement().hasAttribute('inert')).toBe(true) - scrollTo(326) - expect(announcement().hasAttribute('inert')).toBe(true) - scrollTo(320) - expect(announcement().hasAttribute('inert')).toBe(false) - }) - - it('keeps the banner visible near the top and ignores overscroll bounce at both ends', () => { - scrollTo(400) - scrollTo(-30) - expect(announcement().hasAttribute('inert')).toBe(false) - scrollTo(10) - expect(announcement().hasAttribute('inert')).toBe(false) - - scrollTo(1200) - scrollTo(1250) - scrollTo(1200) - expect(announcement().hasAttribute('inert')).toBe(true) - scrollTo(1180) - expect(announcement().hasAttribute('inert')).toBe(false) - }) - - it('keeps the header stationary while a navigation menu is open', () => { - scrollTo(400) - click('Open mobile') - scrollTo(300) - expect(announcement().hasAttribute('inert')).toBe(true) - expect(host.style.scrollPaddingTop).toBe('104px') - - click('Close mobile') - scrollTo(280) - expect(announcement().hasAttribute('inert')).toBe(false) - }) - - it('does not hide a focused announcement link', () => { - host.querySelector('[data-test-announcement]')?.focus() - scrollTo(400) - expect(announcement().hasAttribute('inert')).toBe(false) - }) - - it('restores the full header if native focus scrolling reaches the top while a menu is open', () => { - scrollTo(400) - click('Open mobile') - scrollTo(0) - - expect(announcement().hasAttribute('inert')).toBe(false) - expect(host.style.scrollPaddingTop).toBe('104px') - expect(host.style.overflowY).toBe('hidden') - }) - - it('removes the scroll listener when the shell unmounts', () => { - const removeListener = vi.spyOn(host, 'removeEventListener') - unmount() - expect(removeListener).toHaveBeenCalledWith('scroll', expect.any(Function)) - }) -}) diff --git a/apps/sim/app/(landing)/components/platform-suite/components/governance-loop/governance-loop.test.tsx b/apps/sim/app/(landing)/components/platform-suite/components/governance-loop/governance-loop.test.tsx deleted file mode 100644 index 67d543939de..00000000000 --- a/apps/sim/app/(landing)/components/platform-suite/components/governance-loop/governance-loop.test.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { act, type ReactNode } from 'react' -import { createRoot } from 'react-dom/client' -import { afterEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/emcn', () => ({ - Badge: ({ children }: { children: ReactNode }) => {children}, - cn: (...values: Array) => values.filter(Boolean).join(' '), -})) - -vi.mock('@sim/emcn/icons', () => ({ - Building: () => , - Download: () => , -})) - -vi.mock('@/app/(landing)/components/shared/responsive-design-stage', () => ({ - ResponsiveDesignStage: ({ children }: { children: ReactNode }) =>
{children}
, -})) - -vi.mock('@/app/(landing)/hooks/use-motion-safe-cycle', async () => { - const React = await import('react') - return { - useMotionSafeCycle: ({ - scheduleCycle, - }: { - scheduleCycle: () => { timers: ReturnType[]; totalMs: number } - }) => { - React.useEffect(() => { - const cycle = scheduleCycle() - return () => cycle.timers.forEach(clearTimeout) - }, []) - }, - } -}) - -import { GovernanceLoop } from '@/app/(landing)/components/platform-suite/components/governance-loop' - -afterEach(() => { - vi.useRealTimers() - document.body.replaceChildren() -}) - -describe('GovernanceLoop', () => { - it('lands the workspaces, fills spend against the budget, and trips the near-limit status', () => { - vi.useFakeTimers() - const host = document.createElement('div') - document.body.append(host) - act(() => { - createRoot(host).render() - }) - - expect(host.textContent).toContain('Organization') - expect(host.textContent).toContain('Spend this month') - const rows = () => [...host.querySelectorAll('tbody tr')] - expect(rows()).toHaveLength(5) - expect(rows().every((row) => row.className.includes('opacity-0'))).toBe(true) - expect(host.textContent).toContain('$0 of $6,000') - expect(host.textContent).not.toContain('Near limit') - - act(() => { - vi.advanceTimersByTime(3_000) - }) - expect(rows().every((row) => row.className.includes('opacity-100'))).toBe(true) - expect(host.textContent).toContain('$3,480 of $6,000') - expect(host.textContent).toContain('58% of the org budget') - expect(host.textContent).toContain('Near limit') - }) -}) diff --git a/apps/sim/app/(landing)/components/platform-suite/platform-suite.test.tsx b/apps/sim/app/(landing)/components/platform-suite/platform-suite.test.tsx deleted file mode 100644 index 62ccb1c9657..00000000000 --- a/apps/sim/app/(landing)/components/platform-suite/platform-suite.test.tsx +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { type AnchorHTMLAttributes, act, type ReactNode } from 'react' -import { createRoot } from 'react-dom/client' -import { afterEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/emcn', async (importOriginal) => ({ - ...(await importOriginal()), - cn: (...values: Array) => values.filter(Boolean).join(' '), -})) - -vi.mock('next/link', () => ({ - default: ({ - children, - href, - ...props - }: { children: ReactNode; href: string } & Omit< - AnchorHTMLAttributes, - 'href' - >) => ( - - {children} - - ), -})) - -vi.mock('@/app/(landing)/components/placement-frame', () => ({ - PlacementFrame: ({ className }: { className?: string }) =>
, -})) - -vi.mock('@/app/(landing)/components/shared/product-window', () => ({ - ProductWindow: ({ kind }: { kind: string }) =>
, -})) - -import { PlatformSuite } from '@/app/(landing)/components/platform-suite/platform-suite' - -afterEach(() => { - document.body.replaceChildren() -}) - -describe('PlatformSuite', () => { - it('balances the card copy and redistributes card width on hover', () => { - ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true - const host = document.createElement('div') - document.body.appendChild(host) - const root = createRoot(host) - - act(() => { - root.render() - }) - - expect(host.textContent).toContain('One Platform for every AI Agent') - expect(host.textContent).toContain( - 'Build and deploy agents in one collaborative workspace, with centralized control over access, spend, data, and performance.' - ) - expect(host.textContent).toContain('Build agents') - expect(host.textContent).toContain('Govern at scale') - - const buildCard = host.querySelector('[data-platform-card="0"]') as HTMLElement - const governCard = host.querySelector('[data-platform-card="1"]') as HTMLElement - expect(buildCard.tagName).toBe('DIV') - expect(buildCard.querySelector('h3 a')?.getAttribute('href')).toBe('/workflows') - expect(buildCard.querySelector('h3 a')?.className).toContain('after:absolute after:inset-0') - expect(governCard.querySelector('h3 a')?.getAttribute('href')).toBe('/enterprise') - expect(buildCard.querySelector('svg.iso-integrate-illustration')).not.toBeNull() - expect(governCard.querySelector('svg.iso-monitor-illustration')).not.toBeNull() - const buildMark = buildCard.querySelector('svg') as SVGElement - const buildTitle = buildCard.querySelector('h3') as HTMLElement - expect( - buildMark.compareDocumentPosition(buildTitle) & Node.DOCUMENT_POSITION_FOLLOWING - ).toBeTruthy() - const governContent = governCard.querySelector('[data-platform-card-content]') as HTMLElement - expect(governContent.className).not.toContain('opacity-40') - - expect(buildCard.className).toContain('md:grow') - expect(governCard.className).toContain('md:grow') - - act(() => { - buildCard.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })) - }) - - expect(buildCard.className).toContain('md:grow-[1.25]') - expect(governCard.className).toContain('md:grow-[0.75]') - expect(governContent.className).toContain('opacity-40') - expect( - (buildCard.querySelector('[data-platform-card-content]') as HTMLElement).className - ).not.toContain('opacity-40') - - act(() => { - buildCard.dispatchEvent( - new MouseEvent('mouseout', { bubbles: true, relatedTarget: document.body }) - ) - }) - - expect(buildCard.className).toContain('md:grow') - expect(governCard.className).toContain('md:grow') - - act(() => { - root.unmount() - }) - }) -}) diff --git a/apps/sim/app/(landing)/components/product-demo/components/composer-loop/composer-loop.test.tsx b/apps/sim/app/(landing)/components/product-demo/components/composer-loop/composer-loop.test.tsx deleted file mode 100644 index fca3d17c585..00000000000 --- a/apps/sim/app/(landing)/components/product-demo/components/composer-loop/composer-loop.test.tsx +++ /dev/null @@ -1,123 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { act, type ReactNode } from 'react' -import { usePrefersReducedMotion } from '@sim/emcn/hooks/use-prefers-reduced-motion' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/emcn', () => ({ - cn: (...values: Array) => values.filter(Boolean).join(' '), - usePrefersReducedMotion, -})) -vi.mock('@sim/emcn/icons', () => ({ Blimp: () => null, Table: () => null })) -vi.mock('@/components/ui', () => ({ ThinkingLoader: () => null })) -vi.mock('framer-motion', () => ({ - AnimatePresence: ({ children }: { children: ReactNode }) => children, - MotionConfig: ({ children }: { children: ReactNode }) => children, - motion: { - div: ({ children }: { children: ReactNode }) =>
{children}
, - span: ({ children }: { children: ReactNode }) => {children}, - }, -})) -vi.mock( - '@/app/(landing)/components/hero/components/hero-platform-loop/production-workflow-stage', - () => ({ - ProductionWorkflowStage: ({ builtCount }: { builtCount: number }) => ( -
- ), - }) -) -vi.mock('@/app/(landing)/components/product-demo/components/composer-loop/demo-composer', () => ({ - DemoComposer: ({ prompt }: { prompt: string }) =>
{prompt}
, -})) - -import { ComposerLoop } from '@/app/(landing)/components/product-demo/components/composer-loop/composer-loop' -import { DEMO_BLOCKS } from '@/app/(landing)/components/product-demo/components/composer-loop/demo-workflow-data' - -let root: Root -let host: HTMLDivElement -let reducedMotion: boolean -let motionListeners: Set<() => void> - -beforeEach(() => { - ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }) - reducedMotion = false - motionListeners = new Set() - vi.stubGlobal('matchMedia', () => ({ - get matches() { - return reducedMotion - }, - addEventListener: (_event: string, callback: () => void) => motionListeners.add(callback), - removeEventListener: (_event: string, callback: () => void) => motionListeners.delete(callback), - })) - vi.stubGlobal('IntersectionObserver', undefined) - vi.stubGlobal('ResizeObserver', undefined) - host = document.createElement('div') - document.body.append(host) - root = createRoot(host) -}) - -afterEach(() => { - act(() => root.unmount()) - host.remove() - vi.useRealTimers() - vi.unstubAllGlobals() -}) - -function setReducedMotion(value: boolean) { - act(() => { - reducedMotion = value - motionListeners.forEach((listener) => listener()) - }) -} - -describe('ComposerLoop motion preference', () => { - it('shows the completed workflow without starting decorative timers when motion is reduced', () => { - reducedMotion = true - const onBeat = vi.fn() - act(() => root.render()) - - expect(host.querySelector('[data-workflow-blocks]')?.getAttribute('data-workflow-blocks')).toBe( - String(DEMO_BLOCKS.length) - ) - expect(host.querySelector('[data-composer]')).toBeNull() - expect(onBeat).toHaveBeenLastCalledWith('build') - expect(vi.getTimerCount()).toBe(0) - - act(() => vi.advanceTimersByTime(60_000)) - expect(host.querySelector('[data-composer]')).toBeNull() - expect(vi.getTimerCount()).toBe(0) - }) - - it('cancels an active pass immediately when reduced motion is enabled', () => { - const onBeat = vi.fn() - act(() => root.render()) - act(() => vi.advanceTimersByTime(1_100)) - expect(host.querySelector('[data-composer]')?.textContent).not.toBe('') - expect(vi.getTimerCount()).toBeGreaterThan(0) - - setReducedMotion(true) - expect(host.querySelector('[data-composer]')).toBeNull() - expect(host.querySelector('[data-workflow-blocks]')?.getAttribute('data-workflow-blocks')).toBe( - String(DEMO_BLOCKS.length) - ) - expect(vi.getTimerCount()).toBe(0) - act(() => vi.advanceTimersByTime(60_000)) - expect(onBeat).toHaveBeenLastCalledWith('build') - }) - - it('starts a fresh pass when motion is enabled again and unsubscribes on unmount', () => { - reducedMotion = true - act(() => root.render()) - setReducedMotion(false) - expect(host.querySelector('[data-composer]')?.textContent).toBe('') - act(() => vi.advanceTimersByTime(1_100)) - expect(host.querySelector('[data-composer]')?.textContent).not.toBe('') - - act(() => root.unmount()) - expect(vi.getTimerCount()).toBe(0) - expect(motionListeners.size).toBe(0) - }) -}) diff --git a/apps/sim/app/(landing)/components/product-demo/components/composer-loop/demo-composer.test.tsx b/apps/sim/app/(landing)/components/product-demo/components/composer-loop/demo-composer.test.tsx deleted file mode 100644 index a0d1896cfa4..00000000000 --- a/apps/sim/app/(landing)/components/product-demo/components/composer-loop/demo-composer.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @vitest-environment node - */ -import { renderToStaticMarkup } from 'react-dom/server' -import { describe, expect, it } from 'vitest' -import { - COMPOSER_ACTIONS, - DemoComposer, -} from '@/app/(landing)/components/product-demo/components/composer-loop/demo-composer' - -describe('DemoComposer', () => { - it('rests on the initial placeholder with the product toolbar', () => { - const html = renderToStaticMarkup() - - expect(html).toContain('Ask Sim to') - for (const action of COMPOSER_ACTIONS) { - expect(html).toContain(`data-action="${action}"`) - } - expect(html).toContain('bg-[#808080]') - }) - - it('shows the typed prompt and arms the send disc', () => { - const html = renderToStaticMarkup( - - ) - - expect(html).toContain('Turn my launch notes') - expect(html).toContain('bg-[#383838]') - expect(html).not.toContain('fill-white') - }) - - it('switches to the conversation placeholder and the stop square while sending', () => { - const html = renderToStaticMarkup() - - expect(html).toContain('Send message to Sim') - expect(html).toContain('fill-white') - }) - - it('separates the placeholder and the typed prompt by token', () => { - const resting = renderToStaticMarkup() - const typed = renderToStaticMarkup( - - ) - - expect(resting).toContain('text-[var(--text-muted)]') - expect(typed).toContain('text-[var(--text-primary)]') - expect(typed).not.toContain('text-[var(--text-muted)]') - }) - - it('keeps every control out of the tab order', () => { - const html = renderToStaticMarkup() - - expect(html).not.toContain(' { - const ids = new Set(DEMO_BLOCKS.map((block) => block.id)) - - it('wires every edge between known blocks, escalation branch first out of the condition', () => { - for (const [source, target] of DEMO_EDGES) { - expect(ids.has(source)).toBe(true) - expect(ids.has(target)).toBe(true) - } - const conditionTargets = DEMO_EDGES.filter(([source]) => source === 'severity').map( - ([, target]) => target - ) - expect(conditionTargets).toEqual(['page', 'draft']) - }) - - it('opens with the Start block, then a trigger, and ends on terminals', () => { - expect(DEMO_BLOCKS[0].id).toBe('start') - expect(DEMO_BLOCKS[0].isTrigger).toBe(true) - expect(DEMO_BLOCKS[1].isTrigger).toBe(true) - for (const id of ['log', 'alert']) { - expect(DEMO_BLOCKS.find((block) => block.id === id)?.isTerminal).toBe(true) - } - }) - - it('never stacks two cards on top of each other', () => { - for (const a of DEMO_BLOCKS) { - for (const b of DEMO_BLOCKS) { - if (a === b || a.x !== b.x) continue - expect(Math.abs(a.y - b.y)).toBeGreaterThanOrEqual(150) - } - } - }) - - it('keeps every card inside the design canvas', () => { - for (const block of DEMO_BLOCKS) { - expect(block.x).toBeGreaterThanOrEqual(0) - expect(block.x + 250).toBeLessThanOrEqual(DEMO_CANVAS.width) - expect(block.y).toBeGreaterThanOrEqual(0) - expect(block.y).toBeLessThan(DEMO_CANVAS.height) - } - }) -}) diff --git a/apps/sim/app/(landing)/components/product-demo/components/product-demo-caption/product-demo-caption.test.tsx b/apps/sim/app/(landing)/components/product-demo/components/product-demo-caption/product-demo-caption.test.tsx deleted file mode 100644 index 1c1224f5bb7..00000000000 --- a/apps/sim/app/(landing)/components/product-demo/components/product-demo-caption/product-demo-caption.test.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { act } from 'react' -import { createRoot } from 'react-dom/client' -import { afterEach, describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/emcn', async (importOriginal) => ({ - ...(await importOriginal()), - cn: (...values: Array) => values.filter(Boolean).join(' '), -})) - -import { - ProductDemoBeatProvider, - ProductDemoCaption, - useProductDemoBeat, -} from '@/app/(landing)/components/product-demo/components/product-demo-caption' -import styles from '@/app/(landing)/components/product-demo/components/product-demo-caption/product-demo-caption.module.css' - -function BuildButton() { - const { setBeat } = useProductDemoBeat() - return ( - - ) -} - -afterEach(() => { - vi.useRealTimers() - document.body.replaceChildren() -}) - -describe('ProductDemoCaption', () => { - it('opens still on the describe title, then crossfades to the next beat', () => { - vi.useFakeTimers() - const host = document.createElement('div') - document.body.append(host) - act(() => { - createRoot(host).render( - - - - - ) - }) - - const heading = () => host.querySelector('h2#product-demo-heading') - const titles = () => host.querySelectorAll('[data-product-demo-caption] > *') - expect(heading()?.textContent).toBe('Describe the agent.') - expect(heading()?.className).not.toContain(styles.enter) - expect(titles()).toHaveLength(1) - - act(() => { - host.querySelector('button')?.click() - }) - expect(heading()?.textContent).toBe('Watch the workflow build.') - expect(heading()?.className).toContain(styles.enter) - const stacked = titles() - expect(stacked).toHaveLength(2) - expect(stacked[0].getAttribute('aria-hidden')).toBe('true') - expect(stacked[0].className).toContain('opacity-0') - expect(stacked[0].textContent).toBe('Describe the agent.') - - act(() => { - vi.advanceTimersByTime(320) - }) - expect(titles()).toHaveLength(1) - expect(heading()?.textContent).toBe('Watch the workflow build.') - }) -}) diff --git a/apps/sim/app/(landing)/components/security/components/cert-blocs/cert-blocs.test.tsx b/apps/sim/app/(landing)/components/security/components/cert-blocs/cert-blocs.test.tsx deleted file mode 100644 index 496ac9767df..00000000000 --- a/apps/sim/app/(landing)/components/security/components/cert-blocs/cert-blocs.test.tsx +++ /dev/null @@ -1,196 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { act } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { renderToStaticMarkup } from 'react-dom/server' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { CertBlocs } from '@/app/(landing)/components/security/components/cert-blocs' - -function renderCerts() { - return -} - -describe('CertBlocs', () => { - it('matches the Features stage aspect on white card chrome with dash-normalized strokes', () => { - const html = renderToStaticMarkup(renderCerts()) - - expect(html).toContain('aspect-[5/6]') - expect(html).toContain('rounded-[10px]') - expect(html).toContain('w-[56%]') - expect(html).toContain('bg-[var(--surface-2)]') - expect(html).toContain('border-[var(--border)]') - expect(html).not.toContain('rounded-[12px]') - expect(html).toContain('href="https://trust.sim.ai/"') - expect(html).toContain('pathLength="1"') - expect(html).not.toContain(' { - const html = renderToStaticMarkup(renderCerts()) - - const document = new DOMParser().parseFromString(html, 'text/html') - const stars = document.querySelectorAll('[data-cert-stars] path') - expect(stars).toHaveLength(12) - expect([...stars].every((star) => star.getAttribute('pathLength') === '1')).toBe(true) - expect(html).toContain('in the Sim Trust Center') - }) -}) - -describe('CertBlocs draw trigger', () => { - let observe: ReturnType - let disconnect: ReturnType - let observerCallback: IntersectionObserverCallback | undefined - let observerRoot: Element | Document | null | undefined - let root: Root | null - let host: HTMLDivElement | null - let port: HTMLDivElement | null - - beforeEach(() => { - observe = vi.fn() - disconnect = vi.fn() - observerCallback = undefined - observerRoot = undefined - ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true - - vi.stubGlobal( - 'IntersectionObserver', - class { - constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit) { - observerCallback = callback - observerRoot = options?.root - } - observe = observe - disconnect = disconnect - unobserve = vi.fn() - takeRecords = () => [] - root = null - rootMargin = '' - thresholds = [] - } - ) - - port = document.createElement('div') - port.className = 'h-screen overflow-y-auto' - document.body.append(port) - host = document.createElement('div') - port.append(host) - root = createRoot(host) - vi.stubGlobal('matchMedia', () => ({ - matches: false, - addEventListener: () => {}, - removeEventListener: () => {}, - })) - }) - - afterEach(() => { - act(() => root?.unmount()) - root = null - host?.remove() - host = null - port?.remove() - port = null - vi.unstubAllGlobals() - }) - - it('draws once when the row enters the inner landing scroll port', () => { - vi.stubGlobal('matchMedia', () => ({ - matches: false, - addEventListener: () => {}, - removeEventListener: () => {}, - })) - - act(() => { - root?.render(renderCerts()) - }) - - expect(observerRoot).toBe(port) - expect(observe).toHaveBeenCalledTimes(1) - - act(() => { - observerCallback?.( - [{ isIntersecting: true } as IntersectionObserverEntry], - {} as IntersectionObserver - ) - }) - - expect(disconnect).toHaveBeenCalled() - expect(host?.querySelector('ul')?.className).toMatch(/drawn/) - }) - - it('shows marks fully drawn when the user prefers reduced motion', () => { - vi.stubGlobal('matchMedia', () => ({ - matches: true, - addEventListener: () => {}, - removeEventListener: () => {}, - })) - - act(() => { - root?.render(renderCerts()) - }) - - expect(observe).not.toHaveBeenCalled() - expect(host?.querySelector('ul')?.className).toMatch(/drawn/) - }) - - it('replays the hovered GDPR mark and stars on every entry and cancels unfinished strokes', () => { - act(() => root?.render(renderCerts())) - const card = Array.from(host?.querySelectorAll('a') ?? []).find((link) => - link.textContent?.startsWith('GDPR') - ) - expect(card).toBeTruthy() - /** JSDOM does not inherit the CSS module's custom properties. */ - card?.style.setProperty('--draw-duration-ms', '900') - card?.style.setProperty('--detail-delay-ms', '120') - const cancel = vi.fn() - const strokes = Array.from(host?.querySelectorAll('[pathLength]') ?? []).map((stroke) => { - const animate = vi.fn() - Object.assign(stroke, { animate, getAnimations: () => [{ cancel }] }) - return { stroke, animate } - }) - - for (let entry = 1; entry <= 2; entry += 1) { - act(() => { - card?.dispatchEvent(new MouseEvent('pointerover', { bubbles: true })) - }) - - for (const { stroke, animate } of strokes) { - expect(animate).toHaveBeenCalledTimes(card?.contains(stroke) ? entry : 0) - if (card?.contains(stroke)) { - expect(animate).toHaveBeenLastCalledWith( - expect.any(Array), - expect.objectContaining({ duration: 900 }) - ) - } - } - - act(() => { - card?.dispatchEvent(new MouseEvent('pointerout', { bubbles: true })) - }) - } - - expect(cancel).toHaveBeenCalledTimes((card?.querySelectorAll('[pathLength]').length ?? 0) * 2) - }) - - it('does not replay for touch entry or reduced motion', () => { - act(() => root?.render(renderCerts())) - const card = host?.querySelector('a') - const animate = vi.fn() - for (const stroke of host?.querySelectorAll('[pathLength]') ?? []) { - Object.assign(stroke, { animate, getAnimations: () => [] }) - } - - const touch = new MouseEvent('pointerover', { bubbles: true }) - Object.defineProperty(touch, 'pointerType', { value: 'touch' }) - act(() => card?.dispatchEvent(touch)) - expect(animate).not.toHaveBeenCalled() - - vi.stubGlobal('matchMedia', () => ({ - matches: true, - addEventListener: () => {}, - removeEventListener: () => {}, - })) - act(() => card?.dispatchEvent(new MouseEvent('pointerover', { bubbles: true }))) - expect(animate).not.toHaveBeenCalled() - }) -}) diff --git a/apps/sim/app/(landing)/components/security/components/workspace-controls/workspace-controls.test.tsx b/apps/sim/app/(landing)/components/security/components/workspace-controls/workspace-controls.test.tsx deleted file mode 100644 index 46515e4c80f..00000000000 --- a/apps/sim/app/(landing)/components/security/components/workspace-controls/workspace-controls.test.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @vitest-environment node - */ -import { renderToStaticMarkup } from 'react-dom/server' -import { describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/emcn', async (importOriginal) => ({ - ...(await importOriginal()), - cn: (...values: Array) => values.filter(Boolean).join(' '), -})) - -import { WorkspaceControls } from '@/app/(landing)/components/security/components/workspace-controls/workspace-controls' - -describe('WorkspaceControls', () => { - it('renders six untitled controls on an even three-column grid', () => { - const html = renderToStaticMarkup() - - expect(html).toContain('id="controls"') - expect(html).toContain('aria-label="Workspace controls"') - expect(html).toContain('grid-cols-3') - expect(html).toContain('max-lg:grid-cols-2') - expect(html).toContain('max-sm:grid-cols-1') - expect(html.match(/
  • SSO & SCIM') - expect(html).toContain('>Self-hosting') - expect(html).not.toContain('Open source') - }) - - it('shifts every mark off the 64-grid origin so its stroke sits flush with the text', () => { - const html = renderToStaticMarkup() - const viewBoxes = [...html.matchAll(/viewBox="([^"]+)"/g)].map(([, value]) => value) - - expect(viewBoxes).toHaveLength(6) - for (const viewBox of viewBoxes) { - const [minX] = viewBox.split(' ').map(Number) - expect(minX).toBeGreaterThan(12) - } - }) - - it('carries no heading of its own', () => { - const html = renderToStaticMarkup() - - expect(html).not.toContain('How the workspace runs') - expect(html).not.toContain(' ({ - ...(await importOriginal()), - ChipLink: ({ - variant: _variant, - ...props - }: AnchorHTMLAttributes & { variant?: string }) => , - cn: (...values: Array) => values.filter(Boolean).join(' '), -})) - -import { Security } from '@/app/(landing)/components/security/security' - -describe('Security', () => { - it('renders the governance intro and white certification blocs', () => { - const html = renderToStaticMarkup() - - expect(html).toContain('id="security"') - expect(html).not.toContain('min-h-[100dvh]') - expect(html).toContain('>Central governance for enterprise AI') - expect(html).toContain( - 'Sim is one place to control who can build agents, what they can use, and how the workspace runs.' - ) - expect(html).toContain('>SOC 2 Type II') - expect(html).toContain('>ISO 27001') - expect(html).toContain('>GDPR') - expect(html).toContain('href="https://trust.sim.ai/"') - expect(html).toContain('bg-[var(--surface-2)]') - expect(html).toContain('aspect-[5/6]') - expect(html).toContain('rounded-[10px]') - expect(html).not.toContain('rounded-[12px]') - expect(html).not.toContain('How the workspace runs') - expect(html).not.toContain('Self-hosting') - expect(html).not.toContain('SOC2 compliant') - expect(html).not.toContain('HIPAA') - expect(html).not.toContain('CCPA') - }) -}) diff --git a/apps/sim/app/(landing)/components/shared/logs-run-graph/logs-run-graph.test.tsx b/apps/sim/app/(landing)/components/shared/logs-run-graph/logs-run-graph.test.tsx deleted file mode 100644 index bfd6eb520dd..00000000000 --- a/apps/sim/app/(landing)/components/shared/logs-run-graph/logs-run-graph.test.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { act } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { CoreFeatureCard } from '@/app/(landing)/components/features/components/core-feature-card' -import { LogsRunGraph } from '@/app/(landing)/components/shared/logs-run-graph' -import { RunTraceGraphic } from '@/app/(landing)/logs/components/feature-graphics' - -let root: Root -let host: HTMLDivElement - -beforeEach(() => { - vi.useFakeTimers() - ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true - host = document.createElement('div') - document.body.append(host) - root = createRoot(host) -}) - -afterEach(() => { - act(() => root.unmount()) - host.remove() - vi.restoreAllMocks() - vi.useRealTimers() -}) - -function mount() { - act(() => root.render()) - const graph = host.querySelector('button') - if (!graph) throw new Error('Graph not rendered') - return graph -} - -function pointer(element: Element, type: string, pointerType = 'touch', x = 40, y = 300) { - const event = new MouseEvent(type, { bubbles: true, clientX: x, clientY: y }) - Object.defineProperty(event, 'pointerType', { value: pointerType }) - act(() => element.dispatchEvent(event)) -} - -describe('LogsRunGraph', () => { - it('keeps tapped details visible and dismisses them on outside interaction', () => { - const graph = mount() - const bar = graph.querySelectorAll('[data-run-count]')[1] - pointer(bar, 'pointerdown') - pointer(bar, 'pointerup') - pointer(graph, 'pointerout') - expect(document.querySelector('[role="tooltip"]')?.textContent).toContain('5 succeeded') - expect(graph.getAttribute('aria-label')).toContain('23–22 hours ago') - pointer(document.body, 'pointerdown') - expect(document.querySelector('[role="tooltip"]')).toBeNull() - }) - - it.each(['pointercancel', 'swipe'])('does not open details after a %s gesture', (gesture) => { - const graph = mount() - pointer(graph, 'pointerdown') - if (gesture === 'pointercancel') pointer(graph, 'pointercancel') - pointer(graph, 'pointerup', 'touch', gesture === 'swipe' ? 120 : 40) - expect(document.querySelector('[role="tooltip"]')).toBeNull() - }) - - it.each(['scroll', 'resize'])( - 'dismisses details when the viewport changes through %s', - (event) => { - const graph = mount() - pointer(graph, 'pointerdown') - pointer(graph, 'pointerup') - expect(document.querySelector('[role="tooltip"]')).not.toBeNull() - act(() => (event === 'resize' ? window : host).dispatchEvent(new Event(event))) - expect(document.querySelector('[role="tooltip"]')).toBeNull() - } - ) - - it('supports keyboard exploration and Escape dismissal', () => { - const graph = mount() - act(() => graph.focus()) - act(() => graph.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }))) - expect(graph.getAttribute('aria-label')).toContain('Last hour: 7 succeeded') - act(() => - graph.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true })) - ) - expect(graph.getAttribute('aria-label')).toContain('2–1 hours ago: 6 succeeded') - act(() => graph.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))) - expect(document.querySelector('[role="tooltip"]')).toBeNull() - }) - - it('keeps first-hour details inside a narrow viewport', () => { - const graph = mount() - vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(320) - pointer(graph, 'pointerdown') - pointer(graph, 'pointerup') - const tooltip = document.querySelector('[role="tooltip"]') - expect(tooltip?.style.translate).toContain('calc(192px - 100%)') - expect(tooltip?.className).toContain('w-[176px]') - }) - - it('keeps the interactive chart outside the route link and decorative wrappers', () => { - act(() => - root.render( - } - interactiveVisual - /> - ) - ) - const graph = host.querySelector('[data-run-overview-graph]') - expect(graph).not.toBeNull() - expect(graph?.closest('a, [aria-hidden="true"]')).toBeNull() - expect(host.querySelector('a')?.getAttribute('href')).toBe('/logs') - }) -}) diff --git a/apps/sim/app/(landing)/components/shared/product-window/product-window.test.tsx b/apps/sim/app/(landing)/components/shared/product-window/product-window.test.tsx deleted file mode 100644 index 18264f65db0..00000000000 --- a/apps/sim/app/(landing)/components/shared/product-window/product-window.test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { renderToStaticMarkup } from 'react-dom/server' -import { describe, expect, it, vi } from 'vitest' - -vi.mock('@sim/emcn', async (importOriginal) => ({ - ...(await importOriginal()), - cn: (...values: Array) => values.filter(Boolean).join(' '), -})) -vi.mock('@/app/(landing)/components/shared/product-preview', () => ({ - ProductPreview: () => ( - <> -