diff --git a/packages/vuetify/src/components/VDataTable/__tests__/VDataTable.spec.browser.tsx b/packages/vuetify/src/components/VDataTable/__tests__/VDataTable.spec.browser.tsx deleted file mode 100644 index ade94d615c1..00000000000 --- a/packages/vuetify/src/components/VDataTable/__tests__/VDataTable.spec.browser.tsx +++ /dev/null @@ -1,107 +0,0 @@ -// Components -import { VDataTable } from '../' - -// Utilities -import { render, screen, userEvent } from '@test' -import { ref } from 'vue' - -describe('VDataTable', () => { - it('mirrors external updates', async () => { - const items = ref([ - { id: 1, name: 'Item 1' }, - { id: 2, name: 'Item 2' }, - ]) - - render(() => ( - - )) - - const cells = screen.getAllByRole('cell') - expect.element(cells[0]).toHaveTextContent('1') - - // Update items - items.value = [{ id: 3, name: 'Item 3' }] - - await expect - .poll(() => screen.getAllByRole('cell')[0]) - .toHaveTextContent('3') - }) - it('renders an empty table when no items are provided', () => { - const items = ref([]) - render(() => ( - - )) - - expect.element(screen.getByRole('table')).toBeInTheDocument() - expect.element(screen.getByText('No data available')).toBeInTheDocument() - }) - it('shows a loading state when data is being fetched', async () => { - const loading = ref(true) - const items = ref([]) - render(() => ( - - )) - - const table = screen.getByRole('table') - expect.element(table).toHaveClass('v-data-table--loading') - - loading.value = false - expect.element(table).not.toHaveClass('v-data-table--loading') - }) - - describe('slot rendering', () => { - it('renders default slot', () => { - render(() => ) - expect(screen.getByRole('table')).toBeInTheDocument() - }) - - it('renders with custom header', () => { - const items = [ - { id: 1, name: 'Item 1' }, - { id: 2, name: 'Item 2' }, - ] - render(() => ( - - -
Custom Top Content
-
-
- )) - const customTopContent = screen.getByText('Custom Top Content') - expect(customTopContent).toBeInTheDocument() - }) - // TODO Add more slot test cases - }) - - describe('sorting functionality', () => { - it('handles column sorting', async () => { - const items = ref([ - { id: 1, name: 'B Item' }, - { id: 2, name: 'A Item' }, - ]) - - render(() => ( - - )) - - const headerCell = screen.getByText('Name') - await userEvent.click(headerCell) - - const cells = screen.getAllByRole('cell') - expect.element(cells[0]).toHaveTextContent('A Item') - }) - }) -}) diff --git a/packages/vuetify/src/components/VDataTable/__tests__/VDataTable.spec.cy.tsx b/packages/vuetify/src/components/VDataTable/__tests__/VDataTable.spec.cy.tsx new file mode 100644 index 00000000000..cde77da1731 --- /dev/null +++ b/packages/vuetify/src/components/VDataTable/__tests__/VDataTable.spec.cy.tsx @@ -0,0 +1,458 @@ +/// + +// Components +import { VDataTable } from '..' +import { Application } from '../../../../cypress/templates' + +// Utilities +import { ref } from 'vue' + +const DESSERT_HEADERS = [ + { title: 'Dessert (100g serving)', key: 'name' }, + { title: 'Calories', key: 'calories' }, + { title: 'Fat (g)', key: 'fat' }, + { title: 'Carbs (g)', key: 'carbs' }, + { title: 'Protein (g)', key: 'protein' }, + { title: 'Iron (%)', key: 'iron' }, + { title: 'Group', key: 'group' }, +] + +const DESSERT_ITEMS = [ + { + name: 'Frozen Yogurt', + calories: 159, + fat: 6.0, + carbs: 24, + protein: 4.0, + iron: '1%', + group: 1, + }, + { + name: 'Ice cream sandwich', + calories: 237, + fat: 9.0, + carbs: 37, + protein: 4.3, + iron: '1%', + group: 3, + }, + { + name: 'Eclair', + calories: 262, + fat: 16.0, + carbs: 23, + protein: 6.0, + iron: '7%', + group: 2, + }, + { + name: 'Cupcake', + calories: 305, + fat: 3.7, + carbs: 67, + protein: 4.3, + iron: '8%', + group: 2, + }, + { + name: 'Gingerbread', + calories: 356, + fat: 16.0, + carbs: 49, + protein: 3.9, + iron: '16%', + group: 3, + }, + { + name: 'Jelly bean', + calories: 375, + fat: 0.0, + carbs: 94, + protein: 0.0, + iron: '0%', + group: 1, + }, + { + name: 'Lollipop', + calories: 392, + fat: 0.2, + carbs: 98, + protein: 0, + iron: '2%', + group: 2, + }, + { + name: 'Honeycomb', + calories: 408, + fat: 3.2, + carbs: 87, + protein: 6.5, + iron: '45%', + group: 3, + }, + { + name: 'Donut', + calories: 452, + fat: 25.0, + carbs: 51, + protein: 4.9, + iron: '22%', + group: 3, + }, + { + name: 'KitKat', + calories: 518, + fat: 26.0, + carbs: 65, + protein: 7, + iron: '6%', + group: 1, + }, +] + +describe('VDataTable', () => { + it('should reset page when searching', () => { + cy.mount(({ search }: { search: string }) => ( + + + + )) + + cy.get('.v-btn[aria-label="Next page"]') + .click() + cy.setProps({ search: 'a' }) + cy.emitted(VDataTable, 'update:page') + .should('deep.equal', [[2], [1]]) + }) + + // https://github.com/vuetifyjs/vuetify/issues/16999 + it('should search in nested keys', () => { + const nestedItems = [ + { + foo: { + bar: 'hello', + }, + }, + { + foo: { + bar: 'world', + }, + }, + ] + + const headers = [ + { + key: 'unique', + value: 'foo.bar', + title: 'Column', + }, + ] + + cy.mount(props => ( + + + + )) + + cy.get('tbody tr') + .should('have.length', 2) + .invoke('text') + .should('equal', 'helloworld') + .setProps({ + search: 'hello', + }) + .get('tbody tr') + .should('have.length', 1) + .invoke('text') + .should('equal', 'hello') + }) + + it('should not emit click:row event when clicking select or expand', () => { + const onClick = cy.stub() + cy.mount(() => ( + + + + )) + + cy.get('tbody tr') + .eq(2) + .find('.v-checkbox-btn') + .click() + + cy.get('tbody tr') + .eq(3) + .find('.v-btn') + .click() + .then(() => { + expect(onClick).not.to.be.called + }) + + cy.get('tbody tr') + .eq(1) + .click() + .then(() => { + expect(onClick).to.be.calledOnce + }) + }) + + it('should show no-data-text if there are no items', () => { + cy.mount(() => ( + + + + )) + + cy.get('.v-data-table tbody tr').should('have.text', 'No data available') + }) + + // https://github.com/vuetifyjs/vuetify/issues/17226 + it('should change page if we are trying to display a page beyond current page count', () => { + const items = ref(DESSERT_ITEMS.slice()) + cy.mount(() => ( + + + + )) + + cy.get('[aria-label="Next page"]') + .click() + .then(() => { + items.value = DESSERT_ITEMS.slice(0, 5) + }) + .emitted(VDataTable, 'update:page') + .should('deep.equal', [[2], [1]]) + }) + + // https://github.com/vuetifyjs/vuetify/issues/19069 + it('should update the select all checkbox when changing the select-strategy', () => { + const strategy = ref('single') + cy.mount(() => ( + + + + )).get('thead .v-selection-control').should('not.exist') + .then(() => strategy.value = 'all') + .get('thead .v-selection-control').should('exist') + .then(() => strategy.value = 'page') + .get('thead .v-selection-control').should('exist') + }) + + describe('slots', () => { + it('should have top slot', () => { + cy.mount(() => ( + + + {{ + top: _ =>
TOP
, + }} +
+
+ )) + + cy.get('.v-data-table').find('#top').should('have.text', 'TOP') + }) + + it('should have bottom slot (and should overwrite footer)', () => { + cy.mount(() => ( + + + {{ + bottom: _ =>
BOTTOM
, + }} +
+
+ )) + + cy.get('.v-data-table').find('#bottom').should('have.text', 'BOTTOM') + cy.get('.v-data-table-footer').should('not.exist') + }) + + it('should have headers slot (and overwrite default headers)', () => { + cy.mount(() => ( + + + {{ + headers: _ =>
headers
, + }} +
+
+ )) + + cy.get('.v-data-table').find('#headers').should('have.text', 'headers') + cy.get('.v-data-table__th').should('not.exist') + }) + + it('should have colgroup slot', () => { + cy.mount(() => ( + + + {{ + colgroup: ({ columns }) => ( + + { columns.map(column => ( + + ))} + + ), + }} + + + )) + + cy.get('colgroup').should('exist') + }) + + it('should have header.* slots', () => { + cy.mount(() => ( + + + {{ + 'header.data-table-expand': () =>

expand

, + 'header.data-table-select': () =>

select

, + 'header.name': ({ column }) => ( +

{ column.title }

+ ), + }} +
+
+ )) + + cy.get('.v-data-table__th').find('h1').should('exist') + cy.get('.v-data-table__th').find('h2').should('exist') + cy.get('.v-data-table__th').find('h3').should('exist') + }) + + it('should have item slot', () => { + cy.mount(() => ( + + + {{ + item: ({ columns, internalItem }) => ( + + { columns.map(column => ( + column.key != null ? ( + +

{ internalItem.columns[column.key] }

+ + ) : null + ))} + + ), + }} +
+
+ )) + + cy.get('.custom-row').should('have.length', 10) + }) + + it('should have item.* slots', () => { + cy.mount(() => ( + + + {{ + 'item.data-table-expand': () =>

expand

, + 'item.data-table-select': () =>

select

, + 'item.name': ({ value }) => ( +

{ value }

+ ), + }} +
+
+ )) + + cy.get('.v-data-table').find('h1').should('exist') + cy.get('.v-data-table').find('h2').should('exist') + cy.get('.v-data-table').find('h3').should('exist') + }) + }) + + describe('sort', () => { + it('should sort by sortBy', () => { + cy.mount(() => ( + + + + )) + cy.get('thead .v-data-table__td').eq(2).should('have.class', 'v-data-table__th--sorted') + .get('tbody td:nth-child(3)').then(rows => { + const actualFat = Array.from(rows).map(row => { + return Number(row.textContent) + }) + const expectedFat = DESSERT_ITEMS.map(d => d.fat).sort((a, b) => a - b) + expect(actualFat).to.deep.equal(expectedFat) + }) + cy.get('thead .v-data-table__td').eq(2).click() + .get('thead .v-data-table__td').eq(2).should('have.class', 'v-data-table__th--sorted') + .get('tbody td:nth-child(3)').then(rows => { + const actualFat = Array.from(rows).map(row => { + return Number(row.textContent) + }) + const expectedFat = DESSERT_ITEMS.map(d => d.fat).sort((a, b) => b - a) + expect(actualFat).to.deep.equal(expectedFat) + }) + }) + + it('should sort by groupBy and sortBy', () => { + cy.mount(() => ( + + + + )).get('tr.v-data-table-group-header-row .v-data-table__td button + span').then(rows => { + const actualGroup = Array.from(rows).map(row => { + return Number(row.textContent) + }) + const expectedGroup = [...new Set(DESSERT_ITEMS.map(d => d.group))].sort((a, b) => b - a) + expect(actualGroup).to.deep.equal(expectedGroup) + }).get('.v-data-table-group-header-row button').eq(0).click() + .get('.v-data-table__tr td:nth-child(3)').then(rows => { + const actualCalories = Array.from(rows).map(row => { + return Number(row.textContent) + }) + const expectedCalories = DESSERT_ITEMS.filter(d => d.group === 3).map(d => d.calories).sort((a, b) => b - a) + expect(actualCalories).to.deep.equal(expectedCalories) + }) + }) + + // https://github.com/vuetifyjs/vuetify/issues/20046 + it('should sort by groupBy while sort is disabled', () => { + cy.mount(() => ( + + + + )).get('tr.v-data-table-group-header-row .v-data-table__td button + span').then(rows => { + const actualGroup = Array.from(rows).map(row => { + return Number(row.textContent) + }) + const expectedGroup = [...new Set(DESSERT_ITEMS.map(d => d.group))].sort((a, b) => b - a) + expect(actualGroup).to.deep.equal(expectedGroup) + }).get('.v-data-table-group-header-row button').eq(0).click() + .get('.v-data-table__tr td:nth-child(3)').then(rows => { + const actualCalories = Array.from(rows).map(row => { + return Number(row.textContent) + }) + const expectedCalories = DESSERT_ITEMS.filter(d => d.group === 3).map(d => d.calories) + expect(actualCalories).to.deep.equal(expectedCalories) + }) + }) + }) +}) diff --git a/packages/vuetify/src/components/VDataTable/__tests__/VDataTableServer.spec.browser.tsx b/packages/vuetify/src/components/VDataTable/__tests__/VDataTableServer.spec.browser.tsx deleted file mode 100644 index 323e2c0bdb9..00000000000 --- a/packages/vuetify/src/components/VDataTable/__tests__/VDataTableServer.spec.browser.tsx +++ /dev/null @@ -1,182 +0,0 @@ -// Utilities -import { render, screen } from '@test' -import { fireEvent, waitFor } from '@testing-library/vue' -import { ref } from 'vue' -import { VDataTableServer } from '../VDataTableServer' - -const DESSERT_HEADERS = [ - { title: 'Dessert (100g serving)', key: 'name' }, - { title: 'Calories', key: 'calories' }, - { title: 'Fat (g)', key: 'fat' }, - { title: 'Carbs (g)', key: 'carbs' }, - { title: 'Protein (g)', key: 'protein' }, - { title: 'Iron (%)', key: 'iron' }, -] - -const DESSERT_ITEMS = [ - { name: 'Frozen Yogurt', calories: 159, fat: 6.0, carbs: 24, protein: 4.0, iron: '1%' }, - { name: 'Ice cream sandwich', calories: 237, fat: 9.0, carbs: 37, protein: 4.3, iron: '1%' }, - { name: 'Eclair', calories: 262, fat: 16.0, carbs: 23, protein: 6.0, iron: '7%' }, - { name: 'Cupcake', calories: 305, fat: 3.7, carbs: 67, protein: 4.3, iron: '8%' }, - { name: 'Gingerbread', calories: 356, fat: 16.0, carbs: 49, protein: 3.9, iron: '16%' }, - { name: 'Jelly bean', calories: 375, fat: 0.0, carbs: 94, protein: 0.0, iron: '0%' }, - { name: 'Lollipop', calories: 392, fat: 0.2, carbs: 98, protein: 0, iron: '2%' }, - { name: 'Honeycomb', calories: 408, fat: 3.2, carbs: 87, protein: 6.5, iron: '45%' }, - { name: 'Donut', calories: 452, fat: 25.0, carbs: 51, protein: 4.9, iron: '22%' }, - { name: 'KitKat', calories: 518, fat: 26.0, carbs: 65, protein: 7, iron: '6%' }, -] - -describe('VDataTableServer', () => { - const items = ref([]) - const options = ref({ - itemsLength: 0, - page: 1, - itemsPerPage: 2, - search: '', - sortBy: '', - }) - - const load = (opts: { page: number, itemsPerPage: number }) => { - setTimeout(() => { - const start = (opts.page - 1) * opts.itemsPerPage - const end = start + opts.itemsPerPage - items.value = DESSERT_ITEMS.slice(start, end) - options.value = { - ...options.value, - ...opts, - } - }, 10) - } - - it('should render table', async () => { - const itemsLength = 2 - - render(() => ( - - )) - - const headers = screen.getAllByRole('columnheader') - expect(headers).toHaveLength(DESSERT_HEADERS.length) - - const rows = screen.getAllByRole('row') - expect(rows).toHaveLength(itemsLength + 1) // Including the header row - }) - - it('should trigger update event once on mount', async () => { - render(() => ( - - )) - - load({ page: 1, itemsPerPage: 2 }) - - await waitFor(() => { - expect(items.value).toEqual(DESSERT_ITEMS.slice(0, 2)) - }) - }) - - it('should trigger update event once when changing sort', async () => { - render(() => ( - - )) - - fireEvent.click(screen.getAllByRole('columnheader')[0]) - - await waitFor(() => { - expect(options.value.sortBy).toEqual([{ key: 'name', order: 'asc' }]) - }) - }) - - it('should trigger update event once when search changes', async () => { - render(() => ( - - )) - - fireEvent.click(screen.getByLabelText('Next page')) - - await waitFor(() => { - options.value.search = 'frozen' - expect(options.value.search).toBe('frozen') - }) - }) - - // TODO: should trigger update event once when changing itemsPerPage - // it('should trigger update event once when changing itemsPerPage', async () => { - // const items = ref([]) - // const options = ref({ - // itemsLength: DESSERT_ITEMS.length, - // page: 1, - // itemsPerPage: 2, - // }) - - // const load = (opts: { page: number, itemsPerPage: number }) => { - // setTimeout(() => { - // const start = (opts.page - 1) * opts.itemsPerPage - // const end = start + opts.itemsPerPage - // items.value = DESSERT_ITEMS.slice(start, end) - // options.value = { - // ...options.value, - // ...opts, - // } - // }, 10) - // } - - // render(() => ( - // - // )) - - // // Simulate changing itemsPerPage via the select element (combobox) - // const itemsPerPageSelect = screen.getByRole('combobox') // Vuetify uses combobox for select elements - - // // Ensure that the dropdown exists - // expect(itemsPerPageSelect).toBeInTheDocument() - - // // Simulate selecting a new itemsPerPage value (e.g., 10) - // fireEvent.update(itemsPerPageSelect, { target: { value: '10' } }) - - // // Wait for async updates and check the options values - // await waitFor(() => { - // // Expect the updated itemsPerPage value to be 10 - // expect(options.value.itemsPerPage).toBe(10) - // // Ensure that the page remains 1 as no page change was simulated - // expect(options.value.page).toBe(1) - // }) - - // // Ensure the load function was called with correct parameters - // expect(load).toHaveBeenCalledWith({ - // page: 1, // Page should remain 1 - // itemsPerPage: 10, // Items per page should be updated to 10 - // }) - // }) -}) diff --git a/packages/vuetify/src/components/VDataTable/__tests__/VDataTableServer.spec.cy.tsx b/packages/vuetify/src/components/VDataTable/__tests__/VDataTableServer.spec.cy.tsx new file mode 100644 index 00000000000..807a53a3953 --- /dev/null +++ b/packages/vuetify/src/components/VDataTable/__tests__/VDataTableServer.spec.cy.tsx @@ -0,0 +1,295 @@ +/// + +import { Application } from '../../../../cypress/templates' + +// Utilities +import { ref } from 'vue' +import { VDataTableServer } from '..' + +const DESSERT_HEADERS = [ + { title: 'Dessert (100g serving)', key: 'name' }, + { title: 'Calories', key: 'calories' }, + { title: 'Fat (g)', key: 'fat' }, + { title: 'Carbs (g)', key: 'carbs' }, + { title: 'Protein (g)', key: 'protein' }, + { title: 'Iron (%)', key: 'iron' }, +] + +const DESSERT_ITEMS = [ + { + name: 'Frozen Yogurt', + calories: 159, + fat: 6.0, + carbs: 24, + protein: 4.0, + iron: '1%', + }, + { + name: 'Ice cream sandwich', + calories: 237, + fat: 9.0, + carbs: 37, + protein: 4.3, + iron: '1%', + }, + { + name: 'Eclair', + calories: 262, + fat: 16.0, + carbs: 23, + protein: 6.0, + iron: '7%', + }, + { + name: 'Cupcake', + calories: 305, + fat: 3.7, + carbs: 67, + protein: 4.3, + iron: '8%', + }, + { + name: 'Gingerbread', + calories: 356, + fat: 16.0, + carbs: 49, + protein: 3.9, + iron: '16%', + }, + { + name: 'Jelly bean', + calories: 375, + fat: 0.0, + carbs: 94, + protein: 0.0, + iron: '0%', + }, + { + name: 'Lollipop', + calories: 392, + fat: 0.2, + carbs: 98, + protein: 0, + iron: '2%', + }, + { + name: 'Honeycomb', + calories: 408, + fat: 3.2, + carbs: 87, + protein: 6.5, + iron: '45%', + }, + { + name: 'Donut', + calories: 452, + fat: 25.0, + carbs: 51, + protein: 4.9, + iron: '22%', + }, + { + name: 'KitKat', + calories: 518, + fat: 26.0, + carbs: 65, + protein: 7, + iron: '6%', + }, +] + +describe('VDataTableServer', () => { + it('should render table', () => { + const itemsLength = 2 + + cy.mount(() => ( + + )) + + cy.get('.v-data-table thead th').should('have.length', DESSERT_HEADERS.length) + cy.get('.v-data-table tbody tr').should('have.length', itemsLength) + }) + + it('should only trigger update event once on mount', () => { + const items = ref([]) + const options = ref({ + itemsLength: 0, + page: 1, + itemsPerPage: 2, + }) + + function load (opts: { page: number, itemsPerPage: number }) { + setTimeout(() => { + const start = (opts.page - 1) * opts.itemsPerPage + const end = start + opts.itemsPerPage + items.value = DESSERT_ITEMS.slice(start, end) + options.value = { + ...options.value, + ...opts, + } + }, 10) + } + + cy.mount(() => ( + + )) + + cy.get('.v-data-table tbody tr') + .emitted(VDataTableServer, 'update:options') + .should('have.length', 1) + }) + + it('should only trigger update event once when changing itemsPerPage', () => { + const items = ref([]) + const options = ref({ + itemsLength: DESSERT_ITEMS.length, + page: 1, + itemsPerPage: 2, + }) + + // eslint-disable-next-line sonarjs/no-identical-functions + function load (opts: { page: number, itemsPerPage: number }) { + setTimeout(() => { + const start = (opts.page - 1) * opts.itemsPerPage + const end = start + opts.itemsPerPage + items.value = DESSERT_ITEMS.slice(start, end) + options.value = { + ...options.value, + ...opts, + } + }, 10) + } + + cy.mount(() => ( + + + + )) + + cy.get('.v-btn[aria-label="Next page"]') + .click() + cy.get('.v-select') + .click() + cy.get('.v-list-item') + .eq(0) + .click() + cy.emitted(VDataTableServer, 'update:options') + .should('deep.equal', [ + [{ page: 1, itemsPerPage: 2, sortBy: [], groupBy: [], search: undefined }], + [{ page: 2, itemsPerPage: 2, sortBy: [], groupBy: [], search: undefined }], + [{ page: 1, itemsPerPage: 10, sortBy: [], groupBy: [], search: undefined }], + ]) + }) + + it('should only trigger update event once when changing sort', () => { + const items = ref([]) + const options = ref({ + itemsLength: DESSERT_ITEMS.length, + page: 1, + itemsPerPage: 2, + }) + + // eslint-disable-next-line sonarjs/no-identical-functions + function load (opts: { page: number, itemsPerPage: number }) { + setTimeout(() => { + const start = (opts.page - 1) * opts.itemsPerPage + const end = start + opts.itemsPerPage + items.value = DESSERT_ITEMS.slice(start, end) + options.value = { + ...options.value, + ...opts, + } + }, 10) + } + + cy.mount(() => ( + + + + )) + + cy.get('.v-btn[aria-label="Next page"]') + .click() + cy.get('th') + .eq(0) + .click() + cy.emitted(VDataTableServer, 'update:options') + .should('deep.equal', [ + [{ page: 1, itemsPerPage: 2, sortBy: [], groupBy: [], search: undefined }], + [{ page: 2, itemsPerPage: 2, sortBy: [], groupBy: [], search: undefined }], + [{ page: 1, itemsPerPage: 2, sortBy: [{ key: 'name', order: 'asc' }], groupBy: [], search: undefined }], + ]) + }) + + it('should only trigger update event once when search changes', () => { + const items = ref([]) + const options = ref({ + itemsLength: DESSERT_ITEMS.length, + page: 1, + itemsPerPage: 2, + search: '', + }) + + // eslint-disable-next-line sonarjs/no-identical-functions + function load (opts: { page: number, itemsPerPage: number, search: string }) { + setTimeout(() => { + const start = (opts.page - 1) * opts.itemsPerPage + const end = start + opts.itemsPerPage + items.value = DESSERT_ITEMS + .filter(item => !opts.search || item.name.toLowerCase().includes(opts.search.toLowerCase())) + .slice(start, end) + options.value = { + ...options.value, + ...opts, + } + }, 10) + } + + cy.mount(() => ( + + + + )) + + cy + .get('.v-btn[aria-label="Next page"]') + .click() + + cy.then(() => { + options.value = { + ...options.value, + search: 'frozen', + } + }) + .emitted(VDataTableServer, 'update:options') + .should('deep.equal', [ + [{ page: 1, itemsPerPage: 2, sortBy: [], groupBy: [], search: '' }], + [{ page: 2, itemsPerPage: 2, sortBy: [], groupBy: [], search: '' }], + [{ page: 1, itemsPerPage: 2, sortBy: [], groupBy: [], search: 'frozen' }], + ]) + }) +})