mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(grid): refresh database-computed values after edits
This commit is contained in:
@@ -185,7 +185,7 @@ import { buildDataGridColumnLookupItems, dataGridColumnCommentFor, filterDataGri
|
||||
import { uniqueDataGridColumnOrderKeys } from "@/lib/dataGrid/dataGridColumnOrder";
|
||||
import { dataGridColumnLayoutScopeKey, TABLE_DATA_GRID_COLUMN_ORDER_CHANGED_EVENT, tableDataGridColumnOrderScopeKey } from "@/lib/dataGrid/dataGridColumnLayoutStorage";
|
||||
import { summarizeSelection } from "@/lib/dataGrid/gridSelection";
|
||||
import { captureDataGridSelection, restoreDataGridSelection, type PersistedDataGridSelection } from "@/lib/dataGrid/dataGridSelectionPersistence";
|
||||
import { captureDataGridSelection, restoreDataGridSelection, type CaptureDataGridSelectionOptions, type PersistedDataGridSelection } from "@/lib/dataGrid/dataGridSelectionPersistence";
|
||||
import { dataGridFrameCoversRow, dataGridSelectionEdgeMask, dataGridSelectionFrameKindAtCell, dataGridSelectionUsesOuterFrame, resolveDataGridSelectionFrames } from "@/lib/dataGrid/dataGridSelectionFrames";
|
||||
import {
|
||||
createDataGridCellContextMenuItems,
|
||||
@@ -472,6 +472,12 @@ let preservedSelectionOnNextResult: {
|
||||
selection: PersistedDataGridSelection;
|
||||
sourceResult: QueryResult;
|
||||
} | null = null;
|
||||
let preservedDetailsOnNextResult: {
|
||||
sideCell?: PersistedDataGridSelection;
|
||||
cellDialog?: PersistedDataGridSelection;
|
||||
rowDialog?: PersistedDataGridSelection;
|
||||
columnDialog?: PersistedDataGridSelection;
|
||||
} | null = null;
|
||||
|
||||
watch(
|
||||
() => props.result,
|
||||
@@ -3396,6 +3402,7 @@ const editor = useDataGridEditor({
|
||||
currentPage,
|
||||
cacheKey: computed(() => props.pendingStateKey ?? props.cacheKey),
|
||||
onResultPayloadMutated: () => queryStore.invalidateResultEstimateForPayload(props.result),
|
||||
prepareFullReload,
|
||||
emit,
|
||||
});
|
||||
|
||||
@@ -3808,19 +3815,23 @@ function resetInfiniteScrollState() {
|
||||
resetGridVerticalScroll(true);
|
||||
}
|
||||
|
||||
async function onToolbarRefresh() {
|
||||
if (transactionActive.value) {
|
||||
discardChanges();
|
||||
}
|
||||
// Reset infinite scroll state on refresh
|
||||
function prepareFullReload() {
|
||||
if (infiniteScrollEnabled.value) {
|
||||
resetInfiniteScrollState();
|
||||
}
|
||||
const selection = captureCurrentSelectionForRefresh();
|
||||
preservedSelectionOnNextResult = selection ? { selection, sourceResult: props.result } : null;
|
||||
preservedDetailsOnNextResult = captureDetailsForRefresh();
|
||||
preserveTransposeOnNextResult.value = showTranspose.value;
|
||||
isRefreshingData.value = true;
|
||||
beginDataGridNativeSelectionBlock(dataGridNativeSelectionBlockOwner);
|
||||
}
|
||||
|
||||
async function onToolbarRefresh() {
|
||||
if (transactionActive.value) {
|
||||
discardChanges();
|
||||
}
|
||||
prepareFullReload();
|
||||
emit("reload", props.sql, searchText.value, currentWhereInput(), currentOrderBy(), pageSize.value, (currentPage.value - 1) * pageSize.value, "refresh");
|
||||
}
|
||||
|
||||
@@ -4563,14 +4574,20 @@ const {
|
||||
isRowSelected,
|
||||
} = selection;
|
||||
|
||||
function captureCurrentSelectionForRefresh(): PersistedDataGridSelection | null {
|
||||
return captureDataGridSelection({
|
||||
function selectionCaptureBase(): Omit<CaptureDataGridSelectionOptions, "selectedRowIds" | "selectedColumnIndexes" | "selectedCellKeys" | "selectionAnchor" | "selectionFocus" | "selectingAll" | "lastClickedRowIndex"> {
|
||||
return {
|
||||
columns: props.result.columns,
|
||||
sourceColumns: props.sourceColumns,
|
||||
rows: props.result.rows,
|
||||
primaryKeys: props.tableMeta?.primaryKeys ?? [],
|
||||
visibleColumnIndexes: visibleColumnIndexes.value,
|
||||
displayItems: displayItems.value,
|
||||
};
|
||||
}
|
||||
|
||||
function captureCurrentSelectionForRefresh(): PersistedDataGridSelection | null {
|
||||
return captureDataGridSelection({
|
||||
...selectionCaptureBase(),
|
||||
selectedRowIds: selectedRowIds.value,
|
||||
selectedColumnIndexes: selectedColumnIndexes.value,
|
||||
selectedCellKeys: selectedCellKeys.value,
|
||||
@@ -4581,6 +4598,119 @@ function captureCurrentSelectionForRefresh(): PersistedDataGridSelection | null
|
||||
});
|
||||
}
|
||||
|
||||
function captureCellTargetForRefresh(target: { rowIndex: number; col: number } | null): PersistedDataGridSelection | undefined {
|
||||
if (!target) return undefined;
|
||||
const visibleColumnIndex = visibleColumnIndexes.value.indexOf(target.col);
|
||||
if (visibleColumnIndex < 0) return undefined;
|
||||
return (
|
||||
captureDataGridSelection({
|
||||
...selectionCaptureBase(),
|
||||
selectedRowIds: new Set(),
|
||||
selectedColumnIndexes: new Set(),
|
||||
selectedCellKeys: new Set(),
|
||||
selectionAnchor: { rowIndex: target.rowIndex, colIndex: visibleColumnIndex },
|
||||
selectionFocus: { rowIndex: target.rowIndex, colIndex: visibleColumnIndex },
|
||||
selectingAll: false,
|
||||
}) ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
function captureRowTargetForRefresh(rowId: number | null): PersistedDataGridSelection | undefined {
|
||||
if (rowId === null) return undefined;
|
||||
return (
|
||||
captureDataGridSelection({
|
||||
...selectionCaptureBase(),
|
||||
selectedRowIds: new Set([rowId]),
|
||||
selectedColumnIndexes: new Set(),
|
||||
selectedCellKeys: new Set(),
|
||||
selectionAnchor: null,
|
||||
selectionFocus: null,
|
||||
selectingAll: false,
|
||||
}) ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
function captureColumnTargetForRefresh(columnIndex: number | null): PersistedDataGridSelection | undefined {
|
||||
if (columnIndex === null) return undefined;
|
||||
const visibleColumnIndex = visibleColumnIndexes.value.indexOf(columnIndex);
|
||||
if (visibleColumnIndex < 0) return undefined;
|
||||
return (
|
||||
captureDataGridSelection({
|
||||
...selectionCaptureBase(),
|
||||
selectedRowIds: new Set(),
|
||||
selectedColumnIndexes: new Set([visibleColumnIndex]),
|
||||
selectedCellKeys: new Set(),
|
||||
selectionAnchor: null,
|
||||
selectionFocus: null,
|
||||
selectingAll: false,
|
||||
}) ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
function captureDetailsForRefresh() {
|
||||
const details = {
|
||||
sideCell: showCellDetail.value ? captureCellTargetForRefresh(detailCell.value) : undefined,
|
||||
cellDialog: cellDetailDialogOpen.value ? captureCellTargetForRefresh(cellDetailDialogTarget.value) : undefined,
|
||||
rowDialog: rowDetailDialogOpen.value ? captureRowTargetForRefresh(rowDetailDialogRowId.value) : undefined,
|
||||
columnDialog: columnDetailDialogOpen.value ? captureColumnTargetForRefresh(columnDetailDialogColumnIndex.value) : undefined,
|
||||
};
|
||||
return Object.values(details).some(Boolean) ? details : null;
|
||||
}
|
||||
|
||||
function restoreDetailCellAfterRefresh(snapshot: PersistedDataGridSelection | undefined): { rowIndex: number; col: number } | null {
|
||||
if (!snapshot) return null;
|
||||
const restored = restoreDataGridSelection({
|
||||
snapshot,
|
||||
columns: props.result.columns,
|
||||
sourceColumns: props.sourceColumns,
|
||||
rows: props.result.rows,
|
||||
visibleColumnIndexes: visibleColumnIndexes.value,
|
||||
displayItems: displayItems.value,
|
||||
});
|
||||
if (restored?.kind !== "range") return null;
|
||||
const columnIndex = visibleColumnIndexes.value[restored.focus.colIndex];
|
||||
return columnIndex === undefined ? null : { rowIndex: restored.focus.rowIndex, col: columnIndex };
|
||||
}
|
||||
|
||||
function restoreDetailsAfterRefresh(details: NonNullable<typeof preservedDetailsOnNextResult>) {
|
||||
const sideCell = restoreDetailCellAfterRefresh(details.sideCell);
|
||||
if (sideCell) {
|
||||
detailCell.value = sideCell;
|
||||
showCellDetail.value = true;
|
||||
hydrateCellDetailTarget(sideCell);
|
||||
}
|
||||
|
||||
const cellDialog = restoreDetailCellAfterRefresh(details.cellDialog);
|
||||
if (cellDialog) openCellDetailDialog(cellDialog.rowIndex, cellDialog.col);
|
||||
|
||||
if (details.rowDialog) {
|
||||
const restored = restoreDataGridSelection({
|
||||
snapshot: details.rowDialog,
|
||||
columns: props.result.columns,
|
||||
sourceColumns: props.sourceColumns,
|
||||
rows: props.result.rows,
|
||||
visibleColumnIndexes: visibleColumnIndexes.value,
|
||||
displayItems: displayItems.value,
|
||||
});
|
||||
if (restored?.kind === "rows" && restored.rowIds[0] !== undefined) openRowDetailDialog(restored.rowIds[0]);
|
||||
}
|
||||
|
||||
if (details.columnDialog) {
|
||||
const restored = restoreDataGridSelection({
|
||||
snapshot: details.columnDialog,
|
||||
columns: props.result.columns,
|
||||
sourceColumns: props.sourceColumns,
|
||||
rows: props.result.rows,
|
||||
visibleColumnIndexes: visibleColumnIndexes.value,
|
||||
displayItems: displayItems.value,
|
||||
});
|
||||
if (restored?.kind === "columns") {
|
||||
const columnIndex = visibleColumnIndexes.value[restored.columnIndexes[0] ?? -1];
|
||||
if (columnIndex !== undefined) openColumnDetailDialog(columnIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function restoreSelectionAfterRefresh(snapshot: PersistedDataGridSelection) {
|
||||
const restored = restoreDataGridSelection({
|
||||
snapshot,
|
||||
@@ -8634,6 +8764,8 @@ watch(
|
||||
(result, previousResult) => {
|
||||
const selectionSnapshot = preservedSelectionOnNextResult?.selection;
|
||||
preservedSelectionOnNextResult = null;
|
||||
const detailsSnapshot = preservedDetailsOnNextResult;
|
||||
preservedDetailsOnNextResult = null;
|
||||
const shouldPreserveTranspose = preserveTransposeOnNextResult.value;
|
||||
preserveTransposeOnNextResult.value = false;
|
||||
if (isDataGridPrefixAppend(previousResult, result)) return;
|
||||
@@ -8654,6 +8786,7 @@ watch(
|
||||
}
|
||||
exitTransaction();
|
||||
if (selectionSnapshot) restoreSelectionAfterRefresh(selectionSnapshot);
|
||||
if (detailsSnapshot) restoreDetailsAfterRefresh(detailsSnapshot);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dataGridSource = readFileSync(new URL("../DataGrid.vue", import.meta.url), "utf8");
|
||||
|
||||
function functionSource(name: string, nextName: string): string {
|
||||
const start = dataGridSource.indexOf(`function ${name}`);
|
||||
const relativeEnd = dataGridSource.slice(start).search(new RegExp(`\\n(?:async\\s+)?function\\s+${nextName}\\b`));
|
||||
expect(start).toBeGreaterThanOrEqual(0);
|
||||
expect(relativeEnd).toBeGreaterThan(0);
|
||||
return dataGridSource.slice(start, start + relativeEnd);
|
||||
}
|
||||
|
||||
describe("DataGrid save reload integration", () => {
|
||||
it("shares the toolbar full-reload preparation with editor saves", () => {
|
||||
const prepareSource = functionSource("prepareFullReload", "onToolbarRefresh");
|
||||
const toolbarSource = functionSource("onToolbarRefresh", "setAutoRefreshInterval");
|
||||
|
||||
expect(dataGridSource).toContain("prepareFullReload,\n emit,");
|
||||
expect(toolbarSource).toContain("prepareFullReload();");
|
||||
expect(prepareSource).toContain("resetInfiniteScrollState();");
|
||||
expect(prepareSource).toContain("captureCurrentSelectionForRefresh();");
|
||||
expect(prepareSource).toContain("preservedSelectionOnNextResult = selection ? { selection, sourceResult: props.result } : null;");
|
||||
expect(prepareSource).toContain("preservedDetailsOnNextResult = captureDetailsForRefresh();");
|
||||
expect(prepareSource).toContain("preserveTransposeOnNextResult.value = showTranspose.value;");
|
||||
expect(dataGridSource).toContain("if (detailsSnapshot) restoreDetailsAfterRefresh(detailsSnapshot);");
|
||||
});
|
||||
|
||||
it("resets accumulated infinite-scroll pagination before reloading from offset zero", () => {
|
||||
const resetSource = functionSource("resetInfiniteScrollState", "prepareFullReload");
|
||||
const nextPageSource = functionSource("infiniteScrollNextPage", "checkInfiniteScroll");
|
||||
|
||||
expect(resetSource).toContain("currentPage.value = 1;");
|
||||
expect(resetSource).toContain("lastInfiniteScrollPage = 0;");
|
||||
expect(resetSource).toContain("infiniteScrollRequestedOffset = undefined;");
|
||||
expect(resetSource).toContain("infiniteScrollRequestedLimit = undefined;");
|
||||
expect(nextPageSource).toContain("const nextOffset = props.result.rows.length;");
|
||||
expect(nextPageSource).toContain('emit("paginate", nextOffset, nextLimit, currentWhereInput(), currentOrderBy());');
|
||||
});
|
||||
});
|
||||
@@ -1,18 +1,26 @@
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, ref, type Ref } from "vue";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { clearDataGridPendingSnapshot, DATA_GRID_QUICK_ENTRY_DRAFT_ROW_ID, useDataGridEditor } from "@/composables/useDataGridEditor";
|
||||
import type { CellValue } from "@/lib/dataGrid/cellValue";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getConfig: vi.fn(),
|
||||
prepareDataGridSave: vi.fn(),
|
||||
executeBatch: vi.fn(),
|
||||
executeInTransaction: vi.fn(),
|
||||
addHistory: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/backend/api", () => ({}));
|
||||
vi.mock("@/lib/backend/api", () => ({
|
||||
prepareDataGridSave: mocks.prepareDataGridSave,
|
||||
executeBatch: mocks.executeBatch,
|
||||
executeInTransaction: mocks.executeInTransaction,
|
||||
}));
|
||||
vi.mock("@/stores/connectionStore", () => ({
|
||||
useConnectionStore: () => ({ getConfig: mocks.getConfig }),
|
||||
}));
|
||||
vi.mock("@/stores/historyStore", () => ({
|
||||
useHistoryStore: () => ({}),
|
||||
useHistoryStore: () => ({ add: mocks.addHistory }),
|
||||
}));
|
||||
vi.mock("@/stores/productionSafetyStore", () => ({
|
||||
useProductionSafetyStore: () => ({}),
|
||||
@@ -294,3 +302,123 @@ describe("useDataGridEditor appendPastedRowsToNewRow", () => {
|
||||
expect(editor.newRows.value[1]).toEqual(["Ada", "full payload", "Lovelace"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("useDataGridEditor saveChanges reload", () => {
|
||||
beforeEach(() => {
|
||||
mocks.prepareDataGridSave.mockReset();
|
||||
mocks.executeBatch.mockReset();
|
||||
mocks.executeInTransaction.mockReset();
|
||||
mocks.addHistory.mockReset();
|
||||
mocks.getConfig.mockReset();
|
||||
});
|
||||
|
||||
function createSaveTestEditor(options: { currentPage?: Ref<number>; prepareFullReload?: () => void; customSaveHandler?: { save: ReturnType<typeof vi.fn> } } = {}) {
|
||||
const emit = vi.fn();
|
||||
const currentPage = options.currentPage ?? ref(1);
|
||||
const result = ref<{ columns: string[]; rows: CellValue[][] }>({
|
||||
columns: ["id", "status"],
|
||||
rows: [
|
||||
[1, "pending"],
|
||||
[2, "pending"],
|
||||
],
|
||||
});
|
||||
const editor = useDataGridEditor({
|
||||
result: computed(() => result.value),
|
||||
editable: computed(() => true),
|
||||
databaseType: computed(() => "mysql"),
|
||||
connectionId: computed(() => "connection-1"),
|
||||
database: computed(() => "app"),
|
||||
tableMeta: computed(() => ({
|
||||
tableName: "orders_test",
|
||||
columns: [
|
||||
{ name: "id", data_type: "int" },
|
||||
{ name: "status", data_type: "varchar" },
|
||||
],
|
||||
primaryKeys: ["id"],
|
||||
})),
|
||||
sourceColumns: computed(() => undefined),
|
||||
onExecuteSql: computed(() => undefined),
|
||||
customSaveHandler: computed(() => options.customSaveHandler),
|
||||
sql: computed(() => undefined),
|
||||
searchText: ref(""),
|
||||
whereFilterInput: ref(""),
|
||||
currentWhereInput: computed(() => undefined),
|
||||
orderByInput: ref(""),
|
||||
rowStatusFilter: ref("all"),
|
||||
confirmDangerousRowDeletion: computed(() => true),
|
||||
pageSize: ref(100),
|
||||
currentPage,
|
||||
cacheKey: computed(() => undefined),
|
||||
getRowItem: () => undefined,
|
||||
prepareFullReload: options.prepareFullReload,
|
||||
emit,
|
||||
});
|
||||
return { editor, emit, currentPage };
|
||||
}
|
||||
|
||||
it("reloads after a pure row update, so database-computed columns (e.g. ON UPDATE CURRENT_TIMESTAMP) refresh without a manual page reload", async () => {
|
||||
mocks.prepareDataGridSave.mockResolvedValue({ statements: ["UPDATE orders_test SET status='shipped' WHERE id=1"], rollbackStatements: [] });
|
||||
mocks.executeBatch.mockResolvedValue({ affected_rows: 1 });
|
||||
|
||||
const { editor, emit } = createSaveTestEditor();
|
||||
editor.dirtyRows.value.set(0, new Map([[1, "shipped"]]));
|
||||
|
||||
await editor.saveChanges();
|
||||
|
||||
expect(mocks.executeBatch).toHaveBeenCalledTimes(1);
|
||||
expect(emit).toHaveBeenCalledWith("reload", undefined, "", undefined, undefined, 100, 0);
|
||||
});
|
||||
|
||||
it("does not reload when there are no pending changes to save", async () => {
|
||||
const { editor, emit } = createSaveTestEditor();
|
||||
|
||||
await editor.saveChanges();
|
||||
|
||||
expect(mocks.prepareDataGridSave).not.toHaveBeenCalled();
|
||||
expect(emit).not.toHaveBeenCalledWith("reload", expect.anything());
|
||||
});
|
||||
|
||||
it("prepares one first-page reload after saving edits from three accumulated infinite-scroll pages", async () => {
|
||||
mocks.prepareDataGridSave.mockResolvedValue({
|
||||
statements: ["UPDATE orders_test SET status='shipped' WHERE id=1", "UPDATE orders_test SET status='cancelled' WHERE id=2"],
|
||||
rollbackStatements: [],
|
||||
});
|
||||
mocks.executeInTransaction.mockResolvedValue({ affected_rows: 2 });
|
||||
const infiniteScrollState = {
|
||||
lastPage: 3,
|
||||
requestedOffset: 200 as number | undefined,
|
||||
requestedLimit: 100 as number | undefined,
|
||||
};
|
||||
const currentPage = ref(3);
|
||||
const prepareFullReload = vi.fn(() => {
|
||||
currentPage.value = 1;
|
||||
infiniteScrollState.lastPage = 0;
|
||||
infiniteScrollState.requestedOffset = undefined;
|
||||
infiniteScrollState.requestedLimit = undefined;
|
||||
});
|
||||
const created = createSaveTestEditor({ currentPage, prepareFullReload });
|
||||
created.editor.dirtyRows.value.set(0, new Map([[1, "shipped"]]));
|
||||
created.editor.dirtyRows.value.set(1, new Map([[1, "cancelled"]]));
|
||||
|
||||
await created.editor.saveChanges();
|
||||
|
||||
expect(mocks.executeInTransaction).toHaveBeenCalledTimes(1);
|
||||
expect(prepareFullReload).toHaveBeenCalledTimes(1);
|
||||
expect(infiniteScrollState).toEqual({ lastPage: 0, requestedOffset: undefined, requestedLimit: undefined });
|
||||
expect(created.emit).toHaveBeenCalledTimes(1);
|
||||
expect(created.emit).toHaveBeenCalledWith("reload", undefined, "", undefined, undefined, 100, 0);
|
||||
});
|
||||
|
||||
it("keeps the custom save path from reloading after a pure update", async () => {
|
||||
const customSave = vi.fn().mockResolvedValue(undefined);
|
||||
const prepareFullReload = vi.fn();
|
||||
const { editor, emit } = createSaveTestEditor({ customSaveHandler: { save: customSave }, prepareFullReload });
|
||||
editor.dirtyRows.value.set(0, new Map([[1, "shipped"]]));
|
||||
|
||||
await editor.saveChanges();
|
||||
|
||||
expect(customSave).toHaveBeenCalledTimes(1);
|
||||
expect(prepareFullReload).not.toHaveBeenCalled();
|
||||
expect(emit).not.toHaveBeenCalledWith("reload", expect.anything());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,6 +104,7 @@ export interface UseDataGridEditorOptions {
|
||||
cacheKey?: ComputedRef<string | undefined>;
|
||||
/** 保存成功后结果负载被原地修改时通知宿主,使缓存的字节估算失效。 */
|
||||
onResultPayloadMutated?: () => void;
|
||||
prepareFullReload?: () => void;
|
||||
emit: {
|
||||
(event: "reload", sql?: string, searchText?: string, whereInput?: string, orderBy?: string, limit?: number, offset?: number): void;
|
||||
};
|
||||
@@ -1453,6 +1454,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
||||
}
|
||||
|
||||
function reloadCurrentData() {
|
||||
options.prepareFullReload?.();
|
||||
options.emit("reload", sql.value, searchText.value, options.currentWhereInput.value, orderByInput.value.trim() || undefined, pageSize.value, (currentPage.value - 1) * pageSize.value);
|
||||
}
|
||||
|
||||
@@ -1490,6 +1492,10 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
||||
isSaving.value = true;
|
||||
snapshot.newRowRefs.forEach((row) => savingNewRows.add(row));
|
||||
const shouldReloadAfterSave = snapshot.newRows.length > 0 || snapshot.deletedRows.size > 0;
|
||||
// SQL saves may update columns the client can't predict (e.g. an ON UPDATE CURRENT_TIMESTAMP
|
||||
// column or a trigger), so reload after a pure row update too. Custom (non-SQL) data sources
|
||||
// keep the original behavior below, unchanged.
|
||||
const shouldReloadAfterSqlSave = shouldReloadAfterSave || snapshot.dirtyRows.size > 0;
|
||||
|
||||
if (customHandler) {
|
||||
try {
|
||||
@@ -1609,7 +1615,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
||||
clearSavedPendingChanges(snapshot);
|
||||
if (!hasPendingChanges.value) exitTransaction();
|
||||
clearPendingChangeHistory();
|
||||
if (shouldReloadAfterSave) {
|
||||
if (shouldReloadAfterSqlSave) {
|
||||
reloadCurrentData();
|
||||
}
|
||||
await finishSaveChanges(snapshot);
|
||||
|
||||
@@ -141,6 +141,19 @@ describe("data grid selection persistence", () => {
|
||||
expect(restore(columnSnapshot, { visibleColumnIndexes: [2, 0] })).toEqual({ kind: "columns", columnIndexes: [1, 0] });
|
||||
});
|
||||
|
||||
it("restores primary-key row and cell selections after an edited-row reload reorders results", () => {
|
||||
const rowSnapshot = captureDataGridSelection(baseOptions({ selectedRowIds: new Set([1]), lastClickedRowIndex: 1 }))!;
|
||||
const cellSnapshot = captureDataGridSelection(baseOptions({ selectionAnchor: { rowIndex: 1, colIndex: 2 }, selectionFocus: { rowIndex: 1, colIndex: 2 } }))!;
|
||||
const refreshedRows = [
|
||||
[3, 20, "Linus"],
|
||||
[1, 10, "Ada"],
|
||||
[2, 10, "Grace updated"],
|
||||
];
|
||||
|
||||
expect(restore(rowSnapshot, { rows: refreshedRows })).toEqual({ kind: "rows", rowIds: [2], anchorRowIndex: 2, scrollRowIndex: 2 });
|
||||
expect(restore(cellSnapshot, { rows: refreshedRows })).toEqual({ kind: "range", anchor: { rowIndex: 2, colIndex: 2 }, focus: { rowIndex: 2, colIndex: 2 }, selectingAll: false, scrollRowIndex: 2 });
|
||||
});
|
||||
|
||||
it("keeps select-all semantics when the refreshed result gains rows", () => {
|
||||
const snapshot = captureDataGridSelection(baseOptions({ selectionAnchor: { rowIndex: 0, colIndex: 0 }, selectionFocus: { rowIndex: 2, colIndex: 2 }, selectingAll: true }))!;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user