mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-22 17:00:21 +08:00
🐛 fix(data-viewer): 修复表数据刷新后总数缓存过期
- 刷新时校验已知总数是否满足当前页 hasMore 信号 - 旧总数过期时清空 countKey 并重新统计总数 - 增加表数据增长后的分页回归测试
This commit is contained in:
@@ -78,6 +78,11 @@ const flushPromises = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
const createRows = (count: number) => Array.from({ length: count }, (_, i) => ({
|
||||
ID: i + 1,
|
||||
NAME: `row-${i + 1}`,
|
||||
}));
|
||||
|
||||
describe('DataViewer safe editing locator', () => {
|
||||
const renderAndReload = async (tab: TabData = createTab()) => {
|
||||
let renderer: ReactTestRenderer;
|
||||
@@ -195,6 +200,58 @@ describe('DataViewer safe editing locator', () => {
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
it('invalidates a stale known total when table data grows after a manual refresh', async () => {
|
||||
storeState.connections[0].config.type = 'mysql';
|
||||
storeState.connections[0].config.database = 'main';
|
||||
backendApp.DBGetColumns.mockResolvedValue({
|
||||
success: true,
|
||||
data: [{ name: 'ID', key: 'PRI' }, { name: 'NAME', key: '' }],
|
||||
});
|
||||
|
||||
let pageQueryCount = 0;
|
||||
backendApp.DBQuery.mockImplementation(async (_config: any, _dbName: string, sql: string) => {
|
||||
if (/count\s*\(/i.test(String(sql))) {
|
||||
return {
|
||||
success: true,
|
||||
fields: ['total'],
|
||||
data: [{ total: 500 }],
|
||||
};
|
||||
}
|
||||
pageQueryCount += 1;
|
||||
return {
|
||||
success: true,
|
||||
fields: ['ID', 'NAME'],
|
||||
data: pageQueryCount === 1 ? createRows(100) : createRows(101),
|
||||
};
|
||||
});
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<DataViewer tab={createTab({ dbName: 'main', tableName: 'users', title: 'users' })} />);
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(dataGridState.latestProps?.pagination).toMatchObject({
|
||||
total: 100,
|
||||
totalKnown: true,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
dataGridState.latestProps?.onReload();
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(backendApp.DBQuery.mock.calls.some((call: any[]) => /count\s*\(/i.test(String(call[2] || '')))).toBe(true);
|
||||
expect(dataGridState.latestProps?.pagination).toMatchObject({
|
||||
total: 500,
|
||||
totalKnown: true,
|
||||
});
|
||||
expect(dataGridState.latestProps?.data).toHaveLength(100);
|
||||
renderer!.unmount();
|
||||
});
|
||||
|
||||
it('shows an actionable message for DuckDB timeout interruption errors', async () => {
|
||||
storeState.connections[0].config.type = 'duckdb';
|
||||
storeState.connections[0].config.database = 'main';
|
||||
|
||||
@@ -85,6 +85,11 @@ const parseTotalFromCountRow = (row: any): number | null => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const isKnownTotalFreshForPage = (total: unknown, minExpectedTotal: number): boolean => {
|
||||
const parsedTotal = toNonNegativeFiniteNumber(total);
|
||||
return parsedTotal !== null && parsedTotal >= minExpectedTotal;
|
||||
};
|
||||
|
||||
const buildDataViewerReadOnlyLocator = (reason: string): EditRowLocator => ({
|
||||
strategy: 'none',
|
||||
columns: [],
|
||||
@@ -743,9 +748,16 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAct
|
||||
const countKey = `${tab.connectionId}|${dbName}|${tableName}|${whereSQL}`;
|
||||
const derivedTotalKnown = !hasMore;
|
||||
const derivedTotal = derivedTotalKnown ? offset + resultData.length : currentPage * size + 1;
|
||||
const isDuckDB = dbTypeLower === 'duckdb';
|
||||
const minExpectedTotal = hasMore ? offset + resultData.length + 1 : offset + resultData.length;
|
||||
if (derivedTotalKnown) countKeyRef.current = countKey;
|
||||
const staleKnownTotalForCurrentPage =
|
||||
!derivedTotalKnown &&
|
||||
pagination.totalKnown &&
|
||||
countKeyRef.current === countKey &&
|
||||
!isKnownTotalFreshForPage(pagination.total, minExpectedTotal);
|
||||
if (staleKnownTotalForCurrentPage) {
|
||||
countKeyRef.current = '';
|
||||
}
|
||||
latestConfigRef.current = config;
|
||||
latestDbTypeRef.current = dbTypeLower;
|
||||
latestDbNameRef.current = dbName;
|
||||
@@ -767,12 +779,9 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAct
|
||||
};
|
||||
}
|
||||
if (prev.totalKnown && countKeyRef.current === countKey) {
|
||||
if (!isDuckDB) {
|
||||
return { ...prev, current: currentPage, pageSize: size };
|
||||
}
|
||||
// 当当前页存在“下一页”信号时,已知总数至少应大于当前页末尾。
|
||||
// 若旧总数不满足该条件(例如历史统计值为 0),降级为未知总数并回退到 derivedTotal。
|
||||
if (Number.isFinite(prev.total) && prev.total >= minExpectedTotal) {
|
||||
// 若旧总数不满足该条件(例如清空表后又外部写入数据),降级为未知总数并重新统计。
|
||||
if (isKnownTotalFreshForPage(prev.total, minExpectedTotal)) {
|
||||
return { ...prev, current: currentPage, pageSize: size };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user