Files
orca/tests/e2e/SmartWorkspaceSourceResultList.linear-read-error.test.tsx
T

678 lines
26 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @vitest-environment happy-dom
import {
queued,
detailTransport,
events,
mocks,
actual,
repo,
onSelect,
pickerStore,
resetPickerStore,
LinearPicker,
prepareDefaultPicker,
installRuntimeBridge,
aliasStatus,
aliasRow
} from './smart-workspace-linear-read-test-fixtures'
import { LinearIntegrationCard } from '../../src/renderer/src/components/integrations/task-trackers/linear-integration-card'
import { TooltipProvider } from '../../src/renderer/src/components/ui/tooltip'
import { getIssue } from '../../src/main/linear/linear-issue-lookups'
import React from 'react'
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { clearLinearRequestMaps } from '@/store/slices/linear/linear-slice-request-state'
import {
buildTaskSourceContextFromRepo,
getTaskSourceCacheScope
} from '../../src/shared/task-source-context'
import { linearSearchCacheKey } from '@/store/slices/linear/linear-cache'
import { RESULT_LIMIT } from '../../src/renderer/src/components/new-workspace/smart-workspace-name-field-model'
import { getLinearSourceScope } from '@/store/slices/linear/linear-slice-scope'
import { credentialDecryptionMessage } from '../../src/shared/integration-credential-errors'
import type { LinearIssue } from '../../src/shared/linear/issue-types'
import type { LinearConnectionStatus } from '../../src/shared/linear/workspace-types'
import { referenceLinearIssue } from '../../src/renderer/src/components/sidebar/workspace-reference-fixtures.test-support'
vi.mock('../../src/renderer/src/components/skills/setup/linear-agent-skill-install-cta', () => ({
LinearAgentSkillInstallCta: () => null
}))
vi.mock('../../src/renderer/src/components/linear-api-key-dialog', () => ({
LinearApiKeyDialog: () => null
}))
describe('composer Linear read errors', () => {
beforeEach(() => {
mocks.state.linearStatus = {
connected: true,
viewer: null,
selectedWorkspaceId: 'workspace-1'
}
clearLinearRequestMaps()
resetPickerStore()
const { searchLinearIssues: _search, getCachedLinearIssues: _cache, ...initial } = mocks.state
pickerStore.setState(initial)
onSelect.mockClear()
mocks.state.searchLinearIssues.mockReset()
mocks.state.getCachedLinearIssues.mockReset()
mocks.state.getCachedLinearIssues.mockReturnValue(null)
})
afterEach(() => {
cleanup()
clearLinearRequestMaps()
})
it.each([
{ cache: 'cold', attachment: false },
{ cache: 'warm', attachment: false },
{ cache: 'cold', attachment: true },
{ cache: 'warm', attachment: true }
])(
'$cache network failure is safe with attachment=$attachment and successful retry clears it',
async ({ cache, attachment }) => {
if (cache === 'warm') {
const context = buildTaskSourceContextFromRepo({
provider: 'linear',
projectId: repo.id,
repo
})!
const key = `${getTaskSourceCacheScope(context)}::${linearSearchCacheKey('workspace-1', 'launch', RESULT_LIMIT)}`
pickerStore.setState({
linearSearchCache: { [key]: { data: [referenceLinearIssue()], fetchedAt: 1 } }
})
}
mocks.state.searchLinearIssues.mockRejectedValue(new Error('private network diagnostic'))
const { rerender } = render(<LinearPicker value="launch" attachment={attachment} />)
fireEvent.click(screen.getByRole('button', { name: 'Linear tab' }))
const message = 'Unable to load Linear issues. Try again.'
await waitFor(() => expect(screen.getAllByText(message)).toHaveLength(1))
expect(screen.queryByText('Reconnect Linear')).toBeNull()
if (cache === 'warm') {
fireEvent.click(screen.getByText(/Task/))
expect(onSelect).toHaveBeenCalledTimes(1)
}
fireEvent.click(screen.getByRole('button', { name: 'Smart tab' }))
expect(screen.queryByText(message)).toBeNull()
fireEvent.click(screen.getByRole('button', { name: 'Linear tab' }))
mocks.state.searchLinearIssues.mockResolvedValue([referenceLinearIssue()])
rerender(<LinearPicker value="recovered" attachment={attachment} />)
await waitFor(() => expect(screen.getByText(/Task/)).toBeTruthy())
await waitFor(() => expect(screen.queryByText(message)).toBeNull())
}
)
it('shows the read credential error on the Linear tab only, and a successful search clears it', async () => {
mocks.state.searchLinearIssues.mockImplementation(async (query) => {
if (query === 'broken') {
throw new Error(credentialDecryptionMessage('Linear'))
}
return [referenceLinearIssue()]
})
const message = 'Reconnect Linear'
const { rerender } = render(<LinearPicker value="broken" />)
fireEvent.click(screen.getByRole('button', { name: 'Linear tab' }))
await waitFor(() =>
expect(screen.getByRole('status').textContent).toBe(
`${message}Unable to read saved Linear login. Reconnect it in Settings.`
)
)
// The status refresh after the failure re-runs the same read; the error must stay up.
mocks.state.linearStatus = {
...mocks.state.linearStatus,
credentialError: credentialDecryptionMessage('Linear')
}
rerender(<LinearPicker value="broken" />)
expect(screen.getByText(message)).toBeTruthy()
await waitFor(() => expect(mocks.state.searchLinearIssues).toHaveBeenCalled())
expect(screen.getByText(message)).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: 'Smart tab' }))
expect(screen.queryByText(message)).toBeNull()
fireEvent.click(screen.getByRole('button', { name: 'Linear tab' }))
rerender(<LinearPicker value="launch" />)
await waitFor(() => expect(screen.getByText(/Task/)).toBeTruthy())
expect(screen.queryByText(message)).toBeNull()
})
})
it.each(
['list', 'search'].flatMap((kind) =>
['success', 'error', 'queued'].map((outcome) => ({ kind, outcome }))
)
)(
'runtime refresh retires default New Workspace $kind pre-event $outcome',
async ({ kind, outcome }) => {
prepareDefaultPicker({ connected: true, viewer: null, selectedWorkspaceId: 'workspace-1' })
cleanup()
queued.enabled = false
queued.updates.length = 0
const old = Promise.withResolvers<LinearIssue[] | { items: LinearIssue[] }>()
const fresh = Promise.withResolvers<LinearIssue[] | { items: LinearIssue[] }>()
const runtime = kind === 'search' ? mocks.state.searchLinearIssues : mocks.state.list
const data = (title: string) => {
const items = [{ ...referenceLinearIssue(), title }]
return kind === 'search' ? items : { items }
}
runtime.mockReset().mockReturnValueOnce(old.promise).mockReturnValueOnce(fresh.promise)
const control = Promise.withResolvers<LinearIssue | null>()
mocks.state.detail.mockReset()
mocks.state.detail.mockImplementation((context) =>
context.kind === 'task-source' || context.environmentId === 'other'
? control.promise
: Promise.resolve(referenceLinearIssue())
)
const explicit = buildTaskSourceContextFromRepo({
provider: 'linear',
projectId: repo.id,
repo
})!
const controls = [
getLinearSourceScope(explicit),
getLinearSourceScope({ ...explicit, accountLabel: 'Other full source' }),
getLinearSourceScope({ kind: 'environment', environmentId: 'other' })
].map((scope) =>
pickerStore.getState().fetchLinearIssue('ISSUE-1', 'workspace-1', { scope }).acquire()
)
expect(mocks.state.detail).toHaveBeenCalledTimes(3)
installRuntimeBridge()
const mounted = render(<LinearPicker value={kind === 'search' ? 'launch' : ''} defaultSource />)
fireEvent.click(screen.getByRole('button', { name: 'Linear tab' }))
await waitFor(() => expect(runtime).toHaveBeenCalledTimes(1))
const joined = Array.from({ length: 5 }, () =>
kind === 'search'
? pickerStore.getState().searchLinearIssues('launch', RESULT_LIMIT).acquire()
: pickerStore
.getState()
.listLinearIssues({ kind: 'list', filter: 'assigned', limit: RESULT_LIMIT })
.acquire()
)
expect(runtime).toHaveBeenCalledTimes(1)
queued.enabled = true
if (outcome === 'queued') {
await act(async () => {
old.resolve(data('Pre-event'))
await old.promise
})
}
const beforeEdge = queued.updates.splice(0)
act(() =>
events.emit({
type: 'linearLinkedIssueUpdated',
worktreeId: 'fixture-worktree',
identifier: 'ISSUE-1',
workspaceId: 'workspace-1'
})
)
const replacementUpdates = queued.updates.splice(0)
queued.enabled = false
expect(runtime).toHaveBeenCalledTimes(2)
expect(mocks.state.detail).toHaveBeenCalledTimes(4)
act(() => beforeEdge.forEach((update) => update()))
expect(screen.queryByText('Pre-event')).toBeNull()
act(() => replacementUpdates.forEach((update) => update()))
if (outcome !== 'queued') {
await act(async () => {
if (outcome === 'success') {
old.resolve(data('Pre-event'))
} else {
old.reject(new Error('old private diagnostic'))
}
await old.promise.catch(() => {})
})
expect(screen.queryByText('Pre-event')).toBeNull()
}
await act(async () => {
fresh.reject(new Error('private diagnostic'))
await fresh.promise.catch(() => {})
})
await waitFor(() =>
expect(screen.getAllByText('Unable to load Linear issues. Try again.')).toHaveLength(1)
)
expect(runtime).toHaveBeenCalledTimes(2)
const forced = Promise.withResolvers<LinearIssue[] | { items: LinearIssue[] }>()
const external = Promise.withResolvers<LinearIssue[] | { items: LinearIssue[] }>()
runtime.mockReturnValueOnce(forced.promise).mockReturnValueOnce(external.promise)
const force = () =>
kind === 'search'
? pickerStore
.getState()
.searchLinearIssues('launch', RESULT_LIMIT, { force: true })
.acquire()
: pickerStore
.getState()
.listLinearIssues(
{ kind: 'list', filter: 'assigned', limit: RESULT_LIMIT },
{ force: true }
)
.acquire()
let A!: ReturnType<typeof force>, B!: ReturnType<typeof force>
act(() => {
A = force()
B = force()
})
expect(runtime).toHaveBeenCalledTimes(4)
await act(async () => {
forced.resolve(data('Superseded force'))
external.resolve(data('External'))
await external.promise
})
await waitFor(() => expect(screen.getByText(/External/)).toBeTruthy())
expect(screen.queryByText(/Superseded force/)).toBeNull()
expect(screen.queryByText('Unable to load Linear issues. Try again.')).toBeNull()
A.release()
B.release()
control.resolve(referenceLinearIssue())
await Promise.all(controls.map((lease) => lease.waitForCurrentTerminal()))
expect(controls.every((lease) => lease.currentView().kind === 'data')).toBe(true)
controls.forEach((lease) => lease.release())
mounted.unmount()
joined.forEach((lease) => lease.release())
act(() =>
events.emit({
type: 'linearLinkedIssueUpdated',
worktreeId: 'fixture-worktree',
identifier: 'ISSUE-1',
workspaceId: 'workspace-1'
})
)
expect(runtime).toHaveBeenCalledTimes(4)
cleanup()
clearLinearRequestMaps()
}
)
it('attachment Retry reacquires terminal cached missing without another provider read', async () => {
resetPickerStore()
const status: LinearConnectionStatus = {
connected: true,
viewer: null,
selectedWorkspaceId: 'L',
workspaces: [
{
displayName: 'Test',
email: null,
id: 'L',
organizationId: 'org',
organizationName: 'Acme',
organizationUrlKey: 'acme'
}
]
}
mocks.state.linearStatus = status
pickerStore.setState({
linearStatus: status,
linearStatusChecked: true,
linearStatusContextKey: 'local#0',
preflightStatusChecked: true,
preflightStatusContextKey: 'test',
refreshPreflightStatus: vi.fn()
})
mocks.state.detail.mockReset().mockResolvedValue(null)
function Attachment() {
const [attempt, retry] = React.useReducer((value: number) => value + 1, 0)
return (
<LinearPicker
key={attempt}
value="https://linear.app/acme/issue/ENG-1/missing"
defaultSource
attachment
onRetry={retry}
/>
)
}
render(<Attachment />)
const missing = 'Couldn’t load this Linear issue. Check the URL and connected workspace.'
await waitFor(() => expect(screen.getByText(missing)).toBeTruthy())
expect(mocks.state.detail).toHaveBeenCalledTimes(1)
expect(pickerStore.getState().linearIssueCache['L::ENG-1']?.data).toBeNull()
fireEvent.click(screen.getByRole('button', { name: 'Retry' }))
await waitFor(() => expect(screen.getByText(missing)).toBeTruthy())
expect(screen.queryByText('Loading Linear issue…')).toBeNull()
expect(mocks.state.detail).toHaveBeenCalledTimes(1)
})
it.each(['success', 'error', 'queued'])(
'runtime event retires a held same-literal URL alias observation: %s',
async (outcome) => {
const status = aliasStatus()
const row = aliasRow
prepareDefaultPicker(status)
const old = Promise.withResolvers<LinearIssue | null>()
const fresh = Promise.withResolvers<LinearIssue | null>()
const control = Promise.withResolvers<LinearIssue | null>()
let aliasLoads = 0
mocks.state.detail.mockReset().mockImplementation((context, _id, workspaceId) => {
if (
context.kind === 'task-source' ||
context.environmentId === 'other' ||
workspaceId === 'F'
) {
return control.promise
}
if (workspaceId === 'L') {
return ++aliasLoads === 1 ? old.promise : fresh.promise
}
return Promise.resolve(row('Exact W', 'W'))
})
pickerStore.setState({
linearIssueCache: { 'L::ENG-1': { data: row('Warm'), fetchedAt: 1 } }
})
const explicit = buildTaskSourceContextFromRepo({
provider: 'linear',
projectId: repo.id,
repo
})!
const controls = [
pickerStore
.getState()
.fetchLinearIssue('ENG-1', 'F', { seed: { ...row('Foreign', 'F'), id: 'other-U' } })
.acquire(),
pickerStore
.getState()
.fetchLinearIssue('ENG-1', 'L', {
scope: getLinearSourceScope(explicit),
seed: row('Explicit')
})
.acquire(),
pickerStore
.getState()
.fetchLinearIssue('ENG-1', 'L', {
scope: getLinearSourceScope({ kind: 'environment', environmentId: 'other' }),
seed: row('Other runtime')
})
.acquire()
]
const controlViews = controls.map((lease) => lease.currentView())
installRuntimeBridge()
const mounted = render(
<LinearPicker value="https://linear.app/acme/issue/ENG-1/task" defaultSource />
)
fireEvent.click(screen.getByRole('button', { name: 'Linear tab' }))
await waitFor(() => expect(aliasLoads).toBe(1))
queued.enabled = true
if (outcome === 'queued') {
await act(async () => {
old.resolve(row('Pre-event'))
await old.promise
})
}
const oldUpdates = queued.updates.splice(0)
const emit = () =>
events.emit({
type: 'linearLinkedIssueUpdated',
worktreeId: 'fixture-worktree',
identifier: 'ENG-1',
workspaceId: 'W'
})
act(emit)
const currentUpdates = queued.updates.splice(0)
queued.enabled = false
expect(aliasLoads).toBe(2)
expect(
mocks.state.detail.mock.calls.filter(([, , workspaceId]) => workspaceId === 'W')
).toHaveLength(1)
expect(controls.map((lease) => lease.currentView())).toEqual(controlViews)
act(() => oldUpdates.forEach((update) => update()))
expect(screen.queryByText(/Pre-event/)).toBeNull()
act(() => currentUpdates.forEach((update) => update()))
if (outcome !== 'queued') {
await act(async () => {
if (outcome === 'success') {
old.resolve(row('Pre-event'))
} else {
old.reject(new Error('retired private diagnostic'))
}
await old.promise.catch(() => {})
})
}
if (outcome === 'queued') {
await act(async () => {
fresh.reject(new Error('current private diagnostic'))
await fresh.promise.catch(() => {})
})
await waitFor(() =>
expect(screen.getAllByText('Unable to load Linear issues. Try again.')).toHaveLength(1)
)
} else {
expect(screen.queryByText(/Pre-event/)).toBeNull()
await act(async () => {
fresh.resolve(row('External'))
await fresh.promise
})
await waitFor(() => expect(screen.getByText(/External/)).toBeTruthy())
expect(screen.queryByText(/Pre-event/)).toBeNull()
}
expect(
mocks.state.detail.mock.calls.filter(([, , workspaceId]) => workspaceId === 'W')
).toHaveLength(1)
expect(aliasLoads).toBe(2)
control.resolve(row('Control'))
await Promise.all(controls.map((lease) => lease.waitForCurrentTerminal()))
controls.forEach((lease) => lease.release())
mounted.unmount()
act(emit)
expect(aliasLoads).toBe(2)
}
)
it.each(['success', 'fallback', 'legacy', 'release'] as const)(
'cold URL alias actual provider replacement: %s',
async (outcome) => {
const status = aliasStatus(outcome === 'legacy')
prepareDefaultPicker(status)
expect(pickerStore.getState().linearIssueCache).toEqual({})
const old = Promise.withResolvers<LinearIssue | null>()
const fresh = Promise.withResolvers<LinearIssue | null>()
const control = Promise.withResolvers<LinearIssue | null>()
let aliasLoads = 0
detailTransport.search
.mockReset()
.mockImplementation(async (_query, _variables, workspaceId) => {
const issue =
workspaceId === 'L'
? await (++aliasLoads === 1 ? old.promise : fresh.promise)
: aliasRow('Exact W', 'W')
if (!issue) {
throw new Error('missing issue')
}
// Only provider I/O is synthetic; actual main concurrency/mapping/detail settlement runs.
return { data: { issue: { ...issue, labels: { nodes: [] } } } }
})
detailTransport.rpc.mockReset().mockImplementation((target, method, params) => {
expect(target).toEqual({ kind: 'environment', environmentId: 'env-1' })
expect(method).toBe('linear.getIssue')
return getIssue(params.id, params.workspaceId)
})
mocks.state.detail.mockReset().mockImplementation((context, id, workspaceId) => {
if (
context.kind === 'task-source' ||
context.environmentId === 'other' ||
workspaceId === 'F' ||
id === 'ENG-2'
) {
return control.promise
}
return actual.linearGetIssue(context, id, workspaceId)
})
const explicit = buildTaskSourceContextFromRepo({
provider: 'linear',
projectId: repo.id,
repo
})!
const controls =
outcome === 'success'
? [
pickerStore.getState().fetchLinearIssue('ENG-2', 'L').acquire(),
pickerStore.getState().fetchLinearIssue('ENG-1', 'F').acquire(),
pickerStore
.getState()
.fetchLinearIssue('ENG-1', 'L', {
scope: getLinearSourceScope(explicit)
})
.acquire(),
pickerStore
.getState()
.fetchLinearIssue('ENG-1', 'L', {
scope: getLinearSourceScope({ kind: 'environment', environmentId: 'other' })
})
.acquire()
]
: []
const controlViews = controls.map((lease) => lease.currentView())
expect(controlViews.every((view) => view.kind === 'loading' && view.data === null)).toBe(true)
installRuntimeBridge()
const mounted = render(
<LinearPicker value="https://linear.app/acme/issue/ENG-1/task" defaultSource attachment />
)
const emit = () =>
events.emit({
type: 'linearLinkedIssueUpdated',
worktreeId: 'fixture-worktree',
identifier: 'ENG-1',
workspaceId: 'W'
})
const loads = (workspaceId: string) =>
detailTransport.search.mock.calls.filter(([, , workspace]) => workspace === workspaceId)
.length
try {
fireEvent.click(screen.getByRole('button', { name: 'Linear tab' }))
await waitFor(() => expect(loads('L')).toBe(1))
expect(screen.getByText('Loading Linear issue…')).toBeTruthy()
act(emit)
await waitFor(() => expect(loads('L')).toBe(2))
await waitFor(() => expect(loads('W')).toBe(1))
expect(controls.map((lease) => lease.currentView())).toEqual(controlViews)
expect(mocks.state.detail).toHaveBeenCalledTimes(controls.length + 3)
if (outcome === 'release') {
mounted.unmount()
}
await act(async () => {
if (outcome === 'fallback') {
old.reject(new Error('retired provider diagnostic'))
} else {
old.resolve(aliasRow('Pre-event In Review'))
}
await old.promise.catch(() => {})
})
expect(screen.queryByText(/Pre-event/)).toBeNull()
expect(screen.queryByText(/Exact W/)).toBeNull()
if (outcome !== 'release') {
expect(screen.getByText('Loading Linear issue…')).toBeTruthy()
expect(screen.queryByText(/Couldn’t load this Linear issue/)).toBeNull()
}
await act(async () => {
if (outcome === 'fallback') {
// The existing status probe supplies a newly discovered URL candidate W.
mocks.state.linearStatus = { ...status, workspaces: status.workspaces!.toReversed() }
fresh.reject(new Error('current provider diagnostic'))
} else {
fresh.resolve(aliasRow('Current External'))
}
await fresh.promise.catch(() => {})
})
if (outcome !== 'release') {
await waitFor(() =>
expect(
screen.getByText(outcome === 'fallback' ? /Exact W/ : /Current External/)
).toBeTruthy()
)
expect(screen.queryByText('Loading Linear issue…')).toBeNull()
expect(screen.queryByText(/Couldn’t load this Linear issue/)).toBeNull()
expect(screen.queryByText(/provider diagnostic|Pre-event/)).toBeNull()
}
expect(loads('L')).toBe(2)
expect(loads('W')).toBe(1)
expect(controls.map((lease) => lease.currentView())).toEqual(controlViews)
expect(mocks.state.detail).toHaveBeenCalledTimes(controls.length + 3)
controls.forEach((lease) => lease.release())
mounted.unmount()
act(emit)
await waitFor(() => expect(loads('W')).toBe(2))
expect(loads('L')).toBe(2)
expect(mocks.state.detail).toHaveBeenCalledTimes(controls.length + 4)
} finally {
mounted.unmount()
controls.forEach((lease) => lease.release())
await act(async () => {
old.resolve(aliasRow('Cleanup'))
fresh.resolve(aliasRow('Cleanup'))
control.resolve(aliasRow('Control'))
await Promise.allSettled([old.promise, fresh.promise, control.promise])
})
}
}
)
it.each([false, true])(
'warm default launch/12 keeps Task and one error with status outage=%s',
async (outage) => {
prepareDefaultPicker({ connected: true, viewer: null, selectedWorkspaceId: 'workspace-1' })
mocks.state.searchLinearIssues.mockReset().mockResolvedValue([referenceLinearIssue()])
render(<LinearPicker value="launch" defaultSource />)
fireEvent.click(screen.getByRole('button', { name: 'Linear tab' }))
await waitFor(() => expect(screen.getByText(/Task/)).toBeTruthy())
if (outage) {
mocks.statusReads.mockRejectedValue(new Error('remote status transport unavailable'))
}
mocks.state.searchLinearIssues.mockRejectedValue(
new Error('remote search transport unavailable')
)
const read = pickerStore
.getState()
.searchLinearIssues('launch', 12, { force: true, workspaceId: 'workspace-1' })
.acquire()
await act(async () => {
await read.waitForCurrentTerminal()
})
expect(pickerStore.getState().linearStatus.connected).toBe(true)
expect(screen.getByText(/Task/)).toBeTruthy()
expect(screen.getAllByText('Unable to load Linear issues. Try again.')).toHaveLength(1)
expect(mocks.statusReads).not.toHaveBeenCalled()
read.release()
}
)
it('Settings keeps the confirmed account during failed checks, then displays actual disconnected status', async () => {
prepareDefaultPicker({ connected: true, viewer: null, selectedWorkspaceId: 'workspace-1' })
render(
<TooltipProvider>
<LinearIntegrationCard />
</TooltipProvider>
)
expect(screen.getByText('Connected')).toBeTruthy()
mocks.statusReads.mockRejectedValue(new Error('status transport unavailable'))
await act(async () => {
await pickerStore.getState().checkLinearConnection(true)
})
expect(screen.getByText('Connected')).toBeTruthy()
expect(screen.queryByText('Not connected')).toBeNull()
mocks.statusReads.mockResolvedValue({ connected: false, viewer: null })
await act(async () => {
await pickerStore.getState().checkLinearConnection(true)
})
expect(screen.getByText('Not connected')).toBeTruthy()
})
it('a cold default host cannot import the previously focused host account or rows', async () => {
prepareDefaultPicker({ connected: true, viewer: null, selectedWorkspaceId: 'workspace-1' })
mocks.state.searchLinearIssues.mockReset().mockResolvedValue([referenceLinearIssue()])
render(<LinearPicker value="launch" defaultSource />)
fireEvent.click(screen.getByRole('button', { name: 'Linear tab' }))
await waitFor(() => expect(screen.getByText(/Task/)).toBeTruthy())
mocks.statusReads.mockRejectedValue(new Error('new host unavailable'))
const settings = pickerStore.getState().settings
if (!settings) {
throw new Error('fixture requires captured host settings')
}
act(() =>
pickerStore.setState({ settings: { ...settings, activeRuntimeEnvironmentId: 'other' } })
)
expect(screen.queryByText(/Task/)).toBeNull()
await waitFor(() => expect(pickerStore.getState().linearStatusContextKey).toBe('runtime:other#0'))
expect(pickerStore.getState().linearStatusObserved).toBe(false)
expect(pickerStore.getState().linearStatusChecked).toBe(true)
})