Back to Repositories

Testing Button Component Functionality in Bootstrap

This comprehensive test suite validates the Button component functionality in Bootstrap’s JavaScript implementation. The tests cover core button behaviors, state management, and jQuery integration while ensuring proper DOM manipulation and event handling.

Test Coverage Overview

The test suite provides thorough coverage of Bootstrap’s Button component functionality.

Key areas tested include:
  • Button initialization with both CSS selectors and DOM elements
  • Version and data key verification
  • Toggle state management and aria-pressed attributes
  • Event handling through data-api
  • jQuery interface compatibility

Implementation Analysis

The testing approach utilizes Jest’s describe/it pattern for organized test grouping. The implementation leverages fixture-based testing with beforeAll/afterEach hooks for consistent test environment setup and cleanup.

Notable patterns include:
  • Isolated component testing using fixture elements
  • Spy-based interaction verification
  • Instance management validation
  • Clear separation of concerns between test cases

Technical Details

Testing infrastructure includes:
  • Jest as the primary testing framework
  • Custom fixture helpers for DOM manipulation
  • jQuery mock implementation for legacy support
  • ES6 module structure for clean imports
  • Jasmine-style matchers for assertions

Best Practices Demonstrated

The test suite exemplifies several testing best practices for frontend components.

Notable practices include:
  • Consistent setup and teardown procedures
  • Comprehensive edge case coverage
  • Clear test case isolation
  • Effective use of test helpers and utilities
  • Thorough API surface testing

twbs/bootstrap

js/tests/unit/button.spec.js

            
import Button from '../../src/button.js'
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'

describe('Button', () => {
  let fixtureEl

  beforeAll(() => {
    fixtureEl = getFixture()
  })

  afterEach(() => {
    clearFixture()
  })

  it('should take care of element either passed as a CSS selector or DOM element', () => {
    fixtureEl.innerHTML = '<button data-bs-toggle="button">Placeholder</button>'
    const buttonEl = fixtureEl.querySelector('[data-bs-toggle="button"]')
    const buttonBySelector = new Button('[data-bs-toggle="button"]')
    const buttonByElement = new Button(buttonEl)

    expect(buttonBySelector._element).toEqual(buttonEl)
    expect(buttonByElement._element).toEqual(buttonEl)
  })

  describe('VERSION', () => {
    it('should return plugin version', () => {
      expect(Button.VERSION).toEqual(jasmine.any(String))
    })
  })

  describe('DATA_KEY', () => {
    it('should return plugin data key', () => {
      expect(Button.DATA_KEY).toEqual('bs.button')
    })
  })

  describe('data-api', () => {
    it('should toggle active class on click', () => {
      fixtureEl.innerHTML = [
        '<button class="btn" data-bs-toggle="button">btn</button>',
        '<button class="btn testParent" data-bs-toggle="button"><div class="test"></div></button>'
      ].join('')

      const btn = fixtureEl.querySelector('.btn')
      const divTest = fixtureEl.querySelector('.test')
      const btnTestParent = fixtureEl.querySelector('.testParent')

      expect(btn).not.toHaveClass('active')

      btn.click()

      expect(btn).toHaveClass('active')

      btn.click()

      expect(btn).not.toHaveClass('active')

      divTest.click()

      expect(btnTestParent).toHaveClass('active')
    })
  })

  describe('toggle', () => {
    it('should toggle aria-pressed', () => {
      fixtureEl.innerHTML = '<button class="btn" data-bs-toggle="button" aria-pressed="false"></button>'

      const btnEl = fixtureEl.querySelector('.btn')
      const button = new Button(btnEl)

      expect(btnEl.getAttribute('aria-pressed')).toEqual('false')
      expect(btnEl).not.toHaveClass('active')

      button.toggle()

      expect(btnEl.getAttribute('aria-pressed')).toEqual('true')
      expect(btnEl).toHaveClass('active')
    })
  })

  describe('dispose', () => {
    it('should dispose a button', () => {
      fixtureEl.innerHTML = '<button class="btn" data-bs-toggle="button"></button>'

      const btnEl = fixtureEl.querySelector('.btn')
      const button = new Button(btnEl)

      expect(Button.getInstance(btnEl)).not.toBeNull()

      button.dispose()

      expect(Button.getInstance(btnEl)).toBeNull()
    })
  })

  describe('jQueryInterface', () => {
    it('should handle config passed and toggle existing button', () => {
      fixtureEl.innerHTML = '<button class="btn" data-bs-toggle="button"></button>'

      const btnEl = fixtureEl.querySelector('.btn')
      const button = new Button(btnEl)

      const spy = spyOn(button, 'toggle')

      jQueryMock.fn.button = Button.jQueryInterface
      jQueryMock.elements = [btnEl]

      jQueryMock.fn.button.call(jQueryMock, 'toggle')

      expect(spy).toHaveBeenCalled()
    })

    it('should create new button instance and call toggle', () => {
      fixtureEl.innerHTML = '<button class="btn" data-bs-toggle="button"></button>'

      const btnEl = fixtureEl.querySelector('.btn')

      jQueryMock.fn.button = Button.jQueryInterface
      jQueryMock.elements = [btnEl]

      jQueryMock.fn.button.call(jQueryMock, 'toggle')

      expect(Button.getInstance(btnEl)).not.toBeNull()
      expect(btnEl).toHaveClass('active')
    })

    it('should just create a button instance without calling toggle', () => {
      fixtureEl.innerHTML = '<button class="btn" data-bs-toggle="button"></button>'

      const btnEl = fixtureEl.querySelector('.btn')

      jQueryMock.fn.button = Button.jQueryInterface
      jQueryMock.elements = [btnEl]

      jQueryMock.fn.button.call(jQueryMock)

      expect(Button.getInstance(btnEl)).not.toBeNull()
      expect(btnEl).not.toHaveClass('active')
    })
  })

  describe('getInstance', () => {
    it('should return button instance', () => {
      fixtureEl.innerHTML = '<div></div>'

      const div = fixtureEl.querySelector('div')
      const button = new Button(div)

      expect(Button.getInstance(div)).toEqual(button)
      expect(Button.getInstance(div)).toBeInstanceOf(Button)
    })

    it('should return null when there is no button instance', () => {
      fixtureEl.innerHTML = '<div></div>'

      const div = fixtureEl.querySelector('div')

      expect(Button.getInstance(div)).toBeNull()
    })
  })

  describe('getOrCreateInstance', () => {
    it('should return button instance', () => {
      fixtureEl.innerHTML = '<div></div>'

      const div = fixtureEl.querySelector('div')
      const button = new Button(div)

      expect(Button.getOrCreateInstance(div)).toEqual(button)
      expect(Button.getInstance(div)).toEqual(Button.getOrCreateInstance(div, {}))
      expect(Button.getOrCreateInstance(div)).toBeInstanceOf(Button)
    })

    it('should return new instance when there is no button instance', () => {
      fixtureEl.innerHTML = '<div></div>'

      const div = fixtureEl.querySelector('div')

      expect(Button.getInstance(div)).toBeNull()
      expect(Button.getOrCreateInstance(div)).toBeInstanceOf(Button)
    })
  })
})