Commit 3b3366e1 authored by Patrick H. Lauke's avatar Patrick H. Lauke Committed by GitHub
Browse files

remove dropdown.js reliance on roles and fix keyboard navigation

parents 8c975327 6301fabe
Showing with 333 additions and 218 deletions
+333 -218
...@@ -25,10 +25,11 @@ const Dropdown = (($) => { ...@@ -25,10 +25,11 @@ const Dropdown = (($) => {
const JQUERY_NO_CONFLICT = $.fn[NAME] const JQUERY_NO_CONFLICT = $.fn[NAME]
const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key
const SPACE_KEYCODE = 32 // KeyboardEvent.which value for space key const SPACE_KEYCODE = 32 // KeyboardEvent.which value for space key
const TAB_KEYCODE = 9 // KeyboardEvent.which value for tab key
const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key
const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key
const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse) const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse)
const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}|${SPACE_KEYCODE}`) const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`)
const Event = { const Event = {
HIDE : `hide${EVENT_KEY}`, HIDE : `hide${EVENT_KEY}`,
...@@ -37,8 +38,8 @@ const Dropdown = (($) => { ...@@ -37,8 +38,8 @@ const Dropdown = (($) => {
SHOWN : `shown${EVENT_KEY}`, SHOWN : `shown${EVENT_KEY}`,
CLICK : `click${EVENT_KEY}`, CLICK : `click${EVENT_KEY}`,
CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`, CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`,
FOCUSIN_DATA_API : `focusin${EVENT_KEY}${DATA_API_KEY}`, KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}`,
KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}` KEYUP_DATA_API : `keyup${EVENT_KEY}${DATA_API_KEY}`
} }
const ClassName = { const ClassName = {
...@@ -51,11 +52,9 @@ const Dropdown = (($) => { ...@@ -51,11 +52,9 @@ const Dropdown = (($) => {
BACKDROP : '.dropdown-backdrop', BACKDROP : '.dropdown-backdrop',
DATA_TOGGLE : '[data-toggle="dropdown"]', DATA_TOGGLE : '[data-toggle="dropdown"]',
FORM_CHILD : '.dropdown form', FORM_CHILD : '.dropdown form',
ROLE_MENU : '[role="menu"]', MENU : '.dropdown-menu',
ROLE_LISTBOX : '[role="listbox"]',
NAVBAR_NAV : '.navbar-nav', NAVBAR_NAV : '.navbar-nav',
VISIBLE_ITEMS : '[role="menu"] li:not(.disabled) a, ' VISIBLE_ITEMS : '.dropdown-menu .dropdown-item:not(.disabled)'
+ '[role="listbox"] li:not(.disabled) a'
} }
...@@ -164,7 +163,8 @@ const Dropdown = (($) => { ...@@ -164,7 +163,8 @@ const Dropdown = (($) => {
} }
static _clearMenus(event) { static _clearMenus(event) {
if (event && event.which === RIGHT_MOUSE_BUTTON_WHICH) { if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH ||
event.type === 'keyup' && event.which !== TAB_KEYCODE)) {
return return
} }
...@@ -181,7 +181,7 @@ const Dropdown = (($) => { ...@@ -181,7 +181,7 @@ const Dropdown = (($) => {
} }
if (event && (event.type === 'click' && if (event && (event.type === 'click' &&
/input|textarea/i.test(event.target.tagName) || event.type === 'focusin') /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE)
&& $.contains(parent, event.target)) { && $.contains(parent, event.target)) {
continue continue
} }
...@@ -218,7 +218,7 @@ const Dropdown = (($) => { ...@@ -218,7 +218,7 @@ const Dropdown = (($) => {
} }
static _dataApiKeydownHandler(event) { static _dataApiKeydownHandler(event) {
if (!REGEXP_KEYDOWN.test(event.which) || if (!REGEXP_KEYDOWN.test(event.which) || /button/i.test(event.target.tagName) && event.which === SPACE_KEYCODE ||
/input|textarea/i.test(event.target.tagName)) { /input|textarea/i.test(event.target.tagName)) {
return return
} }
...@@ -233,8 +233,8 @@ const Dropdown = (($) => { ...@@ -233,8 +233,8 @@ const Dropdown = (($) => {
const parent = Dropdown._getParentFromElement(this) const parent = Dropdown._getParentFromElement(this)
const isActive = $(parent).hasClass(ClassName.SHOW) const isActive = $(parent).hasClass(ClassName.SHOW)
if (!isActive && event.which !== ESCAPE_KEYCODE || if (!isActive && (event.which !== ESCAPE_KEYCODE || event.which !== SPACE_KEYCODE) ||
isActive && event.which === ESCAPE_KEYCODE) { isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) {
if (event.which === ESCAPE_KEYCODE) { if (event.which === ESCAPE_KEYCODE) {
const toggle = $(parent).find(Selector.DATA_TOGGLE)[0] const toggle = $(parent).find(Selector.DATA_TOGGLE)[0]
...@@ -279,9 +279,8 @@ const Dropdown = (($) => { ...@@ -279,9 +279,8 @@ const Dropdown = (($) => {
$(document) $(document)
.on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler) .on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler)
.on(Event.KEYDOWN_DATA_API, Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler) .on(Event.KEYDOWN_DATA_API, Selector.MENU, Dropdown._dataApiKeydownHandler)
.on(Event.KEYDOWN_DATA_API, Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler) .on(`${Event.CLICK_DATA_API} ${Event.KEYUP_DATA_API}`, Dropdown._clearMenus)
.on(`${Event.CLICK_DATA_API} ${Event.FOCUSIN_DATA_API}`, Dropdown._clearMenus)
.on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle) .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle)
.on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => { .on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => {
e.stopPropagation() e.stopPropagation()
......
...@@ -45,58 +45,62 @@ $(function () { ...@@ -45,58 +45,62 @@ $(function () {
}) })
QUnit.test('should not open dropdown if target is disabled via attribute', function (assert) { QUnit.test('should not open dropdown if target is disabled via attribute', function (assert) {
assert.expect(1) assert.expect(0)
var dropdownHTML = '<ul class="tabs">' var dropdownHTML = '<div class="tabs">'
+ '<li class="dropdown">' + '<div class="dropdown">'
+ '<button disabled href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>' + '<button disabled href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>' + '<a class="dropdown-item" href="#">Secondary link</a>'
+ '<li><a href="#">Something else here</a></li>' + '<a class="dropdown-item" href="#">Something else here</a>'
+ '<li class="divider"/>' + '<div class="divider"/>'
+ '<li><a href="#">Another link</a></li>' + '<a class="dropdown-item" href="#">Another link</a>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown().trigger('click') var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown()
setTimeout(function () {
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click') assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click')
}, 300)
}) })
QUnit.test('should set aria-expanded="true" on target when dropdown menu is shown', function (assert) { QUnit.test('should set aria-expanded="true" on target when dropdown menu is shown', function (assert) {
assert.expect(1) assert.expect(1)
var dropdownHTML = '<ul class="tabs">' var done = assert.async()
+ '<li class="dropdown">' var dropdownHTML = '<div class="tabs">'
+ '<div class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</a>' + '<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</a>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>' + '<a class="dropdown-item" href="#">Secondary link</a>'
+ '<li><a href="#">Something else here</a></li>' + '<a class="dropdown-item" href="#">Something else here</a>'
+ '<li class="divider"/>' + '<div class="divider"/>'
+ '<li><a href="#">Another link</a></li>' + '<a class="dropdown-item" href="#">Another link</a>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
var $dropdown = $(dropdownHTML) var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown()
.find('[data-toggle="dropdown"]') $dropdown
.bootstrapDropdown() .parent('.dropdown')
.trigger('click') .on('shown.bs.dropdown', function () {
assert.strictEqual($dropdown.attr('aria-expanded'), 'true', 'aria-expanded is set to string "true" on click')
assert.strictEqual($dropdown.attr('aria-expanded'), 'true', 'aria-expanded is set to string "true" on click') done()
})
$dropdown.trigger('click')
}) })
QUnit.test('should set aria-expanded="false" on target when dropdown menu is hidden', function (assert) { QUnit.test('should set aria-expanded="false" on target when dropdown menu is hidden', function (assert) {
assert.expect(1) assert.expect(1)
var done = assert.async() var done = assert.async()
var dropdownHTML = '<ul class="tabs">' var dropdownHTML = '<div class="tabs">'
+ '<li class="dropdown">' + '<div class="dropdown">'
+ '<a href="#" class="dropdown-toggle" aria-expanded="false" data-toggle="dropdown">Dropdown</a>' + '<a href="#" class="dropdown-toggle" aria-expanded="false" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>' + '<a class="dropdown-item" href="#">Secondary link</a>'
+ '<li><a href="#">Something else here</a></li>' + '<a class="dropdown-item" href="#">Something else here</a>'
+ '<li class="divider"/>' + '<div class="divider"/>'
+ '<li><a href="#">Another link</a></li>' + '<a class="dropdown-item" href="#">Another link</a>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
var $dropdown = $(dropdownHTML) var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture') .appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]') .find('[data-toggle="dropdown"]')
...@@ -114,86 +118,107 @@ $(function () { ...@@ -114,86 +118,107 @@ $(function () {
}) })
QUnit.test('should not open dropdown if target is disabled via class', function (assert) { QUnit.test('should not open dropdown if target is disabled via class', function (assert) {
assert.expect(1) assert.expect(0)
var dropdownHTML = '<ul class="tabs">' var dropdownHTML = '<div class="tabs">'
+ '<li class="dropdown">' + '<div class="dropdown">'
+ '<button href="#" class="btn dropdown-toggle disabled" data-toggle="dropdown">Dropdown</button>' + '<button href="#" class="btn dropdown-toggle disabled" data-toggle="dropdown">Dropdown</button>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>' + '<a class="dropdown-item" href="#">Secondary link</a>'
+ '<li><a href="#">Something else here</a></li>' + '<a class="dropdown-item" href="#">Something else here</a>'
+ '<li class="divider"/>' + '<div class="divider"/>'
+ '<li><a href="#">Another link</a></li>' + '<a class="dropdown-item" href="#">Another link</a>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown().trigger('click') var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown().trigger('click')
setTimeout(function () {
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click') assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click')
}, 300)
}) })
QUnit.test('should add class show to menu if clicked', function (assert) { QUnit.test('should add class show to menu if clicked', function (assert) {
assert.expect(1) assert.expect(1)
var dropdownHTML = '<ul class="tabs">' var done = assert.async()
+ '<li class="dropdown">' var dropdownHTML = '<div class="tabs">'
+ '<div class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' + '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>' + '<a class="dropdown-item" href="#">Secondary link</a>'
+ '<li><a href="#">Something else here</a></li>' + '<a class="dropdown-item" href="#">Something else here</a>'
+ '<li class="divider"/>' + '<div class="divider"/>'
+ '<li><a href="#">Another link</a></li>' + '<a class="dropdown-item" href="#">Another link</a>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown().trigger('click') var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown()
$dropdown
assert.ok($dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click') .parent('.dropdown')
.on('shown.bs.dropdown', function () {
assert.ok($dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click')
done()
})
$dropdown.trigger('click')
}) })
QUnit.test('should test if element has a # before assuming it\'s a selector', function (assert) { QUnit.test('should test if element has a # before assuming it\'s a selector', function (assert) {
assert.expect(1) assert.expect(1)
var dropdownHTML = '<ul class="tabs">' var done = assert.async()
+ '<li class="dropdown">' var dropdownHTML = '<div class="tabs">'
+ '<div class="dropdown">'
+ '<a href="/foo/" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' + '<a href="/foo/" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>' + '<a class="dropdown-item" href="#">Secondary link</a>'
+ '<li><a href="#">Something else here</a></li>' + '<a class="dropdown-item" href="#">Something else here</a>'
+ '<li class="divider"/>' + '<div class="divider"/>'
+ '<li><a href="#">Another link</a></li>' + '<a class="dropdown-item" href="#">Another link</a>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown().trigger('click') var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown()
$dropdown
assert.ok($dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click') .parent('.dropdown')
.on('shown.bs.dropdown', function () {
assert.ok($dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click')
done()
})
$dropdown.trigger('click')
}) })
QUnit.test('should remove "show" class if body is clicked', function (assert) { QUnit.test('should remove "show" class if body is clicked', function (assert) {
assert.expect(2) assert.expect(2)
var dropdownHTML = '<ul class="tabs">' var done = assert.async()
+ '<li class="dropdown">' var dropdownHTML = '<div class="tabs">'
+ '<div class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' + '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>' + '<a class="dropdown-item" href="#">Secondary link</a>'
+ '<li><a href="#">Something else here</a></li>' + '<a class="dropdown-item" href="#">Something else here</a>'
+ '<li class="divider"/>' + '<div class="divider"/>'
+ '<li><a href="#">Another link</a></li>' + '<a class="dropdown-item" href="#">Another link</a>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
var $dropdown = $(dropdownHTML) var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture') .appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]') .find('[data-toggle="dropdown"]')
.bootstrapDropdown() .bootstrapDropdown()
.trigger('click')
assert.ok($dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click') $dropdown
$(document.body).trigger('click') .parent('.dropdown')
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class removed') .on('shown.bs.dropdown', function () {
assert.ok($dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click')
$(document.body).trigger('click')
}).on('hidden.bs.dropdown', function () {
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class removed')
done()
})
$dropdown.trigger('click')
}) })
QUnit.test('should remove "show" class if body is focused', function (assert) { QUnit.test('should remove "show" class if tabbing outside of menu', function (assert) {
assert.expect(2) assert.expect(2)
var done = assert.async()
var dropdownHTML = '<div class="tabs">' var dropdownHTML = '<div class="tabs">'
+ '<div class="dropdown">' + '<div class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' + '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
...@@ -209,30 +234,37 @@ $(function () { ...@@ -209,30 +234,37 @@ $(function () {
.appendTo('#qunit-fixture') .appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]') .find('[data-toggle="dropdown"]')
.bootstrapDropdown() .bootstrapDropdown()
.trigger('click') $dropdown
.parent('.dropdown')
assert.ok($dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click') .on('shown.bs.dropdown', function () {
$(document.body).trigger('focusin') assert.ok($dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click')
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class removed') var e = $.Event('keyup')
e.which = 9 // Tab
$(document.body).trigger(e)
}).on('hidden.bs.dropdown', function () {
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class removed')
done()
})
$dropdown.trigger('click')
}) })
QUnit.test('should remove "show" class if body is clicked, with multiple dropdowns', function (assert) { QUnit.test('should remove "show" class if body is clicked, with multiple dropdowns', function (assert) {
assert.expect(7) assert.expect(7)
var dropdownHTML = '<ul class="nav">' var done = assert.async()
+ '<li><a href="#menu1">Menu 1</a></li>' var dropdownHTML = '<div class="nav">'
+ '<li class="dropdown" id="testmenu">' + '<div class="dropdown" id="testmenu">'
+ '<a class="dropdown-toggle" data-toggle="dropdown" href="#testmenu">Test menu <span class="caret"/></a>' + '<a class="dropdown-toggle" data-toggle="dropdown" href="#testmenu">Test menu <span class="caret"/></a>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#sub1">Submenu 1</a></li>' + '<a class="dropdown-item" href="#sub1">Submenu 1</a>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
+ '<div class="btn-group">' + '<div class="btn-group">'
+ '<button class="btn">Actions</button>' + '<button class="btn">Actions</button>'
+ '<button class="btn dropdown-toggle" data-toggle="dropdown"><span class="caret"/></button>' + '<button class="btn dropdown-toggle" data-toggle="dropdown"></button>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#">Action 1</a></li>' + '<a class="dropdown-item" href="#">Action 1</a>'
+ '</ul>' + '</div>'
+ '</div>' + '</div>'
var $dropdowns = $(dropdownHTML).appendTo('#qunit-fixture').find('[data-toggle="dropdown"]') var $dropdowns = $(dropdownHTML).appendTo('#qunit-fixture').find('[data-toggle="dropdown"]')
var $first = $dropdowns.first() var $first = $dropdowns.first()
...@@ -240,21 +272,31 @@ $(function () { ...@@ -240,21 +272,31 @@ $(function () {
assert.strictEqual($dropdowns.length, 2, 'two dropdowns') assert.strictEqual($dropdowns.length, 2, 'two dropdowns')
$first.parent('.dropdown')
.on('shown.bs.dropdown', function () {
assert.strictEqual($first.parents('.show').length, 1, '"show" class added on click')
assert.strictEqual($('#qunit-fixture .show').length, 1, 'only one dropdown is shown')
$(document.body).trigger('click')
}).on('hidden.bs.dropdown', function () {
assert.strictEqual($('#qunit-fixture .show').length, 0, '"show" class removed')
$last.trigger('click')
})
$last.parent('.btn-group')
.on('shown.bs.dropdown', function () {
assert.strictEqual($last.parent('.show').length, 1, '"show" class added on click')
assert.strictEqual($('#qunit-fixture .show').length, 1, 'only one dropdown is shown')
$(document.body).trigger('click')
}).on('hidden.bs.dropdown', function () {
assert.strictEqual($('#qunit-fixture .show').length, 0, '"show" class removed')
done()
})
$first.trigger('click') $first.trigger('click')
assert.strictEqual($first.parents('.show').length, 1, '"show" class added on click')
assert.strictEqual($('#qunit-fixture .show').length, 1, 'only one dropdown is shown')
$(document.body).trigger('click')
assert.strictEqual($('#qunit-fixture .show').length, 0, '"show" class removed')
$last.trigger('click')
assert.strictEqual($last.parent('.show').length, 1, '"show" class added on click')
assert.strictEqual($('#qunit-fixture .show').length, 1, 'only one dropdown is shown')
$(document.body).trigger('click')
assert.strictEqual($('#qunit-fixture .show').length, 0, '"show" class removed')
}) })
QUnit.test('should remove "show" class if body is focused, with multiple dropdowns', function (assert) { QUnit.test('should remove "show" class if body if tabbing outside of menu, with multiple dropdowns', function (assert) {
assert.expect(7) assert.expect(7)
var done = assert.async()
var dropdownHTML = '<div class="nav">' var dropdownHTML = '<div class="nav">'
+ '<div class="dropdown" id="testmenu">' + '<div class="dropdown" id="testmenu">'
+ '<a class="dropdown-toggle" data-toggle="dropdown" href="#testmenu">Test menu <span class="caret"/></a>' + '<a class="dropdown-toggle" data-toggle="dropdown" href="#testmenu">Test menu <span class="caret"/></a>'
...@@ -276,32 +318,45 @@ $(function () { ...@@ -276,32 +318,45 @@ $(function () {
assert.strictEqual($dropdowns.length, 2, 'two dropdowns') assert.strictEqual($dropdowns.length, 2, 'two dropdowns')
$first.parent('.dropdown')
.on('shown.bs.dropdown', function () {
assert.strictEqual($first.parents('.show').length, 1, '"show" class added on click')
assert.strictEqual($('#qunit-fixture .show').length, 1, 'only one dropdown is shown')
var e = $.Event('keyup')
e.which = 9 // Tab
$(document.body).trigger(e)
}).on('hidden.bs.dropdown', function () {
assert.strictEqual($('#qunit-fixture .show').length, 0, '"show" class removed')
$last.trigger('click')
})
$last.parent('.btn-group')
.on('shown.bs.dropdown', function () {
assert.strictEqual($last.parent('.show').length, 1, '"show" class added on click')
assert.strictEqual($('#qunit-fixture .show').length, 1, 'only one dropdown is shown')
var e = $.Event('keyup')
e.which = 9 // Tab
$(document.body).trigger(e)
}).on('hidden.bs.dropdown', function () {
assert.strictEqual($('#qunit-fixture .show').length, 0, '"show" class removed')
done()
})
$first.trigger('click') $first.trigger('click')
assert.strictEqual($first.parents('.show').length, 1, '"show" class added on click')
assert.strictEqual($('#qunit-fixture .show').length, 1, 'only one dropdown is show')
$(document.body).trigger('focusin')
assert.strictEqual($('#qunit-fixture .show').length, 0, '"show" class removed')
$last.trigger('click')
assert.strictEqual($last.parent('.show').length, 1, '"show" class added on click')
assert.strictEqual($('#qunit-fixture .show').length, 1, 'only one dropdown is show')
$(document.body).trigger('focusin')
assert.strictEqual($('#qunit-fixture .show').length, 0, '"show" class removed')
}) })
QUnit.test('should fire show and hide event', function (assert) { QUnit.test('should fire show and hide event', function (assert) {
assert.expect(2) assert.expect(2)
var dropdownHTML = '<ul class="tabs">' var dropdownHTML = '<div class="tabs">'
+ '<li class="dropdown">' + '<div class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' + '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>' + '<a class="dropdown-item" href="#">Secondary link</a>'
+ '<li><a href="#">Something else here</a></li>' + '<a class="dropdown-item" href="#">Something else here</a>'
+ '<li class="divider"/>' + '<div class="divider"/>'
+ '<li><a href="#">Another link</a></li>' + '<a class="dropdown-item" href="#">Another link</a>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
var $dropdown = $(dropdownHTML) var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture') .appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]') .find('[data-toggle="dropdown"]')
...@@ -326,17 +381,17 @@ $(function () { ...@@ -326,17 +381,17 @@ $(function () {
QUnit.test('should fire shown and hidden event', function (assert) { QUnit.test('should fire shown and hidden event', function (assert) {
assert.expect(2) assert.expect(2)
var dropdownHTML = '<ul class="tabs">' var dropdownHTML = '<div class="tabs">'
+ '<li class="dropdown">' + '<div class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' + '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>' + '<a class="dropdown-item" href="#">Secondary link</a>'
+ '<li><a href="#">Something else here</a></li>' + '<a class="dropdown-item" href="#">Something else here</a>'
+ '<li class="divider"/>' + '<div class="divider"/>'
+ '<li><a href="#">Another link</a></li>' + '<a class="dropdown-item" href="#">Another link</a>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
var $dropdown = $(dropdownHTML) var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture') .appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]') .find('[data-toggle="dropdown"]')
...@@ -360,17 +415,17 @@ $(function () { ...@@ -360,17 +415,17 @@ $(function () {
QUnit.test('should fire shown and hidden event with a relatedTarget', function (assert) { QUnit.test('should fire shown and hidden event with a relatedTarget', function (assert) {
assert.expect(2) assert.expect(2)
var dropdownHTML = '<ul class="tabs">' var dropdownHTML = '<div class="tabs">'
+ '<li class="dropdown">' + '<div class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' + '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>' + '<a class="dropdown-item" href="#">Secondary link</a>'
+ '<li><a href="#">Something else here</a></li>' + '<a class="dropdown-item" href="#">Something else here</a>'
+ '<li class="divider"/>' + '<div class="divider"/>'
+ '<li><a href="#">Another link</a></li>' + '<a class="dropdown-item" href="#">Another link</a>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
var $dropdown = $(dropdownHTML) var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture') .appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]') .find('[data-toggle="dropdown"]')
...@@ -394,19 +449,19 @@ $(function () { ...@@ -394,19 +449,19 @@ $(function () {
assert.expect(3) assert.expect(3)
var done = assert.async() var done = assert.async()
var dropdownHTML = '<ul class="tabs">' var dropdownHTML = '<div class="tabs">'
+ '<li class="dropdown">' + '<div class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' + '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><a href="#">Secondary link</a></li>' + '<a class="dropdown-item" href="#">Secondary link</a>'
+ '<li><a href="#">Something else here</a></li>' + '<a class="dropdown-item" href="#">Something else here</a>'
+ '<li class="divider"/>' + '<div class="divider"/>'
+ '<li><a href="#">Another link</a></li>' + '<a class="dropdown-item" href="#">Another link</a>'
+ '<li><input type="text" id="input"></li>' + '<input type="text" id="input">'
+ '<li><textarea id="textarea"/></li>' + '<textarea id="textarea"/>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
var $dropdown = $(dropdownHTML) var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture') .appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]') .find('[data-toggle="dropdown"]')
...@@ -433,63 +488,124 @@ $(function () { ...@@ -433,63 +488,124 @@ $(function () {
}) })
QUnit.test('should skip disabled element when using keyboard navigation', function (assert) { QUnit.test('should skip disabled element when using keyboard navigation', function (assert) {
assert.expect(1) assert.expect(2)
var dropdownHTML = '<ul class="tabs">' var done = assert.async()
+ '<li class="dropdown">' var dropdownHTML = '<div class="tabs">'
+ '<div class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<div class="dropdown-menu">'
+ '<a class="dropdown-item disabled" href="#">Disabled link</a>'
+ '<a class="dropdown-item" href="#">Another link</a>'
+ '</div>'
+ '</div>'
+ '</div>'
var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.bootstrapDropdown()
$dropdown
.parent('.dropdown')
.on('shown.bs.dropdown', function () {
assert.ok(true, 'shown was fired')
$dropdown.trigger($.Event('keydown', { which: 40 }))
$dropdown.trigger($.Event('keydown', { which: 40 }))
assert.ok(!$(document.activeElement).is('.disabled'), '.disabled is not focused')
done()
})
$dropdown.trigger('click')
})
QUnit.test('should focus next/previous element when using keyboard navigation', function (assert) {
assert.expect(4)
var done = assert.async()
var dropdownHTML = '<div class="tabs">'
+ '<div class="dropdown">'
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' + '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li class="disabled"><a href="#">Disabled link</a></li>' + '<a id="item1" class="dropdown-item" href="#">A link</a>'
+ '<li><a href="#">Another link</a></li>' + '<a id="item2" class="dropdown-item" href="#">Another link</a>'
+ '</ul>' + '</div>'
+ '</li>' + '</div>'
+ '</ul>' + '</div>'
var $dropdown = $(dropdownHTML) var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture') .appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]') .find('[data-toggle="dropdown"]')
.bootstrapDropdown() .bootstrapDropdown()
.trigger('click')
$dropdown.trigger($.Event('keydown', { which: 40 })) $dropdown
$dropdown.trigger($.Event('keydown', { which: 40 })) .parent('.dropdown')
.on('shown.bs.dropdown', function () {
assert.ok(true, 'shown was fired')
$dropdown.trigger($.Event('keydown', { which: 40 }))
assert.ok($(document.activeElement).is($('#item1')), 'item1 is focused')
$(document.activeElement).trigger($.Event('keydown', { which: 40 }))
assert.ok($(document.activeElement).is($('#item2')), 'item2 is focused')
$(document.activeElement).trigger($.Event('keydown', { which: 38 }))
assert.ok($(document.activeElement).is($('#item1')), 'item1 is focused')
done()
})
$dropdown.trigger('click')
assert.ok(!$(document.activeElement).parent().is('.disabled'), '.disabled is not focused')
}) })
QUnit.test('should not close the dropdown if the user clicks on a text field', function (assert) { QUnit.test('should not close the dropdown if the user clicks on a text field', function (assert) {
assert.expect(1) assert.expect(1)
var dropdownHTML = '<div class="btn-group">' var done = assert.async()
var dropdownHTML = '<div class="dropdown">'
+ '<button type="button" data-toggle="dropdown">Dropdown</button>' + '<button type="button" data-toggle="dropdown">Dropdown</button>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><input id="textField" type="text" /></li>' + '<input id="textField" type="text" />'
+ '</ul>' + '</div>'
+ '</div>' + '</div>'
var $dropdown = $(dropdownHTML) var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture') .appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]') .find('[data-toggle="dropdown"]')
.bootstrapDropdown() .bootstrapDropdown()
.trigger('click')
$('#textField').trigger('click') $dropdown
.parent('.dropdown')
assert.ok($dropdown.parent('.btn-group').hasClass('show'), 'dropdown menu is shown') .on('shown.bs.dropdown', function () {
$('#textField').trigger('click')
assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is shown')
setTimeout(function () {
done()
}, 300)
})
.on('hidden.bs.dropdown', function () {
assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is shown')
})
$dropdown.trigger('click')
}) })
QUnit.test('should not close the dropdown if the user clicks on a textarea', function (assert) { QUnit.test('should not close the dropdown if the user clicks on a textarea', function (assert) {
assert.expect(1) assert.expect(1)
var dropdownHTML = '<div class="btn-group">' var done = assert.async()
var dropdownHTML = '<div class="dropdown">'
+ '<button type="button" data-toggle="dropdown">Dropdown</button>' + '<button type="button" data-toggle="dropdown">Dropdown</button>'
+ '<ul class="dropdown-menu">' + '<div class="dropdown-menu">'
+ '<li><textarea id="textArea"></textarea></li>' + '<textarea id="textArea"></textarea>'
+ '</ul>' + '</div>'
+ '</div>' + '</div>'
var $dropdown = $(dropdownHTML) var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture') .appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]') .find('[data-toggle="dropdown"]')
.bootstrapDropdown() .bootstrapDropdown()
.trigger('click')
$('#textArea').trigger('click')
assert.ok($dropdown.parent('.btn-group').hasClass('show'), 'dropdown menu is shown') $dropdown
.parent('.dropdown')
.on('shown.bs.dropdown', function () {
$('#textArea').trigger('click')
assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is shown')
setTimeout(function () {
done()
}, 300)
})
.on('hidden.bs.dropdown', function () {
assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is shown')
})
$dropdown.trigger('click')
}) })
}) })
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment