diff --git a/frontend/src/pages/index/PanelUpdateModal.tsx b/frontend/src/pages/index/PanelUpdateModal.tsx index c785cc9a9..10a22ee6b 100644 --- a/frontend/src/pages/index/PanelUpdateModal.tsx +++ b/frontend/src/pages/index/PanelUpdateModal.tsx @@ -80,6 +80,39 @@ export default function PanelUpdateModal({ } } + async function runPanelUpdate() { + const baseTip = t('pages.index.dontRefresh'); + const tip = info.latestVersion ? `${baseTip} (${info.latestVersion})` : baseTip; + onClose(); + onBusy({ busy: true, tip }); + const result = await HttpUtil.post<{ runId: string }>('/panel/api/server/updatePanel'); + if (!result?.success) { + onBusy({ busy: false }); + return; + } + const outcome = await pollUpdateStatus(result.obj?.runId ?? ''); + onBusy({ busy: false }); + if (outcome === 'success') { + await PromiseUtil.sleep(800); + window.location.reload(); + return; + } + modal[outcome === 'failed' ? 'error' : 'warning']({ + title: t( + outcome === 'failed' + ? 'pages.index.panelUpdateFailedTitle' + : 'pages.index.panelUpdateUnknownTitle', + ), + content: t( + outcome === 'failed' + ? 'pages.index.panelUpdateFailedDesc' + : 'pages.index.panelUpdateUnknownDesc', + ), + okText: t('refresh'), + onOk: () => window.location.reload(), + }); + } + function updatePanel() { modal.confirm({ title: t('pages.index.panelUpdateDialog'), @@ -89,37 +122,8 @@ export default function PanelUpdateModal({ ), okText: t('confirm'), cancelText: t('cancel'), - onOk: async () => { - const baseTip = t('pages.index.dontRefresh'); - const tip = info.latestVersion ? `${baseTip} (${info.latestVersion})` : baseTip; - onClose(); - onBusy({ busy: true, tip }); - const result = await HttpUtil.post<{ runId: string }>('/panel/api/server/updatePanel'); - if (!result?.success) { - onBusy({ busy: false }); - return; - } - const outcome = await pollUpdateStatus(result.obj?.runId ?? ''); - onBusy({ busy: false }); - if (outcome === 'success') { - await PromiseUtil.sleep(800); - window.location.reload(); - return; - } - modal[outcome === 'failed' ? 'error' : 'warning']({ - title: t( - outcome === 'failed' - ? 'pages.index.panelUpdateFailedTitle' - : 'pages.index.panelUpdateUnknownTitle', - ), - content: t( - outcome === 'failed' - ? 'pages.index.panelUpdateFailedDesc' - : 'pages.index.panelUpdateUnknownDesc', - ), - okText: t('refresh'), - onOk: () => window.location.reload(), - }); + onOk: () => { + void runPanelUpdate(); }, }); } diff --git a/frontend/src/test/panel-update-modal.test.tsx b/frontend/src/test/panel-update-modal.test.tsx new file mode 100644 index 000000000..a94d30a5b --- /dev/null +++ b/frontend/src/test/panel-update-modal.test.tsx @@ -0,0 +1,62 @@ +import { fireEvent, screen } from '@testing-library/react'; +import { Modal } from 'antd'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import PanelUpdateModal from '@/pages/index/PanelUpdateModal'; +import { HttpUtil } from '@/utils'; +import { renderWithProviders } from './test-utils'; + +let restoreUseModal = () => {}; + +describe('panel update modal', () => { + afterEach(() => { + restoreUseModal(); + restoreUseModal = () => {}; + vi.mocked(HttpUtil.post).mockReset(); + }); + + it('lets the confirmation close before the update request finishes', () => { + const confirm = vi.fn(); + const useModal = vi.spyOn(Modal, 'useModal').mockReturnValue([ + { + confirm, + error: vi.fn(), + warning: vi.fn(), + } as unknown as ReturnType[0], + , + ]); + restoreUseModal = () => useModal.mockRestore(); + vi.mocked(HttpUtil.post).mockReturnValue(new Promise(() => {})); + const onClose = vi.fn(); + const onBusy = vi.fn(); + renderWithProviders( + , + ); + + fireEvent.click(screen.getByRole('button', { name: /Update Panel/ })); + expect(confirm).toHaveBeenCalledOnce(); + const config = confirm.mock.calls[0]?.[0]; + if (!config?.onOk) throw new Error('Confirmation onOk handler was not registered'); + + expect(config.onOk()).toBeUndefined(); + expect(onClose).toHaveBeenCalledOnce(); + expect(HttpUtil.post).toHaveBeenCalledWith('/panel/api/server/updatePanel'); + expect(onBusy).toHaveBeenCalledWith({ + busy: true, + tip: 'Installation is in progress, please do not refresh this page (dev+new)', + }); + }); +});