From e72a90dcea740023285ca58af63ed3faa6ad9f29 Mon Sep 17 00:00:00 2001
From: Roman Khmelichek <roman.khmelichek@gmail.com>
Date: Sat, 3 Oct 2015 22:52:13 -0400
Subject: [PATCH] Fix scrollspy regression on nested navs

---
 js/src/scrollspy.js        |  3 +--
 js/tests/unit/scrollspy.js | 45 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 46 insertions(+), 2 deletions(-)

diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js
index bdbd6439c9..c50758e321 100644
--- a/js/src/scrollspy.js
+++ b/js/src/scrollspy.js
@@ -249,9 +249,8 @@ const ScrollSpy = (($) => {
         $link.closest(Selector.DROPDOWN).find(Selector.DROPDOWN_TOGGLE).addClass(ClassName.ACTIVE)
         $link.addClass(ClassName.ACTIVE)
       } else {
-        // todo (fat) this is kinda sus…
         // recursively add actives to tested nav-links
-        $link.parents(Selector.LI).find(Selector.NAV_LINKS).addClass(ClassName.ACTIVE)
+        $link.parents(Selector.LI).find(`${Selector.NAV_LINKS}:first`).addClass(ClassName.ACTIVE)
       }
 
       $(this._scrollElement).trigger(Event.ACTIVATE, {
diff --git a/js/tests/unit/scrollspy.js b/js/tests/unit/scrollspy.js
index 878c4d389d..dc33e47e91 100644
--- a/js/tests/unit/scrollspy.js
+++ b/js/tests/unit/scrollspy.js
@@ -205,6 +205,51 @@ $(function () {
       .then(function () { return testElementIsActiveAfterScroll('#a-2', '#div-2') })
   })
 
+  QUnit.test('should add the active class only for the current target and its parents when using nested navs', function (assert) {
+    assert.expect(3)
+    var navbarHtml =
+        '<nav class="navbar">'
+      + '<ul class="nav">'
+      + '<li>'
+      + '<a class="nav-link" id="a-1" href="#div-1">div 1</a>'
+      + '<ul class="nav">'
+      + '<li><a class="nav-link" id="a-2" href="#div-2">div 2</a></li>'
+      + '<li><a class="nav-link" id="a-3" href="#div-3">div 3</a></li>'
+      + '</ul>'
+      + '</li>'
+      + '</ul>'
+      + '</nav>'
+    var contentHtml =
+        '<div class="content" style="overflow: auto; height: 50px">'
+      + '<div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>'
+      + '<div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>'
+      + '</div>'
+
+    $(navbarHtml).appendTo('#qunit-fixture')
+    var $content = $(contentHtml)
+      .appendTo('#qunit-fixture')
+      .bootstrapScrollspy({ offset: 0, target: '.navbar' })
+
+    var testElementIsActiveAfterScroll = function (element, target) {
+      var deferred = $.Deferred()
+      var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top)
+      $content.one('scroll', function () {
+        assert.ok($(element).hasClass('active'), 'target:' + target + ', element' + element)
+        deferred.resolve()
+      })
+      $content.scrollTop(scrollHeight)
+      return deferred.promise()
+    }
+
+    var done = assert.async()
+    $.when(testElementIsActiveAfterScroll('#a-2', '#div-2'))
+      .then(function () {
+        assert.ok($('#a-1').hasClass('active'), 'parent #a-1 should be active');
+        assert.notOk($('#a-3').hasClass('active'), 'sibling #a-3 should not be active');
+        done()
+      })
+  })
+
   QUnit.test('should add the active class correctly when there are nested elements at 0 scroll offset', function (assert) {
     assert.expect(6)
     var times = 0
-- 
GitLab