diff --git a/app/components/malio/datatable/DataTable.test.ts b/app/components/malio/datatable/DataTable.test.ts index 67098e4..8dc92d7 100644 --- a/app/components/malio/datatable/DataTable.test.ts +++ b/app/components/malio/datatable/DataTable.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest' import { h } from 'vue' import { mount } from '@vue/test-utils' import type { DefineComponent } from 'vue' @@ -189,24 +189,6 @@ describe('MalioDataTable', () => { expect(wrapper.find('[data-test="pagination"]').exists()).toBe(true) }) - it('renders all pages when totalPages <= 5', () => { - const wrapper = mountComponent({ totalItems: 50, perPage: 10 }) - for (let i = 1; i <= 5; i++) { - expect(wrapper.find(`[data-test="page-${i}"]`).exists()).toBe(true) - } - }) - - it('highlights current page', () => { - const wrapper = mountComponent({ totalItems: 50, perPage: 10, page: 3 }) - expect(wrapper.find('[data-test="page-3"]').attributes('aria-current')).toBe('page') - }) - - it('emits update:page on page button click', async () => { - const wrapper = mountComponent({ totalItems: 50, perPage: 10, page: 1 }) - await wrapper.find('[data-test="page-3"]').trigger('click') - expect(wrapper.emitted('update:page')?.[0]).toEqual([3]) - }) - it('Prev button is disabled on page 1', () => { const wrapper = mountComponent({ totalItems: 50, perPage: 10, page: 1 }) expect(wrapper.find('[data-test="prev-button"]').attributes('disabled')).toBeDefined() @@ -229,26 +211,6 @@ describe('MalioDataTable', () => { expect(wrapper.emitted('update:page')?.[0]).toEqual([4]) }) - it('shows ellipsis for truncated pages (> 5 pages)', () => { - const wrapper = mountComponent({ totalItems: 200, perPage: 10, page: 10 }) - const ellipsis = wrapper.findAll('[aria-hidden="true"]') - expect(ellipsis.length).toBeGreaterThan(0) - expect(ellipsis[0].text()).toBe('…') - }) - - it('always shows first and last page when > 5 pages', () => { - const wrapper = mountComponent({ totalItems: 200, perPage: 10, page: 10 }) - expect(wrapper.find('[data-test="page-1"]').exists()).toBe(true) - expect(wrapper.find('[data-test="page-20"]').exists()).toBe(true) - }) - - it('shows 1 neighbor on each side of current page', () => { - const wrapper = mountComponent({ totalItems: 200, perPage: 10, page: 10 }) - expect(wrapper.find('[data-test="page-9"]').exists()).toBe(true) - expect(wrapper.find('[data-test="page-10"]').exists()).toBe(true) - expect(wrapper.find('[data-test="page-11"]').exists()).toBe(true) - }) - it('pagination nav has aria-label', () => { const wrapper = mountComponent({ totalItems: 30 }) expect(wrapper.find('[data-test="pagination-nav"]').attributes('aria-label')).toBe('Pagination') @@ -265,6 +227,80 @@ describe('MalioDataTable', () => { }) }) + describe('Pagination — saut de page (champ)', () => { + beforeEach(() => { vi.useFakeTimers() }) + afterEach(() => { vi.runOnlyPendingTimers(); vi.useRealTimers() }) + + it('affiche la page courante et le total dans le champ', () => { + const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 16 }) + expect((wrapper.find('[data-test="page-input"]').element as HTMLInputElement).value).toBe('16') + expect(wrapper.find('[data-test="total-pages"]').text()).toBe('31') + }) + + it('émet update:page après le debounce pour une valeur valide', async () => { + const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 1 }) + const input = wrapper.find('[data-test="page-input"]') + await input.setValue('16') + expect(wrapper.emitted('update:page')).toBeUndefined() + vi.advanceTimersByTime(400) + expect(wrapper.emitted('update:page')?.at(-1)).toEqual([16]) + }) + + it('n\'émet pas avant la fin du debounce', async () => { + const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 1 }) + await wrapper.find('[data-test="page-input"]').setValue('16') + vi.advanceTimersByTime(399) + expect(wrapper.emitted('update:page')).toBeUndefined() + }) + + it('Entrée applique immédiatement', async () => { + const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 1 }) + const input = wrapper.find('[data-test="page-input"]') + await input.setValue('16') + await input.trigger('keydown.enter') + expect(wrapper.emitted('update:page')?.at(-1)).toEqual([16]) + }) + + it('clampe une valeur > N à la dernière page (Entrée)', async () => { + const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 1 }) + const input = wrapper.find('[data-test="page-input"]') + await input.setValue('50') + await input.trigger('keydown.enter') + expect(wrapper.emitted('update:page')?.at(-1)).toEqual([31]) + }) + + it('restaure la page courante quand le champ est vidé au blur', async () => { + const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 5 }) + const input = wrapper.find('[data-test="page-input"]') + await input.setValue('') + await input.trigger('blur') + expect(wrapper.emitted('update:page')).toBeUndefined() + expect((input.element as HTMLInputElement).value).toBe('5') + }) + + it('n\'émet pas pour 0 et restaure la page courante (Entrée)', async () => { + const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 5 }) + const input = wrapper.find('[data-test="page-input"]') + await input.setValue('0') + await input.trigger('keydown.enter') + expect(wrapper.emitted('update:page')).toBeUndefined() + expect((input.element as HTMLInputElement).value).toBe('5') + }) + + it('retire les caractères non numériques à la frappe', async () => { + const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 1 }) + const input = wrapper.find('[data-test="page-input"]') + await input.setValue('1a2b') + expect((input.element as HTMLInputElement).value).toBe('12') + }) + + it('resynchronise le champ quand la prop page change', async () => { + const wrapper = mountComponent({ totalItems: 310, perPage: 10, page: 1 }) + await wrapper.setProps({ page: 7 }) + expect((wrapper.find('[data-test="page-input"]').element as HTMLInputElement).value).toBe('7') + }) + }) + describe('Per-page selector', () => { it('emits update:per-page and reset page to 1 on change', async () => { const wrapper = mountComponent({ totalItems: 100, perPage: 10, page: 5 })