309 lines
8.6 KiB
TypeScript
309 lines
8.6 KiB
TypeScript
import { flushPromises, mount } from '@vue/test-utils';
|
|
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import PageData from './PageData.vue';
|
|
|
|
describe('page data recovery', () => {
|
|
it('loads the restored page and query without requesting the first page', async () => {
|
|
const get = vi
|
|
.fn()
|
|
.mockResolvedValue({ data: { records: [], totalRow: 50 } });
|
|
const wrapper = mount(PageData, {
|
|
global: {
|
|
directives: { loading: {} },
|
|
},
|
|
props: {
|
|
initialPageNumber: 3,
|
|
initialQueryParams: { categoryId: '7', title: '月报' },
|
|
pageSize: 18,
|
|
pageUrl: '/page',
|
|
requestClient: { get },
|
|
},
|
|
});
|
|
|
|
await flushPromises();
|
|
|
|
expect(get).toHaveBeenCalledTimes(1);
|
|
expect(get).toHaveBeenCalledWith('/page', {
|
|
params: {
|
|
categoryId: '7',
|
|
pageNumber: 3,
|
|
pageSize: 18,
|
|
title: '月报',
|
|
},
|
|
});
|
|
expect((wrapper.vm as any).getPageState()).toEqual({
|
|
pageNumber: 3,
|
|
pageSize: 18,
|
|
});
|
|
});
|
|
|
|
it('shows a retry action after a page request fails', async () => {
|
|
const get = vi
|
|
.fn()
|
|
.mockRejectedValueOnce(new Error('network unavailable'))
|
|
.mockResolvedValueOnce({ data: { records: [], totalRow: 0 } });
|
|
const wrapper = mount(PageData, {
|
|
global: {
|
|
directives: { loading: {} },
|
|
},
|
|
props: { pageUrl: '/page', requestClient: { get } },
|
|
});
|
|
|
|
await flushPromises();
|
|
expect(wrapper.text()).toContain('数据加载失败,请重试');
|
|
|
|
await wrapper.get('button').trigger('click');
|
|
await flushPromises();
|
|
expect(get).toHaveBeenCalledTimes(2);
|
|
expect(wrapper.text()).not.toContain('数据加载失败,请重试');
|
|
});
|
|
|
|
it('contracts an out-of-range page to the latest page with one retry', async () => {
|
|
const get = vi
|
|
.fn()
|
|
.mockResolvedValueOnce({ data: { records: [], totalRow: 21 } })
|
|
.mockResolvedValueOnce({
|
|
data: { records: [{ id: 'last-record' }], totalRow: 21 },
|
|
});
|
|
const wrapper = mount(PageData, {
|
|
global: {
|
|
directives: { loading: {} },
|
|
},
|
|
props: {
|
|
initialPageNumber: 4,
|
|
pageSize: 10,
|
|
pageUrl: '/page',
|
|
requestClient: { get },
|
|
},
|
|
});
|
|
|
|
await flushPromises();
|
|
|
|
expect(get).toHaveBeenCalledTimes(2);
|
|
expect(get.mock.calls.map((call) => call[1].params.pageNumber)).toEqual([
|
|
4, 3,
|
|
]);
|
|
expect((wrapper.vm as any).getPageState()).toEqual({
|
|
pageNumber: 3,
|
|
pageSize: 10,
|
|
});
|
|
});
|
|
|
|
it('coalesces repeated reloads while a page request is still running', async () => {
|
|
let resolveInitialRequest: (value: {
|
|
data: { records: never[]; totalRow: number };
|
|
}) => void = () => {};
|
|
const initialRequest = new Promise<{
|
|
data: { records: never[]; totalRow: number };
|
|
}>((resolve) => {
|
|
resolveInitialRequest = resolve;
|
|
});
|
|
const get = vi
|
|
.fn()
|
|
.mockReturnValueOnce(initialRequest)
|
|
.mockResolvedValue({ data: { records: [], totalRow: 0 } });
|
|
const wrapper = mount(PageData, {
|
|
global: {
|
|
directives: { loading: {} },
|
|
},
|
|
props: { pageUrl: '/page', requestClient: { get } },
|
|
});
|
|
|
|
const firstReload = (wrapper.vm as any).reload({ silent: true });
|
|
const secondReload = (wrapper.vm as any).reload({ silent: true });
|
|
|
|
expect(get).toHaveBeenCalledTimes(1);
|
|
resolveInitialRequest({ data: { records: [], totalRow: 0 } });
|
|
await Promise.all([firstReload, secondReload]);
|
|
await flushPromises();
|
|
|
|
expect(get).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('does not let a lightweight refresh supersede an active page query', async () => {
|
|
let resolveInitialRequest: (value: {
|
|
data: { records: { id: string }[]; totalRow: number };
|
|
}) => void = () => {};
|
|
const initialRequest = new Promise<{
|
|
data: { records: { id: string }[]; totalRow: number };
|
|
}>((resolve) => {
|
|
resolveInitialRequest = resolve;
|
|
});
|
|
const get = vi.fn().mockReturnValueOnce(initialRequest);
|
|
const wrapper = mount(PageData, {
|
|
global: {
|
|
directives: { loading: {} },
|
|
},
|
|
props: {
|
|
pageUrl: '/page',
|
|
refreshUrl: '/refresh',
|
|
requestClient: { get },
|
|
},
|
|
});
|
|
|
|
const refreshRequest = (wrapper.vm as any).reload({
|
|
lightweight: true,
|
|
silent: true,
|
|
});
|
|
expect(get).toHaveBeenCalledTimes(1);
|
|
|
|
resolveInitialRequest({
|
|
data: { records: [{ id: 'counted-result' }], totalRow: 21 },
|
|
});
|
|
await refreshRequest;
|
|
await flushPromises();
|
|
|
|
expect(get).toHaveBeenCalledTimes(1);
|
|
expect(wrapper.emitted('loadSuccess')?.at(-1)?.[0]).toMatchObject({
|
|
lightweight: false,
|
|
recordCount: 1,
|
|
});
|
|
expect(wrapper.find('.el-pagination').exists()).toBe(true);
|
|
});
|
|
|
|
it('uses the lightweight refresh endpoint without replacing the total', async () => {
|
|
const get = vi
|
|
.fn()
|
|
.mockResolvedValueOnce({
|
|
data: { records: [{ id: 'initial' }], totalRow: 35 },
|
|
})
|
|
.mockResolvedValueOnce({ data: [{ id: 'latest' }] });
|
|
const wrapper = mount(PageData, {
|
|
global: {
|
|
directives: { loading: {} },
|
|
},
|
|
props: {
|
|
pageUrl: '/page',
|
|
refreshUrl: '/refresh',
|
|
requestClient: { get },
|
|
},
|
|
});
|
|
await flushPromises();
|
|
|
|
await (wrapper.vm as any).reload({ lightweight: true, silent: true });
|
|
await flushPromises();
|
|
|
|
expect(get).toHaveBeenLastCalledWith('/refresh', {
|
|
params: { pageNumber: 1, pageSize: 10 },
|
|
});
|
|
expect(wrapper.find('.el-pagination').exists()).toBe(true);
|
|
expect(wrapper.emitted('loadSuccess')?.at(-1)?.[0]).toMatchObject({
|
|
lightweight: true,
|
|
pageNumber: 1,
|
|
recordCount: 1,
|
|
});
|
|
});
|
|
|
|
it('falls back to the counted page endpoint outside the first page', async () => {
|
|
const get = vi
|
|
.fn()
|
|
.mockResolvedValue({ data: { records: [], totalRow: 30 } });
|
|
const wrapper = mount(PageData, {
|
|
global: {
|
|
directives: { loading: {} },
|
|
},
|
|
props: {
|
|
initialPageNumber: 2,
|
|
pageUrl: '/page',
|
|
refreshUrl: '/refresh',
|
|
requestClient: { get },
|
|
},
|
|
});
|
|
await flushPromises();
|
|
|
|
await (wrapper.vm as any).reload({ lightweight: true, silent: true });
|
|
await flushPromises();
|
|
|
|
expect(get).toHaveBeenLastCalledWith('/page', {
|
|
params: { pageNumber: 2, pageSize: 10 },
|
|
});
|
|
expect(wrapper.emitted('loadSuccess')?.at(-1)?.[0]).toMatchObject({
|
|
lightweight: false,
|
|
pageNumber: 2,
|
|
});
|
|
});
|
|
|
|
it('restores a mounted list to a new route state with one target request', async () => {
|
|
const get = vi
|
|
.fn()
|
|
.mockResolvedValue({ data: { records: [], totalRow: 100 } });
|
|
const wrapper = mount(PageData, {
|
|
global: {
|
|
directives: { loading: {} },
|
|
},
|
|
props: { pageUrl: '/page', requestClient: { get } },
|
|
});
|
|
await flushPromises();
|
|
|
|
expect(
|
|
(wrapper.vm as any).restoreState({
|
|
pageNumber: 4,
|
|
pageSize: 20,
|
|
queryParams: { title: '月报' },
|
|
}),
|
|
).toBe(true);
|
|
await flushPromises();
|
|
|
|
expect(get).toHaveBeenCalledTimes(2);
|
|
expect(get).toHaveBeenLastCalledWith('/page', {
|
|
params: { pageNumber: 4, pageSize: 20, title: '月报' },
|
|
});
|
|
});
|
|
|
|
it('does not request again when the restored state is unchanged', async () => {
|
|
const get = vi
|
|
.fn()
|
|
.mockResolvedValue({ data: { records: [], totalRow: 50 } });
|
|
const wrapper = mount(PageData, {
|
|
global: {
|
|
directives: { loading: {} },
|
|
},
|
|
props: {
|
|
initialPageNumber: 3,
|
|
initialQueryParams: { categoryId: undefined, title: '月报' },
|
|
pageSize: 20,
|
|
pageUrl: '/page',
|
|
requestClient: { get },
|
|
},
|
|
});
|
|
await flushPromises();
|
|
|
|
expect(
|
|
(wrapper.vm as any).restoreState({
|
|
pageNumber: 3,
|
|
pageSize: 20,
|
|
queryParams: { title: '月报' },
|
|
}),
|
|
).toBe(false);
|
|
await flushPromises();
|
|
|
|
expect(get).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('detaches query state from the caller reactive object', async () => {
|
|
const get = vi
|
|
.fn()
|
|
.mockResolvedValue({ data: { records: [], totalRow: 0 } });
|
|
const wrapper = mount(PageData, {
|
|
global: {
|
|
directives: { loading: {} },
|
|
},
|
|
props: { pageUrl: '/page', requestClient: { get } },
|
|
});
|
|
await flushPromises();
|
|
|
|
const query = { title: '月报' };
|
|
(wrapper.vm as any).setQuery(query);
|
|
await flushPromises();
|
|
query.title = '未提交草稿';
|
|
await flushPromises();
|
|
|
|
expect(get).toHaveBeenCalledTimes(2);
|
|
expect(get).toHaveBeenLastCalledWith('/page', {
|
|
params: { pageNumber: 1, pageSize: 10, title: '月报' },
|
|
});
|
|
});
|
|
});
|