Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
82fbd70
wip - pagination to reka
BGZStephen Nov 17, 2025
47e9175
WIP - reka component
BGZStephen Nov 17, 2025
4a340fc
WIP
BGZStephen Nov 17, 2025
2cd01ec
wip
BGZStephen Nov 17, 2025
30089f9
wip
BGZStephen Nov 17, 2025
4a80dd3
wip
BGZStephen Nov 17, 2025
755178d
wip
BGZStephen Nov 17, 2025
25894ee
wip
BGZStephen Nov 17, 2025
8931e52
wip
BGZStephen Nov 17, 2025
64e2e1d
wip
BGZStephen Nov 17, 2025
09b7ece
wip
BGZStephen Nov 17, 2025
ae5deea
fix sibling count
BGZStephen Nov 17, 2025
02e95f1
bolden
BGZStephen Nov 18, 2025
b69e46f
neaten up, remove duplicates
BGZStephen Nov 18, 2025
16ee339
add test file
BGZStephen Nov 18, 2025
d83fe58
fix linting
BGZStephen Nov 18, 2025
08c2cb2
linting errors, fix tests
BGZStephen Nov 18, 2025
59c6ddb
lint
BGZStephen Nov 18, 2025
a753ff5
remove flakey
BGZStephen Nov 18, 2025
c525b2f
Merge branch 'master' into DES-321-pagination
BGZStephen Nov 18, 2025
d3b8359
Merge branch 'master' into DES-321-pagination
BGZStephen Nov 18, 2025
d38923c
Merge branch 'master' into DES-321-pagination
BGZStephen Nov 18, 2025
29c6c1d
fixup tests
BGZStephen Nov 18, 2025
0454dab
Merge branch 'DES-321-pagination' of github.com:n8n-io/n8n into DES-3…
BGZStephen Nov 18, 2025
c538506
update snapshots
BGZStephen Nov 18, 2025
2f52886
remove unused
BGZStephen Nov 20, 2025
4cf9a79
add v2 component
BGZStephen Nov 20, 2025
b9ea8b9
reset pagination with master
BGZStephen Nov 20, 2025
f0bbf1d
Merge branch 'DES-321-pagination' of github.com:n8n-io/n8n into DES-3…
BGZStephen Nov 20, 2025
543fab2
lint
BGZStephen Nov 20, 2025
82f1d37
reset
BGZStephen Nov 20, 2025
9bbb763
add story
BGZStephen Nov 21, 2025
d957d13
extract types, neaten up
BGZStephen Nov 21, 2025
ee36825
to css
BGZStephen Nov 21, 2025
566ca5b
add markdown
BGZStephen Nov 21, 2025
942302d
tests
BGZStephen Nov 21, 2025
39aa959
lint
BGZStephen Nov 24, 2025
9954e97
pr comments
BGZStephen Nov 24, 2025
d3807f7
remove tests matching functionality that wasn't there
BGZStephen Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite';
import { ref } from 'vue';

import Pagination from './Pagination.vue';

const meta = {
title: 'Design system v3/Pagination',
component: Pagination,
parameters: {
docs: {
source: { type: 'dynamic' },
},
},
} satisfies Meta<typeof Pagination>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
render: (args) => ({
components: { Pagination },
setup() {
const currentPage = ref(args.currentPage ?? 1);
return { args, currentPage };
},
template: `
<div style="padding: 40px;">
<Pagination
:current-page="currentPage"
:page-size="args.pageSize"
:total="args.total"
@update:current-page="(page) => currentPage = page"
/>
</div>
`,
}),
args: {
currentPage: 1,
pageSize: 10,
total: 100,
},
};

export const ManyPages: Story = {
render: (args) => ({
components: { Pagination },
setup() {
const currentPage = ref(args.currentPage ?? 1);
return { args, currentPage };
},
template: `
<div style="padding: 40px;">
<Pagination
:current-page="currentPage"
:page-size="args.pageSize"
:total="args.total"
@update:current-page="(page) => currentPage = page"
/>
</div>
`,
}),
args: {
currentPage: 1,
pageSize: 10,
total: 500,
},
};

export const SmallPageSize: Story = {
render: (args) => ({
components: { Pagination },
setup() {
const currentPage = ref(args.currentPage ?? 1);
return { args, currentPage };
},
template: `
<div style="padding: 40px;">
<Pagination
:current-page="currentPage"
:page-size="args.pageSize"
:total="args.total"
@update:current-page="(page) => currentPage = page"
/>
</div>
`,
}),
args: {
currentPage: 1,
pageSize: 5,
total: 50,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
import userEvent from '@testing-library/user-event';
import { render, waitFor } from '@testing-library/vue';

import Pagination from './Pagination.vue';

describe('v2/components/N8nPagination', () => {
describe('rendering', () => {
it('should render with default props', () => {
const wrapper = render(Pagination, {
props: {
total: 100,
},
});
// Should render page numbers
expect(wrapper.getByText('1')).toBeInTheDocument();
});

it('should render page numbers', () => {
const wrapper = render(Pagination, {
props: {
total: 100,
pageSize: 10,
},
});
// Should render page 1 by default
expect(wrapper.getByText('1')).toBeInTheDocument();
});

it('should render prev and next buttons', () => {
const wrapper = render(Pagination, {
props: {
total: 100,
pageSize: 10,
},
});
// Check for prev and next buttons (they contain ‹ and ›)
expect(wrapper.container.textContent).toContain('‹');
expect(wrapper.container.textContent).toContain('›');
});

it('should render ellipsis when there are many pages', () => {
const wrapper = render(Pagination, {
props: {
total: 500,
pageSize: 10,
},
});
// Ellipsis is rendered as … (horizontal ellipsis character)
expect(wrapper.container.textContent).toContain('…');
});
});

describe('props', () => {
it('should use default pageSize when not provided', () => {
const wrapper = render(Pagination, {
props: {
total: 100,
},
});
// With default pageSize of 10, total 100 should show 10 pages
expect(wrapper.container).toBeInTheDocument();
});

it('should use custom pageSize', () => {
const wrapper = render(Pagination, {
props: {
total: 100,
pageSize: 20,
},
});
// With pageSize 20, total 100 should show 5 pages
expect(wrapper.container).toBeInTheDocument();
});

it('should display correct current page', () => {
const wrapper = render(Pagination, {
props: {
total: 100,
pageSize: 10,
currentPage: 3,
},
});
// Should show page 3
const page3 = wrapper.getByText('3');
expect(page3).toBeInTheDocument();
});

it('should default to page 1 when currentPage is not provided', () => {
const wrapper = render(Pagination, {
props: {
total: 100,
pageSize: 10,
},
});
// Should show page 1
expect(wrapper.getByText('1')).toBeInTheDocument();
});
});

describe('v-model', () => {
it('should emit update:current-page when page changes', async () => {
const wrapper = render(Pagination, {
props: {
total: 100,
pageSize: 10,
currentPage: 1,
},
});

// Find and click page 2
const page2 = wrapper.getByText('2');
await userEvent.click(page2);

await waitFor(() => {
expect(wrapper.emitted('update:current-page')).toBeTruthy();
expect(wrapper.emitted('update:current-page')?.[0]).toEqual([2]);
});
});

it('should update current page when clicking next button', async () => {
const wrapper = render(Pagination, {
props: {
total: 100,
pageSize: 10,
currentPage: 1,
},
});

// Find next button (contains ›)
const nextButton = Array.from(wrapper.container.querySelectorAll('button')).find((btn) =>
btn.textContent?.includes('›'),
);

expect(nextButton).toBeInTheDocument();
await userEvent.click(nextButton!);

await waitFor(() => {
expect(wrapper.emitted('update:current-page')).toBeTruthy();
});
});

it('should update current page when clicking prev button', async () => {
const wrapper = render(Pagination, {
props: {
total: 100,
pageSize: 10,
currentPage: 2,
},
});

// Find prev button (contains ‹)
const prevButton = Array.from(wrapper.container.querySelectorAll('button')).find((btn) =>
btn.textContent?.includes('‹'),
);

expect(prevButton).toBeInTheDocument();
await userEvent.click(prevButton!);

await waitFor(() => {
expect(wrapper.emitted('update:current-page')).toBeTruthy();
});
});
});

describe('active page state', () => {
it('should highlight current page', () => {
const wrapper = render(Pagination, {
props: {
total: 100,
pageSize: 10,
currentPage: 3,
},
});

const page3 = wrapper.getByText('3');
const page3Button = page3.closest('button');
// Active page should have a border (checking for style or class that indicates active state)
// Since CSS modules hash class names, we check that the element exists and is clickable
expect(page3Button).toBeInTheDocument();
});

it('should render current page correctly', () => {
const wrapper = render(Pagination, {
props: {
total: 100,
pageSize: 10,
currentPage: 2,
},
});

// Both pages should be visible
expect(wrapper.getByText('1')).toBeInTheDocument();
expect(wrapper.getByText('2')).toBeInTheDocument();
});
});

describe('ellipsis', () => {
it('should render ellipsis for large page counts', () => {
const wrapper = render(Pagination, {
props: {
total: 500,
pageSize: 10,
currentPage: 1,
},
});
// Should show ellipsis when there are many pages
expect(wrapper.container.textContent).toContain('…');
});

it('should not render ellipsis for small page counts', () => {
const wrapper = render(Pagination, {
props: {
total: 50,
pageSize: 10,
currentPage: 1,
},
});
// With only 5 pages, ellipsis should not be rendered
// This depends on the sibling-count prop (set to 2)
expect(wrapper.container.textContent).not.toContain('…');
});
});

describe('slots', () => {
it('should render default prev-icon when slot is not provided', () => {
const wrapper = render(Pagination, {
props: {
total: 100,
pageSize: 10,
},
});
// Default is ‹
expect(wrapper.container.textContent).toContain('‹');
});

it('should render default next-icon when slot is not provided', () => {
const wrapper = render(Pagination, {
props: {
total: 100,
pageSize: 10,
},
});
// Default is ›
expect(wrapper.container.textContent).toContain('›');
});
});

describe('edge cases', () => {
it('should handle single page', () => {
const wrapper = render(Pagination, {
props: {
total: 5,
pageSize: 10,
},
});
// Should still render pagination with just one page
expect(wrapper.getByText('1')).toBeInTheDocument();
});

it('should handle zero total', () => {
const wrapper = render(Pagination, {
props: {
total: 0,
pageSize: 10,
},
});
expect(wrapper.container).toBeInTheDocument();
});

it('should handle very large page numbers', () => {
const wrapper = render(Pagination, {
props: {
total: 10000,
pageSize: 10,
currentPage: 500,
},
});
expect(wrapper.container).toBeInTheDocument();
});

it('should handle pageSize larger than total', () => {
const wrapper = render(Pagination, {
props: {
total: 5,
pageSize: 100,
},
});
// Should show only one page
expect(wrapper.getByText('1')).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface PaginationProps {
total: number;
currentPage?: number;
pageSize?: number;
}
Loading
Loading