fix(ui-models): contain the card's credential probe rejection
The review named this call site with the other two, and the previous pass
missed it: the editor card's mount-time `credentials.describe` had only a
fulfillment handler, so a transport failure reached the browser as an
unhandled rejection.
The probe is a placeholder hint ("already configured"), never a precondition
for editing, so it now renders without the hint rather than failing. Covered
by a test that fails without the handler.
This commit is contained in:
@@ -141,10 +141,17 @@ export function ProviderEditor(props: ProviderEditorProps): ReactNode {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let stale = false
|
let stale = false
|
||||||
setKeyState(undefined)
|
setKeyState(undefined)
|
||||||
void api.credentials.describe({ refs: [keyRef] }).then((response) => {
|
// The key state is a placeholder hint, not a precondition for editing:
|
||||||
if (stale || !response.result.ok) return
|
// neither a business rejection nor a transport failure may reach the
|
||||||
setKeyState(response.result.value.credentials[keyRef])
|
// browser as an unhandled rejection, so the card simply renders without
|
||||||
})
|
// the "already configured" hint.
|
||||||
|
void api.credentials.describe({ refs: [keyRef] }).then(
|
||||||
|
(response) => {
|
||||||
|
if (stale || !response.result.ok) return
|
||||||
|
setKeyState(response.result.value.credentials[keyRef])
|
||||||
|
},
|
||||||
|
() => undefined,
|
||||||
|
)
|
||||||
return () => { stale = true }
|
return () => { stale = true }
|
||||||
}, [api.credentials, keyRef])
|
}, [api.credentials, keyRef])
|
||||||
|
|
||||||
|
|||||||
@@ -371,6 +371,31 @@ describe('ModelsSection', () => {
|
|||||||
expect(set).not.toHaveBeenCalled()
|
expect(set).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('renders the card without the stored-key hint when the credential probe rejects', async () => {
|
||||||
|
// The probe is a placeholder hint, not a precondition: an escaping
|
||||||
|
// rejection would surface in the browser as an unhandled rejection.
|
||||||
|
const { face } = scriptedFace()
|
||||||
|
face.credentials.describe = vi.fn(() => Promise.reject(new Error('connection lost')))
|
||||||
|
const unhandled = vi.fn()
|
||||||
|
process.on('unhandledRejection', unhandled)
|
||||||
|
try {
|
||||||
|
const controller = new ModelsSettingsStore(face as unknown as WireFace)
|
||||||
|
await controller.load()
|
||||||
|
render(<ModelsSection
|
||||||
|
controller={controller}
|
||||||
|
useSnapshot={bindSnapshotSelector(controller.store)}
|
||||||
|
api={face as never}
|
||||||
|
t={t}
|
||||||
|
/>)
|
||||||
|
const key = await screen.findByLabelText<HTMLInputElement>(en.keyInput)
|
||||||
|
expect(key.placeholder).toBe(en.keyPlaceholder)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10))
|
||||||
|
expect(unhandled).not.toHaveBeenCalled()
|
||||||
|
} finally {
|
||||||
|
process.off('unhandledRejection', unhandled)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('tells the user to reopen when another writer moved the namespace first', async () => {
|
it('tells the user to reopen when another writer moved the namespace first', async () => {
|
||||||
// The stale-draft overwrite: two tabs open the same card, the other saves,
|
// The stale-draft overwrite: two tabs open the same card, the other saves,
|
||||||
// and this one must be refused rather than replay its opening snapshot.
|
// and this one must be refused rather than replay its opening snapshot.
|
||||||
|
|||||||
Reference in New Issue
Block a user