Browse Source

fix(@vben/plugins): apply default sort on initial grid load

pull/8344/head
wenbin-ding 4 weeks ago
parent
commit
e2a069bde0
  1. 160
      packages/effects/plugins/src/vxe-table/__tests__/use-vxe-grid.reactivity.test.ts
  2. 32
      packages/effects/plugins/src/vxe-table/use-vxe-grid.vue

160
packages/effects/plugins/src/vxe-table/__tests__/use-vxe-grid.reactivity.test.ts

@ -2,14 +2,30 @@ import type { App } from 'vue';
import { createApp, defineComponent, h, nextTick, ref } from 'vue';
import { describe, expect, it, vi } from 'vitest';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import VbenVxeGrid from '../use-vxe-grid.vue';
const mockRefs = vi.hoisted(() => ({
gridOptions: undefined as any,
isMobile: undefined as any,
}));
const observedColumns = vi.hoisted(() => [] as unknown[]);
const observedToolbarConfigs = vi.hoisted(() => [] as unknown[]);
const extendProxyOptionsMock = vi.hoisted(() => vi.fn());
vi.mock('@vben/hooks', async () => {
const { ref } = await import('vue');
const gridOptions = ref({
columns: [
{
field: 'operation',
fixed: 'right',
width: 'auto',
},
],
});
mockRefs.gridOptions = gridOptions;
return {
usePriorityValues: () => ({
@ -17,15 +33,7 @@ vi.mock('@vben/hooks', async () => {
formOptions: ref(undefined),
gridClass: ref(undefined),
gridEvents: ref(undefined),
gridOptions: ref({
columns: [
{
field: 'operation',
fixed: 'right',
width: 'auto',
},
],
}),
gridOptions,
separator: ref(undefined),
showSearchForm: ref(true),
tableData: ref(undefined),
@ -38,7 +46,9 @@ vi.mock('@vben/hooks', async () => {
vi.mock('@vben/preferences', async () => {
const { ref } = await import('vue');
return { usePreferences: () => ({ isMobile: ref(false) }) };
const isMobile = ref(false);
mockRefs.isMobile = isMobile;
return { usePreferences: () => ({ isMobile }) };
});
vi.mock('@vben/icons', async () => {
@ -73,6 +83,7 @@ vi.mock('vxe-table', async () => {
inheritAttrs: false,
setup(_, { attrs, slots }) {
return () => {
observedColumns.push(attrs.columns);
observedToolbarConfigs.push(attrs.toolbarConfig);
return h('div', [
slots['toolbar-actions']?.({}),
@ -85,7 +96,9 @@ vi.mock('vxe-table', async () => {
};
});
vi.mock('../extends', () => ({ extendProxyOptions: vi.fn() }));
vi.mock('../extends', () => ({
extendProxyOptions: extendProxyOptionsMock,
}));
vi.mock('../init', async () => {
const { defineComponent, h } = await import('vue');
@ -110,21 +123,46 @@ vi.mock('../viewed-row', () => ({
useViewedRow: vi.fn(),
}));
describe('vben vxe grid toolbar slots', () => {
function createGridApi(commitProxy = vi.fn()) {
return {
grid: { commitProxy },
mount: vi.fn(),
reload: vi.fn(),
setState: vi.fn(),
toggleSearchForm: vi.fn(),
unmount: vi.fn(),
useStore: vi.fn(() => ({})),
};
}
async function flushGridInit() {
await nextTick();
await nextTick();
await nextTick();
}
describe('vben vxe grid reactivity', () => {
beforeEach(() => {
mockRefs.gridOptions.value = {
columns: [
{
field: 'operation',
fixed: 'right',
width: 'auto',
},
],
};
mockRefs.isMobile.value = false;
observedColumns.length = 0;
observedToolbarConfigs.length = 0;
extendProxyOptionsMock.mockClear();
});
it.each(['table-title', 'toolbar-actions', 'toolbar-tools'])(
'keeps toolbar options stable when the %s slot updates',
async (slotName) => {
observedToolbarConfigs.length = 0;
const loading = ref(false);
const api = {
grid: { commitProxy: vi.fn() },
mount: vi.fn(),
reload: vi.fn(),
setState: vi.fn(),
toggleSearchForm: vi.fn(),
unmount: vi.fn(),
useStore: vi.fn(() => ({})),
};
const api = createGridApi();
const Consumer = defineComponent(
() => () =>
h(VbenVxeGrid, { api } as any, {
@ -164,4 +202,80 @@ describe('vben vxe grid toolbar slots', () => {
}
},
);
it('keeps columns stable when unrelated grid options update', async () => {
mockRefs.gridOptions.value = {
columns: [{ field: 'name' }],
pagerConfig: {},
};
const api = createGridApi();
const Consumer = defineComponent(
() => () => h(VbenVxeGrid, { api } as any),
);
const host = document.createElement('div');
document.body.append(host);
const app: App = createApp(Consumer);
try {
app.mount(host);
await flushGridInit();
const initialColumns = observedColumns.at(-1);
mockRefs.isMobile.value = true;
await nextTick();
await nextTick();
expect(observedColumns.at(-1)).toBe(initialColumns);
} finally {
app.unmount();
host.remove();
}
});
it('uses the initial action for the first proxy load', async () => {
mockRefs.gridOptions.value = {
columns: [{ field: 'name', sortable: true }],
proxyConfig: {
ajax: { query: vi.fn() },
autoLoad: true,
},
sortConfig: {
defaultSort: { field: 'name', order: 'asc' },
},
};
const commitProxy = vi.fn();
const api = createGridApi(commitProxy);
const Consumer = defineComponent(
() => () => h(VbenVxeGrid, { api } as any),
);
const host = document.createElement('div');
document.body.append(host);
const app: App = createApp(Consumer);
try {
app.mount(host);
await flushGridInit();
expect(api.setState).toHaveBeenCalledOnce();
expect(extendProxyOptionsMock).toHaveBeenCalledOnce();
expect(commitProxy).toHaveBeenCalledOnce();
expect(commitProxy).toHaveBeenCalledWith('initial', {});
const setStateCallOrder = api.setState.mock.invocationCallOrder[0];
const extendProxyCallOrder =
extendProxyOptionsMock.mock.invocationCallOrder[0];
const commitProxyCallOrder = commitProxy.mock.invocationCallOrder[0];
if (
setStateCallOrder === undefined ||
extendProxyCallOrder === undefined ||
commitProxyCallOrder === undefined
) {
throw new Error('Expected all initialization calls to be recorded');
}
expect(setStateCallOrder).toBeLessThan(commitProxyCallOrder);
expect(extendProxyCallOrder).toBeLessThan(commitProxyCallOrder);
} finally {
app.unmount();
host.remove();
}
});
});

32
packages/effects/plugins/src/vxe-table/use-vxe-grid.vue

@ -203,14 +203,17 @@ const toolbarOptions = computed(() => {
const options = computed(() => {
const globalGridConfig = VxeUI?.getConfig()?.grid ?? {};
const mergedOptions: VxeTableGridProps = cloneDeep(
mergeWithArrayOverride(
{},
toRaw(toolbarOptions.value),
toRaw(gridOptions.value),
globalGridConfig,
),
const rawMergedOptions: VxeTableGridProps = mergeWithArrayOverride(
{},
toRaw(toolbarOptions.value),
toRaw(gridOptions.value),
globalGridConfig,
);
const mergedOptions: VxeTableGridProps = cloneDeep(rawMergedOptions);
// Keep the columns reference stable when unrelated reactive options change.
// Otherwise VXE reloads the columns and loses its initialized sort state.
mergedOptions.columns = rawMergedOptions.columns;
if (mergedOptions.proxyConfig) {
const { ajax } = mergedOptions.proxyConfig;
@ -332,12 +335,6 @@ async function init() {
// 内部主动加载数据,防止form的默认值影响
const autoLoad = defaultGridOptions.proxyConfig?.autoLoad;
const enableProxyConfig = options.value.proxyConfig?.enabled;
if (enableProxyConfig && autoLoad) {
props.api.grid.commitProxy?.(
'query',
formOptions.value ? ((await formApi.getValues()) ?? {}) : {},
);
}
// form 由 vben-form代替,所以不适配formConfig,这里给出警告
const formConfig = gridOptions.value?.formConfig;
@ -353,6 +350,15 @@ async function init() {
extendProxyOptions(props.api, defaultGridOptions, () =>
formApi.getLatestSubmissionValues(),
);
if (enableProxyConfig && autoLoad) {
// Wait for VXE to receive the latest options before applying default sort.
await nextTick();
props.api.grid.commitProxy?.(
'initial',
formOptions.value ? ((await formApi.getValues()) ?? {}) : {},
);
}
}
// formOptions支持响应式

Loading…
Cancel
Save