mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-06 02:21:33 +08:00
🐛 fix(driver-manager): 修复驱动安装交互与 DuckDB Windows 发布链路
- 修复单驱动安装期间右侧目录操作被错误禁用的问题 - 调整 DuckDB Windows 优先下载专属 zip 并兼容带 query 的签名链接 - 补齐本地构建与 CI 发布的 duckdb-driver.zip 产物及回归测试
This commit is contained in:
204
frontend/src/components/DriverManagerModal.test.tsx
Normal file
204
frontend/src/components/DriverManagerModal.test.tsx
Normal file
@@ -0,0 +1,204 @@
|
||||
import React from 'react';
|
||||
import { act, create, type ReactTestRenderer } from 'react-test-renderer';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import DriverManagerModal from './DriverManagerModal';
|
||||
|
||||
const storeState = vi.hoisted(() => ({
|
||||
theme: 'light',
|
||||
appearance: {
|
||||
enabled: true,
|
||||
opacity: 1,
|
||||
blur: 0,
|
||||
uiVersion: 'legacy',
|
||||
},
|
||||
}));
|
||||
|
||||
const backendApp = vi.hoisted(() => ({
|
||||
CheckDriverNetworkStatus: vi.fn(),
|
||||
DownloadDriverPackage: vi.fn(),
|
||||
GetDriverVersionList: vi.fn(),
|
||||
GetDriverVersionPackageSize: vi.fn(),
|
||||
GetDriverStatusList: vi.fn(),
|
||||
InstallLocalDriverPackage: vi.fn(),
|
||||
OpenDriverDownloadDirectory: vi.fn(),
|
||||
RemoveDriverPackage: vi.fn(),
|
||||
SelectDriverPackageDirectory: vi.fn(),
|
||||
SelectDriverPackageFile: vi.fn(),
|
||||
}));
|
||||
|
||||
const runtimeApi = vi.hoisted(() => ({
|
||||
EventsOn: vi.fn(() => vi.fn()),
|
||||
}));
|
||||
|
||||
const messageApi = vi.hoisted(() => ({
|
||||
error: vi.fn(),
|
||||
success: vi.fn(),
|
||||
warning: vi.fn(),
|
||||
info: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../store', () => ({
|
||||
useStore: (selector: (state: typeof storeState) => any) => selector(storeState),
|
||||
}));
|
||||
|
||||
vi.mock('../../wailsjs/go/app/App', () => backendApp);
|
||||
vi.mock('../../wailsjs/runtime/runtime', () => runtimeApi);
|
||||
|
||||
vi.mock('@ant-design/icons', () => {
|
||||
const Icon = () => <span />;
|
||||
return {
|
||||
DeleteOutlined: Icon,
|
||||
DownloadOutlined: Icon,
|
||||
FileSearchOutlined: Icon,
|
||||
FolderOpenOutlined: Icon,
|
||||
InfoCircleFilled: Icon,
|
||||
ReloadOutlined: Icon,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('antd', () => {
|
||||
const Button: any = ({ children, disabled, loading, onClick, ...rest }: any) => (
|
||||
<button type="button" disabled={disabled || loading} onClick={onClick} {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
const Input: any = ({ value, onChange, placeholder }: any) => (
|
||||
<input value={value} onChange={onChange} placeholder={placeholder} />
|
||||
);
|
||||
Input.Search = ({ value, onChange, placeholder }: any) => (
|
||||
<input value={value} onChange={onChange} placeholder={placeholder} />
|
||||
);
|
||||
|
||||
const Select = () => null;
|
||||
const Progress = () => <div data-progress="true" />;
|
||||
const Tag = ({ children }: any) => <span>{children}</span>;
|
||||
const Switch = ({ checked, onChange, disabled }: any) => (
|
||||
<button type="button" disabled={disabled} data-switch-checked={String(checked)} onClick={() => onChange?.(!checked)}>
|
||||
switch
|
||||
</button>
|
||||
);
|
||||
const Space = ({ children }: any) => <div>{children}</div>;
|
||||
const Text = ({ children }: any) => <span>{children}</span>;
|
||||
const Paragraph = ({ children }: any) => <div>{children}</div>;
|
||||
const Typography = { Paragraph, Text };
|
||||
const Alert = ({ children, message, description }: any) => <div>{children}{message}{description}</div>;
|
||||
const Empty: any = ({ description }: any) => <div>{description}</div>;
|
||||
Empty.PRESENTED_IMAGE_SIMPLE = null;
|
||||
const Collapse = ({ items }: any) => (
|
||||
<div>{items?.map((item: any) => <div key={item.key}>{item.label}{item.children}</div>)}</div>
|
||||
);
|
||||
const Modal: any = ({ children, open, footer, title }: any) => (open ? (
|
||||
<section data-modal-title={title}>
|
||||
{children}
|
||||
<div>{footer}</div>
|
||||
</section>
|
||||
) : null);
|
||||
Modal.confirm = vi.fn();
|
||||
|
||||
return {
|
||||
Alert,
|
||||
Button,
|
||||
Collapse,
|
||||
Empty,
|
||||
Input,
|
||||
Modal,
|
||||
Progress,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Tag,
|
||||
Typography,
|
||||
message: messageApi,
|
||||
};
|
||||
});
|
||||
|
||||
const flushPromises = async () => {
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const textContent = (node: any): string =>
|
||||
(node.children || [])
|
||||
.map((item: any) => (typeof item === 'string' ? item : textContent(item)))
|
||||
.join('');
|
||||
|
||||
const findButton = (renderer: ReactTestRenderer, text: string) =>
|
||||
renderer.root.findAll((node) => node.type === 'button' && textContent(node).includes(text))[0];
|
||||
|
||||
describe('DriverManagerModal toolbar actions', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
backendApp.GetDriverStatusList.mockResolvedValue({
|
||||
success: true,
|
||||
data: {
|
||||
downloadDir: 'D:/drivers',
|
||||
drivers: [
|
||||
{
|
||||
type: 'duckdb',
|
||||
name: 'DuckDB',
|
||||
builtIn: false,
|
||||
pinnedVersion: '2.5.6',
|
||||
runtimeAvailable: false,
|
||||
packageInstalled: false,
|
||||
connectable: false,
|
||||
defaultDownloadUrl: 'builtin://activate/duckdb',
|
||||
message: '未启用',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
backendApp.CheckDriverNetworkStatus.mockResolvedValue({
|
||||
success: true,
|
||||
data: {
|
||||
reachable: true,
|
||||
summary: 'ok',
|
||||
recommendedProxy: false,
|
||||
proxyConfigured: false,
|
||||
checks: [],
|
||||
},
|
||||
});
|
||||
backendApp.GetDriverVersionList.mockResolvedValue({
|
||||
success: true,
|
||||
data: {
|
||||
versions: [{ version: '2.5.6', downloadUrl: 'builtin://activate/duckdb', recommended: true }],
|
||||
},
|
||||
});
|
||||
backendApp.DownloadDriverPackage.mockImplementation(() => new Promise(() => {}));
|
||||
backendApp.OpenDriverDownloadDirectory.mockResolvedValue({ success: true });
|
||||
backendApp.SelectDriverPackageDirectory.mockResolvedValue({ success: true, data: { path: 'D:/drivers/import' } });
|
||||
});
|
||||
|
||||
it('keeps directory tools enabled while a single driver install is running', async () => {
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<DriverManagerModal open onClose={vi.fn()} />);
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
const installButton = findButton(renderer!, '安装启用');
|
||||
const openDirButtonBefore = findButton(renderer!, '打开驱动目录');
|
||||
const importDirButtonBefore = findButton(renderer!, '导入驱动目录');
|
||||
const installAllButtonBefore = findButton(renderer!, '安装所有驱动');
|
||||
|
||||
expect(openDirButtonBefore.props.disabled).toBeFalsy();
|
||||
expect(importDirButtonBefore.props.disabled).toBeFalsy();
|
||||
expect(installAllButtonBefore.props.disabled).toBeFalsy();
|
||||
|
||||
await act(async () => {
|
||||
installButton.props.onClick();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const openDirButtonAfter = findButton(renderer!, '打开驱动目录');
|
||||
const importDirButtonAfter = findButton(renderer!, '导入驱动目录');
|
||||
const installAllButtonAfter = findButton(renderer!, '安装所有驱动');
|
||||
|
||||
expect(openDirButtonAfter.props.disabled).toBeFalsy();
|
||||
expect(importDirButtonAfter.props.disabled).toBeFalsy();
|
||||
expect(installAllButtonAfter.props.disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -234,7 +234,8 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
const [versionSizeLoadingMap, setVersionSizeLoadingMap] = useState<Record<string, boolean>>({});
|
||||
const downloadDirRef = useRef(downloadDir);
|
||||
const progressMapRef = useRef<Record<string, DriverProgressState>>({});
|
||||
const batchBusy = batchDirectoryImporting || batchAction !== '' || actionState.kind !== '';
|
||||
const batchBusy = batchDirectoryImporting || batchAction !== '';
|
||||
const installMutatingBusy = batchBusy || actionState.kind !== '';
|
||||
|
||||
useEffect(() => {
|
||||
downloadDirRef.current = downloadDir;
|
||||
@@ -1436,7 +1437,7 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
网络检测
|
||||
</Button>
|
||||
<Button key="close" type="primary" onClick={onClose}>
|
||||
{batchBusy ? '后台运行' : '关闭'}
|
||||
{installMutatingBusy ? '后台运行' : '关闭'}
|
||||
</Button>
|
||||
</Space>
|
||||
)}
|
||||
@@ -1584,12 +1585,12 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
<Switch
|
||||
checked={forceOverwriteInstalled}
|
||||
onChange={(checked) => setForceOverwriteInstalled(checked)}
|
||||
disabled={batchBusy}
|
||||
disabled={batchDirectoryImporting}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
disabled={batchBusy || installableRows.length === 0}
|
||||
disabled={installMutatingBusy || installableRows.length === 0}
|
||||
loading={batchAction === 'install-all'}
|
||||
onClick={() => void installAllDrivers()}
|
||||
>
|
||||
@@ -1598,7 +1599,7 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
disabled={batchBusy || reinstallableRows.length === 0}
|
||||
disabled={installMutatingBusy || reinstallableRows.length === 0}
|
||||
loading={batchAction === 'reinstall-updates'}
|
||||
onClick={() => void reinstallNeededDrivers()}
|
||||
>
|
||||
@@ -1607,7 +1608,7 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
disabled={batchBusy || removableRows.length === 0}
|
||||
disabled={installMutatingBusy || removableRows.length === 0}
|
||||
loading={batchAction === 'remove-all'}
|
||||
onClick={() => void removeAllDrivers()}
|
||||
>
|
||||
@@ -1615,7 +1616,6 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
</Button>
|
||||
<Button
|
||||
icon={<FolderOpenOutlined />}
|
||||
disabled={batchBusy}
|
||||
onClick={() => void openDriverDirectory()}
|
||||
>
|
||||
打开驱动目录
|
||||
@@ -1623,7 +1623,7 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
<Button
|
||||
icon={<FolderOpenOutlined />}
|
||||
loading={batchDirectoryImporting}
|
||||
disabled={batchBusy && !batchDirectoryImporting}
|
||||
disabled={batchDirectoryImporting}
|
||||
onClick={() => void installDriversFromDirectory()}
|
||||
>
|
||||
导入驱动目录
|
||||
|
||||
Reference in New Issue
Block a user