diff --git a/src/components/channels/ChannelConfigModal.tsx b/src/components/channels/ChannelConfigModal.tsx index 8df158c1e..634b0f7ae 100644 --- a/src/components/channels/ChannelConfigModal.tsx +++ b/src/components/channels/ChannelConfigModal.tsx @@ -104,6 +104,10 @@ export function ChannelConfigModal({ : showAccountIdEditor ? accountIdInput.trim() : (accountId ?? (agentId ? (agentId === 'main' ? 'default' : agentId) : undefined)); + const shouldLoadExistingConfig = Boolean( + selectedType && allowExistingConfig && configuredTypes.includes(selectedType) + ); + const accountIdForConfigLoad = shouldLoadExistingConfig ? resolvedAccountId : undefined; useEffect(() => { setSelectedType(initialSelectedType); @@ -124,7 +128,6 @@ export function ChannelConfigModal({ return; } - const shouldLoadExistingConfig = allowExistingConfig && configuredTypes.includes(selectedType); if (!shouldLoadExistingConfig) { setConfigValues({}); setIsExistingConfig(false); @@ -147,7 +150,7 @@ export function ChannelConfigModal({ (async () => { try { - const accountParam = resolvedAccountId ? `?accountId=${encodeURIComponent(resolvedAccountId)}` : ''; + const accountParam = accountIdForConfigLoad ? `?accountId=${encodeURIComponent(accountIdForConfigLoad)}` : ''; const result = await hostApiFetch<{ success: boolean; values?: Record }>( `/api/channels/config/${encodeURIComponent(selectedType)}${accountParam}` ); @@ -173,7 +176,7 @@ export function ChannelConfigModal({ return () => { cancelled = true; }; - }, [allowExistingConfig, configuredTypes, initialConfigValues, resolvedAccountId, selectedType, showChannelName]); + }, [accountIdForConfigLoad, initialConfigValues, selectedType, shouldLoadExistingConfig, showChannelName]); useEffect(() => { if (selectedType && !loadingConfig && showChannelName && firstInputRef.current) { diff --git a/tests/e2e/channels-account-id-persistence.spec.ts b/tests/e2e/channels-account-id-persistence.spec.ts new file mode 100644 index 000000000..5a1a201fd --- /dev/null +++ b/tests/e2e/channels-account-id-persistence.spec.ts @@ -0,0 +1,82 @@ +import { completeSetup, expect, installIpcMocks, test } from './fixtures/electron'; + +function stableStringify(value: unknown): string { + if (value == null || typeof value !== 'object') return JSON.stringify(value); + if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(',')}]`; + const entries = Object.entries(value as Record) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`); + return `{${entries.join(',')}}`; +} + +test.describe('Channels account editor behavior', () => { + test('keeps Feishu credentials when account ID is changed', async ({ electronApp, page }) => { + await installIpcMocks(electronApp, { + gatewayStatus: { state: 'running', port: 18789, pid: 12345 }, + hostApi: { + [stableStringify(['/api/channels/accounts', 'GET'])]: { + ok: true, + data: { + status: 200, + ok: true, + json: { + success: true, + channels: [ + { + channelType: 'feishu', + defaultAccountId: 'default', + status: 'connected', + accounts: [ + { + accountId: 'default', + name: 'Primary Account', + configured: true, + status: 'connected', + isDefault: true, + }, + ], + }, + ], + }, + }, + }, + [stableStringify(['/api/agents', 'GET'])]: { + ok: true, + data: { + status: 200, + ok: true, + json: { + success: true, + agents: [], + }, + }, + }, + }, + }); + + await completeSetup(page); + await page.getByTestId('sidebar-nav-channels').click(); + await expect(page.getByTestId('channels-page')).toBeVisible(); + + const addAccountButton = page.locator('button').filter({ + hasText: /Add Account|添加账号|アカウントを追加/, + }).first(); + await expect(addAccountButton).toBeVisible(); + await addAccountButton.click(); + + const appIdInput = page.locator('input#appId'); + const appSecretInput = page.locator('input#appSecret'); + const accountIdInput = page.locator('input#account-id'); + + await expect(appIdInput).toBeVisible(); + await expect(appSecretInput).toBeVisible(); + await expect(accountIdInput).toBeVisible(); + + await appIdInput.fill('cli_test_app'); + await appSecretInput.fill('secret_test_value'); + await accountIdInput.fill('feishu-renamed-account'); + + await expect(appIdInput).toHaveValue('cli_test_app'); + await expect(appSecretInput).toHaveValue('secret_test_value'); + }); +}); diff --git a/tests/unit/channels-page.test.tsx b/tests/unit/channels-page.test.tsx index 5c3cf5ea4..e8487a044 100644 --- a/tests/unit/channels-page.test.tsx +++ b/tests/unit/channels-page.test.tsx @@ -270,4 +270,27 @@ describe('Channels page status refresh', () => { agentsDeferred.resolve({ success: true, agents: [] }); }); }); + + it('keeps filled Feishu credentials when account ID is edited', async () => { + subscribeHostEventMock.mockImplementation(() => vi.fn()); + + render(); + + await waitFor(() => { + expect(screen.getByText('Feishu / Lark')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByRole('button', { name: 'account.add' })); + + const appIdInput = await screen.findByPlaceholderText('channels:meta.feishu.fields.appId.placeholder'); + const appSecretInput = screen.getByPlaceholderText('channels:meta.feishu.fields.appSecret.placeholder'); + const accountIdInput = screen.getByLabelText('account.customIdLabel'); + + fireEvent.change(appIdInput, { target: { value: 'cli_test_app' } }); + fireEvent.change(appSecretInput, { target: { value: 'secret_test_value' } }); + fireEvent.change(accountIdInput, { target: { value: 'feishu-renamed-account' } }); + + expect(appIdInput).toHaveValue('cli_test_app'); + expect(appSecretInput).toHaveValue('secret_test_value'); + }); });