@taskforcehq/taskforce 0.3.303 → 0.3.304
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 +102 -5
- package/dist/TaskforceCore.js +514 -212
- package/dist/TaskforceCore.test.js +1172 -133
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/TaskSettings.js +58 -38
- package/dist/components/features/TaskSettings.test.js +139 -40
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +2 -1
- package/dist/components/task/TaskForm.js +22 -4
- package/dist/components/task/TaskForm.test.js +46 -0
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +31 -160
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +4 -2
- package/dist/components/views/PlanComparisonPage.test.js +4 -0
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +39 -12
- package/dist/components/views/PlansPage.test.js +59 -2
- package/dist/components/views/StandaloneLayout.js +113 -38
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/PlanEntitlementService.d.ts +3 -1
- package/dist/core/PlanEntitlementService.js +61 -11
- package/dist/core/SignupMonetizationSettings.test.js +37 -2
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/Taskforce.d.ts +15 -11
- package/dist/core/Taskforce.js +43 -2
- package/dist/core/WorkspacePlanMode.test.js +65 -0
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +61 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +122 -0
- package/dist/hooks/sync/transfers.js +431 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
- package/dist/hooks/useSyncOrchestrator.js +314 -873
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +568 -3
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +8 -2
- package/dist/hooks/useTaskforce.js +285 -812
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +22 -10
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +174 -476
- package/dist/mcp/runtime.test.js +81 -8
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.js +13 -19
- package/dist/server/routes/auth.js +37 -2
- package/dist/server/routes/billing.js +18 -67
- package/dist/server/routes/billing.test.js +43 -0
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +110 -237
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +8 -19
- package/dist/server/routes.test.js +243 -5
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.js +12 -10
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +377 -0
- package/dist/sync/syncService.d.ts +8 -1
- package/dist/sync/syncService.js +36 -2
- package/dist/sync/syncService.test.js +48 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +22 -1
- package/dist/sync/workspaceSyncModel.js +65 -0
- package/dist/sync/workspaceSyncModel.test.js +119 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-C3GyzrWm.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-BjR6iBf9.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-DnFqdUME.js} +1 -1
- package/dist/ui/assets/index-DXUdtH1B.js +6 -0
- package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +3 -1
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
|
@@ -1897,16 +1897,68 @@ describe('TaskforceCore Integration', () => {
|
|
|
1897
1897
|
const { container } = render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
1898
1898
|
await waitFor(() => {
|
|
1899
1899
|
expect(screen.getByText('Create your Taskforce account.')).toBeInTheDocument();
|
|
1900
|
+
expect(screen.getByLabelText('Email')).toBeInTheDocument();
|
|
1901
|
+
expect(screen.getByLabelText('Display name')).toBeInTheDocument();
|
|
1900
1902
|
expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();
|
|
1901
1903
|
expect(screen.getByPlaceholderText('Display name')).toBeInTheDocument();
|
|
1902
1904
|
expect(screen.getByRole('button', { name: 'Create Account' })).toBeInTheDocument();
|
|
1903
1905
|
expect(screen.getByRole('button', { name: 'Sign In' })).toBeInTheDocument();
|
|
1904
1906
|
expect(screen.queryByRole('button', { name: 'Register' })).not.toBeInTheDocument();
|
|
1905
1907
|
expect(screen.getByRole('button', { name: 'Close sign in' })).toBeInTheDocument();
|
|
1908
|
+
expect(screen.getByTestId('auth-submit-button')).toBeInTheDocument();
|
|
1909
|
+
expect(document.title).toBe('Create Account | Taskforce');
|
|
1906
1910
|
});
|
|
1907
1911
|
expect(container.querySelector('[aria-pressed]')).not.toBeInTheDocument();
|
|
1908
1912
|
});
|
|
1909
|
-
it('
|
|
1913
|
+
it('keeps the verification screen focused on verification and lets the user request a fresh email token', async () => {
|
|
1914
|
+
const fetchMock = vi.fn((url) => {
|
|
1915
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
1916
|
+
return Promise.resolve({
|
|
1917
|
+
ok: true,
|
|
1918
|
+
json: () => Promise.resolve({
|
|
1919
|
+
authenticated: false,
|
|
1920
|
+
runtimeMode: 'cloud',
|
|
1921
|
+
authRequiredForApi: false
|
|
1922
|
+
})
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
if (url.includes('/api/taskforce/config')) {
|
|
1926
|
+
return Promise.resolve({
|
|
1927
|
+
ok: true,
|
|
1928
|
+
json: () => Promise.resolve({
|
|
1929
|
+
...mockConfig,
|
|
1930
|
+
runtimeMode: 'cloud',
|
|
1931
|
+
authRequiredForApi: false,
|
|
1932
|
+
setupState: {
|
|
1933
|
+
globalSetupState: 'present',
|
|
1934
|
+
workspaceSetupState: 'present',
|
|
1935
|
+
runtimeMode: 'cloud',
|
|
1936
|
+
workspaceId: 'workspace-local-active',
|
|
1937
|
+
mode: 'core',
|
|
1938
|
+
forceSetup: false,
|
|
1939
|
+
forceGlobalSetup: false,
|
|
1940
|
+
forceWorkspaceSetup: false
|
|
1941
|
+
}
|
|
1942
|
+
})
|
|
1943
|
+
});
|
|
1944
|
+
}
|
|
1945
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
1946
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ templates: [] }) });
|
|
1947
|
+
}
|
|
1948
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
1949
|
+
});
|
|
1950
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
1951
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=verify'], children: _jsx(TaskforceCore, {}) }));
|
|
1952
|
+
await waitFor(() => {
|
|
1953
|
+
expect(screen.getByText('Enter your verification token.')).toBeInTheDocument();
|
|
1954
|
+
expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();
|
|
1955
|
+
expect(screen.getByRole('button', { name: 'Verify Email' })).toBeInTheDocument();
|
|
1956
|
+
expect(screen.getByRole('button', { name: 'Back to Sign In' })).toBeInTheDocument();
|
|
1957
|
+
expect(screen.queryByRole('button', { name: 'Register' })).not.toBeInTheDocument();
|
|
1958
|
+
expect(screen.getByRole('button', { name: 'Resend Verification' })).toBeDisabled();
|
|
1959
|
+
});
|
|
1960
|
+
});
|
|
1961
|
+
it('allows the verify screen to resend a verification email after the user re-enters their email', async () => {
|
|
1910
1962
|
const user = userEvent.setup();
|
|
1911
1963
|
const fetchMock = vi.fn((input) => {
|
|
1912
1964
|
const url = String(input);
|
|
@@ -1940,6 +1992,126 @@ describe('TaskforceCore Integration', () => {
|
|
|
1940
1992
|
})
|
|
1941
1993
|
});
|
|
1942
1994
|
}
|
|
1995
|
+
if (url.includes('/api/taskforce/auth/register')) {
|
|
1996
|
+
return Promise.resolve({
|
|
1997
|
+
ok: true,
|
|
1998
|
+
json: () => Promise.resolve({
|
|
1999
|
+
success: true,
|
|
2000
|
+
verificationRequired: true,
|
|
2001
|
+
workspaceSetupRequired: false,
|
|
2002
|
+
emailSent: true
|
|
2003
|
+
})
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
if (url.includes('/api/taskforce/auth/verify-email/request')) {
|
|
2007
|
+
return Promise.resolve({
|
|
2008
|
+
ok: true,
|
|
2009
|
+
json: () => Promise.resolve({
|
|
2010
|
+
success: true,
|
|
2011
|
+
verificationToken: 'resent-token'
|
|
2012
|
+
})
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
if (url.includes('/api/taskforce/public/pricing')) {
|
|
2016
|
+
return Promise.resolve({
|
|
2017
|
+
ok: true,
|
|
2018
|
+
json: () => Promise.resolve({
|
|
2019
|
+
plans: []
|
|
2020
|
+
})
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
2024
|
+
});
|
|
2025
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
2026
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=verify'], children: _jsx(TaskforceCore, {}) }));
|
|
2027
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'verify@example.com');
|
|
2028
|
+
await user.click(screen.getByRole('button', { name: 'Resend Verification' }));
|
|
2029
|
+
await waitFor(() => {
|
|
2030
|
+
expect(screen.getByRole('button', { name: /Resend Verification/ })).toBeInTheDocument();
|
|
2031
|
+
expect(screen.getByDisplayValue('resent-token')).toBeInTheDocument();
|
|
2032
|
+
expect(screen.getByText('Verification email resent.')).toBeInTheDocument();
|
|
2033
|
+
});
|
|
2034
|
+
expect(screen.getByRole('button', { name: /Resend Verification/ })).toBeDisabled();
|
|
2035
|
+
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/api/taskforce/auth/verify-email/request'))).toBe(true);
|
|
2036
|
+
});
|
|
2037
|
+
it('does not switch to verify mode after registration when verification is not required', async () => {
|
|
2038
|
+
const user = userEvent.setup();
|
|
2039
|
+
let authenticated = false;
|
|
2040
|
+
const fetchMock = vi.fn((input) => {
|
|
2041
|
+
const url = String(input);
|
|
2042
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
2043
|
+
return Promise.resolve({
|
|
2044
|
+
ok: true,
|
|
2045
|
+
json: () => Promise.resolve({
|
|
2046
|
+
config: {
|
|
2047
|
+
baseUrl: 'https://app.taskforce.example',
|
|
2048
|
+
apiBaseUrl: 'https://app.taskforce.example/api',
|
|
2049
|
+
cloudAuthBaseUrl: 'https://app.taskforce.example',
|
|
2050
|
+
runtimeMode: 'cloud',
|
|
2051
|
+
authSource: 'cloud',
|
|
2052
|
+
workspaceMode: 'multi-cloud',
|
|
2053
|
+
workspaceSwitchingEnabled: true,
|
|
2054
|
+
authRequiredForApi: true
|
|
2055
|
+
}
|
|
2056
|
+
})
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
2060
|
+
return Promise.resolve({
|
|
2061
|
+
ok: true,
|
|
2062
|
+
json: () => Promise.resolve({
|
|
2063
|
+
authenticated,
|
|
2064
|
+
runtimeMode: 'cloud',
|
|
2065
|
+
authRequiredForApi: true,
|
|
2066
|
+
userId: authenticated ? 'user-no-verify' : null,
|
|
2067
|
+
workspaceId: null,
|
|
2068
|
+
betaAccess: true
|
|
2069
|
+
})
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
if (url.includes('/api/taskforce/config')) {
|
|
2073
|
+
return Promise.resolve({
|
|
2074
|
+
ok: true,
|
|
2075
|
+
json: () => Promise.resolve({
|
|
2076
|
+
...mockConfig,
|
|
2077
|
+
runtimeMode: 'cloud',
|
|
2078
|
+
authRequiredForApi: true,
|
|
2079
|
+
workspaceSwitchingEnabled: true,
|
|
2080
|
+
setupState: {
|
|
2081
|
+
globalSetupState: 'present',
|
|
2082
|
+
workspaceSetupState: 'missing',
|
|
2083
|
+
runtimeMode: 'cloud',
|
|
2084
|
+
workspaceId: 'default',
|
|
2085
|
+
mode: 'core',
|
|
2086
|
+
forceSetup: true,
|
|
2087
|
+
forceGlobalSetup: false,
|
|
2088
|
+
forceWorkspaceSetup: true
|
|
2089
|
+
}
|
|
2090
|
+
})
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
if (url.includes('/api/taskforce/workspaces')) {
|
|
2094
|
+
return Promise.resolve({
|
|
2095
|
+
ok: true,
|
|
2096
|
+
json: () => Promise.resolve({
|
|
2097
|
+
success: true,
|
|
2098
|
+
workspaces: [],
|
|
2099
|
+
currentWorkspaceId: 'default'
|
|
2100
|
+
})
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
if (url.includes('/api/taskforce/account/access-status')) {
|
|
2104
|
+
return Promise.resolve({
|
|
2105
|
+
ok: true,
|
|
2106
|
+
json: () => Promise.resolve({
|
|
2107
|
+
gate: 'ok',
|
|
2108
|
+
hasEntitlement: true,
|
|
2109
|
+
canAccessApp: true,
|
|
2110
|
+
canAccessPlans: true,
|
|
2111
|
+
message: null
|
|
2112
|
+
})
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
1943
2115
|
if (url.includes('/api/taskforce/public/pricing')) {
|
|
1944
2116
|
return Promise.resolve({
|
|
1945
2117
|
ok: true,
|
|
@@ -1949,12 +2121,14 @@ describe('TaskforceCore Integration', () => {
|
|
|
1949
2121
|
});
|
|
1950
2122
|
}
|
|
1951
2123
|
if (url.includes('/api/taskforce/auth/register')) {
|
|
2124
|
+
authenticated = true;
|
|
1952
2125
|
return Promise.resolve({
|
|
1953
2126
|
ok: true,
|
|
1954
2127
|
json: () => Promise.resolve({
|
|
1955
2128
|
success: true,
|
|
1956
|
-
workspaceSetupRequired:
|
|
1957
|
-
verificationRequired: false
|
|
2129
|
+
workspaceSetupRequired: true,
|
|
2130
|
+
verificationRequired: false,
|
|
2131
|
+
commercialState: 'active'
|
|
1958
2132
|
})
|
|
1959
2133
|
});
|
|
1960
2134
|
}
|
|
@@ -1967,7 +2141,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1967
2141
|
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
1968
2142
|
await user.click(screen.getByRole('button', { name: 'Create Account' }));
|
|
1969
2143
|
await waitFor(() => {
|
|
1970
|
-
expect(screen.getByText('
|
|
2144
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
1971
2145
|
});
|
|
1972
2146
|
expect(screen.queryByPlaceholderText('Verification token')).not.toBeInTheDocument();
|
|
1973
2147
|
expect(screen.queryByRole('button', { name: 'Create Account' })).not.toBeInTheDocument();
|
|
@@ -2118,19 +2292,453 @@ describe('TaskforceCore Integration', () => {
|
|
|
2118
2292
|
}
|
|
2119
2293
|
return jsonResponse({});
|
|
2120
2294
|
});
|
|
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' }));
|
|
2295
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
2296
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
2297
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'pending@example.com');
|
|
2298
|
+
await user.type(screen.getByPlaceholderText('Display name'), 'Pending User');
|
|
2299
|
+
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
2300
|
+
await user.click(screen.getByRole('button', { name: 'Create Account' }));
|
|
2301
|
+
await waitFor(() => {
|
|
2302
|
+
expect(screen.getByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
2303
|
+
expect(screen.getByText('Choose a plan to continue.')).toBeInTheDocument();
|
|
2304
|
+
});
|
|
2305
|
+
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/api/taskforce/billing/checkout-session'))).toBe(false);
|
|
2306
|
+
});
|
|
2307
|
+
it('keeps registration on plan selection when commercial onboarding is blocked even if the new cloud user has no workspace yet', async () => {
|
|
2308
|
+
const user = userEvent.setup();
|
|
2309
|
+
let authenticated = false;
|
|
2310
|
+
const fetchMock = vi.fn((input) => {
|
|
2311
|
+
const url = String(input);
|
|
2312
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
2313
|
+
return jsonResponse({
|
|
2314
|
+
config: {
|
|
2315
|
+
baseUrl: 'https://app.taskforce.example',
|
|
2316
|
+
apiBaseUrl: 'https://app.taskforce.example/api',
|
|
2317
|
+
cloudAuthBaseUrl: 'https://app.taskforce.example',
|
|
2318
|
+
runtimeMode: 'cloud',
|
|
2319
|
+
authSource: 'cloud',
|
|
2320
|
+
workspaceMode: 'multi-cloud',
|
|
2321
|
+
workspaceSwitchingEnabled: true,
|
|
2322
|
+
authRequiredForApi: true
|
|
2323
|
+
}
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
2327
|
+
return jsonResponse({
|
|
2328
|
+
authenticated,
|
|
2329
|
+
runtimeMode: 'cloud',
|
|
2330
|
+
authRequiredForApi: true,
|
|
2331
|
+
userId: authenticated ? 'user-new' : null,
|
|
2332
|
+
workspaceId: null,
|
|
2333
|
+
betaAccess: true
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
if (url.includes('/api/taskforce/config')) {
|
|
2337
|
+
return jsonResponse({
|
|
2338
|
+
...mockConfig,
|
|
2339
|
+
runtimeMode: 'cloud',
|
|
2340
|
+
authRequiredForApi: true,
|
|
2341
|
+
workspaceSwitchingEnabled: true,
|
|
2342
|
+
setupState: {
|
|
2343
|
+
globalSetupState: 'present',
|
|
2344
|
+
workspaceSetupState: 'missing',
|
|
2345
|
+
runtimeMode: 'cloud',
|
|
2346
|
+
workspaceId: 'default',
|
|
2347
|
+
mode: 'core',
|
|
2348
|
+
forceSetup: false,
|
|
2349
|
+
forceGlobalSetup: false,
|
|
2350
|
+
forceWorkspaceSetup: false
|
|
2351
|
+
}
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
if (url.includes('/api/taskforce/workspaces')) {
|
|
2355
|
+
return jsonResponse({
|
|
2356
|
+
success: true,
|
|
2357
|
+
workspaces: [],
|
|
2358
|
+
currentWorkspaceId: 'default'
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
if (url.includes('/api/taskforce/account/access-status')) {
|
|
2362
|
+
return jsonResponse({
|
|
2363
|
+
gate: 'plan_selection_required',
|
|
2364
|
+
hasEntitlement: true,
|
|
2365
|
+
canAccessApp: false,
|
|
2366
|
+
canAccessPlans: true,
|
|
2367
|
+
message: 'Choose a plan to continue.'
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
2371
|
+
return jsonResponse({
|
|
2372
|
+
entitlementState: 'pending_plan_selection',
|
|
2373
|
+
gate: 'plan_selection_required',
|
|
2374
|
+
message: 'Choose a plan to continue.',
|
|
2375
|
+
workspaceId: null
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
if (url.includes('/api/taskforce/public/pricing')) {
|
|
2379
|
+
return jsonResponse({ plans: [] });
|
|
2380
|
+
}
|
|
2381
|
+
if (url.includes('/api/taskforce/auth/register')) {
|
|
2382
|
+
authenticated = true;
|
|
2383
|
+
return jsonResponse({
|
|
2384
|
+
success: true,
|
|
2385
|
+
workspaceSetupRequired: true,
|
|
2386
|
+
verificationRequired: false,
|
|
2387
|
+
planSelectionRequired: true,
|
|
2388
|
+
commercialState: 'pending_plan_selection'
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
if (url.includes('/api/taskforce/categories'))
|
|
2392
|
+
return jsonResponse(mockCategories);
|
|
2393
|
+
if (url.includes('/api/taskforce/tasks'))
|
|
2394
|
+
return jsonResponse({ tasks: mockTasks });
|
|
2395
|
+
if (url.includes('/api/taskforce/archive'))
|
|
2396
|
+
return jsonResponse({ archived: [] });
|
|
2397
|
+
if (url.includes('/api/taskforce/data-version'))
|
|
2398
|
+
return jsonResponse({ dataVersion: 1 });
|
|
2399
|
+
if (url.includes('/api/taskforce/types'))
|
|
2400
|
+
return jsonResponse({ types: [] });
|
|
2401
|
+
if (url.includes('/api/taskforce/priorities'))
|
|
2402
|
+
return jsonResponse({ priorities: [] });
|
|
2403
|
+
if (url.includes('/api/taskforce/approaches'))
|
|
2404
|
+
return jsonResponse({ approaches: [] });
|
|
2405
|
+
if (url.includes('/api/taskforce/specialists'))
|
|
2406
|
+
return jsonResponse({ specialists: [] });
|
|
2407
|
+
if (url.includes('/api/taskforce/workflow-templates'))
|
|
2408
|
+
return jsonResponse({ templates: [] });
|
|
2409
|
+
return jsonResponse({});
|
|
2410
|
+
});
|
|
2411
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
2412
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
2413
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'brandnew@example.com');
|
|
2414
|
+
await user.type(screen.getByPlaceholderText('Display name'), 'Brand New');
|
|
2415
|
+
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
2416
|
+
await user.click(screen.getByRole('button', { name: 'Create Account' }));
|
|
2417
|
+
await waitFor(() => {
|
|
2418
|
+
expect(screen.getByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
2419
|
+
expect(screen.getByText('Choose a plan to continue.')).toBeInTheDocument();
|
|
2420
|
+
});
|
|
2421
|
+
expect(screen.queryByText('Configure your new workspace.')).not.toBeInTheDocument();
|
|
2422
|
+
});
|
|
2423
|
+
it('routes successful registration into checkout when onboarding leaves the account checkout-pending', async () => {
|
|
2424
|
+
const user = userEvent.setup();
|
|
2425
|
+
let authenticated = false;
|
|
2426
|
+
const fetchMock = vi.fn((input) => {
|
|
2427
|
+
const url = String(input);
|
|
2428
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
2429
|
+
return jsonResponse({
|
|
2430
|
+
config: {
|
|
2431
|
+
baseUrl: 'https://app.taskforce.example',
|
|
2432
|
+
apiBaseUrl: 'https://app.taskforce.example/api',
|
|
2433
|
+
cloudAuthBaseUrl: 'https://app.taskforce.example',
|
|
2434
|
+
runtimeMode: 'cloud',
|
|
2435
|
+
authSource: 'cloud',
|
|
2436
|
+
workspaceMode: 'multi-cloud',
|
|
2437
|
+
workspaceSwitchingEnabled: true,
|
|
2438
|
+
authRequiredForApi: true
|
|
2439
|
+
}
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
2443
|
+
return jsonResponse({
|
|
2444
|
+
authenticated,
|
|
2445
|
+
runtimeMode: 'cloud',
|
|
2446
|
+
authRequiredForApi: true,
|
|
2447
|
+
userId: authenticated ? 'user-1' : null,
|
|
2448
|
+
workspaceId: authenticated ? 'workspace-1' : null,
|
|
2449
|
+
betaAccess: true
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
if (url.includes('/api/taskforce/config')) {
|
|
2453
|
+
return jsonResponse({
|
|
2454
|
+
...mockConfig,
|
|
2455
|
+
runtimeMode: 'cloud',
|
|
2456
|
+
authRequiredForApi: true,
|
|
2457
|
+
workspaceSwitchingEnabled: true,
|
|
2458
|
+
setupState: {
|
|
2459
|
+
globalSetupState: 'present',
|
|
2460
|
+
workspaceSetupState: 'present',
|
|
2461
|
+
runtimeMode: 'cloud',
|
|
2462
|
+
workspaceId: 'workspace-1',
|
|
2463
|
+
mode: 'core',
|
|
2464
|
+
forceSetup: false,
|
|
2465
|
+
forceGlobalSetup: false,
|
|
2466
|
+
forceWorkspaceSetup: false
|
|
2467
|
+
}
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
if (url.includes('/api/taskforce/workspaces')) {
|
|
2471
|
+
return jsonResponse({
|
|
2472
|
+
success: true,
|
|
2473
|
+
workspaces: [{
|
|
2474
|
+
id: 'workspace-1',
|
|
2475
|
+
slug: 'workspace-1',
|
|
2476
|
+
name: 'Workspace 1',
|
|
2477
|
+
description: null,
|
|
2478
|
+
role: 'owner',
|
|
2479
|
+
status: 'active',
|
|
2480
|
+
betaAccess: true
|
|
2481
|
+
}],
|
|
2482
|
+
currentWorkspaceId: 'workspace-1'
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
if (url.includes('/api/taskforce/account/access-status')) {
|
|
2486
|
+
return jsonResponse({
|
|
2487
|
+
gate: 'checkout_pending',
|
|
2488
|
+
hasEntitlement: true,
|
|
2489
|
+
canAccessApp: false,
|
|
2490
|
+
canAccessPlans: true,
|
|
2491
|
+
message: 'Complete checkout to continue.'
|
|
2492
|
+
});
|
|
2493
|
+
}
|
|
2494
|
+
if (url.includes('/api/taskforce/account/profile-summary')) {
|
|
2495
|
+
return jsonResponse({
|
|
2496
|
+
planId: 'pro',
|
|
2497
|
+
planName: 'Pro',
|
|
2498
|
+
planVersionId: 'pro-v1',
|
|
2499
|
+
entitlementState: 'checkout_pending',
|
|
2500
|
+
gate: 'checkout_pending',
|
|
2501
|
+
message: 'Complete checkout to continue.',
|
|
2502
|
+
workspaceId: 'workspace-1'
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
if (url.includes('/api/taskforce/public/pricing')) {
|
|
2506
|
+
return jsonResponse({
|
|
2507
|
+
plans: [{
|
|
2508
|
+
planId: 'pro',
|
|
2509
|
+
displayName: 'Pro',
|
|
2510
|
+
status: 'active',
|
|
2511
|
+
defaultVersion: {
|
|
2512
|
+
planVersionId: 'pro-v1',
|
|
2513
|
+
versionNumber: 1,
|
|
2514
|
+
seatLimit: null,
|
|
2515
|
+
metadata: {}
|
|
2516
|
+
},
|
|
2517
|
+
features: [],
|
|
2518
|
+
pricing: {
|
|
2519
|
+
month: {
|
|
2520
|
+
planVersionId: 'pro-v1',
|
|
2521
|
+
interval: 'month',
|
|
2522
|
+
pricingType: 'stripe',
|
|
2523
|
+
stripePriceId: 'price_pro_month',
|
|
2524
|
+
displayAmount: '$19.00',
|
|
2525
|
+
unitAmount: 1900,
|
|
2526
|
+
currency: 'usd'
|
|
2527
|
+
},
|
|
2528
|
+
year: {
|
|
2529
|
+
planVersionId: 'pro-v1',
|
|
2530
|
+
interval: 'year',
|
|
2531
|
+
pricingType: 'stripe',
|
|
2532
|
+
stripePriceId: 'price_pro_year',
|
|
2533
|
+
displayAmount: '$190.00',
|
|
2534
|
+
unitAmount: 19000,
|
|
2535
|
+
currency: 'usd'
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
}]
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
if (url.includes('/api/taskforce/auth/register')) {
|
|
2542
|
+
authenticated = true;
|
|
2543
|
+
return jsonResponse({
|
|
2544
|
+
success: true,
|
|
2545
|
+
workspaceSetupRequired: false,
|
|
2546
|
+
verificationRequired: false,
|
|
2547
|
+
checkoutPending: true,
|
|
2548
|
+
commercialState: 'checkout_pending'
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
if (url.includes('/api/taskforce/billing/checkout-session')) {
|
|
2552
|
+
return jsonResponse({
|
|
2553
|
+
checkoutState: 'updated',
|
|
2554
|
+
message: 'Checkout started.'
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2557
|
+
if (url.includes('/api/taskforce/categories')) {
|
|
2558
|
+
return jsonResponse(mockCategories);
|
|
2559
|
+
}
|
|
2560
|
+
if (url.includes('/api/taskforce/tasks')) {
|
|
2561
|
+
return jsonResponse({ tasks: mockTasks });
|
|
2562
|
+
}
|
|
2563
|
+
if (url.includes('/api/taskforce/archive')) {
|
|
2564
|
+
return jsonResponse({ archived: [] });
|
|
2565
|
+
}
|
|
2566
|
+
if (url.includes('/api/taskforce/data-version')) {
|
|
2567
|
+
return jsonResponse({ dataVersion: 1 });
|
|
2568
|
+
}
|
|
2569
|
+
if (url.includes('/api/taskforce/types')) {
|
|
2570
|
+
return jsonResponse({ types: [] });
|
|
2571
|
+
}
|
|
2572
|
+
if (url.includes('/api/taskforce/priorities')) {
|
|
2573
|
+
return jsonResponse({ priorities: [] });
|
|
2574
|
+
}
|
|
2575
|
+
if (url.includes('/api/taskforce/approaches')) {
|
|
2576
|
+
return jsonResponse({ approaches: [] });
|
|
2577
|
+
}
|
|
2578
|
+
if (url.includes('/api/taskforce/specialists')) {
|
|
2579
|
+
return jsonResponse({ specialists: [] });
|
|
2580
|
+
}
|
|
2581
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
2582
|
+
return jsonResponse({ templates: [] });
|
|
2583
|
+
}
|
|
2584
|
+
return jsonResponse({});
|
|
2585
|
+
});
|
|
2586
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
2587
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&planId=pro&planVersionId=pro-v1&interval=year&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
2588
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'checkout@example.com');
|
|
2589
|
+
await user.type(screen.getByPlaceholderText('Display name'), 'Checkout User');
|
|
2590
|
+
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
2591
|
+
await user.click(screen.getByRole('button', { name: 'Create Account' }));
|
|
2592
|
+
await waitFor(() => {
|
|
2593
|
+
expect(screen.getByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
2594
|
+
});
|
|
2595
|
+
await waitFor(() => {
|
|
2596
|
+
expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/api/taskforce/billing/checkout-session'))).toBe(true);
|
|
2597
|
+
});
|
|
2598
|
+
});
|
|
2599
|
+
it('routes successful registration into workspace setup when onboarding auto-grants free, even if a stale pre-registration session check resolves later', async () => {
|
|
2600
|
+
const user = userEvent.setup();
|
|
2601
|
+
let authenticated = false;
|
|
2602
|
+
let authSessionCallCount = 0;
|
|
2603
|
+
let releaseInitialSession = null;
|
|
2604
|
+
const initialSessionGate = new Promise((resolve) => {
|
|
2605
|
+
releaseInitialSession = resolve;
|
|
2606
|
+
});
|
|
2607
|
+
const fetchMock = vi.fn((input) => {
|
|
2608
|
+
const url = String(input);
|
|
2609
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
2610
|
+
return jsonResponse({
|
|
2611
|
+
config: {
|
|
2612
|
+
baseUrl: 'https://app.taskforce.example',
|
|
2613
|
+
apiBaseUrl: 'https://app.taskforce.example/api',
|
|
2614
|
+
cloudAuthBaseUrl: 'https://app.taskforce.example',
|
|
2615
|
+
runtimeMode: 'cloud',
|
|
2616
|
+
authSource: 'cloud',
|
|
2617
|
+
workspaceMode: 'multi-cloud',
|
|
2618
|
+
workspaceSwitchingEnabled: true,
|
|
2619
|
+
authRequiredForApi: true
|
|
2620
|
+
}
|
|
2621
|
+
});
|
|
2622
|
+
}
|
|
2623
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
2624
|
+
authSessionCallCount += 1;
|
|
2625
|
+
const payload = () => ({
|
|
2626
|
+
authenticated,
|
|
2627
|
+
runtimeMode: 'cloud',
|
|
2628
|
+
authRequiredForApi: true,
|
|
2629
|
+
userId: authenticated ? 'user-free-1' : null,
|
|
2630
|
+
workspaceId: null,
|
|
2631
|
+
betaAccess: true
|
|
2632
|
+
});
|
|
2633
|
+
if (authSessionCallCount === 1) {
|
|
2634
|
+
return initialSessionGate.then(() => jsonResponse(payload()));
|
|
2635
|
+
}
|
|
2636
|
+
return jsonResponse(payload());
|
|
2637
|
+
}
|
|
2638
|
+
if (url.includes('/api/taskforce/config')) {
|
|
2639
|
+
return jsonResponse({
|
|
2640
|
+
...mockConfig,
|
|
2641
|
+
runtimeMode: 'cloud',
|
|
2642
|
+
authRequiredForApi: true,
|
|
2643
|
+
workspaceSwitchingEnabled: true,
|
|
2644
|
+
setupState: {
|
|
2645
|
+
globalSetupState: 'present',
|
|
2646
|
+
workspaceSetupState: 'missing',
|
|
2647
|
+
runtimeMode: 'cloud',
|
|
2648
|
+
workspaceId: 'default',
|
|
2649
|
+
mode: 'core',
|
|
2650
|
+
forceSetup: true,
|
|
2651
|
+
forceGlobalSetup: false,
|
|
2652
|
+
forceWorkspaceSetup: true
|
|
2653
|
+
}
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
if (url.includes('/api/taskforce/workspaces')) {
|
|
2657
|
+
return jsonResponse({
|
|
2658
|
+
success: true,
|
|
2659
|
+
workspaces: [],
|
|
2660
|
+
currentWorkspaceId: 'default'
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
if (url.includes('/api/taskforce/account/access-status')) {
|
|
2664
|
+
return jsonResponse({
|
|
2665
|
+
gate: 'ok',
|
|
2666
|
+
hasEntitlement: true,
|
|
2667
|
+
canAccessApp: true,
|
|
2668
|
+
canAccessPlans: true,
|
|
2669
|
+
message: null
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
if (url.includes('/api/taskforce/public/pricing')) {
|
|
2673
|
+
return jsonResponse({ plans: [] });
|
|
2674
|
+
}
|
|
2675
|
+
if (url.includes('/api/taskforce/auth/register')) {
|
|
2676
|
+
authenticated = true;
|
|
2677
|
+
return jsonResponse({
|
|
2678
|
+
success: true,
|
|
2679
|
+
workspaceSetupRequired: true,
|
|
2680
|
+
verificationRequired: false,
|
|
2681
|
+
commercialState: 'active'
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
if (url.includes('/api/taskforce/categories'))
|
|
2685
|
+
return jsonResponse(mockCategories);
|
|
2686
|
+
if (url.includes('/api/taskforce/tasks'))
|
|
2687
|
+
return jsonResponse({ tasks: mockTasks });
|
|
2688
|
+
if (url.includes('/api/taskforce/archive'))
|
|
2689
|
+
return jsonResponse({ archived: [] });
|
|
2690
|
+
if (url.includes('/api/taskforce/data-version'))
|
|
2691
|
+
return jsonResponse({ dataVersion: 1 });
|
|
2692
|
+
if (url.includes('/api/taskforce/types'))
|
|
2693
|
+
return jsonResponse({ types: [] });
|
|
2694
|
+
if (url.includes('/api/taskforce/priorities'))
|
|
2695
|
+
return jsonResponse({ priorities: [] });
|
|
2696
|
+
if (url.includes('/api/taskforce/approaches'))
|
|
2697
|
+
return jsonResponse({ approaches: [] });
|
|
2698
|
+
if (url.includes('/api/taskforce/specialists'))
|
|
2699
|
+
return jsonResponse({ specialists: [] });
|
|
2700
|
+
if (url.includes('/api/taskforce/workflow-templates'))
|
|
2701
|
+
return jsonResponse({ templates: [] });
|
|
2702
|
+
if (url.includes('/api/taskforce/taxonomy-library')) {
|
|
2703
|
+
return jsonResponse({
|
|
2704
|
+
packs: [{
|
|
2705
|
+
id: 'general',
|
|
2706
|
+
label: 'General',
|
|
2707
|
+
description: 'Starter',
|
|
2708
|
+
isDefaultStarter: true,
|
|
2709
|
+
sections: {
|
|
2710
|
+
categories: [{ value: 'general', label: 'General' }],
|
|
2711
|
+
taskTypes: [{ value: 'task', label: 'Task' }],
|
|
2712
|
+
priorities: [{ value: 1, label: 'Low' }]
|
|
2713
|
+
}
|
|
2714
|
+
}]
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
return jsonResponse({});
|
|
2718
|
+
});
|
|
2719
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
2720
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
2721
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'free-user@example.com');
|
|
2722
|
+
await user.type(screen.getByPlaceholderText('Display name'), 'Free User');
|
|
2723
|
+
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
2724
|
+
await user.click(screen.getByRole('button', { name: 'Create Account' }));
|
|
2725
|
+
await act(async () => {
|
|
2726
|
+
await Promise.resolve();
|
|
2727
|
+
});
|
|
2728
|
+
await waitFor(() => {
|
|
2729
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
2730
|
+
});
|
|
2731
|
+
expect(fetchMock.mock.calls.filter(([url]) => String(url).includes('/api/taskforce/auth/session')).length).toBeGreaterThanOrEqual(2);
|
|
2732
|
+
await act(async () => {
|
|
2733
|
+
releaseInitialSession?.();
|
|
2734
|
+
await Promise.resolve();
|
|
2735
|
+
});
|
|
2127
2736
|
await waitFor(() => {
|
|
2128
|
-
expect(screen.
|
|
2129
|
-
expect(screen.getByText('Choose a plan to continue.')).toBeInTheDocument();
|
|
2737
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
2130
2738
|
});
|
|
2131
|
-
expect(
|
|
2739
|
+
expect(screen.queryByText('Sign in with your account credentials.')).not.toBeInTheDocument();
|
|
2132
2740
|
});
|
|
2133
|
-
it('routes successful registration into
|
|
2741
|
+
it('routes successful registration directly into the app when onboarding auto-grants free and the new cloud user already has an accessible workspace', async () => {
|
|
2134
2742
|
const user = userEvent.setup();
|
|
2135
2743
|
let authenticated = false;
|
|
2136
2744
|
const fetchMock = vi.fn((input) => {
|
|
@@ -2154,7 +2762,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
2154
2762
|
authenticated,
|
|
2155
2763
|
runtimeMode: 'cloud',
|
|
2156
2764
|
authRequiredForApi: true,
|
|
2157
|
-
userId: authenticated ? 'user-
|
|
2765
|
+
userId: authenticated ? 'user-free-2' : null,
|
|
2158
2766
|
workspaceId: authenticated ? 'workspace-1' : null,
|
|
2159
2767
|
betaAccess: true
|
|
2160
2768
|
});
|
|
@@ -2165,6 +2773,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
2165
2773
|
runtimeMode: 'cloud',
|
|
2166
2774
|
authRequiredForApi: true,
|
|
2167
2775
|
workspaceSwitchingEnabled: true,
|
|
2776
|
+
workspaceId: 'workspace-1',
|
|
2168
2777
|
setupState: {
|
|
2169
2778
|
globalSetupState: 'present',
|
|
2170
2779
|
workspaceSetupState: 'present',
|
|
@@ -2194,59 +2803,15 @@ describe('TaskforceCore Integration', () => {
|
|
|
2194
2803
|
}
|
|
2195
2804
|
if (url.includes('/api/taskforce/account/access-status')) {
|
|
2196
2805
|
return jsonResponse({
|
|
2197
|
-
gate: '
|
|
2806
|
+
gate: 'ok',
|
|
2198
2807
|
hasEntitlement: true,
|
|
2199
|
-
canAccessApp:
|
|
2808
|
+
canAccessApp: true,
|
|
2200
2809
|
canAccessPlans: true,
|
|
2201
|
-
message:
|
|
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'
|
|
2810
|
+
message: null
|
|
2213
2811
|
});
|
|
2214
2812
|
}
|
|
2215
2813
|
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
|
-
});
|
|
2814
|
+
return jsonResponse({ plans: [] });
|
|
2250
2815
|
}
|
|
2251
2816
|
if (url.includes('/api/taskforce/auth/register')) {
|
|
2252
2817
|
authenticated = true;
|
|
@@ -2254,57 +2819,43 @@ describe('TaskforceCore Integration', () => {
|
|
|
2254
2819
|
success: true,
|
|
2255
2820
|
workspaceSetupRequired: false,
|
|
2256
2821
|
verificationRequired: false,
|
|
2257
|
-
|
|
2258
|
-
commercialState: 'checkout_pending'
|
|
2822
|
+
commercialState: 'active'
|
|
2259
2823
|
});
|
|
2260
2824
|
}
|
|
2261
|
-
if (url.includes('/api/taskforce/
|
|
2262
|
-
return jsonResponse({
|
|
2263
|
-
checkoutState: 'updated',
|
|
2264
|
-
message: 'Checkout started.'
|
|
2265
|
-
});
|
|
2266
|
-
}
|
|
2267
|
-
if (url.includes('/api/taskforce/categories')) {
|
|
2825
|
+
if (url.includes('/api/taskforce/categories'))
|
|
2268
2826
|
return jsonResponse(mockCategories);
|
|
2269
|
-
|
|
2270
|
-
if (url.includes('/api/taskforce/tasks')) {
|
|
2827
|
+
if (url.includes('/api/taskforce/tasks'))
|
|
2271
2828
|
return jsonResponse({ tasks: mockTasks });
|
|
2272
|
-
|
|
2273
|
-
if (url.includes('/api/taskforce/archive')) {
|
|
2829
|
+
if (url.includes('/api/taskforce/archive'))
|
|
2274
2830
|
return jsonResponse({ archived: [] });
|
|
2275
|
-
|
|
2276
|
-
if (url.includes('/api/taskforce/data-version')) {
|
|
2831
|
+
if (url.includes('/api/taskforce/data-version'))
|
|
2277
2832
|
return jsonResponse({ dataVersion: 1 });
|
|
2278
|
-
|
|
2279
|
-
if (url.includes('/api/taskforce/types')) {
|
|
2833
|
+
if (url.includes('/api/taskforce/types'))
|
|
2280
2834
|
return jsonResponse({ types: [] });
|
|
2281
|
-
|
|
2282
|
-
if (url.includes('/api/taskforce/priorities')) {
|
|
2835
|
+
if (url.includes('/api/taskforce/priorities'))
|
|
2283
2836
|
return jsonResponse({ priorities: [] });
|
|
2284
|
-
|
|
2285
|
-
if (url.includes('/api/taskforce/approaches')) {
|
|
2837
|
+
if (url.includes('/api/taskforce/approaches'))
|
|
2286
2838
|
return jsonResponse({ approaches: [] });
|
|
2287
|
-
|
|
2288
|
-
if (url.includes('/api/taskforce/specialists')) {
|
|
2839
|
+
if (url.includes('/api/taskforce/specialists'))
|
|
2289
2840
|
return jsonResponse({ specialists: [] });
|
|
2290
|
-
|
|
2291
|
-
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
2841
|
+
if (url.includes('/api/taskforce/workflow-templates'))
|
|
2292
2842
|
return jsonResponse({ templates: [] });
|
|
2293
|
-
}
|
|
2294
2843
|
return jsonResponse({});
|
|
2295
2844
|
});
|
|
2296
2845
|
vi.stubGlobal('fetch', fetchMock);
|
|
2297
|
-
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&
|
|
2298
|
-
await user.type(await screen.findByPlaceholderText('Email'), '
|
|
2299
|
-
await user.type(screen.getByPlaceholderText('Display name'), '
|
|
2846
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
2847
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'existing-workspace@example.com');
|
|
2848
|
+
await user.type(screen.getByPlaceholderText('Display name'), 'Existing Workspace User');
|
|
2300
2849
|
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
2301
2850
|
await user.click(screen.getByRole('button', { name: 'Create Account' }));
|
|
2302
|
-
await
|
|
2303
|
-
|
|
2851
|
+
await act(async () => {
|
|
2852
|
+
await Promise.resolve();
|
|
2304
2853
|
});
|
|
2305
2854
|
await waitFor(() => {
|
|
2306
|
-
expect(
|
|
2855
|
+
expect(screen.getByTitle(/Account/i)).toBeInTheDocument();
|
|
2307
2856
|
});
|
|
2857
|
+
expect(screen.queryByText('Configure your new workspace.')).not.toBeInTheDocument();
|
|
2858
|
+
expect(screen.queryByRole('heading', { name: 'Subscription Plans' })).not.toBeInTheDocument();
|
|
2308
2859
|
});
|
|
2309
2860
|
it('shows verification before workspace setup when registration still requires email verification', async () => {
|
|
2310
2861
|
const user = userEvent.setup();
|
|
@@ -2765,31 +3316,206 @@ describe('TaskforceCore Integration', () => {
|
|
|
2765
3316
|
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
2766
3317
|
});
|
|
2767
3318
|
vi.stubGlobal('fetch', fetchMock);
|
|
2768
|
-
render(_jsx(MemoryRouter, { initialEntries: ['/'], children: _jsx(TaskforceCore, {}) }));
|
|
3319
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/'], children: _jsx(TaskforceCore, {}) }));
|
|
3320
|
+
await waitFor(() => {
|
|
3321
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
3322
|
+
});
|
|
3323
|
+
const nameInput = screen.getByPlaceholderText('Workspace name');
|
|
3324
|
+
await user.clear(nameInput);
|
|
3325
|
+
await user.type(nameInput, 'Delta Project');
|
|
3326
|
+
await user.type(screen.getByPlaceholderText('Workspace description (optional)'), 'Workspace desc');
|
|
3327
|
+
await user.click(screen.getByRole('button', { name: 'Continue' }));
|
|
3328
|
+
await user.click(screen.getByRole('button', { name: 'Create Workspace' }));
|
|
3329
|
+
await waitFor(() => {
|
|
3330
|
+
expect(screen.getByTitle(/Account/i)).toBeInTheDocument();
|
|
3331
|
+
});
|
|
3332
|
+
expect(fetchMock).toHaveBeenCalledWith('/api/taskforce/workspace-profile', expect.objectContaining({ method: 'POST' }));
|
|
3333
|
+
const workspaceSyncStateCall = fetchMock.mock.calls.find((call) => {
|
|
3334
|
+
if (!String(call[0]).includes('/api/taskforce/ui-state'))
|
|
3335
|
+
return false;
|
|
3336
|
+
const init = call[1];
|
|
3337
|
+
if (String(init?.method || '').toUpperCase() !== 'POST')
|
|
3338
|
+
return false;
|
|
3339
|
+
const body = JSON.parse(String(init?.body || '{}'));
|
|
3340
|
+
return body?.stateKey === 'workspace-sync';
|
|
3341
|
+
});
|
|
3342
|
+
const workspaceSyncStateBody = JSON.parse(String(workspaceSyncStateCall?.[1]?.body || '{}'));
|
|
3343
|
+
expect(workspaceSyncStateBody.stateKey).toBe('workspace-sync');
|
|
3344
|
+
expect(workspaceSyncStateBody.patch?.enabled).toBe(false);
|
|
3345
|
+
});
|
|
3346
|
+
it('creates the first cloud workspace through the workspace creation flow during setup', async () => {
|
|
3347
|
+
const user = userEvent.setup();
|
|
3348
|
+
let workspaceCreated = false;
|
|
3349
|
+
const fetchMock = vi.fn((input, init) => {
|
|
3350
|
+
const url = String(input);
|
|
3351
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
3352
|
+
return jsonResponse({
|
|
3353
|
+
success: true,
|
|
3354
|
+
config: {
|
|
3355
|
+
baseUrl: 'https://app.taskforce.example',
|
|
3356
|
+
apiBaseUrl: 'https://app.taskforce.example/api',
|
|
3357
|
+
cloudAuthBaseUrl: 'https://app.taskforce.example',
|
|
3358
|
+
runtimeMode: 'cloud',
|
|
3359
|
+
authSource: 'cloud',
|
|
3360
|
+
workspaceMode: 'multi-cloud',
|
|
3361
|
+
workspaceSwitchingEnabled: true,
|
|
3362
|
+
cloudAuthViaLocalProxy: false
|
|
3363
|
+
}
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3366
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
3367
|
+
return jsonResponse({
|
|
3368
|
+
authenticated: true,
|
|
3369
|
+
runtimeMode: 'cloud',
|
|
3370
|
+
authRequiredForApi: true,
|
|
3371
|
+
userId: 'user-cloud-1',
|
|
3372
|
+
workspaceId: workspaceCreated ? 'workspace-cloud-new' : 'default'
|
|
3373
|
+
});
|
|
3374
|
+
}
|
|
3375
|
+
if (url.includes('/api/taskforce/account/access-status')) {
|
|
3376
|
+
return jsonResponse({
|
|
3377
|
+
gate: 'ok',
|
|
3378
|
+
hasEntitlement: true,
|
|
3379
|
+
canAccessApp: true,
|
|
3380
|
+
canAccessPlans: true,
|
|
3381
|
+
message: null
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
if (url.includes('/api/taskforce/config')) {
|
|
3385
|
+
return jsonResponse({
|
|
3386
|
+
...mockConfig,
|
|
3387
|
+
runtimeMode: 'cloud',
|
|
3388
|
+
authRequiredForApi: true,
|
|
3389
|
+
userId: 'user-cloud-1',
|
|
3390
|
+
projectName: workspaceCreated ? 'Cloud Mission' : '',
|
|
3391
|
+
setupState: {
|
|
3392
|
+
globalSetupState: 'present',
|
|
3393
|
+
workspaceSetupState: workspaceCreated ? 'present' : 'missing',
|
|
3394
|
+
runtimeMode: 'cloud',
|
|
3395
|
+
workspaceId: workspaceCreated ? 'workspace-cloud-new' : 'default',
|
|
3396
|
+
mode: 'core',
|
|
3397
|
+
forceSetup: false,
|
|
3398
|
+
forceGlobalSetup: false,
|
|
3399
|
+
forceWorkspaceSetup: false,
|
|
3400
|
+
workspace: workspaceCreated
|
|
3401
|
+
? { id: 'workspace-cloud-new', slug: 'workspace-cloud-new', name: 'Cloud Mission', description: 'Cloud workspace desc' }
|
|
3402
|
+
: null,
|
|
3403
|
+
suggestedWorkspaceName: ''
|
|
3404
|
+
},
|
|
3405
|
+
runtimeCapabilities: {
|
|
3406
|
+
runtimeMode: 'cloud',
|
|
3407
|
+
supportsCloudAuth: true,
|
|
3408
|
+
supportsAdminManagedSetupOverrides: true,
|
|
3409
|
+
supportsPackageScriptSetupOverrides: false
|
|
3410
|
+
}
|
|
3411
|
+
});
|
|
3412
|
+
}
|
|
3413
|
+
if (url.includes('/api/taskforce/workspaces') && String(init?.method || 'GET').toUpperCase() === 'GET') {
|
|
3414
|
+
return jsonResponse({
|
|
3415
|
+
success: true,
|
|
3416
|
+
workspaces: workspaceCreated
|
|
3417
|
+
? [{
|
|
3418
|
+
id: 'workspace-cloud-new',
|
|
3419
|
+
slug: 'workspace-cloud-new',
|
|
3420
|
+
name: 'Cloud Mission',
|
|
3421
|
+
description: 'Cloud workspace desc',
|
|
3422
|
+
role: 'owner',
|
|
3423
|
+
status: 'active',
|
|
3424
|
+
betaAccess: true
|
|
3425
|
+
}]
|
|
3426
|
+
: [],
|
|
3427
|
+
currentWorkspaceId: workspaceCreated ? 'workspace-cloud-new' : 'default'
|
|
3428
|
+
});
|
|
3429
|
+
}
|
|
3430
|
+
if (url.includes('/api/taskforce/workspaces') && String(init?.method || '').toUpperCase() === 'POST') {
|
|
3431
|
+
workspaceCreated = true;
|
|
3432
|
+
return jsonResponse({
|
|
3433
|
+
success: true,
|
|
3434
|
+
workspace: {
|
|
3435
|
+
id: 'workspace-cloud-new',
|
|
3436
|
+
slug: 'workspace-cloud-new',
|
|
3437
|
+
name: 'Cloud Mission',
|
|
3438
|
+
description: 'Cloud workspace desc',
|
|
3439
|
+
role: 'owner',
|
|
3440
|
+
status: 'active',
|
|
3441
|
+
betaAccess: true
|
|
3442
|
+
}
|
|
3443
|
+
}, 201);
|
|
3444
|
+
}
|
|
3445
|
+
if (url.includes('/api/taskforce/session/workspace')) {
|
|
3446
|
+
return jsonResponse({
|
|
3447
|
+
success: true,
|
|
3448
|
+
workspaceId: 'workspace-cloud-new',
|
|
3449
|
+
role: 'owner'
|
|
3450
|
+
});
|
|
3451
|
+
}
|
|
3452
|
+
if (url.includes('/api/taskforce/ui-state?key=app')) {
|
|
3453
|
+
return jsonResponse({ success: true, state: {} });
|
|
3454
|
+
}
|
|
3455
|
+
if (url.includes('/api/taskforce/ui-state?key=workspace-sync')) {
|
|
3456
|
+
return jsonResponse({
|
|
3457
|
+
success: true,
|
|
3458
|
+
state: {
|
|
3459
|
+
version: 2,
|
|
3460
|
+
enabled: false,
|
|
3461
|
+
phase: 'idle',
|
|
3462
|
+
pullCursor: null,
|
|
3463
|
+
lastPullAt: null,
|
|
3464
|
+
lastPushAt: null,
|
|
3465
|
+
lastSyncedAt: null
|
|
3466
|
+
}
|
|
3467
|
+
});
|
|
3468
|
+
}
|
|
3469
|
+
if (url.includes('/api/taskforce/ui-state')) {
|
|
3470
|
+
return jsonResponse({ success: true });
|
|
3471
|
+
}
|
|
3472
|
+
if (url.includes('/api/taskforce/categories')) {
|
|
3473
|
+
return jsonResponse(mockCategories);
|
|
3474
|
+
}
|
|
3475
|
+
if (url.includes('/api/taskforce/tasks')) {
|
|
3476
|
+
return jsonResponse({ tasks: mockTasks });
|
|
3477
|
+
}
|
|
3478
|
+
if (url.includes('/api/taskforce/archive')) {
|
|
3479
|
+
return jsonResponse({ archived: [] });
|
|
3480
|
+
}
|
|
3481
|
+
if (url.includes('/api/taskforce/data-version')) {
|
|
3482
|
+
return jsonResponse({ dataVersion: 1 });
|
|
3483
|
+
}
|
|
3484
|
+
if (url.includes('/api/taskforce/types')) {
|
|
3485
|
+
return jsonResponse({ types: [] });
|
|
3486
|
+
}
|
|
3487
|
+
if (url.includes('/api/taskforce/priorities')) {
|
|
3488
|
+
return jsonResponse({ priorities: [] });
|
|
3489
|
+
}
|
|
3490
|
+
if (url.includes('/api/taskforce/approaches')) {
|
|
3491
|
+
return jsonResponse({ approaches: [] });
|
|
3492
|
+
}
|
|
3493
|
+
if (url.includes('/api/taskforce/specialists')) {
|
|
3494
|
+
return jsonResponse({ specialists: [] });
|
|
3495
|
+
}
|
|
3496
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
3497
|
+
return jsonResponse({ templates: [] });
|
|
3498
|
+
}
|
|
3499
|
+
return jsonResponse({});
|
|
3500
|
+
});
|
|
3501
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
3502
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/setup?step=workspace'], children: _jsx(TaskforceCore, {}) }));
|
|
2769
3503
|
await waitFor(() => {
|
|
2770
|
-
expect(screen.getByText('
|
|
3504
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
2771
3505
|
});
|
|
2772
|
-
|
|
3506
|
+
expect(screen.getByLabelText('Cloud runtime')).toBeInTheDocument();
|
|
3507
|
+
const nameInput = screen.getByPlaceholderText('Workspace name');
|
|
2773
3508
|
await user.clear(nameInput);
|
|
2774
|
-
await user.type(nameInput, '
|
|
2775
|
-
await user.type(screen.getByPlaceholderText('
|
|
2776
|
-
await user.click(screen.getByRole('button', { name: '
|
|
3509
|
+
await user.type(nameInput, 'Cloud Mission');
|
|
3510
|
+
await user.type(screen.getByPlaceholderText('Workspace description (optional)'), 'Cloud workspace desc');
|
|
3511
|
+
await user.click(screen.getByRole('button', { name: 'Continue' }));
|
|
3512
|
+
await user.click(screen.getByRole('button', { name: 'Create Workspace' }));
|
|
2777
3513
|
await waitFor(() => {
|
|
2778
3514
|
expect(screen.getByTitle(/Account/i)).toBeInTheDocument();
|
|
2779
3515
|
});
|
|
2780
|
-
expect(fetchMock).
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
return false;
|
|
2784
|
-
const init = call[1];
|
|
2785
|
-
if (String(init?.method || '').toUpperCase() !== 'POST')
|
|
2786
|
-
return false;
|
|
2787
|
-
const body = JSON.parse(String(init?.body || '{}'));
|
|
2788
|
-
return body?.stateKey === 'workspace-sync';
|
|
2789
|
-
});
|
|
2790
|
-
const workspaceSyncStateBody = JSON.parse(String(workspaceSyncStateCall?.[1]?.body || '{}'));
|
|
2791
|
-
expect(workspaceSyncStateBody.stateKey).toBe('workspace-sync');
|
|
2792
|
-
expect(workspaceSyncStateBody.patch?.enabled).toBe(false);
|
|
3516
|
+
expect(fetchMock.mock.calls.some((call) => (String(call[0]).includes('/api/taskforce/workspaces')
|
|
3517
|
+
&& String(call[1]?.method || '').toUpperCase() === 'POST'))).toBe(true);
|
|
3518
|
+
expect(fetchMock.mock.calls.some((call) => String(call[0]).includes('/api/taskforce/workspace-profile'))).toBe(false);
|
|
2793
3519
|
});
|
|
2794
3520
|
it('applies the selected starter taxonomy pack during workspace setup', async () => {
|
|
2795
3521
|
const user = userEvent.setup();
|
|
@@ -2897,10 +3623,22 @@ describe('TaskforceCore Integration', () => {
|
|
|
2897
3623
|
vi.stubGlobal('fetch', fetchMock);
|
|
2898
3624
|
render(_jsx(MemoryRouter, { initialEntries: ['/'], children: _jsx(TaskforceCore, {}) }));
|
|
2899
3625
|
await waitFor(() => {
|
|
2900
|
-
expect(screen.getByText('
|
|
2901
|
-
});
|
|
2902
|
-
|
|
2903
|
-
await user.click(screen.getByRole('button', { name: '
|
|
3626
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
3627
|
+
});
|
|
3628
|
+
expect(screen.getByLabelText('Local runtime')).toBeInTheDocument();
|
|
3629
|
+
await user.click(screen.getByRole('button', { name: 'Continue' }));
|
|
3630
|
+
expect(screen.queryByRole('radio', { name: /create new local workspace/i })).not.toBeInTheDocument();
|
|
3631
|
+
expect(screen.queryByRole('radio', { name: /sync existing cloud workspace/i })).not.toBeInTheDocument();
|
|
3632
|
+
expect(screen.queryByText(/Includes \d+ categories?/i)).not.toBeInTheDocument();
|
|
3633
|
+
expect(screen.getByRole('option', { name: 'General' })).toBeInTheDocument();
|
|
3634
|
+
expect(screen.getByText('Task')).toBeInTheDocument();
|
|
3635
|
+
expect(screen.getByText('Low')).toBeInTheDocument();
|
|
3636
|
+
await user.selectOptions(screen.getByRole('combobox'), 'software-development');
|
|
3637
|
+
expect(screen.getByText('Engineering')).toBeInTheDocument();
|
|
3638
|
+
expect(screen.getByText('Feature, Bug')).toBeInTheDocument();
|
|
3639
|
+
expect(screen.getByText('Low, Medium, High, Critical')).toBeInTheDocument();
|
|
3640
|
+
await user.click(screen.getByRole('button', { name: 'Continue' }));
|
|
3641
|
+
await user.click(screen.getByRole('button', { name: 'Create Workspace' }));
|
|
2904
3642
|
await waitFor(() => {
|
|
2905
3643
|
expect(screen.getByTitle(/Account/i)).toBeInTheDocument();
|
|
2906
3644
|
});
|
|
@@ -2993,9 +3731,9 @@ describe('TaskforceCore Integration', () => {
|
|
|
2993
3731
|
expect(configCalls).toBeGreaterThan(1);
|
|
2994
3732
|
});
|
|
2995
3733
|
expect(screen.getByRole('button', { name: /add task/i })).toBeInTheDocument();
|
|
2996
|
-
expect(screen.queryByText('
|
|
3734
|
+
expect(screen.queryByText('Configure your new workspace.')).not.toBeInTheDocument();
|
|
2997
3735
|
});
|
|
2998
|
-
it('
|
|
3736
|
+
it('keeps setup blocking while the selected cloud workspace is performing its first local import', async () => {
|
|
2999
3737
|
const user = userEvent.setup();
|
|
3000
3738
|
let workspaceSaved = false;
|
|
3001
3739
|
const fetchMock = vi.fn((url, init) => {
|
|
@@ -3071,9 +3809,45 @@ describe('TaskforceCore Integration', () => {
|
|
|
3071
3809
|
if (url.includes('/api/taskforce/session/workspace')) {
|
|
3072
3810
|
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true }) });
|
|
3073
3811
|
}
|
|
3812
|
+
if (url.includes('/api/taskforce/ui-state?key=workspace-sync')) {
|
|
3813
|
+
return Promise.resolve({
|
|
3814
|
+
ok: true,
|
|
3815
|
+
json: () => Promise.resolve({
|
|
3816
|
+
success: true,
|
|
3817
|
+
state: workspaceSaved
|
|
3818
|
+
? {
|
|
3819
|
+
version: 2,
|
|
3820
|
+
enabled: true,
|
|
3821
|
+
phase: 'attach-cloud',
|
|
3822
|
+
pullCursor: null,
|
|
3823
|
+
lastPullAt: null,
|
|
3824
|
+
lastPushAt: null,
|
|
3825
|
+
lastSyncedAt: null
|
|
3826
|
+
}
|
|
3827
|
+
: {
|
|
3828
|
+
version: 2,
|
|
3829
|
+
enabled: false,
|
|
3830
|
+
phase: 'idle',
|
|
3831
|
+
pullCursor: null,
|
|
3832
|
+
lastPullAt: null,
|
|
3833
|
+
lastPushAt: null,
|
|
3834
|
+
lastSyncedAt: null
|
|
3835
|
+
}
|
|
3836
|
+
})
|
|
3837
|
+
});
|
|
3838
|
+
}
|
|
3074
3839
|
if (url.includes('/api/taskforce/ui-state')) {
|
|
3075
3840
|
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true }) });
|
|
3076
3841
|
}
|
|
3842
|
+
if (url.includes('https://cloud.example.com/api/taskforce/sync/workspace/handshake')) {
|
|
3843
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true }) });
|
|
3844
|
+
}
|
|
3845
|
+
if (url.includes('https://cloud.example.com/api/taskforce/sync/workspace/pull')) {
|
|
3846
|
+
return new Promise(() => { });
|
|
3847
|
+
}
|
|
3848
|
+
if (url === '/api/taskforce/sync/workspace/apply-local') {
|
|
3849
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true }) });
|
|
3850
|
+
}
|
|
3077
3851
|
if (url.includes('/api/taskforce/tasks')) {
|
|
3078
3852
|
return Promise.resolve({ ok: true, json: () => Promise.resolve({ tasks: mockTasks }) });
|
|
3079
3853
|
}
|
|
@@ -3085,17 +3859,18 @@ describe('TaskforceCore Integration', () => {
|
|
|
3085
3859
|
vi.stubGlobal('fetch', fetchMock);
|
|
3086
3860
|
render(_jsx(MemoryRouter, { initialEntries: ['/setup?step=workspace&source=cloud'], children: _jsx(TaskforceCore, { config: { cloudAuthBaseUrl: 'https://cloud.example.com' } }) }));
|
|
3087
3861
|
await waitFor(() => {
|
|
3088
|
-
expect(screen.getByText('
|
|
3862
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
3089
3863
|
});
|
|
3090
|
-
await user.click(screen.getByRole('radio', { name: /sync existing cloud project/i }));
|
|
3091
3864
|
await waitFor(() => {
|
|
3092
3865
|
expect(screen.getByRole('option', { name: /Cloud Project 123/ })).toBeInTheDocument();
|
|
3093
3866
|
});
|
|
3094
3867
|
await user.selectOptions(screen.getByRole('combobox'), 'workspace-cloud-123');
|
|
3095
|
-
await user.click(screen.getByRole('button', { name: '
|
|
3868
|
+
await user.click(screen.getByRole('button', { name: 'Connect Workspace' }));
|
|
3096
3869
|
await waitFor(() => {
|
|
3097
|
-
expect(screen.
|
|
3870
|
+
expect(screen.getByText('Importing "Cloud Project 123" from cloud into local.')).toBeInTheDocument();
|
|
3098
3871
|
});
|
|
3872
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
3873
|
+
expect(screen.getByRole('button', { name: 'Importing Workspace...' })).toBeInTheDocument();
|
|
3099
3874
|
const workspaceProfileCall = fetchMock.mock.calls.find((call) => String(call[0]).includes('/api/taskforce/workspace-profile'));
|
|
3100
3875
|
const workspaceProfileBody = JSON.parse(String(workspaceProfileCall?.[1]?.body || '{}'));
|
|
3101
3876
|
expect(workspaceProfileBody.workspaceId).toBe('workspace-cloud-123');
|
|
@@ -3110,8 +3885,258 @@ describe('TaskforceCore Integration', () => {
|
|
|
3110
3885
|
});
|
|
3111
3886
|
const workspaceSyncStateBody = JSON.parse(String(workspaceSyncStateCall?.[1]?.body || '{}'));
|
|
3112
3887
|
expect(workspaceSyncStateBody.stateKey).toBe('workspace-sync');
|
|
3113
|
-
expect(workspaceSyncStateBody.patch?.
|
|
3888
|
+
expect(workspaceSyncStateBody.patch?.enabled).toBe(true);
|
|
3889
|
+
expect(workspaceSyncStateBody.patch?.phase).toBe('attach-cloud');
|
|
3114
3890
|
expect(fetchMock.mock.calls.some((call) => String(call[0]).includes('https://cloud.example.com/api/taskforce/workspaces'))).toBe(true);
|
|
3891
|
+
expect(screen.queryByTitle(/Account/i)).not.toBeInTheDocument();
|
|
3892
|
+
});
|
|
3893
|
+
it('shows sign-in and register actions on signed-out cloud workspace setup and disables connect', async () => {
|
|
3894
|
+
const fetchMock = vi.fn((url) => {
|
|
3895
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
3896
|
+
return Promise.resolve({
|
|
3897
|
+
ok: true,
|
|
3898
|
+
json: () => Promise.resolve({
|
|
3899
|
+
authenticated: false,
|
|
3900
|
+
runtimeMode: 'local',
|
|
3901
|
+
authRequiredForApi: false
|
|
3902
|
+
})
|
|
3903
|
+
});
|
|
3904
|
+
}
|
|
3905
|
+
if (url.includes('/api/taskforce/config')) {
|
|
3906
|
+
return Promise.resolve({
|
|
3907
|
+
ok: true,
|
|
3908
|
+
json: () => Promise.resolve({
|
|
3909
|
+
...mockConfig,
|
|
3910
|
+
runtimeMode: 'local',
|
|
3911
|
+
authRequiredForApi: false,
|
|
3912
|
+
setupState: {
|
|
3913
|
+
globalSetupState: 'present',
|
|
3914
|
+
workspaceSetupState: 'missing',
|
|
3915
|
+
runtimeMode: 'local',
|
|
3916
|
+
workspaceId: 'default',
|
|
3917
|
+
mode: 'core',
|
|
3918
|
+
forceSetup: false,
|
|
3919
|
+
forceGlobalSetup: false,
|
|
3920
|
+
forceWorkspaceSetup: true,
|
|
3921
|
+
workspace: null,
|
|
3922
|
+
suggestedWorkspaceName: 'Suggested Project'
|
|
3923
|
+
},
|
|
3924
|
+
runtimeCapabilities: {
|
|
3925
|
+
runtimeMode: 'local',
|
|
3926
|
+
supportsCloudAuth: true,
|
|
3927
|
+
supportsAdminManagedSetupOverrides: false,
|
|
3928
|
+
supportsPackageScriptSetupOverrides: true
|
|
3929
|
+
}
|
|
3930
|
+
})
|
|
3931
|
+
});
|
|
3932
|
+
}
|
|
3933
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
3934
|
+
});
|
|
3935
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
3936
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/setup?step=workspace&source=cloud&postAuth=select-cloud-workspace'], children: _jsx(TaskforceCore, { config: { cloudAuthBaseUrl: 'https://cloud.example.com' } }) }));
|
|
3937
|
+
await waitFor(() => {
|
|
3938
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
3939
|
+
});
|
|
3940
|
+
expect(screen.getByRole('button', { name: 'Sign In' })).toBeInTheDocument();
|
|
3941
|
+
expect(screen.getByRole('button', { name: 'Register' })).toBeInTheDocument();
|
|
3942
|
+
expect(screen.getByRole('button', { name: 'Connect Workspace' })).toBeDisabled();
|
|
3943
|
+
});
|
|
3944
|
+
it('returns signed-in users to the cloud workspace picker after setup sign-in', async () => {
|
|
3945
|
+
const user = userEvent.setup();
|
|
3946
|
+
let authenticated = false;
|
|
3947
|
+
const fetchMock = vi.fn((url, init) => {
|
|
3948
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
3949
|
+
return Promise.resolve({
|
|
3950
|
+
ok: true,
|
|
3951
|
+
json: () => Promise.resolve({
|
|
3952
|
+
authenticated,
|
|
3953
|
+
runtimeMode: 'local',
|
|
3954
|
+
authRequiredForApi: false
|
|
3955
|
+
})
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3958
|
+
if (url.includes('/api/taskforce/config')) {
|
|
3959
|
+
return Promise.resolve({
|
|
3960
|
+
ok: true,
|
|
3961
|
+
json: () => Promise.resolve({
|
|
3962
|
+
...mockConfig,
|
|
3963
|
+
runtimeMode: 'local',
|
|
3964
|
+
authRequiredForApi: false,
|
|
3965
|
+
setupState: {
|
|
3966
|
+
globalSetupState: 'present',
|
|
3967
|
+
workspaceSetupState: 'missing',
|
|
3968
|
+
runtimeMode: 'local',
|
|
3969
|
+
workspaceId: 'default',
|
|
3970
|
+
mode: 'core',
|
|
3971
|
+
forceSetup: false,
|
|
3972
|
+
forceGlobalSetup: false,
|
|
3973
|
+
forceWorkspaceSetup: true,
|
|
3974
|
+
workspace: null,
|
|
3975
|
+
suggestedWorkspaceName: 'Suggested Project'
|
|
3976
|
+
},
|
|
3977
|
+
runtimeCapabilities: {
|
|
3978
|
+
runtimeMode: 'local',
|
|
3979
|
+
supportsCloudAuth: true,
|
|
3980
|
+
supportsAdminManagedSetupOverrides: false,
|
|
3981
|
+
supportsPackageScriptSetupOverrides: true
|
|
3982
|
+
}
|
|
3983
|
+
})
|
|
3984
|
+
});
|
|
3985
|
+
}
|
|
3986
|
+
if (url.includes('/api/taskforce/auth/login') && String(init?.method || '').toUpperCase() === 'POST') {
|
|
3987
|
+
authenticated = true;
|
|
3988
|
+
return Promise.resolve({
|
|
3989
|
+
ok: true,
|
|
3990
|
+
json: () => Promise.resolve({
|
|
3991
|
+
success: true,
|
|
3992
|
+
workspaceSetupRequired: true
|
|
3993
|
+
})
|
|
3994
|
+
});
|
|
3995
|
+
}
|
|
3996
|
+
if (url.includes('https://cloud.example.com/api/taskforce/workspaces')) {
|
|
3997
|
+
return Promise.resolve({
|
|
3998
|
+
ok: true,
|
|
3999
|
+
json: () => Promise.resolve({
|
|
4000
|
+
success: true,
|
|
4001
|
+
workspaces: [
|
|
4002
|
+
{
|
|
4003
|
+
id: 'workspace-cloud-123',
|
|
4004
|
+
name: 'Cloud Project 123',
|
|
4005
|
+
description: 'Cloud desc'
|
|
4006
|
+
}
|
|
4007
|
+
]
|
|
4008
|
+
})
|
|
4009
|
+
});
|
|
4010
|
+
}
|
|
4011
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true }) });
|
|
4012
|
+
});
|
|
4013
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
4014
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/setup?step=workspace&source=cloud&postAuth=select-cloud-workspace'], children: _jsx(TaskforceCore, { config: { cloudAuthBaseUrl: 'https://cloud.example.com' } }) }));
|
|
4015
|
+
await waitFor(() => {
|
|
4016
|
+
expect(screen.getByRole('button', { name: 'Sign In' })).toBeInTheDocument();
|
|
4017
|
+
});
|
|
4018
|
+
await user.click(screen.getByRole('button', { name: 'Sign In' }));
|
|
4019
|
+
await waitFor(() => {
|
|
4020
|
+
expect(screen.getByTestId('auth-email-input')).toBeInTheDocument();
|
|
4021
|
+
});
|
|
4022
|
+
await user.type(screen.getByTestId('auth-email-input'), 'agent@example.com');
|
|
4023
|
+
await user.type(screen.getByTestId('auth-password-input'), 'password123');
|
|
4024
|
+
await user.click(screen.getByTestId('auth-submit-button'));
|
|
4025
|
+
await waitFor(() => {
|
|
4026
|
+
expect(screen.getByRole('option', { name: /Cloud Project 123/ })).toBeInTheDocument();
|
|
4027
|
+
});
|
|
4028
|
+
await waitFor(() => {
|
|
4029
|
+
expect(screen.queryByRole('button', { name: 'Register' })).not.toBeInTheDocument();
|
|
4030
|
+
expect(screen.getByRole('button', { name: 'Connect Workspace' })).toBeEnabled();
|
|
4031
|
+
});
|
|
4032
|
+
});
|
|
4033
|
+
it('loads workspace setup after cloud sign-in when the user has no workspaces', async () => {
|
|
4034
|
+
const user = userEvent.setup();
|
|
4035
|
+
let authenticated = false;
|
|
4036
|
+
const fetchMock = vi.fn((input, init) => {
|
|
4037
|
+
const url = String(input);
|
|
4038
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
4039
|
+
return jsonResponse({
|
|
4040
|
+
success: true,
|
|
4041
|
+
config: {
|
|
4042
|
+
baseUrl: 'https://app.taskforce.example',
|
|
4043
|
+
apiBaseUrl: 'https://app.taskforce.example/api',
|
|
4044
|
+
cloudAuthBaseUrl: 'https://app.taskforce.example',
|
|
4045
|
+
runtimeMode: 'cloud',
|
|
4046
|
+
authSource: 'cloud',
|
|
4047
|
+
workspaceMode: 'multi-cloud',
|
|
4048
|
+
workspaceSwitchingEnabled: true,
|
|
4049
|
+
cloudAuthViaLocalProxy: false
|
|
4050
|
+
}
|
|
4051
|
+
});
|
|
4052
|
+
}
|
|
4053
|
+
if (url.includes('/api/taskforce/auth/session')) {
|
|
4054
|
+
return jsonResponse({
|
|
4055
|
+
authenticated,
|
|
4056
|
+
runtimeMode: 'cloud',
|
|
4057
|
+
authRequiredForApi: true,
|
|
4058
|
+
userId: authenticated ? 'user-cloud-setup' : 'anonymous',
|
|
4059
|
+
workspaceId: 'default'
|
|
4060
|
+
});
|
|
4061
|
+
}
|
|
4062
|
+
if (url.includes('/api/taskforce/auth/login')) {
|
|
4063
|
+
authenticated = true;
|
|
4064
|
+
return jsonResponse({ success: true });
|
|
4065
|
+
}
|
|
4066
|
+
if (url.includes('/api/taskforce/workspaces')) {
|
|
4067
|
+
return jsonResponse({
|
|
4068
|
+
success: true,
|
|
4069
|
+
workspaces: [],
|
|
4070
|
+
currentWorkspaceId: 'default'
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
4073
|
+
if (url.includes('/api/taskforce/account/access-status')) {
|
|
4074
|
+
return jsonResponse({
|
|
4075
|
+
gate: 'ok',
|
|
4076
|
+
hasEntitlement: true,
|
|
4077
|
+
canAccessApp: true,
|
|
4078
|
+
canAccessPlans: true,
|
|
4079
|
+
message: null
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
4082
|
+
if (url.includes('/api/taskforce/config')) {
|
|
4083
|
+
return jsonResponse({
|
|
4084
|
+
...mockConfig,
|
|
4085
|
+
runtimeMode: 'cloud',
|
|
4086
|
+
authRequiredForApi: true,
|
|
4087
|
+
userId: authenticated ? 'user-cloud-setup' : 'anonymous',
|
|
4088
|
+
setupState: {
|
|
4089
|
+
globalSetupState: 'present',
|
|
4090
|
+
workspaceSetupState: authenticated ? 'missing' : 'present',
|
|
4091
|
+
runtimeMode: 'cloud',
|
|
4092
|
+
workspaceId: 'default',
|
|
4093
|
+
mode: 'core',
|
|
4094
|
+
forceSetup: false,
|
|
4095
|
+
forceGlobalSetup: false,
|
|
4096
|
+
forceWorkspaceSetup: false,
|
|
4097
|
+
workspace: null,
|
|
4098
|
+
suggestedWorkspaceName: ''
|
|
4099
|
+
},
|
|
4100
|
+
runtimeCapabilities: {
|
|
4101
|
+
runtimeMode: 'cloud',
|
|
4102
|
+
supportsCloudAuth: true,
|
|
4103
|
+
supportsAdminManagedSetupOverrides: true,
|
|
4104
|
+
supportsPackageScriptSetupOverrides: false
|
|
4105
|
+
}
|
|
4106
|
+
});
|
|
4107
|
+
}
|
|
4108
|
+
if (url.includes('/api/taskforce/ui-state?key=app')) {
|
|
4109
|
+
return jsonResponse({ success: true, state: {} });
|
|
4110
|
+
}
|
|
4111
|
+
if (url.includes('/api/taskforce/ui-state?key=workspace-sync')) {
|
|
4112
|
+
return jsonResponse({
|
|
4113
|
+
success: true,
|
|
4114
|
+
state: {
|
|
4115
|
+
version: 2,
|
|
4116
|
+
enabled: false,
|
|
4117
|
+
phase: 'idle',
|
|
4118
|
+
pullCursor: null,
|
|
4119
|
+
lastPullAt: null,
|
|
4120
|
+
lastPushAt: null,
|
|
4121
|
+
lastSyncedAt: null
|
|
4122
|
+
}
|
|
4123
|
+
});
|
|
4124
|
+
}
|
|
4125
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
4126
|
+
return jsonResponse({ templates: [] });
|
|
4127
|
+
}
|
|
4128
|
+
return jsonResponse({});
|
|
4129
|
+
});
|
|
4130
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
4131
|
+
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=login&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
4132
|
+
await user.type(await screen.findByPlaceholderText('Email'), 'setup@example.com');
|
|
4133
|
+
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
4134
|
+
await user.click(screen.getByRole('button', { name: 'Sign In' }));
|
|
4135
|
+
await waitFor(() => {
|
|
4136
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
4137
|
+
});
|
|
4138
|
+
expect(screen.queryByText('Resolving workspace access...')).not.toBeInTheDocument();
|
|
4139
|
+
expect(fetchMock.mock.calls.some((call) => String(call[0]).includes('/api/taskforce/config'))).toBe(true);
|
|
3115
4140
|
});
|
|
3116
4141
|
it('enables workspace cloud sync from local setup when signed in and checkbox is selected', async () => {
|
|
3117
4142
|
const user = userEvent.setup();
|
|
@@ -3183,7 +4208,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
3183
4208
|
vi.stubGlobal('fetch', fetchMock);
|
|
3184
4209
|
render(_jsx(MemoryRouter, { initialEntries: ['/setup?step=workspace'], children: _jsx(TaskforceCore, { config: { cloudAuthBaseUrl: 'https://cloud.example.com' } }) }));
|
|
3185
4210
|
await waitFor(() => {
|
|
3186
|
-
expect(screen.getByText('
|
|
4211
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
3187
4212
|
});
|
|
3188
4213
|
await waitFor(() => {
|
|
3189
4214
|
expect(screen.getByRole('checkbox', { name: /sync to cloud after setup/i })).toBeInTheDocument();
|
|
@@ -3621,7 +4646,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
3621
4646
|
vi.stubGlobal('fetch', fetchMock);
|
|
3622
4647
|
const initialRender = render(_jsx(MemoryRouter, { initialEntries: ['/setup?step=workspace'], children: _jsx(TaskforceCore, { config: { cloudAuthBaseUrl: 'https://cloud.example.com' } }) }));
|
|
3623
4648
|
await waitFor(() => {
|
|
3624
|
-
expect(screen.getByText('
|
|
4649
|
+
expect(screen.getByText('Configure your new workspace.')).toBeInTheDocument();
|
|
3625
4650
|
});
|
|
3626
4651
|
await waitFor(() => {
|
|
3627
4652
|
expect(screen.getByRole('checkbox', { name: /sync to cloud after setup/i })).toBeInTheDocument();
|
|
@@ -3637,7 +4662,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
3637
4662
|
initialRender.unmount();
|
|
3638
4663
|
render(_jsx(MemoryRouter, { initialEntries: ['/'], children: _jsx(TaskforceCore, { config: { cloudAuthBaseUrl: 'https://cloud.example.com' } }) }));
|
|
3639
4664
|
await waitFor(() => {
|
|
3640
|
-
expect(screen.queryByText('
|
|
4665
|
+
expect(screen.queryByText('Configure your new workspace.')).not.toBeInTheDocument();
|
|
3641
4666
|
expect(screen.getByRole('button', { name: /add task/i })).toBeInTheDocument();
|
|
3642
4667
|
});
|
|
3643
4668
|
expect(cloudWorkspaceCreated).toBe(false);
|
|
@@ -3878,18 +4903,31 @@ describe('TaskforceCore Integration', () => {
|
|
|
3878
4903
|
vi.stubGlobal('fetch', fetchMock);
|
|
3879
4904
|
render(_jsx(TaskforceCore, { config: { cloudAuthBaseUrl: 'https://cloud.example.com' } }));
|
|
3880
4905
|
await waitFor(() => {
|
|
3881
|
-
expect(screen.getByTitle(
|
|
4906
|
+
expect(screen.getByTitle(/Open tasks/)).toHaveTextContent(/1/);
|
|
3882
4907
|
expect(screen.getByTitle(/Account/i)).toBeInTheDocument();
|
|
3883
4908
|
});
|
|
3884
4909
|
await user.click(screen.getByTitle(/Account/i));
|
|
3885
4910
|
await user.click(await screen.findByRole('menuitem', { name: 'Sign Out' }));
|
|
3886
4911
|
await waitFor(() => {
|
|
3887
4912
|
expect(fetchMock.mock.calls.some((call) => String(call[0]).includes('https://cloud.example.com/api/taskforce/auth/logout'))).toBe(true);
|
|
3888
|
-
expect(screen.getByTitle(
|
|
4913
|
+
expect(screen.getByTitle(/Open tasks/)).toHaveTextContent(/1/);
|
|
3889
4914
|
});
|
|
3890
4915
|
await user.click(screen.getByTitle(/Account/i));
|
|
3891
4916
|
expect(await screen.findByText('Sign In / Register')).toBeInTheDocument();
|
|
3892
4917
|
});
|
|
4918
|
+
it('uses an explicit cloud sign-out redirect and broader cloud logout reset path', () => {
|
|
4919
|
+
const standaloneLayoutSource = fs.readFileSync(path.join(__dirname, 'components/views/StandaloneLayout.tsx'), 'utf8');
|
|
4920
|
+
const useTaskforceSource = fs.readFileSync(path.join(__dirname, 'hooks/useTaskforce.ts'), 'utf8');
|
|
4921
|
+
expect(standaloneLayoutSource).toContain("await logout();");
|
|
4922
|
+
expect(standaloneLayoutSource).toContain("if (runtimeMode === 'cloud') {");
|
|
4923
|
+
expect(standaloneLayoutSource).toContain("navigate('/login', { replace: true });");
|
|
4924
|
+
expect(useTaskforceSource).toContain('const resetCloudLogoutClientState = useCallback(() => {');
|
|
4925
|
+
expect(useTaskforceSource).toContain('setArchivedTasks([]);');
|
|
4926
|
+
expect(useTaskforceSource).toContain('setDeletedTasks([]);');
|
|
4927
|
+
expect(useTaskforceSource).toContain('setAssigneeOptions(createDefaultAssigneeOptions());');
|
|
4928
|
+
expect(useTaskforceSource).toContain('setUiStateReady(false);');
|
|
4929
|
+
expect(useTaskforceSource).toContain('resetCloudLogoutClientState();');
|
|
4930
|
+
});
|
|
3893
4931
|
it('does not pull from cloud when workspace sync toggle is disabled', async () => {
|
|
3894
4932
|
const fetchMock = vi.fn((url) => {
|
|
3895
4933
|
if (url.includes('/api/taskforce/auth/session')) {
|
|
@@ -4758,9 +5796,10 @@ describe('TaskforceCore Integration', () => {
|
|
|
4758
5796
|
it('keeps local workspace context authoritative over cloud auth session workspace id', async () => {
|
|
4759
5797
|
const useTaskforcePath = path.join(process.cwd(), 'src/hooks/useTaskforce.ts');
|
|
4760
5798
|
const useTaskforceSource = fs.readFileSync(useTaskforcePath, 'utf8');
|
|
4761
|
-
expect(useTaskforceSource).toContain("const
|
|
4762
|
-
expect(useTaskforceSource).toContain('Local runtime
|
|
4763
|
-
expect(useTaskforceSource).toContain('
|
|
5799
|
+
expect(useTaskforceSource).toContain("const authOnlyCloudMode = !isCloudHost && Boolean(normalizedCloudAuthBaseUrl) && !normalizedApiBaseUrl;");
|
|
5800
|
+
expect(useTaskforceSource).toContain('Local runtime workspace identity must be sourced from local DB (/api/taskforce/config),');
|
|
5801
|
+
expect(useTaskforceSource).toContain('not from persisted browser context or cloud session payloads.');
|
|
5802
|
+
expect(useTaskforceSource).toContain("if (initialRuntimeMode === 'local') return 'default';");
|
|
4764
5803
|
});
|
|
4765
5804
|
it.skip('blocks local cloud-sync pull/apply when workspaceId remains default', async () => {
|
|
4766
5805
|
const fetchMock = vi.fn((url, init) => {
|