@taskforcehq/taskforce 0.3.304 → 0.3.305
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 +140 -247
- package/dist/TaskforceCore.js +165 -70
- package/dist/TaskforceCore.test.js +799 -90
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +1 -1
- package/dist/components/features/DocumentViewer.js +32 -9
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +193 -6
- package/dist/components/features/TaskSettings.js +29 -3
- package/dist/components/features/TaskSettings.test.js +42 -1
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskForm.d.ts +8 -8
- package/dist/components/task/TaskForm.js +358 -221
- package/dist/components/task/TaskForm.test.js +456 -381
- package/dist/components/task/TaskKanban.js +2 -1
- package/dist/components/views/PlanComparisonPage.js +5 -8
- package/dist/components/views/PlanComparisonPage.test.js +51 -41
- package/dist/components/views/PlansPage.js +126 -84
- package/dist/components/views/PlansPage.test.js +308 -39
- package/dist/components/views/StandaloneLayout.js +28 -22
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +6 -1
- package/dist/core/AiProfileService.js +161 -16
- package/dist/core/AiProfileService.test.js +3 -1
- package/dist/core/AiProfiles.test.js +200 -0
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +7 -1
- package/dist/core/PlanEntitlementService.js +117 -42
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +46 -21
- package/dist/core/SystemAdmin.test.js +212 -56
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +3 -0
- package/dist/core/Taskforce.js +50 -10
- package/dist/core/WorkspacePlanMode.test.js +12 -12
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- package/dist/hooks/sync/auth.js +3 -1
- package/dist/hooks/sync/transfers.d.ts +2 -1
- package/dist/hooks/sync/transfers.js +8 -4
- package/dist/hooks/useSyncOrchestrator.d.ts +7 -4
- package/dist/hooks/useSyncOrchestrator.js +75 -7
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +80 -10
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +12 -1
- package/dist/hooks/useTaskMutations.test.js +30 -1
- package/dist/hooks/useTaskforce.d.ts +5 -2
- package/dist/hooks/useTaskforce.js +17 -5
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +3 -1
- package/dist/hooks/useWorkspaceSyncController.js +11 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/runtime.js +2362 -3530
- package/dist/mcp/runtime.test.js +159 -0
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +138 -61
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +1 -4
- package/dist/server/routes/auth.js +23 -63
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +104 -188
- package/dist/server/routes/billing.test.js +214 -86
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +4 -2
- package/dist/server/routes/documents.js +73 -7
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +1 -1
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.js +1 -1
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +1 -1
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +1 -1
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +76 -162
- package/dist/server/routes.test.js +672 -30
- package/dist/sync/contentSyncState.js +16 -7
- package/dist/sync/syncApplyHandlers.test.js +2 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +8 -0
- package/dist/sync/syncService.test.js +14 -0
- package/dist/sync/workspaceSyncModel.d.ts +3 -0
- package/dist/sync/workspaceSyncModel.js +12 -3
- package/dist/sync/workspaceSyncModel.test.js +34 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/ui/assets/{AgentsModule-BLWVbuKP.js → AgentsModule-N2MnQBZk.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
- package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/{index-Ii0B0rTO.css → index-C2-OXZV7.css} +1 -1
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/package.json +8 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +5 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
- package/dist/ui/assets/index-DXUdtH1B.js +0 -6
|
@@ -830,6 +830,9 @@ describe('TaskforceCore Integration', () => {
|
|
|
830
830
|
});
|
|
831
831
|
it('shows full-page login when cloud auth is required and user is unauthenticated', async () => {
|
|
832
832
|
const fetchMock = vi.fn((url) => {
|
|
833
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
834
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ config: { runtimeMode: 'local', workspaceMode: 'single-local', authSource: 'cloud' } }) });
|
|
835
|
+
}
|
|
833
836
|
if (url.includes('/api/taskforce/auth/session')) {
|
|
834
837
|
return Promise.resolve({
|
|
835
838
|
ok: true,
|
|
@@ -929,8 +932,8 @@ describe('TaskforceCore Integration', () => {
|
|
|
929
932
|
json: () => Promise.resolve({
|
|
930
933
|
plans: [
|
|
931
934
|
{
|
|
932
|
-
planId: '
|
|
933
|
-
displayName: '
|
|
935
|
+
planId: 'basic',
|
|
936
|
+
displayName: 'Basic Plan test mode',
|
|
934
937
|
status: 'active',
|
|
935
938
|
features: [],
|
|
936
939
|
pricing: { month: null, year: null },
|
|
@@ -946,7 +949,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
946
949
|
render(_jsx(MemoryRouter, { initialEntries: ['/pricing'], children: _jsx(TaskforceCore, {}) }));
|
|
947
950
|
await waitFor(() => {
|
|
948
951
|
expect(screen.queryByPlaceholderText('Email')).not.toBeInTheDocument();
|
|
949
|
-
expect(screen.getByText('
|
|
952
|
+
expect(screen.getByText('Basic Plan test mode')).toBeInTheDocument();
|
|
950
953
|
expect(screen.getByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
951
954
|
});
|
|
952
955
|
});
|
|
@@ -1019,8 +1022,8 @@ describe('TaskforceCore Integration', () => {
|
|
|
1019
1022
|
json: () => Promise.resolve({
|
|
1020
1023
|
plans: [
|
|
1021
1024
|
{
|
|
1022
|
-
planId: '
|
|
1023
|
-
displayName: '
|
|
1025
|
+
planId: 'free',
|
|
1026
|
+
displayName: 'Free',
|
|
1024
1027
|
status: 'active',
|
|
1025
1028
|
features: [],
|
|
1026
1029
|
pricing: {
|
|
@@ -1028,7 +1031,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1028
1031
|
year: null
|
|
1029
1032
|
},
|
|
1030
1033
|
defaultVersion: {
|
|
1031
|
-
planVersionId: '
|
|
1034
|
+
planVersionId: 'free-v1',
|
|
1032
1035
|
versionNumber: 1,
|
|
1033
1036
|
seatLimit: null,
|
|
1034
1037
|
metadata: {}
|
|
@@ -1042,8 +1045,8 @@ describe('TaskforceCore Integration', () => {
|
|
|
1042
1045
|
return Promise.resolve({
|
|
1043
1046
|
ok: true,
|
|
1044
1047
|
json: () => Promise.resolve({
|
|
1045
|
-
planId: '
|
|
1046
|
-
planName: '
|
|
1048
|
+
planId: 'free',
|
|
1049
|
+
planName: 'Free',
|
|
1047
1050
|
entitlementState: 'active'
|
|
1048
1051
|
})
|
|
1049
1052
|
});
|
|
@@ -1052,8 +1055,8 @@ describe('TaskforceCore Integration', () => {
|
|
|
1052
1055
|
return Promise.resolve({
|
|
1053
1056
|
ok: true,
|
|
1054
1057
|
json: () => Promise.resolve({
|
|
1055
|
-
planId: '
|
|
1056
|
-
planName: '
|
|
1058
|
+
planId: 'free',
|
|
1059
|
+
planName: 'Free',
|
|
1057
1060
|
entitlementState: 'active'
|
|
1058
1061
|
})
|
|
1059
1062
|
});
|
|
@@ -1154,8 +1157,8 @@ describe('TaskforceCore Integration', () => {
|
|
|
1154
1157
|
json: () => Promise.resolve({
|
|
1155
1158
|
plans: [
|
|
1156
1159
|
{
|
|
1157
|
-
planId: '
|
|
1158
|
-
displayName: '
|
|
1160
|
+
planId: 'free',
|
|
1161
|
+
displayName: 'Free',
|
|
1159
1162
|
status: 'active',
|
|
1160
1163
|
features: [],
|
|
1161
1164
|
pricing: {
|
|
@@ -1163,7 +1166,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1163
1166
|
year: null
|
|
1164
1167
|
},
|
|
1165
1168
|
defaultVersion: {
|
|
1166
|
-
planVersionId: '
|
|
1169
|
+
planVersionId: 'free-v1',
|
|
1167
1170
|
versionNumber: 1,
|
|
1168
1171
|
seatLimit: null,
|
|
1169
1172
|
metadata: {}
|
|
@@ -1269,8 +1272,8 @@ describe('TaskforceCore Integration', () => {
|
|
|
1269
1272
|
json: () => Promise.resolve({
|
|
1270
1273
|
plans: [
|
|
1271
1274
|
{
|
|
1272
|
-
planId: '
|
|
1273
|
-
displayName: '
|
|
1275
|
+
planId: 'free',
|
|
1276
|
+
displayName: 'Free',
|
|
1274
1277
|
status: 'active',
|
|
1275
1278
|
features: [],
|
|
1276
1279
|
pricing: {
|
|
@@ -1278,7 +1281,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1278
1281
|
year: null
|
|
1279
1282
|
},
|
|
1280
1283
|
defaultVersion: {
|
|
1281
|
-
planVersionId: '
|
|
1284
|
+
planVersionId: 'free-v1',
|
|
1282
1285
|
versionNumber: 1,
|
|
1283
1286
|
seatLimit: null,
|
|
1284
1287
|
metadata: {}
|
|
@@ -1292,9 +1295,9 @@ describe('TaskforceCore Integration', () => {
|
|
|
1292
1295
|
return Promise.resolve({
|
|
1293
1296
|
ok: true,
|
|
1294
1297
|
json: () => Promise.resolve({
|
|
1295
|
-
planId: '
|
|
1296
|
-
planName: '
|
|
1297
|
-
planVersionId: '
|
|
1298
|
+
planId: 'free',
|
|
1299
|
+
planName: 'Free',
|
|
1300
|
+
planVersionId: 'free-v1',
|
|
1298
1301
|
entitlementState: 'active'
|
|
1299
1302
|
})
|
|
1300
1303
|
});
|
|
@@ -1323,7 +1326,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1323
1326
|
render(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans&gate=missing_entitlement'], children: _jsx(TaskforceCore, {}) }));
|
|
1324
1327
|
await waitFor(() => {
|
|
1325
1328
|
expect(screen.getByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
1326
|
-
expect(screen.getByText('
|
|
1329
|
+
expect(screen.getByText('Free')).toBeInTheDocument();
|
|
1327
1330
|
});
|
|
1328
1331
|
await waitFor(() => {
|
|
1329
1332
|
expect(screen.queryByText('No entitlement is linked to this account.')).not.toBeInTheDocument();
|
|
@@ -1393,8 +1396,8 @@ describe('TaskforceCore Integration', () => {
|
|
|
1393
1396
|
return Promise.resolve({
|
|
1394
1397
|
ok: true,
|
|
1395
1398
|
json: () => Promise.resolve({
|
|
1396
|
-
planId: '
|
|
1397
|
-
planName: '
|
|
1399
|
+
planId: 'free',
|
|
1400
|
+
planName: 'Free',
|
|
1398
1401
|
entitlementState: 'active'
|
|
1399
1402
|
})
|
|
1400
1403
|
});
|
|
@@ -1450,14 +1453,14 @@ describe('TaskforceCore Integration', () => {
|
|
|
1450
1453
|
});
|
|
1451
1454
|
await waitFor(() => {
|
|
1452
1455
|
expect(screen.getByText('Subscription')).toBeInTheDocument();
|
|
1453
|
-
expect(screen.getByText('
|
|
1456
|
+
expect(screen.getByText('Free')).toBeInTheDocument();
|
|
1454
1457
|
expect(screen.getByText('Runtime')).toBeInTheDocument();
|
|
1455
1458
|
expect(screen.getByText('LOCAL')).toBeInTheDocument();
|
|
1456
1459
|
expect(screen.getByText('Environment')).toBeInTheDocument();
|
|
1457
1460
|
expect(screen.getByText('CLOUD.EXAMPLE.COM')).toBeInTheDocument();
|
|
1458
1461
|
expect(screen.queryByText(/Current Plan:/i)).not.toBeInTheDocument();
|
|
1459
1462
|
});
|
|
1460
|
-
await user.click(screen.getByRole('button', { name: /Subscription
|
|
1463
|
+
await user.click(screen.getByRole('button', { name: /Subscription Free/i }));
|
|
1461
1464
|
await waitFor(() => {
|
|
1462
1465
|
expect(screen.getByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
1463
1466
|
});
|
|
@@ -1512,8 +1515,8 @@ describe('TaskforceCore Integration', () => {
|
|
|
1512
1515
|
return Promise.resolve({
|
|
1513
1516
|
ok: true,
|
|
1514
1517
|
json: () => Promise.resolve({
|
|
1515
|
-
planId: '
|
|
1516
|
-
planName: '
|
|
1518
|
+
planId: 'free',
|
|
1519
|
+
planName: 'Free',
|
|
1517
1520
|
entitlementState: 'active'
|
|
1518
1521
|
})
|
|
1519
1522
|
});
|
|
@@ -1563,7 +1566,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1563
1566
|
}
|
|
1564
1567
|
await waitFor(() => {
|
|
1565
1568
|
expect(screen.getByText('Subscription')).toBeInTheDocument();
|
|
1566
|
-
expect(screen.getByText('
|
|
1569
|
+
expect(screen.getByText('Free')).toBeInTheDocument();
|
|
1567
1570
|
expect(screen.getByText('Runtime')).toBeInTheDocument();
|
|
1568
1571
|
expect(screen.getByText('LOCAL')).toBeInTheDocument();
|
|
1569
1572
|
expect(screen.getByText('Environment')).toBeInTheDocument();
|
|
@@ -1590,9 +1593,9 @@ describe('TaskforceCore Integration', () => {
|
|
|
1590
1593
|
userId: 'user-new',
|
|
1591
1594
|
displayName: 'New User',
|
|
1592
1595
|
workspaceId: 'workspace-local-active',
|
|
1593
|
-
planId: '
|
|
1594
|
-
planVersionId: '
|
|
1595
|
-
planName: '
|
|
1596
|
+
planId: 'free',
|
|
1597
|
+
planVersionId: 'free-v1',
|
|
1598
|
+
planName: 'Free',
|
|
1596
1599
|
teamManagementAllowed: false,
|
|
1597
1600
|
teamPlanMode: 'personal',
|
|
1598
1601
|
workspaceRole: 'member'
|
|
@@ -1690,11 +1693,11 @@ describe('TaskforceCore Integration', () => {
|
|
|
1690
1693
|
return jsonResponse({
|
|
1691
1694
|
plans: [
|
|
1692
1695
|
{
|
|
1693
|
-
planId: '
|
|
1694
|
-
displayName: '
|
|
1696
|
+
planId: 'free',
|
|
1697
|
+
displayName: 'Free',
|
|
1695
1698
|
status: 'active',
|
|
1696
1699
|
defaultVersion: {
|
|
1697
|
-
planVersionId: '
|
|
1700
|
+
planVersionId: 'free-v1',
|
|
1698
1701
|
versionNumber: 1,
|
|
1699
1702
|
seatLimit: null,
|
|
1700
1703
|
metadata: {}
|
|
@@ -1825,7 +1828,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1825
1828
|
await user.click(screen.getByTitle(/Account/i));
|
|
1826
1829
|
await waitFor(() => {
|
|
1827
1830
|
expect(screen.getByText(accounts.newcomer.email)).toBeInTheDocument();
|
|
1828
|
-
expect(screen.getByText('
|
|
1831
|
+
expect(screen.getByText('Free')).toBeInTheDocument();
|
|
1829
1832
|
});
|
|
1830
1833
|
await user.click(screen.getByRole('menuitem', { name: 'Sign Out' }));
|
|
1831
1834
|
await waitFor(() => {
|
|
@@ -1846,7 +1849,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1846
1849
|
await waitFor(() => {
|
|
1847
1850
|
expect(screen.getByText(accounts.ross.email)).toBeInTheDocument();
|
|
1848
1851
|
expect(screen.getByText('Team 5')).toBeInTheDocument();
|
|
1849
|
-
expect(screen.queryByText('
|
|
1852
|
+
expect(screen.queryByText('Free')).not.toBeInTheDocument();
|
|
1850
1853
|
expect(screen.getByRole('menuitem', { name: 'Team Management' })).toBeInTheDocument();
|
|
1851
1854
|
});
|
|
1852
1855
|
await user.click(screen.getByRole('button', { name: /Subscription Team 5/i }));
|
|
@@ -2034,6 +2037,69 @@ describe('TaskforceCore Integration', () => {
|
|
|
2034
2037
|
expect(screen.getByRole('button', { name: /Resend Verification/ })).toBeDisabled();
|
|
2035
2038
|
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/api/taskforce/auth/verify-email/request'))).toBe(true);
|
|
2036
2039
|
});
|
|
2040
|
+
it('shows neutral verification copy when resend succeeds without attempting delivery', async () => {
|
|
2041
|
+
const user = userEvent.setup();
|
|
2042
|
+
const fetchMock = vi.fn((input) => {
|
|
2043
|
+
const url = String(input);
|
|
2044
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
2045
|
+
return Promise.resolve({
|
|
2046
|
+
ok: true,
|
|
2047
|
+
json: () => Promise.resolve({
|
|
2048
|
+
authenticated: false,
|
|
2049
|
+
runtimeMode: 'cloud',
|
|
2050
|
+
authRequiredForApi: false
|
|
2051
|
+
})
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
if (url.includes('/api/taskforce/config')) {
|
|
2055
|
+
return Promise.resolve({
|
|
2056
|
+
ok: true,
|
|
2057
|
+
json: () => Promise.resolve({
|
|
2058
|
+
...mockConfig,
|
|
2059
|
+
runtimeMode: 'cloud',
|
|
2060
|
+
authRequiredForApi: false,
|
|
2061
|
+
setupState: {
|
|
2062
|
+
globalSetupState: 'present',
|
|
2063
|
+
workspaceSetupState: 'present',
|
|
2064
|
+
runtimeMode: 'cloud',
|
|
2065
|
+
workspaceId: 'workspace-local-active',
|
|
2066
|
+
mode: 'core',
|
|
2067
|
+
forceSetup: false,
|
|
2068
|
+
forceGlobalSetup: false,
|
|
2069
|
+
forceWorkspaceSetup: false
|
|
2070
|
+
}
|
|
2071
|
+
})
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
if (url.includes('/api/taskforce/auth/verify-email/request')) {
|
|
2075
|
+
return Promise.resolve({
|
|
2076
|
+
ok: true,
|
|
2077
|
+
json: () => Promise.resolve({
|
|
2078
|
+
success: true,
|
|
2079
|
+
emailSent: false,
|
|
2080
|
+
deliveryAttempted: false,
|
|
2081
|
+
verificationToken: null
|
|
2082
|
+
})
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
if (url.includes('/api/taskforce/public/pricing')) {
|
|
2086
|
+
return Promise.resolve({
|
|
2087
|
+
ok: true,
|
|
2088
|
+
json: () => Promise.resolve({
|
|
2089
|
+
plans: []
|
|
2090
|
+
})
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
2094
|
+
});
|
|
2095
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
2096
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=verify'], children: _jsx(TaskforceCore, {}) }));
|
|
2097
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'verify@example.com');
|
|
2098
|
+
await user.click(screen.getByRole('button', { name: 'Resend Verification' }));
|
|
2099
|
+
await waitFor(() => {
|
|
2100
|
+
expect(screen.getByText('If that account exists and still needs verification, we sent an email.')).toBeInTheDocument();
|
|
2101
|
+
});
|
|
2102
|
+
});
|
|
2037
2103
|
it('does not switch to verify mode after registration when verification is not required', async () => {
|
|
2038
2104
|
const user = userEvent.setup();
|
|
2039
2105
|
let authenticated = false;
|
|
@@ -2228,11 +2294,11 @@ describe('TaskforceCore Integration', () => {
|
|
|
2228
2294
|
if (url.includes('/api/taskforce/public/pricing')) {
|
|
2229
2295
|
return jsonResponse({
|
|
2230
2296
|
plans: [{
|
|
2231
|
-
planId: '
|
|
2232
|
-
displayName: '
|
|
2297
|
+
planId: 'free',
|
|
2298
|
+
displayName: 'Free',
|
|
2233
2299
|
status: 'active',
|
|
2234
2300
|
defaultVersion: {
|
|
2235
|
-
planVersionId: '
|
|
2301
|
+
planVersionId: 'free-v1',
|
|
2236
2302
|
versionNumber: 1,
|
|
2237
2303
|
seatLimit: null,
|
|
2238
2304
|
metadata: {}
|
|
@@ -2240,10 +2306,10 @@ describe('TaskforceCore Integration', () => {
|
|
|
2240
2306
|
features: [],
|
|
2241
2307
|
pricing: {
|
|
2242
2308
|
month: {
|
|
2243
|
-
planVersionId: '
|
|
2309
|
+
planVersionId: 'free-v1',
|
|
2244
2310
|
interval: 'month',
|
|
2245
2311
|
pricingType: 'free',
|
|
2246
|
-
stripePriceId: 'free:
|
|
2312
|
+
stripePriceId: 'free:free-v1:month',
|
|
2247
2313
|
displayAmount: '$0.00',
|
|
2248
2314
|
unitAmount: 0,
|
|
2249
2315
|
currency: 'usd'
|
|
@@ -2493,9 +2559,9 @@ describe('TaskforceCore Integration', () => {
|
|
|
2493
2559
|
}
|
|
2494
2560
|
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
2495
2561
|
return jsonResponse({
|
|
2496
|
-
planId: '
|
|
2497
|
-
planName: '
|
|
2498
|
-
planVersionId: '
|
|
2562
|
+
planId: 'basic',
|
|
2563
|
+
planName: 'Basic',
|
|
2564
|
+
planVersionId: 'basic-v1',
|
|
2499
2565
|
entitlementState: 'checkout_pending',
|
|
2500
2566
|
gate: 'checkout_pending',
|
|
2501
2567
|
message: 'Complete checkout to continue.',
|
|
@@ -2505,11 +2571,11 @@ describe('TaskforceCore Integration', () => {
|
|
|
2505
2571
|
if (url.includes('/api/taskforce/public/pricing')) {
|
|
2506
2572
|
return jsonResponse({
|
|
2507
2573
|
plans: [{
|
|
2508
|
-
planId: '
|
|
2509
|
-
displayName: '
|
|
2574
|
+
planId: 'basic',
|
|
2575
|
+
displayName: 'Basic',
|
|
2510
2576
|
status: 'active',
|
|
2511
2577
|
defaultVersion: {
|
|
2512
|
-
planVersionId: '
|
|
2578
|
+
planVersionId: 'basic-v1',
|
|
2513
2579
|
versionNumber: 1,
|
|
2514
2580
|
seatLimit: null,
|
|
2515
2581
|
metadata: {}
|
|
@@ -2517,19 +2583,19 @@ describe('TaskforceCore Integration', () => {
|
|
|
2517
2583
|
features: [],
|
|
2518
2584
|
pricing: {
|
|
2519
2585
|
month: {
|
|
2520
|
-
planVersionId: '
|
|
2586
|
+
planVersionId: 'basic-v1',
|
|
2521
2587
|
interval: 'month',
|
|
2522
2588
|
pricingType: 'stripe',
|
|
2523
|
-
stripePriceId: '
|
|
2589
|
+
stripePriceId: 'price_basic_month',
|
|
2524
2590
|
displayAmount: '$19.00',
|
|
2525
2591
|
unitAmount: 1900,
|
|
2526
2592
|
currency: 'usd'
|
|
2527
2593
|
},
|
|
2528
2594
|
year: {
|
|
2529
|
-
planVersionId: '
|
|
2595
|
+
planVersionId: 'basic-v1',
|
|
2530
2596
|
interval: 'year',
|
|
2531
2597
|
pricingType: 'stripe',
|
|
2532
|
-
stripePriceId: '
|
|
2598
|
+
stripePriceId: 'price_basic_year',
|
|
2533
2599
|
displayAmount: '$190.00',
|
|
2534
2600
|
unitAmount: 19000,
|
|
2535
2601
|
currency: 'usd'
|
|
@@ -2584,7 +2650,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
2584
2650
|
return jsonResponse({});
|
|
2585
2651
|
});
|
|
2586
2652
|
vi.stubGlobal('fetch', fetchMock);
|
|
2587
|
-
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&planId=
|
|
2653
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&planId=basic&planVersionId=basic-v1&interval=year&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
2588
2654
|
await user.type(await screen.findByPlaceholderText('Email'), 'checkout@example.com');
|
|
2589
2655
|
await user.type(screen.getByPlaceholderText('Display name'), 'Checkout User');
|
|
2590
2656
|
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
@@ -2977,6 +3043,162 @@ describe('TaskforceCore Integration', () => {
|
|
|
2977
3043
|
expect(screen.getByRole('button', { name: 'Register' })).toBeInTheDocument();
|
|
2978
3044
|
});
|
|
2979
3045
|
});
|
|
3046
|
+
it('shows neutral password reset copy when reset delivery was not attempted', async () => {
|
|
3047
|
+
const user = userEvent.setup();
|
|
3048
|
+
const fetchMock = vi.fn((input) => {
|
|
3049
|
+
const url = String(input);
|
|
3050
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
3051
|
+
return Promise.resolve({
|
|
3052
|
+
ok: true,
|
|
3053
|
+
json: () => Promise.resolve({
|
|
3054
|
+
success: true,
|
|
3055
|
+
config: {
|
|
3056
|
+
baseUrl: 'https://app.taskforce.example',
|
|
3057
|
+
apiBaseUrl: 'https://app.taskforce.example/api',
|
|
3058
|
+
cloudAuthBaseUrl: 'https://app.taskforce.example',
|
|
3059
|
+
runtimeMode: 'cloud',
|
|
3060
|
+
authSource: 'cloud',
|
|
3061
|
+
workspaceMode: 'multi-cloud',
|
|
3062
|
+
workspaceSwitchingEnabled: true,
|
|
3063
|
+
cloudAuthViaLocalProxy: false
|
|
3064
|
+
}
|
|
3065
|
+
})
|
|
3066
|
+
});
|
|
3067
|
+
}
|
|
3068
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
3069
|
+
return Promise.resolve({
|
|
3070
|
+
ok: true,
|
|
3071
|
+
json: () => Promise.resolve({
|
|
3072
|
+
authenticated: false,
|
|
3073
|
+
runtimeMode: 'cloud',
|
|
3074
|
+
authRequiredForApi: false
|
|
3075
|
+
})
|
|
3076
|
+
});
|
|
3077
|
+
}
|
|
3078
|
+
if (url.includes('/api/taskforce/config')) {
|
|
3079
|
+
return Promise.resolve({
|
|
3080
|
+
ok: true,
|
|
3081
|
+
json: () => Promise.resolve({
|
|
3082
|
+
...mockConfig,
|
|
3083
|
+
runtimeMode: 'cloud',
|
|
3084
|
+
authRequiredForApi: false,
|
|
3085
|
+
setupState: {
|
|
3086
|
+
globalSetupState: 'present',
|
|
3087
|
+
workspaceSetupState: 'present',
|
|
3088
|
+
runtimeMode: 'cloud',
|
|
3089
|
+
workspaceId: 'workspace-local-active',
|
|
3090
|
+
mode: 'core',
|
|
3091
|
+
forceSetup: false,
|
|
3092
|
+
forceGlobalSetup: false,
|
|
3093
|
+
forceWorkspaceSetup: false
|
|
3094
|
+
}
|
|
3095
|
+
})
|
|
3096
|
+
});
|
|
3097
|
+
}
|
|
3098
|
+
if (url.includes('/api/taskforce/auth/password-reset/request')) {
|
|
3099
|
+
return Promise.resolve({
|
|
3100
|
+
ok: true,
|
|
3101
|
+
json: () => Promise.resolve({
|
|
3102
|
+
success: true,
|
|
3103
|
+
emailSent: false,
|
|
3104
|
+
deliveryAttempted: false,
|
|
3105
|
+
resetToken: null
|
|
3106
|
+
})
|
|
3107
|
+
});
|
|
3108
|
+
}
|
|
3109
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
3110
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ templates: [] }) });
|
|
3111
|
+
}
|
|
3112
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
3113
|
+
});
|
|
3114
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
3115
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=forgot&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
3116
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'nobody@example.com');
|
|
3117
|
+
await user.click(screen.getByRole('button', { name: 'Send Reset Link' }));
|
|
3118
|
+
await waitFor(() => {
|
|
3119
|
+
expect(screen.getByText('If that account exists, we sent password reset instructions.')).toBeInTheDocument();
|
|
3120
|
+
});
|
|
3121
|
+
});
|
|
3122
|
+
it('keeps forgot-password users on the request form when reset delivery fails', async () => {
|
|
3123
|
+
const user = userEvent.setup();
|
|
3124
|
+
const fetchMock = vi.fn((input) => {
|
|
3125
|
+
const url = String(input);
|
|
3126
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
3127
|
+
return Promise.resolve({
|
|
3128
|
+
ok: true,
|
|
3129
|
+
json: () => Promise.resolve({
|
|
3130
|
+
success: true,
|
|
3131
|
+
config: {
|
|
3132
|
+
baseUrl: 'https://app.taskforce.example',
|
|
3133
|
+
apiBaseUrl: 'https://app.taskforce.example/api',
|
|
3134
|
+
cloudAuthBaseUrl: 'https://app.taskforce.example',
|
|
3135
|
+
runtimeMode: 'cloud',
|
|
3136
|
+
authSource: 'cloud',
|
|
3137
|
+
workspaceMode: 'multi-cloud',
|
|
3138
|
+
workspaceSwitchingEnabled: true,
|
|
3139
|
+
cloudAuthViaLocalProxy: false
|
|
3140
|
+
}
|
|
3141
|
+
})
|
|
3142
|
+
});
|
|
3143
|
+
}
|
|
3144
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
3145
|
+
return Promise.resolve({
|
|
3146
|
+
ok: true,
|
|
3147
|
+
json: () => Promise.resolve({
|
|
3148
|
+
authenticated: false,
|
|
3149
|
+
runtimeMode: 'cloud',
|
|
3150
|
+
authRequiredForApi: false
|
|
3151
|
+
})
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
if (url.includes('/api/taskforce/config')) {
|
|
3155
|
+
return Promise.resolve({
|
|
3156
|
+
ok: true,
|
|
3157
|
+
json: () => Promise.resolve({
|
|
3158
|
+
...mockConfig,
|
|
3159
|
+
runtimeMode: 'cloud',
|
|
3160
|
+
authRequiredForApi: false,
|
|
3161
|
+
setupState: {
|
|
3162
|
+
globalSetupState: 'present',
|
|
3163
|
+
workspaceSetupState: 'present',
|
|
3164
|
+
runtimeMode: 'cloud',
|
|
3165
|
+
workspaceId: 'workspace-local-active',
|
|
3166
|
+
mode: 'core',
|
|
3167
|
+
forceSetup: false,
|
|
3168
|
+
forceGlobalSetup: false,
|
|
3169
|
+
forceWorkspaceSetup: false
|
|
3170
|
+
}
|
|
3171
|
+
})
|
|
3172
|
+
});
|
|
3173
|
+
}
|
|
3174
|
+
if (url.includes('/api/taskforce/auth/password-reset/request')) {
|
|
3175
|
+
return Promise.resolve({
|
|
3176
|
+
ok: true,
|
|
3177
|
+
json: () => Promise.resolve({
|
|
3178
|
+
success: true,
|
|
3179
|
+
emailSent: false,
|
|
3180
|
+
deliveryAttempted: true,
|
|
3181
|
+
emailError: 'SMTP temporary failure',
|
|
3182
|
+
resetToken: null
|
|
3183
|
+
})
|
|
3184
|
+
});
|
|
3185
|
+
}
|
|
3186
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
3187
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ templates: [] }) });
|
|
3188
|
+
}
|
|
3189
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
3190
|
+
});
|
|
3191
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
3192
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=forgot&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
3193
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'owner@example.com');
|
|
3194
|
+
await user.click(screen.getByRole('button', { name: 'Send Reset Link' }));
|
|
3195
|
+
await waitFor(() => {
|
|
3196
|
+
expect(screen.getByText('Password reset email failed to send. SMTP temporary failure')).toBeInTheDocument();
|
|
3197
|
+
});
|
|
3198
|
+
expect(screen.getByText('Request a password reset link.')).toBeInTheDocument();
|
|
3199
|
+
expect(screen.queryByText('Enter the password reset token and choose a new password.')).not.toBeInTheDocument();
|
|
3200
|
+
expect(screen.getByRole('button', { name: 'Send Reset Link' })).toBeInTheDocument();
|
|
3201
|
+
});
|
|
2980
3202
|
it('redirects register users to plans when signup plan configuration blocks account creation', async () => {
|
|
2981
3203
|
const user = userEvent.setup();
|
|
2982
3204
|
const fetchMock = vi.fn((input) => {
|
|
@@ -3135,15 +3357,20 @@ describe('TaskforceCore Integration', () => {
|
|
|
3135
3357
|
expect(screen.getByRole('button', { name: 'Sign In to Continue' })).toBeInTheDocument();
|
|
3136
3358
|
});
|
|
3137
3359
|
});
|
|
3138
|
-
it('
|
|
3360
|
+
it('uses invite join after invite-mode sign-in even if invite reinspection has not refreshed yet', async () => {
|
|
3361
|
+
let authenticated = false;
|
|
3362
|
+
let inviteInspectCalls = 0;
|
|
3139
3363
|
const fetchMock = vi.fn((url) => {
|
|
3140
3364
|
if (url.includes('/api/taskforce/auth/session')) {
|
|
3141
3365
|
return Promise.resolve({
|
|
3142
3366
|
ok: true,
|
|
3143
3367
|
json: () => Promise.resolve({
|
|
3144
|
-
authenticated
|
|
3368
|
+
authenticated,
|
|
3145
3369
|
runtimeMode: 'cloud',
|
|
3146
|
-
authRequiredForApi: true
|
|
3370
|
+
authRequiredForApi: true,
|
|
3371
|
+
userId: authenticated ? 'user-existing' : 'anonymous',
|
|
3372
|
+
workspaceId: authenticated ? 'workspace-home' : 'default',
|
|
3373
|
+
email: authenticated ? 'existing@example.com' : null
|
|
3147
3374
|
})
|
|
3148
3375
|
});
|
|
3149
3376
|
}
|
|
@@ -3158,7 +3385,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
3158
3385
|
globalSetupState: 'present',
|
|
3159
3386
|
workspaceSetupState: 'present',
|
|
3160
3387
|
runtimeMode: 'cloud',
|
|
3161
|
-
workspaceId: 'workspace-
|
|
3388
|
+
workspaceId: authenticated ? 'workspace-home' : 'default',
|
|
3162
3389
|
mode: 'core',
|
|
3163
3390
|
forceSetup: false,
|
|
3164
3391
|
forceGlobalSetup: false,
|
|
@@ -3168,54 +3395,314 @@ describe('TaskforceCore Integration', () => {
|
|
|
3168
3395
|
});
|
|
3169
3396
|
}
|
|
3170
3397
|
if (url.includes('/api/taskforce/auth/invite/inspect')) {
|
|
3398
|
+
inviteInspectCalls += 1;
|
|
3399
|
+
if (authenticated && inviteInspectCalls > 1) {
|
|
3400
|
+
return new Promise(() => undefined);
|
|
3401
|
+
}
|
|
3171
3402
|
return Promise.resolve({
|
|
3172
3403
|
ok: true,
|
|
3173
3404
|
json: () => Promise.resolve({
|
|
3174
3405
|
success: true,
|
|
3175
3406
|
state: 'valid',
|
|
3176
|
-
email: '
|
|
3407
|
+
email: 'existing@example.com',
|
|
3177
3408
|
workspaceId: 'workspace-1',
|
|
3178
|
-
inviteeKind: '
|
|
3179
|
-
passwordRequired:
|
|
3409
|
+
inviteeKind: 'existing_user',
|
|
3410
|
+
passwordRequired: false
|
|
3180
3411
|
})
|
|
3181
3412
|
});
|
|
3182
3413
|
}
|
|
3183
|
-
if (url.includes('/api/taskforce/
|
|
3184
|
-
|
|
3414
|
+
if (url.includes('/api/taskforce/auth/login')) {
|
|
3415
|
+
authenticated = true;
|
|
3416
|
+
return Promise.resolve({
|
|
3417
|
+
ok: true,
|
|
3418
|
+
json: () => Promise.resolve({ success: true })
|
|
3419
|
+
});
|
|
3185
3420
|
}
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
expect(screen.getByRole('button', { name: 'Create Account and Join' })).toBeInTheDocument();
|
|
3194
|
-
});
|
|
3195
|
-
});
|
|
3196
|
-
it('shows global setup route when forced and persists selected mode', async () => {
|
|
3197
|
-
const user = userEvent.setup();
|
|
3198
|
-
let selectedMode = null;
|
|
3199
|
-
const fetchMock = vi.fn((url, init) => {
|
|
3200
|
-
if (url.includes('/api/taskforce/auth/session')) {
|
|
3421
|
+
if (url.includes('/api/taskforce/auth/invite/join')) {
|
|
3422
|
+
return Promise.resolve({
|
|
3423
|
+
ok: true,
|
|
3424
|
+
json: () => Promise.resolve({ success: true, workspaceId: 'workspace-1' })
|
|
3425
|
+
});
|
|
3426
|
+
}
|
|
3427
|
+
if (url.includes('/api/taskforce/workspaces')) {
|
|
3201
3428
|
return Promise.resolve({
|
|
3202
3429
|
ok: true,
|
|
3203
3430
|
json: () => Promise.resolve({
|
|
3204
|
-
authenticated:
|
|
3205
|
-
|
|
3206
|
-
|
|
3431
|
+
currentWorkspaceId: authenticated ? 'workspace-home' : null,
|
|
3432
|
+
workspaces: authenticated
|
|
3433
|
+
? [{ id: 'workspace-home', name: 'Home workspace', role: 'owner' }]
|
|
3434
|
+
: []
|
|
3207
3435
|
})
|
|
3208
3436
|
});
|
|
3209
3437
|
}
|
|
3210
|
-
if (url.includes('/api/taskforce/
|
|
3211
|
-
|
|
3438
|
+
if (url.includes('/api/taskforce/tasks')) {
|
|
3439
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ tasks: [], config: {} }) });
|
|
3440
|
+
}
|
|
3441
|
+
if (url.includes('/api/taskforce/account/access-status')) {
|
|
3212
3442
|
return Promise.resolve({
|
|
3213
3443
|
ok: true,
|
|
3214
3444
|
json: () => Promise.resolve({
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3445
|
+
success: true,
|
|
3446
|
+
authenticated,
|
|
3447
|
+
commercialState: 'active',
|
|
3448
|
+
planSelectionRequired: false,
|
|
3449
|
+
checkoutPending: false
|
|
3450
|
+
})
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
3454
|
+
return Promise.resolve({
|
|
3455
|
+
ok: true,
|
|
3456
|
+
json: () => Promise.resolve({
|
|
3457
|
+
success: true,
|
|
3458
|
+
currentWorkspaceId: authenticated ? 'workspace-home' : null,
|
|
3459
|
+
workspaces: authenticated ? [{ id: 'workspace-home', name: 'Home workspace' }] : []
|
|
3460
|
+
})
|
|
3461
|
+
});
|
|
3462
|
+
}
|
|
3463
|
+
if (url.includes('/api/taskforce/session/workspace')) {
|
|
3464
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true, workspaceId: 'workspace-1' }) });
|
|
3465
|
+
}
|
|
3466
|
+
if (url.includes('/api/taskforce/ui-state?key=workspace-sync')) {
|
|
3467
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true, state: null }) });
|
|
3468
|
+
}
|
|
3469
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
3470
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ templates: [] }) });
|
|
3471
|
+
}
|
|
3472
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
3473
|
+
});
|
|
3474
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
3475
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=invite&token=invite-token&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
3476
|
+
await waitFor(() => {
|
|
3477
|
+
expect(screen.getByRole('button', { name: 'Sign In to Continue' })).toBeInTheDocument();
|
|
3478
|
+
});
|
|
3479
|
+
await userEvent.type(screen.getByPlaceholderText('Password'), 'ExistingUserPass123!');
|
|
3480
|
+
await userEvent.click(screen.getByTestId('auth-submit-button'));
|
|
3481
|
+
await waitFor(() => {
|
|
3482
|
+
expect(screen.getByText('Signed in. Review the invite details to continue.')).toBeInTheDocument();
|
|
3483
|
+
expect(screen.getByRole('button', { name: 'Join Workspace' })).toBeInTheDocument();
|
|
3484
|
+
});
|
|
3485
|
+
await userEvent.click(screen.getByTestId('auth-submit-button'));
|
|
3486
|
+
await waitFor(() => {
|
|
3487
|
+
expect(fetchMock.mock.calls.some((call) => String(call[0]).includes('/api/taskforce/auth/invite/join'))).toBe(true);
|
|
3488
|
+
});
|
|
3489
|
+
expect(fetchMock.mock.calls.filter((call) => String(call[0]).includes('/api/taskforce/auth/login'))).toHaveLength(1);
|
|
3490
|
+
});
|
|
3491
|
+
it('keeps existing-user invites recoverable when the wrong account signs in first', async () => {
|
|
3492
|
+
let authenticated = false;
|
|
3493
|
+
let sessionEmail = null;
|
|
3494
|
+
const fetchMock = vi.fn((url, init) => {
|
|
3495
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
3496
|
+
return Promise.resolve({
|
|
3497
|
+
ok: true,
|
|
3498
|
+
json: () => Promise.resolve({
|
|
3499
|
+
authenticated,
|
|
3500
|
+
runtimeMode: 'cloud',
|
|
3501
|
+
authRequiredForApi: true,
|
|
3502
|
+
userId: authenticated ? 'user-other' : 'anonymous',
|
|
3503
|
+
workspaceId: authenticated ? 'workspace-home' : 'default',
|
|
3504
|
+
email: sessionEmail
|
|
3505
|
+
})
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
if (url.includes('/api/taskforce/config')) {
|
|
3509
|
+
return Promise.resolve({
|
|
3510
|
+
ok: true,
|
|
3511
|
+
json: () => Promise.resolve({
|
|
3512
|
+
...mockConfig,
|
|
3513
|
+
runtimeMode: 'cloud',
|
|
3514
|
+
authRequiredForApi: true,
|
|
3515
|
+
setupState: {
|
|
3516
|
+
globalSetupState: 'present',
|
|
3517
|
+
workspaceSetupState: 'present',
|
|
3518
|
+
runtimeMode: 'cloud',
|
|
3519
|
+
workspaceId: authenticated ? 'workspace-home' : 'default',
|
|
3520
|
+
mode: 'core',
|
|
3521
|
+
forceSetup: false,
|
|
3522
|
+
forceGlobalSetup: false,
|
|
3523
|
+
forceWorkspaceSetup: false
|
|
3524
|
+
}
|
|
3525
|
+
})
|
|
3526
|
+
});
|
|
3527
|
+
}
|
|
3528
|
+
if (url.includes('/api/taskforce/auth/invite/inspect')) {
|
|
3529
|
+
return Promise.resolve({
|
|
3530
|
+
ok: true,
|
|
3531
|
+
json: () => Promise.resolve({
|
|
3532
|
+
success: true,
|
|
3533
|
+
state: 'valid',
|
|
3534
|
+
email: 'existing@example.com',
|
|
3535
|
+
workspaceId: 'workspace-1',
|
|
3536
|
+
inviteeKind: 'existing_user',
|
|
3537
|
+
passwordRequired: false
|
|
3538
|
+
})
|
|
3539
|
+
});
|
|
3540
|
+
}
|
|
3541
|
+
if (url.includes('/api/taskforce/auth/login')) {
|
|
3542
|
+
const body = typeof init?.body === 'string' ? JSON.parse(init.body) : {};
|
|
3543
|
+
authenticated = true;
|
|
3544
|
+
sessionEmail = String(body?.email || '').trim().toLowerCase() || null;
|
|
3545
|
+
return Promise.resolve({
|
|
3546
|
+
ok: true,
|
|
3547
|
+
json: () => Promise.resolve({ success: true })
|
|
3548
|
+
});
|
|
3549
|
+
}
|
|
3550
|
+
if (url.includes('/api/taskforce/auth/invite/join')) {
|
|
3551
|
+
return Promise.resolve({
|
|
3552
|
+
ok: false,
|
|
3553
|
+
json: () => Promise.resolve({
|
|
3554
|
+
success: false,
|
|
3555
|
+
code: 'INVITE_ACCOUNT_MISMATCH',
|
|
3556
|
+
error: 'Sign in with the invited account to join this workspace.'
|
|
3557
|
+
})
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3560
|
+
if (url.includes('/api/taskforce/workspaces')) {
|
|
3561
|
+
return Promise.resolve({
|
|
3562
|
+
ok: true,
|
|
3563
|
+
json: () => Promise.resolve({
|
|
3564
|
+
currentWorkspaceId: authenticated ? 'workspace-home' : null,
|
|
3565
|
+
workspaces: authenticated
|
|
3566
|
+
? [{ id: 'workspace-home', name: 'Home workspace', role: 'owner' }]
|
|
3567
|
+
: []
|
|
3568
|
+
})
|
|
3569
|
+
});
|
|
3570
|
+
}
|
|
3571
|
+
if (url.includes('/api/taskforce/tasks')) {
|
|
3572
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ tasks: [], config: {} }) });
|
|
3573
|
+
}
|
|
3574
|
+
if (url.includes('/api/taskforce/account/access-status')) {
|
|
3575
|
+
return Promise.resolve({
|
|
3576
|
+
ok: true,
|
|
3577
|
+
json: () => Promise.resolve({
|
|
3578
|
+
success: true,
|
|
3579
|
+
authenticated,
|
|
3580
|
+
commercialState: 'active',
|
|
3581
|
+
planSelectionRequired: false,
|
|
3582
|
+
checkoutPending: false
|
|
3583
|
+
})
|
|
3584
|
+
});
|
|
3585
|
+
}
|
|
3586
|
+
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
3587
|
+
return Promise.resolve({
|
|
3588
|
+
ok: true,
|
|
3589
|
+
json: () => Promise.resolve({
|
|
3590
|
+
success: true,
|
|
3591
|
+
currentWorkspaceId: authenticated ? 'workspace-home' : null,
|
|
3592
|
+
workspaces: authenticated ? [{ id: 'workspace-home', name: 'Home workspace' }] : []
|
|
3593
|
+
})
|
|
3594
|
+
});
|
|
3595
|
+
}
|
|
3596
|
+
if (url.includes('/api/taskforce/session/workspace')) {
|
|
3597
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true, workspaceId: 'workspace-home' }) });
|
|
3598
|
+
}
|
|
3599
|
+
if (url.includes('/api/taskforce/ui-state?key=workspace-sync')) {
|
|
3600
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true, state: null }) });
|
|
3601
|
+
}
|
|
3602
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
3603
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ templates: [] }) });
|
|
3604
|
+
}
|
|
3605
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
3606
|
+
});
|
|
3607
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
3608
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=invite&token=invite-token&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
3609
|
+
await waitFor(() => {
|
|
3610
|
+
expect(screen.getByRole('button', { name: 'Sign In to Continue' })).toBeInTheDocument();
|
|
3611
|
+
});
|
|
3612
|
+
await userEvent.clear(screen.getByPlaceholderText('Email'));
|
|
3613
|
+
await userEvent.type(screen.getByPlaceholderText('Email'), 'other@example.com');
|
|
3614
|
+
await userEvent.type(screen.getByPlaceholderText('Password'), 'OtherUserPass123!');
|
|
3615
|
+
await userEvent.click(screen.getByTestId('auth-submit-button'));
|
|
3616
|
+
await waitFor(() => {
|
|
3617
|
+
expect(screen.getByText('Sign in with the invited account to join this workspace.')).toBeInTheDocument();
|
|
3618
|
+
expect(screen.getByDisplayValue('existing@example.com')).toBeInTheDocument();
|
|
3619
|
+
expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
|
|
3620
|
+
expect(screen.queryByRole('button', { name: 'Join Workspace' })).not.toBeInTheDocument();
|
|
3621
|
+
});
|
|
3622
|
+
expect(fetchMock.mock.calls.filter((call) => String(call[0]).includes('/api/taskforce/auth/login'))).toHaveLength(1);
|
|
3623
|
+
expect(fetchMock.mock.calls.some((call) => String(call[0]).includes('/api/taskforce/auth/invite/join'))).toBe(false);
|
|
3624
|
+
});
|
|
3625
|
+
it('shows password creation for new-user invites', async () => {
|
|
3626
|
+
const fetchMock = vi.fn((url) => {
|
|
3627
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
3628
|
+
return Promise.resolve({
|
|
3629
|
+
ok: true,
|
|
3630
|
+
json: () => Promise.resolve({
|
|
3631
|
+
authenticated: false,
|
|
3632
|
+
runtimeMode: 'cloud',
|
|
3633
|
+
authRequiredForApi: true
|
|
3634
|
+
})
|
|
3635
|
+
});
|
|
3636
|
+
}
|
|
3637
|
+
if (url.includes('/api/taskforce/config')) {
|
|
3638
|
+
return Promise.resolve({
|
|
3639
|
+
ok: true,
|
|
3640
|
+
json: () => Promise.resolve({
|
|
3641
|
+
...mockConfig,
|
|
3642
|
+
runtimeMode: 'cloud',
|
|
3643
|
+
authRequiredForApi: true,
|
|
3644
|
+
setupState: {
|
|
3645
|
+
globalSetupState: 'present',
|
|
3646
|
+
workspaceSetupState: 'present',
|
|
3647
|
+
runtimeMode: 'cloud',
|
|
3648
|
+
workspaceId: 'workspace-1',
|
|
3649
|
+
mode: 'core',
|
|
3650
|
+
forceSetup: false,
|
|
3651
|
+
forceGlobalSetup: false,
|
|
3652
|
+
forceWorkspaceSetup: false
|
|
3653
|
+
}
|
|
3654
|
+
})
|
|
3655
|
+
});
|
|
3656
|
+
}
|
|
3657
|
+
if (url.includes('/api/taskforce/auth/invite/inspect')) {
|
|
3658
|
+
return Promise.resolve({
|
|
3659
|
+
ok: true,
|
|
3660
|
+
json: () => Promise.resolve({
|
|
3661
|
+
success: true,
|
|
3662
|
+
state: 'valid',
|
|
3663
|
+
email: 'new@example.com',
|
|
3664
|
+
workspaceId: 'workspace-1',
|
|
3665
|
+
inviteeKind: 'new_user',
|
|
3666
|
+
passwordRequired: true
|
|
3667
|
+
})
|
|
3668
|
+
});
|
|
3669
|
+
}
|
|
3670
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
3671
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ templates: [] }) });
|
|
3672
|
+
}
|
|
3673
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
3674
|
+
});
|
|
3675
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
3676
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=invite&token=invite-token&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
3677
|
+
await waitFor(() => {
|
|
3678
|
+
expect(screen.getByText('Create your account to join this workspace.')).toBeInTheDocument();
|
|
3679
|
+
expect(screen.getByPlaceholderText('Create password')).toBeInTheDocument();
|
|
3680
|
+
expect(screen.getByRole('button', { name: 'Create Account and Join' })).toBeInTheDocument();
|
|
3681
|
+
});
|
|
3682
|
+
});
|
|
3683
|
+
it('shows global setup route when forced and persists selected mode', async () => {
|
|
3684
|
+
const user = userEvent.setup();
|
|
3685
|
+
let selectedMode = null;
|
|
3686
|
+
const fetchMock = vi.fn((url, init) => {
|
|
3687
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
3688
|
+
return Promise.resolve({
|
|
3689
|
+
ok: true,
|
|
3690
|
+
json: () => Promise.resolve({
|
|
3691
|
+
authenticated: true,
|
|
3692
|
+
runtimeMode: 'local',
|
|
3693
|
+
authRequiredForApi: false
|
|
3694
|
+
})
|
|
3695
|
+
});
|
|
3696
|
+
}
|
|
3697
|
+
if (url.includes('/api/taskforce/config')) {
|
|
3698
|
+
const mode = selectedMode;
|
|
3699
|
+
return Promise.resolve({
|
|
3700
|
+
ok: true,
|
|
3701
|
+
json: () => Promise.resolve({
|
|
3702
|
+
...mockConfig,
|
|
3703
|
+
runtimeMode: 'local',
|
|
3704
|
+
authRequiredForApi: false,
|
|
3705
|
+
setupState: {
|
|
3219
3706
|
globalSetupState: mode ? 'present' : 'missing',
|
|
3220
3707
|
workspaceSetupState: 'present',
|
|
3221
3708
|
runtimeMode: 'local',
|
|
@@ -3887,9 +4374,229 @@ describe('TaskforceCore Integration', () => {
|
|
|
3887
4374
|
expect(workspaceSyncStateBody.stateKey).toBe('workspace-sync');
|
|
3888
4375
|
expect(workspaceSyncStateBody.patch?.enabled).toBe(true);
|
|
3889
4376
|
expect(workspaceSyncStateBody.patch?.phase).toBe('attach-cloud');
|
|
4377
|
+
expect(workspaceSyncStateBody.patch?.setupIntent).toBe('attach-cloud-import');
|
|
3890
4378
|
expect(fetchMock.mock.calls.some((call) => String(call[0]).includes('https://cloud.example.com/api/taskforce/workspaces'))).toBe(true);
|
|
3891
4379
|
expect(screen.queryByTitle(/Account/i)).not.toBeInTheDocument();
|
|
3892
4380
|
});
|
|
4381
|
+
it('keeps attach-cloud import blocking after refresh when setup intent is persisted', async () => {
|
|
4382
|
+
const fetchMock = vi.fn((url) => {
|
|
4383
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
4384
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ config: { runtimeMode: 'local', workspaceMode: 'single-local', authSource: 'cloud' } }) });
|
|
4385
|
+
}
|
|
4386
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
4387
|
+
return Promise.resolve({
|
|
4388
|
+
ok: true,
|
|
4389
|
+
json: () => Promise.resolve({
|
|
4390
|
+
authenticated: true,
|
|
4391
|
+
runtimeMode: 'local',
|
|
4392
|
+
authRequiredForApi: false
|
|
4393
|
+
})
|
|
4394
|
+
});
|
|
4395
|
+
}
|
|
4396
|
+
if (url.includes('/api/taskforce/config')) {
|
|
4397
|
+
return Promise.resolve({
|
|
4398
|
+
ok: true,
|
|
4399
|
+
json: () => Promise.resolve({
|
|
4400
|
+
...mockConfig,
|
|
4401
|
+
runtimeMode: 'local',
|
|
4402
|
+
authRequiredForApi: false,
|
|
4403
|
+
workspaceId: 'workspace-cloud-123',
|
|
4404
|
+
setupState: {
|
|
4405
|
+
globalSetupState: 'present',
|
|
4406
|
+
workspaceSetupState: 'missing',
|
|
4407
|
+
runtimeMode: 'local',
|
|
4408
|
+
workspaceId: 'workspace-cloud-123',
|
|
4409
|
+
mode: 'core',
|
|
4410
|
+
forceSetup: false,
|
|
4411
|
+
forceGlobalSetup: false,
|
|
4412
|
+
forceWorkspaceSetup: true,
|
|
4413
|
+
workspace: null,
|
|
4414
|
+
suggestedWorkspaceName: 'Cloud Project 123'
|
|
4415
|
+
},
|
|
4416
|
+
runtimeCapabilities: {
|
|
4417
|
+
runtimeMode: 'local',
|
|
4418
|
+
supportsCloudAuth: true,
|
|
4419
|
+
supportsAdminManagedSetupOverrides: false,
|
|
4420
|
+
supportsPackageScriptSetupOverrides: true
|
|
4421
|
+
}
|
|
4422
|
+
})
|
|
4423
|
+
});
|
|
4424
|
+
}
|
|
4425
|
+
if (url.includes('/api/taskforce/ui-state?key=workspace-sync')) {
|
|
4426
|
+
return Promise.resolve({
|
|
4427
|
+
ok: true,
|
|
4428
|
+
json: () => Promise.resolve({
|
|
4429
|
+
success: true,
|
|
4430
|
+
state: {
|
|
4431
|
+
version: 2,
|
|
4432
|
+
enabled: true,
|
|
4433
|
+
phase: 'attach-cloud',
|
|
4434
|
+
setupIntent: 'attach-cloud-import',
|
|
4435
|
+
pullCursor: null,
|
|
4436
|
+
lastPullAt: null,
|
|
4437
|
+
lastPushAt: null,
|
|
4438
|
+
lastSyncedAt: null
|
|
4439
|
+
}
|
|
4440
|
+
})
|
|
4441
|
+
});
|
|
4442
|
+
}
|
|
4443
|
+
if (url.includes('https://cloud.example.com/api/taskforce/workspaces')) {
|
|
4444
|
+
return Promise.resolve({
|
|
4445
|
+
ok: true,
|
|
4446
|
+
json: () => Promise.resolve({
|
|
4447
|
+
success: true,
|
|
4448
|
+
workspaces: [
|
|
4449
|
+
{
|
|
4450
|
+
id: 'workspace-cloud-123',
|
|
4451
|
+
name: 'Cloud Project 123',
|
|
4452
|
+
description: 'Cloud desc'
|
|
4453
|
+
}
|
|
4454
|
+
]
|
|
4455
|
+
})
|
|
4456
|
+
});
|
|
4457
|
+
}
|
|
4458
|
+
if (url.includes('/api/taskforce/ui-state?key=app')) {
|
|
4459
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true, state: {} }) });
|
|
4460
|
+
}
|
|
4461
|
+
if (url.includes('/api/taskforce/tasks')) {
|
|
4462
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ tasks: mockTasks }) });
|
|
4463
|
+
}
|
|
4464
|
+
if (url.includes('/api/taskforce/archive')) {
|
|
4465
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ archived: [] }) });
|
|
4466
|
+
}
|
|
4467
|
+
if (url.includes('/api/taskforce/categories')) {
|
|
4468
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockCategories) });
|
|
4469
|
+
}
|
|
4470
|
+
if (url.includes('/api/taskforce/global-settings')) {
|
|
4471
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
4472
|
+
}
|
|
4473
|
+
if (url.includes('/api/taskforce/data-version')) {
|
|
4474
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ dataVersion: 1 }) });
|
|
4475
|
+
}
|
|
4476
|
+
if (url.includes('/api/taskforce/types'))
|
|
4477
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ types: [] }) });
|
|
4478
|
+
if (url.includes('/api/taskforce/priorities'))
|
|
4479
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ priorities: [] }) });
|
|
4480
|
+
if (url.includes('/api/taskforce/approaches'))
|
|
4481
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ approaches: [] }) });
|
|
4482
|
+
if (url.includes('/api/taskforce/specialists'))
|
|
4483
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ specialists: [] }) });
|
|
4484
|
+
if (url.includes('/api/taskforce/workflow-templates'))
|
|
4485
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ templates: [] }) });
|
|
4486
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
4487
|
+
});
|
|
4488
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
4489
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/'], children: _jsx(TaskforceCore, { config: { cloudAuthBaseUrl: 'https://cloud.example.com' } }) }));
|
|
4490
|
+
await waitFor(() => {
|
|
4491
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
4492
|
+
});
|
|
4493
|
+
await waitFor(() => {
|
|
4494
|
+
expect(screen.getByText('Importing "Cloud Project 123" from cloud into local.')).toBeInTheDocument();
|
|
4495
|
+
});
|
|
4496
|
+
});
|
|
4497
|
+
it('clears persisted attach-cloud import intent after the first successful import', async () => {
|
|
4498
|
+
const uiStatePosts = [];
|
|
4499
|
+
const fetchMock = vi.fn((url, init) => {
|
|
4500
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
4501
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ config: { runtimeMode: 'local', workspaceMode: 'single-local', authSource: 'cloud' } }) });
|
|
4502
|
+
}
|
|
4503
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
4504
|
+
return Promise.resolve({
|
|
4505
|
+
ok: true,
|
|
4506
|
+
json: () => Promise.resolve({
|
|
4507
|
+
authenticated: true,
|
|
4508
|
+
runtimeMode: 'local',
|
|
4509
|
+
authRequiredForApi: false
|
|
4510
|
+
})
|
|
4511
|
+
});
|
|
4512
|
+
}
|
|
4513
|
+
if (url.includes('/api/taskforce/config')) {
|
|
4514
|
+
return Promise.resolve({
|
|
4515
|
+
ok: true,
|
|
4516
|
+
json: () => Promise.resolve({
|
|
4517
|
+
...mockConfig,
|
|
4518
|
+
runtimeMode: 'local',
|
|
4519
|
+
authRequiredForApi: false,
|
|
4520
|
+
workspaceId: 'workspace-cloud-123'
|
|
4521
|
+
})
|
|
4522
|
+
});
|
|
4523
|
+
}
|
|
4524
|
+
if (url.includes('/api/taskforce/ui-state?key=workspace-sync')) {
|
|
4525
|
+
return Promise.resolve({
|
|
4526
|
+
ok: true,
|
|
4527
|
+
json: () => Promise.resolve({
|
|
4528
|
+
success: true,
|
|
4529
|
+
state: {
|
|
4530
|
+
version: 2,
|
|
4531
|
+
enabled: true,
|
|
4532
|
+
phase: 'active',
|
|
4533
|
+
setupIntent: 'attach-cloud-import',
|
|
4534
|
+
pullCursor: 'cursor-1',
|
|
4535
|
+
lastPullAt: '2026-04-15T12:00:00.000Z',
|
|
4536
|
+
lastPushAt: null,
|
|
4537
|
+
lastSyncedAt: '2026-04-15T12:00:00.000Z'
|
|
4538
|
+
}
|
|
4539
|
+
})
|
|
4540
|
+
});
|
|
4541
|
+
}
|
|
4542
|
+
if (url.includes('/api/taskforce/ui-state') && String(init?.method || '').toUpperCase() === 'POST') {
|
|
4543
|
+
uiStatePosts.push(JSON.parse(String(init?.body || '{}')));
|
|
4544
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true }) });
|
|
4545
|
+
}
|
|
4546
|
+
if (url.includes('https://cloud.example.com/api/taskforce/workspaces')) {
|
|
4547
|
+
return Promise.resolve({
|
|
4548
|
+
ok: true,
|
|
4549
|
+
json: () => Promise.resolve({
|
|
4550
|
+
success: true,
|
|
4551
|
+
workspaces: [
|
|
4552
|
+
{
|
|
4553
|
+
id: 'workspace-cloud-123',
|
|
4554
|
+
name: 'Cloud Project 123',
|
|
4555
|
+
description: 'Cloud desc'
|
|
4556
|
+
}
|
|
4557
|
+
]
|
|
4558
|
+
})
|
|
4559
|
+
});
|
|
4560
|
+
}
|
|
4561
|
+
if (url.includes('/api/taskforce/ui-state?key=app')) {
|
|
4562
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true, state: {} }) });
|
|
4563
|
+
}
|
|
4564
|
+
if (url.includes('/api/taskforce/tasks')) {
|
|
4565
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ tasks: mockTasks }) });
|
|
4566
|
+
}
|
|
4567
|
+
if (url.includes('/api/taskforce/archive')) {
|
|
4568
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ archived: [] }) });
|
|
4569
|
+
}
|
|
4570
|
+
if (url.includes('/api/taskforce/categories')) {
|
|
4571
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockCategories) });
|
|
4572
|
+
}
|
|
4573
|
+
if (url.includes('/api/taskforce/global-settings')) {
|
|
4574
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
4575
|
+
}
|
|
4576
|
+
if (url.includes('/api/taskforce/data-version')) {
|
|
4577
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ dataVersion: 1 }) });
|
|
4578
|
+
}
|
|
4579
|
+
if (url.includes('/api/taskforce/types'))
|
|
4580
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ types: [] }) });
|
|
4581
|
+
if (url.includes('/api/taskforce/priorities'))
|
|
4582
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ priorities: [] }) });
|
|
4583
|
+
if (url.includes('/api/taskforce/approaches'))
|
|
4584
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ approaches: [] }) });
|
|
4585
|
+
if (url.includes('/api/taskforce/specialists'))
|
|
4586
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ specialists: [] }) });
|
|
4587
|
+
if (url.includes('/api/taskforce/workflow-templates'))
|
|
4588
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ templates: [] }) });
|
|
4589
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
4590
|
+
});
|
|
4591
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
4592
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/setup?step=workspace&source=cloud'], children: _jsx(TaskforceCore, { config: { cloudAuthBaseUrl: 'https://cloud.example.com' } }) }));
|
|
4593
|
+
await waitFor(() => {
|
|
4594
|
+
expect(screen.getByText('Test Project')).toBeInTheDocument();
|
|
4595
|
+
});
|
|
4596
|
+
await waitFor(() => {
|
|
4597
|
+
expect(uiStatePosts.some((entry) => entry?.stateKey === 'workspace-sync' && entry?.patch?.setupIntent === null)).toBe(true);
|
|
4598
|
+
});
|
|
4599
|
+
});
|
|
3893
4600
|
it('shows sign-in and register actions on signed-out cloud workspace setup and disables connect', async () => {
|
|
3894
4601
|
const fetchMock = vi.fn((url) => {
|
|
3895
4602
|
if (url.includes('/api/taskforce/auth/session')) {
|
|
@@ -4214,10 +4921,11 @@ describe('TaskforceCore Integration', () => {
|
|
|
4214
4921
|
expect(screen.getByRole('checkbox', { name: /sync to cloud after setup/i })).toBeInTheDocument();
|
|
4215
4922
|
});
|
|
4216
4923
|
await user.click(screen.getByRole('checkbox', { name: /sync to cloud after setup/i }));
|
|
4217
|
-
const nameInput = screen.getByPlaceholderText('
|
|
4924
|
+
const nameInput = screen.getByPlaceholderText('Workspace name');
|
|
4218
4925
|
await user.clear(nameInput);
|
|
4219
4926
|
await user.type(nameInput, 'Local Project');
|
|
4220
|
-
await user.click(screen.getByRole('button', { name: '
|
|
4927
|
+
await user.click(screen.getByRole('button', { name: 'Continue' }));
|
|
4928
|
+
await user.click(screen.getByRole('button', { name: 'Create Workspace' }));
|
|
4221
4929
|
await waitFor(() => {
|
|
4222
4930
|
expect(screen.getByTitle(/Account/i)).toBeInTheDocument();
|
|
4223
4931
|
});
|
|
@@ -4231,10 +4939,11 @@ describe('TaskforceCore Integration', () => {
|
|
|
4231
4939
|
return body?.stateKey === 'workspace-sync';
|
|
4232
4940
|
});
|
|
4233
4941
|
const workspaceSyncStateBody = JSON.parse(String(workspaceSyncStateCall?.[1]?.body || '{}'));
|
|
4234
|
-
expect(workspaceSyncStateBody.patch?.
|
|
4235
|
-
expect(workspaceSyncStateBody.patch?.
|
|
4236
|
-
|
|
4237
|
-
expect(workspaceSyncStateBody.patch?.
|
|
4942
|
+
expect(workspaceSyncStateBody.patch?.version).toBe(2);
|
|
4943
|
+
expect(workspaceSyncStateBody.patch?.enabled).toBe(true);
|
|
4944
|
+
expect(workspaceSyncStateBody.patch?.phase).toBe('provision-local');
|
|
4945
|
+
expect(workspaceSyncStateBody.patch?.setupIntent).toBeNull();
|
|
4946
|
+
expect(workspaceSyncStateBody.patch?.pullCursor).toBeNull();
|
|
4238
4947
|
});
|
|
4239
4948
|
it('creates a new cloud workspace only after guided setup submit', async () => {
|
|
4240
4949
|
const user = userEvent.setup();
|