@taskforcehq/taskforce 0.3.302 → 0.3.303
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/dist/Taskforce.module.css +11 -0
- package/dist/TaskforceCore.js +59 -9
- package/dist/TaskforceCore.test.js +717 -14
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/views/PlansPage.d.ts +3 -1
- package/dist/components/views/PlansPage.js +110 -51
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +100 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +106 -151
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
- package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +29 -5
- package/dist/core/PlanEntitlementService.js +297 -219
- package/dist/core/SignupMonetizationSettings.test.js +173 -49
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/Taskforce.d.ts +51 -10
- package/dist/core/Taskforce.js +218 -49
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/types.d.ts +11 -12
- package/dist/hooks/useSyncOrchestrator.js +17 -3
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
- package/dist/hooks/useTaskforce.d.ts +3 -0
- package/dist/hooks/useTaskforce.js +9 -4
- package/dist/mcp/runtime.js +90 -29
- package/dist/mcp/runtime.test.js +272 -81
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -0
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.js +2 -0
- package/dist/server/index.test.js +2 -1
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +80 -23
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +194 -53
- package/dist/server/routes/billing.js +506 -23
- package/dist/server/routes/billing.test.js +937 -45
- package/dist/server/routes/shared.js +5 -3
- package/dist/server/routes/sync.integration.test.js +160 -0
- package/dist/server/routes/sync.js +15 -5
- package/dist/server/routes.js +3 -3
- package/dist/server/routes.test.js +287 -76
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/contentSyncState.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -16
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +21 -8
- package/dist/sync/workspacePullFeed.d.ts +10 -0
- package/dist/sync/workspacePullFeed.js +111 -63
- package/dist/sync/workspacePullFeed.test.js +112 -0
- package/dist/ui/assets/{AgentsModule-JG5jc3he.js → AgentsModule-CpsTmrIW.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-C3GyzrWm.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-4-Rh2K2n.js} +1 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
- package/dist/ui/assets/{TaskSettings-Bqd4cCzW.js → TaskSettings-BOC5F6Ag.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
- package/dist/ui/assets/index-CLIMgR6g.js +6 -0
- package/dist/ui/assets/index-DneETxcu.css +1 -0
- package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-pWocEipT.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/package.json +3 -3
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/index-MXeWoebC.css +0 -1
- package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
- package/dist/utils/billingStatusCache.d.ts +0 -21
- package/dist/utils/billingStatusCache.js +0 -70
|
@@ -48,6 +48,7 @@ function jsonResponse(body, status = 200) {
|
|
|
48
48
|
describe('TaskforceCore Integration', () => {
|
|
49
49
|
beforeEach(() => {
|
|
50
50
|
localStorage.clear();
|
|
51
|
+
sessionStorage.clear();
|
|
51
52
|
vi.stubGlobal('fetch', vi.fn((url) => {
|
|
52
53
|
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
53
54
|
return Promise.resolve({ ok: true, json: () => Promise.resolve({ config: { runtimeMode: 'local', workspaceMode: 'single-local', authSource: 'cloud' } }) });
|
|
@@ -1037,7 +1038,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1037
1038
|
})
|
|
1038
1039
|
});
|
|
1039
1040
|
}
|
|
1040
|
-
if (url.includes('/api/taskforce/
|
|
1041
|
+
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
1041
1042
|
return Promise.resolve({
|
|
1042
1043
|
ok: true,
|
|
1043
1044
|
json: () => Promise.resolve({
|
|
@@ -1047,7 +1048,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1047
1048
|
})
|
|
1048
1049
|
});
|
|
1049
1050
|
}
|
|
1050
|
-
if (url.includes('/api/taskforce/
|
|
1051
|
+
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
1051
1052
|
return Promise.resolve({
|
|
1052
1053
|
ok: true,
|
|
1053
1054
|
json: () => Promise.resolve({
|
|
@@ -1143,7 +1144,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1143
1144
|
hasEntitlement: false,
|
|
1144
1145
|
canAccessApp: false,
|
|
1145
1146
|
canAccessPlans: true,
|
|
1146
|
-
message: '
|
|
1147
|
+
message: 'Onboarding policy is misconfigured.'
|
|
1147
1148
|
})
|
|
1148
1149
|
});
|
|
1149
1150
|
}
|
|
@@ -1196,7 +1197,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1196
1197
|
render(_jsx(MemoryRouter, { initialEntries: ['/'], children: _jsx(TaskforceCore, {}) }));
|
|
1197
1198
|
await waitFor(() => {
|
|
1198
1199
|
expect(screen.getByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
1199
|
-
expect(screen.getByText('
|
|
1200
|
+
expect(screen.getByText('Onboarding policy is misconfigured. Contact support or a system administrator.')).toBeInTheDocument();
|
|
1200
1201
|
});
|
|
1201
1202
|
});
|
|
1202
1203
|
it('suppresses stale missing-entitlement gate messaging when billing status is already active', async () => {
|
|
@@ -1287,7 +1288,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1287
1288
|
})
|
|
1288
1289
|
});
|
|
1289
1290
|
}
|
|
1290
|
-
if (url.includes('/api/taskforce/
|
|
1291
|
+
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
1291
1292
|
return Promise.resolve({
|
|
1292
1293
|
ok: true,
|
|
1293
1294
|
json: () => Promise.resolve({
|
|
@@ -1388,7 +1389,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1388
1389
|
})
|
|
1389
1390
|
});
|
|
1390
1391
|
}
|
|
1391
|
-
if (url.includes('/api/taskforce/
|
|
1392
|
+
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
1392
1393
|
return Promise.resolve({
|
|
1393
1394
|
ok: true,
|
|
1394
1395
|
json: () => Promise.resolve({
|
|
@@ -1445,7 +1446,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1445
1446
|
await waitFor(() => {
|
|
1446
1447
|
expect(screen.queryByText(/Signed in as/i)).not.toBeInTheDocument();
|
|
1447
1448
|
expect(screen.getByText('member@example.com')).toBeInTheDocument();
|
|
1448
|
-
expect(fetchMock.mock.calls.some((call) => String(call[0]).includes('/api/taskforce/
|
|
1449
|
+
expect(fetchMock.mock.calls.some((call) => String(call[0]).includes('/api/taskforce/account/profile-summary'))).toBe(true);
|
|
1449
1450
|
});
|
|
1450
1451
|
await waitFor(() => {
|
|
1451
1452
|
expect(screen.getByText('Subscription')).toBeInTheDocument();
|
|
@@ -1507,7 +1508,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1507
1508
|
})
|
|
1508
1509
|
});
|
|
1509
1510
|
}
|
|
1510
|
-
if (url.includes('/api/taskforce/
|
|
1511
|
+
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
1511
1512
|
return Promise.resolve({
|
|
1512
1513
|
ok: true,
|
|
1513
1514
|
json: () => Promise.resolve({
|
|
@@ -1569,6 +1570,292 @@ describe('TaskforceCore Integration', () => {
|
|
|
1569
1570
|
expect(screen.getByText('CLOUD.EXAMPLE.COM')).toBeInTheDocument();
|
|
1570
1571
|
});
|
|
1571
1572
|
}, 10000);
|
|
1573
|
+
it('preserves the original user billing and team access after registering and switching back in local runtime', async () => {
|
|
1574
|
+
const user = userEvent.setup();
|
|
1575
|
+
const accounts = {
|
|
1576
|
+
ross: {
|
|
1577
|
+
email: 'ross.burke@gmail.com',
|
|
1578
|
+
userId: 'user-ross',
|
|
1579
|
+
displayName: 'Ross Burke',
|
|
1580
|
+
workspaceId: 'workspace-local-active',
|
|
1581
|
+
planId: 'team-5',
|
|
1582
|
+
planVersionId: 'team-5-v1',
|
|
1583
|
+
planName: 'Team 5',
|
|
1584
|
+
teamManagementAllowed: true,
|
|
1585
|
+
teamPlanMode: 'team',
|
|
1586
|
+
workspaceRole: 'owner'
|
|
1587
|
+
},
|
|
1588
|
+
newcomer: {
|
|
1589
|
+
email: 'new.user@example.com',
|
|
1590
|
+
userId: 'user-new',
|
|
1591
|
+
displayName: 'New User',
|
|
1592
|
+
workspaceId: 'workspace-local-active',
|
|
1593
|
+
planId: 'starter',
|
|
1594
|
+
planVersionId: 'starter-v1',
|
|
1595
|
+
planName: 'Starter',
|
|
1596
|
+
teamManagementAllowed: false,
|
|
1597
|
+
teamPlanMode: 'personal',
|
|
1598
|
+
workspaceRole: 'member'
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
let currentAccountKey = 'ross';
|
|
1602
|
+
const currentAccount = () => (currentAccountKey ? accounts[currentAccountKey] : null);
|
|
1603
|
+
const fetchMock = vi.fn((input, init) => {
|
|
1604
|
+
const url = String(input);
|
|
1605
|
+
const account = currentAccount();
|
|
1606
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
1607
|
+
return jsonResponse({
|
|
1608
|
+
success: true,
|
|
1609
|
+
config: {
|
|
1610
|
+
baseUrl: 'https://cloud.example.com',
|
|
1611
|
+
apiBaseUrl: 'https://cloud.example.com',
|
|
1612
|
+
cloudAuthBaseUrl: 'https://cloud.example.com',
|
|
1613
|
+
runtimeMode: 'local',
|
|
1614
|
+
authSource: 'cloud',
|
|
1615
|
+
workspaceMode: 'single-local',
|
|
1616
|
+
workspaceSwitchingEnabled: false,
|
|
1617
|
+
cloudAuthViaLocalProxy: false
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
1622
|
+
return jsonResponse({
|
|
1623
|
+
authenticated: Boolean(account),
|
|
1624
|
+
runtimeMode: 'local',
|
|
1625
|
+
authRequiredForApi: false,
|
|
1626
|
+
email: account?.email || '',
|
|
1627
|
+
displayName: account?.displayName || '',
|
|
1628
|
+
userId: account?.userId || 'anonymous',
|
|
1629
|
+
workspaceId: account?.workspaceId || '',
|
|
1630
|
+
betaAccess: true
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
if (url.includes('/api/taskforce/auth/logout')) {
|
|
1634
|
+
currentAccountKey = null;
|
|
1635
|
+
return jsonResponse({ success: true });
|
|
1636
|
+
}
|
|
1637
|
+
if (url.includes('/api/taskforce/auth/register')) {
|
|
1638
|
+
currentAccountKey = 'newcomer';
|
|
1639
|
+
return jsonResponse({
|
|
1640
|
+
success: true,
|
|
1641
|
+
workspaceSetupRequired: false,
|
|
1642
|
+
verificationRequired: false
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
if (url.includes('/api/taskforce/auth/login')) {
|
|
1646
|
+
const body = typeof init?.body === 'string' ? JSON.parse(init.body) : {};
|
|
1647
|
+
currentAccountKey = String(body?.email || '').trim().toLowerCase() === accounts.ross.email
|
|
1648
|
+
? 'ross'
|
|
1649
|
+
: 'newcomer';
|
|
1650
|
+
return jsonResponse({ success: true });
|
|
1651
|
+
}
|
|
1652
|
+
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
1653
|
+
return jsonResponse(account
|
|
1654
|
+
? {
|
|
1655
|
+
planId: account.planId,
|
|
1656
|
+
planVersionId: account.planVersionId,
|
|
1657
|
+
planName: account.planName,
|
|
1658
|
+
entitlementState: 'active',
|
|
1659
|
+
workspaceId: account.workspaceId,
|
|
1660
|
+
workspaceRole: account.workspaceRole,
|
|
1661
|
+
canManageWorkspace: account.workspaceRole === 'owner' || account.workspaceRole === 'admin',
|
|
1662
|
+
teamPlanMode: account.teamPlanMode,
|
|
1663
|
+
teamManagementAllowed: account.teamManagementAllowed
|
|
1664
|
+
}
|
|
1665
|
+
: {
|
|
1666
|
+
planId: null,
|
|
1667
|
+
planVersionId: null,
|
|
1668
|
+
planName: null,
|
|
1669
|
+
entitlementState: null,
|
|
1670
|
+
workspaceId: 'workspace-local-active',
|
|
1671
|
+
workspaceRole: null,
|
|
1672
|
+
canManageWorkspace: false,
|
|
1673
|
+
teamPlanMode: 'unknown',
|
|
1674
|
+
teamManagementAllowed: false
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
if (url.includes('/api/taskforce/auth/workspace-members')) {
|
|
1678
|
+
return jsonResponse({
|
|
1679
|
+
users: account ? [{
|
|
1680
|
+
userId: account.userId,
|
|
1681
|
+
email: account.email,
|
|
1682
|
+
displayName: account.displayName,
|
|
1683
|
+
role: account.workspaceRole,
|
|
1684
|
+
status: 'active',
|
|
1685
|
+
disabled: false
|
|
1686
|
+
}] : []
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
if (url.includes('/api/taskforce/public/pricing')) {
|
|
1690
|
+
return jsonResponse({
|
|
1691
|
+
plans: [
|
|
1692
|
+
{
|
|
1693
|
+
planId: 'starter',
|
|
1694
|
+
displayName: 'Starter',
|
|
1695
|
+
status: 'active',
|
|
1696
|
+
defaultVersion: {
|
|
1697
|
+
planVersionId: 'starter-v1',
|
|
1698
|
+
versionNumber: 1,
|
|
1699
|
+
seatLimit: null,
|
|
1700
|
+
metadata: {}
|
|
1701
|
+
},
|
|
1702
|
+
features: [],
|
|
1703
|
+
pricing: {
|
|
1704
|
+
month: { pricingType: 'free', stripePriceId: null, unitAmount: 0, currency: null, interval: 'month' },
|
|
1705
|
+
year: null
|
|
1706
|
+
}
|
|
1707
|
+
},
|
|
1708
|
+
{
|
|
1709
|
+
planId: 'team-5',
|
|
1710
|
+
displayName: 'Team 5',
|
|
1711
|
+
status: 'active',
|
|
1712
|
+
defaultVersion: {
|
|
1713
|
+
planVersionId: 'team-5-v1',
|
|
1714
|
+
versionNumber: 1,
|
|
1715
|
+
seatLimit: 5,
|
|
1716
|
+
metadata: {}
|
|
1717
|
+
},
|
|
1718
|
+
features: [],
|
|
1719
|
+
pricing: {
|
|
1720
|
+
month: { pricingType: 'stripe', stripePriceId: 'price_team_5_month', unitAmount: 5000, currency: 'usd', interval: 'month' },
|
|
1721
|
+
year: { pricingType: 'stripe', stripePriceId: 'price_team_5_year', unitAmount: 50000, currency: 'usd', interval: 'year' }
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
]
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
if (url.includes('/api/taskforce/ui-state?key=app')) {
|
|
1728
|
+
return jsonResponse({ success: true, state: {} });
|
|
1729
|
+
}
|
|
1730
|
+
if (url.includes('/api/taskforce/ui-state?key=workspace-sync')) {
|
|
1731
|
+
return jsonResponse({
|
|
1732
|
+
success: true,
|
|
1733
|
+
state: {
|
|
1734
|
+
version: 2,
|
|
1735
|
+
enabled: false,
|
|
1736
|
+
phase: 'idle',
|
|
1737
|
+
pullCursor: null,
|
|
1738
|
+
lastPullAt: null,
|
|
1739
|
+
lastPushAt: null,
|
|
1740
|
+
lastSyncedAt: null
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
if (url.includes('/api/taskforce/config')) {
|
|
1745
|
+
return jsonResponse({
|
|
1746
|
+
...mockConfig,
|
|
1747
|
+
runtimeMode: 'local',
|
|
1748
|
+
authRequiredForApi: false,
|
|
1749
|
+
workspaceId: 'workspace-local-active',
|
|
1750
|
+
setupState: {
|
|
1751
|
+
globalSetupState: 'present',
|
|
1752
|
+
workspaceSetupState: 'present',
|
|
1753
|
+
runtimeMode: 'local',
|
|
1754
|
+
workspaceId: 'workspace-local-active',
|
|
1755
|
+
mode: 'core',
|
|
1756
|
+
forceSetup: false,
|
|
1757
|
+
forceGlobalSetup: false,
|
|
1758
|
+
forceWorkspaceSetup: false
|
|
1759
|
+
}
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
if (url.includes('/api/taskforce/tasks')) {
|
|
1763
|
+
return jsonResponse({ tasks: mockTasks });
|
|
1764
|
+
}
|
|
1765
|
+
if (url.includes('/api/taskforce/archive')) {
|
|
1766
|
+
return jsonResponse({ archived: [] });
|
|
1767
|
+
}
|
|
1768
|
+
if (url.includes('/api/taskforce/categories')) {
|
|
1769
|
+
return jsonResponse(mockCategories);
|
|
1770
|
+
}
|
|
1771
|
+
if (url.includes('/api/taskforce/types')) {
|
|
1772
|
+
return jsonResponse({ types: [] });
|
|
1773
|
+
}
|
|
1774
|
+
if (url.includes('/api/taskforce/priorities')) {
|
|
1775
|
+
return jsonResponse({ priorities: [] });
|
|
1776
|
+
}
|
|
1777
|
+
if (url.includes('/api/taskforce/approaches')) {
|
|
1778
|
+
return jsonResponse({ approaches: [] });
|
|
1779
|
+
}
|
|
1780
|
+
if (url.includes('/api/taskforce/specialists')) {
|
|
1781
|
+
return jsonResponse({ specialists: [] });
|
|
1782
|
+
}
|
|
1783
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
1784
|
+
return jsonResponse({ templates: [] });
|
|
1785
|
+
}
|
|
1786
|
+
if (url.includes('/api/taskforce/workspace/assignee-options')) {
|
|
1787
|
+
return jsonResponse({ assignees: [] });
|
|
1788
|
+
}
|
|
1789
|
+
if (url.includes('/api/taskforce/initiatives')) {
|
|
1790
|
+
return jsonResponse([]);
|
|
1791
|
+
}
|
|
1792
|
+
if (url.includes('/api/taskforce/workstreams')) {
|
|
1793
|
+
return jsonResponse([]);
|
|
1794
|
+
}
|
|
1795
|
+
return jsonResponse({});
|
|
1796
|
+
});
|
|
1797
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
1798
|
+
render(_jsx(TaskforceCore, { config: { cloudAuthBaseUrl: 'https://cloud.example.com' } }));
|
|
1799
|
+
await waitFor(() => {
|
|
1800
|
+
expect(screen.getByTitle(/Account/i)).toBeInTheDocument();
|
|
1801
|
+
});
|
|
1802
|
+
await user.click(screen.getByTitle(/Account/i));
|
|
1803
|
+
await waitFor(() => {
|
|
1804
|
+
expect(screen.getByText(accounts.ross.email)).toBeInTheDocument();
|
|
1805
|
+
expect(screen.getByText('Team 5')).toBeInTheDocument();
|
|
1806
|
+
expect(screen.getByRole('menuitem', { name: 'Team Management' })).toBeInTheDocument();
|
|
1807
|
+
});
|
|
1808
|
+
await user.click(screen.getByRole('menuitem', { name: 'Sign Out' }));
|
|
1809
|
+
await waitFor(() => {
|
|
1810
|
+
expect(screen.getByTitle(/Account/i)).toBeInTheDocument();
|
|
1811
|
+
});
|
|
1812
|
+
await user.click(screen.getByTitle(/Account/i));
|
|
1813
|
+
await user.click(await screen.findByRole('menuitem', { name: 'Sign In / Register' }));
|
|
1814
|
+
await waitFor(() => {
|
|
1815
|
+
expect(screen.getByRole('button', { name: 'Register' })).toBeInTheDocument();
|
|
1816
|
+
});
|
|
1817
|
+
await user.click(screen.getByRole('button', { name: 'Register' }));
|
|
1818
|
+
await user.type(await screen.findByPlaceholderText('Email'), accounts.newcomer.email);
|
|
1819
|
+
await user.type(screen.getByPlaceholderText('Display name'), accounts.newcomer.displayName);
|
|
1820
|
+
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
1821
|
+
await user.click(screen.getByRole('button', { name: 'Create Account' }));
|
|
1822
|
+
await waitFor(() => {
|
|
1823
|
+
expect(screen.getByTitle(/Account/i)).toBeInTheDocument();
|
|
1824
|
+
});
|
|
1825
|
+
await user.click(screen.getByTitle(/Account/i));
|
|
1826
|
+
await waitFor(() => {
|
|
1827
|
+
expect(screen.getByText(accounts.newcomer.email)).toBeInTheDocument();
|
|
1828
|
+
expect(screen.getByText('Starter')).toBeInTheDocument();
|
|
1829
|
+
});
|
|
1830
|
+
await user.click(screen.getByRole('menuitem', { name: 'Sign Out' }));
|
|
1831
|
+
await waitFor(() => {
|
|
1832
|
+
expect(screen.getByTitle(/Account/i)).toBeInTheDocument();
|
|
1833
|
+
});
|
|
1834
|
+
await user.click(screen.getByTitle(/Account/i));
|
|
1835
|
+
await user.click(await screen.findByRole('menuitem', { name: 'Sign In / Register' }));
|
|
1836
|
+
await waitFor(() => {
|
|
1837
|
+
expect(screen.getByText('Sign in with your account credentials.')).toBeInTheDocument();
|
|
1838
|
+
});
|
|
1839
|
+
await user.type(await screen.findByPlaceholderText('Email'), accounts.ross.email);
|
|
1840
|
+
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
1841
|
+
await user.click(screen.getByRole('button', { name: 'Sign In' }));
|
|
1842
|
+
await waitFor(() => {
|
|
1843
|
+
expect(screen.getByTitle(/Account/i)).toBeInTheDocument();
|
|
1844
|
+
});
|
|
1845
|
+
await user.click(screen.getByTitle(/Account/i));
|
|
1846
|
+
await waitFor(() => {
|
|
1847
|
+
expect(screen.getByText(accounts.ross.email)).toBeInTheDocument();
|
|
1848
|
+
expect(screen.getByText('Team 5')).toBeInTheDocument();
|
|
1849
|
+
expect(screen.queryByText('Starter')).not.toBeInTheDocument();
|
|
1850
|
+
expect(screen.getByRole('menuitem', { name: 'Team Management' })).toBeInTheDocument();
|
|
1851
|
+
});
|
|
1852
|
+
await user.click(screen.getByRole('button', { name: /Subscription Team 5/i }));
|
|
1853
|
+
await waitFor(() => {
|
|
1854
|
+
expect(screen.getByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
1855
|
+
});
|
|
1856
|
+
const currentPlanCard = screen.getByTestId('plan-card-team-5');
|
|
1857
|
+
expect(within(currentPlanCard).getByTestId('plan-manage-team-5')).toHaveTextContent('Current Plan');
|
|
1858
|
+
});
|
|
1572
1859
|
it('allows local runtime to open the shared cloud-backed register route', async () => {
|
|
1573
1860
|
const fetchMock = vi.fn((url) => {
|
|
1574
1861
|
if (url.includes('/api/taskforce/auth/session')) {
|
|
@@ -1685,6 +1972,407 @@ describe('TaskforceCore Integration', () => {
|
|
|
1685
1972
|
expect(screen.queryByPlaceholderText('Verification token')).not.toBeInTheDocument();
|
|
1686
1973
|
expect(screen.queryByRole('button', { name: 'Create Account' })).not.toBeInTheDocument();
|
|
1687
1974
|
});
|
|
1975
|
+
it('routes successful registration into plan selection when onboarding leaves the account pending plan selection', async () => {
|
|
1976
|
+
const user = userEvent.setup();
|
|
1977
|
+
let authenticated = false;
|
|
1978
|
+
const fetchMock = vi.fn((input) => {
|
|
1979
|
+
const url = String(input);
|
|
1980
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
1981
|
+
return jsonResponse({
|
|
1982
|
+
config: {
|
|
1983
|
+
baseUrl: 'https://app.taskforce.example',
|
|
1984
|
+
apiBaseUrl: 'https://app.taskforce.example/api',
|
|
1985
|
+
cloudAuthBaseUrl: 'https://app.taskforce.example',
|
|
1986
|
+
runtimeMode: 'cloud',
|
|
1987
|
+
authSource: 'cloud',
|
|
1988
|
+
workspaceMode: 'multi-cloud',
|
|
1989
|
+
workspaceSwitchingEnabled: true,
|
|
1990
|
+
authRequiredForApi: true
|
|
1991
|
+
}
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
1995
|
+
return jsonResponse({
|
|
1996
|
+
authenticated,
|
|
1997
|
+
runtimeMode: 'cloud',
|
|
1998
|
+
authRequiredForApi: true,
|
|
1999
|
+
userId: authenticated ? 'user-1' : null,
|
|
2000
|
+
workspaceId: authenticated ? 'workspace-1' : null,
|
|
2001
|
+
betaAccess: true
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
if (url.includes('/api/taskforce/config')) {
|
|
2005
|
+
return jsonResponse({
|
|
2006
|
+
...mockConfig,
|
|
2007
|
+
runtimeMode: 'cloud',
|
|
2008
|
+
authRequiredForApi: true,
|
|
2009
|
+
workspaceSwitchingEnabled: true,
|
|
2010
|
+
setupState: {
|
|
2011
|
+
globalSetupState: 'present',
|
|
2012
|
+
workspaceSetupState: 'present',
|
|
2013
|
+
runtimeMode: 'cloud',
|
|
2014
|
+
workspaceId: 'workspace-1',
|
|
2015
|
+
mode: 'core',
|
|
2016
|
+
forceSetup: false,
|
|
2017
|
+
forceGlobalSetup: false,
|
|
2018
|
+
forceWorkspaceSetup: false
|
|
2019
|
+
}
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
if (url.includes('/api/taskforce/workspaces')) {
|
|
2023
|
+
return jsonResponse({
|
|
2024
|
+
success: true,
|
|
2025
|
+
workspaces: [{
|
|
2026
|
+
id: 'workspace-1',
|
|
2027
|
+
slug: 'workspace-1',
|
|
2028
|
+
name: 'Workspace 1',
|
|
2029
|
+
description: null,
|
|
2030
|
+
role: 'owner',
|
|
2031
|
+
status: 'active',
|
|
2032
|
+
betaAccess: true
|
|
2033
|
+
}],
|
|
2034
|
+
currentWorkspaceId: 'workspace-1'
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
if (url.includes('/api/taskforce/account/access-status')) {
|
|
2038
|
+
return jsonResponse({
|
|
2039
|
+
gate: 'plan_selection_required',
|
|
2040
|
+
hasEntitlement: true,
|
|
2041
|
+
canAccessApp: false,
|
|
2042
|
+
canAccessPlans: true,
|
|
2043
|
+
message: 'Choose a plan to continue.'
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
2047
|
+
return jsonResponse({
|
|
2048
|
+
entitlementState: 'pending_plan_selection',
|
|
2049
|
+
gate: 'plan_selection_required',
|
|
2050
|
+
message: 'Choose a plan to continue.',
|
|
2051
|
+
workspaceId: 'workspace-1'
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
if (url.includes('/api/taskforce/public/pricing')) {
|
|
2055
|
+
return jsonResponse({
|
|
2056
|
+
plans: [{
|
|
2057
|
+
planId: 'starter',
|
|
2058
|
+
displayName: 'Starter',
|
|
2059
|
+
status: 'active',
|
|
2060
|
+
defaultVersion: {
|
|
2061
|
+
planVersionId: 'starter-v1',
|
|
2062
|
+
versionNumber: 1,
|
|
2063
|
+
seatLimit: null,
|
|
2064
|
+
metadata: {}
|
|
2065
|
+
},
|
|
2066
|
+
features: [],
|
|
2067
|
+
pricing: {
|
|
2068
|
+
month: {
|
|
2069
|
+
planVersionId: 'starter-v1',
|
|
2070
|
+
interval: 'month',
|
|
2071
|
+
pricingType: 'free',
|
|
2072
|
+
stripePriceId: 'free:starter-v1:month',
|
|
2073
|
+
displayAmount: '$0.00',
|
|
2074
|
+
unitAmount: 0,
|
|
2075
|
+
currency: 'usd'
|
|
2076
|
+
},
|
|
2077
|
+
year: null
|
|
2078
|
+
}
|
|
2079
|
+
}]
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
if (url.includes('/api/taskforce/auth/register')) {
|
|
2083
|
+
authenticated = true;
|
|
2084
|
+
return jsonResponse({
|
|
2085
|
+
success: true,
|
|
2086
|
+
workspaceSetupRequired: false,
|
|
2087
|
+
verificationRequired: false,
|
|
2088
|
+
planSelectionRequired: true,
|
|
2089
|
+
commercialState: 'pending_plan_selection'
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
if (url.includes('/api/taskforce/categories')) {
|
|
2093
|
+
return jsonResponse(mockCategories);
|
|
2094
|
+
}
|
|
2095
|
+
if (url.includes('/api/taskforce/tasks')) {
|
|
2096
|
+
return jsonResponse({ tasks: mockTasks });
|
|
2097
|
+
}
|
|
2098
|
+
if (url.includes('/api/taskforce/archive')) {
|
|
2099
|
+
return jsonResponse({ archived: [] });
|
|
2100
|
+
}
|
|
2101
|
+
if (url.includes('/api/taskforce/data-version')) {
|
|
2102
|
+
return jsonResponse({ dataVersion: 1 });
|
|
2103
|
+
}
|
|
2104
|
+
if (url.includes('/api/taskforce/types')) {
|
|
2105
|
+
return jsonResponse({ types: [] });
|
|
2106
|
+
}
|
|
2107
|
+
if (url.includes('/api/taskforce/priorities')) {
|
|
2108
|
+
return jsonResponse({ priorities: [] });
|
|
2109
|
+
}
|
|
2110
|
+
if (url.includes('/api/taskforce/approaches')) {
|
|
2111
|
+
return jsonResponse({ approaches: [] });
|
|
2112
|
+
}
|
|
2113
|
+
if (url.includes('/api/taskforce/specialists')) {
|
|
2114
|
+
return jsonResponse({ specialists: [] });
|
|
2115
|
+
}
|
|
2116
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
2117
|
+
return jsonResponse({ templates: [] });
|
|
2118
|
+
}
|
|
2119
|
+
return jsonResponse({});
|
|
2120
|
+
});
|
|
2121
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
2122
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
2123
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'pending@example.com');
|
|
2124
|
+
await user.type(screen.getByPlaceholderText('Display name'), 'Pending User');
|
|
2125
|
+
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
2126
|
+
await user.click(screen.getByRole('button', { name: 'Create Account' }));
|
|
2127
|
+
await waitFor(() => {
|
|
2128
|
+
expect(screen.getByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
2129
|
+
expect(screen.getByText('Choose a plan to continue.')).toBeInTheDocument();
|
|
2130
|
+
});
|
|
2131
|
+
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/api/taskforce/billing/checkout-session'))).toBe(false);
|
|
2132
|
+
});
|
|
2133
|
+
it('routes successful registration into checkout when onboarding leaves the account checkout-pending', async () => {
|
|
2134
|
+
const user = userEvent.setup();
|
|
2135
|
+
let authenticated = false;
|
|
2136
|
+
const fetchMock = vi.fn((input) => {
|
|
2137
|
+
const url = String(input);
|
|
2138
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
2139
|
+
return jsonResponse({
|
|
2140
|
+
config: {
|
|
2141
|
+
baseUrl: 'https://app.taskforce.example',
|
|
2142
|
+
apiBaseUrl: 'https://app.taskforce.example/api',
|
|
2143
|
+
cloudAuthBaseUrl: 'https://app.taskforce.example',
|
|
2144
|
+
runtimeMode: 'cloud',
|
|
2145
|
+
authSource: 'cloud',
|
|
2146
|
+
workspaceMode: 'multi-cloud',
|
|
2147
|
+
workspaceSwitchingEnabled: true,
|
|
2148
|
+
authRequiredForApi: true
|
|
2149
|
+
}
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
2153
|
+
return jsonResponse({
|
|
2154
|
+
authenticated,
|
|
2155
|
+
runtimeMode: 'cloud',
|
|
2156
|
+
authRequiredForApi: true,
|
|
2157
|
+
userId: authenticated ? 'user-1' : null,
|
|
2158
|
+
workspaceId: authenticated ? 'workspace-1' : null,
|
|
2159
|
+
betaAccess: true
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
if (url.includes('/api/taskforce/config')) {
|
|
2163
|
+
return jsonResponse({
|
|
2164
|
+
...mockConfig,
|
|
2165
|
+
runtimeMode: 'cloud',
|
|
2166
|
+
authRequiredForApi: true,
|
|
2167
|
+
workspaceSwitchingEnabled: true,
|
|
2168
|
+
setupState: {
|
|
2169
|
+
globalSetupState: 'present',
|
|
2170
|
+
workspaceSetupState: 'present',
|
|
2171
|
+
runtimeMode: 'cloud',
|
|
2172
|
+
workspaceId: 'workspace-1',
|
|
2173
|
+
mode: 'core',
|
|
2174
|
+
forceSetup: false,
|
|
2175
|
+
forceGlobalSetup: false,
|
|
2176
|
+
forceWorkspaceSetup: false
|
|
2177
|
+
}
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
if (url.includes('/api/taskforce/workspaces')) {
|
|
2181
|
+
return jsonResponse({
|
|
2182
|
+
success: true,
|
|
2183
|
+
workspaces: [{
|
|
2184
|
+
id: 'workspace-1',
|
|
2185
|
+
slug: 'workspace-1',
|
|
2186
|
+
name: 'Workspace 1',
|
|
2187
|
+
description: null,
|
|
2188
|
+
role: 'owner',
|
|
2189
|
+
status: 'active',
|
|
2190
|
+
betaAccess: true
|
|
2191
|
+
}],
|
|
2192
|
+
currentWorkspaceId: 'workspace-1'
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
if (url.includes('/api/taskforce/account/access-status')) {
|
|
2196
|
+
return jsonResponse({
|
|
2197
|
+
gate: 'checkout_pending',
|
|
2198
|
+
hasEntitlement: true,
|
|
2199
|
+
canAccessApp: false,
|
|
2200
|
+
canAccessPlans: true,
|
|
2201
|
+
message: 'Complete checkout to continue.'
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
2205
|
+
return jsonResponse({
|
|
2206
|
+
planId: 'pro',
|
|
2207
|
+
planName: 'Pro',
|
|
2208
|
+
planVersionId: 'pro-v1',
|
|
2209
|
+
entitlementState: 'checkout_pending',
|
|
2210
|
+
gate: 'checkout_pending',
|
|
2211
|
+
message: 'Complete checkout to continue.',
|
|
2212
|
+
workspaceId: 'workspace-1'
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
if (url.includes('/api/taskforce/public/pricing')) {
|
|
2216
|
+
return jsonResponse({
|
|
2217
|
+
plans: [{
|
|
2218
|
+
planId: 'pro',
|
|
2219
|
+
displayName: 'Pro',
|
|
2220
|
+
status: 'active',
|
|
2221
|
+
defaultVersion: {
|
|
2222
|
+
planVersionId: 'pro-v1',
|
|
2223
|
+
versionNumber: 1,
|
|
2224
|
+
seatLimit: null,
|
|
2225
|
+
metadata: {}
|
|
2226
|
+
},
|
|
2227
|
+
features: [],
|
|
2228
|
+
pricing: {
|
|
2229
|
+
month: {
|
|
2230
|
+
planVersionId: 'pro-v1',
|
|
2231
|
+
interval: 'month',
|
|
2232
|
+
pricingType: 'stripe',
|
|
2233
|
+
stripePriceId: 'price_pro_month',
|
|
2234
|
+
displayAmount: '$19.00',
|
|
2235
|
+
unitAmount: 1900,
|
|
2236
|
+
currency: 'usd'
|
|
2237
|
+
},
|
|
2238
|
+
year: {
|
|
2239
|
+
planVersionId: 'pro-v1',
|
|
2240
|
+
interval: 'year',
|
|
2241
|
+
pricingType: 'stripe',
|
|
2242
|
+
stripePriceId: 'price_pro_year',
|
|
2243
|
+
displayAmount: '$190.00',
|
|
2244
|
+
unitAmount: 19000,
|
|
2245
|
+
currency: 'usd'
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}]
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
if (url.includes('/api/taskforce/auth/register')) {
|
|
2252
|
+
authenticated = true;
|
|
2253
|
+
return jsonResponse({
|
|
2254
|
+
success: true,
|
|
2255
|
+
workspaceSetupRequired: false,
|
|
2256
|
+
verificationRequired: false,
|
|
2257
|
+
checkoutPending: true,
|
|
2258
|
+
commercialState: 'checkout_pending'
|
|
2259
|
+
});
|
|
2260
|
+
}
|
|
2261
|
+
if (url.includes('/api/taskforce/billing/checkout-session')) {
|
|
2262
|
+
return jsonResponse({
|
|
2263
|
+
checkoutState: 'updated',
|
|
2264
|
+
message: 'Checkout started.'
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
2267
|
+
if (url.includes('/api/taskforce/categories')) {
|
|
2268
|
+
return jsonResponse(mockCategories);
|
|
2269
|
+
}
|
|
2270
|
+
if (url.includes('/api/taskforce/tasks')) {
|
|
2271
|
+
return jsonResponse({ tasks: mockTasks });
|
|
2272
|
+
}
|
|
2273
|
+
if (url.includes('/api/taskforce/archive')) {
|
|
2274
|
+
return jsonResponse({ archived: [] });
|
|
2275
|
+
}
|
|
2276
|
+
if (url.includes('/api/taskforce/data-version')) {
|
|
2277
|
+
return jsonResponse({ dataVersion: 1 });
|
|
2278
|
+
}
|
|
2279
|
+
if (url.includes('/api/taskforce/types')) {
|
|
2280
|
+
return jsonResponse({ types: [] });
|
|
2281
|
+
}
|
|
2282
|
+
if (url.includes('/api/taskforce/priorities')) {
|
|
2283
|
+
return jsonResponse({ priorities: [] });
|
|
2284
|
+
}
|
|
2285
|
+
if (url.includes('/api/taskforce/approaches')) {
|
|
2286
|
+
return jsonResponse({ approaches: [] });
|
|
2287
|
+
}
|
|
2288
|
+
if (url.includes('/api/taskforce/specialists')) {
|
|
2289
|
+
return jsonResponse({ specialists: [] });
|
|
2290
|
+
}
|
|
2291
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
2292
|
+
return jsonResponse({ templates: [] });
|
|
2293
|
+
}
|
|
2294
|
+
return jsonResponse({});
|
|
2295
|
+
});
|
|
2296
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
2297
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&planId=pro&planVersionId=pro-v1&interval=year&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
2298
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'checkout@example.com');
|
|
2299
|
+
await user.type(screen.getByPlaceholderText('Display name'), 'Checkout User');
|
|
2300
|
+
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
2301
|
+
await user.click(screen.getByRole('button', { name: 'Create Account' }));
|
|
2302
|
+
await waitFor(() => {
|
|
2303
|
+
expect(screen.getByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
2304
|
+
});
|
|
2305
|
+
await waitFor(() => {
|
|
2306
|
+
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/api/taskforce/billing/checkout-session'))).toBe(true);
|
|
2307
|
+
});
|
|
2308
|
+
});
|
|
2309
|
+
it('shows verification before workspace setup when registration still requires email verification', async () => {
|
|
2310
|
+
const user = userEvent.setup();
|
|
2311
|
+
const fetchMock = vi.fn((input) => {
|
|
2312
|
+
const url = String(input);
|
|
2313
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
2314
|
+
return Promise.resolve({
|
|
2315
|
+
ok: true,
|
|
2316
|
+
json: () => Promise.resolve({
|
|
2317
|
+
authenticated: false,
|
|
2318
|
+
runtimeMode: 'cloud',
|
|
2319
|
+
authRequiredForApi: true
|
|
2320
|
+
})
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2323
|
+
if (url.includes('/api/taskforce/config')) {
|
|
2324
|
+
return Promise.resolve({
|
|
2325
|
+
ok: true,
|
|
2326
|
+
json: () => Promise.resolve({
|
|
2327
|
+
...mockConfig,
|
|
2328
|
+
runtimeMode: 'cloud',
|
|
2329
|
+
authRequiredForApi: true,
|
|
2330
|
+
setupState: {
|
|
2331
|
+
globalSetupState: 'present',
|
|
2332
|
+
workspaceSetupState: 'present',
|
|
2333
|
+
runtimeMode: 'cloud',
|
|
2334
|
+
workspaceId: 'default',
|
|
2335
|
+
mode: 'core',
|
|
2336
|
+
forceSetup: false,
|
|
2337
|
+
forceGlobalSetup: false,
|
|
2338
|
+
forceWorkspaceSetup: false
|
|
2339
|
+
}
|
|
2340
|
+
})
|
|
2341
|
+
});
|
|
2342
|
+
}
|
|
2343
|
+
if (url.includes('/api/taskforce/public/pricing')) {
|
|
2344
|
+
return Promise.resolve({
|
|
2345
|
+
ok: true,
|
|
2346
|
+
json: () => Promise.resolve({
|
|
2347
|
+
plans: []
|
|
2348
|
+
})
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
if (url.includes('/api/taskforce/auth/register')) {
|
|
2352
|
+
return Promise.resolve({
|
|
2353
|
+
ok: true,
|
|
2354
|
+
json: () => Promise.resolve({
|
|
2355
|
+
success: true,
|
|
2356
|
+
workspaceSetupRequired: true,
|
|
2357
|
+
verificationRequired: true,
|
|
2358
|
+
emailSent: true
|
|
2359
|
+
})
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
2363
|
+
});
|
|
2364
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
2365
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&next=%2Fplans'], children: _jsx(TaskforceCore, {}) }));
|
|
2366
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'local@example.com');
|
|
2367
|
+
await user.type(screen.getByPlaceholderText('Display name'), 'Local User');
|
|
2368
|
+
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
2369
|
+
await user.click(screen.getByRole('button', { name: 'Create Account' }));
|
|
2370
|
+
await waitFor(() => {
|
|
2371
|
+
expect(screen.getByPlaceholderText('Verification token')).toBeInTheDocument();
|
|
2372
|
+
});
|
|
2373
|
+
expect(screen.queryByRole('button', { name: 'Create Account' })).not.toBeInTheDocument();
|
|
2374
|
+
expect(screen.queryByText('Subscription Plans')).not.toBeInTheDocument();
|
|
2375
|
+
});
|
|
1688
2376
|
it('allows returning to sign-in/register from forgot mode in local runtime', async () => {
|
|
1689
2377
|
const user = userEvent.setup();
|
|
1690
2378
|
const fetchMock = vi.fn((url) => {
|
|
@@ -1820,7 +2508,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1820
2508
|
json: () => Promise.resolve({
|
|
1821
2509
|
success: false,
|
|
1822
2510
|
code: 'SIGNUP_PLAN_NOT_ENABLED',
|
|
1823
|
-
error: 'Configured
|
|
2511
|
+
error: 'Configured onboarding plan version \"team-10-v1\" belongs to a disabled plan.'
|
|
1824
2512
|
})
|
|
1825
2513
|
});
|
|
1826
2514
|
}
|
|
@@ -2961,6 +3649,13 @@ describe('TaskforceCore Integration', () => {
|
|
|
2961
3649
|
expect(standaloneLayoutSource).toContain('openSharedLoginRoute(\'login\')');
|
|
2962
3650
|
expect(standaloneLayoutSource).toContain('navigate(`/login?mode=${mode}&next=${encodeURIComponent(target)}`)');
|
|
2963
3651
|
});
|
|
3652
|
+
it('keeps the account badge neutral until the authenticated identity is fully resolved', () => {
|
|
3653
|
+
const standaloneLayoutPath = path.join(process.cwd(), 'src/components/views/StandaloneLayout.tsx');
|
|
3654
|
+
const standaloneLayoutSource = fs.readFileSync(standaloneLayoutPath, 'utf8');
|
|
3655
|
+
expect(standaloneLayoutSource).toContain("const shouldShowResolvedAccountInitial = isAuthenticated && authSessionResolved && accountBadgeLabel.length > 0;");
|
|
3656
|
+
expect(standaloneLayoutSource).toContain("const shouldShowResolvedAccountAvatar = isAuthenticated && authSessionResolved && accountAvatarUrl.length > 0;");
|
|
3657
|
+
expect(standaloneLayoutSource).toContain('<User size={14} className={accountStyles.avatarIcon} />');
|
|
3658
|
+
});
|
|
2964
3659
|
it('keeps workspace management cloud-only while allowing cloud-backed team management from local runtime', () => {
|
|
2965
3660
|
const standaloneLayoutPath = path.join(process.cwd(), 'src/components/views/StandaloneLayout.tsx');
|
|
2966
3661
|
const standaloneLayoutSource = fs.readFileSync(standaloneLayoutPath, 'utf8');
|
|
@@ -2968,17 +3663,25 @@ describe('TaskforceCore Integration', () => {
|
|
|
2968
3663
|
const useTaskforceSource = fs.readFileSync(useTaskforcePath, 'utf8');
|
|
2969
3664
|
expect(standaloneLayoutSource).toContain("const canManageCloudWorkspaces = runtimeMode === 'cloud' && workspaceSwitchingEnabled && isAuthenticated;");
|
|
2970
3665
|
expect(standaloneLayoutSource).toContain("const canAccessCloudTeamManagement = isAuthenticated && (runtimeMode === 'cloud' || cloudAuthConfigured);");
|
|
2971
|
-
expect(standaloneLayoutSource).toContain("const teamManagementWorkspaceId = String(authWorkspaceId || currentWorkspaceId || '').trim();");
|
|
2972
|
-
expect(standaloneLayoutSource).toContain("
|
|
3666
|
+
expect(standaloneLayoutSource).toContain("const teamManagementWorkspaceId = String(accountProfileSummary?.workspaceId || authWorkspaceId || currentWorkspaceId || '').trim();");
|
|
3667
|
+
expect(standaloneLayoutSource).toContain("const accountProfileSummaryUrl = resolveCloudAuthUrl(ACCOUNT_PROFILE_SUMMARY_PATH);");
|
|
2973
3668
|
expect(standaloneLayoutSource).toContain('computeCanOpenTeamManagement(canAccessCloudTeamManagement, teamWorkspaceRole, teamPlanMode)');
|
|
2974
3669
|
expect(useTaskforceSource).toContain("const [authWorkspaceId, setAuthWorkspaceId] = useState<string>('');");
|
|
2975
3670
|
});
|
|
2976
3671
|
it('routes local plans and billing UI through the connected cloud billing base', () => {
|
|
2977
3672
|
const standaloneLayoutPath = path.join(process.cwd(), 'src/components/views/StandaloneLayout.tsx');
|
|
2978
3673
|
const standaloneLayoutSource = fs.readFileSync(standaloneLayoutPath, 'utf8');
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3674
|
+
const plansPagePath = path.join(process.cwd(), 'src/components/views/PlansPage.tsx');
|
|
3675
|
+
const plansPageSource = fs.readFileSync(plansPagePath, 'utf8');
|
|
3676
|
+
const useTaskforcePath = path.join(process.cwd(), 'src/hooks/useTaskforce.ts');
|
|
3677
|
+
const useTaskforceSource = fs.readFileSync(useTaskforcePath, 'utf8');
|
|
3678
|
+
expect(standaloneLayoutSource).toContain("const accountProfileSummaryUrl = resolveCloudAuthUrl(ACCOUNT_PROFILE_SUMMARY_PATH);");
|
|
3679
|
+
expect(standaloneLayoutSource).toContain("resolveCloudAuthUrl('/api/taskforce/billing/portal-session')");
|
|
3680
|
+
expect(standaloneLayoutSource).toContain("resolveCloudAuthUrl={resolveCloudAuthUrl}");
|
|
3681
|
+
expect(plansPageSource).toContain('const buildCloudBillingUrl = useCallback((path: string): string => {');
|
|
3682
|
+
expect(plansPageSource).toContain("const accountProfileSummaryUrl = buildCloudBillingUrl(ACCOUNT_PROFILE_SUMMARY_PATH);");
|
|
3683
|
+
expect(useTaskforceSource).toContain("|| path.startsWith('/api/taskforce/account/')");
|
|
3684
|
+
expect(useTaskforceSource).toContain("|| path.startsWith('/api/taskforce/billing/')");
|
|
2982
3685
|
});
|
|
2983
3686
|
it('uses shared /login flow for local sign in instead of a duplicate modal', async () => {
|
|
2984
3687
|
const standaloneLayoutPath = path.join(process.cwd(), 'src/components/views/StandaloneLayout.tsx');
|