diff --git a/frontend/src/connectors/useConnectorLauncher.test.tsx b/frontend/src/connectors/useConnectorLauncher.test.tsx new file mode 100644 index 00000000..af4eee49 --- /dev/null +++ b/frontend/src/connectors/useConnectorLauncher.test.tsx @@ -0,0 +1,92 @@ +import { configureStore } from '@reduxjs/toolkit'; +import { act } from 'react'; +import { createRoot, type Root } from 'react-dom/client'; +import { Provider } from 'react-redux'; +import { MemoryRouter } from 'react-router-dom'; + +// The wizard is tested on its own; here only what it is opened with matters. +vi.mock('./ConnectWizard', () => ({ + default: ({ + mode, + connectionId, + }: { + mode: string; + connectionId?: string; + }) =>
{`${mode}:${connectionId ?? ''}`}
, +})); +vi.mock('../modals/MCPServerModal', () => ({ default: () => null })); + +import connectorsReducer from './connectorsSlice'; +import type { ConnectorDefinition } from './types'; +import useConnectorLauncher, { + type LaunchOptions, +} from './useConnectorLauncher'; + +Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); + +const LINEAR = { + key: 'mcp:linear', + name: 'Linear', + auth_kind: 'mcp_oauth', + mcp_url: 'https://mcp.linear.app/mcp', + sync_ingestor: 'linear', +} as unknown as ConnectorDefinition; + +let launchRef: ((c: ConnectorDefinition, o?: LaunchOptions) => void) | null = + null; + +function Harness() { + const { launch, modals } = useConnectorLauncher(); + launchRef = launch; + return <>{modals}; +} + +describe('useConnectorLauncher', () => { + let container: HTMLDivElement; + let root: Root; + + beforeEach(async () => { + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); + const store = configureStore({ + reducer: { + connectors: connectorsReducer, + preference: (state = { token: null }) => state, + }, + }); + await act(async () => { + root.render( + + + + + , + ); + }); + }); + + afterEach(async () => { + await act(async () => root.unmount()); + container.remove(); + }); + + const wizard = () => + container.querySelector('[data-testid="wizard"]')?.textContent; + + it('opens an MCP preset that syncs straight at picking what to sync', async () => { + await act(async () => + launchRef!(LINEAR, { mode: 'sync', connectionId: 'c1' }), + ); + expect(wizard()).toBe('sync:c1'); + }); + + it('opens an MCP preset at signing in to connect or reconnect', async () => { + await act(async () => launchRef!(LINEAR)); + expect(wizard()).toBe('connect:'); + await act(async () => + launchRef!(LINEAR, { mode: 'reconnect', connectionId: 'c1' }), + ); + expect(wizard()).toBe('reconnect:c1'); + }); +}); diff --git a/frontend/src/connectors/useConnectorLauncher.tsx b/frontend/src/connectors/useConnectorLauncher.tsx index b4b4c9e0..2028cdf0 100644 --- a/frontend/src/connectors/useConnectorLauncher.tsx +++ b/frontend/src/connectors/useConnectorLauncher.tsx @@ -62,7 +62,8 @@ export default function useConnectorLauncher({ setActive({ kind: 'wizard', connector, - mode: options.mode === 'reconnect' ? 'reconnect' : 'connect', + // Sync more from an account skips signing in, like any connector. + mode: options.mode ?? 'connect', connectionId: options.connectionId, mcpToolId: typeof options.mcpServer?.id === 'string'