fix(editor): Reset current page if out of bounds after page size change (#17124)

This commit is contained in:
Raúl Gómez Morales
2025-07-10 12:08:16 +02:00
committed by GitHub
parent 591aa2d20c
commit b9e7b719c0
3 changed files with 69 additions and 23 deletions

View File

@@ -20,7 +20,7 @@ const itemFactory = () => ({
});
type Item = ReturnType<typeof itemFactory>;
const items: Item[] = [...Array(20).keys()].map(itemFactory);
const items: Item[] = [...Array(106).keys()].map(itemFactory);
const headers: Array<TableHeader<Item>> = [
{
title: 'Id',
@@ -102,26 +102,30 @@ describe('N8nDataTableServer', () => {
await userEvent.click(container.querySelector('thead tr th')!);
await userEvent.click(within(getByTestId('pagination')).getByLabelText('page 2'));
// change the page size select option
const selectInput = await findAllByRole('combobox'); // Find the select input
await userEvent.click(selectInput[0]);
const options = await getRenderedOptions();
expect(options.length).toBe(4);
const option50 = Array.from(options).find((option) => option.textContent === '50');
await userEvent.click(option50!);
expect(emitted('update:options').length).toBeGreaterThanOrEqual(4);
expect(emitted('update:options').length).toBe(3);
expect(emitted('update:options')[0]).toStrictEqual([
expect.objectContaining({ sortBy: [{ id: 'id', desc: false }] }),
]);
expect(emitted('update:options')[1]).toStrictEqual([
expect.objectContaining({ sortBy: [{ id: 'id', desc: true }] }),
]);
expect(emitted('update:options')[2]).toStrictEqual([expect.objectContaining({ page: 1 })]);
expect(emitted('update:options')[3]).toStrictEqual([
expect.objectContaining({ itemsPerPage: 50 }),
// // change the page size select option
const selectInput = await findAllByRole('combobox'); // Find the select input
await userEvent.click(selectInput[0]);
const options = await getRenderedOptions();
expect(options.length).toBe(4);
// account for the debounce
await new Promise((r) => setTimeout(r, 100));
const option50 = Array.from(options).find((option) => option.textContent === '50');
await userEvent.click(option50!);
expect(emitted('update:options').length).toBe(4);
expect(emitted('update:options').at(-1)).toStrictEqual([
expect.objectContaining({ page: 1, itemsPerPage: 50 }),
]);
});
@@ -157,4 +161,28 @@ describe('N8nDataTableServer', () => {
expect(queryByTestId('pagination')).not.toBeInTheDocument();
});
it('should adjust page to highest available when page size changes and current page exceeds maximum', async () => {
const { emitted, findAllByRole } = renderComponent({
props: { items, headers, itemsLength: 106, itemsPerPage: 50, page: 2 },
});
// change the page size select option
const selectInput = await findAllByRole('combobox'); // Find the select input
await userEvent.click(selectInput[0]);
const options = await getRenderedOptions();
expect(options.length).toBe(4);
const option100 = Array.from(options).find((option) => option.textContent === '100');
await userEvent.click(option100!);
// With 106 items and 50 per page, max page should be 2 (0-based index 1)
// Since we were on page 2, we should be adjusted to page 1
expect(emitted('update:options')).toEqual(
expect.arrayContaining([
expect.arrayContaining([expect.objectContaining({ page: 1, itemsPerPage: 100 })]),
]),
);
});
});

View File

@@ -37,6 +37,7 @@ import type {
Updater,
} from '@tanstack/vue-table';
import { createColumnHelper, FlexRender, getCoreRowModel, useVueTable } from '@tanstack/vue-table';
import { useThrottleFn } from '@vueuse/core';
import { ElCheckbox, ElOption, ElSelect, ElSkeletonItem } from 'element-plus';
import get from 'lodash/get';
import { computed, h, ref, shallowRef, useSlots, watch } from 'vue';
@@ -204,10 +205,7 @@ const page = defineModel<number>('page', { default: 0 });
watch(page, () => table.setPageIndex(page.value));
const itemsPerPage = defineModel<number>('items-per-page', { default: 10 });
watch(itemsPerPage, () => {
page.value = 0;
table.setPageSize(itemsPerPage.value);
});
watch(itemsPerPage, () => table.setPageSize(itemsPerPage.value));
const pagination = computed<PaginationState>({
get() {
@@ -313,6 +311,20 @@ const rowSelection = ref(
}, {}),
);
const emitUpdateOptions = useThrottleFn(
(payload: TableOptions) => emit('update:options', payload),
100,
);
function handlePageSizeChange(newPageSize: number) {
// Calculate the maximum available page (0-based indexing)
const maxPage = Math.max(0, Math.ceil(props.itemsLength / newPageSize) - 1);
const newPage = Math.min(page.value, maxPage);
page.value = newPage;
itemsPerPage.value = newPageSize;
}
const columnHelper = createColumnHelper<T>();
const table = useVueTable({
data,
@@ -334,12 +346,13 @@ const table = useVueTable({
getCoreRowModel: getCoreRowModel(),
onSortingChange: handleSortingChange,
onPaginationChange(updaterOrValue) {
pagination.value =
const newValue =
typeof updaterOrValue === 'function' ? updaterOrValue(pagination.value) : updaterOrValue;
emit('update:options', {
page: page.value,
itemsPerPage: itemsPerPage.value,
// prevent duplicate events from being fired
void emitUpdateOptions({
page: newValue.pageIndex,
itemsPerPage: newValue.pageSize,
sortBy: sortBy.value,
});
},
@@ -474,6 +487,7 @@ const table = useVueTable({
class="table-pagination__sizes__select"
size="small"
:teleported="false"
@update:model-value="handlePageSizeChange"
>
<ElOption v-for="item in pageSizes" :key="item" :label="item" :value="item" />
</ElSelect>

View File

@@ -508,4 +508,8 @@ async function onUpdateMfaEnforced(value: boolean) {
align-items: center;
flex-shrink: 0;
}
.container {
padding-bottom: 20px;
}
</style>