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: '月报' }, }); }); });