import { expect, fixture, html, elementUpdated } from '@open-wc/testing'; import './nile-pagination'; import type { NilePagination } from './nile-pagination'; describe('NilePagination', () => { // ═══════════════════════════════════════════════════ // RENDERING & REGISTRATION // ═══════════════════════════════════════════════════ it('1. should render without errors', async () => { const el = await fixture(html``); expect(el).to.exist; }); it('2. should have a shadow root', async () => { const el = await fixture(html``); expect(el.shadowRoot).to.not.be.null; }); it('3. should be registered as a custom element', async () => { expect(customElements.get('nile-pagination')).to.exist; }); it('4. should have correct tag name', async () => { const el = await fixture(html``); expect(el.tagName.toLowerCase()).to.equal('nile-pagination'); }); it('5. should have styles defined', async () => { const { NilePagination } = await import('./nile-pagination'); expect(NilePagination.styles).to.exist; }); it('6. should have open shadow root mode', async () => { const el = await fixture(html``); expect(el.shadowRoot!.mode).to.equal('open'); }); it('7. should be connected when in DOM', async () => { const el = await fixture(html``); expect(el.isConnected).to.be.true; }); it('8. should disconnect when removed', async () => { const el = await fixture(html``); el.remove(); expect(el.isConnected).to.be.false; }); it('9. should render multiple instances', async () => { const wrapper = await fixture(html`
`); expect(wrapper.querySelectorAll('nile-pagination').length).to.equal(2); }); it('10. should be creatable via document.createElement', async () => { const el = document.createElement('nile-pagination') as NilePagination; document.body.appendChild(el); await el.updateComplete; expect(el.shadowRoot).to.not.be.null; document.body.removeChild(el); }); // ═══════════════════════════════════════════════════ // DEFAULT PROPERTY VALUES // ═══════════════════════════════════════════════════ it('11. should default totalItems to 0', async () => { const el = await fixture(html``); expect(el.totalItems).to.equal(0); }); it('12. should default currentPage to 1', async () => { const el = await fixture(html``); expect(el.currentPage).to.equal(1); }); it('13. should default pageSize to 50', async () => { const el = await fixture(html``); expect(el.pageSize).to.equal(50); }); it('14. should default variant to fluid', async () => { const el = await fixture(html``); expect(el.variant).to.equal('fluid'); }); it('15. should default disabled to false', async () => { const el = await fixture(html``); expect(el.disabled).to.be.false; }); it('16. should default showTitle to true', async () => { const el = await fixture(html``); expect(el.showTitle).to.be.true; }); it('17. should default pageSizeOptions to [10, 25, 50, 100]', async () => { const el = await fixture(html``); expect(el.pageSizeOptions).to.deep.equal([10, 25, 50, 100]); }); // ═══════════════════════════════════════════════════ // ATTRIBUTE → PROPERTY CONVERTERS (totalItems) // ═══════════════════════════════════════════════════ it('18. should parse totalitems attribute as number', async () => { const el = await fixture(html``); expect(el.totalItems).to.equal(200); }); it('19. should floor non-integer totalitems', async () => { const el = await fixture(html``); expect(el.totalItems).to.equal(99); }); it('20. should default totalitems to 0 for negative values', async () => { const el = await fixture(html``); expect(el.totalItems).to.equal(0); }); it('21. should default totalitems to 0 for non-numeric strings', async () => { const el = await fixture(html``); expect(el.totalItems).to.equal(0); }); it('22. should default totalitems to 0 for Infinity', async () => { const el = await fixture(html``); expect(el.totalItems).to.equal(0); }); it('23. should reflect totalitems attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('totalitems')).to.equal('150'); }); // ═══════════════════════════════════════════════════ // ATTRIBUTE → PROPERTY CONVERTERS (currentPage) // ═══════════════════════════════════════════════════ it('24. should parse currentpage attribute as number', async () => { const el = await fixture(html``); expect(el.currentPage).to.equal(3); }); it('25. should floor non-integer currentpage', async () => { const el = await fixture(html``); expect(el.currentPage).to.equal(2); }); it('26. should default currentpage to 1 for values < 1', async () => { const el = await fixture(html``); expect(el.currentPage).to.equal(1); }); it('27. should default currentpage to 1 for negative values', async () => { const el = await fixture(html``); expect(el.currentPage).to.equal(1); }); it('28. should default currentpage to 1 for non-numeric strings', async () => { const el = await fixture(html``); expect(el.currentPage).to.equal(1); }); it('29. should reflect currentpage attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('currentpage')).to.equal('5'); }); // ═══════════════════════════════════════════════════ // ATTRIBUTE → PROPERTY CONVERTERS (pageSize) // ═══════════════════════════════════════════════════ it('30. should parse pagesize attribute as number', async () => { const el = await fixture(html``); expect(el.pageSize).to.equal(25); }); it('31. should floor non-integer pagesize', async () => { const el = await fixture(html``); expect(el.pageSize).to.equal(10); }); it('32. should default pagesize to 50 for values < 1', async () => { const el = await fixture(html``); expect(el.pageSize).to.equal(50); }); it('33. should default pagesize to 50 for negative values', async () => { const el = await fixture(html``); expect(el.pageSize).to.equal(50); }); it('34. should default pagesize to 50 for non-numeric strings', async () => { const el = await fixture(html``); expect(el.pageSize).to.equal(50); }); it('35. should reflect pagesize attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('pagesize')).to.equal('10'); }); // ═══════════════════════════════════════════════════ // ATTRIBUTE → PROPERTY CONVERTERS (pageSizeOptions) // ═══════════════════════════════════════════════════ it('36. should parse pagesizeoptions from JSON array', async () => { const el = await fixture(html``); expect(el.pageSizeOptions).to.deep.equal([5, 10, 20]); }); it('37. should fallback to defaults for invalid pagesizeoptions JSON', async () => { const el = await fixture(html``); expect(el.pageSizeOptions).to.deep.equal([10, 25, 50, 100]); }); it('38. should fallback to defaults for non-number array pagesizeoptions', async () => { const el = await fixture(html``); expect(el.pageSizeOptions).to.deep.equal([10, 25, 50, 100]); }); it('39. should fallback to defaults for non-array pagesizeoptions', async () => { const el = await fixture(html``); expect(el.pageSizeOptions).to.deep.equal([10, 25, 50, 100]); }); // ═══════════════════════════════════════════════════ // TOTAL PAGES CALCULATION // ═══════════════════════════════════════════════════ it('40. should calculate totalPages as 1 when totalItems is 0', async () => { const el = await fixture(html``); await el.updateComplete; // With 0 items, totalPages should still be at least 1 const nav = el.shadowRoot!.querySelector('nav'); expect(nav).to.exist; }); it('41. should calculate totalPages correctly (100 items, 10 per page = 10 pages)', async () => { const el = await fixture(html``); await el.updateComplete; // Should render page buttons for 10 pages in fluid variant const buttons = el.shadowRoot!.querySelectorAll('ul.pagination nile-button'); // 7 visible pages (1..7 since <=7) + prev + next = 9 when totalPages=10, with ellipsis expect(buttons.length).to.be.greaterThan(2); // At least prev + next }); it('42. should calculate totalPages correctly with remainder (105 items, 10 per page = 11 pages)', async () => { const el = await fixture(html``); await el.updateComplete; const nav = el.shadowRoot!.querySelector('nav'); expect(nav).to.exist; }); // ═══════════════════════════════════════════════════ // FLUID VARIANT RENDERING // ═══════════════════════════════════════════════════ it('43. should render wrapper with fluid class by default', async () => { const el = await fixture(html``); const wrapper = el.shadowRoot!.querySelector('.pagination-wrapper'); expect(wrapper).to.exist; expect(wrapper!.classList.contains('fluid')).to.be.true; }); it('44. should render nav element in fluid variant', async () => { const el = await fixture(html``); const nav = el.shadowRoot!.querySelector('nav'); expect(nav).to.exist; expect(nav!.getAttribute('aria-label')).to.equal('Pagination'); }); it('45. should render range text in fluid variant with showTitle', async () => { const el = await fixture(html``); await el.updateComplete; const rangeText = el.shadowRoot!.querySelector('.range-text'); expect(rangeText).to.exist; expect(rangeText!.textContent).to.contain('Showing'); }); it('46. should show correct range text for first page', async () => { const el = await fixture(html``); await el.updateComplete; const rangeText = el.shadowRoot!.querySelector('.range-text'); expect(rangeText!.textContent).to.contain('1'); expect(rangeText!.textContent).to.contain('10'); expect(rangeText!.textContent).to.contain('100'); }); it('47. should show correct range text for middle page', async () => { const el = await fixture(html``); await el.updateComplete; const rangeText = el.shadowRoot!.querySelector('.range-text'); expect(rangeText!.textContent).to.contain('41'); expect(rangeText!.textContent).to.contain('50'); expect(rangeText!.textContent).to.contain('100'); }); it('48. should show correct range text for last page', async () => { const el = await fixture(html``); await el.updateComplete; const rangeText = el.shadowRoot!.querySelector('.range-text'); expect(rangeText!.textContent).to.contain('91'); expect(rangeText!.textContent).to.contain('95'); }); it('49. should show "Showing 0 of 0" when totalItems is 0 in fluid variant', async () => { const el = await fixture(html``); await el.updateComplete; const rangeText = el.shadowRoot!.querySelector('.range-text'); expect(rangeText).to.exist; expect(rangeText!.textContent).to.contain('0 of 0'); }); it('50. should not render range text in fluid variant when showTitle is false', async () => { const el = await fixture(html``); await el.updateComplete; const rangeText = el.shadowRoot!.querySelector('.range-text'); expect(rangeText).to.be.null; }); it('51. should render page-size-select in fluid variant', async () => { const el = await fixture(html``); const pageSizeSelect = el.shadowRoot!.querySelector('.page-size-select'); expect(pageSizeSelect).to.exist; }); it('52. should render "Items per page" label in fluid variant', async () => { const el = await fixture(html``); const label = el.shadowRoot!.querySelector('.page-size-label'); expect(label).to.exist; expect(label!.textContent).to.contain('Items per page'); }); it('53. should render page size dropdown showing current pageSize in fluid variant', async () => { const el = await fixture(html``); await el.updateComplete; const trigger = el.shadowRoot!.querySelector('.page-size-dropdown nile-button'); expect(trigger).to.exist; expect(trigger!.textContent).to.contain('25'); }); it('54. should render page size options as menu items in fluid variant', async () => { const el = await fixture(html``); await el.updateComplete; const menuItems = el.shadowRoot!.querySelectorAll('.page-size-dropdown nile-menu-item'); expect(menuItems.length).to.equal(4); // [10, 25, 50, 100] }); it('55. should render ul.pagination in fluid variant', async () => { const el = await fixture(html``); const ul = el.shadowRoot!.querySelector('ul.pagination'); expect(ul).to.exist; }); it('56. should render prev button in fluid variant', async () => { const el = await fixture(html``); const prevBtn = el.shadowRoot!.querySelector('.prev-button'); expect(prevBtn).to.exist; }); it('57. should render next button in fluid variant', async () => { const el = await fixture(html``); const nextBtn = el.shadowRoot!.querySelector('.next-button'); expect(nextBtn).to.exist; }); it('58. should render page number buttons in fluid variant for <= 7 pages', async () => { const el = await fixture(html``); await el.updateComplete; // 5 pages, all visible + prev + next const buttons = el.shadowRoot!.querySelectorAll('ul.pagination > li > nile-button'); expect(buttons.length).to.equal(7); // 5 pages + prev + next }); it('59. should render ellipsis dropdown for > 7 pages in fluid variant', async () => { const el = await fixture(html``); await el.updateComplete; const ellipsis = el.shadowRoot!.querySelector('.ellipsis-dropdown'); expect(ellipsis).to.exist; }); it('60. should highlight current page with primary variant button', async () => { const el = await fixture(html``); await el.updateComplete; const pageButtons = el.shadowRoot!.querySelectorAll('ul.pagination > li > nile-button:not(.prev-button):not(.next-button)'); const currentBtn = Array.from(pageButtons).find(btn => btn.textContent!.trim() === '3'); expect(currentBtn).to.exist; expect(currentBtn!.getAttribute('variant')).to.equal('primary'); }); it('61. should use ghost variant for non-current pages', async () => { const el = await fixture(html``); await el.updateComplete; const pageButtons = el.shadowRoot!.querySelectorAll('ul.pagination > li > nile-button:not(.prev-button):not(.next-button)'); const otherBtn = Array.from(pageButtons).find(btn => btn.textContent!.trim() === '1'); expect(otherBtn).to.exist; expect(otherBtn!.getAttribute('variant')).to.equal('ghost'); }); // ═══════════════════════════════════════════════════ // COMPACT VARIANT RENDERING // ═══════════════════════════════════════════════════ it('62. should render wrapper with compact class', async () => { const el = await fixture(html``); const wrapper = el.shadowRoot!.querySelector('.pagination-wrapper.compact'); expect(wrapper).to.exist; }); it('63. should render compact range text without "Showing" prefix', async () => { const el = await fixture(html``); await el.updateComplete; const rangeText = el.shadowRoot!.querySelector('.range-text'); expect(rangeText).to.exist; // compact trims "Showing " from the start expect(rangeText!.textContent!.trim().startsWith('Showing')).to.be.false; }); it('64. should render compact page size dropdown', async () => { const el = await fixture(html``); await el.updateComplete; const dropdown = el.shadowRoot!.querySelector('.compact-dropdown'); expect(dropdown).to.exist; }); it('65. should render compact pagination with current page dropdown', async () => { const el = await fixture(html``); await el.updateComplete; const compactPagination = el.shadowRoot!.querySelector('.compact-pagination'); expect(compactPagination).to.exist; }); it('66. should show current page in compact page dropdown trigger', async () => { const el = await fixture(html``); await el.updateComplete; const trigger = el.shadowRoot!.querySelector('.current-page-btn'); expect(trigger).to.exist; expect(trigger!.textContent).to.contain('3'); }); it('67. should render all page numbers in compact page dropdown', async () => { const el = await fixture(html``); await el.updateComplete; const menuItems = el.shadowRoot!.querySelectorAll('.compact-scroll-wrapper1 nile-menu-item'); expect(menuItems.length).to.equal(5); // 5 total pages }); it('68. should render prev and next buttons in compact variant', async () => { const el = await fixture(html``); await el.updateComplete; const prevBtn = el.shadowRoot!.querySelector('.prev-button'); const nextBtn = el.shadowRoot!.querySelector('.next-button'); expect(prevBtn).to.exist; expect(nextBtn).to.exist; }); it('69. should not render page-size-select in compact variant', async () => { const el = await fixture(html``); await el.updateComplete; const pageSizeSelect = el.shadowRoot!.querySelector('.page-size-select'); expect(pageSizeSelect).to.be.null; }); it('70. should render compact page size options as menu items', async () => { const el = await fixture(html``); await el.updateComplete; const menuItems = el.shadowRoot!.querySelectorAll('.compact-scroll-wrapper nile-menu-item'); expect(menuItems.length).to.equal(4); // default [10, 25, 50, 100] }); // ═══════════════════════════════════════════════════ // MINI VARIANT RENDERING // ═══════════════════════════════════════════════════ it('71. should render mini wrapper with mini class', async () => { const el = await fixture(html``); const wrapper = el.shadowRoot!.querySelector('.pagination-wrapper.mini'); expect(wrapper).to.exist; }); it('72. should render "Showing" label in mini variant', async () => { const el = await fixture(html``); await el.updateComplete; const label = el.shadowRoot!.querySelector('.mini-showing-label'); expect(label).to.exist; expect(label!.textContent).to.contain('Showing'); }); it('73. should render "of N" label in mini variant with correct total pages', async () => { const el = await fixture(html``); await el.updateComplete; const ofLabel = el.shadowRoot!.querySelector('.mini-of-label'); expect(ofLabel).to.exist; expect(ofLabel!.textContent).to.contain('of 10'); }); it('74. should render mini page dropdown with current page', async () => { const el = await fixture(html``); await el.updateComplete; const pageBtn = el.shadowRoot!.querySelector('.mini-page-btn'); expect(pageBtn).to.exist; expect(pageBtn!.textContent).to.contain('3'); }); it('75. should render all page options in mini dropdown', async () => { const el = await fixture(html``); await el.updateComplete; const menuItems = el.shadowRoot!.querySelectorAll('.mini-scroll-wrapper nile-menu-item'); expect(menuItems.length).to.equal(5); // 5 total pages }); it('76. should render mini prev button', async () => { const el = await fixture(html``); await el.updateComplete; const prevBtn = el.shadowRoot!.querySelector('.mini-prev-button'); expect(prevBtn).to.exist; }); it('77. should render mini next button', async () => { const el = await fixture(html``); await el.updateComplete; const nextBtn = el.shadowRoot!.querySelector('.mini-next-button'); expect(nextBtn).to.exist; }); it('78. should render mini-nav container', async () => { const el = await fixture(html``); await el.updateComplete; const nav = el.shadowRoot!.querySelector('.mini-nav'); expect(nav).to.exist; }); it('79. should render mini-pagination list', async () => { const el = await fixture(html``); await el.updateComplete; const ul = el.shadowRoot!.querySelector('.mini-pagination'); expect(ul).to.exist; }); it('80. should not render pager-container in mini variant', async () => { const el = await fixture(html``); await el.updateComplete; const pagerContainer = el.shadowRoot!.querySelector('.pager-container'); expect(pagerContainer).to.be.null; }); it('81. should not render page number buttons in mini variant', async () => { const el = await fixture(html``); await el.updateComplete; // mini only has prev/next, no numbered page buttons in the list const ellipsis = el.shadowRoot!.querySelector('.ellipsis-dropdown'); expect(ellipsis).to.be.null; }); // ═══════════════════════════════════════════════════ // NAVIGATION — PREV / NEXT BUTTONS // ═══════════════════════════════════════════════════ it('82. should disable prev button on first page (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; const prevBtn = el.shadowRoot!.querySelector('.prev-button') as HTMLElement; expect(prevBtn.hasAttribute('disabled')).to.be.true; }); it('83. should enable prev button when not on first page (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; const prevBtn = el.shadowRoot!.querySelector('.prev-button') as HTMLElement; expect(prevBtn.hasAttribute('disabled')).to.be.false; }); it('84. should disable next button on last page (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; const nextBtn = el.shadowRoot!.querySelector('.next-button') as HTMLElement; expect(nextBtn.hasAttribute('disabled')).to.be.true; }); it('85. should enable next button when not on last page (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; const nextBtn = el.shadowRoot!.querySelector('.next-button') as HTMLElement; expect(nextBtn.hasAttribute('disabled')).to.be.false; }); it('86. should disable mini prev button on first page', async () => { const el = await fixture(html``); await el.updateComplete; const prevBtn = el.shadowRoot!.querySelector('.mini-prev-button') as HTMLElement; expect(prevBtn.hasAttribute('disabled')).to.be.true; }); it('87. should enable mini prev button when not on first page', async () => { const el = await fixture(html``); await el.updateComplete; const prevBtn = el.shadowRoot!.querySelector('.mini-prev-button') as HTMLElement; expect(prevBtn.hasAttribute('disabled')).to.be.false; }); it('88. should disable mini next button on last page', async () => { const el = await fixture(html``); await el.updateComplete; const nextBtn = el.shadowRoot!.querySelector('.mini-next-button') as HTMLElement; expect(nextBtn.hasAttribute('disabled')).to.be.true; }); it('89. should enable mini next button when not on last page', async () => { const el = await fixture(html``); await el.updateComplete; const nextBtn = el.shadowRoot!.querySelector('.mini-next-button') as HTMLElement; expect(nextBtn.hasAttribute('disabled')).to.be.false; }); // ═══════════════════════════════════════════════════ // NAVIGATION — goToPage + nile-change EVENT // ═══════════════════════════════════════════════════ it('90. should navigate to next page on next button click (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; let detail: any; el.addEventListener('nile-change', ((e: CustomEvent) => { detail = e.detail; }) as EventListener); const nextBtn = el.shadowRoot!.querySelector('.next-button') as HTMLElement; nextBtn.click(); await el.updateComplete; expect(detail).to.exist; expect(detail.page).to.equal(2); expect(detail.previousPage).to.equal(1); expect(detail.pageSize).to.equal(10); }); it('91. should navigate to previous page on prev button click (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; let detail: any; el.addEventListener('nile-change', ((e: CustomEvent) => { detail = e.detail; }) as EventListener); const prevBtn = el.shadowRoot!.querySelector('.prev-button') as HTMLElement; prevBtn.click(); await el.updateComplete; expect(detail).to.exist; expect(detail.page).to.equal(4); expect(detail.previousPage).to.equal(5); }); it('92. should navigate to clicked page number (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; let detail: any; el.addEventListener('nile-change', ((e: CustomEvent) => { detail = e.detail; }) as EventListener); // Find page 3 button const pageButtons = el.shadowRoot!.querySelectorAll('ul.pagination > li > nile-button:not(.prev-button):not(.next-button)'); const page3Btn = Array.from(pageButtons).find(btn => btn.textContent!.trim() === '3') as HTMLElement; expect(page3Btn).to.exist; page3Btn!.click(); await el.updateComplete; expect(detail.page).to.equal(3); expect(detail.previousPage).to.equal(1); }); it('93. should update currentPage property after navigation', async () => { const el = await fixture(html``); await el.updateComplete; const nextBtn = el.shadowRoot!.querySelector('.next-button') as HTMLElement; nextBtn.click(); await el.updateComplete; expect(el.currentPage).to.equal(2); }); it('94. should navigate to next page on mini next button click', async () => { const el = await fixture(html``); await el.updateComplete; let detail: any; el.addEventListener('nile-change', ((e: CustomEvent) => { detail = e.detail; }) as EventListener); const nextBtn = el.shadowRoot!.querySelector('.mini-next-button') as HTMLElement; nextBtn.click(); await el.updateComplete; expect(detail.page).to.equal(2); expect(detail.previousPage).to.equal(1); }); it('95. should navigate to prev page on mini prev button click', async () => { const el = await fixture(html``); await el.updateComplete; let detail: any; el.addEventListener('nile-change', ((e: CustomEvent) => { detail = e.detail; }) as EventListener); const prevBtn = el.shadowRoot!.querySelector('.mini-prev-button') as HTMLElement; prevBtn.click(); await el.updateComplete; expect(detail.page).to.equal(4); expect(detail.previousPage).to.equal(5); }); // ═══════════════════════════════════════════════════ // DISABLED STATE // ═══════════════════════════════════════════════════ it('96. should not navigate when disabled (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; let eventFired = false; el.addEventListener('nile-change', () => { eventFired = true; }); const nextBtn = el.shadowRoot!.querySelector('.next-button') as HTMLElement; nextBtn.click(); await el.updateComplete; expect(eventFired).to.be.false; expect(el.currentPage).to.equal(5); }); it('97. should disable all nav buttons when disabled is true (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; const prevBtn = el.shadowRoot!.querySelector('.prev-button') as HTMLElement; const nextBtn = el.shadowRoot!.querySelector('.next-button') as HTMLElement; expect(prevBtn.hasAttribute('disabled')).to.be.true; expect(nextBtn.hasAttribute('disabled')).to.be.true; }); it('98. should disable all page number buttons when disabled (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; const pageButtons = el.shadowRoot!.querySelectorAll('ul.pagination > li > nile-button'); pageButtons.forEach(btn => { expect((btn as HTMLElement).hasAttribute('disabled')).to.be.true; }); }); it('99. should disable page-size dropdown trigger when disabled (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; const dropdown = el.shadowRoot!.querySelector('.page-size-dropdown') as HTMLElement; expect(dropdown.hasAttribute('disabled')).to.be.true; }); it('100. should not navigate when disabled (mini)', async () => { const el = await fixture(html``); await el.updateComplete; let eventFired = false; el.addEventListener('nile-change', () => { eventFired = true; }); const nextBtn = el.shadowRoot!.querySelector('.mini-next-button') as HTMLElement; nextBtn.click(); await el.updateComplete; expect(eventFired).to.be.false; expect(el.currentPage).to.equal(5); }); it('101. should disable mini nav buttons when disabled', async () => { const el = await fixture(html``); await el.updateComplete; const prevBtn = el.shadowRoot!.querySelector('.mini-prev-button') as HTMLElement; const nextBtn = el.shadowRoot!.querySelector('.mini-next-button') as HTMLElement; expect(prevBtn.hasAttribute('disabled')).to.be.true; expect(nextBtn.hasAttribute('disabled')).to.be.true; }); it('102. should disable compact dropdown when disabled', async () => { const el = await fixture(html``); await el.updateComplete; const dropdown = el.shadowRoot!.querySelector('.compact-dropdown') as HTMLElement; expect(dropdown.hasAttribute('disabled')).to.be.true; }); it('103. should disable mini page dropdown when disabled', async () => { const el = await fixture(html``); await el.updateComplete; const dropdown = el.shadowRoot!.querySelector('.mini-page-dropdown') as HTMLElement; expect(dropdown.hasAttribute('disabled')).to.be.true; }); // ═══════════════════════════════════════════════════ // PAGE SIZE CHANGE // ═══════════════════════════════════════════════════ it('104. should emit nile-change with page=1 on page size change (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; let detail: any; el.addEventListener('nile-change', ((e: CustomEvent) => { detail = e.detail; }) as EventListener); const menuItems = el.shadowRoot!.querySelectorAll('.page-size-dropdown nile-menu-item'); // Click the first option (10) (menuItems[0] as HTMLElement).click(); await el.updateComplete; expect(detail).to.exist; expect(detail.page).to.equal(1); expect(detail.previousPage).to.equal(3); expect(detail.pageSize).to.equal(10); }); it('105. should reset to page 1 after page size change', async () => { const el = await fixture(html``); await el.updateComplete; const menuItems = el.shadowRoot!.querySelectorAll('.page-size-dropdown nile-menu-item'); (menuItems[0] as HTMLElement).click(); await el.updateComplete; expect(el.currentPage).to.equal(1); expect(el.pageSize).to.equal(10); }); it('106. should not emit event when selecting same page size', async () => { const el = await fixture(html``); await el.updateComplete; let eventFired = false; el.addEventListener('nile-change', () => { eventFired = true; }); // 50 is the 3rd option (index 2) in default [10,25,50,100] const menuItems = el.shadowRoot!.querySelectorAll('.page-size-dropdown nile-menu-item'); (menuItems[2] as HTMLElement).click(); await el.updateComplete; expect(eventFired).to.be.false; }); it('107. should not change page size when disabled', async () => { const el = await fixture(html``); await el.updateComplete; let eventFired = false; el.addEventListener('nile-change', () => { eventFired = true; }); const menuItems = el.shadowRoot!.querySelectorAll('.page-size-dropdown nile-menu-item'); (menuItems[0] as HTMLElement).click(); await el.updateComplete; expect(eventFired).to.be.false; expect(el.pageSize).to.equal(50); }); it('108. should emit nile-change with page=1 on compact page size change', async () => { const el = await fixture(html``); await el.updateComplete; let detail: any; el.addEventListener('nile-change', ((e: CustomEvent) => { detail = e.detail; }) as EventListener); const menuItems = el.shadowRoot!.querySelectorAll('.compact-scroll-wrapper nile-menu-item'); (menuItems[0] as HTMLElement).click(); await el.updateComplete; expect(detail).to.exist; expect(detail.page).to.equal(1); expect(detail.pageSize).to.equal(10); }); // ═══════════════════════════════════════════════════ // DYNAMIC PROPERTY UPDATES // ═══════════════════════════════════════════════════ it('109. should re-render when totalItems changes', async () => { const el = await fixture(html``); await el.updateComplete; el.totalItems = 200; await el.updateComplete; // With 200 items / 10 per page = 20 pages, should show ellipsis const ellipsis = el.shadowRoot!.querySelector('.ellipsis-dropdown'); expect(ellipsis).to.exist; }); it('110. should re-render when currentPage changes programmatically', async () => { const el = await fixture(html``); await el.updateComplete; el.currentPage = 3; await el.updateComplete; const pageButtons = el.shadowRoot!.querySelectorAll('ul.pagination > li > nile-button:not(.prev-button):not(.next-button)'); const currentBtn = Array.from(pageButtons).find(btn => btn.getAttribute('variant') === 'primary'); expect(currentBtn).to.exist; expect(currentBtn!.textContent!.trim()).to.equal('3'); }); it('111. should re-render when pageSize changes programmatically', async () => { const el = await fixture(html``); await el.updateComplete; el.pageSize = 25; await el.updateComplete; // 100/25 = 4 pages, so all pages visible (<=7) const pageButtons = el.shadowRoot!.querySelectorAll('ul.pagination > li > nile-button:not(.prev-button):not(.next-button)'); expect(pageButtons.length).to.equal(4); }); it('112. should re-render when variant changes', async () => { const el = await fixture(html``); await el.updateComplete; expect(el.shadowRoot!.querySelector('.pagination-wrapper.fluid')).to.exist; el.variant = 'compact'; await el.updateComplete; expect(el.shadowRoot!.querySelector('.pagination-wrapper.compact')).to.exist; }); it('113. should switch to mini rendering when variant changes to mini', async () => { const el = await fixture(html``); await el.updateComplete; el.variant = 'mini'; await el.updateComplete; expect(el.shadowRoot!.querySelector('.pagination-wrapper.mini')).to.exist; expect(el.shadowRoot!.querySelector('.mini-showing-label')).to.exist; }); it('114. should update disabled state dynamically', async () => { const el = await fixture(html``); await el.updateComplete; el.disabled = true; await el.updateComplete; const prevBtn = el.shadowRoot!.querySelector('.prev-button') as HTMLElement; expect(prevBtn.hasAttribute('disabled')).to.be.true; }); // ═══════════════════════════════════════════════════ // EVENT DETAILS // ═══════════════════════════════════════════════════ it('115. should emit nile-change event that bubbles', async () => { const el = await fixture(html``); await el.updateComplete; let bubbled = false; document.addEventListener('nile-change', () => { bubbled = true; }, { once: true }); const nextBtn = el.shadowRoot!.querySelector('.next-button') as HTMLElement; nextBtn.click(); await el.updateComplete; expect(bubbled).to.be.true; }); it('116. should include page, previousPage, and pageSize in event detail', async () => { const el = await fixture(html``); await el.updateComplete; let detail: any; el.addEventListener('nile-change', ((e: CustomEvent) => { detail = e.detail; }) as EventListener); const nextBtn = el.shadowRoot!.querySelector('.next-button') as HTMLElement; nextBtn.click(); await el.updateComplete; expect(detail).to.have.property('page', 3); expect(detail).to.have.property('previousPage', 2); expect(detail).to.have.property('pageSize', 25); }); // ═══════════════════════════════════════════════════ // PAGINATION ITEMS (ELLIPSIS LOGIC) — FLUID // ═══════════════════════════════════════════════════ it('117. should show all pages without ellipsis when totalPages <= 7', async () => { const el = await fixture(html``); await el.updateComplete; const ellipsis = el.shadowRoot!.querySelector('.ellipsis-dropdown'); expect(ellipsis).to.be.null; }); it('118. should show ellipsis when totalPages > 7', async () => { const el = await fixture(html``); await el.updateComplete; const ellipsis = el.shadowRoot!.querySelector('.ellipsis-dropdown'); expect(ellipsis).to.exist; }); it('119. should show right ellipsis when on early pages', async () => { const el = await fixture(html``); await el.updateComplete; const ellipsisDropdowns = el.shadowRoot!.querySelectorAll('.ellipsis-dropdown'); expect(ellipsisDropdowns.length).to.be.greaterThan(0); }); it('120. should show left ellipsis when on late pages', async () => { const el = await fixture(html``); await el.updateComplete; const ellipsisDropdowns = el.shadowRoot!.querySelectorAll('.ellipsis-dropdown'); expect(ellipsisDropdowns.length).to.be.greaterThan(0); }); it('121. should show two ellipsis when on middle pages', async () => { const el = await fixture(html``); await el.updateComplete; const ellipsisDropdowns = el.shadowRoot!.querySelectorAll('.ellipsis-dropdown'); expect(ellipsisDropdowns.length).to.equal(2); }); it('122. should render hidden pages in ellipsis dropdown', async () => { const el = await fixture(html``); await el.updateComplete; const ellipsisMenuItems = el.shadowRoot!.querySelectorAll('.ellipsis-scroll-wrapper nile-menu-item'); expect(ellipsisMenuItems.length).to.be.greaterThan(0); }); // ═══════════════════════════════════════════════════ // EDGE CASES // ═══════════════════════════════════════════════════ it('123. should handle single page (totalItems <= pageSize)', async () => { const el = await fixture(html``); await el.updateComplete; const prevBtn = el.shadowRoot!.querySelector('.prev-button') as HTMLElement; const nextBtn = el.shadowRoot!.querySelector('.next-button') as HTMLElement; expect(prevBtn.hasAttribute('disabled')).to.be.true; expect(nextBtn.hasAttribute('disabled')).to.be.true; }); it('124. should handle totalItems equal to pageSize (exactly one page)', async () => { const el = await fixture(html``); await el.updateComplete; const pageButtons = el.shadowRoot!.querySelectorAll('ul.pagination > li > nile-button:not(.prev-button):not(.next-button)'); expect(pageButtons.length).to.equal(1); }); it('125. should handle large totalItems', async () => { const el = await fixture(html``); await el.updateComplete; expect(el.shadowRoot!.querySelector('nav')).to.exist; }); it('126. should handle mini with single page', async () => { const el = await fixture(html``); await el.updateComplete; const ofLabel = el.shadowRoot!.querySelector('.mini-of-label'); expect(ofLabel!.textContent).to.contain('of 1'); const prevBtn = el.shadowRoot!.querySelector('.mini-prev-button') as HTMLElement; const nextBtn = el.shadowRoot!.querySelector('.mini-next-button') as HTMLElement; expect(prevBtn.hasAttribute('disabled')).to.be.true; expect(nextBtn.hasAttribute('disabled')).to.be.true; }); it('127. should handle compact with single page', async () => { const el = await fixture(html``); await el.updateComplete; const prevBtn = el.shadowRoot!.querySelector('.prev-button') as HTMLElement; const nextBtn = el.shadowRoot!.querySelector('.next-button') as HTMLElement; expect(prevBtn.hasAttribute('disabled')).to.be.true; expect(nextBtn.hasAttribute('disabled')).to.be.true; }); // ═══════════════════════════════════════════════════ // ACCESSIBILITY & PARTS // ═══════════════════════════════════════════════════ it('128. should have aria-label="Pagination" on nav in fluid variant', async () => { const el = await fixture(html``); const nav = el.shadowRoot!.querySelector('nav'); expect(nav!.getAttribute('aria-label')).to.equal('Pagination'); }); it('129. should have aria-label="Pagination" on nav in compact variant', async () => { const el = await fixture(html``); const nav = el.shadowRoot!.querySelector('nav'); expect(nav!.getAttribute('aria-label')).to.equal('Pagination'); }); it('130. should have aria-label="Pagination" on mini nav', async () => { const el = await fixture(html``); const nav = el.shadowRoot!.querySelector('nav'); expect(nav!.getAttribute('aria-label')).to.equal('Pagination'); }); it('131. should expose wrapper part', async () => { const el = await fixture(html``); const wrapper = el.shadowRoot!.querySelector('[part="wrapper"]'); expect(wrapper).to.exist; }); it('132. should expose range-text part in fluid variant', async () => { const el = await fixture(html``); await el.updateComplete; const rangeText = el.shadowRoot!.querySelector('[part="range-text"]'); expect(rangeText).to.exist; }); it('133. should expose pagesize-label part in fluid variant', async () => { const el = await fixture(html``); const label = el.shadowRoot!.querySelector('[part="pagesize-label"]'); expect(label).to.exist; }); it('134. should expose page-list part in fluid variant', async () => { const el = await fixture(html``); const pageList = el.shadowRoot!.querySelector('[part="page-list"]'); expect(pageList).to.exist; }); it('135. should expose mini parts', async () => { const el = await fixture(html``); await el.updateComplete; expect(el.shadowRoot!.querySelector('[part="mini-showing-label"]')).to.exist; expect(el.shadowRoot!.querySelector('[part="mini-page-dropdown"]')).to.exist; expect(el.shadowRoot!.querySelector('[part="mini-of-label"]')).to.exist; expect(el.shadowRoot!.querySelector('[part="mini-nav"]')).to.exist; expect(el.shadowRoot!.querySelector('[part="mini-page-list"]')).to.exist; }); // ═══════════════════════════════════════════════════ // NILE-ICON RENDERING // ═══════════════════════════════════════════════════ it('136. should render nile-icon elements in fluid pagination', async () => { const el = await fixture(html``); const icons = el.shadowRoot!.querySelectorAll('nile-icon'); expect(icons.length).to.be.greaterThan(0); }); it('137. should render chevron icon in page-size dropdown (fluid)', async () => { const el = await fixture(html``); const chevron = el.shadowRoot!.querySelector('.page-size-dropdown .chevron'); expect(chevron).to.exist; }); it('138. should render chevron icon in mini page dropdown', async () => { const el = await fixture(html``); await el.updateComplete; const chevron = el.shadowRoot!.querySelector('.mini-page-dropdown .chevron'); expect(chevron).to.exist; }); it('139. should render chevron icon in compact page-size dropdown', async () => { const el = await fixture(html``); await el.updateComplete; const chevron = el.shadowRoot!.querySelector('.compact-dropdown .chevron'); expect(chevron).to.exist; }); it('140. should render chevron icon in compact page dropdown', async () => { const el = await fixture(html``); await el.updateComplete; const chevron = el.shadowRoot!.querySelector('.compact-dropdown1 .chevron'); expect(chevron).to.exist; }); // ═══════════════════════════════════════════════════ // NEGATIVE DOM CHECKS // ═══════════════════════════════════════════════════ it('141. should not contain canvas', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('canvas')).to.be.null; }); it('142. should not contain video', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('video')).to.be.null; }); it('143. should not contain form', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('form')).to.be.null; }); it('144. should not contain img', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('img')).to.be.null; }); it('145. should not contain table', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('table')).to.be.null; }); it('146. should not contain nile-input', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('nile-input')).to.be.null; }); it('147. should not contain nile-dialog', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('nile-dialog')).to.be.null; }); it('148. should not contain nile-checkbox', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('nile-checkbox')).to.be.null; }); it('149. should not contain nile-select', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('nile-select')).to.be.null; }); it('150. should not contain anchor tag', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('a')).to.be.null; }); // ═══════════════════════════════════════════════════ // GENERIC DOM ATTRIBUTES // ═══════════════════════════════════════════════════ it('151. should support class attribute', async () => { const el = await fixture(html``); expect(el.classList.contains('pg')).to.be.true; }); it('152. should support id attribute', async () => { const el = await fixture(html``); expect(el.id).to.equal('pg1'); }); it('153. should support hidden attribute', async () => { const el = await fixture(html``); expect(el.hidden).to.be.true; }); it('154. should support data attributes', async () => { const el = await fixture(html``); expect(el.dataset.test).to.equal('val'); }); it('155. should support aria-label attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('aria-label')).to.equal('Page nav'); }); it('156. should support dir attribute', async () => { const el = await fixture(html``); expect(el.dir).to.equal('rtl'); }); it('157. should support style attribute', async () => { const el = await fixture(html``); expect(el.style.color).to.equal('red'); }); it('158. should support role attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('role')).to.equal('navigation'); }); // ═══════════════════════════════════════════════════ // CUSTOM PAGE SIZE OPTIONS // ═══════════════════════════════════════════════════ it('159. should render custom page size options (fluid)', async () => { const el = await fixture(html``); await el.updateComplete; const menuItems = el.shadowRoot!.querySelectorAll('.page-size-dropdown nile-menu-item'); expect(menuItems.length).to.equal(3); }); it('160. should render custom page size options (compact)', async () => { const el = await fixture(html``); await el.updateComplete; const menuItems = el.shadowRoot!.querySelectorAll('.compact-scroll-wrapper nile-menu-item'); expect(menuItems.length).to.equal(3); }); // ═══════════════════════════════════════════════════ // RE-RENDER & LIFECYCLE // ═══════════════════════════════════════════════════ it('161. should handle multiple rapid requestUpdate calls', async () => { const el = await fixture(html``); for (let i = 0; i < 5; i++) { el.requestUpdate(); await el.updateComplete; } expect(el.shadowRoot).to.not.be.null; }); it('162. should have render method', async () => { const el = await fixture(html``); expect(el.render).to.be.a('function'); }); it('163. should resolve updateComplete', async () => { const el = await fixture(html``); const result = await el.updateComplete; expect(result).to.not.be.undefined; }); // ═══════════════════════════════════════════════════ // COMPACT VARIANT — PAGE NAVIGATION VIA DROPDOWN // ═══════════════════════════════════════════════════ it('164. should navigate to selected page in compact dropdown', async () => { const el = await fixture(html``); await el.updateComplete; let detail: any; el.addEventListener('nile-change', ((e: CustomEvent) => { detail = e.detail; }) as EventListener); const menuItems = el.shadowRoot!.querySelectorAll('.compact-scroll-wrapper1 nile-menu-item'); // Click page 3 (index 2) (menuItems[2] as HTMLElement).click(); await el.updateComplete; expect(detail).to.exist; expect(detail.page).to.equal(3); expect(detail.previousPage).to.equal(1); }); // ═══════════════════════════════════════════════════ // MINI VARIANT — PAGE NAVIGATION VIA DROPDOWN // ═══════════════════════════════════════════════════ it('165. should navigate to selected page in mini dropdown', async () => { const el = await fixture(html``); await el.updateComplete; let detail: any; el.addEventListener('nile-change', ((e: CustomEvent) => { detail = e.detail; }) as EventListener); const menuItems = el.shadowRoot!.querySelectorAll('.mini-scroll-wrapper nile-menu-item'); // Click page 4 (index 3) (menuItems[3] as HTMLElement).click(); await el.updateComplete; expect(detail).to.exist; expect(detail.page).to.equal(4); expect(detail.previousPage).to.equal(1); }); // ═══════════════════════════════════════════════════ // NILE-BUTTON & NILE-MENU PRESENCE // ═══════════════════════════════════════════════════ it('166. should have nile-button in shadow root', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('nile-button')).to.exist; }); it('167. should have nile-menu in shadow root', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('nile-menu')).to.exist; }); it('168. should have nile-dropdown in shadow root', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('nile-dropdown')).to.exist; }); it('169. should have nile-menu-item in shadow root', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('nile-menu-item')).to.exist; }); it('170. should have nile-icon in shadow root', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('nile-icon')).to.exist; }); // ═══════════════════════════════════════════════════ // FULL INTEGRATION // ═══════════════════════════════════════════════════ it('171. full integration — fluid variant with all props', async () => { const el = await fixture(html` `); await el.updateComplete; expect(el.totalItems).to.equal(200); expect(el.pageSize).to.equal(25); expect(el.currentPage).to.equal(3); expect(el.variant).to.equal('fluid'); expect(el.id).to.equal('pg-fluid'); expect(el.shadowRoot!.querySelector('.pagination-wrapper.fluid')).to.exist; expect(el.shadowRoot!.querySelector('nav')).to.exist; expect(el.shadowRoot!.querySelector('.range-text')).to.exist; expect(el.shadowRoot!.querySelector('.page-size-select')).to.exist; }); it('172. full integration — compact variant with all props', async () => { const el = await fixture(html` `); await el.updateComplete; expect(el.totalItems).to.equal(100); expect(el.pageSize).to.equal(10); expect(el.currentPage).to.equal(5); expect(el.variant).to.equal('compact'); expect(el.shadowRoot!.querySelector('.pagination-wrapper.compact')).to.exist; expect(el.shadowRoot!.querySelector('.compact-pagination')).to.exist; expect(el.shadowRoot!.querySelector('.current-page-btn')).to.exist; }); it('173. full integration — mini variant with all props', async () => { const el = await fixture(html` `); await el.updateComplete; expect(el.totalItems).to.equal(100); expect(el.pageSize).to.equal(10); expect(el.currentPage).to.equal(5); expect(el.variant).to.equal('mini'); expect(el.shadowRoot!.querySelector('.pagination-wrapper.mini')).to.exist; expect(el.shadowRoot!.querySelector('.mini-showing-label')).to.exist; expect(el.shadowRoot!.querySelector('.mini-of-label')).to.exist; expect(el.shadowRoot!.querySelector('.mini-page-btn')).to.exist; expect(el.shadowRoot!.querySelector('.mini-prev-button')).to.exist; expect(el.shadowRoot!.querySelector('.mini-next-button')).to.exist; }); });