bootstrap/js/tests
Amit Rathiesh 13aa16a99b
Fix: Popover with hover and click triggers closes on mouseleave (#41511)
* Fix: Popover with hover and click triggers closes on mouseleave

When a popover is configured with `trigger: 'hover click'`,
if you open it by a click, it would incorrectly close when the
mouse pointer leaves the trigger element. This was because the
`mouseleave` event (part of the hover trigger) would hide the
popover without adequately respecting the click trigger's intent
to keep it open.

This commit modifies the click event listener within `Tooltip.js`
(which Popover extends) to explicitly manage the `_activeTrigger[TRIGGER_CLICK]`
state:
- When a click opens the popover or makes a hover-opened popover
  sticky, `_activeTrigger[TRIGGER_CLICK]` is set to `true`.
- When a click closes an already click-activated popover,
  `_activeTrigger[TRIGGER_CLICK]` is set to `false`.

The `_leave()` method, called by `mouseleave`, already checks
`_isWithActiveTrigger()`. With `_activeTrigger[TRIGGER_CLICK]`
now accurately reflecting the click state, `_leave()` will not
hide a click-activated popover when the mouse leaves the trigger
element. The popover will now correctly remain open until a
subsequent click closes it.

* Removed `test-popover.html`

* Fix linting issues

* Add unit test

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: Julien Déramond <juderamond@gmail.com>
2025-06-03 12:19:24 -07:00
..
helpers Rename `fixtureId` to `FIXTURE_ID` in `js/tests/helpers` for consistency (#41150) 2025-02-08 18:37:52 +01:00
integration Tweak and re-organize ESLint config (#38369) 2023-03-29 13:49:30 -04:00
unit Fix: Popover with hover and click triggers closes on mouseleave (#41511) 2025-06-03 12:19:24 -07:00
visual [Docs] Remove some unnecessary Twitter references/examples (#41174) 2025-04-02 23:03:06 -07:00
README.md Docs: migration from Hugo to Astro (#41251) 2025-04-15 18:37:47 +02:00
browsers.js Tweak and re-organize ESLint config (#38369) 2023-03-29 13:49:30 -04:00
karma.conf.js Tweak and re-organize ESLint config (#38369) 2023-03-29 13:49:30 -04:00

README.md

How does Bootstraps test suite work?

Bootstrap uses Jasmine. Each plugin has a file dedicated to its tests in tests/unit/<plugin-name>.spec.js.

  • visual/ contains "visual" tests which are run interactively in real browsers and require manual verification by humans.

To run the unit test suite via Karma, run npm run js-test. To run the unit test suite via Karma and debug, run npm run js-debug.

How do I add a new unit test?

  1. Locate and open the file dedicated to the plugin which you need to add tests to (tests/unit/<plugin-name>.spec.js).
  2. Review the Jasmine API Documentation and use the existing tests as references for how to structure your new tests.
  3. Write the necessary unit test(s) for the new or revised functionality.
  4. Run npm run js-test to see the results of your newly-added test(s).

Note: Your new unit tests should fail before your changes are applied to the plugin, and should pass after your changes are applied to the plugin.

What should a unit test look like?

  • Each test should have a unique name clearly stating what unit is being tested.
  • Each test should be in the corresponding describe.
  • Each test should test only one unit per test, although one test can include several assertions. Create multiple tests for multiple units of functionality.
  • Each test should use expect to ensure something is expected.
  • Each test should follow the projects JavaScript Code Guidelines

Code coverage

Currently were aiming for at least 90% test coverage for our code. To ensure your changes meet or exceed this limit, run npm run js-test-karma and open the file in js/coverage/lcov-report/index.html to see the code coverage for each plugin. See more details when you select a plugin and ensure your change is fully covered by unit tests.

Example tests

// Synchronous test
describe('getInstance', () => {
  it('should return null if there is no instance', () => {
    // Make assertion
    expect(Tab.getInstance(fixtureEl)).toBeNull()
  })

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

    const divEl = fixtureEl.querySelector('div')
    const tab = new Tab(divEl)

    // Make assertion
    expect(Tab.getInstance(divEl)).toEqual(tab)
  })
})

// Asynchronous test
it('should show a tooltip without the animation', () => {
  return new Promise(resolve => {
    fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'

    const tooltipEl = fixtureEl.querySelector('a')
    const tooltip = new Tooltip(tooltipEl, {
      animation: false
    })

    tooltipEl.addEventListener('shown.bs.tooltip', () => {
      const tip = document.querySelector('.tooltip')

      expect(tip).not.toBeNull()
      expect(tip.classList.contains('fade')).toEqual(false)
      resolve()
    })

    tooltip.show()
  })
})