fix(grid): refresh database-computed values after edits
This commit is contained in:
1 parent
b3b6d29099
commit
5acdbfa61e
5 files changed
+332
-12
No files matched your search
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user