@taskforcehq/taskforce 0.3.309 → 0.3.313
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -127
- package/dist/TaskforceCore.js +1 -1
- package/dist/components/context/ContextAttachmentManager.js +4 -3
- package/dist/components/features/InitiativesModule.js +1 -1
- package/dist/components/features/TaskSettings.js +1 -1
- package/dist/components/ui/DisclosureSectionHeader.d.ts +12 -0
- package/dist/components/ui/DisclosureSectionHeader.js +8 -0
- package/dist/components/views/PlanComparisonPage.js +1 -1
- package/dist/components/views/StandaloneLayout.js +18 -4
- package/dist/components/views/panels/PlanningDrawer.js +2 -1
- package/dist/components/views/standalone/modals/AccountHubModal.js +1 -1
- package/dist/components/views/standalone/modals/HelpModal.d.ts +3 -1
- package/dist/components/views/standalone/modals/HelpModal.js +17 -2
- package/dist/core/PlanEntitlementService.d.ts +2 -0
- package/dist/core/PlanEntitlementService.js +57 -4
- package/dist/hooks/useFilterSort.js +1 -1
- package/dist/hooks/useTaskforce.js +2 -2
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +5 -2
- package/dist/localization/locales/en-US.js +1 -1
- package/dist/localization/locales/es-419.js +1 -1
- package/dist/localization/locales/pt-BR.js +1 -1
- package/dist/mcp/runtime.js +49 -14
- package/dist/server/routes/billing.js +0 -1
- package/dist/ui/assets/AgentsModule-BULqIo8e.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-CCckzWYZ.js → AnnotatedAttachmentWorkspace-DHr4rHK9.js} +1 -1
- package/dist/ui/assets/ContextAttachmentManager-DoMe2OpY.js +3 -0
- package/dist/ui/assets/{DocumentWorkspace-BVaWPA25.js → DocumentWorkspace-DumBWhvb.js} +1 -1
- package/dist/ui/assets/{EntityActivityTimeline-DSj0ThaK.js → EntityActivityTimeline-sHk8Nzbq.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-AxMkWtxH.js → InitiativesModule-3X9UNzHh.js} +1 -1
- package/dist/ui/assets/PlansPage-C3A3EYaV.js +1 -0
- package/dist/ui/assets/PlansPage-DeAzWYTb.css +1 -0
- package/dist/ui/assets/{TaskContextUpload-Dw4rTF4i.js → TaskContextUpload-C8G7pWkX.js} +1 -1
- package/dist/ui/assets/{TaskSettings-f2ga42_V.js → TaskSettings-DM1o9Wla.js} +2 -2
- package/dist/ui/assets/{WorkflowsModule-E4UL3mov.js → WorkflowsModule-CW0crzvD.js} +1 -1
- package/dist/ui/assets/documentReferences-BZB3nMRb.js +1 -0
- package/dist/ui/assets/index-BWFQ9mGn.js +5 -0
- package/dist/ui/assets/{index-BvAbqx5Q.css → index-DcK_8tuE.css} +1 -1
- package/dist/ui/assets/{vendor-icons-B32hGuF2.js → vendor-icons-CLnehDTw.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/supportContact.d.ts +9 -0
- package/dist/utils/supportContact.js +32 -0
- package/package.json +12 -4
- package/scripts/postinstall.js +4 -3
- package/dist/TaskforceCore.CategoryFallback.test.d.ts +0 -1
- package/dist/TaskforceCore.CategoryFallback.test.js +0 -101
- package/dist/TaskforceCore.filter-persistence.test.d.ts +0 -1
- package/dist/TaskforceCore.filter-persistence.test.js +0 -402
- package/dist/TaskforceCore.routing.test.d.ts +0 -1
- package/dist/TaskforceCore.routing.test.js +0 -311
- package/dist/TaskforceCore.test.d.ts +0 -1
- package/dist/TaskforceCore.test.js +0 -7910
- package/dist/TaskforceWidget.test.d.ts +0 -1
- package/dist/TaskforceWidget.test.js +0 -92
- package/dist/UnsavedChangesRelaxed.test.d.ts +0 -1
- package/dist/UnsavedChangesRelaxed.test.js +0 -16
- package/dist/__tests__/localizationCatalogs.test.d.ts +0 -1
- package/dist/__tests__/localizationCatalogs.test.js +0 -54
- package/dist/__tests__/workflowSimulation.test.d.ts +0 -1
- package/dist/__tests__/workflowSimulation.test.js +0 -113
- package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.d.ts +0 -1
- package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.js +0 -278
- package/dist/annotatedAttachments/payload.test.d.ts +0 -1
- package/dist/annotatedAttachments/payload.test.js +0 -67
- package/dist/compat/workspaceSyncCompat.test.d.ts +0 -1
- package/dist/compat/workspaceSyncCompat.test.js +0 -124
- package/dist/components/features/AgentsModule.test.d.ts +0 -1
- package/dist/components/features/AgentsModule.test.js +0 -601
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.d.ts +0 -1
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +0 -2883
- package/dist/components/features/DocumentWorkspace.test.d.ts +0 -1
- package/dist/components/features/DocumentWorkspace.test.js +0 -1439
- package/dist/components/features/TaskSettings.test.d.ts +0 -1
- package/dist/components/features/TaskSettings.test.js +0 -1674
- package/dist/components/features/settings/TaxonomyLibraryManager.test.d.ts +0 -1
- package/dist/components/features/settings/TaxonomyLibraryManager.test.js +0 -141
- package/dist/components/task/TaskActionHeader.test.d.ts +0 -1
- package/dist/components/task/TaskActionHeader.test.js +0 -91
- package/dist/components/task/TaskCard.test.d.ts +0 -1
- package/dist/components/task/TaskCard.test.js +0 -513
- package/dist/components/task/TaskContextUpload.test.d.ts +0 -1
- package/dist/components/task/TaskContextUpload.test.js +0 -506
- package/dist/components/task/TaskForm.test.d.ts +0 -1
- package/dist/components/task/TaskForm.test.js +0 -946
- package/dist/components/task/TaskKanban.empty-column-drop.test.d.ts +0 -1
- package/dist/components/task/TaskKanban.empty-column-drop.test.js +0 -66
- package/dist/components/task/TaskKanban.planning-drop.test.d.ts +0 -1
- package/dist/components/task/TaskKanban.planning-drop.test.js +0 -93
- package/dist/components/task/TaskKanban.test.d.ts +0 -1
- package/dist/components/task/TaskKanban.test.js +0 -99
- package/dist/components/task/TaskList.test.d.ts +0 -1
- package/dist/components/task/TaskList.test.js +0 -473
- package/dist/components/task/TaskStatusActions.test.d.ts +0 -1
- package/dist/components/task/TaskStatusActions.test.js +0 -54
- package/dist/components/task/taskKanbanDropRules.test.d.ts +0 -1
- package/dist/components/task/taskKanbanDropRules.test.js +0 -114
- package/dist/components/ui/LevelSelector.test.d.ts +0 -1
- package/dist/components/ui/LevelSelector.test.js +0 -55
- package/dist/components/ui/Markdown.test.d.ts +0 -1
- package/dist/components/ui/Markdown.test.js +0 -38
- package/dist/components/ui/MultiSelectFilter.test.d.ts +0 -1
- package/dist/components/ui/MultiSelectFilter.test.js +0 -74
- package/dist/components/ui/TaxonomyDropdown.test.d.ts +0 -1
- package/dist/components/ui/TaxonomyDropdown.test.js +0 -130
- package/dist/components/ui/TopNoticeLayer.test.d.ts +0 -1
- package/dist/components/ui/TopNoticeLayer.test.js +0 -25
- package/dist/components/views/AppShellHeader.test.d.ts +0 -1
- package/dist/components/views/AppShellHeader.test.js +0 -42
- package/dist/components/views/PlanComparisonPage.test.d.ts +0 -1
- package/dist/components/views/PlanComparisonPage.test.js +0 -757
- package/dist/components/views/PlansPage.test.d.ts +0 -1
- package/dist/components/views/PlansPage.test.js +0 -710
- package/dist/components/views/chrome/WorkspaceChromeSections.test.d.ts +0 -1
- package/dist/components/views/chrome/WorkspaceChromeSections.test.js +0 -54
- package/dist/components/views/chrome/workspaceChrome.test.d.ts +0 -1
- package/dist/components/views/chrome/workspaceChrome.test.js +0 -52
- package/dist/components/views/panels/DocumentFilterToolbar.test.d.ts +0 -1
- package/dist/components/views/panels/DocumentFilterToolbar.test.js +0 -19
- package/dist/components/views/panels/FilterToolbar.test.d.ts +0 -1
- package/dist/components/views/panels/FilterToolbar.test.js +0 -47
- package/dist/components/views/panels/PlanningDrawer.test.d.ts +0 -1
- package/dist/components/views/panels/PlanningDrawer.test.js +0 -529
- package/dist/components/views/planningScope.test.d.ts +0 -1
- package/dist/components/views/planningScope.test.js +0 -60
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +0 -1
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +0 -66
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +0 -1
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +0 -24
- package/dist/components/views/teamManagementAccess.test.d.ts +0 -1
- package/dist/components/views/teamManagementAccess.test.js +0 -44
- package/dist/config/deployment.test.d.ts +0 -1
- package/dist/config/deployment.test.js +0 -90
- package/dist/config/envSchema.test.d.ts +0 -1
- package/dist/config/envSchema.test.js +0 -125
- package/dist/core/AiProfileService.test.d.ts +0 -1
- package/dist/core/AiProfileService.test.js +0 -184
- package/dist/core/AiProfiles.test.d.ts +0 -1
- package/dist/core/AiProfiles.test.js +0 -1873
- package/dist/core/AuthTokenService.test.d.ts +0 -1
- package/dist/core/AuthTokenService.test.js +0 -78
- package/dist/core/BulkUpdateFieldsPreview.test.d.ts +0 -1
- package/dist/core/BulkUpdateFieldsPreview.test.js +0 -62
- package/dist/core/CompletedAt.test.d.ts +0 -1
- package/dist/core/CompletedAt.test.js +0 -44
- package/dist/core/CreatedBy.test.d.ts +0 -1
- package/dist/core/CreatedBy.test.js +0 -188
- package/dist/core/DeletedTaskTrash.test.d.ts +0 -1
- package/dist/core/DeletedTaskTrash.test.js +0 -104
- package/dist/core/EntitlementsPolicy.test.d.ts +0 -1
- package/dist/core/EntitlementsPolicy.test.js +0 -280
- package/dist/core/GlobalSettingsService.test.d.ts +0 -1
- package/dist/core/GlobalSettingsService.test.js +0 -45
- package/dist/core/JsonBackupRuntime.test.d.ts +0 -1
- package/dist/core/JsonBackupRuntime.test.js +0 -155
- package/dist/core/PlanFeatureCatalog.test.d.ts +0 -1
- package/dist/core/PlanFeatureCatalog.test.js +0 -139
- package/dist/core/PlanVersionPolicy.test.d.ts +0 -1
- package/dist/core/PlanVersionPolicy.test.js +0 -204
- package/dist/core/PlanningEntities.test.d.ts +0 -1
- package/dist/core/PlanningEntities.test.js +0 -509
- package/dist/core/ScheduleFields.test.d.ts +0 -1
- package/dist/core/ScheduleFields.test.js +0 -193
- package/dist/core/SetupState.test.d.ts +0 -1
- package/dist/core/SetupState.test.js +0 -113
- package/dist/core/SignupMonetizationSettings.test.d.ts +0 -1
- package/dist/core/SignupMonetizationSettings.test.js +0 -515
- package/dist/core/Stats.test.d.ts +0 -1
- package/dist/core/Stats.test.js +0 -36
- package/dist/core/SystemAdmin.test.d.ts +0 -1
- package/dist/core/SystemAdmin.test.js +0 -1106
- package/dist/core/TaskActivitySync.test.d.ts +0 -1
- package/dist/core/TaskActivitySync.test.js +0 -90
- package/dist/core/TaskAttachmentsCanonical.test.d.ts +0 -1
- package/dist/core/TaskAttachmentsCanonical.test.js +0 -356
- package/dist/core/TaskAuditTimeline.test.d.ts +0 -1
- package/dist/core/TaskAuditTimeline.test.js +0 -319
- package/dist/core/TaskTaxonomyValidation.test.d.ts +0 -1
- package/dist/core/TaskTaxonomyValidation.test.js +0 -440
- package/dist/core/Taskforce.ids.test.d.ts +0 -1
- package/dist/core/Taskforce.ids.test.js +0 -611
- package/dist/core/Taskforce.listTasksSlim.test.d.ts +0 -1
- package/dist/core/Taskforce.listTasksSlim.test.js +0 -91
- package/dist/core/Taskforce.mcpAuth.test.d.ts +0 -1
- package/dist/core/Taskforce.mcpAuth.test.js +0 -450
- package/dist/core/Taskforce.realtimeMutation.test.d.ts +0 -1
- package/dist/core/Taskforce.realtimeMutation.test.js +0 -131
- package/dist/core/UserProfileAvatarDrafts.test.d.ts +0 -1
- package/dist/core/UserProfileAvatarDrafts.test.js +0 -138
- package/dist/core/WorkspaceDelete.test.d.ts +0 -1
- package/dist/core/WorkspaceDelete.test.js +0 -159
- package/dist/core/WorkspacePermissions.test.d.ts +0 -1
- package/dist/core/WorkspacePermissions.test.js +0 -270
- package/dist/core/WorkspacePlanMode.test.d.ts +0 -1
- package/dist/core/WorkspacePlanMode.test.js +0 -383
- package/dist/core/WorkspaceSyncSettings.test.d.ts +0 -1
- package/dist/core/WorkspaceSyncSettings.test.js +0 -65
- package/dist/core/__tests__/TaskAttachmentsCanonical.test.d.ts +0 -1
- package/dist/core/__tests__/TaskAttachmentsCanonical.test.js +0 -212
- package/dist/documentReviews/DocumentReviewSessionStore.test.d.ts +0 -1
- package/dist/documentReviews/DocumentReviewSessionStore.test.js +0 -127
- package/dist/hooks/authGuardPolicy.test.d.ts +0 -1
- package/dist/hooks/authGuardPolicy.test.js +0 -76
- package/dist/hooks/bootstrapTimeouts.test.d.ts +0 -1
- package/dist/hooks/bootstrapTimeouts.test.js +0 -12
- package/dist/hooks/sync/controlPlane.test.d.ts +0 -1
- package/dist/hooks/sync/controlPlane.test.js +0 -121
- package/dist/hooks/sync/lifecyclePolicy.test.d.ts +0 -1
- package/dist/hooks/sync/lifecyclePolicy.test.js +0 -124
- package/dist/hooks/sync/orchestratorShared.test.d.ts +0 -1
- package/dist/hooks/sync/orchestratorShared.test.js +0 -98
- package/dist/hooks/sync/pullLifecycle.test.d.ts +0 -1
- package/dist/hooks/sync/pullLifecycle.test.js +0 -80
- package/dist/hooks/sync/recovery.test.d.ts +0 -1
- package/dist/hooks/sync/recovery.test.js +0 -292
- package/dist/hooks/sync/transfers.test.d.ts +0 -1
- package/dist/hooks/sync/transfers.test.js +0 -928
- package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +0 -1
- package/dist/hooks/sync/useRecentSyncEvents.test.js +0 -124
- package/dist/hooks/sync/useSyncStatusActions.test.d.ts +0 -1
- package/dist/hooks/sync/useSyncStatusActions.test.js +0 -133
- package/dist/hooks/sync/useSyncStatusControls.test.d.ts +0 -1
- package/dist/hooks/sync/useSyncStatusControls.test.js +0 -88
- package/dist/hooks/useFilterSort.test.d.ts +0 -1
- package/dist/hooks/useFilterSort.test.js +0 -419
- package/dist/hooks/useRealtimeSync.test.d.ts +0 -1
- package/dist/hooks/useRealtimeSync.test.js +0 -311
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.d.ts +0 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +0 -54
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +0 -1
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +0 -228
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.d.ts +0 -1
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +0 -2027
- package/dist/hooks/useTaskData.test.d.ts +0 -1
- package/dist/hooks/useTaskData.test.js +0 -291
- package/dist/hooks/useTaskMutations.test.d.ts +0 -1
- package/dist/hooks/useTaskMutations.test.js +0 -256
- package/dist/hooks/useTaskforce.contract.test.d.ts +0 -1
- package/dist/hooks/useTaskforce.contract.test.js +0 -7
- package/dist/hooks/useTaskforce.priority-filters.test.d.ts +0 -1
- package/dist/hooks/useTaskforce.priority-filters.test.js +0 -28
- package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +0 -1
- package/dist/hooks/useTaskforce.runtime-routing.test.js +0 -246
- package/dist/hooks/useTaskforce.sync-behavior.test.d.ts +0 -1
- package/dist/hooks/useTaskforce.sync-behavior.test.js +0 -3644
- package/dist/hooks/useWorkspaceSyncController.test.d.ts +0 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +0 -192
- package/dist/hooks/workspace/useTaskforceApiRouting.test.d.ts +0 -1
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +0 -77
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +0 -1
- package/dist/hooks/workspace/workspaceLocalState.test.js +0 -59
- package/dist/localization/localizationCatalogs.test.d.ts +0 -1
- package/dist/localization/localizationCatalogs.test.js +0 -37
- package/dist/mcp/aiProfileBootstrap.test.d.ts +0 -1
- package/dist/mcp/aiProfileBootstrap.test.js +0 -68
- package/dist/mcp/canonicalAssetHelpers.test.d.ts +0 -1
- package/dist/mcp/canonicalAssetHelpers.test.js +0 -67
- package/dist/mcp/clientIntegrations.test.d.ts +0 -1
- package/dist/mcp/clientIntegrations.test.js +0 -219
- package/dist/mcp/optionResolution.test.d.ts +0 -1
- package/dist/mcp/optionResolution.test.js +0 -43
- package/dist/mcp/runtime.test.d.ts +0 -1
- package/dist/mcp/runtime.test.js +0 -5120
- package/dist/mcp/structuredCommentValidation.test.d.ts +0 -1
- package/dist/mcp/structuredCommentValidation.test.js +0 -31
- package/dist/mcp/structuredDocValidation.test.d.ts +0 -1
- package/dist/mcp/structuredDocValidation.test.js +0 -35
- package/dist/mcp/toolRegistry.test.d.ts +0 -1
- package/dist/mcp/toolRegistry.test.js +0 -34
- package/dist/migrations/billingSchemaParity.test.d.ts +0 -1
- package/dist/migrations/billingSchemaParity.test.js +0 -160
- package/dist/migrations/taskSchemaMigrations.test.d.ts +0 -1
- package/dist/migrations/taskSchemaMigrations.test.js +0 -225
- package/dist/qaOpen.test.d.ts +0 -1
- package/dist/qaOpen.test.js +0 -56
- package/dist/releaseVersion.test.d.ts +0 -1
- package/dist/releaseVersion.test.js +0 -32
- package/dist/runtime/appGates.test.d.ts +0 -1
- package/dist/runtime/appGates.test.js +0 -42
- package/dist/server/aiProfileAvatarAssets.test.d.ts +0 -1
- package/dist/server/aiProfileAvatarAssets.test.js +0 -213
- package/dist/server/annotatedAttachmentsRoutes.test.d.ts +0 -1
- package/dist/server/annotatedAttachmentsRoutes.test.js +0 -532
- package/dist/server/auth.test.d.ts +0 -1
- package/dist/server/auth.test.js +0 -131
- package/dist/server/buildInfo.test.d.ts +0 -1
- package/dist/server/buildInfo.test.js +0 -29
- package/dist/server/cors.test.d.ts +0 -1
- package/dist/server/cors.test.js +0 -77
- package/dist/server/documentReviewRoutes.test.d.ts +0 -1
- package/dist/server/documentReviewRoutes.test.js +0 -190
- package/dist/server/email.test.d.ts +0 -1
- package/dist/server/email.test.js +0 -113
- package/dist/server/index.cookieProxy.test.d.ts +0 -1
- package/dist/server/index.cookieProxy.test.js +0 -67
- package/dist/server/index.rateLimit.test.d.ts +0 -1
- package/dist/server/index.rateLimit.test.js +0 -22
- package/dist/server/index.test.d.ts +0 -1
- package/dist/server/index.test.js +0 -283
- package/dist/server/rateLimit.test.d.ts +0 -1
- package/dist/server/rateLimit.test.js +0 -107
- package/dist/server/realtimeSync.auth.test.d.ts +0 -1
- package/dist/server/realtimeSync.auth.test.js +0 -62
- package/dist/server/realtimeSync.replay.test.d.ts +0 -1
- package/dist/server/realtimeSync.replay.test.js +0 -192
- package/dist/server/realtimeSync.workspaceIsolation.test.d.ts +0 -1
- package/dist/server/realtimeSync.workspaceIsolation.test.js +0 -101
- package/dist/server/routes/billing.test.d.ts +0 -1
- package/dist/server/routes/billing.test.js +0 -3455
- package/dist/server/routes/shared.test.d.ts +0 -1
- package/dist/server/routes/shared.test.js +0 -32
- package/dist/server/routes/sync.integration.test.d.ts +0 -1
- package/dist/server/routes/sync.integration.test.js +0 -2932
- package/dist/server/routes/syncAuxRoutes.test.d.ts +0 -1
- package/dist/server/routes/syncAuxRoutes.test.js +0 -158
- package/dist/server/routes/syncRouteGuards.test.d.ts +0 -1
- package/dist/server/routes/syncRouteGuards.test.js +0 -94
- package/dist/server/routes.test.d.ts +0 -1
- package/dist/server/routes.test.js +0 -15612
- package/dist/server/runtimeMode.test.d.ts +0 -1
- package/dist/server/runtimeMode.test.js +0 -26
- package/dist/server/securityHeaders.test.d.ts +0 -1
- package/dist/server/securityHeaders.test.js +0 -70
- package/dist/services/templateEngine.test.d.ts +0 -1
- package/dist/services/templateEngine.test.js +0 -150
- package/dist/services/templates.test.d.ts +0 -1
- package/dist/services/templates.test.js +0 -45
- package/dist/services/workflowDocumentFixtures.test.d.ts +0 -1
- package/dist/services/workflowDocumentFixtures.test.js +0 -78
- package/dist/services/workflowExportMatrix.test.d.ts +0 -1
- package/dist/services/workflowExportMatrix.test.js +0 -113
- package/dist/services/workflowExportSnapshots.test.d.ts +0 -1
- package/dist/services/workflowExportSnapshots.test.js +0 -30
- package/dist/services/workflowGeneration.test.d.ts +0 -1
- package/dist/services/workflowGeneration.test.js +0 -12
- package/dist/services/workflowSimulation.test.d.ts +0 -1
- package/dist/services/workflowSimulation.test.js +0 -113
- package/dist/services/workflowV2Policy.test.d.ts +0 -1
- package/dist/services/workflowV2Policy.test.js +0 -80
- package/dist/shared/customTaxonomyLibrary.test.d.ts +0 -1
- package/dist/shared/customTaxonomyLibrary.test.js +0 -20
- package/dist/shared/passwordPolicy.test.d.ts +0 -1
- package/dist/shared/passwordPolicy.test.js +0 -34
- package/dist/shared/runtimeContract.test.d.ts +0 -1
- package/dist/shared/runtimeContract.test.js +0 -23
- package/dist/shared/taxonomyLibrary.test.d.ts +0 -1
- package/dist/shared/taxonomyLibrary.test.js +0 -26
- package/dist/shared/taxonomyState.test.d.ts +0 -1
- package/dist/shared/taxonomyState.test.js +0 -71
- package/dist/soak/preflight.cli.d.ts +0 -1
- package/dist/soak/preflight.cli.js +0 -28
- package/dist/soak/preflight.d.ts +0 -26
- package/dist/soak/preflight.js +0 -113
- package/dist/soak/preflight.test.d.ts +0 -1
- package/dist/soak/preflight.test.js +0 -39
- package/dist/storage/objectStorageClient.test.d.ts +0 -1
- package/dist/storage/objectStorageClient.test.js +0 -82
- package/dist/storage/objectStorageConfig.test.d.ts +0 -1
- package/dist/storage/objectStorageConfig.test.js +0 -50
- package/dist/storage/postgresAdapter.test.d.ts +0 -1
- package/dist/storage/postgresAdapter.test.js +0 -29
- package/dist/storage/workspaceAssetStore.test.d.ts +0 -1
- package/dist/storage/workspaceAssetStore.test.js +0 -588
- package/dist/sync/cloudSyncApi.test.d.ts +0 -1
- package/dist/sync/cloudSyncApi.test.js +0 -111
- package/dist/sync/collaborationSyncPayload.test.d.ts +0 -1
- package/dist/sync/collaborationSyncPayload.test.js +0 -125
- package/dist/sync/contentSyncPayload.test.d.ts +0 -1
- package/dist/sync/contentSyncPayload.test.js +0 -143
- package/dist/sync/encryptionService.test.d.ts +0 -1
- package/dist/sync/encryptionService.test.js +0 -52
- package/dist/sync/planningSyncPayload.test.d.ts +0 -1
- package/dist/sync/planningSyncPayload.test.js +0 -92
- package/dist/sync/realtimeFeature.test.d.ts +0 -1
- package/dist/sync/realtimeFeature.test.js +0 -30
- package/dist/sync/realtimeReconcile.test.d.ts +0 -1
- package/dist/sync/realtimeReconcile.test.js +0 -43
- package/dist/sync/syncApplyHandlers.test.d.ts +0 -1
- package/dist/sync/syncApplyHandlers.test.js +0 -626
- package/dist/sync/syncService.test.d.ts +0 -1
- package/dist/sync/syncService.test.js +0 -903
- package/dist/sync/taskSyncPayload.test.d.ts +0 -1
- package/dist/sync/taskSyncPayload.test.js +0 -54
- package/dist/sync/workspacePullFeed.test.d.ts +0 -1
- package/dist/sync/workspacePullFeed.test.js +0 -438
- package/dist/sync/workspaceSyncModel.test.d.ts +0 -1
- package/dist/sync/workspaceSyncModel.test.js +0 -1442
- package/dist/test/setup.d.ts +0 -1
- package/dist/test/setup.js +0 -15
- package/dist/ui/assets/AgentsModule-BS4Pi_nC.js +0 -1
- package/dist/ui/assets/ContextAttachmentManager-CfG_ER7C.js +0 -3
- package/dist/ui/assets/PlansPage-B_z-D6Nh.css +0 -1
- package/dist/ui/assets/PlansPage-Ndt7mYfo.js +0 -1
- package/dist/ui/assets/documentReferences-BOUcJm6-.js +0 -1
- package/dist/ui/assets/index-MAHKvFqp.js +0 -4
- package/dist/utils/accountProfileSummaryCache.test.d.ts +0 -1
- package/dist/utils/accountProfileSummaryCache.test.js +0 -63
- package/dist/utils/annotatedAttachments.test.d.ts +0 -1
- package/dist/utils/annotatedAttachments.test.js +0 -108
- package/dist/utils/assignees.test.d.ts +0 -1
- package/dist/utils/assignees.test.js +0 -25
- package/dist/utils/avatarUpload.test.d.ts +0 -1
- package/dist/utils/avatarUpload.test.js +0 -40
- package/dist/utils/canonicalAssetKind.test.d.ts +0 -1
- package/dist/utils/canonicalAssetKind.test.js +0 -24
- package/dist/utils/coalescedRefresh.test.d.ts +0 -1
- package/dist/utils/coalescedRefresh.test.js +0 -30
- package/dist/utils/constants.icons.test.d.ts +0 -1
- package/dist/utils/constants.icons.test.js +0 -41
- package/dist/utils/planParser.test.d.ts +0 -1
- package/dist/utils/planParser.test.js +0 -183
- package/dist/utils/planningReferences.test.d.ts +0 -1
- package/dist/utils/planningReferences.test.js +0 -17
- package/dist/utils/syncEventDetails.test.d.ts +0 -1
- package/dist/utils/syncEventDetails.test.js +0 -23
- package/dist/utils/syncEventPresentation.test.d.ts +0 -1
- package/dist/utils/syncEventPresentation.test.js +0 -64
- package/dist/utils/syncStatusPresentation.test.d.ts +0 -1
- package/dist/utils/syncStatusPresentation.test.js +0 -164
- package/dist/utils/taskActivity.test.d.ts +0 -1
- package/dist/utils/taskActivity.test.js +0 -218
- package/dist/utils/taskDraftDefaults.test.d.ts +0 -1
- package/dist/utils/taskDraftDefaults.test.js +0 -34
- package/dist/utils/taskDragZones.test.d.ts +0 -1
- package/dist/utils/taskDragZones.test.js +0 -14
- package/dist/utils/taskNormalization.test.d.ts +0 -1
- package/dist/utils/taskNormalization.test.js +0 -158
- package/dist/utils/taskSearch.test.d.ts +0 -1
- package/dist/utils/taskSearch.test.js +0 -69
- package/dist/utils/taskUtils.test.d.ts +0 -1
- package/dist/utils/taskUtils.test.js +0 -19
- package/dist/utils/taskforceApiClient.test.d.ts +0 -1
- package/dist/utils/taskforceApiClient.test.js +0 -44
- package/dist/utils/taxonomyFormDefaults.test.d.ts +0 -1
- package/dist/utils/taxonomyFormDefaults.test.js +0 -63
- package/dist/utils/uiNotice.test.d.ts +0 -1
- package/dist/utils/uiNotice.test.js +0 -18
- package/dist/utils/workspaceIdentity.test.d.ts +0 -1
- package/dist/utils/workspaceIdentity.test.js +0 -61
- package/dist/utils/workspaceLifecycle.test.d.ts +0 -1
- package/dist/utils/workspaceLifecycle.test.js +0 -64
- package/dist/utils/workspaceSyncPresentation.test.d.ts +0 -1
- package/dist/utils/workspaceSyncPresentation.test.js +0 -71
- package/scripts/bootstrap-playwright-auth.mjs +0 -155
- package/scripts/check-cloud-mcp.mjs +0 -83
- package/scripts/check-compat-boundaries.mjs +0 -41
- package/scripts/check-env.ts +0 -31
- package/scripts/check-localization-strings.mjs +0 -49
- package/scripts/check-runtime-boundaries.mjs +0 -44
- package/scripts/debug-global.mjs +0 -34
- package/scripts/debug-login.mjs +0 -32
- package/scripts/debug-server.mjs +0 -36
- package/scripts/deploy-prod.sh +0 -41
- package/scripts/deploy-staging.sh +0 -23
- package/scripts/export-sqlite-to-postgres.mjs +0 -123
- package/scripts/generate-env-example.ts +0 -8
- package/scripts/mcp-stdio-adapter.mjs +0 -160
- package/scripts/migrate-task-data-assets.mjs +0 -408
- package/scripts/playwright-auth.sh +0 -19
- package/scripts/qa-open-lib.mjs +0 -175
- package/scripts/qa-open-lib.mjs.d.ts +0 -49
- package/scripts/qa-open.mjs +0 -352
- package/scripts/release-version-lib.mjs +0 -88
- package/scripts/release-version.mjs +0 -72
- package/scripts/run-billing-performance-smoke.sh +0 -24
- package/scripts/run-dev-proxy.sh +0 -51
- package/scripts/run-e2e-realtime.sh +0 -30
- package/scripts/run-smoke.sh +0 -35
- package/scripts/run-soak.sh +0 -41
- package/scripts/test-global.js +0 -143
- package/scripts/update-homebrew-formula.sh +0 -108
- package/scripts/with-esbuild.sh +0 -11
|
@@ -1,1674 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
3
|
-
import userEvent from '@testing-library/user-event';
|
|
4
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
5
|
-
import { TaskSettings } from './TaskSettings';
|
|
6
|
-
import { TASKFORCE_THEME_OPTIONS } from '../../utils/theme';
|
|
7
|
-
describe('TaskSettings Component', () => {
|
|
8
|
-
// Launch decision: Cloud MCP settings must stay identity-neutral for shared connections.
|
|
9
|
-
// Do not reintroduce per-connection AI identity controls here unless explicitly requested.
|
|
10
|
-
// Default mock props
|
|
11
|
-
const defaultSettingsModel = {
|
|
12
|
-
currentTheme: 'dark',
|
|
13
|
-
globalTheme: 'dark',
|
|
14
|
-
themeUseGlobalDefault: false,
|
|
15
|
-
keyShortcut: 'Alt+T',
|
|
16
|
-
jsonBackupEnabled: false,
|
|
17
|
-
globalJsonBackupEnabled: false,
|
|
18
|
-
jsonBackupUseGlobalDefault: false,
|
|
19
|
-
showTaskCardStatusLabel: true,
|
|
20
|
-
exportWorkflowsPath: '.agent/workflows',
|
|
21
|
-
exportingResource: null,
|
|
22
|
-
exportResult: null,
|
|
23
|
-
pathSaved: false,
|
|
24
|
-
initialSection: null,
|
|
25
|
-
exportEnvironment: 'antigravity',
|
|
26
|
-
fetchCloudAuthApi: vi.fn(async () => ({
|
|
27
|
-
ok: true,
|
|
28
|
-
json: async () => ({ items: [] })
|
|
29
|
-
})),
|
|
30
|
-
// Mock Callbacks
|
|
31
|
-
onThemeChange: vi.fn(),
|
|
32
|
-
onSaveTheme: vi.fn(),
|
|
33
|
-
onSaveGlobalTheme: vi.fn(),
|
|
34
|
-
onResetProjectToGlobal: vi.fn(),
|
|
35
|
-
onKeyShortcutChange: vi.fn(),
|
|
36
|
-
onJsonBackupEnabledChange: vi.fn(),
|
|
37
|
-
onSaveGlobalJsonBackupEnabled: vi.fn(),
|
|
38
|
-
onSaveGlobalWeekStartsOn: vi.fn(),
|
|
39
|
-
onChecklistDropdownEnabledChange: vi.fn(),
|
|
40
|
-
onShowTaskCardStatusLabelChange: vi.fn(async () => true),
|
|
41
|
-
onSaveSettings: vi.fn(),
|
|
42
|
-
onExportWorkflowsPathChange: vi.fn(),
|
|
43
|
-
onExportEnvironmentChange: vi.fn(),
|
|
44
|
-
onShowFolderBrowserChange: vi.fn(),
|
|
45
|
-
onBrowserTargetChange: vi.fn(),
|
|
46
|
-
onFetchFolders: vi.fn(),
|
|
47
|
-
// Category Manager Props
|
|
48
|
-
categories: [
|
|
49
|
-
{ value: 'general', label: 'General', disabled: false },
|
|
50
|
-
{ value: 'backend', label: 'Backend', color: 'red-500', icon: 'Server', path: 'src/backend' }
|
|
51
|
-
],
|
|
52
|
-
pathValidation: {
|
|
53
|
-
'src/backend': { exists: true, isDirectory: true, isFile: false }
|
|
54
|
-
},
|
|
55
|
-
onUpdateCategory: vi.fn(),
|
|
56
|
-
onRemoveCategory: vi.fn(),
|
|
57
|
-
onSaveCategory: vi.fn(),
|
|
58
|
-
onAddPath: vi.fn(),
|
|
59
|
-
onRemovePath: vi.fn(),
|
|
60
|
-
onUpdateCategoryIcon: vi.fn(),
|
|
61
|
-
onUpdateCategoryColor: vi.fn(),
|
|
62
|
-
// Type Manager Props
|
|
63
|
-
types: [
|
|
64
|
-
{ value: 'task', label: 'Task' },
|
|
65
|
-
{ value: 'issue', label: 'Issue' }
|
|
66
|
-
],
|
|
67
|
-
onSaveType: vi.fn(),
|
|
68
|
-
onRemoveType: vi.fn(),
|
|
69
|
-
onUpdateType: vi.fn(),
|
|
70
|
-
// Taxonomy Props
|
|
71
|
-
taxonomies: [],
|
|
72
|
-
onUpdateTaxonomies: vi.fn(),
|
|
73
|
-
priorities: [
|
|
74
|
-
{ value: 1, label: 'Low', color: 'teal-500', icon: 'ArrowDown' },
|
|
75
|
-
{ value: 2, label: 'Medium', color: 'blue-500', icon: 'Minus' },
|
|
76
|
-
{ value: 3, label: 'High', color: 'amber-500', icon: 'ArrowUp' },
|
|
77
|
-
],
|
|
78
|
-
onUpdatePriorities: vi.fn(),
|
|
79
|
-
onAnalyzeSystemTaxonomyPack: vi.fn(() => ({
|
|
80
|
-
unmatchedCategoryValues: [],
|
|
81
|
-
unmatchedTypeValues: [],
|
|
82
|
-
incompatiblePriorityValues: []
|
|
83
|
-
})),
|
|
84
|
-
onApplySystemTaxonomyPack: vi.fn(async () => ({ success: true })),
|
|
85
|
-
approaches: [
|
|
86
|
-
{ value: 'evaluate', label: 'Evaluate', color: 'amber-500', icon: 'Search' },
|
|
87
|
-
{ value: 'collaborate', label: 'Collaborate', color: 'blue-500', icon: 'Users' },
|
|
88
|
-
{ value: 'plan', label: 'Plan', color: 'teal-500', icon: 'FileText' },
|
|
89
|
-
],
|
|
90
|
-
onUpdateApproaches: vi.fn(),
|
|
91
|
-
};
|
|
92
|
-
const defaultProps = {
|
|
93
|
-
settingsModel: defaultSettingsModel,
|
|
94
|
-
onSectionChange: vi.fn()
|
|
95
|
-
};
|
|
96
|
-
const renderComponent = (overrides = {}) => {
|
|
97
|
-
const { settingsModel: settingsModelOverrides = {}, ...rest } = overrides;
|
|
98
|
-
const mergedSettingsModel = {
|
|
99
|
-
...defaultSettingsModel,
|
|
100
|
-
...settingsModelOverrides
|
|
101
|
-
};
|
|
102
|
-
if (Object.prototype.hasOwnProperty.call(overrides, 'initialSection')) {
|
|
103
|
-
mergedSettingsModel.initialSection = overrides.initialSection;
|
|
104
|
-
}
|
|
105
|
-
const props = {
|
|
106
|
-
...defaultProps,
|
|
107
|
-
...rest,
|
|
108
|
-
settingsModel: mergedSettingsModel
|
|
109
|
-
};
|
|
110
|
-
return render(_jsx(TaskSettings, { ...props }));
|
|
111
|
-
};
|
|
112
|
-
const renderMcpComponent = (overrides = {}) => {
|
|
113
|
-
const { settingsModel: settingsModelOverrides = {}, ...rest } = overrides;
|
|
114
|
-
return renderComponent({
|
|
115
|
-
...rest,
|
|
116
|
-
renderMode: 'mcp-only',
|
|
117
|
-
settingsModel: {
|
|
118
|
-
...settingsModelOverrides,
|
|
119
|
-
initialSection: 'mcp'
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
};
|
|
123
|
-
const openMcpSettings = async (user) => {
|
|
124
|
-
const mcpButton = screen.queryByRole('button', { name: /^MCP$/i });
|
|
125
|
-
if (mcpButton) {
|
|
126
|
-
await user.click(mcpButton);
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
const openCloudMcpSettings = async (user) => {
|
|
130
|
-
await openMcpSettings(user);
|
|
131
|
-
const cloudToggle = screen.queryByRole('button', { name: 'Cloud MCP' });
|
|
132
|
-
if (cloudToggle) {
|
|
133
|
-
await user.click(cloudToggle);
|
|
134
|
-
}
|
|
135
|
-
await screen.findByText('Cloud MCP Configuration');
|
|
136
|
-
};
|
|
137
|
-
const originalFetch = global.fetch;
|
|
138
|
-
it('4.1 Open settings - Renders correctly', () => {
|
|
139
|
-
renderComponent();
|
|
140
|
-
expect(screen.getByText('Sections')).toBeInTheDocument();
|
|
141
|
-
expect(screen.getByRole('button', { name: /general/i })).toBeInTheDocument();
|
|
142
|
-
});
|
|
143
|
-
it('4.1a Sidebar renders all primary settings sections', () => {
|
|
144
|
-
renderComponent();
|
|
145
|
-
expect(screen.getByRole('button', { name: /general/i })).toBeInTheDocument();
|
|
146
|
-
expect(screen.getByRole('button', { name: /appearance/i })).toBeInTheDocument();
|
|
147
|
-
expect(screen.getByRole('button', { name: /application/i })).toBeInTheDocument();
|
|
148
|
-
expect(screen.getByRole('button', { name: /task cards/i })).toBeInTheDocument();
|
|
149
|
-
expect(screen.getByRole('button', { name: /task forms/i })).toBeInTheDocument();
|
|
150
|
-
expect(screen.queryByRole('button', { name: /documents/i })).not.toBeInTheDocument();
|
|
151
|
-
expect(screen.queryByRole('button', { name: /commands/i })).not.toBeInTheDocument();
|
|
152
|
-
expect(screen.queryByRole('button', { name: /initiatives/i })).not.toBeInTheDocument();
|
|
153
|
-
expect(screen.queryByRole('button', { name: /workflows/i })).not.toBeInTheDocument();
|
|
154
|
-
expect(screen.queryByRole('button', { name: /agents/i })).not.toBeInTheDocument();
|
|
155
|
-
expect(screen.getByRole('button', { name: /taxonomy/i })).toBeInTheDocument();
|
|
156
|
-
expect(screen.queryByRole('button', { name: /^mcp$/i })).not.toBeInTheDocument();
|
|
157
|
-
expect(screen.getByRole('button', { name: /integrations/i })).toBeInTheDocument();
|
|
158
|
-
});
|
|
159
|
-
it('4.1b General tab removes old profile/account chrome', () => {
|
|
160
|
-
renderComponent();
|
|
161
|
-
expect(screen.queryByText('Workspace Profile')).not.toBeInTheDocument();
|
|
162
|
-
expect(screen.queryByText('Account Plan')).not.toBeInTheDocument();
|
|
163
|
-
expect(screen.queryByText('Project Settings')).not.toBeInTheDocument();
|
|
164
|
-
expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument();
|
|
165
|
-
expect(screen.getByRole('button', { name: 'Reset to Global' })).toBeInTheDocument();
|
|
166
|
-
});
|
|
167
|
-
it('4.1c Danger zone is hidden in local mode', () => {
|
|
168
|
-
renderComponent({
|
|
169
|
-
settingsModel: {
|
|
170
|
-
runtimeMode: 'local',
|
|
171
|
-
workspaceSwitchingEnabled: true,
|
|
172
|
-
isAuthenticated: true,
|
|
173
|
-
currentWorkspaceRole: 'owner'
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
expect(screen.queryByText('Danger Zone')).not.toBeInTheDocument();
|
|
177
|
-
});
|
|
178
|
-
it('4.1d Global tab no longer shows operating mode', async () => {
|
|
179
|
-
const user = userEvent.setup();
|
|
180
|
-
renderComponent();
|
|
181
|
-
await user.click(screen.getByRole('button', { name: 'Global' }));
|
|
182
|
-
expect(screen.queryByText('Operating Mode')).not.toBeInTheDocument();
|
|
183
|
-
});
|
|
184
|
-
it('4.3 Theme toggle', async () => {
|
|
185
|
-
const user = userEvent.setup();
|
|
186
|
-
renderComponent();
|
|
187
|
-
await user.click(screen.getByRole('button', { name: /application/i }));
|
|
188
|
-
const lightBtn = screen.getByRole('button', { name: /^light$/i });
|
|
189
|
-
await user.click(lightBtn);
|
|
190
|
-
expect(defaultSettingsModel.onThemeChange).toHaveBeenCalledWith('light');
|
|
191
|
-
expect(defaultSettingsModel.onSaveTheme).toHaveBeenCalledWith('light');
|
|
192
|
-
});
|
|
193
|
-
it('4.3a Renders all registered themes in the picker', async () => {
|
|
194
|
-
const user = userEvent.setup();
|
|
195
|
-
renderComponent();
|
|
196
|
-
await user.click(screen.getByRole('button', { name: /application/i }));
|
|
197
|
-
for (const themeOption of TASKFORCE_THEME_OPTIONS) {
|
|
198
|
-
expect(screen.getByRole('button', { name: new RegExp(`^${themeOption.label}$`, 'i') })).toBeInTheDocument();
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
it('4.4 Theme persists', async () => {
|
|
202
|
-
const user = userEvent.setup();
|
|
203
|
-
// This tests that the callback is invoked, persistence is handled by parent
|
|
204
|
-
renderComponent();
|
|
205
|
-
await user.click(screen.getByRole('button', { name: /application/i }));
|
|
206
|
-
const midnightBtn = screen.getByRole('button', { name: /^midnight$/i });
|
|
207
|
-
await user.click(midnightBtn);
|
|
208
|
-
expect(defaultSettingsModel.onThemeChange).toHaveBeenCalledWith('midnight');
|
|
209
|
-
expect(defaultSettingsModel.onSaveTheme).toHaveBeenCalledWith('midnight');
|
|
210
|
-
});
|
|
211
|
-
it('4.4a Dawn theme can be selected', async () => {
|
|
212
|
-
const user = userEvent.setup();
|
|
213
|
-
renderComponent();
|
|
214
|
-
await user.click(screen.getByRole('button', { name: /application/i }));
|
|
215
|
-
const dawnBtn = screen.getByRole('button', { name: /^dawn$/i });
|
|
216
|
-
await user.click(dawnBtn);
|
|
217
|
-
expect(defaultSettingsModel.onThemeChange).toHaveBeenCalledWith('dawn');
|
|
218
|
-
expect(defaultSettingsModel.onSaveTheme).toHaveBeenCalledWith('dawn');
|
|
219
|
-
});
|
|
220
|
-
it('4.4b JSON backup toggle updates setting', async () => {
|
|
221
|
-
const user = userEvent.setup();
|
|
222
|
-
renderComponent();
|
|
223
|
-
const backupToggle = screen.getByRole('checkbox', { name: /enable automatic json backup/i });
|
|
224
|
-
await user.click(backupToggle);
|
|
225
|
-
expect(defaultSettingsModel.onJsonBackupEnabledChange).toHaveBeenCalledWith(true);
|
|
226
|
-
});
|
|
227
|
-
it('4.4c JSON backup controls are hidden in cloud mode', async () => {
|
|
228
|
-
const user = userEvent.setup();
|
|
229
|
-
renderComponent({
|
|
230
|
-
settingsModel: {
|
|
231
|
-
runtimeMode: 'cloud'
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
expect(screen.queryByRole('checkbox', { name: /enable automatic json backup/i })).not.toBeInTheDocument();
|
|
235
|
-
await user.click(screen.getByRole('button', { name: 'Global' }));
|
|
236
|
-
expect(screen.queryByRole('checkbox', { name: /enable global json backup default/i })).not.toBeInTheDocument();
|
|
237
|
-
});
|
|
238
|
-
it('4.4d Task forms section owns checklist dropdown setting', async () => {
|
|
239
|
-
const user = userEvent.setup();
|
|
240
|
-
renderComponent();
|
|
241
|
-
await user.click(screen.getByRole('button', { name: /task forms/i }));
|
|
242
|
-
const checklistToggle = screen.getByRole('checkbox', { name: /enable checklist dropdown in task form/i });
|
|
243
|
-
await user.click(checklistToggle);
|
|
244
|
-
expect(defaultSettingsModel.onChecklistDropdownEnabledChange).toHaveBeenCalledWith(false);
|
|
245
|
-
});
|
|
246
|
-
it('4.4e Task cards section owns the status label setting', async () => {
|
|
247
|
-
const user = userEvent.setup();
|
|
248
|
-
renderComponent();
|
|
249
|
-
await user.click(screen.getByRole('button', { name: /task cards/i }));
|
|
250
|
-
const statusLabelToggle = screen.getByRole('checkbox', { name: /show status label/i });
|
|
251
|
-
await user.click(statusLabelToggle);
|
|
252
|
-
expect(defaultSettingsModel.onShowTaskCardStatusLabelChange).toHaveBeenCalledWith(false);
|
|
253
|
-
});
|
|
254
|
-
it('4.4f Initial appearance section opens directly', () => {
|
|
255
|
-
renderComponent({ initialSection: 'appearance-task-forms' });
|
|
256
|
-
expect(screen.getByRole('checkbox', { name: /enable checklist dropdown in task form/i })).toBeInTheDocument();
|
|
257
|
-
});
|
|
258
|
-
// 4.5 Open category manager - It's always visible in the new design
|
|
259
|
-
it('4.5 Categories section is visible', () => {
|
|
260
|
-
renderComponent({ initialSection: 'categories' });
|
|
261
|
-
expect(screen.getAllByText('Categories')[0]).toBeInTheDocument();
|
|
262
|
-
});
|
|
263
|
-
it('4.6 Add category', async () => {
|
|
264
|
-
const user = userEvent.setup();
|
|
265
|
-
renderComponent({ initialSection: 'categories' });
|
|
266
|
-
// Click create button
|
|
267
|
-
const createBtn = screen.getByTitle('Create new category');
|
|
268
|
-
await user.click(createBtn);
|
|
269
|
-
// Type name
|
|
270
|
-
const input = screen.getByPlaceholderText('e.g. Backend, Frontend, API...');
|
|
271
|
-
await user.type(input, 'Frontend');
|
|
272
|
-
// Click Create
|
|
273
|
-
const submitBtn = screen.getByRole('button', { name: 'Create' });
|
|
274
|
-
await user.click(submitBtn);
|
|
275
|
-
expect(defaultSettingsModel.onSaveCategory).toHaveBeenCalledWith('Frontend');
|
|
276
|
-
});
|
|
277
|
-
it('4.7 Rename category', async () => {
|
|
278
|
-
const user = userEvent.setup();
|
|
279
|
-
renderComponent({ initialSection: 'categories' });
|
|
280
|
-
// Select a category (using custom dropdown)
|
|
281
|
-
const dropdown = screen.getByText('Select a category to configure...').closest('button');
|
|
282
|
-
await user.click(dropdown);
|
|
283
|
-
const option = screen.getByText('Backend');
|
|
284
|
-
await user.click(option);
|
|
285
|
-
// Find label input - getByDisplayValue finds both option and input, so we filter
|
|
286
|
-
const elements = screen.getAllByDisplayValue('Backend');
|
|
287
|
-
const labelInput = elements.find(el => el.tagName === 'INPUT');
|
|
288
|
-
if (!labelInput)
|
|
289
|
-
throw new Error('Could not find input with value "Backend"');
|
|
290
|
-
await user.clear(labelInput);
|
|
291
|
-
await user.type(labelInput, 'Backend API');
|
|
292
|
-
await user.tab(); // Trigger blur
|
|
293
|
-
expect(defaultSettingsModel.onUpdateCategory).toHaveBeenCalledWith(expect.objectContaining({ label: 'Backend API' }), 'Backend');
|
|
294
|
-
});
|
|
295
|
-
it('4.8 Retire category', async () => {
|
|
296
|
-
// Mock window.confirm
|
|
297
|
-
vi.spyOn(window, 'confirm').mockImplementation(() => true);
|
|
298
|
-
const user = userEvent.setup();
|
|
299
|
-
renderComponent({ initialSection: 'categories' });
|
|
300
|
-
// Select category
|
|
301
|
-
const dropdown = screen.getByText('Select a category to configure...').closest('button');
|
|
302
|
-
await user.click(dropdown);
|
|
303
|
-
const option = screen.getByText('Backend');
|
|
304
|
-
await user.click(option);
|
|
305
|
-
// Click retire
|
|
306
|
-
const retireBtn = screen.getByTitle('Retire Category');
|
|
307
|
-
await user.click(retireBtn);
|
|
308
|
-
expect(defaultSettingsModel.onRemoveCategory).toHaveBeenCalledWith('backend');
|
|
309
|
-
});
|
|
310
|
-
it('4.8a Protected fallback category cannot be retired', async () => {
|
|
311
|
-
const user = userEvent.setup();
|
|
312
|
-
renderComponent({
|
|
313
|
-
initialSection: 'categories',
|
|
314
|
-
settingsModel: {
|
|
315
|
-
categories: [
|
|
316
|
-
{ value: 'default', label: 'General', disabled: false },
|
|
317
|
-
{ value: 'backend', label: 'Backend', disabled: false }
|
|
318
|
-
]
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
const dropdown = screen.getByText('Select a category to configure...').closest('button');
|
|
322
|
-
await user.click(dropdown);
|
|
323
|
-
await user.click(screen.getAllByText('General').at(-1));
|
|
324
|
-
expect(screen.queryByTitle('Retire Category')).not.toBeInTheDocument();
|
|
325
|
-
expect(screen.getByText('General is the protected fallback category and cannot be retired.')).toBeInTheDocument();
|
|
326
|
-
});
|
|
327
|
-
it('4.9 Category icon', async () => {
|
|
328
|
-
const user = userEvent.setup();
|
|
329
|
-
renderComponent({ initialSection: 'categories' });
|
|
330
|
-
// Select category
|
|
331
|
-
const dropdown = screen.getByText('Select a category to configure...').closest('button');
|
|
332
|
-
await user.click(dropdown);
|
|
333
|
-
const option = screen.getByText('Backend');
|
|
334
|
-
await user.click(option);
|
|
335
|
-
// Click an icon (assuming some icons are rendered)
|
|
336
|
-
// We'll just look for one of the icon buttons
|
|
337
|
-
const iconBtn = screen.getByTitle('Bug'); // Assuming Bug is in AVAILABLE_ICONS
|
|
338
|
-
await user.click(iconBtn);
|
|
339
|
-
expect(defaultSettingsModel.onUpdateCategoryIcon).toHaveBeenCalledWith('backend', 'Bug');
|
|
340
|
-
});
|
|
341
|
-
it('4.10 Category color', async () => {
|
|
342
|
-
const user = userEvent.setup();
|
|
343
|
-
renderComponent({ initialSection: 'categories' });
|
|
344
|
-
// Select category
|
|
345
|
-
const dropdown = screen.getByText('Select a category to configure...').closest('button');
|
|
346
|
-
await user.click(dropdown);
|
|
347
|
-
const option = screen.getByText('Backend');
|
|
348
|
-
await user.click(option);
|
|
349
|
-
// Click a color swatch
|
|
350
|
-
const colorBtn = screen.getByTitle('green-500'); // Use color token
|
|
351
|
-
await user.click(colorBtn);
|
|
352
|
-
expect(defaultSettingsModel.onUpdateCategoryColor).toHaveBeenCalledWith('backend', 'green-500');
|
|
353
|
-
});
|
|
354
|
-
it('4.11 Context paths', async () => {
|
|
355
|
-
const user = userEvent.setup();
|
|
356
|
-
renderComponent({ initialSection: 'categories' });
|
|
357
|
-
// Select category
|
|
358
|
-
const dropdown = screen.getByText('Select a category to configure...').closest('button');
|
|
359
|
-
await user.click(dropdown);
|
|
360
|
-
const option = screen.getByText('Backend');
|
|
361
|
-
await user.click(option);
|
|
362
|
-
// Add path
|
|
363
|
-
const input = screen.getByPlaceholderText('Add path (e.g. frontend/src/...)');
|
|
364
|
-
await user.type(input, 'src/api');
|
|
365
|
-
const addBtn = screen.getByTitle('Add path');
|
|
366
|
-
await user.click(addBtn);
|
|
367
|
-
expect(defaultSettingsModel.onAddPath).toHaveBeenCalledWith('backend', 'src/api');
|
|
368
|
-
});
|
|
369
|
-
// 4.12 Open type manager - Always visible
|
|
370
|
-
it('4.12 Task Types section is visible', () => {
|
|
371
|
-
renderComponent({ initialSection: 'types' });
|
|
372
|
-
expect(screen.getAllByText('Task Types')[0]).toBeInTheDocument();
|
|
373
|
-
});
|
|
374
|
-
it('4.13 Add type', async () => {
|
|
375
|
-
const user = userEvent.setup();
|
|
376
|
-
renderComponent({ initialSection: 'types' });
|
|
377
|
-
// Find type section create button (it's the second "Create new..." button effectively, but inside TypeManager)
|
|
378
|
-
// Best to scope by container or find by unique attributes
|
|
379
|
-
const typeDropdown = screen.getByText('Select a type to configure...').closest('button');
|
|
380
|
-
await user.click(typeDropdown);
|
|
381
|
-
// Click create button next to types select
|
|
382
|
-
// We can find it by looking for the button with title "Create new type"
|
|
383
|
-
const createTypeBtn = screen.getByTitle('Create new type');
|
|
384
|
-
await user.click(createTypeBtn);
|
|
385
|
-
const input = screen.getByPlaceholderText('e.g. Bug, Feature, Chore...');
|
|
386
|
-
await user.type(input, 'Refactor');
|
|
387
|
-
const createBtn = screen.getByRole('button', { name: 'Create' });
|
|
388
|
-
await user.click(createBtn);
|
|
389
|
-
expect(defaultSettingsModel.onSaveType).toHaveBeenCalledWith('Refactor');
|
|
390
|
-
});
|
|
391
|
-
it('4.14 Retire type', async () => {
|
|
392
|
-
vi.spyOn(window, 'confirm').mockImplementation(() => true);
|
|
393
|
-
const user = userEvent.setup();
|
|
394
|
-
renderComponent({ initialSection: 'types' });
|
|
395
|
-
const dropdown = screen.getByText('Select a type to configure...').closest('button');
|
|
396
|
-
await user.click(dropdown);
|
|
397
|
-
const option = screen.getAllByText('Issue').at(-1);
|
|
398
|
-
await user.click(option);
|
|
399
|
-
const retireBtn = screen.getByTitle('Retire Type');
|
|
400
|
-
await user.click(retireBtn);
|
|
401
|
-
expect(defaultSettingsModel.onRemoveType).toHaveBeenCalledWith('issue');
|
|
402
|
-
});
|
|
403
|
-
it('4.14ab Retired types can be restored', async () => {
|
|
404
|
-
const user = userEvent.setup();
|
|
405
|
-
renderComponent({
|
|
406
|
-
initialSection: 'types',
|
|
407
|
-
settingsModel: {
|
|
408
|
-
types: [
|
|
409
|
-
{ value: 'task', label: 'Task', color: 'blue-500', icon: 'CheckSquare', status: 'active' },
|
|
410
|
-
{ value: 'issue', label: 'Issue', color: 'red-500', icon: 'AlertCircle', status: 'retired' }
|
|
411
|
-
]
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
const dropdown = screen.getByText('Select a type to configure...').closest('button');
|
|
415
|
-
await user.click(dropdown);
|
|
416
|
-
await user.click(screen.getByText('Issue (Retired)'));
|
|
417
|
-
const restoreBtn = screen.getByTitle('Restore Type');
|
|
418
|
-
await user.click(restoreBtn);
|
|
419
|
-
expect(defaultSettingsModel.onUpdateType).toHaveBeenCalledWith('issue', { status: 'active' });
|
|
420
|
-
});
|
|
421
|
-
it('4.14aa Protected fallback type cannot be retired', async () => {
|
|
422
|
-
const user = userEvent.setup();
|
|
423
|
-
renderComponent({
|
|
424
|
-
initialSection: 'types',
|
|
425
|
-
settingsModel: {
|
|
426
|
-
types: [
|
|
427
|
-
{ value: 'task', label: 'Task', color: 'blue-500', icon: 'CheckSquare' },
|
|
428
|
-
{ value: 'review', label: 'Review', color: 'violet-500', icon: 'Search' }
|
|
429
|
-
]
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
const dropdown = screen.getByText('Select a type to configure...').closest('button');
|
|
433
|
-
await user.click(dropdown);
|
|
434
|
-
await user.click(screen.getByText('Task'));
|
|
435
|
-
expect(screen.queryByTitle('Retire Type')).not.toBeInTheDocument();
|
|
436
|
-
expect(screen.getByText('Task is the protected fallback type and cannot be retired.')).toBeInTheDocument();
|
|
437
|
-
});
|
|
438
|
-
it('4.14a Rename type', async () => {
|
|
439
|
-
const user = userEvent.setup();
|
|
440
|
-
renderComponent({ initialSection: 'types' });
|
|
441
|
-
const dropdown = screen.getByText('Select a type to configure...').closest('button');
|
|
442
|
-
await user.click(dropdown);
|
|
443
|
-
const option = screen.getByText('Task');
|
|
444
|
-
await user.click(option);
|
|
445
|
-
const labelInput = screen.getByDisplayValue('Task');
|
|
446
|
-
await user.clear(labelInput);
|
|
447
|
-
await user.type(labelInput, 'Action Item');
|
|
448
|
-
await user.tab();
|
|
449
|
-
expect(defaultSettingsModel.onUpdateType).toHaveBeenCalledWith('task', { label: 'Action Item' });
|
|
450
|
-
});
|
|
451
|
-
it('4.14b Update type color', async () => {
|
|
452
|
-
const user = userEvent.setup();
|
|
453
|
-
renderComponent({ initialSection: 'types' });
|
|
454
|
-
const dropdown = screen.getByText('Select a type to configure...').closest('button');
|
|
455
|
-
await user.click(dropdown);
|
|
456
|
-
await user.click(screen.getByText('Task'));
|
|
457
|
-
await user.click(screen.getByTitle('green-500'));
|
|
458
|
-
expect(defaultSettingsModel.onUpdateType).toHaveBeenCalledWith('task', { color: 'green-500' });
|
|
459
|
-
});
|
|
460
|
-
it('4.15 Export workflows', async () => {
|
|
461
|
-
renderComponent({ initialSection: 'commands' });
|
|
462
|
-
expect(screen.getByText('Command Workflows')).toBeInTheDocument();
|
|
463
|
-
});
|
|
464
|
-
it('4.14c Priority editor hides generic taxonomy controls', async () => {
|
|
465
|
-
const user = userEvent.setup();
|
|
466
|
-
renderComponent();
|
|
467
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
468
|
-
const priorityRow = screen.getByText('Priority Levels');
|
|
469
|
-
await user.click(priorityRow.closest('button'));
|
|
470
|
-
const dropdown = screen.getByText('Select a priority level to configure...').closest('button');
|
|
471
|
-
await user.click(dropdown);
|
|
472
|
-
await user.click(screen.getByText('Medium'));
|
|
473
|
-
expect(screen.queryByText('Mark as Required')).not.toBeInTheDocument();
|
|
474
|
-
expect(screen.queryByText('Allow Multi-select')).not.toBeInTheDocument();
|
|
475
|
-
expect(screen.queryByText('Widget Type')).not.toBeInTheDocument();
|
|
476
|
-
expect(screen.getByText(/fixed ordered scale/i)).toBeInTheDocument();
|
|
477
|
-
});
|
|
478
|
-
it('4.14c1 Taxonomy libraries are visible from the taxonomy settings list', async () => {
|
|
479
|
-
const user = userEvent.setup();
|
|
480
|
-
renderComponent();
|
|
481
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
482
|
-
expect(screen.getByRole('button', { name: /system taxonomy library/i })).toBeInTheDocument();
|
|
483
|
-
expect(screen.getByText(/browse curated packs for categories, task types, and priority scales/i)).toBeInTheDocument();
|
|
484
|
-
expect(screen.getByRole('button', { name: /custom taxonomy library/i })).toBeInTheDocument();
|
|
485
|
-
});
|
|
486
|
-
it('4.14c2 System Taxonomy Library opens preview and apply controls', async () => {
|
|
487
|
-
const user = userEvent.setup();
|
|
488
|
-
renderComponent();
|
|
489
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
490
|
-
await user.click(screen.getByRole('button', { name: /system taxonomy library/i }));
|
|
491
|
-
expect(screen.getByText('Browse starter packs before you replace anything.')).toBeInTheDocument();
|
|
492
|
-
expect(screen.getByText('Software Development')).toBeInTheDocument();
|
|
493
|
-
expect(screen.getByText('Apply Selected Sections')).toBeInTheDocument();
|
|
494
|
-
expect(screen.getByRole('checkbox', { name: 'Categories' })).toBeInTheDocument();
|
|
495
|
-
expect(screen.getByRole('checkbox', { name: 'Types' })).toBeInTheDocument();
|
|
496
|
-
expect(screen.getByRole('checkbox', { name: 'Priorities' })).toBeInTheDocument();
|
|
497
|
-
expect(screen.getByRole('checkbox', { name: /remap unmatched existing values to default/i })).toBeInTheDocument();
|
|
498
|
-
});
|
|
499
|
-
it('4.14c2b System Taxonomy Library applies a selected pack', async () => {
|
|
500
|
-
const user = userEvent.setup();
|
|
501
|
-
const onApplySystemTaxonomyPack = vi.fn(async () => ({ success: true }));
|
|
502
|
-
renderComponent({
|
|
503
|
-
settingsModel: {
|
|
504
|
-
onApplySystemTaxonomyPack
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
508
|
-
await user.click(screen.getByRole('button', { name: /system taxonomy library/i }));
|
|
509
|
-
await user.click(screen.getByRole('button', { name: 'Apply Pack' }));
|
|
510
|
-
expect(onApplySystemTaxonomyPack).toHaveBeenCalledWith(expect.objectContaining({
|
|
511
|
-
pack: expect.objectContaining({ id: 'general' }),
|
|
512
|
-
sections: {
|
|
513
|
-
categories: true,
|
|
514
|
-
types: true,
|
|
515
|
-
priorities: true
|
|
516
|
-
},
|
|
517
|
-
remapExistingValuesToDefault: false
|
|
518
|
-
}));
|
|
519
|
-
expect(await screen.findByText(/was applied to the selected taxonomy sections/i)).toBeInTheDocument();
|
|
520
|
-
});
|
|
521
|
-
it('4.14c2c System Taxonomy Library surfaces preflight warnings for unmatched values', async () => {
|
|
522
|
-
const user = userEvent.setup();
|
|
523
|
-
renderComponent({
|
|
524
|
-
settingsModel: {
|
|
525
|
-
onAnalyzeSystemTaxonomyPack: vi.fn(() => ({
|
|
526
|
-
unmatchedCategoryValues: ['marketing'],
|
|
527
|
-
unmatchedTypeValues: ['request', 'incident'],
|
|
528
|
-
incompatiblePriorityValues: [4]
|
|
529
|
-
}))
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
533
|
-
await user.click(screen.getByRole('button', { name: /system taxonomy library/i }));
|
|
534
|
-
expect(screen.getByText(/1 category value will be preserved as legacy/i)).toBeInTheDocument();
|
|
535
|
-
expect(screen.getByText(/2 type values will be preserved as legacy/i)).toBeInTheDocument();
|
|
536
|
-
expect(screen.getByText(/priority replacement will be blocked unless you remap/i)).toBeInTheDocument();
|
|
537
|
-
});
|
|
538
|
-
it('4.14c3 Custom Taxonomy Library opens the custom pack browser', async () => {
|
|
539
|
-
const user = userEvent.setup();
|
|
540
|
-
renderComponent();
|
|
541
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
542
|
-
await user.click(screen.getByRole('button', { name: /custom taxonomy library/i }));
|
|
543
|
-
expect(screen.getByText('Browse reusable custom taxonomy packs.')).toBeInTheDocument();
|
|
544
|
-
expect(screen.getByText('Choose Taxonomies to Install')).toBeInTheDocument();
|
|
545
|
-
expect(screen.getByRole('button', { name: /install selected taxonomies/i })).toBeInTheDocument();
|
|
546
|
-
});
|
|
547
|
-
it('4.14d Retires custom taxonomy options instead of deleting them', async () => {
|
|
548
|
-
const user = userEvent.setup();
|
|
549
|
-
renderComponent({
|
|
550
|
-
settingsModel: {
|
|
551
|
-
taxonomies: [{
|
|
552
|
-
id: 'review-status',
|
|
553
|
-
label: 'Review Status',
|
|
554
|
-
widgetType: 'select',
|
|
555
|
-
isSystem: false,
|
|
556
|
-
multiSelect: false,
|
|
557
|
-
options: [
|
|
558
|
-
{ value: 'pending', label: 'Pending', status: 'active' },
|
|
559
|
-
{ value: 'legal-review', label: 'Legal Review', status: 'active' }
|
|
560
|
-
]
|
|
561
|
-
}]
|
|
562
|
-
}
|
|
563
|
-
});
|
|
564
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
565
|
-
await user.click(screen.getByText('Review Status').closest('button'));
|
|
566
|
-
const dropdown = screen.getByText('Select an option to configure...').closest('button');
|
|
567
|
-
await user.click(dropdown);
|
|
568
|
-
await user.click(screen.getByText('Legal Review'));
|
|
569
|
-
await user.click(screen.getByTitle('Retire Option'));
|
|
570
|
-
expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenCalledWith([
|
|
571
|
-
expect.objectContaining({
|
|
572
|
-
id: 'review-status',
|
|
573
|
-
options: [
|
|
574
|
-
expect.objectContaining({ value: 'pending', status: 'active' }),
|
|
575
|
-
expect.objectContaining({ value: 'legal-review', status: 'retired' })
|
|
576
|
-
]
|
|
577
|
-
})
|
|
578
|
-
]);
|
|
579
|
-
});
|
|
580
|
-
it('4.14e Toggles custom taxonomy filter visibility', async () => {
|
|
581
|
-
const user = userEvent.setup();
|
|
582
|
-
renderComponent({
|
|
583
|
-
settingsModel: {
|
|
584
|
-
taxonomies: [{
|
|
585
|
-
id: 'review-status',
|
|
586
|
-
label: 'Review Status',
|
|
587
|
-
widgetType: 'select',
|
|
588
|
-
isSystem: false,
|
|
589
|
-
multiSelect: false,
|
|
590
|
-
filterEnabled: true,
|
|
591
|
-
options: [
|
|
592
|
-
{ value: 'pending', label: 'Pending', status: 'active' }
|
|
593
|
-
]
|
|
594
|
-
}]
|
|
595
|
-
}
|
|
596
|
-
});
|
|
597
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
598
|
-
await user.click(screen.getByText('Review Status').closest('button'));
|
|
599
|
-
const filterToggle = screen.getByRole('checkbox', { name: 'Show in Filters' });
|
|
600
|
-
await user.click(filterToggle);
|
|
601
|
-
expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenCalledWith([
|
|
602
|
-
expect.objectContaining({
|
|
603
|
-
id: 'review-status',
|
|
604
|
-
filterEnabled: false
|
|
605
|
-
})
|
|
606
|
-
]);
|
|
607
|
-
});
|
|
608
|
-
it('4.14e1 Toggles custom taxonomy task-form visibility', async () => {
|
|
609
|
-
const user = userEvent.setup();
|
|
610
|
-
renderComponent({
|
|
611
|
-
settingsModel: {
|
|
612
|
-
taxonomies: [{
|
|
613
|
-
id: 'review-status',
|
|
614
|
-
label: 'Review Status',
|
|
615
|
-
widgetType: 'select',
|
|
616
|
-
isSystem: false,
|
|
617
|
-
multiSelect: false,
|
|
618
|
-
formEnabled: true,
|
|
619
|
-
options: [
|
|
620
|
-
{ value: 'pending', label: 'Pending', status: 'active' }
|
|
621
|
-
]
|
|
622
|
-
}]
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
626
|
-
await user.click(screen.getByText('Review Status').closest('button'));
|
|
627
|
-
const formToggle = screen.getByRole('checkbox', { name: 'Show in Task Form' });
|
|
628
|
-
await user.click(formToggle);
|
|
629
|
-
expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenCalledWith([
|
|
630
|
-
expect.objectContaining({
|
|
631
|
-
id: 'review-status',
|
|
632
|
-
formEnabled: false
|
|
633
|
-
})
|
|
634
|
-
]);
|
|
635
|
-
});
|
|
636
|
-
it('4.14f Updates custom taxonomy description', async () => {
|
|
637
|
-
const user = userEvent.setup();
|
|
638
|
-
renderComponent({
|
|
639
|
-
settingsModel: {
|
|
640
|
-
taxonomies: [{
|
|
641
|
-
id: 'review-status',
|
|
642
|
-
label: 'Review Status',
|
|
643
|
-
widgetType: 'select',
|
|
644
|
-
isSystem: false,
|
|
645
|
-
multiSelect: false,
|
|
646
|
-
options: [
|
|
647
|
-
{ value: 'pending', label: 'Pending', status: 'active' }
|
|
648
|
-
]
|
|
649
|
-
}]
|
|
650
|
-
}
|
|
651
|
-
});
|
|
652
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
653
|
-
await user.click(screen.getByText('Review Status').closest('button'));
|
|
654
|
-
const descriptionInput = screen.getByPlaceholderText('Optional guidance for when and how to use this taxonomy...');
|
|
655
|
-
fireEvent.change(descriptionInput, { target: { value: 'Use this to track review routing.' } });
|
|
656
|
-
expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
|
|
657
|
-
expect.objectContaining({
|
|
658
|
-
id: 'review-status',
|
|
659
|
-
description: 'Use this to track review routing.'
|
|
660
|
-
})
|
|
661
|
-
]);
|
|
662
|
-
});
|
|
663
|
-
it('4.14g Updates custom taxonomy aliases', async () => {
|
|
664
|
-
const user = userEvent.setup();
|
|
665
|
-
renderComponent({
|
|
666
|
-
settingsModel: {
|
|
667
|
-
taxonomies: [{
|
|
668
|
-
id: 'review-status',
|
|
669
|
-
label: 'Review Status',
|
|
670
|
-
widgetType: 'select',
|
|
671
|
-
isSystem: false,
|
|
672
|
-
multiSelect: false,
|
|
673
|
-
options: [
|
|
674
|
-
{ value: 'pending', label: 'Pending', status: 'active' }
|
|
675
|
-
]
|
|
676
|
-
}]
|
|
677
|
-
}
|
|
678
|
-
});
|
|
679
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
680
|
-
await user.click(screen.getByText('Review Status').closest('button'));
|
|
681
|
-
const aliasInputs = screen.getAllByPlaceholderText('Optional aliases, comma-separated');
|
|
682
|
-
fireEvent.change(aliasInputs[0], { target: { value: 'review route, routing' } });
|
|
683
|
-
fireEvent.blur(aliasInputs[0]);
|
|
684
|
-
expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
|
|
685
|
-
expect.objectContaining({
|
|
686
|
-
id: 'review-status',
|
|
687
|
-
aliases: ['review route', 'routing']
|
|
688
|
-
})
|
|
689
|
-
]);
|
|
690
|
-
});
|
|
691
|
-
it('4.14h Updates custom taxonomy option aliases', async () => {
|
|
692
|
-
const user = userEvent.setup();
|
|
693
|
-
renderComponent({
|
|
694
|
-
settingsModel: {
|
|
695
|
-
taxonomies: [{
|
|
696
|
-
id: 'review-status',
|
|
697
|
-
label: 'Review Status',
|
|
698
|
-
widgetType: 'select',
|
|
699
|
-
isSystem: false,
|
|
700
|
-
multiSelect: false,
|
|
701
|
-
options: [
|
|
702
|
-
{ value: 'pending', label: 'Pending', status: 'active' }
|
|
703
|
-
]
|
|
704
|
-
}]
|
|
705
|
-
}
|
|
706
|
-
});
|
|
707
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
708
|
-
await user.click(screen.getByText('Review Status').closest('button'));
|
|
709
|
-
const dropdown = screen.getByText('Select an option to configure...').closest('button');
|
|
710
|
-
await user.click(dropdown);
|
|
711
|
-
await user.click(screen.getByText('Pending'));
|
|
712
|
-
const aliasInputs = screen.getAllByPlaceholderText('Optional aliases, comma-separated');
|
|
713
|
-
fireEvent.change(aliasInputs[1], { target: { value: 'queued, awaiting review' } });
|
|
714
|
-
fireEvent.blur(aliasInputs[1]);
|
|
715
|
-
expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
|
|
716
|
-
expect.objectContaining({
|
|
717
|
-
id: 'review-status',
|
|
718
|
-
options: [
|
|
719
|
-
expect.objectContaining({
|
|
720
|
-
value: 'pending',
|
|
721
|
-
aliases: ['queued', 'awaiting review']
|
|
722
|
-
})
|
|
723
|
-
]
|
|
724
|
-
})
|
|
725
|
-
]);
|
|
726
|
-
});
|
|
727
|
-
it('4.14i Retires whole custom taxonomies instead of deleting them', async () => {
|
|
728
|
-
vi.spyOn(window, 'confirm').mockImplementation(() => true);
|
|
729
|
-
const user = userEvent.setup();
|
|
730
|
-
renderComponent({
|
|
731
|
-
settingsModel: {
|
|
732
|
-
taxonomies: [{
|
|
733
|
-
id: 'review-status',
|
|
734
|
-
label: 'Review Status',
|
|
735
|
-
widgetType: 'select',
|
|
736
|
-
isSystem: false,
|
|
737
|
-
multiSelect: false,
|
|
738
|
-
options: [{ value: 'pending', label: 'Pending', status: 'active' }]
|
|
739
|
-
}]
|
|
740
|
-
}
|
|
741
|
-
});
|
|
742
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
743
|
-
await user.click(screen.getByText('Review Status').closest('button'));
|
|
744
|
-
await user.click(screen.getByRole('button', { name: /retire taxonomy/i }));
|
|
745
|
-
expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
|
|
746
|
-
expect.objectContaining({
|
|
747
|
-
id: 'review-status',
|
|
748
|
-
status: 'retired'
|
|
749
|
-
})
|
|
750
|
-
]);
|
|
751
|
-
});
|
|
752
|
-
it('4.14j Restores retired custom taxonomies', async () => {
|
|
753
|
-
const user = userEvent.setup();
|
|
754
|
-
renderComponent({
|
|
755
|
-
settingsModel: {
|
|
756
|
-
taxonomies: [{
|
|
757
|
-
id: 'review-status',
|
|
758
|
-
label: 'Review Status',
|
|
759
|
-
status: 'retired',
|
|
760
|
-
widgetType: 'select',
|
|
761
|
-
isSystem: false,
|
|
762
|
-
multiSelect: false,
|
|
763
|
-
options: [{ value: 'pending', label: 'Pending', status: 'active' }]
|
|
764
|
-
}]
|
|
765
|
-
}
|
|
766
|
-
});
|
|
767
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
768
|
-
await user.click(screen.getByText(/Review Status \(Retired\)/).closest('button'));
|
|
769
|
-
await user.click(screen.getByRole('button', { name: /restore taxonomy/i }));
|
|
770
|
-
expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
|
|
771
|
-
expect.objectContaining({
|
|
772
|
-
id: 'review-status',
|
|
773
|
-
status: 'active'
|
|
774
|
-
})
|
|
775
|
-
]);
|
|
776
|
-
});
|
|
777
|
-
it('4.14k Reorders custom taxonomies from the list view', async () => {
|
|
778
|
-
const user = userEvent.setup();
|
|
779
|
-
renderComponent({
|
|
780
|
-
settingsModel: {
|
|
781
|
-
taxonomies: [
|
|
782
|
-
{
|
|
783
|
-
id: 'alpha',
|
|
784
|
-
label: 'Alpha',
|
|
785
|
-
widgetType: 'select',
|
|
786
|
-
isSystem: false,
|
|
787
|
-
multiSelect: false,
|
|
788
|
-
options: []
|
|
789
|
-
},
|
|
790
|
-
{
|
|
791
|
-
id: 'beta',
|
|
792
|
-
label: 'Beta',
|
|
793
|
-
widgetType: 'select',
|
|
794
|
-
isSystem: false,
|
|
795
|
-
multiSelect: false,
|
|
796
|
-
options: []
|
|
797
|
-
}
|
|
798
|
-
]
|
|
799
|
-
}
|
|
800
|
-
});
|
|
801
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
802
|
-
await user.click(screen.getAllByTitle('Move taxonomy down').find((button) => !button.hasAttribute('disabled')));
|
|
803
|
-
expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
|
|
804
|
-
expect.objectContaining({ id: 'beta' }),
|
|
805
|
-
expect.objectContaining({ id: 'alpha' })
|
|
806
|
-
]);
|
|
807
|
-
});
|
|
808
|
-
it('4.14l Reorders custom taxonomy options from the detail view', async () => {
|
|
809
|
-
const user = userEvent.setup();
|
|
810
|
-
renderComponent({
|
|
811
|
-
settingsModel: {
|
|
812
|
-
taxonomies: [{
|
|
813
|
-
id: 'review-status',
|
|
814
|
-
label: 'Review Status',
|
|
815
|
-
widgetType: 'select',
|
|
816
|
-
isSystem: false,
|
|
817
|
-
multiSelect: false,
|
|
818
|
-
options: [
|
|
819
|
-
{ value: 'pending', label: 'Pending', status: 'active' },
|
|
820
|
-
{ value: 'approved', label: 'Approved', status: 'active' }
|
|
821
|
-
]
|
|
822
|
-
}]
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
await user.click(screen.getByRole('button', { name: /taxonomy/i }));
|
|
826
|
-
await user.click(screen.getByText('Review Status').closest('button'));
|
|
827
|
-
const dropdown = screen.getByText('Select an option to configure...').closest('button');
|
|
828
|
-
await user.click(dropdown);
|
|
829
|
-
await user.click(screen.getByText('Approved'));
|
|
830
|
-
await user.click(screen.getByTitle('Move option up'));
|
|
831
|
-
expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
|
|
832
|
-
expect.objectContaining({
|
|
833
|
-
id: 'review-status',
|
|
834
|
-
options: [
|
|
835
|
-
expect.objectContaining({ value: 'approved' }),
|
|
836
|
-
expect.objectContaining({ value: 'pending' })
|
|
837
|
-
]
|
|
838
|
-
})
|
|
839
|
-
]);
|
|
840
|
-
});
|
|
841
|
-
it('4.16 Agents section shows registered AI profiles', async () => {
|
|
842
|
-
renderComponent({ initialSection: 'agents' });
|
|
843
|
-
expect(screen.getByText('Registered AI Profiles')).toBeInTheDocument();
|
|
844
|
-
});
|
|
845
|
-
it('4.17 initialSection navigates to detail view', () => {
|
|
846
|
-
renderComponent({ initialSection: 'categories' });
|
|
847
|
-
// In detail view, Categories is shown in detailTitle
|
|
848
|
-
expect(screen.getByText('Categories')).toBeInTheDocument();
|
|
849
|
-
expect(screen.queryByText('System Taxonomies')).not.toBeInTheDocument();
|
|
850
|
-
});
|
|
851
|
-
it('4.18 legacy documents initialSection falls back to general tab', () => {
|
|
852
|
-
renderComponent({ initialSection: 'documents' });
|
|
853
|
-
expect(screen.getByRole('button', { name: /General/i })).toBeInTheDocument();
|
|
854
|
-
expect(screen.queryByText('Cloud Document Storage')).not.toBeInTheDocument();
|
|
855
|
-
});
|
|
856
|
-
it('4.19 document settings copy is not rendered in settings', () => {
|
|
857
|
-
renderComponent();
|
|
858
|
-
expect(screen.queryByText('Document Storage (Admin)')).not.toBeInTheDocument();
|
|
859
|
-
expect(screen.queryByText('Local Documents')).not.toBeInTheDocument();
|
|
860
|
-
expect(screen.queryByText('Cloud Document Storage')).not.toBeInTheDocument();
|
|
861
|
-
expect(screen.queryByRole('button', { name: 'Open App Admin' })).not.toBeInTheDocument();
|
|
862
|
-
});
|
|
863
|
-
it('4.20 Cloud runtime shows cloud MCP only', async () => {
|
|
864
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
865
|
-
ok: true,
|
|
866
|
-
json: async () => ({ items: [] })
|
|
867
|
-
}));
|
|
868
|
-
const user = userEvent.setup();
|
|
869
|
-
renderMcpComponent({
|
|
870
|
-
settingsModel: {
|
|
871
|
-
runtimeMode: 'cloud',
|
|
872
|
-
isAuthenticated: true,
|
|
873
|
-
cloudAuthBaseUrl: 'https://cloud.example.com',
|
|
874
|
-
fetchCloudAuthApi
|
|
875
|
-
}
|
|
876
|
-
});
|
|
877
|
-
await openMcpSettings(user);
|
|
878
|
-
expect(await screen.findByText('Cloud MCP Configuration')).toBeInTheDocument();
|
|
879
|
-
expect(screen.getByText(/Cloud MCP connects agents to Taskforce through the hosted MCP endpoint/i)).toBeInTheDocument();
|
|
880
|
-
expect(screen.getByText(/Once the MCP is configured, prompt your agent:/i)).toBeInTheDocument();
|
|
881
|
-
expect(screen.getByRole('button', { name: 'Resolve your profile with Taskforce.' })).toBeInTheDocument();
|
|
882
|
-
const cloudInstructionLabel = screen.getByText('General MCP Configuration Instructions');
|
|
883
|
-
const cloudProfilePrompt = screen.getByText(/Once the MCP is configured, prompt your agent:/i);
|
|
884
|
-
expect(cloudInstructionLabel.compareDocumentPosition(cloudProfilePrompt) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
|
885
|
-
expect(screen.getByRole('button', { name: 'Generate Token' })).toBeInTheDocument();
|
|
886
|
-
expect(screen.queryByRole('button', { name: 'Local MCP' })).not.toBeInTheDocument();
|
|
887
|
-
expect(screen.queryByText('MCP Path Mapping (Host Root)')).not.toBeInTheDocument();
|
|
888
|
-
expect(fetchCloudAuthApi).toHaveBeenCalledWith('/api/taskforce/settings/mcp/tokens', expect.objectContaining({
|
|
889
|
-
method: 'GET',
|
|
890
|
-
credentials: 'include'
|
|
891
|
-
}));
|
|
892
|
-
}, 10000);
|
|
893
|
-
it('4.20a MCP setup tells users how to register the agent profile', async () => {
|
|
894
|
-
renderMcpComponent({
|
|
895
|
-
settingsModel: {
|
|
896
|
-
runtimeMode: 'local',
|
|
897
|
-
isAuthenticated: false
|
|
898
|
-
}
|
|
899
|
-
});
|
|
900
|
-
expect(screen.getByText(/Local MCP connects through your local Taskforce install/i)).toBeInTheDocument();
|
|
901
|
-
expect(screen.getByText(/Once the MCP is configured, prompt your agent:/i)).toBeInTheDocument();
|
|
902
|
-
expect(screen.getByRole('button', { name: 'Resolve your profile with Taskforce.' })).toBeInTheDocument();
|
|
903
|
-
const localInstructionLabel = screen.getByText('General MCP Configuration Instructions');
|
|
904
|
-
const localProfilePrompt = screen.getByText(/Once the MCP is configured, prompt your agent:/i);
|
|
905
|
-
expect(localInstructionLabel.compareDocumentPosition(localProfilePrompt) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
|
906
|
-
expect(screen.queryByText('Ask Your Agent to Register')).not.toBeInTheDocument();
|
|
907
|
-
});
|
|
908
|
-
it('4.21 Local authenticated runtime defaults MCP tab to local with local-first toggle', async () => {
|
|
909
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
910
|
-
ok: true,
|
|
911
|
-
json: async () => ({ items: [] })
|
|
912
|
-
}));
|
|
913
|
-
const user = userEvent.setup();
|
|
914
|
-
renderMcpComponent({
|
|
915
|
-
settingsModel: {
|
|
916
|
-
runtimeMode: 'local',
|
|
917
|
-
isAuthenticated: true,
|
|
918
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
919
|
-
cloudAuthBaseUrl: 'https://cloud.example.com',
|
|
920
|
-
fetchCloudAuthApi
|
|
921
|
-
}
|
|
922
|
-
});
|
|
923
|
-
await openMcpSettings(user);
|
|
924
|
-
const localButton = await screen.findByRole('button', { name: 'Local MCP' });
|
|
925
|
-
const cloudButton = screen.getByRole('button', { name: 'Cloud MCP' });
|
|
926
|
-
expect(localButton.compareDocumentPosition(cloudButton) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
|
927
|
-
expect(screen.getByText('Local MCP Configuration')).toBeInTheDocument();
|
|
928
|
-
expect(screen.queryByText('Cloud MCP Configuration')).not.toBeInTheDocument();
|
|
929
|
-
expect(screen.queryByText('MCP Path Mapping (Host Root)')).not.toBeInTheDocument();
|
|
930
|
-
await user.click(screen.getByRole('button', { name: 'Advanced' }));
|
|
931
|
-
expect(screen.getByText('MCP Path Mapping (Host Root)')).toBeInTheDocument();
|
|
932
|
-
await user.click(cloudButton);
|
|
933
|
-
expect(screen.getByText('Cloud MCP Configuration')).toBeInTheDocument();
|
|
934
|
-
expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
|
|
935
|
-
expect(fetchCloudAuthApi).toHaveBeenCalledWith('/api/taskforce/settings/mcp/tokens', expect.objectContaining({
|
|
936
|
-
method: 'GET',
|
|
937
|
-
credentials: 'include'
|
|
938
|
-
}));
|
|
939
|
-
}, 10000);
|
|
940
|
-
it('4.23b MCP token creation uses the provided cloud auth helper', async () => {
|
|
941
|
-
const fetchCloudAuthApi = vi.fn(async (path, init) => {
|
|
942
|
-
if (path === '/api/taskforce/settings/mcp/tokens' && init?.method === 'POST') {
|
|
943
|
-
return {
|
|
944
|
-
ok: true,
|
|
945
|
-
json: async () => ({
|
|
946
|
-
token: 'tfmcp_example_secret',
|
|
947
|
-
record: {
|
|
948
|
-
id: 'mcp-token-1',
|
|
949
|
-
name: 'Workspace Alpha MCP',
|
|
950
|
-
tokenPrefix: 'tfmcp_example',
|
|
951
|
-
scopes: ['tasks:read', 'tasks:write', 'workspace:read'],
|
|
952
|
-
status: 'active',
|
|
953
|
-
createdAt: new Date().toISOString()
|
|
954
|
-
}
|
|
955
|
-
})
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
return {
|
|
959
|
-
ok: true,
|
|
960
|
-
json: async () => ({ items: [] })
|
|
961
|
-
};
|
|
962
|
-
});
|
|
963
|
-
const user = userEvent.setup();
|
|
964
|
-
renderMcpComponent({
|
|
965
|
-
settingsModel: {
|
|
966
|
-
runtimeMode: 'local',
|
|
967
|
-
isAuthenticated: true,
|
|
968
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
969
|
-
fetchCloudAuthApi
|
|
970
|
-
}
|
|
971
|
-
});
|
|
972
|
-
await openCloudMcpSettings(user);
|
|
973
|
-
await user.click(screen.getByRole('button', { name: 'Generate Token' }));
|
|
974
|
-
expect(fetchCloudAuthApi).toHaveBeenCalledWith('/api/taskforce/settings/mcp/tokens', expect.objectContaining({
|
|
975
|
-
method: 'POST',
|
|
976
|
-
credentials: 'include'
|
|
977
|
-
}));
|
|
978
|
-
expect(fetchCloudAuthApi).toHaveBeenCalledWith('/api/taskforce/settings/mcp/tokens', expect.objectContaining({
|
|
979
|
-
body: JSON.stringify({
|
|
980
|
-
name: 'Workspace Alpha MCP',
|
|
981
|
-
scopes: ['tasks:read', 'tasks:write', 'workspace:read'],
|
|
982
|
-
expiresAt: null
|
|
983
|
-
})
|
|
984
|
-
}));
|
|
985
|
-
expect(await screen.findByDisplayValue('tfmcp_example_secret')).toBeInTheDocument();
|
|
986
|
-
}, 10000);
|
|
987
|
-
it('4.23c MCP token name defaults to the workspace name', async () => {
|
|
988
|
-
const user = userEvent.setup();
|
|
989
|
-
renderMcpComponent({
|
|
990
|
-
settingsModel: {
|
|
991
|
-
runtimeMode: 'local',
|
|
992
|
-
isAuthenticated: true,
|
|
993
|
-
currentWorkspaceName: 'Workspace Alpha'
|
|
994
|
-
}
|
|
995
|
-
});
|
|
996
|
-
await openCloudMcpSettings(user);
|
|
997
|
-
expect(await screen.findByDisplayValue('Workspace Alpha MCP')).toBeInTheDocument();
|
|
998
|
-
});
|
|
999
|
-
it('4.23d MCP test connection uses the provided cloud auth helper', async () => {
|
|
1000
|
-
const fetchCloudAuthApi = vi.fn(async (path, init) => {
|
|
1001
|
-
if (path === '/api/taskforce/settings/mcp/test-connection') {
|
|
1002
|
-
return {
|
|
1003
|
-
ok: true,
|
|
1004
|
-
json: async () => ({
|
|
1005
|
-
result: {
|
|
1006
|
-
workspaceName: 'Workspace Alpha',
|
|
1007
|
-
workspaceId: 'workspace-alpha',
|
|
1008
|
-
scopes: ['tasks:read', 'tasks:write', 'workspace:read'],
|
|
1009
|
-
endpoint: 'https://performance-mcp.taskforcehq.ai/mcp'
|
|
1010
|
-
}
|
|
1011
|
-
})
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
return {
|
|
1015
|
-
ok: true,
|
|
1016
|
-
json: async () => ({ items: [] })
|
|
1017
|
-
};
|
|
1018
|
-
});
|
|
1019
|
-
const user = userEvent.setup();
|
|
1020
|
-
renderMcpComponent({
|
|
1021
|
-
settingsModel: {
|
|
1022
|
-
runtimeMode: 'local',
|
|
1023
|
-
isAuthenticated: true,
|
|
1024
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1025
|
-
fetchCloudAuthApi
|
|
1026
|
-
}
|
|
1027
|
-
});
|
|
1028
|
-
await openCloudMcpSettings(user);
|
|
1029
|
-
expect(screen.queryByText('Test Connection')).not.toBeInTheDocument();
|
|
1030
|
-
await user.type(await screen.findByPlaceholderText('Generate a token to populate the full secret'), 'tfmcp_example_secret');
|
|
1031
|
-
await user.click(screen.getByRole('button', { name: 'Test Token' }));
|
|
1032
|
-
expect(fetchCloudAuthApi).toHaveBeenCalledWith('/api/taskforce/settings/mcp/test-connection', expect.objectContaining({
|
|
1033
|
-
method: 'POST',
|
|
1034
|
-
credentials: 'include'
|
|
1035
|
-
}));
|
|
1036
|
-
expect(await screen.findByText('Connection looks good for this workspace.')).toBeInTheDocument();
|
|
1037
|
-
expect(screen.getByText(/Granted scopes: tasks:read, tasks:write, workspace:read/i)).toBeInTheDocument();
|
|
1038
|
-
expect(screen.getByText(/Endpoint: https:\/\/performance-mcp\.taskforcehq\.ai\/mcp/i)).toBeInTheDocument();
|
|
1039
|
-
}, 10000);
|
|
1040
|
-
it('4.23d2 MCP test connection shows a dedicated-host incident message when the endpoint returns a gateway failure', async () => {
|
|
1041
|
-
const fetchCloudAuthApi = vi.fn(async (path) => {
|
|
1042
|
-
if (path === '/api/taskforce/settings/mcp/test-connection') {
|
|
1043
|
-
return {
|
|
1044
|
-
ok: false,
|
|
1045
|
-
json: async () => ({
|
|
1046
|
-
code: 'MCP_ENDPOINT_GATEWAY_ERROR',
|
|
1047
|
-
error: 'Cloud MCP endpoint https://performance-mcp.taskforcehq.ai/mcp returned 502.',
|
|
1048
|
-
details: {
|
|
1049
|
-
endpoint: 'https://performance-mcp.taskforcehq.ai/mcp',
|
|
1050
|
-
upstreamStatus: 502
|
|
1051
|
-
}
|
|
1052
|
-
})
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
return {
|
|
1056
|
-
ok: true,
|
|
1057
|
-
json: async () => ({ items: [] })
|
|
1058
|
-
};
|
|
1059
|
-
});
|
|
1060
|
-
const user = userEvent.setup();
|
|
1061
|
-
renderMcpComponent({
|
|
1062
|
-
settingsModel: {
|
|
1063
|
-
runtimeMode: 'local',
|
|
1064
|
-
isAuthenticated: true,
|
|
1065
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1066
|
-
cloudMcpBaseUrl: 'https://performance-mcp.taskforcehq.ai',
|
|
1067
|
-
fetchCloudAuthApi
|
|
1068
|
-
}
|
|
1069
|
-
});
|
|
1070
|
-
await openCloudMcpSettings(user);
|
|
1071
|
-
await user.type(await screen.findByPlaceholderText('Generate a token to populate the full secret'), 'tfmcp_example_secret');
|
|
1072
|
-
await user.click(screen.getByRole('button', { name: 'Test Token' }));
|
|
1073
|
-
expect(await screen.findByText('The dedicated MCP host is responding with a gateway error.')).toBeInTheDocument();
|
|
1074
|
-
expect(screen.getByText(/Endpoint: https:\/\/performance-mcp\.taskforcehq\.ai\/mcp/i)).toBeInTheDocument();
|
|
1075
|
-
expect(screen.getByText(/Error code: MCP_ENDPOINT_GATEWAY_ERROR/i)).toBeInTheDocument();
|
|
1076
|
-
expect(screen.getByText(/This usually means the cloud MCP service is degraded/i)).toBeInTheDocument();
|
|
1077
|
-
}, 10000);
|
|
1078
|
-
it('4.23e Antigravity uses serverUrl instead of transport in the cloud snippet', async () => {
|
|
1079
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1080
|
-
ok: true,
|
|
1081
|
-
json: async () => ({ items: [] })
|
|
1082
|
-
}));
|
|
1083
|
-
const user = userEvent.setup();
|
|
1084
|
-
renderMcpComponent({
|
|
1085
|
-
settingsModel: {
|
|
1086
|
-
runtimeMode: 'local',
|
|
1087
|
-
isAuthenticated: true,
|
|
1088
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1089
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1090
|
-
fetchCloudAuthApi
|
|
1091
|
-
}
|
|
1092
|
-
});
|
|
1093
|
-
await openCloudMcpSettings(user);
|
|
1094
|
-
await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'antigravity');
|
|
1095
|
-
const snippetMatches = await screen.findAllByText(/serverUrl/);
|
|
1096
|
-
expect(snippetMatches.length).toBeGreaterThan(0);
|
|
1097
|
-
expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
|
|
1098
|
-
expect(screen.queryByText(/"transport"/)).not.toBeInTheDocument();
|
|
1099
|
-
}, 10000);
|
|
1100
|
-
it('4.23e1 falls back to the dedicated MCP hostname when only the cloud app host is configured', async () => {
|
|
1101
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1102
|
-
ok: true,
|
|
1103
|
-
json: async () => ({ items: [] })
|
|
1104
|
-
}));
|
|
1105
|
-
const user = userEvent.setup();
|
|
1106
|
-
renderMcpComponent({
|
|
1107
|
-
settingsModel: {
|
|
1108
|
-
runtimeMode: 'local',
|
|
1109
|
-
isAuthenticated: true,
|
|
1110
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1111
|
-
cloudAuthBaseUrl: 'https://performance-app.taskforcehq.ai',
|
|
1112
|
-
cloudMcpBaseUrl: '',
|
|
1113
|
-
fetchCloudAuthApi
|
|
1114
|
-
}
|
|
1115
|
-
});
|
|
1116
|
-
await openCloudMcpSettings(user);
|
|
1117
|
-
expect(await screen.findByText(/https:\/\/performance-mcp\.taskforcehq\.ai\/mcp/)).toBeInTheDocument();
|
|
1118
|
-
expect(screen.queryByText(/https:\/\/performance-app\.taskforcehq\.ai\/mcp/)).not.toBeInTheDocument();
|
|
1119
|
-
}, 10000);
|
|
1120
|
-
it('4.23e1b prefers the dedicated MCP hostname even when cloudMcpBaseUrl is stale and points at the app host', async () => {
|
|
1121
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1122
|
-
ok: true,
|
|
1123
|
-
json: async () => ({ items: [] })
|
|
1124
|
-
}));
|
|
1125
|
-
const user = userEvent.setup();
|
|
1126
|
-
renderMcpComponent({
|
|
1127
|
-
settingsModel: {
|
|
1128
|
-
runtimeMode: 'local',
|
|
1129
|
-
isAuthenticated: true,
|
|
1130
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1131
|
-
cloudAuthBaseUrl: 'https://performance-app.taskforcehq.ai',
|
|
1132
|
-
cloudMcpBaseUrl: 'https://performance-app.taskforcehq.ai',
|
|
1133
|
-
fetchCloudAuthApi
|
|
1134
|
-
}
|
|
1135
|
-
});
|
|
1136
|
-
await openCloudMcpSettings(user);
|
|
1137
|
-
expect(await screen.findByText(/https:\/\/performance-mcp\.taskforcehq\.ai\/mcp/)).toBeInTheDocument();
|
|
1138
|
-
expect(screen.queryByText(/https:\/\/performance-app\.taskforcehq\.ai\/mcp/)).not.toBeInTheDocument();
|
|
1139
|
-
}, 10000);
|
|
1140
|
-
it('4.23e2 Cursor uses top-level url instead of transport in the cloud config', async () => {
|
|
1141
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1142
|
-
ok: true,
|
|
1143
|
-
json: async () => ({ items: [] })
|
|
1144
|
-
}));
|
|
1145
|
-
const user = userEvent.setup();
|
|
1146
|
-
renderMcpComponent({
|
|
1147
|
-
settingsModel: {
|
|
1148
|
-
runtimeMode: 'local',
|
|
1149
|
-
isAuthenticated: true,
|
|
1150
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1151
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1152
|
-
fetchCloudAuthApi
|
|
1153
|
-
}
|
|
1154
|
-
});
|
|
1155
|
-
await openCloudMcpSettings(user);
|
|
1156
|
-
const urlMatches = await screen.findAllByText(/"url"/);
|
|
1157
|
-
expect(urlMatches.length).toBeGreaterThan(0);
|
|
1158
|
-
expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
|
|
1159
|
-
expect(screen.queryByText(/"transport"/)).not.toBeInTheDocument();
|
|
1160
|
-
expect(screen.queryByText(/serverUrl/)).not.toBeInTheDocument();
|
|
1161
|
-
}, 10000);
|
|
1162
|
-
it('4.23e2b Cline uses streamableHttp in the cloud config', async () => {
|
|
1163
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1164
|
-
ok: true,
|
|
1165
|
-
json: async () => ({ items: [] })
|
|
1166
|
-
}));
|
|
1167
|
-
const user = userEvent.setup();
|
|
1168
|
-
renderMcpComponent({
|
|
1169
|
-
settingsModel: {
|
|
1170
|
-
runtimeMode: 'local',
|
|
1171
|
-
isAuthenticated: true,
|
|
1172
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1173
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1174
|
-
fetchCloudAuthApi
|
|
1175
|
-
}
|
|
1176
|
-
});
|
|
1177
|
-
await openCloudMcpSettings(user);
|
|
1178
|
-
await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'cline');
|
|
1179
|
-
expect(await screen.findByText(/Cline Configuration Instructions/i)).toBeInTheDocument();
|
|
1180
|
-
expect(screen.getByText(/"type"/)).toBeInTheDocument();
|
|
1181
|
-
expect(screen.getByText(/streamableHttp/)).toBeInTheDocument();
|
|
1182
|
-
expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
|
|
1183
|
-
}, 10000);
|
|
1184
|
-
it('4.23e2c VS Code uses servers with http transport in the cloud config', async () => {
|
|
1185
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1186
|
-
ok: true,
|
|
1187
|
-
json: async () => ({ items: [] })
|
|
1188
|
-
}));
|
|
1189
|
-
const user = userEvent.setup();
|
|
1190
|
-
renderMcpComponent({
|
|
1191
|
-
settingsModel: {
|
|
1192
|
-
runtimeMode: 'local',
|
|
1193
|
-
isAuthenticated: true,
|
|
1194
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1195
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1196
|
-
fetchCloudAuthApi
|
|
1197
|
-
}
|
|
1198
|
-
});
|
|
1199
|
-
await openCloudMcpSettings(user);
|
|
1200
|
-
await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'vscode');
|
|
1201
|
-
expect(await screen.findByText(/VS Code Configuration Instructions/i)).toBeInTheDocument();
|
|
1202
|
-
expect(screen.getByText(/"servers"/)).toBeInTheDocument();
|
|
1203
|
-
expect(screen.getByText(/"type"/)).toBeInTheDocument();
|
|
1204
|
-
expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
|
|
1205
|
-
}, 10000);
|
|
1206
|
-
it('4.23e3 Codex shows config.toml MCP output', async () => {
|
|
1207
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1208
|
-
ok: true,
|
|
1209
|
-
json: async () => ({ items: [] })
|
|
1210
|
-
}));
|
|
1211
|
-
const user = userEvent.setup();
|
|
1212
|
-
renderMcpComponent({
|
|
1213
|
-
settingsModel: {
|
|
1214
|
-
runtimeMode: 'local',
|
|
1215
|
-
isAuthenticated: true,
|
|
1216
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1217
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1218
|
-
fetchCloudAuthApi
|
|
1219
|
-
}
|
|
1220
|
-
});
|
|
1221
|
-
await openCloudMcpSettings(user);
|
|
1222
|
-
await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'codex');
|
|
1223
|
-
expect((await screen.findAllByText(/\.codex\/config\.toml/i)).length).toBeGreaterThan(0);
|
|
1224
|
-
expect(screen.getByText(/\[mcp_servers\./)).toBeInTheDocument();
|
|
1225
|
-
expect(screen.getByText(/http_headers = \{ Authorization = "Bearer/i)).toBeInTheDocument();
|
|
1226
|
-
expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
|
|
1227
|
-
}, 10000);
|
|
1228
|
-
it('4.23e3b Claude Code shows a remote MCP add command', async () => {
|
|
1229
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1230
|
-
ok: true,
|
|
1231
|
-
json: async () => ({ items: [] })
|
|
1232
|
-
}));
|
|
1233
|
-
const user = userEvent.setup();
|
|
1234
|
-
renderMcpComponent({
|
|
1235
|
-
settingsModel: {
|
|
1236
|
-
runtimeMode: 'local',
|
|
1237
|
-
isAuthenticated: true,
|
|
1238
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1239
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1240
|
-
fetchCloudAuthApi
|
|
1241
|
-
}
|
|
1242
|
-
});
|
|
1243
|
-
await openCloudMcpSettings(user);
|
|
1244
|
-
await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'claude-code');
|
|
1245
|
-
expect(await screen.findByText(/Claude Code Configuration Instructions/i)).toBeInTheDocument();
|
|
1246
|
-
expect(screen.getByText(/claude mcp add --transport http/i)).toBeInTheDocument();
|
|
1247
|
-
expect(screen.getByText(/claude mcp add --transport http taskforce-/i)).toBeInTheDocument();
|
|
1248
|
-
expect(screen.getByText(/--header "Authorization: Bearer/i)).toBeInTheDocument();
|
|
1249
|
-
}, 10000);
|
|
1250
|
-
it('4.23e3c Gemini CLI shows a remote MCP add command', async () => {
|
|
1251
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1252
|
-
ok: true,
|
|
1253
|
-
json: async () => ({ items: [] })
|
|
1254
|
-
}));
|
|
1255
|
-
const user = userEvent.setup();
|
|
1256
|
-
renderMcpComponent({
|
|
1257
|
-
settingsModel: {
|
|
1258
|
-
runtimeMode: 'local',
|
|
1259
|
-
isAuthenticated: true,
|
|
1260
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1261
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1262
|
-
fetchCloudAuthApi
|
|
1263
|
-
}
|
|
1264
|
-
});
|
|
1265
|
-
await openCloudMcpSettings(user);
|
|
1266
|
-
await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'gemini-cli');
|
|
1267
|
-
expect(await screen.findByText(/Gemini CLI Configuration Instructions/i)).toBeInTheDocument();
|
|
1268
|
-
expect(screen.getByText(/gemini mcp add taskforce-/i)).toBeInTheDocument();
|
|
1269
|
-
expect(screen.getByText(/--transport http/i)).toBeInTheDocument();
|
|
1270
|
-
expect(screen.getByText(/--header "Authorization: Bearer/i)).toBeInTheDocument();
|
|
1271
|
-
expect(screen.getByText(/Add --scope user if you want it available across all projects/i)).toBeInTheDocument();
|
|
1272
|
-
}, 10000);
|
|
1273
|
-
it('4.23e3d OpenClaw shows an openclaw.json MCP snippet', async () => {
|
|
1274
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1275
|
-
ok: true,
|
|
1276
|
-
json: async () => ({ items: [] })
|
|
1277
|
-
}));
|
|
1278
|
-
const user = userEvent.setup();
|
|
1279
|
-
renderMcpComponent({
|
|
1280
|
-
settingsModel: {
|
|
1281
|
-
runtimeMode: 'local',
|
|
1282
|
-
isAuthenticated: true,
|
|
1283
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1284
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1285
|
-
fetchCloudAuthApi
|
|
1286
|
-
}
|
|
1287
|
-
});
|
|
1288
|
-
await openCloudMcpSettings(user);
|
|
1289
|
-
await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'openclaw');
|
|
1290
|
-
expect(await screen.findByText(/OpenClaw Configuration Instructions/i)).toBeInTheDocument();
|
|
1291
|
-
expect((await screen.findAllByText(/~\/\.openclaw\/openclaw\.json/i)).length).toBeGreaterThan(0);
|
|
1292
|
-
const configBlocks = Array.from(document.querySelectorAll('pre code'));
|
|
1293
|
-
const openClawConfigBlock = configBlocks.find((block) => block.textContent?.includes('https://mcp.taskforcehq.ai/mcp'));
|
|
1294
|
-
expect(openClawConfigBlock?.textContent).toContain('"mcp"');
|
|
1295
|
-
expect(openClawConfigBlock?.textContent).toContain('"servers"');
|
|
1296
|
-
expect(openClawConfigBlock?.textContent).toContain('"transport": "streamable-http"');
|
|
1297
|
-
expect(openClawConfigBlock?.textContent).toContain('"Authorization": "Bearer');
|
|
1298
|
-
expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
|
|
1299
|
-
}, 10000);
|
|
1300
|
-
it('4.23e4 Cloud selector excludes desktop OAuth clients from the MCP client list', async () => {
|
|
1301
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1302
|
-
ok: true,
|
|
1303
|
-
json: async () => ({ items: [] })
|
|
1304
|
-
}));
|
|
1305
|
-
const user = userEvent.setup();
|
|
1306
|
-
renderMcpComponent({
|
|
1307
|
-
settingsModel: {
|
|
1308
|
-
runtimeMode: 'local',
|
|
1309
|
-
isAuthenticated: true,
|
|
1310
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1311
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1312
|
-
fetchCloudAuthApi
|
|
1313
|
-
}
|
|
1314
|
-
});
|
|
1315
|
-
await openCloudMcpSettings(user);
|
|
1316
|
-
expect(screen.queryByRole('option', { name: /Claude Desktop/i })).not.toBeInTheDocument();
|
|
1317
|
-
expect(screen.queryByRole('option', { name: /ChatGPT Desktop/i })).not.toBeInTheDocument();
|
|
1318
|
-
expect(await screen.findByText('General MCP Configuration Instructions')).toBeInTheDocument();
|
|
1319
|
-
}, 10000);
|
|
1320
|
-
it('4.23e4b Integrations tab shows ChatGPT and Claude connector management separately from PAT tokens', async () => {
|
|
1321
|
-
const fetchCloudAuthApi = vi.fn(async (path) => {
|
|
1322
|
-
if (path.startsWith('/api/taskforce/settings/mcp/connectors')) {
|
|
1323
|
-
return {
|
|
1324
|
-
ok: true,
|
|
1325
|
-
json: async () => ({
|
|
1326
|
-
items: [
|
|
1327
|
-
{
|
|
1328
|
-
id: 'grant-1',
|
|
1329
|
-
provider: 'chatgpt',
|
|
1330
|
-
clientId: 'client-1',
|
|
1331
|
-
clientName: 'ChatGPT Connector',
|
|
1332
|
-
workspaceId: 'workspace-alpha',
|
|
1333
|
-
userId: 'user-1',
|
|
1334
|
-
scopes: ['tasks:read', 'tasks:write', 'workspace:read'],
|
|
1335
|
-
status: 'active',
|
|
1336
|
-
createdAt: '2026-03-14T12:00:00.000Z',
|
|
1337
|
-
lastUsedAt: null,
|
|
1338
|
-
},
|
|
1339
|
-
{
|
|
1340
|
-
id: 'grant-2',
|
|
1341
|
-
provider: 'claude',
|
|
1342
|
-
clientId: 'client-2',
|
|
1343
|
-
clientName: 'Claude Desktop Connector',
|
|
1344
|
-
workspaceId: 'workspace-alpha',
|
|
1345
|
-
userId: 'user-1',
|
|
1346
|
-
scopes: ['tasks:read', 'tasks:write', 'workspace:read'],
|
|
1347
|
-
status: 'active',
|
|
1348
|
-
createdAt: '2026-03-14T13:00:00.000Z',
|
|
1349
|
-
lastUsedAt: null,
|
|
1350
|
-
}
|
|
1351
|
-
]
|
|
1352
|
-
})
|
|
1353
|
-
};
|
|
1354
|
-
}
|
|
1355
|
-
return {
|
|
1356
|
-
ok: true,
|
|
1357
|
-
json: async () => ({ items: [] })
|
|
1358
|
-
};
|
|
1359
|
-
});
|
|
1360
|
-
const user = userEvent.setup();
|
|
1361
|
-
renderComponent({
|
|
1362
|
-
settingsModel: {
|
|
1363
|
-
runtimeMode: 'local',
|
|
1364
|
-
isAuthenticated: true,
|
|
1365
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1366
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1367
|
-
fetchCloudAuthApi
|
|
1368
|
-
}
|
|
1369
|
-
});
|
|
1370
|
-
await user.click(screen.getByRole('button', { name: /Integrations/i }));
|
|
1371
|
-
expect(await screen.findByRole('heading', { name: 'Integrations' })).toBeInTheDocument();
|
|
1372
|
-
expect(await screen.findByText('ChatGPT Connector')).toBeInTheDocument();
|
|
1373
|
-
expect(await screen.findByText('Claude Desktop Connector')).toBeInTheDocument();
|
|
1374
|
-
expect(screen.getAllByText(/Taskforce handles OAuth and binds each connector authorization/i)).toHaveLength(2);
|
|
1375
|
-
expect(screen.getAllByRole('button', { name: /Disconnect/i })).toHaveLength(2);
|
|
1376
|
-
}, 10000);
|
|
1377
|
-
it('4.23e4c MCP tab no longer shows connector management cards', async () => {
|
|
1378
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1379
|
-
ok: true,
|
|
1380
|
-
json: async () => ({ items: [] })
|
|
1381
|
-
}));
|
|
1382
|
-
const user = userEvent.setup();
|
|
1383
|
-
renderMcpComponent({
|
|
1384
|
-
settingsModel: {
|
|
1385
|
-
runtimeMode: 'local',
|
|
1386
|
-
isAuthenticated: true,
|
|
1387
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1388
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1389
|
-
fetchCloudAuthApi
|
|
1390
|
-
}
|
|
1391
|
-
});
|
|
1392
|
-
await openCloudMcpSettings(user);
|
|
1393
|
-
expect(screen.queryByText('ChatGPT Connector')).not.toBeInTheDocument();
|
|
1394
|
-
expect(screen.queryByText(/^Connectors$/)).not.toBeInTheDocument();
|
|
1395
|
-
}, 10000);
|
|
1396
|
-
it('4.23g Cloud MCP configuration offers snippet and full-config copy actions', async () => {
|
|
1397
|
-
const user = userEvent.setup();
|
|
1398
|
-
renderMcpComponent({
|
|
1399
|
-
settingsModel: {
|
|
1400
|
-
runtimeMode: 'local',
|
|
1401
|
-
isAuthenticated: true,
|
|
1402
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1403
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1404
|
-
}
|
|
1405
|
-
});
|
|
1406
|
-
await openCloudMcpSettings(user);
|
|
1407
|
-
expect(screen.getAllByRole('button', { name: /copy/i }).length).toBeGreaterThan(0);
|
|
1408
|
-
}, 10000);
|
|
1409
|
-
it('4.23g1 Cloud MCP copy warns before a token is generated', async () => {
|
|
1410
|
-
const user = userEvent.setup();
|
|
1411
|
-
renderMcpComponent({
|
|
1412
|
-
settingsModel: {
|
|
1413
|
-
runtimeMode: 'local',
|
|
1414
|
-
isAuthenticated: true,
|
|
1415
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1416
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1417
|
-
}
|
|
1418
|
-
});
|
|
1419
|
-
await openCloudMcpSettings(user);
|
|
1420
|
-
await user.click(screen.getByTitle('Copy full configuration file'));
|
|
1421
|
-
expect(screen.getByText('Generate a token before copying the Cloud MCP configuration.')).toBeInTheDocument();
|
|
1422
|
-
}, 10000);
|
|
1423
|
-
it('4.23g2 Cloud MCP settings do not surface automation identity controls', async () => {
|
|
1424
|
-
const user = userEvent.setup();
|
|
1425
|
-
renderMcpComponent({
|
|
1426
|
-
settingsModel: {
|
|
1427
|
-
runtimeMode: 'local',
|
|
1428
|
-
isAuthenticated: true,
|
|
1429
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1430
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1431
|
-
}
|
|
1432
|
-
});
|
|
1433
|
-
await openCloudMcpSettings(user);
|
|
1434
|
-
expect(screen.queryByText('Automation Identity (Optional)')).not.toBeInTheDocument();
|
|
1435
|
-
expect(screen.queryByRole('combobox', { name: 'Use existing AI profile' })).not.toBeInTheDocument();
|
|
1436
|
-
}, 10000);
|
|
1437
|
-
it('4.23g3 Cloud MCP config does not include AI identity headers by default', async () => {
|
|
1438
|
-
const user = userEvent.setup();
|
|
1439
|
-
renderMcpComponent({
|
|
1440
|
-
settingsModel: {
|
|
1441
|
-
runtimeMode: 'local',
|
|
1442
|
-
isAuthenticated: true,
|
|
1443
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1444
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1445
|
-
}
|
|
1446
|
-
});
|
|
1447
|
-
await openCloudMcpSettings(user);
|
|
1448
|
-
await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'codex');
|
|
1449
|
-
expect(screen.queryByText(/X-Taskforce-AI-Name/i)).not.toBeInTheDocument();
|
|
1450
|
-
expect(screen.queryByText(/X-Taskforce-AI-Profile-Token/i)).not.toBeInTheDocument();
|
|
1451
|
-
}, 10000);
|
|
1452
|
-
it('4.23h Active tokens sort first and inactive tokens only show delete actions', async () => {
|
|
1453
|
-
const user = userEvent.setup();
|
|
1454
|
-
const confirmSpy = vi.spyOn(window, 'confirm').mockImplementation(() => true);
|
|
1455
|
-
const fetchCloudAuthApi = vi.fn(async (path, init) => {
|
|
1456
|
-
if (path === '/api/taskforce/settings/mcp/tokens/token-expired' && init?.method === 'DELETE') {
|
|
1457
|
-
return {
|
|
1458
|
-
ok: true,
|
|
1459
|
-
json: async () => ({ success: true, deleted: true })
|
|
1460
|
-
};
|
|
1461
|
-
}
|
|
1462
|
-
return {
|
|
1463
|
-
ok: true,
|
|
1464
|
-
json: async () => ({
|
|
1465
|
-
items: [
|
|
1466
|
-
{
|
|
1467
|
-
id: 'token-expired',
|
|
1468
|
-
name: 'Expired Token',
|
|
1469
|
-
scopes: ['tasks:read'],
|
|
1470
|
-
tokenPrefix: 'tfmcp_expired',
|
|
1471
|
-
status: 'expired',
|
|
1472
|
-
createdAt: '2026-03-03T00:00:00.000Z',
|
|
1473
|
-
lastUsedAt: null,
|
|
1474
|
-
},
|
|
1475
|
-
{
|
|
1476
|
-
id: 'token-revoked',
|
|
1477
|
-
name: 'Old Token',
|
|
1478
|
-
scopes: ['tasks:read'],
|
|
1479
|
-
tokenPrefix: 'tfmcp_old',
|
|
1480
|
-
status: 'revoked',
|
|
1481
|
-
createdAt: '2026-03-01T00:00:00.000Z',
|
|
1482
|
-
lastUsedAt: null,
|
|
1483
|
-
},
|
|
1484
|
-
{
|
|
1485
|
-
id: 'token-active',
|
|
1486
|
-
name: 'Current Token',
|
|
1487
|
-
scopes: ['tasks:read', 'tasks:write'],
|
|
1488
|
-
tokenPrefix: 'tfmcp_new',
|
|
1489
|
-
status: 'active',
|
|
1490
|
-
createdAt: '2026-03-02T00:00:00.000Z',
|
|
1491
|
-
lastUsedAt: null,
|
|
1492
|
-
}
|
|
1493
|
-
]
|
|
1494
|
-
})
|
|
1495
|
-
};
|
|
1496
|
-
});
|
|
1497
|
-
renderMcpComponent({
|
|
1498
|
-
settingsModel: {
|
|
1499
|
-
runtimeMode: 'local',
|
|
1500
|
-
isAuthenticated: true,
|
|
1501
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1502
|
-
fetchCloudAuthApi
|
|
1503
|
-
}
|
|
1504
|
-
});
|
|
1505
|
-
await openCloudMcpSettings(user);
|
|
1506
|
-
expect(await screen.findByText('Current Token')).toBeInTheDocument();
|
|
1507
|
-
expect(screen.getByText('Expired Token')).toBeInTheDocument();
|
|
1508
|
-
expect(screen.getByText('Old Token')).toBeInTheDocument();
|
|
1509
|
-
const tokenLabels = screen.getAllByText(/^(Current Token|Expired Token|Old Token)$/);
|
|
1510
|
-
expect(tokenLabels[0]).toHaveTextContent('Current Token');
|
|
1511
|
-
expect(tokenLabels[1]).toHaveTextContent('Expired Token');
|
|
1512
|
-
expect(tokenLabels[2]).toHaveTextContent('Old Token');
|
|
1513
|
-
expect(screen.getAllByRole('button', { name: 'Regenerate' })).toHaveLength(1);
|
|
1514
|
-
expect(screen.getAllByRole('button', { name: 'Revoke' })).toHaveLength(1);
|
|
1515
|
-
expect(screen.getAllByRole('button', { name: 'Delete' })).toHaveLength(2);
|
|
1516
|
-
await user.click(screen.getAllByRole('button', { name: 'Delete' })[0]);
|
|
1517
|
-
expect(confirmSpy).toHaveBeenCalledWith('Delete this token record? This does not affect active tokens.');
|
|
1518
|
-
expect(fetchCloudAuthApi).toHaveBeenCalledWith('/api/taskforce/settings/mcp/tokens/token-expired', expect.objectContaining({
|
|
1519
|
-
method: 'DELETE',
|
|
1520
|
-
credentials: 'include'
|
|
1521
|
-
}));
|
|
1522
|
-
await waitFor(() => expect(screen.queryByText('Expired Token')).not.toBeInTheDocument());
|
|
1523
|
-
confirmSpy.mockRestore();
|
|
1524
|
-
}, 10000);
|
|
1525
|
-
it('4.23f Cloud MCP snippet keeps the server id short enough for client tool-name limits', async () => {
|
|
1526
|
-
const user = userEvent.setup();
|
|
1527
|
-
renderMcpComponent({
|
|
1528
|
-
settingsModel: {
|
|
1529
|
-
runtimeMode: 'local',
|
|
1530
|
-
isAuthenticated: true,
|
|
1531
|
-
workspaceId: 'workspace-019c771a-b6d7-7bc8-b837-c1133f3ba7a2',
|
|
1532
|
-
currentWorkspaceName: 'Workspace Alpha',
|
|
1533
|
-
cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
|
|
1534
|
-
}
|
|
1535
|
-
});
|
|
1536
|
-
await openCloudMcpSettings(user);
|
|
1537
|
-
const codeNodes = Array.from(document.querySelectorAll('code'));
|
|
1538
|
-
const snippetNode = codeNodes.find((node) => node.textContent?.includes('"mcpServers"'));
|
|
1539
|
-
expect(snippetNode).toBeTruthy();
|
|
1540
|
-
const parsed = JSON.parse(snippetNode?.textContent || '{}');
|
|
1541
|
-
const serverId = Object.keys(parsed?.mcpServers || {})[0] || '';
|
|
1542
|
-
expect(serverId.startsWith('taskforce-')).toBe(true);
|
|
1543
|
-
expect(serverId.length).toBeLessThanOrEqual(33);
|
|
1544
|
-
expect(`mcp_${serverId}_set_schedule_preferences`.length).toBeLessThanOrEqual(64);
|
|
1545
|
-
}, 10000);
|
|
1546
|
-
it('4.24 Local signed-out runtime keeps Cloud MCP visible with an auth prompt', async () => {
|
|
1547
|
-
const user = userEvent.setup();
|
|
1548
|
-
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
1549
|
-
ok: true,
|
|
1550
|
-
json: async () => ({ items: [] })
|
|
1551
|
-
}));
|
|
1552
|
-
renderMcpComponent({
|
|
1553
|
-
settingsModel: {
|
|
1554
|
-
runtimeMode: 'local',
|
|
1555
|
-
isAuthenticated: false,
|
|
1556
|
-
fetchCloudAuthApi
|
|
1557
|
-
}
|
|
1558
|
-
});
|
|
1559
|
-
await openMcpSettings(user);
|
|
1560
|
-
expect(screen.getByRole('button', { name: 'Local MCP' })).toBeInTheDocument();
|
|
1561
|
-
expect(screen.getByRole('button', { name: 'Cloud MCP' })).toBeInTheDocument();
|
|
1562
|
-
expect(screen.getByRole('combobox', { name: 'MCP client' })).toHaveValue('general');
|
|
1563
|
-
expect(screen.getByText('General MCP Configuration Instructions')).toBeInTheDocument();
|
|
1564
|
-
expect(screen.queryByText('MCP Path Mapping (Host Root)')).not.toBeInTheDocument();
|
|
1565
|
-
await user.click(screen.getByRole('button', { name: 'Advanced' }));
|
|
1566
|
-
expect(screen.getByText('MCP Path Mapping (Host Root)')).toBeInTheDocument();
|
|
1567
|
-
await user.click(screen.getByRole('button', { name: 'Cloud MCP' }));
|
|
1568
|
-
expect(screen.getByText('Sign in to use Cloud MCP')).toBeInTheDocument();
|
|
1569
|
-
expect(screen.getByText(/Register or sign in to generate cloud MCP tokens/i)).toBeInTheDocument();
|
|
1570
|
-
expect(screen.queryByText('Cloud MCP Configuration')).not.toBeInTheDocument();
|
|
1571
|
-
expect(fetchCloudAuthApi).not.toHaveBeenCalled();
|
|
1572
|
-
});
|
|
1573
|
-
it('4.24a Local signed-out Cloud MCP prompt can open login and registration', async () => {
|
|
1574
|
-
const user = userEvent.setup();
|
|
1575
|
-
const onOpenCloudAuth = vi.fn();
|
|
1576
|
-
renderMcpComponent({
|
|
1577
|
-
settingsModel: {
|
|
1578
|
-
runtimeMode: 'local',
|
|
1579
|
-
isAuthenticated: false,
|
|
1580
|
-
onOpenCloudAuth
|
|
1581
|
-
}
|
|
1582
|
-
});
|
|
1583
|
-
await openMcpSettings(user);
|
|
1584
|
-
await user.click(screen.getByRole('button', { name: 'Cloud MCP' }));
|
|
1585
|
-
await user.click(screen.getByRole('button', { name: 'Sign In' }));
|
|
1586
|
-
expect(onOpenCloudAuth).toHaveBeenCalledWith('login');
|
|
1587
|
-
await user.click(screen.getByRole('button', { name: 'Register' }));
|
|
1588
|
-
expect(onOpenCloudAuth).toHaveBeenCalledWith('register');
|
|
1589
|
-
});
|
|
1590
|
-
it('4.24b Local MCP selector offers General first and keeps the rest alphabetical', async () => {
|
|
1591
|
-
const user = userEvent.setup();
|
|
1592
|
-
renderMcpComponent({
|
|
1593
|
-
settingsModel: {
|
|
1594
|
-
runtimeMode: 'local',
|
|
1595
|
-
isAuthenticated: false
|
|
1596
|
-
}
|
|
1597
|
-
});
|
|
1598
|
-
await openMcpSettings(user);
|
|
1599
|
-
const options = screen.getAllByRole('option').map((option) => option.textContent);
|
|
1600
|
-
expect(options).toEqual([
|
|
1601
|
-
'General',
|
|
1602
|
-
'Antigravity',
|
|
1603
|
-
'Claude Code CLI',
|
|
1604
|
-
'Cline',
|
|
1605
|
-
'Codex',
|
|
1606
|
-
'Cursor',
|
|
1607
|
-
'Gemini CLI',
|
|
1608
|
-
'OpenClaw',
|
|
1609
|
-
'VS Code / Copilot',
|
|
1610
|
-
'Windsurf'
|
|
1611
|
-
]);
|
|
1612
|
-
});
|
|
1613
|
-
it('4.24c Local Codex shows config.toml MCP output without token headers', async () => {
|
|
1614
|
-
const user = userEvent.setup();
|
|
1615
|
-
renderMcpComponent({
|
|
1616
|
-
settingsModel: {
|
|
1617
|
-
runtimeMode: 'local',
|
|
1618
|
-
isAuthenticated: false,
|
|
1619
|
-
projectRoot: '/Users/rossburke/projects/taskforce',
|
|
1620
|
-
projectName: 'taskforce',
|
|
1621
|
-
tenantId: 'tenant-alpha',
|
|
1622
|
-
workspaceId: 'workspace-alpha',
|
|
1623
|
-
mcpScriptPath: '/usr/local/bin/taskforce'
|
|
1624
|
-
}
|
|
1625
|
-
});
|
|
1626
|
-
await openMcpSettings(user);
|
|
1627
|
-
await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'codex');
|
|
1628
|
-
expect(await screen.findByText(/Codex Configuration Instructions/i)).toBeInTheDocument();
|
|
1629
|
-
expect((await screen.findAllByText(/\.codex\/config\.toml/i)).length).toBeGreaterThan(0);
|
|
1630
|
-
expect(screen.getByText(/\[mcp_servers\./)).toBeInTheDocument();
|
|
1631
|
-
expect(screen.getByText(/command = "taskforce"/i)).toBeInTheDocument();
|
|
1632
|
-
expect(screen.queryByText(/Authorization/i)).not.toBeInTheDocument();
|
|
1633
|
-
});
|
|
1634
|
-
it('4.24d Local VS Code uses stdio config under servers', async () => {
|
|
1635
|
-
const user = userEvent.setup();
|
|
1636
|
-
renderMcpComponent({
|
|
1637
|
-
settingsModel: {
|
|
1638
|
-
runtimeMode: 'local',
|
|
1639
|
-
isAuthenticated: false,
|
|
1640
|
-
projectRoot: '/Users/rossburke/projects/taskforce',
|
|
1641
|
-
projectName: 'taskforce',
|
|
1642
|
-
tenantId: 'tenant-alpha',
|
|
1643
|
-
workspaceId: 'workspace-alpha',
|
|
1644
|
-
mcpScriptPath: '/usr/local/bin/taskforce'
|
|
1645
|
-
}
|
|
1646
|
-
});
|
|
1647
|
-
await openMcpSettings(user);
|
|
1648
|
-
await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'vscode');
|
|
1649
|
-
expect(await screen.findByText(/VS Code Configuration Instructions/i)).toBeInTheDocument();
|
|
1650
|
-
expect(screen.getByText(/"servers"/)).toBeInTheDocument();
|
|
1651
|
-
expect(screen.getByText(/"type"/)).toBeInTheDocument();
|
|
1652
|
-
expect(screen.getByText(/"stdio"/)).toBeInTheDocument();
|
|
1653
|
-
});
|
|
1654
|
-
it('4.24e Local Claude Code shows a stdio add command', async () => {
|
|
1655
|
-
const user = userEvent.setup();
|
|
1656
|
-
renderMcpComponent({
|
|
1657
|
-
settingsModel: {
|
|
1658
|
-
runtimeMode: 'local',
|
|
1659
|
-
isAuthenticated: false,
|
|
1660
|
-
projectRoot: '/Users/rossburke/projects/taskforce',
|
|
1661
|
-
projectName: 'taskforce',
|
|
1662
|
-
tenantId: 'tenant-alpha',
|
|
1663
|
-
workspaceId: 'workspace-alpha',
|
|
1664
|
-
mcpScriptPath: '/usr/local/bin/taskforce'
|
|
1665
|
-
}
|
|
1666
|
-
});
|
|
1667
|
-
await openMcpSettings(user);
|
|
1668
|
-
await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'claude-code');
|
|
1669
|
-
expect(await screen.findByText(/Claude Code Configuration Instructions/i)).toBeInTheDocument();
|
|
1670
|
-
expect(screen.getByText(/claude mcp add taskforce-/i)).toBeInTheDocument();
|
|
1671
|
-
expect(screen.getByText(/--env TASKFORCE_TENANT_ID=tenant-alpha/i)).toBeInTheDocument();
|
|
1672
|
-
expect(screen.getByText(/-- taskforce mcp/i)).toBeInTheDocument();
|
|
1673
|
-
});
|
|
1674
|
-
});
|