test(plugins): make the refresh spinner hold deterministic
Extract settleRefresh() so the spinner-minimum hold has one settle point, advance the e2e fake clock past the 400ms hold before asserting the button restores, and drive the disposal-during-hold store test on fake timers for both success and failure reads.
This commit is contained in:
1 parent
eb9efc6265
commit
db5ed22884
3 files changed
+33
-18
No files matched your search
@@ -312,6 +312,7 @@ describe('web e2e: plugin manager', () => {
|
||||
await recordRefresh(`${outcome}: pending`)
|
||||
|
||||
release.resolve(undefined)
|
||||
await probe.clock.runFor(400)
|
||||
await expect.poll(() => refresh.isEnabled()).toBe(true)
|
||||
expect(await refresh.getAttribute('aria-busy')).toBe('false')
|
||||
expect(await panel.getAttribute('aria-busy')).toBe('false')
|
||||
|
||||
@@ -744,15 +744,19 @@ export class PluginManagerController {
|
||||
// Hold the spinner to its minimum so a fast read does not flash it, then settle.
|
||||
const remaining = REFRESH_SPINNER_MIN_MS - (Date.now() - startedAt)
|
||||
if (remaining > 0) await new Promise<void>((resolve) => { setTimeout(resolve, remaining) })
|
||||
if (!this.disposed) {
|
||||
const failed = this.getSnapshot().status === 'error'
|
||||
this.patch(failed && this.hasCachedInventory
|
||||
? { status: 'ready', refreshStatus: 'idle', notice: { kind: 'refresh-failed', seq: ++this.noticeSeq } }
|
||||
: { refreshStatus: failed ? 'failed' : 'idle' })
|
||||
}
|
||||
this.settleRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
/** Publish refresh feedback only before disposal. */
|
||||
private settleRefresh(): void {
|
||||
if (this.disposed) return
|
||||
const failed = this.getSnapshot().status === 'error'
|
||||
this.patch(failed && this.hasCachedInventory
|
||||
? { status: 'ready', refreshStatus: 'idle', notice: { kind: 'refresh-failed', seq: ++this.noticeSeq } }
|
||||
: { refreshStatus: failed ? 'failed' : 'idle' })
|
||||
}
|
||||
|
||||
private async read(): Promise<void> {
|
||||
try {
|
||||
do {
|
||||
|
||||
@@ -1233,18 +1233,28 @@ describe('PluginManagerController', () => {
|
||||
expect(state()).toBe(before)
|
||||
})
|
||||
|
||||
it('drops a manual refresh that settles after disposal', async () => {
|
||||
const gate = deferred<ReturnType<typeof ok<BundleInfo[]>>>()
|
||||
const { plugins, face, state, controller } = bench()
|
||||
await controller.load()
|
||||
plugins.listBundles.mockReturnValueOnce(gate.promise)
|
||||
face.refresh()
|
||||
await vi.waitFor(() => { expect(state().refreshStatus).toBe('refreshing') })
|
||||
controller.dispose()
|
||||
const before = state()
|
||||
gate.resolve(ok([{ ...BUNDLE, version: '9.9.9' }]))
|
||||
await new Promise<void>((resolve) => { setTimeout(resolve, 10) })
|
||||
expect(state()).toBe(before)
|
||||
it.each([false, true])('drops refresh feedback when disposed during the spinner hold (failure: %s)', async (failure) => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const { plugins, face, state, controller } = bench()
|
||||
await controller.load()
|
||||
plugins.listBundles.mockResolvedValueOnce(failure ? refused('gateway/internal', 'offline') : ok([BUNDLE]))
|
||||
face.refresh()
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
expect(state().refreshStatus).toBe('refreshing')
|
||||
expect(vi.getTimerCount()).toBe(1)
|
||||
const before = state()
|
||||
const listener = vi.fn()
|
||||
const unsubscribe = face.hooks.pluginManager.subscribe(listener)
|
||||
onTestFinished(unsubscribe)
|
||||
controller.dispose()
|
||||
await vi.advanceTimersByTimeAsync(400)
|
||||
expect(state()).toBe(before)
|
||||
expect(listener).not.toHaveBeenCalled()
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('settles a manual refresh whose read outlasts the spinner minimum without an extra hold', async () => {
|
||||
|
||||
Reference in new issue
Block a user