From 0d144bc050dd7f320768c6c92a1cf7ad20b0860d Mon Sep 17 00:00:00 2001 From: Nicolas Villanueva <1890113+MexicanAce@users.noreply.github.com> Date: Thu, 28 Nov 2024 07:14:39 -0800 Subject: [PATCH] feat: add page size support for pagination (#336) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ Add page size dropdown to show `10`, `20`, `50`, or `100` records in paginated tables ## Why ❔ Allows users to see more data on a single page. Closes #215 ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. ## Evidence https://github.com/user-attachments/assets/77d0aefe-f4ba-4f22-b765-5b3e8872e1a7 --- .../app/src/components/common/Dropdown.vue | 8 +- .../app/src/components/common/Pagination.vue | 186 ++++++++++++------ .../app/src/components/transactions/Table.vue | 27 ++- .../composables/common/useFetchCollection.ts | 9 +- packages/app/src/locales/en.json | 3 + packages/app/src/locales/uk.json | 3 + packages/app/src/views/BatchesView.vue | 14 +- packages/app/tests/components/Account.spec.ts | 5 +- .../components/common/Pagination.spec.ts | 30 ++- .../components/transactions/Table.spec.ts | 14 +- .../tests/components/transfers/Table.spec.ts | 5 + .../common/useFetchCollection.spec.ts | 2 +- .../tests/composables/useTransactions.spec.ts | 2 +- packages/app/tests/e2e/src/pages/base.page.ts | 4 + .../e2e/src/steps/blockexplorer.steps.ts | 5 + packages/app/tests/views/BatchView.spec.ts | 5 +- packages/app/tests/views/BlockView.spec.ts | 5 +- packages/app/tests/views/HomeView.spec.ts | 5 +- 18 files changed, 229 insertions(+), 103 deletions(-) diff --git a/packages/app/src/components/common/Dropdown.vue b/packages/app/src/components/common/Dropdown.vue index 87b5d6a6f8..9093bb24da 100644 --- a/packages/app/src/components/common/Dropdown.vue +++ b/packages/app/src/components/common/Dropdown.vue @@ -17,7 +17,7 @@ - + - + + diff --git a/packages/app/tests/components/Account.spec.ts b/packages/app/tests/components/Account.spec.ts index 39fb86887f..c058bc21eb 100644 --- a/packages/app/tests/components/Account.spec.ts +++ b/packages/app/tests/components/Account.spec.ts @@ -17,10 +17,13 @@ const router = { value: {}, }, }; +const routeQueryMock = vi.fn(() => ({})); vi.mock("vue-router", () => ({ useRouter: () => router, - useRoute: () => vi.fn(), + useRoute: () => ({ + query: routeQueryMock(), + }), })); describe("Account:", () => { diff --git a/packages/app/tests/components/common/Pagination.spec.ts b/packages/app/tests/components/common/Pagination.spec.ts index 762e896bc3..e887afe6c5 100644 --- a/packages/app/tests/components/common/Pagination.spec.ts +++ b/packages/app/tests/components/common/Pagination.spec.ts @@ -1,3 +1,5 @@ +import { createI18n } from "vue-i18n"; + import { describe, expect, it, vi } from "vitest"; import { fireEvent } from "@testing-library/vue"; @@ -5,6 +7,8 @@ import { mount, RouterLinkStub } from "@vue/test-utils"; import Pagination from "@/components/common/Pagination.vue"; +import enUS from "@/locales/en.json"; + vi.mock("vue-router", () => ({ useRouter: () => vi.fn(), useRoute: () => ({ @@ -13,6 +17,14 @@ vi.mock("vue-router", () => ({ })); describe("Pagination:", () => { + const i18n = createI18n({ + locale: "en", + allowComposition: true, + messages: { + en: enUS, + }, + }); + it("renders default state properly", () => { const wrapper = mount(Pagination, { props: { @@ -23,12 +35,13 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); const pageLinks = wrapper.findAllComponents(RouterLinkStub).filter((e) => e.classes().includes("page")); expect(pageLinks.length).toBe(5); - expect(pageLinks[0].props().to.query).toEqual({}); + expect(pageLinks[0].props().to.query).toEqual({ pageSize: "10" }); for (let a = 2; a < 5; a++) { expect(pageLinks[a - 1].props().to.query.page).toBe(a); } @@ -46,9 +59,10 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); - expect(wrapper.classes("disabled")).toBe(true); + expect(wrapper.find(".page-numbers-container").classes("disabled")).toBe(true); }); describe("Dots:", () => { @@ -62,6 +76,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); expect(wrapper.findAll(".dots").length).toBe(1); @@ -77,6 +92,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); expect(wrapper.findAll(".dots").length).toBe(1); @@ -92,6 +108,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); expect(wrapper.findAll(".dots").length).toBe(2); @@ -109,6 +126,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); @@ -116,7 +134,7 @@ describe("Pagination:", () => { expect(wrapper.emitted("update:activePage")).toEqual([[5], [5], [5], [6]]); }); describe("Back & Next buttons:", () => { - it("back button doesn't have a query and is disabled if first page is active", () => { + it("back button only has pageSize query and is disabled if first page is active", () => { const wrapper = mount(Pagination, { props: { activePage: 1, @@ -126,10 +144,11 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); const pageLinks = wrapper.findAllComponents(RouterLinkStub).filter((e) => e.classes().includes("arrow")); - expect(pageLinks[0].props().to.query).toEqual({}); + expect(pageLinks[0].props().to.query).toEqual({ pageSize: "10" }); expect(pageLinks[0].classes().includes("disabled")).toEqual(true); }); it("next button is disabled if last page is active", () => { @@ -142,6 +161,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); const pageLinks = wrapper.findAllComponents(RouterLinkStub).filter((e) => e.classes().includes("arrow")); @@ -158,6 +178,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); const pageLinks = wrapper.findAllComponents(RouterLinkStub).filter((e) => e.classes().includes("arrow")); @@ -177,6 +198,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); const pageLinks = wrapper.findAllComponents(RouterLinkStub); diff --git a/packages/app/tests/components/transactions/Table.spec.ts b/packages/app/tests/components/transactions/Table.spec.ts index 58c4288267..0c8febfa73 100644 --- a/packages/app/tests/components/transactions/Table.spec.ts +++ b/packages/app/tests/components/transactions/Table.spec.ts @@ -18,8 +18,16 @@ import type { TransactionListItem } from "@/composables/useTransactions"; import $testId from "@/plugins/testId"; +const router = { + push: vi.fn(), +}; + +const routeQueryMock = vi.fn(() => ({})); vi.mock("vue-router", () => ({ - useRoute: vi.fn(() => ({ query: {} })), + useRoute: () => ({ + query: routeQueryMock(), + }), + useRouter: () => router, })); vi.mock("@/composables/useTokenLibrary", () => { return { @@ -278,12 +286,12 @@ describe("Transfers:", () => { }); it("renders pagination", async () => { - expect(renderResult!.container.querySelector(".pagination")).not.toBeNull(); + expect(renderResult!.container.querySelector(".pagination-container")).not.toBeNull(); }); it("does not render pagination if pagination prop is false", async () => { await renderResult?.rerender({ pagination: false }); - expect(renderResult!.container.querySelector(".pagination")).toBeNull(); + expect(renderResult!.container.querySelector(".pagination-container")).toBeNull(); }); }); diff --git a/packages/app/tests/components/transfers/Table.spec.ts b/packages/app/tests/components/transfers/Table.spec.ts index 37a1ea76b0..77a5f08259 100644 --- a/packages/app/tests/components/transfers/Table.spec.ts +++ b/packages/app/tests/components/transfers/Table.spec.ts @@ -16,7 +16,12 @@ import elements from "tests/e2e/testId.json"; import $testId from "@/plugins/testId"; +const router = { + push: vi.fn(), +}; + vi.mock("vue-router", () => ({ + useRouter: () => router, useRoute: vi.fn(), })); vi.mock("@/composables/useTokenLibrary", () => { diff --git a/packages/app/tests/composables/common/useFetchCollection.spec.ts b/packages/app/tests/composables/common/useFetchCollection.spec.ts index 45b703e125..100bd068c9 100644 --- a/packages/app/tests/composables/common/useFetchCollection.spec.ts +++ b/packages/app/tests/composables/common/useFetchCollection.spec.ts @@ -111,7 +111,7 @@ describe("useFetchCollection:", () => { await fc.load(1, new Date("2023-05-01T10:00:00.000Z")); expect($fetch).toHaveBeenCalledWith( - "https://block-explorer-api.testnets.zksync.dev/?pageSize=10&page=1&toDate=2023-05-01T10%3A00%3A00.000Z" + "https://block-explorer-api.testnets.zksync.dev/?limit=10&page=1&toDate=2023-05-01T10%3A00%3A00.000Z" ); }); }); diff --git a/packages/app/tests/composables/useTransactions.spec.ts b/packages/app/tests/composables/useTransactions.spec.ts index 3a58681d18..2924a9c3f2 100644 --- a/packages/app/tests/composables/useTransactions.spec.ts +++ b/packages/app/tests/composables/useTransactions.spec.ts @@ -90,7 +90,7 @@ describe("useTransactions:", () => { const composable = useTransactions(searchParams); await composable.load(1); expect(fetchMock.mock.calls[0][0]).toBe( - "https://block-explorer-api.testnets.zksync.dev/transactions?blockNumber=0&l1BatchNumber=0&pageSize=10&page=1" + "https://block-explorer-api.testnets.zksync.dev/transactions?blockNumber=0&l1BatchNumber=0&limit=10&page=1" ); }); diff --git a/packages/app/tests/e2e/src/pages/base.page.ts b/packages/app/tests/e2e/src/pages/base.page.ts index d3efdf341a..4d72816682 100644 --- a/packages/app/tests/e2e/src/pages/base.page.ts +++ b/packages/app/tests/e2e/src/pages/base.page.ts @@ -20,6 +20,10 @@ export class BasePage { return "//*[@aria-label='Pagination']"; } + get pageSizeDropdown() { + return "//div[contains(@class, 'page-size-container')]"; + } + async getColumnByText(text: string) { element = `//th[text()="${text}"]`; result = await this.world.page?.locator(element).first(); diff --git a/packages/app/tests/e2e/src/steps/blockexplorer.steps.ts b/packages/app/tests/e2e/src/steps/blockexplorer.steps.ts index 4fdef1efc3..f36b96826a 100644 --- a/packages/app/tests/e2e/src/steps/blockexplorer.steps.ts +++ b/packages/app/tests/e2e/src/steps/blockexplorer.steps.ts @@ -319,6 +319,11 @@ Then("Pagination form should be visible", async function (this: ICustomWorld) { result = await this.page?.locator(element); await expect(result).toBeVisible(config.increasedTimeout); + + element = basePage.pageSizeDropdown; + result = await this.page?.locator(element); + + await expect(result).toBeVisible(config.increasedTimeout); }); Then("Column with {string} name is visible", async function (this: ICustomWorld, columnName: string) { diff --git a/packages/app/tests/views/BatchView.spec.ts b/packages/app/tests/views/BatchView.spec.ts index 8d4d6900f2..2cc1e622a3 100644 --- a/packages/app/tests/views/BatchView.spec.ts +++ b/packages/app/tests/views/BatchView.spec.ts @@ -17,6 +17,7 @@ const router = { value: {}, }, }; +const routeQueryMock = vi.fn(() => ({})); vi.mock("@/composables/useSearch", () => { return { @@ -28,7 +29,9 @@ vi.mock("@/composables/useSearch", () => { vi.mock("vue-router", () => ({ useRouter: () => router, - useRoute: () => vi.fn(), + useRoute: () => ({ + query: routeQueryMock(), + }), createWebHistory: () => vi.fn(), createRouter: () => vi.fn(), })); diff --git a/packages/app/tests/views/BlockView.spec.ts b/packages/app/tests/views/BlockView.spec.ts index 37572b2f79..6586df112e 100644 --- a/packages/app/tests/views/BlockView.spec.ts +++ b/packages/app/tests/views/BlockView.spec.ts @@ -17,6 +17,7 @@ const router = { value: {}, }, }; +const routeQueryMock = vi.fn(() => ({})); vi.mock("@/composables/useSearch", () => { return { @@ -28,7 +29,9 @@ vi.mock("@/composables/useSearch", () => { vi.mock("vue-router", () => ({ useRouter: () => router, - useRoute: () => vi.fn(), + useRoute: () => ({ + query: routeQueryMock(), + }), createWebHistory: () => vi.fn(), createRouter: () => vi.fn(), })); diff --git a/packages/app/tests/views/HomeView.spec.ts b/packages/app/tests/views/HomeView.spec.ts index 7168f27062..8b23c816bf 100644 --- a/packages/app/tests/views/HomeView.spec.ts +++ b/packages/app/tests/views/HomeView.spec.ts @@ -26,6 +26,7 @@ const getBatchesMockCollection = (length: number) => status: "sealed", timestamp: "2022-04-13T13:09:31.000Z", })); +const routeQueryMock = vi.fn(() => ({})); vi.mock("ohmyfetch", () => { return { @@ -35,7 +36,9 @@ vi.mock("ohmyfetch", () => { vi.mock("vue-router", () => ({ useRouter: () => vi.fn(), - useRoute: () => vi.fn(), + useRoute: () => ({ + query: routeQueryMock(), + }), createWebHistory: () => vi.fn(), createRouter: () => vi.fn(), }));