/* eslint-disable no-restricted-globals */ import { elementUpdated, expect, fixture, oneEvent } from '@open-wc/testing'; import { html } from 'lit'; import { IxGrid } from '../IxGrid.js'; import { IxGridRowFilter } from '../components/IxGridRowFilter.js'; import '../ix-grid-no-rows.js'; import '../ix-grid.js'; const rows = [ { name: 'one', }, { name: 'two', }, { name: 'three', }, ]; const columns = [ { name: 'one', header: 'one', bodyRenderer: row => html` ${row.name}`, filterable: true, filterOperators: ['equals'], }, { name: 'two', header: 'one', bodyRenderer: row => html` ${row.name}`, filterable: true, filterOperators: ['equals', 'contains'], }, { name: 'three', header: 'one', bodyRenderer: row => html` ${row.name}`, filterable: true, filterOperators: ['equals', 'contains'], }, ]; const sessionStorageKey = 'test-session-key'; describe('IxGrid', () => { beforeEach(() => { sessionStorage.clear(); }); it('renders a grid', async () => { const el = await fixture(html``); expect(el).to.not.be.null; }); it('renders the correct number of rows', async () => { const el = await fixture(html``); expect(rows.length).to.equal(3); expect(el).to.not.be.null; }); it('renders no rows component state', async () => { const el = await fixture(html``); const noRows = el.shadowRoot?.querySelector('slot[name="no-rows"]'); expect(noRows).to.not.be.null; }); it('renders an ix-grid-row-filter', async () => { const el = await fixture(html``); const rowFilter = el.shadowRoot?.querySelector('ix-grid-row-filter'); expect(rowFilter).to.be.instanceOf(IxGridRowFilter); expect((rowFilter).filterValueChangeDebounceTime).to.equal( 1000 ); }); it('resets pagination upon filter change', async () => { const el = await fixture(html``); const pagination = el.shadowRoot?.querySelector('ix-pagination'); pagination!.dispatchEvent( new CustomEvent('updatePagination', { detail: { page: 2, pageSize: 10, }, bubbles: true, composed: true, }) ); expect(el.page).to.equal(2); const rowFilter = el.shadowRoot?.querySelector('ix-grid-row-filter'); rowFilter!.dispatchEvent( new CustomEvent('rowFilter', { detail: { resetPage: true, }, bubbles: true, composed: true, }) ); expect(el.page).to.equal(1); }); it('should read and set sort, order, page and page size from URL if readParamsFromURL is set to true', async () => { const el = await fixture(html``); sessionStorage.removeItem(`urlPageSizeRead`); const url = '?sort=lastName&order=asc&page=2&size=5'; history.pushState(null, '', `${location.pathname}${url}`); el.firstUpdated(); expect(el.page).to.be.eq(2); expect(el.pageSize).to.be.eq(5); expect(el.sortedColumn).to.be.eq('lastName'); expect(el.sortDirection).to.be.eq('asc'); }); it('sessionStorage sets the URL', async () => { sessionStorage.setItem( `grid-${sessionStorageKey}`, JSON.stringify({ pageSize: 25, }) ); const el = await fixture(html``); await elementUpdated(el); expect(location.href).to.contain('size=25'); }); it('page and page size from URL sets sessionStorage', async () => { sessionStorage.setItem( `grid-${sessionStorageKey}`, JSON.stringify({ pageSize: 10, }) ); const el = await fixture(html``); const url = '?sort=lastName&order=asc&page=2&size=25'; history.pushState(null, '', `${location.pathname}${url}`); await elementUpdated(el); const sessionData = JSON.parse( sessionStorage.getItem(`grid-${sessionStorageKey}`) || '{}' ); expect(sessionData.pageSize).to.be.eq(25); }); it('should set sort, order, page, page size and filters in the URL when addParamsToURL is set to true', async () => { const columnsWithFilters = [ { name: 'firstName', header: 'First name', filterable: true, dataType: 'string', filterOperators: ['contains', 'equals'], }, { name: 'lastName', header: 'Last Name', filterable: true, dataType: 'string', filterOperators: ['equals'], }, { name: 'createdDate', header: 'Created Date', filterable: true, dataType: 'dateTime', filterOperators: ['equals'], }, ]; const existingSearchParams = '?realUsername=Earl&userAge=30'; history.pushState(null, '', `${location.pathname}${existingSearchParams}`); const el = await fixture(html``); const rowFilter = el.shadowRoot?.querySelector('ix-grid-row-filter'); rowFilter!.dispatchEvent( new CustomEvent('rowFilter', { detail: { filters: [ { columnField: 'firstName', operatorValue: 'contains', value: 'test', }, { columnField: 'createdDate', operatorValue: 'equals', value: '2024-10-10', }, ], }, bubbles: true, composed: true, }) ); el.sortedColumn = 'firstName'; el.sortDirection = 'desc'; const pagination = el.shadowRoot?.querySelector('ix-pagination'); pagination!.dispatchEvent( new CustomEvent('updatePagination', { detail: { page: 3, pageSize: 5, }, bubbles: true, composed: true, }) ); expect(location.search).to.be.eq( `${existingSearchParams}&sort=firstName&order=desc&page=3&size=5&firstName_contains=test&createdDate_equals=2024-10-10` ); }); describe('IxGrid updateSort', () => { it('should update URL with sort, order, page, pageSize, filters', async () => { history.replaceState(null, '', '/?userId=123&token=abc'); const updateSortColumns = [ { name: 'name', header: 'Name', filterable: true, filterOperators: ['contains'], sortable: true, }, ]; const el = await fixture(html` `); await el.updateComplete; await el.handleSort('name'); const url = new URL(window.location.href); const { searchParams } = url; expect(searchParams.get('sort')).to.equal('name'); expect(searchParams.get('order')).to.equal('asc'); expect(searchParams.get('page')).to.equal('1'); expect(searchParams.get('size')).to.equal('10'); expect(searchParams.get('userId')).to.equal('123'); expect(searchParams.get('token')).to.equal('abc'); }); }); describe('IxGrid LocalStorage Persistence', () => { beforeEach(() => { localStorage.clear(); }); it('should reset localStorage value if columns length does not match', async () => { const tempEl = await fixture( html`` ); const localStorageKey = tempEl.columnsLocalStorageKey; // Simulating a user fully deleting and adding a fake column to their local storage localStorage.setItem( localStorageKey, JSON.stringify([{ name: 'six', header: 'six' }]) ); const el = await fixture( html`` ); await el.updateComplete; const storedColumns = JSON.parse( localStorage.getItem(el.columnsLocalStorageKey) || '[]' ); expect(storedColumns.length).to.equal(columns.length); expect(storedColumns.map(c => c.name)).to.deep.equal( columns.map(c => c.name) ); }); it('should reset localStorage value if it contains a column that does not exist in the provided columns', async () => { const tempEl = await fixture( html`` ); const localStorageKey = tempEl.columnsLocalStorageKey; // Simulating a user adding an additional column to local storage localStorage.setItem( localStorageKey, JSON.stringify([ ...columns, { name: 'nonexistent', header: 'Nonexistent' }, ]) ); const el = await fixture( html`` ); await el.updateComplete; const storedColumns = JSON.parse( localStorage.getItem(el.columnsLocalStorageKey) || '[]' ); expect(storedColumns.length).to.equal(columns.length); expect(storedColumns.map(c => c.name)).to.deep.equal( columns.map(c => c.name) ); }); it('should delete localStorage if a provided column does not exist in localStorage and not create new local storage', async () => { const initialColumns = columns.slice(0, -1); const tempEl = await fixture( html`` ); const localStorageKey = tempEl.columnsLocalStorageKey; // Simulating a user deleting most columns from local storage localStorage.setItem(localStorageKey, JSON.stringify(initialColumns)); const el = await fixture( html`` ); await el.updateComplete; expect(localStorage.getItem(el.columnsLocalStorageKey)).to.equal(null); }); it('should reorder columns based on table header flex order', async () => { const el = await fixture( html`` ); await el.updateComplete; const headerCells = el.grid.shadowRoot?.querySelectorAll('th'); if (!headerCells || headerCells.length !== columns.length) { throw new Error( 'Table headers not found or do not match expected count' ); } const orderMap = [2, 0, 1]; headerCells.forEach((th, index) => { const thElement = th as HTMLElement; thElement.style.order = orderMap[index].toString(); }); await el.updateComplete; await el.reorderColumnsFromTable(); const reorderedColumns = [columns[1], columns[2], columns[0]]; const storedColumns = JSON.parse( localStorage.getItem(el.columnsLocalStorageKey) || '[]' ); expect(storedColumns.map(c => c.name)).to.deep.equal( reorderedColumns.map(c => c.name) ); }); it('should reorder columns when a filter reorder event is triggered', async () => { const el = await fixture( html`` ); await el.updateComplete; const reorderedColumns = [columns[2], columns[0], columns[1]]; const event = new CustomEvent('columnFilter', { detail: { reorderedColumns }, bubbles: true, composed: true, }); el.reorderColumnsFromFilter(event); await el.updateComplete; const storedColumns = JSON.parse( localStorage.getItem(el.columnsLocalStorageKey) || '[]' ); expect(el.displayColumns.map(c => c.name)).to.deep.equal( reorderedColumns.map(c => c.name) ); expect(storedColumns.map(c => c.name)).to.deep.equal( reorderedColumns.map(c => c.name) ); }); }); describe('IxGrid handlePopState', () => { const handlePopStateColumns = [ { name: 'customcolumnfilter', header: 'Custom', filterable: true, filterOperators: ['contains'], }, { name: 'id', header: 'ID', filterable: true, filterOperators: ['equals'], }, ]; it('should handle empty sort/order/page/size in URL', async () => { history.replaceState(null, '', '/?sort=&order=&page=&size='); const el = await fixture(html` `); await el.updateComplete; const changeEventPromise = oneEvent(el, 'change'); window.dispatchEvent(new PopStateEvent('popstate')); const event = await changeEventPromise; expect(el.sortedColumn).to.equal(''); expect(el.sortDirection).to.equal(''); expect(el.page).to.equal(1); expect(el.pageSize).to.equal(10); expect(event.detail.filters).to.deep.equal({}); }); it('should handle valid sort/order/page/size in URL', async () => { history.replaceState(null, '', '/?sort=id&order=desc&page=1&size=20'); const el = await fixture(html` `); await el.updateComplete; const changeEventPromise = oneEvent(el, 'change'); window.dispatchEvent(new PopStateEvent('popstate')); const event = await changeEventPromise; expect(el.sortedColumn).to.equal('id'); expect(el.sortDirection).to.equal('desc'); expect(el.page).to.equal(1); expect(el.pageSize).to.equal(20); expect(event.detail.filters).to.deep.equal({}); }); it('should update filters from URL with custom filter', async () => { history.replaceState(null, '', '/?customcolumnfilter_contains=foobar'); const el = await fixture(html` `); await el.updateComplete; const changeEventPromise = oneEvent(el, 'change'); window.dispatchEvent(new PopStateEvent('popstate')); const event = await changeEventPromise; expect(event.detail.filters).to.deep.equal({ customcolumnfilter: 'foobar', }); }); it('should dispatch a change event with correct detail', async () => { history.replaceState( null, '', '/?sort=id&order=desc&page=1&size=20&customcolumnfilter_contains=foobar' ); const el = await fixture(html` `); await el.updateComplete; const changeEventPromise = oneEvent(el, 'change'); window.dispatchEvent(new PopStateEvent('popstate')); const event = await changeEventPromise; expect(event.detail).to.include({ columnName: 'id', sortOrder: 'desc', page: 1, pageSize: 20, }); expect(event.detail.filters).to.deep.equal({ customcolumnfilter: 'foobar', }); expect(event.detail.filtersOperators).to.deep.equal([ { columnField: 'customcolumnfilter', operator: 'contains', }, ]); }); }); });