Skip to content

Commit dc7b205

Browse files
committed
fix(json-render-ui): clear action errors on retry
1 parent cac6900 commit dc7b205

2 files changed

Lines changed: 63 additions & 1 deletion

File tree

‎packages/json-render-ui/src/action-bridge.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export interface JsonRenderActionBridge {
1515
handlers: Record<string, (params?: Record<string, unknown>) => Promise<unknown>>
1616
/** Reactive per-action loading flags. */
1717
loading: Record<string, boolean>
18-
/** The most recent action failure, or `null`. */
18+
/** The most recent action failure, cleared when that action is retried. */
1919
error: { value: JsonRenderActionError | null }
2020
}
2121

@@ -53,6 +53,8 @@ export function createActionBridge(
5353
error.value = { action, error: err }
5454
throw err
5555
}
56+
if (error.value?.action === action)
57+
error.value = null
5658
loading[action] = true
5759
try {
5860
return await rpc.call(action, params)

‎packages/json-render-ui/test/action-bridge.test.ts‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,64 @@ describe('action bridge', () => {
4949
expect(call).not.toHaveBeenCalled()
5050
expect(bridge.error.value?.action).toBe('doThing')
5151
})
52+
53+
it('clears an action error when retrying and keeps it clear after success', async () => {
54+
expect.assertions(6)
55+
const retry = Promise.withResolvers<string>()
56+
const call = vi.fn(async () => 'ok')
57+
.mockRejectedValueOnce(new Error('first attempt failed'))
58+
.mockReturnValueOnce(retry.promise)
59+
const bridge = createActionBridge({ call })
60+
61+
await expect(bridge.handlers.refreshData()).rejects.toThrow('first attempt failed')
62+
const pending = bridge.handlers.refreshData()
63+
expect(bridge.loading.refreshData).toBe(true)
64+
expect(bridge.error.value).toBeNull()
65+
retry.resolve('ok')
66+
await expect(pending).resolves.toBe('ok')
67+
expect(bridge.error.value).toBeNull()
68+
expect(bridge.loading.refreshData).toBe(false)
69+
})
70+
71+
it('reports the new error when a retry fails', async () => {
72+
expect.assertions(3)
73+
const retryError = new Error('retry failed')
74+
const call = vi.fn()
75+
.mockRejectedValueOnce(new Error('first attempt failed'))
76+
.mockRejectedValueOnce(retryError)
77+
const bridge = createActionBridge({ call })
78+
79+
await expect(bridge.handlers.refreshData()).rejects.toThrow('first attempt failed')
80+
await expect(bridge.handlers.refreshData()).rejects.toThrow('retry failed')
81+
expect(bridge.error.value).toEqual({ action: 'refreshData', error: retryError })
82+
})
83+
84+
it('preserves an error when another action succeeds', async () => {
85+
expect.assertions(3)
86+
const error = new Error('refresh failed')
87+
const call = vi.fn(async () => 'ok').mockRejectedValueOnce(error)
88+
const bridge = createActionBridge({ call })
89+
90+
await expect(bridge.handlers.refreshData()).rejects.toThrow('refresh failed')
91+
await expect(bridge.handlers.saveData()).resolves.toBe('ok')
92+
expect(bridge.error.value).toEqual({ action: 'refreshData', error })
93+
})
94+
95+
it('preserves a failure received while a retry is pending', async () => {
96+
expect.assertions(4)
97+
const retry = Promise.withResolvers<string>()
98+
const error = new Error('save failed')
99+
const call = vi.fn(async () => 'ok')
100+
.mockRejectedValueOnce(new Error('refresh failed'))
101+
.mockReturnValueOnce(retry.promise)
102+
.mockRejectedValueOnce(error)
103+
const bridge = createActionBridge({ call })
104+
105+
await expect(bridge.handlers.refreshData()).rejects.toThrow('refresh failed')
106+
const pending = bridge.handlers.refreshData()
107+
await expect(bridge.handlers.saveData()).rejects.toThrow('save failed')
108+
retry.resolve('ok')
109+
await expect(pending).resolves.toBe('ok')
110+
expect(bridge.error.value).toEqual({ action: 'saveData', error })
111+
})
52112
})

0 commit comments

Comments
 (0)