From aac54618a3a8ff143e5e6c5ca96d656863f61f80 Mon Sep 17 00:00:00 2001 From: liuhy Date: Fri, 24 Jul 2026 22:16:02 -0700 Subject: [PATCH] Preserve topic table pagination --- .../instance/__tests__/TopicPage.test.tsx | 123 ++++++++++++++++++ web/src/pages/instance/topic.tsx | 36 ++++- 2 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 web/src/pages/instance/__tests__/TopicPage.test.tsx diff --git a/web/src/pages/instance/__tests__/TopicPage.test.tsx b/web/src/pages/instance/__tests__/TopicPage.test.tsx new file mode 100644 index 00000000..701afd10 --- /dev/null +++ b/web/src/pages/instance/__tests__/TopicPage.test.tsx @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from 'vitest'; +import { render, screen, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { App } from 'antd'; +import { LangProvider } from '../../../i18n/LangContext'; +import type { Topic } from '../../../api/metadata'; +import TopicPage from '../topic'; + +const topicServiceMocks = vi.hoisted(() => ({ + batchDeleteTopics: vi.fn(), + createTopic: vi.fn(), + deleteTopic: vi.fn(), + getTopicConsumers: vi.fn(), + getTopicRoutes: vi.fn(), + listTopics: vi.fn(), + sendTopicMessage: vi.fn(), +})); + +vi.mock('../../../services/topicService', () => topicServiceMocks); + +beforeAll(() => { + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation((query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }); +}); + +const buildTopics = (count: number): Topic[] => + Array.from({ length: count }, (_, index) => { + const suffix = String(index + 1).padStart(2, '0'); + return { + name: `topic-${suffix}`, + namespace: 'default', + type: 'NORMAL', + clusterId: 'rmq-cn-v5-prod-01', + writeQueues: 8, + readQueues: 8, + perm: 'RW', + messageCount: index, + tps: index, + consumerGroupCount: 0, + remark: `Topic ${suffix}`, + createdAt: '2026-01-01T00:00:00Z', + updatedAt: '2026-01-01T00:00:00Z', + }; + }); + +const renderWithProviders = () => + render( + + + + + , + ); + +const getTableBody = () => { + const tableBody = document.querySelector('.ant-table-tbody'); + expect(tableBody).not.toBeNull(); + return tableBody as HTMLElement; +}; + +describe('TopicPage', () => { + beforeEach(() => { + topicServiceMocks.listTopics.mockResolvedValue(buildTopics(25)); + topicServiceMocks.getTopicRoutes.mockResolvedValue([]); + topicServiceMocks.getTopicConsumers.mockResolvedValue([]); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('keeps the current table page after opening and closing topic details', async () => { + const user = userEvent.setup(); + renderWithProviders(); + + expect(await screen.findByText('topic-01')).toBeInTheDocument(); + + const secondPage = document.querySelector('.ant-pagination-item-2'); + expect(secondPage).not.toBeNull(); + await user.click(secondPage as HTMLElement); + + await waitFor(() => expect(within(getTableBody()).getByText('topic-21')).toBeInTheDocument()); + expect(within(getTableBody()).queryByText('topic-01')).not.toBeInTheDocument(); + + await user.click(screen.getAllByRole('button', { name: /详情/ })[0]); + await waitFor(() => expect(topicServiceMocks.getTopicRoutes).toHaveBeenCalledWith('topic-21')); + + const closeButton = document.querySelector('.ant-modal-close'); + expect(closeButton).not.toBeNull(); + await user.click(closeButton as HTMLElement); + + expect(within(getTableBody()).getByText('topic-21')).toBeInTheDocument(); + expect(within(getTableBody()).queryByText('topic-01')).not.toBeInTheDocument(); + }); +}); diff --git a/web/src/pages/instance/topic.tsx b/web/src/pages/instance/topic.tsx index 4569bfac..b3d9cfeb 100644 --- a/web/src/pages/instance/topic.tsx +++ b/web/src/pages/instance/topic.tsx @@ -223,6 +223,8 @@ const TopicPage = () => { const [searchText, setSearchText] = useState(''); const [typeFilter, setTypeFilter] = useState(''); const [nsFilter, setNsFilter] = useState(''); + const [tablePage, setTablePage] = useState(1); + const [tablePageSize, setTablePageSize] = useState(20); const [viewMode, setViewMode] = useState('列表'); const [detailModalOpen, setDetailModalOpen] = useState(false); const [selectedTopic, setSelectedTopic] = useState(null); @@ -266,6 +268,13 @@ const TopicPage = () => { [topics, searchText, typeFilter, nsFilter], ); + const maxTablePage = Math.max(1, Math.ceil(filteredTopics.length / tablePageSize)); + const currentTablePage = Math.min(tablePage, maxTablePage); + + const resetTablePage = () => { + setTablePage(1); + }; + // ─── Open detail modal ──────────────────────────────────────── const openDetail = async (topic: Topic) => { setSelectedTopic(topic); @@ -621,22 +630,34 @@ const TopicPage = () => { placeholder="搜索 Topic 名称" allowClear style={{ width: 260 }} - onSearch={setSearchText} + onSearch={(value) => { + setSearchText(value); + resetTablePage(); + }} onChange={(e) => { - if (!e.target.value) setSearchText(''); + if (!e.target.value) { + setSearchText(''); + resetTablePage(); + } }} /> { + setNsFilter(value); + resetTablePage(); + }} options={NAMESPACE_OPTIONS} style={{ width: 140 }} /> @@ -708,9 +729,14 @@ const TopicPage = () => { onChange: (keys) => setSelectedRowKeys(keys), }} pagination={{ - pageSize: 20, + current: currentTablePage, + pageSize: tablePageSize, showSizeChanger: true, showTotal: (t) => `共 ${t} 条`, + onChange: (page, pageSize) => { + setTablePage(page); + setTablePageSize(pageSize); + }, }} size="small" onRow={(record) => ({