diff --git a/frontend/src/views/admin/ProxiesView.vue b/frontend/src/views/admin/ProxiesView.vue index 5772b785a1..978c042761 100644 --- a/frontend/src/views/admin/ProxiesView.vue +++ b/frontend/src/views/admin/ProxiesView.vue @@ -24,7 +24,7 @@ v-model="filters.protocol" :options="protocolOptions" :placeholder="t('admin.proxies.allProtocols')" - @change="loadProxies" + @change="handleFilterChange" />
@@ -32,7 +32,7 @@ v-model="filters.status" :options="statusOptions" :placeholder="t('admin.proxies.allStatus')" - @change="loadProxies" + @change="handleFilterChange" />
@@ -1221,6 +1221,11 @@ const loadProxies = async () => { } } +const handleFilterChange = () => { + pagination.page = 1 + loadProxies() +} + let searchTimeout: ReturnType const handleSearch = () => { clearTimeout(searchTimeout) diff --git a/frontend/src/views/admin/__tests__/ProxiesView.filters.spec.ts b/frontend/src/views/admin/__tests__/ProxiesView.filters.spec.ts new file mode 100644 index 0000000000..e9088e263a --- /dev/null +++ b/frontend/src/views/admin/__tests__/ProxiesView.filters.spec.ts @@ -0,0 +1,104 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { flushPromises, shallowMount } from '@vue/test-utils' +import ProxiesView from '../ProxiesView.vue' + +const { listProxies, getAllWithCount } = vi.hoisted(() => ({ + listProxies: vi.fn(), + getAllWithCount: vi.fn() +})) + +vi.mock('@/api/admin', () => ({ + adminAPI: { proxies: { list: listProxies, getAllWithCount } } +})) +vi.mock('@/stores/app', () => ({ useAppStore: () => ({ showError: vi.fn() }) })) +vi.mock('vue-i18n', async () => ({ + ...await vi.importActual('vue-i18n'), + useI18n: () => ({ t: (key: string) => key }) +})) + +const mountView = () => shallowMount(ProxiesView, { + global: { + stubs: { + AppLayout: { template: '
' }, + TablePageLayout: { + template: '
' + }, + DataTable: { + props: ['data'], + template: '
{{ data.map(row => row.name).join(",") }}
' + }, + Pagination: { + props: ['page'], + emits: ['update:page'], + template: '' + }, + Select: { + props: ['modelValue', 'options', 'placeholder'], + emits: ['update:modelValue', 'change'], + template: `` + } + } + } +}) + +let wrapper: ReturnType + +beforeEach(() => { + vi.clearAllMocks() + getAllWithCount.mockResolvedValue([]) + listProxies.mockResolvedValue({ items: [], total: 100, pages: 5 }) +}) + +afterEach(() => wrapper?.unmount()) + +describe('proxy list filter pagination', () => { + it.each([ + ['protocol', 'admin.proxies.allProtocols', 'socks5', ''], + ['protocol', 'admin.proxies.allProtocols', '', 'http'], + ['status', 'admin.proxies.allStatus', 'expired', ''], + ['status', 'admin.proxies.allStatus', '', 'active'] + ])('starts at page one when changing %s through %s to "%s"', async (field, placeholder, value, initial) => { + wrapper = mountView() + await flushPromises() + const filter = wrapper.get(`select[data-filter="${placeholder}"]`) + if (initial) { + await filter.setValue(initial) + await flushPromises() + } + await wrapper.get('[data-test="page"]').trigger('click') + await flushPromises() + expect(listProxies.mock.lastCall?.[0]).toBe(2) + const [, pageSize, previousFilters] = listProxies.mock.lastCall! + listProxies.mockImplementation(async (page) => ({ + items: page === 1 ? [{ id: 1, name: 'matching-proxy' }] : [], + total: 1, + pages: 1 + })) + + await filter.setValue(value) + await flushPromises() + + expect(listProxies).toHaveBeenLastCalledWith( + 1, pageSize, { ...previousFilters, [field]: value || undefined }, + { signal: expect.any(AbortSignal) } + ) + expect(wrapper.get('[data-test="page"]').text()).toBe('1') + expect(wrapper.get('[data-test="rows"]').text()).toBe('matching-proxy') + }) + + it('keeps the current page when refreshing the same filter', async () => { + wrapper = mountView() + await flushPromises() + await wrapper.get('[data-test="page"]').trigger('click') + await flushPromises() + + await wrapper.get('button[title="common.refresh"]').trigger('click') + await flushPromises() + + expect(listProxies.mock.lastCall?.[0]).toBe(2) + expect(wrapper.get('[data-test="page"]').text()).toBe('2') + }) +})