<reponame>bhavesh-asana/SEAGrid<filename>data-catalog/node_modules/bootstrap-vue/src/components/pagination/pagination.spec.js<gh_stars>0import { mount } from '@vue/test-utils'
import { waitNT } from '../../../tests/utils'
import { isVisible, getBCR, contains } from '../../utils/dom'
import { BPagination } from './pagination'

const wrapperArrayToArray = wrapperArray => {
  const array = []
  for (let i = 0; i < wrapperArray.length; i++) {
    array.push(wrapperArray.at(i))
  }
  return array
}

describe('pagination', () => {
  it('renders with correct basic structure for root element', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        totalRows: 1,
        perPage: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    // Classes
    expect(wrapper.classes()).toContain('pagination')
    expect(wrapper.classes()).toContain('b-pagination')
    expect(wrapper.classes()).not.toContain('pagination-sm')
    expect(wrapper.classes()).not.toContain('pagination-lg')
    expect(wrapper.classes()).not.toContain('justify-content-center')
    expect(wrapper.classes()).not.toContain('justify-content-end')
    expect(wrapper.classes()).not.toContain('b-pagination-pills')
    // Attributes
    expect(wrapper.attributes('role')).toBe('menubar')
    expect(wrapper.attributes('aria-disabled')).toBe('false')
    expect(wrapper.attributes('aria-label')).toBe('Pagination')

    wrapper.destroy()
  })

  it('renders with correct basic inner structure', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        totalRows: 1,
        perPage: 1,
        value: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    const lis = wrapper.findAll('li')
    expect(lis).toBeDefined()
    expect(lis.length).toBe(5)

    lis.wrappers.forEach((li, index) => {
      expect(li.classes()).toContain('page-item')
      const pageLink = li.find('.page-link')
      expect(pageLink).toBeDefined()
      if (index === 2) {
        expect(li.classes()).toContain('active')
        expect(li.classes()).not.toContain('disabled')
        expect(pageLink.element.tagName).toBe('BUTTON')
      } else {
        expect(li.classes()).not.toContain('active')
        expect(li.classes()).toContain('disabled')
        expect(pageLink.element.tagName).toBe('SPAN')
      }
    })

    const first = lis.at(0)
    const prev = lis.at(1)
    const page = lis.at(2)
    const next = lis.at(3)
    const last = lis.at(4)

    // Button content
    expect(first.find('.page-link').text()).toEqual('«')
    expect(prev.find('.page-link').text()).toEqual('‹')
    expect(page.find('.page-link').text()).toEqual('1')
    expect(next.find('.page-link').text()).toEqual('›')
    expect(last.find('.page-link').text()).toEqual('»')

    // Page button attrs
    expect(page.find('.page-link').attributes('type')).toEqual('button')
    expect(page.find('.page-link').attributes('role')).toEqual('menuitemradio')
    expect(page.find('.page-link').attributes('aria-checked')).toEqual('true')
    expect(page.find('.page-link').attributes('aria-posinset')).toEqual('1')
    expect(page.find('.page-link').attributes('aria-setsize')).toEqual('1')
    expect(page.find('.page-link').attributes('tabindex')).toEqual('0')
    expect(page.find('.page-link').attributes('aria-label')).toEqual('Go to page 1')

    wrapper.destroy()
  })

  it('renders scopedSlot page', async () => {
    const scopes = []
    const wrapper = mount(BPagination, {
      propsData: {
        totalRows: 3,
        perPage: 1,
        limit: 10,
        value: 1
      },
      scopedSlots: {
        page: scope => {
          const pageNum = scope.page
          scopes[pageNum - 1] = scope
          return `Page ${scope.page}`
        }
      }
    })

    expect(wrapper).toBeDefined()
    await waitNT(wrapper.vm)
    expect(scopes.length).toBe(3)
    expect(scopes[0]).toEqual({
      page: 1,
      content: '1',
      active: true,
      index: 0,
      disabled: false
    })
    expect(scopes[1]).toEqual({
      page: 2,
      content: '2',
      active: false,
      index: 1,
      disabled: false
    })
    expect(scopes[2]).toEqual({
      page: 3,
      content: '3',
      active: false,
      index: 2,
      disabled: false
    })

    const $links = wrapper.findAll('button.page-link')
    expect($links.length).toBe(5)
    expect($links.at(0).text()).toBe('Page 1')
    expect($links.at(1).text()).toBe('Page 2')
    expect($links.at(2).text()).toBe('Page 3')

    wrapper.destroy()
  })

  it('renders correct number of elements when total-rows changes', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        size: 'sm',
        totalRows: 1,
        perPage: 1,
        limit: 10
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    expect(wrapper.findAll('li').length).toBe(5)

    await wrapper.setProps({
      totalRows: 4
    })
    await waitNT(wrapper.vm)

    expect(wrapper.element.tagName).toBe('UL')
    expect(wrapper.findAll('li').length).toBe(8)

    await wrapper.setProps({
      perPage: 2
    })
    await waitNT(wrapper.vm)

    expect(wrapper.element.tagName).toBe('UL')
    expect(wrapper.findAll('li').length).toBe(6)

    wrapper.destroy()
  })

  it('has class "b-pagination-pills" when prop pills is set', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        pills: true,
        totalRows: 1,
        perPage: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    // Classes
    expect(wrapper.classes()).toContain('pagination')
    expect(wrapper.classes()).toContain('b-pagination')
    expect(wrapper.classes()).toContain('b-pagination-pills')
    expect(wrapper.classes()).not.toContain('pagination-lg')
    expect(wrapper.classes()).not.toContain('justify-content-center')
    expect(wrapper.classes()).not.toContain('justify-content-end')
    // Attributes
    expect(wrapper.attributes('role')).toBe('menubar')
    expect(wrapper.attributes('aria-disabled')).toBe('false')
    expect(wrapper.attributes('aria-label')).toBe('Pagination')

    wrapper.destroy()
  })

  it('has class "pagination-sm" when prop size="sm"', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        size: 'sm',
        totalRows: 1,
        perPage: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    // Classes
    expect(wrapper.classes()).toContain('pagination')
    expect(wrapper.classes()).toContain('b-pagination')
    expect(wrapper.classes()).toContain('pagination-sm')
    expect(wrapper.classes()).not.toContain('pagination-lg')
    expect(wrapper.classes()).not.toContain('justify-content-center')
    expect(wrapper.classes()).not.toContain('justify-content-end')
    expect(wrapper.classes()).not.toContain('b-pagination-pills')
    // Attributes
    expect(wrapper.attributes('role')).toBe('menubar')
    expect(wrapper.attributes('aria-disabled')).toBe('false')
    expect(wrapper.attributes('aria-label')).toBe('Pagination')

    wrapper.destroy()
  })

  it('has class "pagination-lg" when prop size="lg"', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        size: 'lg',
        totalRows: 1,
        perPage: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    // Classes
    expect(wrapper.classes()).toContain('pagination')
    expect(wrapper.classes()).toContain('b-pagination')
    expect(wrapper.classes()).not.toContain('pagination-sm')
    expect(wrapper.classes()).toContain('pagination-lg')
    expect(wrapper.classes()).not.toContain('justify-content-center')
    expect(wrapper.classes()).not.toContain('justify-content-end')
    expect(wrapper.classes()).not.toContain('b-pagination-pills')
    // Attributes
    expect(wrapper.attributes('role')).toBe('menubar')
    expect(wrapper.attributes('aria-disabled')).toBe('false')
    expect(wrapper.attributes('aria-label')).toBe('Pagination')

    wrapper.destroy()
  })

  it('has class "pagination-foo" when prop size="foo"', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        size: 'foo',
        totalRows: 1,
        perPage: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    // Classes
    expect(wrapper.classes()).toContain('pagination-foo')
    expect(wrapper.classes()).toContain('pagination')
    expect(wrapper.classes()).toContain('b-pagination')
    expect(wrapper.classes()).not.toContain('pagination-sm')
    expect(wrapper.classes()).not.toContain('pagination-lg')
    expect(wrapper.classes()).not.toContain('justify-content-center')
    expect(wrapper.classes()).not.toContain('justify-content-end')
    expect(wrapper.classes()).not.toContain('b-pagination-pills')
    // Attributes
    expect(wrapper.attributes('role')).toBe('menubar')
    expect(wrapper.attributes('aria-disabled')).toBe('false')
    expect(wrapper.attributes('aria-label')).toBe('Pagination')

    wrapper.destroy()
  })

  it('has class "justify-content-center" when prop align="center"', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        align: 'center',
        totalRows: 1,
        perPage: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    // Classes
    expect(wrapper.classes()).toContain('pagination')
    expect(wrapper.classes()).toContain('b-pagination')
    expect(wrapper.classes()).not.toContain('pagination-sm')
    expect(wrapper.classes()).not.toContain('pagination-lg')
    expect(wrapper.classes()).toContain('justify-content-center')
    expect(wrapper.classes()).not.toContain('justify-content-end')
    expect(wrapper.classes()).not.toContain('b-pagination-pills')
    // Attributes
    expect(wrapper.attributes('role')).toBe('menubar')
    expect(wrapper.attributes('aria-disabled')).toBe('false')
    expect(wrapper.attributes('aria-label')).toBe('Pagination')

    wrapper.destroy()
  })

  it('has class "justify-content-end" when prop align="right"', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        align: 'right',
        totalRows: 1,
        perPage: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    // Classes
    expect(wrapper.classes()).toContain('pagination')
    expect(wrapper.classes()).toContain('b-pagination')
    expect(wrapper.classes()).not.toContain('pagination-sm')
    expect(wrapper.classes()).not.toContain('pagination-lg')
    expect(wrapper.classes()).not.toContain('justify-content-center')
    expect(wrapper.classes()).toContain('justify-content-end')
    expect(wrapper.classes()).not.toContain('b-pagination-pills')
    // Attributes
    expect(wrapper.attributes('role')).toBe('menubar')
    expect(wrapper.attributes('aria-disabled')).toBe('false')
    expect(wrapper.attributes('aria-label')).toBe('Pagination')

    wrapper.destroy()
  })

  it('has class "justify-content-end" when prop align="end"', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        align: 'end',
        totalRows: 1,
        perPage: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    // Classes
    expect(wrapper.classes()).toContain('pagination')
    expect(wrapper.classes()).toContain('b-pagination')
    expect(wrapper.classes()).not.toContain('pagination-sm')
    expect(wrapper.classes()).not.toContain('pagination-lg')
    expect(wrapper.classes()).not.toContain('justify-content-center')
    expect(wrapper.classes()).toContain('justify-content-end')
    expect(wrapper.classes()).not.toContain('b-pagination-pills')
    // Attributes
    expect(wrapper.attributes('role')).toBe('menubar')
    expect(wrapper.attributes('aria-disabled')).toBe('false')
    expect(wrapper.attributes('aria-label')).toBe('Pagination')

    wrapper.destroy()
  })

  it('has class "text-center" and "flex-fill" when prop align="fill"', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        align: 'fill',
        totalRows: 5,
        perPage: 1,
        limit: 4,
        value: 3
      }
    })
    expect(wrapper.element.tagName).toBe('UL')

    // Classes
    expect(wrapper.classes()).toContain('text-center')
    expect(wrapper.classes()).toContain('pagination')
    expect(wrapper.classes()).toContain('b-pagination')
    expect(wrapper.classes()).not.toContain('pagination-sm')
    expect(wrapper.classes()).not.toContain('pagination-lg')
    expect(wrapper.classes()).not.toContain('justify-content-center')
    expect(wrapper.classes()).not.toContain('justify-content-end')
    expect(wrapper.classes()).not.toContain('b-pagination-pills')

    expect(wrapper.findAll('li.flex-fill').length).toBe(8)

    wrapper.destroy()
  })

  it('has correct number of links when `hide-ellipsis` is enabled', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        hideEllipsis: true,
        totalRows: 100,
        perPage: 10,
        value: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    expect(wrapper.findAll('li').length).toBe(9)

    await wrapper.setProps({ value: 5 })
    await waitNT(wrapper.vm)
    expect(wrapper.findAll('li').length).toBe(9)

    wrapper.destroy()
  })

  it('has attribute aria-controls on page links when prop aria-controls is set', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        hideGotoEndButtons: true,
        hideEllipsis: true,
        totalRows: 3,
        perPage: 1,
        value: 1,
        ariaControls: 'foo'
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    expect(wrapper.findAll('li').length).toBe(5)
    expect(wrapper.findAll('button.page-link').length).toBe(4)
    expect(
      wrapper
        .findAll('button.page-link')
        .wrappers.every(w => w.element.matches('[aria-controls="foo"]'))
    ).toBe(true)

    await wrapper.setProps({
      ariaControls: null
    })
    await waitNT(wrapper.vm)
    expect(wrapper.findAll('li').length).toBe(5)
    expect(wrapper.findAll('button.page-link').length).toBe(4)
    expect(
      wrapper.findAll('button.page-link').wrappers.every(w => w.find('[aria-controls]').exists())
    ).toBe(false)

    wrapper.destroy()
  })

  it('has attribute aria-label on page links', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        hideGotoEndButtons: true,
        hideEllipsis: true,
        totalRows: 3,
        perPage: 1,
        value: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    expect(wrapper.findAll('li').length).toBe(5)
    expect(wrapper.findAll('button').length).toBe(4)
    expect(
      wrapper
        .findAll('button')
        .at(0)
        .attributes('aria-label')
    ).toBe('Go to page 1')
    expect(
      wrapper
        .findAll('button')
        .at(1)
        .attributes('aria-label')
    ).toBe('Go to page 2')
    expect(
      wrapper
        .findAll('button')
        .at(2)
        .attributes('aria-label')
    ).toBe('Go to page 3')
    expect(
      wrapper
        .findAll('button')
        .at(3)
        .attributes('aria-label')
    ).toBe('Go to next page')

    wrapper.destroy()
  })

  it('has all links disabled when prop disabled set', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        totalRows: 3,
        perPage: 1,
        value: 1,
        disabled: true
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    expect(wrapper.findAll('li').length).toBe(7)
    expect(wrapper.findAll('.page-item').length).toBe(7)
    expect(
      wrapper.findAll('.page-item').wrappers.every(w => w.element.matches('li.page-item.disabled'))
    ).toBe(true)
    expect(
      wrapper.findAll('.page-link').wrappers.every(w => w.element.matches('span.page-link'))
    ).toBe(true)
    expect(
      wrapper
        .findAll('.page-link')
        .at(2)
        .attributes('aria-disabled')
    ).toBe('true')
    expect(
      wrapper
        .findAll('.page-link')
        .at(3)
        .attributes('aria-disabled')
    ).toBe('true')
    expect(
      wrapper
        .findAll('.page-link')
        .at(4)
        .attributes('aria-disabled')
    ).toBe('true')

    wrapper.destroy()
  })

  it('renders classes bv-d-xs-down-none when more than 3 pages', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        totalRows: 70,
        perPage: 10,
        limit: 7,
        value: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')
    const lis = wrapper.findAll('li')
    expect(lis).toBeDefined()
    // Including bookend buttons
    expect(lis.length).toBe(11)

    // Should have the last 4 page buttons with the
    // display classes when currentPage = 0
    expect(wrapper.vm.computedCurrentPage).toBe(1)
    // Grab the page buttons (includes bookends)
    wrapper.findAll('li').wrappers.forEach((li, index) => {
      expect(li.classes()).toContain('page-item')
      if (index === 0) {
        // First button
        expect(li.classes()).toContain('disabled')
      } else if (index === 1) {
        // Prev button
        expect(li.classes()).toContain('disabled')
      } else if (index === 9) {
        // Next button
        expect(li.classes()).not.toContain('disabled')
      } else if (index === 10) {
        // Last button
        expect(li.classes()).not.toContain('disabled')
      } else {
        // Page number buttons
        if (index === 2) {
          expect(li.classes()).toContain('active')
        } else {
          expect(li.classes()).not.toContain('active')
        }
        if (index < 5) {
          expect(li.classes()).not.toContain('bv-d-xs-down-none')
        } else if (index > 4) {
          expect(li.classes()).toContain('bv-d-xs-down-none')
        }
      }
    })

    // Should have the first and last 2 pages buttons with the
    // display classes when currentPage = 4
    await wrapper.setProps({
      value: '4'
    })
    await waitNT(wrapper.vm)
    expect(wrapper.vm.computedCurrentPage).toBe(4)
    // Grab the page buttons (including bookends)
    wrapper.findAll('li').wrappers.forEach((li, index) => {
      expect(li.classes()).toContain('page-item')
      if (index === 0) {
        // First button
        expect(li.classes()).not.toContain('disabled')
      } else if (index === 1) {
        // Prev button
        expect(li.classes()).not.toContain('disabled')
      } else if (index === 9) {
        // Next button
        expect(li.classes()).not.toContain('disabled')
      } else if (index === 10) {
        // Last button
        expect(li.classes()).not.toContain('disabled')
      } else {
        // Page number buttons
        if (index === 5) {
          expect(li.classes()).toContain('active')
        } else {
          expect(li.classes()).not.toContain('active')
        }
        if (index > 3 && index < 7) {
          expect(li.classes()).not.toContain('bv-d-xs-down-none')
        } else if (index < 4 || index > 6) {
          expect(li.classes()).toContain('bv-d-xs-down-none')
        }
      }
    })

    // Should have the first 4 pages buttons with the
    // display classes when currentPage = 4
    await wrapper.setProps({
      value: '7'
    })
    await waitNT(wrapper.vm)
    expect(wrapper.vm.computedCurrentPage).toBe(7)
    // Grab the page buttons (including bookends)
    wrapper.findAll('li').wrappers.forEach((li, index) => {
      expect(li.classes()).toContain('page-item')
      // Page number buttons
      if (index >= 2 && index <= 5) {
        // Pages 1 to 4
        expect(li.classes()).toContain('bv-d-xs-down-none')
      } else if (index >= 6 && index <= 8) {
        // Pages 5 to 7
        expect(li.classes()).not.toContain('bv-d-xs-down-none')
      }
    })

    wrapper.destroy()
  })

  it('places ellipsis in correct places', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        totalRows: 70,
        perPage: 10,
        limit: 5,
        value: 1
      }
    })
    expect(wrapper.element.tagName).toBe('UL')

    // Should have ellipsis in place of last button
    // When currentPage = 0
    expect(wrapper.vm.computedCurrentPage).toBe(1)
    // Grab the page buttons
    let lis = wrapper.findAll('li')
    expect(lis.length).toBe(9)
    expect(lis.at(2).attributes('role')).not.toBe('separator')
    expect(lis.at(6).attributes('role')).toBe('separator')

    // Should have both ellipsis showing when currentPage = 4
    await wrapper.setProps({
      value: '4'
    })
    await waitNT(wrapper.vm)
    expect(wrapper.vm.computedCurrentPage).toBe(4)
    lis = wrapper.findAll('li')
    expect(lis.length).toBe(9)
    expect(lis.at(2).attributes('role')).toBe('separator')
    expect(lis.at(6).attributes('role')).toBe('separator')

    // Should have first ellipsis showing when currentPage = 5
    await wrapper.setProps({
      value: 5
    })
    await waitNT(wrapper.vm)
    expect(wrapper.vm.computedCurrentPage).toBe(5)
    lis = wrapper.findAll('li')
    expect(lis.length).toBe(9)
    expect(lis.at(2).attributes('role')).toBe('separator')
    expect(lis.at(6).attributes('role')).not.toBe('separator')

    wrapper.destroy()
  })

  it('clicking buttons updates the v-model', async () => {
    const App = {
      methods: {
        onPageClick(bvEvent, page) {
          // Prevent 3rd page from being selected
          if (page === 3) {
            bvEvent.preventDefault()
          }
        }
      },
      render(h) {
        return h(BPagination, {
          props: {
            totalRows: 5,
            perPage: 1,
            value: 1
          },
          on: { 'page-click': this.onPageClick }
        })
      }
    }

    const wrapper = mount(App)
    expect(wrapper).toBeDefined()

    const pagination = wrapper.findComponent(BPagination)
    expect(pagination).toBeDefined()
    expect(pagination.element.tagName).toBe('UL')

    // Grab the page buttons
    const lis = pagination.findAll('li')
    expect(lis.length).toBe(9)

    expect(pagination.vm.computedCurrentPage).toBe(1)
    expect(pagination.emitted('input')).not.toBeDefined()
    expect(pagination.emitted('change')).not.toBeDefined()
    expect(pagination.emitted('page-click')).not.toBeDefined()

    // Click on current (1st) page button (does nothing)
    await lis
      .at(2)
      .find('button')
      .trigger('click')
    expect(pagination.vm.computedCurrentPage).toBe(1)
    expect(pagination.emitted('input')).not.toBeDefined()
    expect(pagination.emitted('change')).not.toBeDefined()
    expect(pagination.emitted('page-click')).not.toBeDefined()

    // Click on 2nd button
    await lis
      .at(3)
      .find('button')
      .trigger('click')
    expect(pagination.vm.computedCurrentPage).toBe(2)
    expect(pagination.emitted('input')).toBeDefined()
    expect(pagination.emitted('change')).toBeDefined()
    expect(pagination.emitted('page-click')).toBeDefined()
    expect(pagination.emitted('input')[0][0]).toBe(2)
    expect(pagination.emitted('change')[0][0]).toBe(2)
    expect(pagination.emitted('page-click').length).toBe(1)

    // Click goto last button
    await lis
      .at(8)
      .find('button')
      .trigger('keydown.space') // Generates a click event
    expect(pagination.vm.computedCurrentPage).toBe(5)
    expect(pagination.emitted('input')[1][0]).toBe(5)
    expect(pagination.emitted('change')[1][0]).toBe(5)
    expect(pagination.emitted('page-click').length).toBe(2)

    // Click prev button
    await lis
      .at(1)
      .find('button')
      .trigger('click')
    expect(pagination.vm.computedCurrentPage).toBe(4)
    expect(pagination.emitted('input')[2][0]).toBe(4)
    expect(pagination.emitted('change')[2][0]).toBe(4)
    expect(pagination.emitted('page-click').length).toBe(3)

    // Click on 3rd button (prevented)
    await lis
      .at(4)
      .find('button')
      .trigger('click')
    expect(pagination.vm.computedCurrentPage).toBe(4)
    expect(pagination.emitted('input').length).toBe(3)
    expect(pagination.emitted('change').length).toBe(3)
    expect(pagination.emitted('page-click').length).toBe(4)

    wrapper.destroy()
  })

  it('changing the limit changes the number of buttons shown', async () => {
    const wrapper = mount(BPagination, {
      propsData: {
        totalRows: 9,
        perPage: 1,
        value: 5,
        limit: 10
      }
    })
    expect(wrapper.element.tagName).toBe('UL')

    // Should be 13 <li> total
    expect(wrapper.findAll('li').length).toBe(13)

    await wrapper.setProps({
      limit: 4
    })
    await waitNT(wrapper.vm)

    // Should be 8 <li> total
    expect(wrapper.findAll('li').length).toBe(8)

    wrapper.destroy()
  })

  it('changing the number of pages to less than current page number resets to page 1', async () => {
    // https://github.com/bootstrap-vue/bootstrap-vue/issues/3716
    const wrapper = mount(BPagination, {
      propsData: {
        totalRows: 10,
        perPage: 1,
        value: 10, // Set to last page
        limit: 20
      }
    })
    expect(wrapper.vm).toBeDefined()

    expect(wrapper.vm.currentPage).toBe(10)
    expect(wrapper.emitted('input')).not.toBeDefined()

    // Change total rows to larger value. Should not change page number
    await wrapper.setProps({
      totalRows: 20
    })
    await waitNT(wrapper.vm)
    expect(wrapper.vm.currentPage).toBe(10)
    expect(wrapper.emitted('input')).not.toBeDefined()

    // Change to page 20
    await wrapper.setProps({
      value: 20
    })
    await waitNT(wrapper.vm)
    expect(wrapper.vm.currentPage).toBe(20)
    expect(wrapper.emitted('input')).toBeDefined()
    expect(wrapper.emitted('input').length).toBe(1)
    expect(wrapper.emitted('input')[0][0]).toBe(20)

    // Decrease number of pages should reset to page 1
    await wrapper.setProps({
      totalRows: 10
    })
    await waitNT(wrapper.vm)
    expect(wrapper.vm.currentPage).toBe(1)
    expect(wrapper.emitted('input').length).toBe(2)
    expect(wrapper.emitted('input')[1][0]).toBe(1)

    // Change to page 3
    await wrapper.setProps({
      value: 3
    })
    await waitNT(wrapper.vm)
    expect(wrapper.vm.currentPage).toBe(3)
    expect(wrapper.emitted('input').length).toBe(3)
    expect(wrapper.emitted('input')[2][0]).toBe(3)

    // Decrease number of pages to 5 should not reset to page 1
    await wrapper.setProps({
      totalRows: 5
    })
    await waitNT(wrapper.vm)
    expect(wrapper.vm.currentPage).toBe(3)
    expect(wrapper.emitted('input').length).toBe(3)

    wrapper.destroy()
  })

  it('changing per-page resets to page 1', async () => {
    // https://github.com/bootstrap-vue/bootstrap-vue/issues/2987
    const wrapper = mount(BPagination, {
      propsData: {
        totalRows: 10,
        perPage: 1,
        value: 4,
        limit: 20
      }
    })
    expect(wrapper.vm).toBeDefined()

    expect(wrapper.vm.currentPage).toBe(4)
    expect(wrapper.emitted('input')).not.toBeDefined()

    // Change perPage
    await wrapper.setProps({
      perPage: 2
    })
    await waitNT(wrapper.vm)
    expect(wrapper.vm.currentPage).toBe(1)
    expect(wrapper.emitted('input')).toBeDefined()
    expect(wrapper.emitted('input').length).toBe(1)
    expect(wrapper.emitted('input')[0][0]).toBe(1)

    // Change page to 3
    await wrapper.setProps({
      value: 3
    })
    await waitNT(wrapper.vm)
    expect(wrapper.vm.currentPage).toBe(3)
    expect(wrapper.emitted('input').length).toBe(2)
    expect(wrapper.emitted('input')[1][0]).toBe(3)

    // Change perPage. Should reset to page 1, even though
    // current page is within range of numberOfPages
    await wrapper.setProps({
      perPage: 1
    })
    await waitNT(wrapper.vm)
    expect(wrapper.vm.currentPage).toBe(1)
    expect(wrapper.emitted('input').length).toBe(3)
    expect(wrapper.emitted('input')[2][0]).toBe(1)

    wrapper.destroy()
  })

  it('fist-number and last-number props work', async () => {
    const selector = '.page-item .page-link'
    let items = []

    const wrapper = mount(BPagination, {
      propsData: {
        value: 1,
        totalRows: 10,
        perPage: 1,
        limit: 5,
        firstNumber: true,
        lastNumber: true
      }
    })

    expect(wrapper.vm).toBeDefined()
    await waitNT(wrapper.vm)
    expect(wrapper.findAll(selector).length).toBe(9)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '2', '3', '4', '5', '…', '10', '›'])

    await wrapper.setProps({
      value: 2
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '2', '3', '4', '5', '…', '10', '›'])

    await wrapper.setProps({
      value: 3
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '2', '3', '4', '5', '…', '10', '›'])

    await wrapper.setProps({
      value: 4
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '2', '3', '4', '5', '…', '10', '›'])

    await wrapper.setProps({
      value: 5
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '…', '4', '5', '6', '…', '10', '›'])

    await wrapper.setProps({
      value: 6
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '…', '5', '6', '7', '…', '10', '›'])

    await wrapper.setProps({
      value: 7
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '…', '6', '7', '8', '9', '10', '›'])

    await wrapper.setProps({
      value: 8
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '…', '6', '7', '8', '9', '10', '›'])

    await wrapper.setProps({
      value: 9
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '…', '6', '7', '8', '9', '10', '›'])

    await wrapper.setProps({
      value: 10
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '…', '6', '7', '8', '9', '10', '›'])

    wrapper.destroy()
  })

  it('fist-number and last-number props work with limit <=3', async () => {
    const selector = '.page-item .page-link'
    let items = []

    const wrapper = mount(BPagination, {
      propsData: {
        value: 1,
        totalRows: 10,
        perPage: 1,
        limit: 3,
        firstNumber: true,
        lastNumber: true
      }
    })

    expect(wrapper.vm).toBeDefined()
    await waitNT(wrapper.vm)
    expect(wrapper.findAll(selector).length).toBe(7)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '2', '3', '4', '10', '›'])

    await wrapper.setProps({
      value: 2
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '2', '3', '4', '10', '›'])

    await wrapper.setProps({
      value: 3
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '2', '3', '4', '10', '›'])

    await wrapper.setProps({
      value: 4
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '3', '4', '5', '10', '›'])

    await wrapper.setProps({
      value: 5
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '4', '5', '6', '10', '›'])

    await wrapper.setProps({
      value: 6
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '5', '6', '7', '10', '›'])

    await wrapper.setProps({
      value: 7
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '6', '7', '8', '10', '›'])

    await wrapper.setProps({
      value: 8
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '7', '8', '9', '10', '›'])

    await wrapper.setProps({
      value: 9
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '7', '8', '9', '10', '›'])

    await wrapper.setProps({
      value: 10
    })
    await waitNT(wrapper.vm)
    items = wrapperArrayToArray(wrapper.findAll(selector)).map(w => w.text())
    expect(items).toEqual(['‹', '1', '7', '8', '9', '10', '›'])

    wrapper.destroy()
  })

  // These tests are wrapped in a new describe to limit the scope of the getBCR Mock
  describe('pagination keyboard navigation', () => {
    const origGetBCR = Element.prototype.getBoundingClientRect

    beforeEach(() => {
      // Mock `getBoundingClientRect()` so that the `isVisible(el)` test returns `true`
      // In our test below, all pagination buttons would normally be visible
      Element.prototype.getBoundingClientRect = jest.fn(() => ({
        width: 24,
        height: 24,
        top: 0,
        left: 0,
        bottom: 0,
        right: 0
      }))
    })
    afterEach(() => {
      // Restore prototype
      Element.prototype.getBoundingClientRect = origGetBCR
    })

    it('keyboard navigation works', async () => {
      const wrapper = mount(BPagination, {
        propsData: {
          totalRows: 3,
          perPage: 1,
          value: 2,
          limit: 3
        },
        attachTo: document.body
      })
      await waitNT(wrapper.vm)
      expect(wrapper.element.tagName).toBe('UL')
      await waitNT(wrapper.vm)
      // Grab the button links (2 bookends + 3 pages + 2 bookends)
      const links = wrapper.findAll('button.page-link')
      expect(links.length).toBe(7)

      // Sanity check for getBCR override
      expect(wrapper.element.getBoundingClientRect().width).toBe(24)
      expect(getBCR(links.at(3).element).width).toBe(24)
      expect(contains(document.body, links.at(3).element)).toBe(true)
      expect(isVisible(links.at(3).element)).toBe(true)

      // Focus the active button
      links.at(3).element.focus()
      await waitNT(wrapper.vm)
      expect(document.activeElement).toEqual(links.at(3).element)

      // LEFT
      await wrapper.trigger('keydown.left')
      expect(document.activeElement).toEqual(links.at(2).element)

      // RIGHT
      await links.at(2).trigger('keydown.right')
      expect(document.activeElement).toEqual(links.at(3).element)

      // UP (same as LEFT)
      await wrapper.trigger('keydown.up')
      expect(document.activeElement).toEqual(links.at(2).element)

      // DOWN (same as RIGHT)
      await links.at(2).trigger('keydown.down')
      expect(document.activeElement).toEqual(links.at(3).element)

      // SHIFT-RIGHT
      await links.at(2).trigger('keydown.right', { shiftKey: true })
      expect(document.activeElement).toEqual(links.at(6).element)

      // SHIFT-LEFT
      await links.at(6).trigger('keydown.left', { shiftKey: true })
      expect(document.activeElement).toEqual(links.at(0).element)

      wrapper.destroy()
    })

    it('internal method focusCurrent() works', async () => {
      const wrapper = mount(BPagination, {
        propsData: {
          totalRows: 3,
          perPage: 1,
          value: 2,
          limit: 3
        },
        attachTo: document.body
      })
      await waitNT(wrapper.vm)
      expect(wrapper.element.tagName).toBe('UL')
      // Grab the button links (2 bookends + 3 pages + 2 bookends)
      const links = wrapper.findAll('button.page-link')
      expect(links.length).toBe(7)

      // Focus the last button
      links.at(6).element.focus()
      await waitNT(wrapper.vm)
      expect(document.activeElement).toEqual(links.at(6).element)

      wrapper.vm.focusCurrent()
      await waitNT(wrapper.vm)
      expect(document.activeElement).toEqual(links.at(3).element)

      wrapper.destroy()
    })

    it('Current page button is focused when button display changes', async () => {
      const wrapper = mount(BPagination, {
        propsData: {
          totalRows: 10,
          perPage: 1,
          value: 1,
          limit: 5
        },
        attachTo: document.body
      })
      let links

      await waitNT(wrapper.vm)
      expect(wrapper.element.tagName).toBe('UL')
      // Grab the button links (2 disabled bookends + 4 pages + (-ellipsis) + 2 bookends)
      links = wrapper.findAll('button.page-link')
      expect(links.length).toBe(6)

      // Click on the 4th button (page 4, index 3)
      links.at(3).element.click()
      await waitNT(wrapper.vm)
      // Links re-rendered with first bookends enabled and an ellipsis
      links = wrapper.findAll('button.page-link')
      // The 4th link should be page 4, and retain focus
      expect(document.activeElement).toEqual(links.at(3).element)

      wrapper.destroy()
    })
  })
})
