modal.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  1. $(function () {
  2. 'use strict'
  3. window.Util = typeof bootstrap !== 'undefined' ? bootstrap.Util : Util
  4. QUnit.module('modal plugin')
  5. QUnit.test('should be defined on jquery object', function (assert) {
  6. assert.expect(1)
  7. assert.ok($(document.body).modal, 'modal method is defined')
  8. })
  9. QUnit.module('modal', {
  10. before: function () {
  11. // Enable the scrollbar measurer
  12. $('<style type="text/css"> .modal-scrollbar-measure { position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll; } </style>').appendTo('head')
  13. // Function to calculate the scrollbar width which is then compared to the padding or margin changes
  14. $.fn.getScrollbarWidth = $.fn.modal.Constructor.prototype._getScrollbarWidth
  15. // Simulate scrollbars
  16. $('html').css('padding-right', '16px')
  17. },
  18. beforeEach: function () {
  19. // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
  20. $.fn.bootstrapModal = $.fn.modal.noConflict()
  21. },
  22. afterEach: function () {
  23. $('.modal-backdrop, #modal-test').remove()
  24. $(document.body).removeClass('modal-open')
  25. $.fn.modal = $.fn.bootstrapModal
  26. delete $.fn.bootstrapModal
  27. $('#qunit-fixture').html('')
  28. }
  29. })
  30. QUnit.test('should provide no conflict', function (assert) {
  31. assert.expect(1)
  32. assert.strictEqual(typeof $.fn.modal, 'undefined', 'modal was set back to undefined (orig value)')
  33. })
  34. QUnit.test('should throw explicit error on undefined method', function (assert) {
  35. assert.expect(1)
  36. var $el = $('<div id="modal-test"/>')
  37. $el.bootstrapModal()
  38. try {
  39. $el.bootstrapModal('noMethod')
  40. } catch (err) {
  41. assert.strictEqual(err.message, 'No method named "noMethod"')
  42. }
  43. })
  44. QUnit.test('should return jquery collection containing the element', function (assert) {
  45. assert.expect(2)
  46. var $el = $('<div id="modal-test"/>')
  47. var $modal = $el.bootstrapModal()
  48. assert.ok($modal instanceof $, 'returns jquery collection')
  49. assert.strictEqual($modal[0], $el[0], 'collection contains element')
  50. })
  51. QUnit.test('should expose defaults var for settings', function (assert) {
  52. assert.expect(1)
  53. assert.ok($.fn.bootstrapModal.Constructor.Default, 'default object exposed')
  54. })
  55. QUnit.test('should insert into dom when show method is called', function (assert) {
  56. assert.expect(1)
  57. var done = assert.async()
  58. $('<div id="modal-test"/>')
  59. .on('shown.bs.modal', function () {
  60. assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom')
  61. done()
  62. })
  63. .bootstrapModal('show')
  64. })
  65. QUnit.test('should fire show event', function (assert) {
  66. assert.expect(1)
  67. var done = assert.async()
  68. $('<div id="modal-test"/>')
  69. .on('show.bs.modal', function () {
  70. assert.ok(true, 'show event fired')
  71. done()
  72. })
  73. .bootstrapModal('show')
  74. })
  75. QUnit.test('should not fire shown when show was prevented', function (assert) {
  76. assert.expect(1)
  77. var done = assert.async()
  78. $('<div id="modal-test"/>')
  79. .on('show.bs.modal', function (e) {
  80. e.preventDefault()
  81. assert.ok(true, 'show event fired')
  82. done()
  83. })
  84. .on('shown.bs.modal', function () {
  85. assert.ok(false, 'shown event fired')
  86. })
  87. .bootstrapModal('show')
  88. })
  89. QUnit.test('should hide modal when hide is called', function (assert) {
  90. assert.expect(3)
  91. var done = assert.async()
  92. $('<div id="modal-test"/>')
  93. .on('shown.bs.modal', function () {
  94. assert.ok($('#modal-test').is(':visible'), 'modal visible')
  95. assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom')
  96. $(this).bootstrapModal('hide')
  97. })
  98. .on('hidden.bs.modal', function () {
  99. assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
  100. done()
  101. })
  102. .bootstrapModal('show')
  103. })
  104. QUnit.test('should toggle when toggle is called', function (assert) {
  105. assert.expect(3)
  106. var done = assert.async()
  107. $('<div id="modal-test"/>')
  108. .on('shown.bs.modal', function () {
  109. assert.ok($('#modal-test').is(':visible'), 'modal visible')
  110. assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom')
  111. $(this).bootstrapModal('toggle')
  112. })
  113. .on('hidden.bs.modal', function () {
  114. assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
  115. done()
  116. })
  117. .bootstrapModal('toggle')
  118. })
  119. QUnit.test('should remove from dom when click [data-dismiss="modal"]', function (assert) {
  120. assert.expect(3)
  121. var done = assert.async()
  122. $('<div id="modal-test"><span class="close" data-dismiss="modal"/></div>')
  123. .on('shown.bs.modal', function () {
  124. assert.ok($('#modal-test').is(':visible'), 'modal visible')
  125. assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom')
  126. $(this).find('.close').trigger('click')
  127. })
  128. .on('hidden.bs.modal', function () {
  129. assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
  130. done()
  131. })
  132. .bootstrapModal('toggle')
  133. })
  134. QUnit.test('should allow modal close with "backdrop:false"', function (assert) {
  135. assert.expect(2)
  136. var done = assert.async()
  137. $('<div id="modal-test" data-backdrop="false"/>')
  138. .on('shown.bs.modal', function () {
  139. assert.ok($('#modal-test').is(':visible'), 'modal visible')
  140. $(this).bootstrapModal('hide')
  141. })
  142. .on('hidden.bs.modal', function () {
  143. assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
  144. done()
  145. })
  146. .bootstrapModal('show')
  147. })
  148. QUnit.test('should close modal when clicking outside of modal-content', function (assert) {
  149. assert.expect(3)
  150. var done = assert.async()
  151. $('<div id="modal-test"><div class="contents"/></div>')
  152. .on('shown.bs.modal', function () {
  153. assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom')
  154. $('.contents').trigger('click')
  155. assert.ok($('#modal-test').is(':visible'), 'modal visible')
  156. $('#modal-test').trigger('click')
  157. })
  158. .on('hidden.bs.modal', function () {
  159. assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
  160. done()
  161. })
  162. .bootstrapModal('show')
  163. })
  164. QUnit.test('should not close modal when clicking outside of modal-content if data-backdrop="true"', function (assert) {
  165. assert.expect(1)
  166. var done = assert.async()
  167. $('<div id="modal-test" data-backdrop="false"><div class="contents"/></div>')
  168. .on('shown.bs.modal', function () {
  169. $('#modal-test').trigger('click')
  170. assert.ok($('#modal-test').is(':visible'), 'modal not hidden')
  171. done()
  172. })
  173. .bootstrapModal('show')
  174. })
  175. QUnit.test('should close modal when escape key is pressed via keydown', function (assert) {
  176. assert.expect(3)
  177. var done = assert.async()
  178. var $div = $('<div id="modal-test"/>')
  179. $div
  180. .on('shown.bs.modal', function () {
  181. assert.ok($('#modal-test').length, 'modal inserted into dom')
  182. assert.ok($('#modal-test').is(':visible'), 'modal visible')
  183. $div.trigger($.Event('keydown', {
  184. which: 27
  185. }))
  186. setTimeout(function () {
  187. assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
  188. $div.remove()
  189. done()
  190. }, 0)
  191. })
  192. .bootstrapModal('show')
  193. })
  194. QUnit.test('should not close modal when escape key is pressed via keyup', function (assert) {
  195. assert.expect(3)
  196. var done = assert.async()
  197. var $div = $('<div id="modal-test"/>')
  198. $div
  199. .on('shown.bs.modal', function () {
  200. assert.ok($('#modal-test').length, 'modal inserted into dom')
  201. assert.ok($('#modal-test').is(':visible'), 'modal visible')
  202. $div.trigger($.Event('keyup', {
  203. which: 27
  204. }))
  205. setTimeout(function () {
  206. assert.ok($div.is(':visible'), 'modal still visible')
  207. $div.remove()
  208. done()
  209. }, 0)
  210. })
  211. .bootstrapModal('show')
  212. })
  213. QUnit.test('should trigger hide event once when clicking outside of modal-content', function (assert) {
  214. assert.expect(1)
  215. var done = assert.async()
  216. var triggered
  217. $('<div id="modal-test"><div class="contents"/></div>')
  218. .on('shown.bs.modal', function () {
  219. triggered = 0
  220. $('#modal-test').trigger('click')
  221. })
  222. .on('hide.bs.modal', function () {
  223. triggered += 1
  224. assert.strictEqual(triggered, 1, 'modal hide triggered once')
  225. done()
  226. })
  227. .bootstrapModal('show')
  228. })
  229. QUnit.test('should remove aria-hidden attribute when shown, add it back when hidden', function (assert) {
  230. assert.expect(3)
  231. var done = assert.async()
  232. $('<div id="modal-test" aria-hidden="true"/>')
  233. .on('shown.bs.modal', function () {
  234. assert.notOk($('#modal-test').is('[aria-hidden]'), 'aria-hidden attribute removed')
  235. $(this).bootstrapModal('hide')
  236. })
  237. .on('hidden.bs.modal', function () {
  238. assert.ok($('#modal-test').is('[aria-hidden]'), 'aria-hidden attribute added')
  239. assert.strictEqual($('#modal-test').attr('aria-hidden'), 'true', 'correct aria-hidden="true" added')
  240. done()
  241. })
  242. .bootstrapModal('show')
  243. })
  244. QUnit.test('should add aria-modal attribute when shown, remove it again when hidden', function (assert) {
  245. assert.expect(3)
  246. var done = assert.async()
  247. $('<div id="modal-test"/>')
  248. .on('shown.bs.modal', function () {
  249. assert.ok($('#modal-test').is('[aria-modal]'), 'aria-modal attribute added')
  250. assert.strictEqual($('#modal-test').attr('aria-modal'), 'true', 'correct aria-modal="true" added')
  251. $(this).bootstrapModal('hide')
  252. })
  253. .on('hidden.bs.modal', function () {
  254. assert.notOk($('#modal-test').is('[aria-modal]'), 'aria-modal attribute removed')
  255. done()
  256. })
  257. .bootstrapModal('show')
  258. })
  259. QUnit.test('should close reopened modal with [data-dismiss="modal"] click', function (assert) {
  260. assert.expect(2)
  261. var done = assert.async()
  262. $('<div id="modal-test"><div class="contents"><div id="close" data-dismiss="modal"/></div></div>')
  263. .one('shown.bs.modal', function () {
  264. $('#close').trigger('click')
  265. })
  266. .one('hidden.bs.modal', function () {
  267. // After one open-close cycle
  268. assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
  269. $(this)
  270. .one('shown.bs.modal', function () {
  271. $('#close').trigger('click')
  272. })
  273. .one('hidden.bs.modal', function () {
  274. assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
  275. done()
  276. })
  277. .bootstrapModal('show')
  278. })
  279. .bootstrapModal('show')
  280. })
  281. QUnit.test('should restore focus to toggling element when modal is hidden after having been opened via data-api', function (assert) {
  282. assert.expect(1)
  283. var done = assert.async()
  284. var $toggleBtn = $('<button data-toggle="modal" data-target="#modal-test"/>').appendTo('#qunit-fixture')
  285. $('<div id="modal-test"><div class="contents"><div id="close" data-dismiss="modal"/></div></div>')
  286. .on('hidden.bs.modal', function () {
  287. setTimeout(function () {
  288. assert.ok($(document.activeElement).is($toggleBtn), 'toggling element is once again focused')
  289. done()
  290. }, 0)
  291. })
  292. .on('shown.bs.modal', function () {
  293. $('#close').trigger('click')
  294. })
  295. .appendTo('#qunit-fixture')
  296. $toggleBtn.trigger('click')
  297. })
  298. QUnit.test('should not restore focus to toggling element if the associated show event gets prevented', function (assert) {
  299. assert.expect(1)
  300. var done = assert.async()
  301. var $toggleBtn = $('<button data-toggle="modal" data-target="#modal-test"/>').appendTo('#qunit-fixture')
  302. var $otherBtn = $('<button id="other-btn"/>').appendTo('#qunit-fixture')
  303. $('<div id="modal-test"><div class="contents"><div id="close" data-dismiss="modal"/></div>')
  304. .one('show.bs.modal', function (e) {
  305. e.preventDefault()
  306. $otherBtn.trigger('focus')
  307. setTimeout($.proxy(function () {
  308. $(this).bootstrapModal('show')
  309. }, this), 0)
  310. })
  311. .on('hidden.bs.modal', function () {
  312. setTimeout(function () {
  313. assert.ok($(document.activeElement).is($otherBtn), 'focus returned to toggling element')
  314. done()
  315. }, 0)
  316. })
  317. .on('shown.bs.modal', function () {
  318. $('#close').trigger('click')
  319. })
  320. .appendTo('#qunit-fixture')
  321. $toggleBtn.trigger('click')
  322. })
  323. QUnit.test('should adjust the inline padding of the modal when opening', function (assert) {
  324. assert.expect(1)
  325. var done = assert.async()
  326. $('<div id="modal-test"/>')
  327. .on('shown.bs.modal', function () {
  328. var expectedPadding = $(this).getScrollbarWidth() + 'px'
  329. var currentPadding = $(this).css('padding-right')
  330. assert.strictEqual(currentPadding, expectedPadding, 'modal padding should be adjusted while opening')
  331. done()
  332. })
  333. .bootstrapModal('show')
  334. })
  335. QUnit.test('should adjust the inline body padding when opening and restore when closing', function (assert) {
  336. assert.expect(2)
  337. var done = assert.async()
  338. var $body = $(document.body)
  339. var originalPadding = $body.css('padding-right')
  340. $('<div id="modal-test"/>')
  341. .on('hidden.bs.modal', function () {
  342. var currentPadding = $body.css('padding-right')
  343. assert.strictEqual(currentPadding, originalPadding, 'body padding should be reset after closing')
  344. $body.removeAttr('style')
  345. done()
  346. })
  347. .on('shown.bs.modal', function () {
  348. var expectedPadding = parseFloat(originalPadding) + $(this).getScrollbarWidth() + 'px'
  349. var currentPadding = $body.css('padding-right')
  350. assert.strictEqual(currentPadding, expectedPadding, 'body padding should be adjusted while opening')
  351. $(this).bootstrapModal('hide')
  352. })
  353. .bootstrapModal('show')
  354. })
  355. QUnit.test('should store the original body padding in data-padding-right before showing', function (assert) {
  356. assert.expect(2)
  357. var done = assert.async()
  358. var $body = $(document.body)
  359. var originalPadding = '0px'
  360. $body.css('padding-right', originalPadding)
  361. $('<div id="modal-test"/>')
  362. .on('hidden.bs.modal', function () {
  363. assert.strictEqual(typeof $body.data('padding-right'), 'undefined', 'data-padding-right should be cleared after closing')
  364. $body.removeAttr('style')
  365. done()
  366. })
  367. .on('shown.bs.modal', function () {
  368. assert.strictEqual($body.data('padding-right'), originalPadding, 'original body padding should be stored in data-padding-right')
  369. $(this).bootstrapModal('hide')
  370. })
  371. .bootstrapModal('show')
  372. })
  373. QUnit.test('should not adjust the inline body padding when it does not overflow', function (assert) {
  374. assert.expect(1)
  375. var done = assert.async()
  376. var $body = $(document.body)
  377. var originalPadding = $body.css('padding-right')
  378. // Hide scrollbars to prevent the body overflowing
  379. $body.css('overflow', 'hidden') // Real scrollbar (for in-browser testing)
  380. $('html').css('padding-right', '0px') // Simulated scrollbar (for PhantomJS)
  381. $('<div id="modal-test"/>')
  382. .on('shown.bs.modal', function () {
  383. var currentPadding = $body.css('padding-right')
  384. assert.strictEqual(currentPadding, originalPadding, 'body padding should not be adjusted')
  385. $(this).bootstrapModal('hide')
  386. // Restore scrollbars
  387. $body.css('overflow', 'auto')
  388. $('html').css('padding-right', '16px')
  389. done()
  390. })
  391. .bootstrapModal('show')
  392. })
  393. QUnit.test('should adjust the inline padding of fixed elements when opening and restore when closing', function (assert) {
  394. assert.expect(2)
  395. var done = assert.async()
  396. var $element = $('<div class="fixed-top"></div>').appendTo('#qunit-fixture')
  397. var originalPadding = $element.css('padding-right')
  398. $('<div id="modal-test"/>')
  399. .on('hidden.bs.modal', function () {
  400. var currentPadding = $element.css('padding-right')
  401. assert.strictEqual(currentPadding, originalPadding, 'fixed element padding should be reset after closing')
  402. $element.remove()
  403. done()
  404. })
  405. .on('shown.bs.modal', function () {
  406. var expectedPadding = parseFloat(originalPadding) + $(this).getScrollbarWidth() + 'px'
  407. var currentPadding = $element.css('padding-right')
  408. assert.strictEqual(currentPadding, expectedPadding, 'fixed element padding should be adjusted while opening')
  409. $(this).bootstrapModal('hide')
  410. })
  411. .bootstrapModal('show')
  412. })
  413. QUnit.test('should store the original padding of fixed elements in data-padding-right before showing', function (assert) {
  414. assert.expect(2)
  415. var done = assert.async()
  416. var $element = $('<div class="fixed-top"></div>').appendTo('#qunit-fixture')
  417. var originalPadding = '0px'
  418. $element.css('padding-right', originalPadding)
  419. $('<div id="modal-test"/>')
  420. .on('hidden.bs.modal', function () {
  421. assert.strictEqual(typeof $element.data('padding-right'), 'undefined', 'data-padding-right should be cleared after closing')
  422. $element.remove()
  423. done()
  424. })
  425. .on('shown.bs.modal', function () {
  426. assert.strictEqual($element.data('padding-right'), originalPadding, 'original fixed element padding should be stored in data-padding-right')
  427. $(this).bootstrapModal('hide')
  428. })
  429. .bootstrapModal('show')
  430. })
  431. QUnit.test('should adjust the inline margin of sticky elements when opening and restore when closing', function (assert) {
  432. assert.expect(2)
  433. var done = assert.async()
  434. var $element = $('<div class="sticky-top"></div>').appendTo('#qunit-fixture')
  435. var originalPadding = $element.css('margin-right')
  436. $('<div id="modal-test"/>')
  437. .on('hidden.bs.modal', function () {
  438. var currentPadding = $element.css('margin-right')
  439. assert.strictEqual(currentPadding, originalPadding, 'sticky element margin should be reset after closing')
  440. $element.remove()
  441. done()
  442. })
  443. .on('shown.bs.modal', function () {
  444. var expectedPadding = parseFloat(originalPadding) - $(this).getScrollbarWidth() + 'px'
  445. var currentPadding = $element.css('margin-right')
  446. assert.strictEqual(currentPadding, expectedPadding, 'sticky element margin should be adjusted while opening')
  447. $(this).bootstrapModal('hide')
  448. })
  449. .bootstrapModal('show')
  450. })
  451. QUnit.test('should store the original margin of sticky elements in data-margin-right before showing', function (assert) {
  452. assert.expect(2)
  453. var done = assert.async()
  454. var $element = $('<div class="sticky-top"></div>').appendTo('#qunit-fixture')
  455. var originalPadding = '0px'
  456. $element.css('margin-right', originalPadding)
  457. $('<div id="modal-test"/>')
  458. .on('hidden.bs.modal', function () {
  459. assert.strictEqual(typeof $element.data('margin-right'), 'undefined', 'data-margin-right should be cleared after closing')
  460. $element.remove()
  461. done()
  462. })
  463. .on('shown.bs.modal', function () {
  464. assert.strictEqual($element.data('margin-right'), originalPadding, 'original sticky element margin should be stored in data-margin-right')
  465. $(this).bootstrapModal('hide')
  466. })
  467. .bootstrapModal('show')
  468. })
  469. QUnit.test('should ignore values set via CSS when trying to restore body padding after closing', function (assert) {
  470. assert.expect(1)
  471. var done = assert.async()
  472. var $body = $(document.body)
  473. var $style = $('<style>body { padding-right: 42px; }</style>').appendTo('head')
  474. $('<div id="modal-test"/>')
  475. .on('hidden.bs.modal', function () {
  476. assert.strictEqual($body.attr('style').indexOf('padding-right'), -1, 'body does not have inline padding set')
  477. $style.remove()
  478. done()
  479. })
  480. .on('shown.bs.modal', function () {
  481. $(this).bootstrapModal('hide')
  482. })
  483. .bootstrapModal('show')
  484. })
  485. QUnit.test('should ignore other inline styles when trying to restore body padding after closing', function (assert) {
  486. assert.expect(2)
  487. var done = assert.async()
  488. var $body = $(document.body)
  489. var $style = $('<style>body { padding-right: 42px; }</style>').appendTo('head')
  490. $body.css('color', 'red')
  491. $('<div id="modal-test"/>')
  492. .on('hidden.bs.modal', function () {
  493. assert.strictEqual($body[0].style.paddingRight, '', 'body does not have inline padding set')
  494. assert.strictEqual($body[0].style.color, 'red', 'body still has other inline styles set')
  495. $body.removeAttr('style')
  496. $style.remove()
  497. done()
  498. })
  499. .on('shown.bs.modal', function () {
  500. $(this).bootstrapModal('hide')
  501. })
  502. .bootstrapModal('show')
  503. })
  504. QUnit.test('should properly restore non-pixel inline body padding after closing', function (assert) {
  505. assert.expect(1)
  506. var done = assert.async()
  507. var $body = $(document.body)
  508. $body.css('padding-right', '5%')
  509. $('<div id="modal-test"/>')
  510. .on('hidden.bs.modal', function () {
  511. assert.strictEqual($body[0].style.paddingRight, '5%', 'body does not have inline padding set')
  512. $body.removeAttr('style')
  513. done()
  514. })
  515. .on('shown.bs.modal', function () {
  516. $(this).bootstrapModal('hide')
  517. })
  518. .bootstrapModal('show')
  519. })
  520. QUnit.test('should not follow link in area tag', function (assert) {
  521. assert.expect(2)
  522. var done = assert.async()
  523. $('<map><area id="test" shape="default" data-toggle="modal" data-target="#modal-test" href="demo.html"/></map>')
  524. .appendTo('#qunit-fixture')
  525. $('<div id="modal-test"><div class="contents"><div id="close" data-dismiss="modal"/></div></div>')
  526. .appendTo('#qunit-fixture')
  527. $('#test')
  528. .on('click.bs.modal.data-api', function (event) {
  529. assert.notOk(event.isDefaultPrevented(), 'navigating to href will happen')
  530. setTimeout(function () {
  531. assert.ok(event.isDefaultPrevented(), 'model shown instead of navigating to href')
  532. done()
  533. }, 1)
  534. })
  535. .trigger('click')
  536. })
  537. QUnit.test('should not parse target as html', function (assert) {
  538. assert.expect(1)
  539. var done = assert.async()
  540. var $toggleBtn = $('<button data-toggle="modal" data-target="&lt;div id=&quot;modal-test&quot;&gt;&lt;div class=&quot;contents&quot;&lt;div&lt;div id=&quot;close&quot; data-dismiss=&quot;modal&quot;/&gt;&lt;/div&gt;&lt;/div&gt;"/>')
  541. .appendTo('#qunit-fixture')
  542. $toggleBtn.trigger('click')
  543. setTimeout(function () {
  544. assert.strictEqual($('#modal-test').length, 0, 'target has not been parsed and added to the document')
  545. done()
  546. }, 0)
  547. })
  548. QUnit.test('should not execute js from target', function (assert) {
  549. assert.expect(0)
  550. var done = assert.async()
  551. // This toggle button contains XSS payload in its data-target
  552. // Note: it uses the onerror handler of an img element to execute the js, because a simple script element does not work here
  553. // a script element works in manual tests though, so here it is likely blocked by the qunit framework
  554. var $toggleBtn = $('<button data-toggle="modal" data-target="&lt;div&gt;&lt;image src=&quot;missing.png&quot; onerror=&quot;$(&apos;#qunit-fixture button.control&apos;).trigger(&apos;click&apos;)&quot;&gt;&lt;/div&gt;"/>')
  555. .appendTo('#qunit-fixture')
  556. // The XSS payload above does not have a closure over this function and cannot access the assert object directly
  557. // However, it can send a click event to the following control button, which will then fail the assert
  558. $('<button>')
  559. .addClass('control')
  560. .on('click', function () {
  561. assert.notOk(true, 'XSS payload is not executed as js')
  562. })
  563. .appendTo('#qunit-fixture')
  564. $toggleBtn.trigger('click')
  565. setTimeout(done, 500)
  566. })
  567. QUnit.test('should not try to open a modal which is already visible', function (assert) {
  568. assert.expect(1)
  569. var done = assert.async()
  570. var count = 0
  571. $('<div id="modal-test"/>').on('shown.bs.modal', function () {
  572. count++
  573. }).on('hidden.bs.modal', function () {
  574. assert.strictEqual(count, 1, 'show() runs only once')
  575. done()
  576. })
  577. .bootstrapModal('show')
  578. .bootstrapModal('show')
  579. .bootstrapModal('hide')
  580. })
  581. QUnit.test('transition duration should be the modal-dialog duration before triggering shown event', function (assert) {
  582. assert.expect(1)
  583. var done = assert.async()
  584. var style = [
  585. '<style>',
  586. ' .modal.fade .modal-dialog {',
  587. ' transition: -webkit-transform .3s ease-out;',
  588. ' transition: transform .3s ease-out;',
  589. ' transition: transform .3s ease-out,-webkit-transform .3s ease-out;',
  590. ' -webkit-transform: translate(0,-50px);',
  591. ' transform: translate(0,-50px);',
  592. ' }',
  593. '</style>'
  594. ].join('')
  595. var $style = $(style).appendTo('head')
  596. var modalHTML = [
  597. '<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">',
  598. ' <div class="modal-dialog" role="document">',
  599. ' <div class="modal-content">',
  600. ' <div class="modal-body">...</div>',
  601. ' </div>',
  602. ' </div>',
  603. '</div>'
  604. ].join('')
  605. var $modal = $(modalHTML).appendTo('#qunit-fixture')
  606. var expectedTransitionDuration = 300
  607. var spy = sinon.spy(Util, 'getTransitionDurationFromElement')
  608. $modal.on('shown.bs.modal', function () {
  609. assert.ok(spy.returned(expectedTransitionDuration))
  610. $style.remove()
  611. spy.restore()
  612. done()
  613. })
  614. .bootstrapModal('show')
  615. })
  616. QUnit.test('should dispose modal', function (assert) {
  617. assert.expect(3)
  618. var done = assert.async()
  619. var $modal = $([
  620. '<div id="modal-test">',
  621. ' <div class="modal-dialog">',
  622. ' <div class="modal-content">',
  623. ' <div class="modal-body" />',
  624. ' </div>',
  625. ' </div>',
  626. '</div>'
  627. ].join('')).appendTo('#qunit-fixture')
  628. $modal.on('shown.bs.modal', function () {
  629. var spy = sinon.spy($.fn, 'off')
  630. $(this).bootstrapModal('dispose')
  631. var modalDataApiEvent = []
  632. $._data(document, 'events').click
  633. .forEach(function (e) {
  634. if (e.namespace === 'bs.data-api.modal') {
  635. modalDataApiEvent.push(e)
  636. }
  637. })
  638. assert.ok(typeof $(this).data('bs.modal') === 'undefined', 'modal data object was disposed')
  639. assert.ok(spy.callCount === 4, '`jQuery.off` was called')
  640. assert.ok(modalDataApiEvent.length === 1, '`Event.CLICK_DATA_API` on `document` was not removed')
  641. $.fn.off.restore()
  642. done()
  643. }).bootstrapModal('show')
  644. })
  645. QUnit.test('should enforce focus', function (assert) {
  646. assert.expect(4)
  647. var done = assert.async()
  648. var $modal = $([
  649. '<div id="modal-test" data-show="false">',
  650. ' <div class="modal-dialog">',
  651. ' <div class="modal-content">',
  652. ' <div class="modal-body" />',
  653. ' </div>',
  654. ' </div>',
  655. '</div>'
  656. ].join(''))
  657. .bootstrapModal()
  658. .appendTo('#qunit-fixture')
  659. var modal = $modal.data('bs.modal')
  660. var spy = sinon.spy(modal, '_enforceFocus')
  661. var spyDocOff = sinon.spy($(document), 'off')
  662. var spyDocOn = sinon.spy($(document), 'on')
  663. $modal.one('shown.bs.modal', function () {
  664. assert.ok(spy.called, '_enforceFocus called')
  665. assert.ok(spyDocOff.withArgs('focusin.bs.modal'))
  666. assert.ok(spyDocOn.withArgs('focusin.bs.modal'))
  667. var spyFocus = sinon.spy(modal._element, 'focus')
  668. var event = $.Event('focusin', {
  669. target: $('#qunit-fixture')[0]
  670. })
  671. $(document).one('focusin', function () {
  672. assert.ok(spyFocus.called)
  673. done()
  674. })
  675. $(document).trigger(event)
  676. })
  677. .bootstrapModal('show')
  678. })
  679. QUnit.test('should scroll to top of the modal body if the modal has .modal-dialog-scrollable class', function (assert) {
  680. assert.expect(2)
  681. var done = assert.async()
  682. var $modal = $([
  683. '<div id="modal-test">',
  684. ' <div class="modal-dialog modal-dialog-scrollable">',
  685. ' <div class="modal-content">',
  686. ' <div class="modal-body" style="height: 100px; overflow-y: auto;">',
  687. ' <div style="height: 200px" />',
  688. ' </div>',
  689. ' </div>',
  690. ' </div>',
  691. '</div>'
  692. ].join('')).appendTo('#qunit-fixture')
  693. var $modalBody = $('.modal-body')
  694. $modalBody.scrollTop(100)
  695. assert.strictEqual($modalBody.scrollTop(), 100)
  696. $modal.on('shown.bs.modal', function () {
  697. assert.strictEqual($modalBody.scrollTop(), 0, 'modal body scrollTop should be 0 when opened')
  698. done()
  699. })
  700. .bootstrapModal('show')
  701. })
  702. })