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