diff --git a/js/src/collapse.js b/js/src/collapse.js
index a6b540de9e846d2160846595ab93baf7367fef63..9c7ff927766563606a4096351501dd08ffd4eb02 100644
--- a/js/src/collapse.js
+++ b/js/src/collapse.js
@@ -5,7 +5,9 @@
  * --------------------------------------------------------------------------
  */
 
-import $ from 'jquery'
+import Data from './dom/data'
+import EventHandler from './dom/eventHandler'
+import SelectorEngine from './dom/selectorEngine'
 import Util from './util'
 
 /**
@@ -19,7 +21,6 @@ const VERSION             = '4.3.1'
 const DATA_KEY            = 'bs.collapse'
 const EVENT_KEY           = `.${DATA_KEY}`
 const DATA_API_KEY        = '.data-api'
-const JQUERY_NO_CONFLICT  = $.fn[NAME]
 
 const Default = {
   toggle : true,
@@ -67,19 +68,19 @@ class Collapse {
     this._isTransitioning = false
     this._element         = element
     this._config          = this._getConfig(config)
-    this._triggerArray    = [].slice.call(document.querySelectorAll(
+    this._triggerArray    = Util.makeArray(SelectorEngine.find(
       `[data-toggle="collapse"][href="#${element.id}"],` +
       `[data-toggle="collapse"][data-target="#${element.id}"]`
     ))
 
-    const toggleList = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE))
+    const toggleList = Util.makeArray(document.querySelectorAll(Selector.DATA_TOGGLE))
     for (let i = 0, len = toggleList.length; i < len; i++) {
       const elem = toggleList[i]
       const selector = Util.getSelectorFromElement(elem)
-      const filterElement = [].slice.call(document.querySelectorAll(selector))
+      const filterElement = Util.makeArray(document.querySelectorAll(selector))
         .filter((foundElem) => foundElem === element)
 
-      if (selector !== null && filterElement.length > 0) {
+      if (selector !== null && filterElement.length) {
         this._selector = selector
         this._triggerArray.push(elem)
       }
@@ -109,7 +110,7 @@ class Collapse {
   // Public
 
   toggle() {
-    if ($(this._element).hasClass(ClassName.SHOW)) {
+    if (this._element.classList.contains(ClassName.SHOW)) {
       this.hide()
     } else {
       this.show()
@@ -118,7 +119,7 @@ class Collapse {
 
   show() {
     if (this._isTransitioning ||
-      $(this._element).hasClass(ClassName.SHOW)) {
+      this._element.classList.contains(ClassName.SHOW)) {
       return
     }
 
@@ -126,7 +127,7 @@ class Collapse {
     let activesData
 
     if (this._parent) {
-      actives = [].slice.call(this._parent.querySelectorAll(Selector.ACTIVES))
+      actives = Util.makeArray(this._parent.querySelectorAll(Selector.ACTIVES))
         .filter((elem) => {
           if (typeof this._config.parent === 'string') {
             return elem.getAttribute('data-parent') === this._config.parent
@@ -141,74 +142,70 @@ class Collapse {
     }
 
     if (actives) {
-      activesData = $(actives).not(this._selector).data(DATA_KEY)
+      activesData = Data.getData(actives[0], DATA_KEY)
       if (activesData && activesData._isTransitioning) {
         return
       }
     }
 
-    const startEvent = $.Event(Event.SHOW)
-    $(this._element).trigger(startEvent)
-    if (startEvent.isDefaultPrevented()) {
+    const startEvent = EventHandler.trigger(this._element, Event.SHOW)
+    if (startEvent.defaultPrevented) {
       return
     }
 
     if (actives) {
-      Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide')
+      actives.forEach((elemActive) => Collapse._collapseInterface(elemActive, 'hide'))
       if (!activesData) {
-        $(actives).data(DATA_KEY, null)
+        Data.setData(actives[0], DATA_KEY, null)
       }
     }
 
     const dimension = this._getDimension()
 
-    $(this._element)
-      .removeClass(ClassName.COLLAPSE)
-      .addClass(ClassName.COLLAPSING)
+    this._element.classList.remove(ClassName.COLLAPSE)
+    this._element.classList.add(ClassName.COLLAPSING)
 
     this._element.style[dimension] = 0
 
     if (this._triggerArray.length) {
-      $(this._triggerArray)
-        .removeClass(ClassName.COLLAPSED)
-        .attr('aria-expanded', true)
+      this._triggerArray.forEach((element) => {
+        element.classList.remove(ClassName.COLLAPSED)
+        element.setAttribute('aria-expanded', true)
+      })
     }
 
     this.setTransitioning(true)
 
     const complete = () => {
-      $(this._element)
-        .removeClass(ClassName.COLLAPSING)
-        .addClass(ClassName.COLLAPSE)
-        .addClass(ClassName.SHOW)
+      this._element.classList.remove(ClassName.COLLAPSING)
+      this._element.classList.add(ClassName.COLLAPSE)
+      this._element.classList.add(ClassName.SHOW)
 
       this._element.style[dimension] = ''
 
       this.setTransitioning(false)
 
-      $(this._element).trigger(Event.SHOWN)
+      EventHandler.trigger(this._element, Event.SHOWN)
     }
 
     const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)
     const scrollSize = `scroll${capitalizedDimension}`
     const transitionDuration = Util.getTransitionDurationFromElement(this._element)
 
-    $(this._element)
-      .one(Util.TRANSITION_END, complete)
+    EventHandler.one(this._element, Util.TRANSITION_END, complete)
 
-    Util.emulateTransitionEnd(transitionDuration)
+    Util.emulateTransitionEnd(this._element, transitionDuration)
     this._element.style[dimension] = `${this._element[scrollSize]}px`
   }
 
   hide() {
     if (this._isTransitioning ||
-      !$(this._element).hasClass(ClassName.SHOW)) {
+      !this._element.classList.contains(ClassName.SHOW)) {
       return
     }
 
-    const startEvent = $.Event(Event.HIDE)
-    $(this._element).trigger(startEvent)
-    if (startEvent.isDefaultPrevented()) {
+    const startEvent = EventHandler.trigger(this._element, Event.HIDE)
+    if (startEvent.defaultPrevented) {
       return
     }
 
@@ -218,10 +215,9 @@ class Collapse {
 
     Util.reflow(this._element)
 
-    $(this._element)
-      .addClass(ClassName.COLLAPSING)
-      .removeClass(ClassName.COLLAPSE)
-      .removeClass(ClassName.SHOW)
+    this._element.classList.add(ClassName.COLLAPSING)
+    this._element.classList.remove(ClassName.COLLAPSE)
+    this._element.classList.remove(ClassName.SHOW)
 
     const triggerArrayLength = this._triggerArray.length
     if (triggerArrayLength > 0) {
@@ -230,10 +226,11 @@ class Collapse {
         const selector = Util.getSelectorFromElement(trigger)
 
         if (selector !== null) {
-          const $elem = $([].slice.call(document.querySelectorAll(selector)))
-          if (!$elem.hasClass(ClassName.SHOW)) {
-            $(trigger).addClass(ClassName.COLLAPSED)
-              .attr('aria-expanded', false)
+          const elem = SelectorEngine.findOne(selector)
+
+          if (!elem.classList.contains(ClassName.SHOW)) {
+            trigger.classList.add(ClassName.COLLAPSED)
+            trigger.setAttribute('aria-expanded', false)
           }
         }
       }
@@ -243,19 +240,16 @@ class Collapse {
 
     const complete = () => {
       this.setTransitioning(false)
-      $(this._element)
-        .removeClass(ClassName.COLLAPSING)
-        .addClass(ClassName.COLLAPSE)
-        .trigger(Event.HIDDEN)
+      this._element.classList.remove(ClassName.COLLAPSING)
+      this._element.classList.add(ClassName.COLLAPSE)
+      EventHandler.trigger(this._element, Event.HIDDEN)
     }
 
     this._element.style[dimension] = ''
     const transitionDuration = Util.getTransitionDurationFromElement(this._element)
 
-    $(this._element)
-      .one(Util.TRANSITION_END, complete)
-
-    Util.emulateTransitionEnd(transitionDuration)
+    EventHandler.one(this._element, Util.TRANSITION_END, complete)
+    Util.emulateTransitionEnd(this._element, transitionDuration)
   }
 
   setTransitioning(isTransitioning) {
@@ -263,7 +257,7 @@ class Collapse {
   }
 
   dispose() {
-    $.removeData(this._element, DATA_KEY)
+    Data.removeData(this._element, DATA_KEY)
 
     this._config          = null
     this._parent          = null
@@ -285,7 +279,7 @@ class Collapse {
   }
 
   _getDimension() {
-    const hasWidth = $(this._element).hasClass(Dimension.WIDTH)
+    const hasWidth = this._element.classList.contains(Dimension.WIDTH)
     return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT
   }
 
@@ -300,14 +294,14 @@ class Collapse {
         parent = this._config.parent[0]
       }
     } else {
-      parent = document.querySelector(this._config.parent)
+      parent = SelectorEngine.findOne(this._config.parent)
     }
 
     const selector =
       `[data-toggle="collapse"][data-parent="${this._config.parent}"]`
 
-    const children = [].slice.call(parent.querySelectorAll(selector))
-    $(children).each((i, element) => {
+    const elements = Util.makeArray(SelectorEngine.find(selector, parent))
+    elements.forEach((element) => {
       this._addAriaAndCollapsedClass(
         Collapse._getTargetFromElement(element),
         [element]
@@ -318,12 +312,19 @@ class Collapse {
   }
 
   _addAriaAndCollapsedClass(element, triggerArray) {
-    const isOpen = $(element).hasClass(ClassName.SHOW)
-
-    if (triggerArray.length) {
-      $(triggerArray)
-        .toggleClass(ClassName.COLLAPSED, !isOpen)
-        .attr('aria-expanded', isOpen)
+    if (element) {
+      const isOpen = element.classList.contains(ClassName.SHOW)
+
+      if (triggerArray.length) {
+        triggerArray.forEach((elem) => {
+          if (!isOpen) {
+            elem.classList.add(ClassName.COLLAPSED)
+          } else {
+            elem.classList.remove(ClassName.COLLAPSED)
+          }
+          elem.setAttribute('aria-expanded', isOpen)
+        })
+      }
     }
   }
 
@@ -334,31 +335,34 @@ class Collapse {
     return selector ? document.querySelector(selector) : null
   }
 
-  static _jQueryInterface(config) {
-    return this.each(function () {
-      const $this   = $(this)
-      let data      = $this.data(DATA_KEY)
-      const _config = {
-        ...Default,
-        ...$this.data(),
-        ...typeof config === 'object' && config ? config : {}
-      }
+  static _collapseInterface(element, config) {
+    let data      = Data.getData(element, DATA_KEY)
+    const _config = {
+      ...Default,
+      ...Util.getDataAttributes(element),
+      ...typeof config === 'object' && config ? config : {}
+    }
 
-      if (!data && _config.toggle && /show|hide/.test(config)) {
-        _config.toggle = false
-      }
+    if (!data && _config.toggle && /show|hide/.test(config)) {
+      _config.toggle = false
+    }
 
-      if (!data) {
-        data = new Collapse(this, _config)
-        $this.data(DATA_KEY, data)
-      }
+    if (!data) {
+      data = new Collapse(element, _config)
+      Data.setData(element, DATA_KEY, data)
+    }
 
-      if (typeof config === 'string') {
-        if (typeof data[config] === 'undefined') {
-          throw new TypeError(`No method named "${config}"`)
-        }
-        data[config]()
+    if (typeof config === 'string') {
+      if (typeof data[config] === 'undefined') {
+        throw new Error(`No method named "${config}"`)
       }
+      data[config]()
+    }
+  }
+
+  static _jQueryInterface(config) {
+    return this.each(function () {
+      Collapse._collapseInterface(this, config)
     })
   }
 }
@@ -369,21 +373,31 @@ class Collapse {
  * ------------------------------------------------------------------------
  */
 
-$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
+EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
   // preventDefault only for <a> elements (which change the URL) not inside the collapsible element
-  if (event.currentTarget.tagName === 'A') {
+  if (event.target.tagName === 'A') {
     event.preventDefault()
   }
 
-  const $trigger = $(this)
-  const selector = Util.getSelectorFromElement(this)
-  const selectors = [].slice.call(document.querySelectorAll(selector))
+  const triggerData      = Util.getDataAttributes(this)
+  const selector         = Util.getSelectorFromElement(this)
+  const selectorElements = Util.makeArray(SelectorEngine.find(selector))
+
+  selectorElements.forEach((element) => {
+    const data = Data.getData(element, DATA_KEY)
+    let config
+    if (data) {
+      // update parent attribute
+      if (data._parent === null && typeof triggerData.parent === 'string') {
+        data._config.parent = triggerData.parent
+        data._parent = data._getParent()
+      }
+      config = 'toggle'
+    } else {
+      config = triggerData
+    }
 
-  $(selectors).each(function () {
-    const $target = $(this)
-    const data    = $target.data(DATA_KEY)
-    const config  = data ? 'toggle' : $trigger.data()
-    Collapse._jQueryInterface.call($target, config)
+    Collapse._collapseInterface(element, config)
   })
 })
 
@@ -391,13 +405,18 @@ $(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
  * ------------------------------------------------------------------------
  * jQuery
  * ------------------------------------------------------------------------
+ * add .collapse to jQuery only if jQuery is present
  */
 
-$.fn[NAME] = Collapse._jQueryInterface
-$.fn[NAME].Constructor = Collapse
-$.fn[NAME].noConflict = () => {
-  $.fn[NAME] = JQUERY_NO_CONFLICT
-  return Collapse._jQueryInterface
+const $ = Util.jQuery
+if (typeof $ !== 'undefined') {
+  const JQUERY_NO_CONFLICT  = $.fn[NAME]
+  $.fn[NAME]                = Collapse._jQueryInterface
+  $.fn[NAME].Constructor    = Collapse
+  $.fn[NAME].noConflict     = () => {
+    $.fn[NAME] = JQUERY_NO_CONFLICT
+    return Collapse._jQueryInterface
+  }
 }
 
 export default Collapse
diff --git a/js/src/util.js b/js/src/util.js
index 607d50fd43c04e89cda4f4c53751af4f16bae24b..e0a81b9ec87c17e86b0979c0f1892fc5c51cc7f1 100644
--- a/js/src/util.js
+++ b/js/src/util.js
@@ -154,7 +154,14 @@ const Util = {
     if (typeof nodeList === 'undefined' || nodeList === null) {
       return []
     }
-    return Array.prototype.slice.call(nodeList)
+
+    const strRepresentation = Object.prototype.toString.call(nodeList)
+    if (strRepresentation === '[object NodeList]' ||
+      strRepresentation === '[object HTMLCollection]' || strRepresentation === '[object Array]') {
+      return Array.prototype.slice.call(nodeList)
+    }
+
+    return [nodeList]
   },
 
   getDataAttributes(element) {
diff --git a/js/tests/unit/collapse.js b/js/tests/unit/collapse.js
index e7fb8893edae7be4dafd05078275a947ea3f0224..3df60200b91553f28349a2aae769589835bd8ee2 100644
--- a/js/tests/unit/collapse.js
+++ b/js/tests/unit/collapse.js
@@ -71,7 +71,7 @@ $(function () {
       assert.ok(!/height/i.test($el2.attr('style')), 'has height reset')
       done()
     })
-    $target.trigger('click')
+    EventHandler.trigger($target[0], 'click')
   })
 
   QUnit.test('should collapse only the first collapse', function (assert) {
@@ -165,7 +165,7 @@ $(function () {
         done()
       })
 
-    $target.trigger('click')
+    EventHandler.trigger($target[0], 'click')
   })
 
   QUnit.test('should add "collapsed" class to target when collapse is hidden', function (assert) {
@@ -181,7 +181,7 @@ $(function () {
         done()
       })
 
-    $target.trigger('click')
+    EventHandler.trigger($target[0], 'click')
   })
 
   QUnit.test('should remove "collapsed" class from all triggers targeting the collapse when the collapse is shown', function (assert) {
@@ -199,7 +199,7 @@ $(function () {
         done()
       })
 
-    $target.trigger('click')
+    EventHandler.trigger($target[0], 'click')
   })
 
   QUnit.test('should add "collapsed" class to all triggers targeting the collapse when the collapse is hidden', function (assert) {
@@ -217,7 +217,7 @@ $(function () {
         done()
       })
 
-    $target.trigger('click')
+    EventHandler.trigger($target[0], 'click')
   })
 
   QUnit.test('should not close a collapse when initialized with "show" option if already shown', function (assert) {
@@ -309,7 +309,7 @@ $(function () {
         done()
       })
 
-    $target3.trigger('click')
+    EventHandler.trigger($target3[0], 'click')
   })
 
   QUnit.test('should allow dots in data-parent', function (assert) {
@@ -343,7 +343,7 @@ $(function () {
         done()
       })
 
-    $target3.trigger('click')
+    EventHandler.trigger($target3[0], 'click')
   })
 
   QUnit.test('should set aria-expanded="true" on trigger/control when collapse is shown', function (assert) {
@@ -359,7 +359,7 @@ $(function () {
         done()
       })
 
-    $target.trigger('click')
+    EventHandler.trigger($target[0], 'click')
   })
 
   QUnit.test('should set aria-expanded="false" on trigger/control when collapse is hidden', function (assert) {
@@ -375,7 +375,7 @@ $(function () {
         done()
       })
 
-    $target.trigger('click')
+    EventHandler.trigger($target[0], 'click')
   })
 
   QUnit.test('should set aria-expanded="true" on all triggers targeting the collapse when the collapse is shown', function (assert) {
@@ -393,7 +393,7 @@ $(function () {
         done()
       })
 
-    $target.trigger('click')
+    EventHandler.trigger($target[0], 'click')
   })
 
   QUnit.test('should set aria-expanded="false" on all triggers targeting the collapse when the collapse is hidden', function (assert) {
@@ -411,7 +411,7 @@ $(function () {
         done()
       })
 
-    $target.trigger('click')
+    EventHandler.trigger($target[0], 'click')
   })
 
   QUnit.test('should change aria-expanded from active accordion trigger/control to "false" and set the trigger/control for the newly active one to "true"', function (assert) {
@@ -445,7 +445,7 @@ $(function () {
         done()
       })
 
-    $target3.trigger('click')
+    EventHandler.trigger($target3[0], 'click')
   })
 
   QUnit.test('should not fire show event if show is prevented because other element is still transitioning', function (assert) {
@@ -470,13 +470,12 @@ $(function () {
     var $target2 = $('<a role="button" data-toggle="collapse" href="#body2"/>').appendTo($groups.eq(1))
     var $body2   = $('<div id="body2" class="collapse" data-parent="#accordion"/>').appendTo($groups.eq(1))
 
-    $target2.trigger('click')
+    EventHandler.trigger($target2[0], 'click')
 
-    $body2
-      .toggleClass('show collapsing')
-      .data('bs.collapse')._isTransitioning = 1
+    $body2.toggleClass('show collapsing')
+    Data.getData($body2[0], 'bs.collapse')._isTransitioning = true
 
-    $target1.trigger('click')
+    EventHandler.trigger($target1[0], 'click')
 
     setTimeout(function () {
       assert.ok(!showFired, 'show event did not fire')
@@ -541,9 +540,9 @@ $(function () {
         assert.ok($collapseTwo.hasClass('show'), '#collapseTwo is shown')
         done()
       })
-      $triggerTwo.trigger($.Event('click'))
+      EventHandler.trigger($triggerTwo[0], 'click')
     })
-    $trigger.trigger($.Event('click'))
+    EventHandler.trigger($trigger[0], 'click')
   })
 
   QUnit.test('should allow accordion to contain nested elements', function (assert) {
@@ -687,40 +686,40 @@ $(function () {
     var $collapseTwo = $('#collapseTwo')
     var $nestedCollapseOne = $('#nestedCollapseOne')
 
-    $collapseOne.one('shown.bs.collapse', function () {
+    EventHandler.one($collapseOne[0], 'shown.bs.collapse', function () {
       assert.ok($collapseOne.hasClass('show'), '#collapseOne is shown')
       assert.ok(!$collapseTwo.hasClass('show'), '#collapseTwo is not shown')
       assert.ok(!$('#nestedCollapseOne').hasClass('show'), '#nestedCollapseOne is not shown')
-      $nestedCollapseOne.one('shown.bs.collapse', function () {
+
+      EventHandler.one($nestedCollapseOne[0], 'shown.bs.collapse', function () {
         assert.ok($collapseOne.hasClass('show'), '#collapseOne is shown')
         assert.ok(!$collapseTwo.hasClass('show'), '#collapseTwo is not shown')
         assert.ok($nestedCollapseOne.hasClass('show'), '#nestedCollapseOne is shown')
-        $collapseTwo.one('shown.bs.collapse', function () {
+        EventHandler.one($collapseTwo[0], 'shown.bs.collapse', function () {
           assert.ok(!$collapseOne.hasClass('show'), '#collapseOne is not shown')
           assert.ok($collapseTwo.hasClass('show'), '#collapseTwo is shown')
           assert.ok($nestedCollapseOne.hasClass('show'), '#nestedCollapseOne is shown')
           done()
         })
-        $triggerTwo.trigger($.Event('click'))
+        EventHandler.trigger($triggerTwo[0], 'click')
       })
-      $nestedTrigger.trigger($.Event('click'))
+      EventHandler.trigger($nestedTrigger[0], 'click')
     })
-    $trigger.trigger($.Event('click'))
+    EventHandler.trigger($trigger[0], 'click')
   })
 
   QUnit.test('should not prevent event for input', function (assert) {
     assert.expect(3)
     var done = assert.async()
     var $target = $('<input type="checkbox" data-toggle="collapse" data-target="#collapsediv1" />').appendTo('#qunit-fixture')
+    var $collapse = $('<div id="collapsediv1"/>').appendTo('#qunit-fixture')
 
-    $('<div id="collapsediv1"/>')
-      .appendTo('#qunit-fixture')
-      .on('shown.bs.collapse', function () {
-        assert.ok($(this).hasClass('show'))
-        assert.ok($target.attr('aria-expanded') === 'true')
-        assert.ok($target.prop('checked'))
-        done()
-      })
+    EventHandler.one($collapse[0], 'shown.bs.collapse', function () {
+      assert.ok($collapse.hasClass('show'))
+      assert.ok($target.attr('aria-expanded') === 'true')
+      assert.ok($target.prop('checked'))
+      done()
+    })
 
     $target.trigger($.Event('click'))
   })
@@ -750,11 +749,11 @@ $(function () {
           assert.ok($trigger3.hasClass('collapsed'), 'trigger3 has collapsed class')
           done()
         })
-        $trigger1.trigger('click')
+        EventHandler.trigger($trigger1[0], 'click')
       })
-      $trigger2.trigger('click')
+      EventHandler.trigger($trigger2[0], 'click')
     })
-    $trigger3.trigger('click')
+    EventHandler.trigger($trigger3[0], 'click')
   })
 
   QUnit.test('should set aria-expanded="true" to triggers targeting shown collaspe and aria-expanded="false" only when all the targeted collapses are shown', function (assert) {
@@ -782,11 +781,11 @@ $(function () {
           assert.strictEqual($trigger3.attr('aria-expanded'), 'false', 'aria-expanded on trigger3 is "false"')
           done()
         })
-        $trigger1.trigger('click')
+        EventHandler.trigger($trigger1[0], 'click')
       })
-      $trigger2.trigger('click')
+      EventHandler.trigger($trigger2[0], 'click')
     })
-    $trigger3.trigger('click')
+    EventHandler.trigger($trigger3[0], 'click')
   })
 
   QUnit.test('should not prevent interactions inside the collapse element', function (assert) {
@@ -798,19 +797,17 @@ $(function () {
       '<div id="collapsediv1" class="collapse">' +
       ' <input type="checkbox" id="testCheckbox" />' +
       '</div>'
-
-    $(htmlCollapse)
-      .appendTo('#qunit-fixture')
-      .on('shown.bs.collapse', function () {
-        assert.ok($target.prop('checked'), '$trigger is checked')
-        var $testCheckbox = $('#testCheckbox')
-        $testCheckbox.trigger($.Event('click'))
-        setTimeout(function () {
-          assert.ok($testCheckbox.prop('checked'), '$testCheckbox is checked too')
-          done()
-        }, 5)
-      })
-
+    var $collapse = $(htmlCollapse).appendTo('#qunit-fixture')
+
+    EventHandler.one($collapse[0], 'shown.bs.collapse', function () {
+      assert.ok($target.prop('checked'), '$trigger is checked')
+      var $testCheckbox = $('#testCheckbox')
+      $testCheckbox.trigger($.Event('click'))
+      setTimeout(function () {
+        assert.ok($testCheckbox.prop('checked'), '$testCheckbox is checked too')
+        done()
+      }, 5)
+    })
     $target.trigger($.Event('click'))
   })
 
diff --git a/js/tests/visual/collapse.html b/js/tests/visual/collapse.html
index e084bd08bc721cf1e53f88c1482b58916a63cb9f..49d2ae82ab64cf5e3431e0f70bfb1577e0082553 100644
--- a/js/tests/visual/collapse.html
+++ b/js/tests/visual/collapse.html
@@ -73,6 +73,8 @@
 
     <script src="../../../node_modules/jquery/dist/jquery.slim.min.js"></script>
     <script src="../../dist/dom/eventHandler.js"></script>
+    <script src="../../dist/dom/selectorEngine.js"></script>
+    <script src="../../dist/dom/data.js"></script>
     <script src="../../dist/util.js"></script>
     <script src="../../dist/collapse.js"></script>
   </body>