test(desktop): import settings panels once so first-test imports can't time out
Several settings suites imported their component inside each test body.
The first in-test `await import` paid the whole jsdom env init + module
transform + import graph inside test one's 15s budget, which times out
under CI runner load; the late-finishing import could also leak its first
render into the next test ("multiple Honcho settings buttons").
Load each panel once at module scope (same pattern as
profile-scope.test.tsx / config-settings.test.tsx), where the cost is
outside every test timeout. No behavior change: hoisted vi.mock factories
still intercept these imports identically.
Seen on #122304, #122269, #122368, #121523.
This commit is contained in:
@@ -37,6 +37,12 @@ vi.mock('@/store/notifications', () => ({
|
||||
notifyError: (...args: unknown[]) => notifyError(...args)
|
||||
}))
|
||||
|
||||
// Load once at module scope so no test's 15s budget pays the heavy transform
|
||||
// + import (the first-test timeout flake under CI load).
|
||||
const { CustomEndpointsSettings } = await import('./custom-endpoints-settings')
|
||||
const { $activeGatewayProfile, $profiles } = await import('@/store/profile')
|
||||
const { $settingsScopeOverride } = await import('@/store/settings-scope')
|
||||
|
||||
const emptyResponse: CustomEndpointsResponse = {
|
||||
current: { base_url: '', model: '', provider: '' },
|
||||
endpoints: []
|
||||
@@ -60,18 +66,15 @@ const savedResponse: CustomEndpointsResponse = {
|
||||
ok: true
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
const { $activeGatewayProfile, $profiles } = await import('@/store/profile')
|
||||
const { $settingsScopeOverride } = await import('@/store/settings-scope')
|
||||
beforeEach(() => {
|
||||
$activeGatewayProfile.set('default')
|
||||
$settingsScopeOverride.set(null)
|
||||
$profiles.set([])
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
vi.clearAllMocks()
|
||||
const { $settingsScopeOverride } = await import('@/store/settings-scope')
|
||||
$settingsScopeOverride.set(null)
|
||||
})
|
||||
|
||||
@@ -79,7 +82,6 @@ describe('CustomEndpointsSettings', () => {
|
||||
it('localizes endpoint editing on language changes without changing transport or draft identifiers', async () => {
|
||||
getCustomEndpoints.mockResolvedValue(emptyResponse)
|
||||
saveCustomEndpoint.mockResolvedValue(savedResponse)
|
||||
const { CustomEndpointsSettings } = await import('./custom-endpoints-settings')
|
||||
let language!: I18nContextValue
|
||||
|
||||
function Surface() {
|
||||
@@ -129,7 +131,6 @@ describe('CustomEndpointsSettings', () => {
|
||||
transport_checked: 'codex_responses'
|
||||
})
|
||||
saveCustomEndpoint.mockResolvedValue(savedResponse)
|
||||
const { CustomEndpointsSettings } = await import('./custom-endpoints-settings')
|
||||
|
||||
render(<CustomEndpointsSettings />)
|
||||
|
||||
@@ -164,8 +165,6 @@ describe('CustomEndpointsSettings', () => {
|
||||
})
|
||||
|
||||
it('loads and saves endpoints for the Settings Applies-to profile, not only the active bot', async () => {
|
||||
const { $activeGatewayProfile, $profiles } = await import('@/store/profile')
|
||||
const { $settingsScopeOverride } = await import('@/store/settings-scope')
|
||||
$activeGatewayProfile.set('carousel-director')
|
||||
$settingsScopeOverride.set('content-studio')
|
||||
$profiles.set(
|
||||
@@ -181,7 +180,6 @@ describe('CustomEndpointsSettings', () => {
|
||||
)
|
||||
getCustomEndpoints.mockResolvedValue(emptyResponse)
|
||||
saveCustomEndpoint.mockResolvedValue(savedResponse)
|
||||
const { CustomEndpointsSettings } = await import('./custom-endpoints-settings')
|
||||
|
||||
render(<CustomEndpointsSettings />)
|
||||
|
||||
@@ -206,7 +204,6 @@ describe('CustomEndpointsSettings', () => {
|
||||
...savedResponse,
|
||||
endpoints: [{ ...savedResponse.endpoints[0], api_mode: 'anthropic_messages' }]
|
||||
})
|
||||
const { CustomEndpointsSettings } = await import('./custom-endpoints-settings')
|
||||
|
||||
render(<CustomEndpointsSettings />)
|
||||
|
||||
@@ -220,7 +217,6 @@ describe('CustomEndpointsSettings', () => {
|
||||
getCustomEndpoints.mockResolvedValue(emptyResponse)
|
||||
const onConfigSaved = vi.fn()
|
||||
const onMainModelChanged = vi.fn()
|
||||
const { CustomEndpointsSettings } = await import('./custom-endpoints-settings')
|
||||
|
||||
const view = render(
|
||||
<CustomEndpointsSettings onConfigSaved={onConfigSaved} onMainModelChanged={onMainModelChanged} />
|
||||
@@ -255,7 +251,6 @@ describe('CustomEndpointsSettings', () => {
|
||||
models: ['model-a'],
|
||||
resolved_base_url: 'http://h.test/v1'
|
||||
})
|
||||
const { CustomEndpointsSettings } = await import('./custom-endpoints-settings')
|
||||
render(<CustomEndpointsSettings onConfigSaved={vi.fn()} onMainModelChanged={vi.fn()} />)
|
||||
|
||||
await screen.findByText('No custom endpoints')
|
||||
|
||||
@@ -15,6 +15,10 @@ vi.mock('@/hermes', () => ({
|
||||
getGlobalModelOptions: () => getGlobalModelOptions()
|
||||
}))
|
||||
|
||||
// Load once at module scope so no test's 15s budget pays the heavy transform
|
||||
// + import (the first-test timeout flake under CI load).
|
||||
const { FallbackModelsField } = await import('./fallback-models-field')
|
||||
|
||||
beforeEach(() => {
|
||||
getGlobalModelOptions.mockResolvedValue({
|
||||
providers: [
|
||||
@@ -30,8 +34,7 @@ afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
async function renderField(value: unknown, onChange = vi.fn()) {
|
||||
const { FallbackModelsField } = await import('./fallback-models-field')
|
||||
function renderField(value: unknown, onChange = vi.fn()) {
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||||
|
||||
render(
|
||||
@@ -43,8 +46,7 @@ async function renderField(value: unknown, onChange = vi.fn()) {
|
||||
return onChange
|
||||
}
|
||||
|
||||
async function renderFieldWithRerender(value: unknown, onChange = vi.fn()) {
|
||||
const { FallbackModelsField } = await import('./fallback-models-field')
|
||||
function renderFieldWithRerender(value: unknown, onChange = vi.fn()) {
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||||
|
||||
const view = render(
|
||||
|
||||
@@ -18,6 +18,11 @@ vi.mock('@/hermes', () => ({
|
||||
setEnvVar: vi.fn()
|
||||
}))
|
||||
|
||||
// Load once at module scope so no test's 15s budget pays the heavy transform
|
||||
// + import (the first-test timeout flake under CI load).
|
||||
const { KeysSettings } = await import('./keys-settings')
|
||||
const { $settingsScopeOverride } = await import('@/store/settings-scope')
|
||||
|
||||
beforeEach(() => {
|
||||
getEnvVars.mockResolvedValue({})
|
||||
Object.defineProperty(Element.prototype, 'scrollIntoView', {
|
||||
@@ -32,8 +37,6 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
async function renderKeysSettings(view: 'settings' | 'tools', route = '/settings') {
|
||||
const { KeysSettings } = await import('./keys-settings')
|
||||
|
||||
await act(async () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={[route]}>
|
||||
@@ -111,8 +114,6 @@ describe('KeysSettings', () => {
|
||||
FIRECRAWL_API_KEY: envVar('tool', { description: 'Crawl and extract websites.' })
|
||||
})
|
||||
|
||||
const { KeysSettings } = await import('./keys-settings')
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/settings?tab=keys']}>
|
||||
<KeysSettings view="tools" />
|
||||
@@ -135,16 +136,12 @@ describe('KeysSettings', () => {
|
||||
// changes, but the in-flight edit map was not reset with it. A value typed
|
||||
// while targeting profile-b survived the switch to profile-c, where the
|
||||
// still-live Save would persist it into the WRONG profile.
|
||||
const { $settingsScopeOverride } = await import('@/store/settings-scope')
|
||||
|
||||
$settingsScopeOverride.set('profile-b')
|
||||
getEnvVars.mockResolvedValue({
|
||||
WIDGET_API_KEY: envVar('tool', { description: 'Widget key.', is_set: true, redacted_value: '••••••' })
|
||||
})
|
||||
|
||||
try {
|
||||
const { KeysSettings } = await import('./keys-settings')
|
||||
|
||||
const { container } = render(
|
||||
<MemoryRouter initialEntries={['/settings']}>
|
||||
<KeysSettings view="tools" />
|
||||
|
||||
@@ -20,6 +20,10 @@ vi.mock('@/store/notifications', () => ({
|
||||
notifyError: vi.fn()
|
||||
}))
|
||||
|
||||
// Load once at module scope so no test's 15s budget pays the heavy transform
|
||||
// + import (the first-test timeout flake under CI load).
|
||||
const { ProviderConfigModal } = await import('./provider-config-modal')
|
||||
|
||||
function field(
|
||||
overrides: Partial<MemoryProviderField> & Pick<MemoryProviderField, 'key' | 'kind'>
|
||||
): MemoryProviderField {
|
||||
@@ -65,8 +69,7 @@ afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
async function renderModal(open = true) {
|
||||
const { ProviderConfigModal } = await import('./provider-config-modal')
|
||||
function renderModal(open = true) {
|
||||
const onOpenChange = vi.fn()
|
||||
const onSaved = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
|
||||
@@ -22,6 +22,13 @@ vi.mock('@/store/notifications', () => ({
|
||||
notifyError: vi.fn()
|
||||
}))
|
||||
|
||||
// Load the panel once at module scope, outside every test timeout. The first
|
||||
// in-test `await import` paid the whole transform + import inside test one's
|
||||
// 15s budget — the recurring CI timeout — and the late-finishing import could
|
||||
// leak its first render into the next test ("multiple Honcho settings
|
||||
// buttons").
|
||||
const { ProviderConfigPanel } = await import('./provider-config-panel')
|
||||
|
||||
function honchoSchema(): MemoryProviderConfig {
|
||||
return {
|
||||
name: 'honcho',
|
||||
@@ -108,9 +115,7 @@ afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
async function renderPanel(provider = 'honcho') {
|
||||
const { ProviderConfigPanel } = await import('./provider-config-panel')
|
||||
|
||||
function renderPanel(provider = 'honcho') {
|
||||
return render(<ProviderConfigPanel provider={provider} />)
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,12 @@ vi.mock('@/store/onboarding', () => ({
|
||||
startManualLocalEndpoint: (reason: null | string) => startManualLocalEndpoint(reason)
|
||||
}))
|
||||
|
||||
// Load once at module scope so no test's 15s budget pays the heavy transform
|
||||
// + import (the first-test timeout flake under CI load).
|
||||
const { ProvidersSettings } = await import('./providers-settings')
|
||||
const { $settingsScopeOverride } = await import('@/store/settings-scope')
|
||||
const { $activeGatewayProfile, $profiles } = await import('@/store/profile')
|
||||
|
||||
function provider(id: string, loggedIn: boolean, patch: Partial<OAuthProvider> = {}): OAuthProvider {
|
||||
return {
|
||||
cli_command: `hermes auth add ${id}`,
|
||||
@@ -90,7 +96,6 @@ afterEach(() => {
|
||||
// Removal goes through confirm() from @/store/confirm, so the host has to be
|
||||
// mounted for the prompt to render — same as in the real app shell.
|
||||
async function renderProvidersSettings() {
|
||||
const { ProvidersSettings } = await import('./providers-settings')
|
||||
let result: ReturnType<typeof render>
|
||||
await act(async () => {
|
||||
result = render(
|
||||
@@ -106,8 +111,6 @@ async function renderProvidersSettings() {
|
||||
|
||||
describe('ProvidersSettings', () => {
|
||||
it('reads and saves API keys for the shared Settings target and reloads when it changes', async () => {
|
||||
const { $settingsScopeOverride } = await import('@/store/settings-scope')
|
||||
const { $activeGatewayProfile, $profiles } = await import('@/store/profile')
|
||||
$activeGatewayProfile.set('profile-a')
|
||||
$settingsScopeOverride.set('profile-b')
|
||||
$profiles.set(
|
||||
@@ -122,8 +125,6 @@ describe('ProvidersSettings', () => {
|
||||
}))
|
||||
)
|
||||
getEnvVars.mockResolvedValue({ WIDGET_API_KEY: keyVar({ provider: 'widget', provider_label: 'Widget' }) })
|
||||
const { ProvidersSettings } = await import('./providers-settings')
|
||||
|
||||
try {
|
||||
const { container } = render(<ProvidersSettings onClose={vi.fn()} onViewChange={vi.fn()} view="keys" />)
|
||||
await screen.findByText('Widget')
|
||||
@@ -147,7 +148,6 @@ describe('ProvidersSettings', () => {
|
||||
})
|
||||
|
||||
it('uses the settings target for account reads, removal and sign-in', async () => {
|
||||
const { $settingsScopeOverride } = await import('@/store/settings-scope')
|
||||
$settingsScopeOverride.set('beta')
|
||||
|
||||
try {
|
||||
@@ -231,7 +231,6 @@ describe('ProvidersSettings', () => {
|
||||
})
|
||||
listOAuthProviders.mockResolvedValue({ providers: [] })
|
||||
|
||||
const { ProvidersSettings } = await import('./providers-settings')
|
||||
await act(async () => {
|
||||
render(<ProvidersSettings onClose={vi.fn()} onViewChange={vi.fn()} view="keys" />)
|
||||
})
|
||||
@@ -250,7 +249,6 @@ describe('ProvidersSettings', () => {
|
||||
})
|
||||
listOAuthProviders.mockResolvedValue({ providers: [] })
|
||||
|
||||
const { ProvidersSettings } = await import('./providers-settings')
|
||||
render(<ProvidersSettings onClose={vi.fn()} onViewChange={vi.fn()} view="keys" />)
|
||||
|
||||
// Equal priority → alphabetical tiebreak: Acme, Middle, Zebra.
|
||||
@@ -283,7 +281,6 @@ describe('ProvidersSettings', () => {
|
||||
getEnvVars.mockResolvedValue({})
|
||||
listOAuthProviders.mockResolvedValue({ providers: [] })
|
||||
|
||||
const { ProvidersSettings } = await import('./providers-settings')
|
||||
render(<ProvidersSettings onClose={vi.fn()} onViewChange={vi.fn()} view="keys" />)
|
||||
|
||||
const row = await screen.findByText('Local / custom endpoint')
|
||||
|
||||
@@ -22,6 +22,10 @@ vi.mock('@/store/notifications', () => ({
|
||||
notifyError: vi.fn()
|
||||
}))
|
||||
|
||||
// Load once at module scope so no test's 15s budget pays the heavy transform
|
||||
// + import (the first-test timeout flake under CI load).
|
||||
const { TerminalBackendPanel } = await import('./terminal-backend-panel')
|
||||
|
||||
function backends(overrides: Partial<TerminalBackendsResponse> = {}): TerminalBackendsResponse {
|
||||
return {
|
||||
active: 'local',
|
||||
@@ -68,7 +72,6 @@ afterEach(() => {
|
||||
|
||||
describe('TerminalBackendPanel', () => {
|
||||
it('marks the active backend as pressed', async () => {
|
||||
const { TerminalBackendPanel } = await import('./terminal-backend-panel')
|
||||
render(<TerminalBackendPanel onConfiguredChange={vi.fn()} />)
|
||||
|
||||
const local = await screen.findByRole('button', { name: /Local/ })
|
||||
@@ -77,7 +80,6 @@ describe('TerminalBackendPanel', () => {
|
||||
|
||||
it('selects a backend when clicked and reports the change', async () => {
|
||||
const onConfiguredChange = vi.fn()
|
||||
const { TerminalBackendPanel } = await import('./terminal-backend-panel')
|
||||
render(<TerminalBackendPanel onConfiguredChange={onConfiguredChange} />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: /SSH/ }))
|
||||
@@ -93,7 +95,6 @@ describe('TerminalBackendPanel', () => {
|
||||
const confirmGate = deferred<boolean>()
|
||||
confirmMock.mockReturnValue(confirmGate.promise)
|
||||
selectTerminalBackend.mockResolvedValue({ ok: true, backend: 'docker' })
|
||||
const { TerminalBackendPanel } = await import('./terminal-backend-panel')
|
||||
render(<TerminalBackendPanel onConfiguredChange={vi.fn()} />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: /Docker/ }))
|
||||
@@ -118,7 +119,6 @@ describe('TerminalBackendPanel', () => {
|
||||
it('does not select a needs_setup backend when the confirm dialog is declined', async () => {
|
||||
const confirmGate = deferred<boolean>()
|
||||
confirmMock.mockReturnValue(confirmGate.promise)
|
||||
const { TerminalBackendPanel } = await import('./terminal-backend-panel')
|
||||
render(<TerminalBackendPanel onConfiguredChange={vi.fn()} />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: /Docker/ }))
|
||||
@@ -137,7 +137,6 @@ describe('TerminalBackendPanel', () => {
|
||||
})
|
||||
|
||||
it('does not re-select the already active backend', async () => {
|
||||
const { TerminalBackendPanel } = await import('./terminal-backend-panel')
|
||||
render(<TerminalBackendPanel onConfiguredChange={vi.fn()} />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: /Local/ }))
|
||||
|
||||
Reference in New Issue
Block a user