From c5b8c79ac491d5d150a29fb9da289e2a352fd283 Mon Sep 17 00:00:00 2001
From: GeoSot <geo.sotis@gmail.com>
Date: Thu, 8 Apr 2021 14:11:03 +0300
Subject: [PATCH 1/3] Tab.js: Fixes on click * use prevent default only if
 triggered by anchor * disable auto-initialization if trigger is disabled *
 Remove `isDisabled` check on `show` mehtod, as it is being handled by the
 click handler

---
 js/src/tab.js | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/js/src/tab.js b/js/src/tab.js
index ffca5f299e..4d823cc61c 100644
--- a/js/src/tab.js
+++ b/js/src/tab.js
@@ -66,8 +66,7 @@ class Tab extends BaseComponent {
   show() {
     if ((this._element.parentNode &&
       this._element.parentNode.nodeType === Node.ELEMENT_NODE &&
-      this._element.classList.contains(CLASS_NAME_ACTIVE)) ||
-      isDisabled(this._element)) {
+      this._element.classList.contains(CLASS_NAME_ACTIVE))) {
       return
     }
 
@@ -202,7 +201,13 @@ class Tab extends BaseComponent {
  */
 
 EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
-  event.preventDefault()
+  if (['A', 'AREA'].includes(this.tagName)) {
+    event.preventDefault()
+  }
+
+  if (isDisabled(this)) {
+    return
+  }
 
   const data = Data.get(this, DATA_KEY) || new Tab(this)
   data.show()
-- 
GitLab


From 0b144cbb5f541c0ba52fb485dda04300f54797b5 Mon Sep 17 00:00:00 2001
From: alpadev <2838324+alpadev@users.noreply.github.com>
Date: Mon, 19 Apr 2021 18:54:15 +0200
Subject: [PATCH 2/3] Readd isDisabled check

Sorry @GeoSot.. Figured my proposal is breaking our spec and it would be still possible to trigger show via jQuery. Maybe it's better to leave it there even if it's somewhat redundant..
---
 js/src/tab.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/js/src/tab.js b/js/src/tab.js
index 4d823cc61c..e03944cfa4 100644
--- a/js/src/tab.js
+++ b/js/src/tab.js
@@ -66,7 +66,8 @@ class Tab extends BaseComponent {
   show() {
     if ((this._element.parentNode &&
       this._element.parentNode.nodeType === Node.ELEMENT_NODE &&
-      this._element.classList.contains(CLASS_NAME_ACTIVE))) {
+      this._element.classList.contains(CLASS_NAME_ACTIVE)) ||
+      isDisabled(this._element)) {
       return
     }
 
-- 
GitLab


From 9bd99d64e452de70664b38c5f238c7f4eaa04f16 Mon Sep 17 00:00:00 2001
From: GeoSot <geo.sotis@gmail.com>
Date: Mon, 19 Apr 2021 21:45:17 +0300
Subject: [PATCH 3/3] Add tests | change existing tests to meet the new check
 only during click handling

---
 js/src/tab.js             |   3 +-
 js/tests/unit/tab.spec.js | 121 ++++++++++++++++++++++----------------
 2 files changed, 70 insertions(+), 54 deletions(-)

diff --git a/js/src/tab.js b/js/src/tab.js
index e03944cfa4..4d823cc61c 100644
--- a/js/src/tab.js
+++ b/js/src/tab.js
@@ -66,8 +66,7 @@ class Tab extends BaseComponent {
   show() {
     if ((this._element.parentNode &&
       this._element.parentNode.nodeType === Node.ELEMENT_NODE &&
-      this._element.classList.contains(CLASS_NAME_ACTIVE)) ||
-      isDisabled(this._element)) {
+      this._element.classList.contains(CLASS_NAME_ACTIVE))) {
       return
     }
 
diff --git a/js/tests/unit/tab.spec.js b/js/tests/unit/tab.spec.js
index 5b98bad9d5..4741b495de 100644
--- a/js/tests/unit/tab.spec.js
+++ b/js/tests/unit/tab.spec.js
@@ -198,58 +198,6 @@ describe('Tab', () => {
       }, 30)
     })
 
-    it('should not fire shown when tab has disabled attribute', done => {
-      fixtureEl.innerHTML = [
-        '<ul class="nav nav-tabs" role="tablist">',
-        '  <li class="nav-item" role="presentation"><button type="button" data-bs-target="#home" class="nav-link active" role="tab" aria-selected="true">Home</button></li>',
-        '  <li class="nav-item" role="presentation"><button type="button" data-bs-target="#profile" class="nav-link" disabled role="tab">Profile</button></li>',
-        '</ul>',
-        '<div class="tab-content">',
-        '  <div class="tab-pane active" id="home" role="tabpanel"></div>',
-        '  <div class="tab-pane" id="profile" role="tabpanel"></div>',
-        '</div>'
-      ].join('')
-
-      const triggerDisabled = fixtureEl.querySelector('button[disabled]')
-      const tab = new Tab(triggerDisabled)
-
-      triggerDisabled.addEventListener('shown.bs.tab', () => {
-        throw new Error('should not trigger shown event')
-      })
-
-      tab.show()
-      setTimeout(() => {
-        expect().nothing()
-        done()
-      }, 30)
-    })
-
-    it('should not fire shown when tab has disabled class', done => {
-      fixtureEl.innerHTML = [
-        '<ul class="nav nav-tabs" role="tablist">',
-        '  <li class="nav-item" role="presentation"><a href="#home" class="nav-link active" role="tab" aria-selected="true">Home</a></li>',
-        '  <li class="nav-item" role="presentation"><a href="#profile" class="nav-link disabled" role="tab">Profile</a></li>',
-        '</ul>',
-        '<div class="tab-content">',
-        '  <div class="tab-pane active" id="home" role="tabpanel"></div>',
-        '  <div class="tab-pane" id="profile" role="tabpanel"></div>',
-        '</div>'
-      ].join('')
-
-      const triggerDisabled = fixtureEl.querySelector('a.disabled')
-      const tab = new Tab(triggerDisabled)
-
-      triggerDisabled.addEventListener('shown.bs.tab', () => {
-        throw new Error('should not trigger shown event')
-      })
-
-      tab.show()
-      setTimeout(() => {
-        expect().nothing()
-        done()
-      }, 30)
-    })
-
     it('show and shown events should reference correct relatedTarget', done => {
       fixtureEl.innerHTML = [
         '<ul class="nav nav-tabs" role="tablist">',
@@ -695,5 +643,74 @@ describe('Tab', () => {
 
       secondNavEl.click()
     })
+
+    it('should prevent default when the trigger is <a> or <area>', done => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav" role="tablist">',
+        '  <li><a type="button" href="#test"  class="active" role="tab" data-bs-toggle="tab">Home</a></li>',
+        '  <li><a type="button" href="#test2" role="tab" data-bs-toggle="tab">Home</a></li>',
+        '</ul>'
+      ].join('')
+
+      const tabEl = fixtureEl.querySelector('[href="#test2"]')
+      spyOn(Event.prototype, 'preventDefault').and.callThrough()
+
+      tabEl.addEventListener('shown.bs.tab', () => {
+        expect(tabEl.classList.contains('active')).toEqual(true)
+        expect(Event.prototype.preventDefault).toHaveBeenCalled()
+        done()
+      })
+
+      tabEl.click()
+    })
+
+    it('should not fire shown when tab has disabled attribute', done => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav nav-tabs" role="tablist">',
+        '  <li class="nav-item" role="presentation"><button type="button" data-bs-target="#home" class="nav-link active" role="tab" aria-selected="true">Home</button></li>',
+        '  <li class="nav-item" role="presentation"><button type="button" data-bs-target="#profile" class="nav-link" disabled role="tab">Profile</button></li>',
+        '</ul>',
+        '<div class="tab-content">',
+        '  <div class="tab-pane active" id="home" role="tabpanel"></div>',
+        '  <div class="tab-pane" id="profile" role="tabpanel"></div>',
+        '</div>'
+      ].join('')
+
+      const triggerDisabled = fixtureEl.querySelector('button[disabled]')
+      triggerDisabled.addEventListener('shown.bs.tab', () => {
+        throw new Error('should not trigger shown event')
+      })
+
+      triggerDisabled.click()
+      setTimeout(() => {
+        expect().nothing()
+        done()
+      }, 30)
+    })
+
+    it('should not fire shown when tab has disabled class', done => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav nav-tabs" role="tablist">',
+        '  <li class="nav-item" role="presentation"><a href="#home" class="nav-link active" role="tab" aria-selected="true">Home</a></li>',
+        '  <li class="nav-item" role="presentation"><a href="#profile" class="nav-link disabled" role="tab">Profile</a></li>',
+        '</ul>',
+        '<div class="tab-content">',
+        '  <div class="tab-pane active" id="home" role="tabpanel"></div>',
+        '  <div class="tab-pane" id="profile" role="tabpanel"></div>',
+        '</div>'
+      ].join('')
+
+      const triggerDisabled = fixtureEl.querySelector('a.disabled')
+
+      triggerDisabled.addEventListener('shown.bs.tab', () => {
+        throw new Error('should not trigger shown event')
+      })
+
+      triggerDisabled.click()
+      setTimeout(() => {
+        expect().nothing()
+        done()
+      }, 30)
+    })
   })
 })
-- 
GitLab