modal.js 36 KB
Newer Older
1
$(function () {
2
  'use strict'
3

4
5
  window.Util = typeof bootstrap !== 'undefined' ? bootstrap.Util : Util

fat's avatar
fat committed
6
  QUnit.module('modal plugin')
XhmikosR's avatar
XhmikosR committed
7

fat's avatar
fat committed
8
9
10
  QUnit.test('should be defined on jquery object', function (assert) {
    assert.expect(1)
    assert.ok($(document.body).modal, 'modal method is defined')
XhmikosR's avatar
XhmikosR committed
11
12
  })

fat's avatar
fat committed
13
  QUnit.module('modal', {
14
15
16
    before: function () {
      // Enable the scrollbar measurer
      $('<style type="text/css"> .modal-scrollbar-measure { position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll; } </style>').appendTo('head')
17
      // Function to calculate the scrollbar width which is then compared to the padding or margin changes
18
      $.fn.getScrollbarWidth = $.fn.modal.Constructor.prototype._getScrollbarWidth
19
20

      // Simulate scrollbars
David Bailey's avatar
David Bailey committed
21
      $('html').css('padding-right', '16px')
22
    },
fat's avatar
fat committed
23
    beforeEach: function () {
24
25
26
      // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
      $.fn.bootstrapModal = $.fn.modal.noConflict()
    },
fat's avatar
fat committed
27
    afterEach: function () {
28
29
      $('.modal-backdrop, #modal-test').remove()
      $(document.body).removeClass('modal-open')
30
31
      $.fn.modal = $.fn.bootstrapModal
      delete $.fn.bootstrapModal
32
      $('#qunit-fixture').html('')
33
34
35
    }
  })

fat's avatar
fat committed
36
37
  QUnit.test('should provide no conflict', function (assert) {
    assert.expect(1)
XhmikosR's avatar
XhmikosR committed
38
    assert.strictEqual(typeof $.fn.modal, 'undefined', 'modal was set back to undefined (orig value)')
39
40
  })

41
42
43
44
45
46
  QUnit.test('should throw explicit error on undefined method', function (assert) {
    assert.expect(1)
    var $el = $('<div id="modal-test"/>')
    $el.bootstrapModal()
    try {
      $el.bootstrapModal('noMethod')
XhmikosR's avatar
XhmikosR committed
47
48
    } catch (error) {
      assert.strictEqual(error.message, 'No method named "noMethod"')
49
50
51
    }
  })

fat's avatar
fat committed
52
53
  QUnit.test('should return jquery collection containing the element', function (assert) {
    assert.expect(2)
Heinrich Fenkart's avatar
Heinrich Fenkart committed
54
55
    var $el = $('<div id="modal-test"/>')
    var $modal = $el.bootstrapModal()
XhmikosR's avatar
XhmikosR committed
56
    assert.true($modal instanceof $, 'returns jquery collection')
fat's avatar
fat committed
57
    assert.strictEqual($modal[0], $el[0], 'collection contains element')
XhmikosR's avatar
XhmikosR committed
58
59
  })

fat's avatar
fat committed
60
61
  QUnit.test('should expose defaults var for settings', function (assert) {
    assert.expect(1)
62
    assert.ok($.fn.bootstrapModal.Constructor.Default, 'default object exposed')
XhmikosR's avatar
XhmikosR committed
63
64
  })

fat's avatar
fat committed
65
66
  QUnit.test('should insert into dom when show method is called', function (assert) {
    assert.expect(1)
67
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
68
69

    $('<div id="modal-test"/>')
XhmikosR's avatar
XhmikosR committed
70
      .on('shown.bs.modal', function () {
71
        assert.notStrictEqual($('#modal-test').length, 0, 'modal inserted into dom')
72
        done()
XhmikosR's avatar
XhmikosR committed
73
      })
74
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
75
76
  })

fat's avatar
fat committed
77
78
  QUnit.test('should fire show event', function (assert) {
    assert.expect(1)
79
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
80
81

    $('<div id="modal-test"/>')
XhmikosR's avatar
XhmikosR committed
82
      .on('show.bs.modal', function () {
fat's avatar
fat committed
83
        assert.ok(true, 'show event fired')
84
        done()
XhmikosR's avatar
XhmikosR committed
85
      })
86
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
87
88
  })

fat's avatar
fat committed
89
90
  QUnit.test('should not fire shown when show was prevented', function (assert) {
    assert.expect(1)
91
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
92
93

    $('<div id="modal-test"/>')
XhmikosR's avatar
XhmikosR committed
94
95
      .on('show.bs.modal', function (e) {
        e.preventDefault()
fat's avatar
fat committed
96
        assert.ok(true, 'show event fired')
97
        done()
XhmikosR's avatar
XhmikosR committed
98
99
      })
      .on('shown.bs.modal', function () {
fat's avatar
fat committed
100
        assert.ok(false, 'shown event fired')
XhmikosR's avatar
XhmikosR committed
101
      })
102
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
103
104
  })

105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
  QUnit.test('should be shown after the first call to show() has been prevented while fading is enabled', function (assert) {
    assert.expect(2)
    var done = assert.async()

    var $el = $('<div class="modal fade"><div class="modal-dialog" style="transition-duration: 20ms;"/></div>').appendTo('#qunit-fixture')

    var prevented = false
    $el
      .on('show.bs.modal', function (e) {
        if (!prevented) {
          e.preventDefault()
          prevented = true

          setTimeout(function () {
            $el.bootstrapModal('show')
          })
        }
      })
      .on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
124
125
        assert.true(prevented, 'show prevented')
        assert.true($el.hasClass('fade'))
126
127
128
129
130
        done()
      })
      .bootstrapModal('show')
  })

fat's avatar
fat committed
131
132
  QUnit.test('should hide modal when hide is called', function (assert) {
    assert.expect(3)
133
    var done = assert.async()
XhmikosR's avatar
XhmikosR committed
134

Heinrich Fenkart's avatar
Heinrich Fenkart committed
135
    $('<div id="modal-test"/>')
XhmikosR's avatar
XhmikosR committed
136
      .on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
137
        assert.true($('#modal-test').is(':visible'), 'modal visible')
138
        assert.notStrictEqual($('#modal-test').length, 0, 'modal inserted into dom')
139
        $(this).bootstrapModal('hide')
XhmikosR's avatar
XhmikosR committed
140
141
      })
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
142
        assert.false($('#modal-test').is(':visible'), 'modal hidden')
143
        done()
XhmikosR's avatar
XhmikosR committed
144
      })
145
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
146
147
  })

fat's avatar
fat committed
148
149
  QUnit.test('should toggle when toggle is called', function (assert) {
    assert.expect(3)
150
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
151
152

    $('<div id="modal-test"/>')
XhmikosR's avatar
XhmikosR committed
153
      .on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
154
        assert.true($('#modal-test').is(':visible'), 'modal visible')
155
        assert.notStrictEqual($('#modal-test').length, 0, 'modal inserted into dom')
Heinrich Fenkart's avatar
Heinrich Fenkart committed
156
        $(this).bootstrapModal('toggle')
XhmikosR's avatar
XhmikosR committed
157
158
      })
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
159
        assert.false($('#modal-test').is(':visible'), 'modal hidden')
160
        done()
XhmikosR's avatar
XhmikosR committed
161
      })
162
      .bootstrapModal('toggle')
XhmikosR's avatar
XhmikosR committed
163
164
  })

fat's avatar
fat committed
165
166
  QUnit.test('should remove from dom when click [data-dismiss="modal"]', function (assert) {
    assert.expect(3)
167
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
168
169

    $('<div id="modal-test"><span class="close" data-dismiss="modal"/></div>')
XhmikosR's avatar
XhmikosR committed
170
      .on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
171
        assert.true($('#modal-test').is(':visible'), 'modal visible')
172
        assert.notStrictEqual($('#modal-test').length, 0, 'modal inserted into dom')
fat's avatar
fat committed
173
        $(this).find('.close').trigger('click')
XhmikosR's avatar
XhmikosR committed
174
175
      })
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
176
        assert.false($('#modal-test').is(':visible'), 'modal hidden')
177
        done()
XhmikosR's avatar
XhmikosR committed
178
      })
179
      .bootstrapModal('toggle')
XhmikosR's avatar
XhmikosR committed
180
181
  })

fat's avatar
fat committed
182
183
  QUnit.test('should allow modal close with "backdrop:false"', function (assert) {
    assert.expect(2)
184
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
185
186

    $('<div id="modal-test" data-backdrop="false"/>')
XhmikosR's avatar
XhmikosR committed
187
      .on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
188
        assert.true($('#modal-test').is(':visible'), 'modal visible')
Heinrich Fenkart's avatar
Heinrich Fenkart committed
189
        $(this).bootstrapModal('hide')
XhmikosR's avatar
XhmikosR committed
190
191
      })
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
192
        assert.false($('#modal-test').is(':visible'), 'modal hidden')
193
        done()
XhmikosR's avatar
XhmikosR committed
194
      })
195
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
196
197
  })

fat's avatar
fat committed
198
199
  QUnit.test('should close modal when clicking outside of modal-content', function (assert) {
    assert.expect(3)
200
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
201
202

    $('<div id="modal-test"><div class="contents"/></div>')
203
      .on('shown.bs.modal', function () {
204
        assert.notStrictEqual($('#modal-test').length, 0, 'modal inserted into dom')
fat's avatar
fat committed
205
        $('.contents').trigger('click')
XhmikosR's avatar
XhmikosR committed
206
        assert.true($('#modal-test').is(':visible'), 'modal visible')
fat's avatar
fat committed
207
        $('#modal-test').trigger('click')
XhmikosR's avatar
XhmikosR committed
208
      })
209
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
210
        assert.false($('#modal-test').is(':visible'), 'modal hidden')
211
        done()
XhmikosR's avatar
XhmikosR committed
212
      })
213
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
214
215
  })

216
217
218
219
220
221
222
  QUnit.test('should not close modal when clicking outside of modal-content if data-backdrop="true"', function (assert) {
    assert.expect(1)
    var done = assert.async()

    $('<div id="modal-test" data-backdrop="false"><div class="contents"/></div>')
      .on('shown.bs.modal', function () {
        $('#modal-test').trigger('click')
XhmikosR's avatar
XhmikosR committed
223
        assert.true($('#modal-test').is(':visible'), 'modal not hidden')
224
225
226
227
228
        done()
      })
      .bootstrapModal('show')
  })

fat's avatar
fat committed
229
230
  QUnit.test('should close modal when escape key is pressed via keydown', function (assert) {
    assert.expect(3)
231
    var done = assert.async()
232

fat's avatar
fat committed
233
234
    var $div = $('<div id="modal-test"/>')
    $div
235
      .on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
236
237
        assert.notStrictEqual($('#modal-test').length, 0, 'modal inserted into dom')
        assert.true($('#modal-test').is(':visible'), 'modal visible')
XhmikosR's avatar
XhmikosR committed
238
239
240
        $div.trigger($.Event('keydown', {
          which: 27
        }))
241
242

        setTimeout(function () {
XhmikosR's avatar
XhmikosR committed
243
          assert.false($('#modal-test').is(':visible'), 'modal hidden')
fat's avatar
fat committed
244
          $div.remove()
245
          done()
246
247
248
249
250
        }, 0)
      })
      .bootstrapModal('show')
  })

fat's avatar
fat committed
251
252
  QUnit.test('should not close modal when escape key is pressed via keyup', function (assert) {
    assert.expect(3)
253
    var done = assert.async()
254

fat's avatar
fat committed
255
256
    var $div = $('<div id="modal-test"/>')
    $div
257
      .on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
258
259
        assert.notStrictEqual($('#modal-test').length, 0, 'modal inserted into dom')
        assert.true($('#modal-test').is(':visible'), 'modal visible')
XhmikosR's avatar
XhmikosR committed
260
261
262
        $div.trigger($.Event('keyup', {
          which: 27
        }))
263
264

        setTimeout(function () {
XhmikosR's avatar
XhmikosR committed
265
          assert.true($div.is(':visible'), 'modal still visible')
fat's avatar
fat committed
266
          $div.remove()
267
          done()
268
269
270
271
272
        }, 0)
      })
      .bootstrapModal('show')
  })

fat's avatar
fat committed
273
274
  QUnit.test('should trigger hide event once when clicking outside of modal-content', function (assert) {
    assert.expect(1)
275
    var done = assert.async()
XhmikosR's avatar
XhmikosR committed
276
277
278

    var triggered

Heinrich Fenkart's avatar
Heinrich Fenkart committed
279
    $('<div id="modal-test"><div class="contents"/></div>')
280
      .on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
281
        triggered = 0
fat's avatar
fat committed
282
        $('#modal-test').trigger('click')
XhmikosR's avatar
XhmikosR committed
283
      })
284
      .on('hide.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
285
        triggered += 1
fat's avatar
fat committed
286
        assert.strictEqual(triggered, 1, 'modal hide triggered once')
287
        done()
XhmikosR's avatar
XhmikosR committed
288
      })
289
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
290
291
  })

292
293
294
295
296
297
  QUnit.test('should remove aria-hidden attribute when shown, add it back when hidden', function (assert) {
    assert.expect(3)
    var done = assert.async()

    $('<div id="modal-test" aria-hidden="true"/>')
      .on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
298
        assert.false($('#modal-test').is('[aria-hidden]'), 'aria-hidden attribute removed')
299
300
301
        $(this).bootstrapModal('hide')
      })
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
302
        assert.true($('#modal-test').is('[aria-hidden]'), 'aria-hidden attribute added')
303
304
        assert.strictEqual($('#modal-test').attr('aria-hidden'), 'true', 'correct aria-hidden="true" added')
        done()
305
306
307
308
309
310
311
312
313
314
      })
      .bootstrapModal('show')
  })

  QUnit.test('should add aria-modal attribute when shown, remove it again when hidden', function (assert) {
    assert.expect(3)
    var done = assert.async()

    $('<div id="modal-test"/>')
      .on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
315
        assert.true($('#modal-test').is('[aria-modal]'), 'aria-modal attribute added')
316
317
318
319
        assert.strictEqual($('#modal-test').attr('aria-modal'), 'true', 'correct aria-modal="true" added')
        $(this).bootstrapModal('hide')
      })
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
320
        assert.false($('#modal-test').is('[aria-modal]'), 'aria-modal attribute removed')
321
        done()
322
323
324
325
      })
      .bootstrapModal('show')
  })

XhmikosR's avatar
XhmikosR committed
326
327
328
329
330
331
  QUnit.test('should add role="dialog" attribute when shown, remove it again when hidden', function (assert) {
    assert.expect(3)
    var done = assert.async()

    $('<div id="modal-test"/>')
      .on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
332
        assert.true($('#modal-test').is('[role]'), 'role attribute added')
XhmikosR's avatar
XhmikosR committed
333
334
335
336
        assert.strictEqual($('#modal-test').attr('role'), 'dialog', 'correct role="dialog" added')
        $(this).bootstrapModal('hide')
      })
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
337
        assert.false($('#modal-test').is('[role]'), 'role attribute removed')
XhmikosR's avatar
XhmikosR committed
338
339
340
341
342
        done()
      })
      .bootstrapModal('show')
  })

fat's avatar
fat committed
343
344
  QUnit.test('should close reopened modal with [data-dismiss="modal"] click', function (assert) {
    assert.expect(2)
345
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
346
347

    $('<div id="modal-test"><div class="contents"><div id="close" data-dismiss="modal"/></div></div>')
348
      .one('shown.bs.modal', function () {
fat's avatar
fat committed
349
        $('#close').trigger('click')
XhmikosR's avatar
XhmikosR committed
350
351
      })
      .one('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
352
        // After one open-close cycle
XhmikosR's avatar
XhmikosR committed
353
        assert.false($('#modal-test').is(':visible'), 'modal hidden')
Heinrich Fenkart's avatar
Heinrich Fenkart committed
354
        $(this)
355
          .one('shown.bs.modal', function () {
fat's avatar
fat committed
356
            $('#close').trigger('click')
357
          })
Heinrich Fenkart's avatar
Heinrich Fenkart committed
358
          .one('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
359
            assert.false($('#modal-test').is(':visible'), 'modal hidden')
360
            done()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
361
362
          })
          .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
363
      })
364
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
365
  })
Chris Rebert's avatar
Chris Rebert committed
366

fat's avatar
fat committed
367
368
  QUnit.test('should restore focus to toggling element when modal is hidden after having been opened via data-api', function (assert) {
    assert.expect(1)
369
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
370
371
372
373

    var $toggleBtn = $('<button data-toggle="modal" data-target="#modal-test"/>').appendTo('#qunit-fixture')

    $('<div id="modal-test"><div class="contents"><div id="close" data-dismiss="modal"/></div></div>')
Chris Rebert's avatar
Chris Rebert committed
374
      .on('hidden.bs.modal', function () {
Heinrich Fenkart's avatar
Heinrich Fenkart committed
375
        setTimeout(function () {
XhmikosR's avatar
XhmikosR committed
376
          assert.true($(document.activeElement).is($toggleBtn), 'toggling element is once again focused')
377
          done()
Chris Rebert's avatar
Chris Rebert committed
378
379
380
        }, 0)
      })
      .on('shown.bs.modal', function () {
fat's avatar
fat committed
381
        $('#close').trigger('click')
Chris Rebert's avatar
Chris Rebert committed
382
383
      })
      .appendTo('#qunit-fixture')
Heinrich Fenkart's avatar
Heinrich Fenkart committed
384

fat's avatar
fat committed
385
    $toggleBtn.trigger('click')
Chris Rebert's avatar
Chris Rebert committed
386
387
  })

fat's avatar
fat committed
388
389
  QUnit.test('should not restore focus to toggling element if the associated show event gets prevented', function (assert) {
    assert.expect(1)
390
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
391
392
393
394
    var $toggleBtn = $('<button data-toggle="modal" data-target="#modal-test"/>').appendTo('#qunit-fixture')
    var $otherBtn = $('<button id="other-btn"/>').appendTo('#qunit-fixture')

    $('<div id="modal-test"><div class="contents"><div id="close" data-dismiss="modal"/></div>')
Chris Rebert's avatar
Chris Rebert committed
395
396
      .one('show.bs.modal', function (e) {
        e.preventDefault()
fat's avatar
fat committed
397
        $otherBtn.trigger('focus')
Heinrich Fenkart's avatar
Heinrich Fenkart committed
398
399
400
        setTimeout($.proxy(function () {
          $(this).bootstrapModal('show')
        }, this), 0)
Chris Rebert's avatar
Chris Rebert committed
401
402
      })
      .on('hidden.bs.modal', function () {
Heinrich Fenkart's avatar
Heinrich Fenkart committed
403
        setTimeout(function () {
XhmikosR's avatar
XhmikosR committed
404
          assert.true($(document.activeElement).is($otherBtn), 'focus returned to toggling element')
405
          done()
Chris Rebert's avatar
Chris Rebert committed
406
407
408
        }, 0)
      })
      .on('shown.bs.modal', function () {
fat's avatar
fat committed
409
        $('#close').trigger('click')
Chris Rebert's avatar
Chris Rebert committed
410
411
      })
      .appendTo('#qunit-fixture')
Heinrich Fenkart's avatar
Heinrich Fenkart committed
412

fat's avatar
fat committed
413
414
415
    $toggleBtn.trigger('click')
  })

David Bailey's avatar
David Bailey committed
416
417
418
419
420
421
422
423
424
425
426
427
428
429
  QUnit.test('should adjust the inline padding of the modal when opening', function (assert) {
    assert.expect(1)
    var done = assert.async()

    $('<div id="modal-test"/>')
      .on('shown.bs.modal', function () {
        var expectedPadding = $(this).getScrollbarWidth() + 'px'
        var currentPadding = $(this).css('padding-right')
        assert.strictEqual(currentPadding, expectedPadding, 'modal padding should be adjusted while opening')
        done()
      })
      .bootstrapModal('show')
  })

430
  QUnit.test('should adjust the inline body padding when opening and restore when closing', function (assert) {
fat's avatar
fat committed
431
432
433
    assert.expect(2)
    var done = assert.async()
    var $body = $(document.body)
434
    var originalPadding = $body.css('padding-right')
fat's avatar
fat committed
435
436
437

    $('<div id="modal-test"/>')
      .on('hidden.bs.modal', function () {
438
439
        var currentPadding = $body.css('padding-right')
        assert.strictEqual(currentPadding, originalPadding, 'body padding should be reset after closing')
fat's avatar
fat committed
440
441
442
443
        $body.removeAttr('style')
        done()
      })
      .on('shown.bs.modal', function () {
444
        var expectedPadding = parseFloat(originalPadding) + $(this).getScrollbarWidth() + 'px'
445
        var currentPadding = $body.css('padding-right')
446
        assert.strictEqual(currentPadding, expectedPadding, 'body padding should be adjusted while opening')
fat's avatar
fat committed
447
448
449
450
451
        $(this).bootstrapModal('hide')
      })
      .bootstrapModal('show')
  })

452
453
  QUnit.test('should store the original body padding in data-padding-right before showing', function (assert) {
    assert.expect(2)
fat's avatar
fat committed
454
455
    var done = assert.async()
    var $body = $(document.body)
456
457
    var originalPadding = '0px'
    $body.css('padding-right', originalPadding)
fat's avatar
fat committed
458
459
460

    $('<div id="modal-test"/>')
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
461
        assert.strictEqual(typeof $body.data('padding-right'), 'undefined', 'data-padding-right should be cleared after closing')
462
        $body.removeAttr('style')
fat's avatar
fat committed
463
464
465
        done()
      })
      .on('shown.bs.modal', function () {
466
        assert.strictEqual($body.data('padding-right'), originalPadding, 'original body padding should be stored in data-padding-right')
fat's avatar
fat committed
467
468
469
470
471
        $(this).bootstrapModal('hide')
      })
      .bootstrapModal('show')
  })

David Bailey's avatar
David Bailey committed
472
473
474
475
476
477
  QUnit.test('should not adjust the inline body padding when it does not overflow', function (assert) {
    assert.expect(1)
    var done = assert.async()
    var $body = $(document.body)
    var originalPadding = $body.css('padding-right')

David Bailey's avatar
David Bailey committed
478
    // Hide scrollbars to prevent the body overflowing
XhmikosR's avatar
XhmikosR committed
479
480
    $body.css('overflow', 'hidden') // Real scrollbar (for in-browser testing)
    $('html').css('padding-right', '0px') // Simulated scrollbar (for PhantomJS)
David Bailey's avatar
David Bailey committed
481

David Bailey's avatar
David Bailey committed
482
483
484
485
486
487
    $('<div id="modal-test"/>')
      .on('shown.bs.modal', function () {
        var currentPadding = $body.css('padding-right')
        assert.strictEqual(currentPadding, originalPadding, 'body padding should not be adjusted')
        $(this).bootstrapModal('hide')

XhmikosR's avatar
XhmikosR committed
488
        // Restore scrollbars
David Bailey's avatar
David Bailey committed
489
490
        $body.css('overflow', 'auto')
        $('html').css('padding-right', '16px')
David Bailey's avatar
David Bailey committed
491
492
493
494
495
        done()
      })
      .bootstrapModal('show')
  })

496
  QUnit.test('should adjust the inline padding of fixed elements when opening and restore when closing', function (assert) {
497
498
    assert.expect(2)
    var done = assert.async()
499
500
    var $element = $('<div class="fixed-top"></div>').appendTo('#qunit-fixture')
    var originalPadding = $element.css('padding-right')
501
502

    $('<div id="modal-test"/>')
503
504
505
506
507
508
      .on('hidden.bs.modal', function () {
        var currentPadding = $element.css('padding-right')
        assert.strictEqual(currentPadding, originalPadding, 'fixed element padding should be reset after closing')
        $element.remove()
        done()
      })
509
      .on('shown.bs.modal', function () {
510
        var expectedPadding = parseFloat(originalPadding) + $(this).getScrollbarWidth() + 'px'
511
        var currentPadding = $element.css('padding-right')
512
        assert.strictEqual(currentPadding, expectedPadding, 'fixed element padding should be adjusted while opening')
513
514
515
516
517
518
519
520
521
522
523
524
525
526
        $(this).bootstrapModal('hide')
      })
      .bootstrapModal('show')
  })

  QUnit.test('should store the original padding of fixed elements in data-padding-right before showing', function (assert) {
    assert.expect(2)
    var done = assert.async()
    var $element = $('<div class="fixed-top"></div>').appendTo('#qunit-fixture')
    var originalPadding = '0px'
    $element.css('padding-right', originalPadding)

    $('<div id="modal-test"/>')
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
527
        assert.strictEqual(typeof $element.data('padding-right'), 'undefined', 'data-padding-right should be cleared after closing')
528
        $element.remove()
529
530
        done()
      })
531
532
533
534
      .on('shown.bs.modal', function () {
        assert.strictEqual($element.data('padding-right'), originalPadding, 'original fixed element padding should be stored in data-padding-right')
        $(this).bootstrapModal('hide')
      })
535
536
537
      .bootstrapModal('show')
  })

538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
  QUnit.test('should adjust the inline margin of sticky elements when opening and restore when closing', function (assert) {
    assert.expect(2)
    var done = assert.async()
    var $element = $('<div class="sticky-top"></div>').appendTo('#qunit-fixture')
    var originalPadding = $element.css('margin-right')

    $('<div id="modal-test"/>')
      .on('hidden.bs.modal', function () {
        var currentPadding = $element.css('margin-right')
        assert.strictEqual(currentPadding, originalPadding, 'sticky element margin should be reset after closing')
        $element.remove()
        done()
      })
      .on('shown.bs.modal', function () {
        var expectedPadding = parseFloat(originalPadding) - $(this).getScrollbarWidth() + 'px'
        var currentPadding = $element.css('margin-right')
        assert.strictEqual(currentPadding, expectedPadding, 'sticky element margin should be adjusted while opening')
        $(this).bootstrapModal('hide')
      })
      .bootstrapModal('show')
  })

  QUnit.test('should store the original margin of sticky elements in data-margin-right before showing', function (assert) {
    assert.expect(2)
    var done = assert.async()
    var $element = $('<div class="sticky-top"></div>').appendTo('#qunit-fixture')
    var originalPadding = '0px'
    $element.css('margin-right', originalPadding)

    $('<div id="modal-test"/>')
      .on('hidden.bs.modal', function () {
        assert.strictEqual(typeof $element.data('margin-right'), 'undefined', 'data-margin-right should be cleared after closing')
        $element.remove()
        done()
      })
      .on('shown.bs.modal', function () {
        assert.strictEqual($element.data('margin-right'), originalPadding, 'original sticky element margin should be stored in data-margin-right')
575
576
        $(this).bootstrapModal('hide')
      })
577
578
579
580
581
582
583
584
585
586
      .bootstrapModal('show')
  })

  QUnit.test('should ignore values set via CSS when trying to restore body padding after closing', function (assert) {
    assert.expect(1)
    var done = assert.async()
    var $body = $(document.body)
    var $style = $('<style>body { padding-right: 42px; }</style>').appendTo('head')

    $('<div id="modal-test"/>')
587
      .on('hidden.bs.modal', function () {
David Bailey's avatar
David Bailey committed
588
        assert.strictEqual($body.attr('style').indexOf('padding-right'), -1, 'body does not have inline padding set')
589
        $style.remove()
590
591
        done()
      })
592
593
594
      .on('shown.bs.modal', function () {
        $(this).bootstrapModal('hide')
      })
595
596
597
      .bootstrapModal('show')
  })

fat's avatar
fat committed
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
  QUnit.test('should ignore other inline styles when trying to restore body padding after closing', function (assert) {
    assert.expect(2)
    var done = assert.async()
    var $body = $(document.body)
    var $style = $('<style>body { padding-right: 42px; }</style>').appendTo('head')

    $body.css('color', 'red')

    $('<div id="modal-test"/>')
      .on('hidden.bs.modal', function () {
        assert.strictEqual($body[0].style.paddingRight, '', 'body does not have inline padding set')
        assert.strictEqual($body[0].style.color, 'red', 'body still has other inline styles set')
        $body.removeAttr('style')
        $style.remove()
        done()
      })
      .on('shown.bs.modal', function () {
        $(this).bootstrapModal('hide')
      })
      .bootstrapModal('show')
  })

  QUnit.test('should properly restore non-pixel inline body padding after closing', function (assert) {
    assert.expect(1)
    var done = assert.async()
    var $body = $(document.body)

    $body.css('padding-right', '5%')

    $('<div id="modal-test"/>')
      .on('hidden.bs.modal', function () {
        assert.strictEqual($body[0].style.paddingRight, '5%', 'body does not have inline padding set')
        $body.removeAttr('style')
        done()
      })
      .on('shown.bs.modal', function () {
        $(this).bootstrapModal('hide')
      })
      .bootstrapModal('show')
Chris Rebert's avatar
Chris Rebert committed
637
  })
638
639
640
641
642
643
644
645
646
647
648
649
650

  QUnit.test('should not follow link in area tag', function (assert) {
    assert.expect(2)
    var done = assert.async()

    $('<map><area id="test" shape="default" data-toggle="modal" data-target="#modal-test" href="demo.html"/></map>')
      .appendTo('#qunit-fixture')

    $('<div id="modal-test"><div class="contents"><div id="close" data-dismiss="modal"/></div></div>')
      .appendTo('#qunit-fixture')

    $('#test')
      .on('click.bs.modal.data-api', function (event) {
XhmikosR's avatar
XhmikosR committed
651
        assert.false(event.isDefaultPrevented(), 'navigating to href will happen')
652
653

        setTimeout(function () {
XhmikosR's avatar
XhmikosR committed
654
          assert.true(event.isDefaultPrevented(), 'model shown instead of navigating to href')
655
656
657
658
659
          done()
        }, 1)
      })
      .trigger('click')
  })
660
661
662
663
664

  QUnit.test('should not parse target as html', function (assert) {
    assert.expect(1)
    var done = assert.async()

665
666
    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;"/>')
      .appendTo('#qunit-fixture')
667

668
669
    $toggleBtn.trigger('click')
    setTimeout(function () {
670
671
      assert.strictEqual($('#modal-test').length, 0, 'target has not been parsed and added to the document')
      done()
672
    }, 0)
673
674
  })

XhmikosR's avatar
XhmikosR committed
675
  // eslint-disable-next-line qunit/resolve-async
676
677
678
679
  QUnit.test('should not execute js from target', function (assert) {
    assert.expect(0)
    var done = assert.async()

680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
    // This toggle button contains XSS payload in its data-target
    // Note: it uses the onerror handler of an img element to execute the js, because a simple script element does not work here
    //       a script element works in manual tests though, so here it is likely blocked by the qunit framework
    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;"/>')
      .appendTo('#qunit-fixture')
    // The XSS payload above does not have a closure over this function and cannot access the assert object directly
    // However, it can send a click event to the following control button, which will then fail the assert
    $('<button>')
      .addClass('control')
      .on('click', function () {
        assert.notOk(true, 'XSS payload is not executed as js')
      })
      .appendTo('#qunit-fixture')

    $toggleBtn.trigger('click')

    setTimeout(done, 500)
697
  })
lucascono's avatar
lucascono committed
698
699
700
701
702
703
704
705
706
707
708

  QUnit.test('should not try to open a modal which is already visible', function (assert) {
    assert.expect(1)
    var done = assert.async()
    var count = 0

    $('<div id="modal-test"/>').on('shown.bs.modal', function () {
      count++
    }).on('hidden.bs.modal', function () {
      assert.strictEqual(count, 1, 'show() runs only once')
      done()
XhmikosR's avatar
XhmikosR committed
709
710
711
712
    })
      .bootstrapModal('show')
      .bootstrapModal('show')
      .bootstrapModal('hide')
lucascono's avatar
lucascono committed
713
  })
714
715

  QUnit.test('transition duration should be the modal-dialog duration before triggering shown event', function (assert) {
716
    assert.expect(1)
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
    var done = assert.async()
    var style = [
      '<style>',
      '  .modal.fade .modal-dialog {',
      '    transition: -webkit-transform .3s ease-out;',
      '    transition: transform .3s ease-out;',
      '    transition: transform .3s ease-out,-webkit-transform .3s ease-out;',
      '    -webkit-transform: translate(0,-50px);',
      '    transform: translate(0,-50px);',
      '  }',
      '</style>'
    ].join('')

    var $style = $(style).appendTo('head')
    var modalHTML = [
      '<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">',
      '  <div class="modal-dialog" role="document">',
      '    <div class="modal-content">',
      '      <div class="modal-body">...</div>',
      '    </div>',
      '  </div>',
      '</div>'
    ].join('')

    var $modal = $(modalHTML).appendTo('#qunit-fixture')
742
743
    var expectedTransitionDuration = 300
    var spy = sinon.spy(Util, 'getTransitionDurationFromElement')
744
745

    $modal.on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
746
      assert.true(spy.returned(expectedTransitionDuration))
747
      $style.remove()
748
      spy.restore()
749
750
751
752
      done()
    })
      .bootstrapModal('show')
  })
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772

  QUnit.test('should dispose modal', function (assert) {
    assert.expect(3)
    var done = assert.async()

    var $modal = $([
      '<div id="modal-test">',
      '  <div class="modal-dialog">',
      '    <div class="modal-content">',
      '      <div class="modal-body" />',
      '    </div>',
      '  </div>',
      '</div>'
    ].join('')).appendTo('#qunit-fixture')

    $modal.on('shown.bs.modal', function () {
      var spy = sinon.spy($.fn, 'off')

      $(this).bootstrapModal('dispose')

Johann-S's avatar
Johann-S committed
773
774
775
776
777
778
779
      var modalDataApiEvent = []
      $._data(document, 'events').click
        .forEach(function (e) {
          if (e.namespace === 'bs.data-api.modal') {
            modalDataApiEvent.push(e)
          }
        })
780

XhmikosR's avatar
XhmikosR committed
781
      assert.strictEqual(typeof $(this).data('bs.modal'), 'undefined', 'modal data object was disposed')
782

XhmikosR's avatar
XhmikosR committed
783
      assert.strictEqual(spy.callCount, 4, '`jQuery.off` was called')
784

XhmikosR's avatar
XhmikosR committed
785
      assert.strictEqual(modalDataApiEvent.length, 1, '`Event.CLICK_DATA_API` on `document` was not removed')
786
787
788
789
790

      $.fn.off.restore()
      done()
    }).bootstrapModal('show')
  })
791

XhmikosR's avatar
XhmikosR committed
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
  QUnit.test('should not adjust the inline body padding when it does not overflow, even on a scaled display', function (assert) {
    assert.expect(1)
    var done = assert.async()

    var $modal = $([
      '<div id="modal-test">',
      '  <div class="modal-dialog">',
      '    <div class="modal-content">',
      '      <div class="modal-body" />',
      '    </div>',
      '  </div>',
      '</div>'
    ].join('')).appendTo('#qunit-fixture')

    var originalPadding = window.getComputedStyle(document.body).paddingRight

    // Remove body margins as would be done by Bootstrap css
    document.body.style.margin = '0'

    // Hide scrollbars to prevent the body overflowing
    document.body.style.overflow = 'hidden'

    // Simulate a discrepancy between exact, i.e. floating point body width, and rounded body width
    // as it can occur when zooming or scaling the display to something else than 100%
    document.documentElement.style.paddingRight = '.48px'

    $modal.on('shown.bs.modal', function () {
      var currentPadding = window.getComputedStyle(document.body).paddingRight

      assert.strictEqual(currentPadding, originalPadding, 'body padding should not be adjusted')

      // Restore overridden css
      document.body.style.removeProperty('margin')
      document.body.style.removeProperty('overflow')
      document.documentElement.style.paddingRight = '16px'
      done()
    }).bootstrapModal('show')
  })

831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
  QUnit.test('should enforce focus', function (assert) {
    assert.expect(4)
    var done = assert.async()

    var $modal = $([
      '<div id="modal-test" data-show="false">',
      '  <div class="modal-dialog">',
      '    <div class="modal-content">',
      '      <div class="modal-body" />',
      '    </div>',
      '  </div>',
      '</div>'
    ].join(''))
      .bootstrapModal()
      .appendTo('#qunit-fixture')

    var modal = $modal.data('bs.modal')
    var spy = sinon.spy(modal, '_enforceFocus')
    var spyDocOff = sinon.spy($(document), 'off')
    var spyDocOn = sinon.spy($(document), 'on')

    $modal.one('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
853
      assert.true(spy.called, '_enforceFocus called')
854
855
856
857
858
859
860
861
862
      assert.ok(spyDocOff.withArgs('focusin.bs.modal'))
      assert.ok(spyDocOn.withArgs('focusin.bs.modal'))

      var spyFocus = sinon.spy(modal._element, 'focus')
      var event = $.Event('focusin', {
        target: $('#qunit-fixture')[0]
      })

      $(document).one('focusin', function () {
XhmikosR's avatar
XhmikosR committed
863
        assert.true(spyFocus.called)
864
865
866
867
868
869
870
        done()
      })

      $(document).trigger(event)
    })
      .bootstrapModal('show')
  })
Shohei Yoshida's avatar
Shohei Yoshida committed
871
872

  QUnit.test('should scroll to top of the modal body if the modal has .modal-dialog-scrollable class', function (assert) {
XhmikosR's avatar
XhmikosR committed
873
    assert.expect(3)
Shohei Yoshida's avatar
Shohei Yoshida committed
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
    var done = assert.async()

    var $modal = $([
      '<div id="modal-test">',
      '  <div class="modal-dialog modal-dialog-scrollable">',
      '    <div class="modal-content">',
      '      <div class="modal-body" style="height: 100px; overflow-y: auto;">',
      '        <div style="height: 200px" />',
      '      </div>',
      '    </div>',
      '  </div>',
      '</div>'
    ].join('')).appendTo('#qunit-fixture')

    var $modalBody = $('.modal-body')
    $modalBody.scrollTop(100)
XhmikosR's avatar
XhmikosR committed
890
891
    assert.true($modalBody.scrollTop() > 95)
    assert.true($modalBody.scrollTop() <= 100)
Shohei Yoshida's avatar
Shohei Yoshida committed
892
893
894
895
896
897
898

    $modal.on('shown.bs.modal', function () {
      assert.strictEqual($modalBody.scrollTop(), 0, 'modal body scrollTop should be 0 when opened')
      done()
    })
      .bootstrapModal('show')
  })
Johann-S's avatar
Johann-S committed
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918

  QUnit.test('should set .modal\'s scroll top to 0 if .modal-dialog-scrollable and modal body do not exists', function (assert) {
    assert.expect(1)
    var done = assert.async()

    var $modal = $([
      '<div id="modal-test">',
      '  <div class="modal-dialog modal-dialog-scrollable">',
      '    <div class="modal-content">',
      '    </div>',
      '  </div>',
      '</div>'
    ].join('')).appendTo('#qunit-fixture')

    $modal.on('shown.bs.modal', function () {
      assert.strictEqual($modal.scrollTop(), 0)
      done()
    })
      .bootstrapModal('show')
  })
919
920
921
922
923
924
925
926
927
928
929

  QUnit.test('should not close modal when clicking outside of modal-content if backdrop = static', function (assert) {
    assert.expect(1)
    var done = assert.async()
    var $modal = $('<div class="modal" data-backdrop="static"><div class="modal-dialog" /></div>').appendTo('#qunit-fixture')

    $modal.on('shown.bs.modal', function () {
      $modal.trigger('click')
      setTimeout(function () {
        var modal = $modal.data('bs.modal')

XhmikosR's avatar
XhmikosR committed
930
        assert.true(modal._isShown)
931
932
933
934
        done()
      }, 10)
    })
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
935
        assert.true(false, 'should not hide the modal')
936
937
938
939
940
      })
      .bootstrapModal({
        backdrop: 'static'
      })
  })
Giovanni Mendoza's avatar
Giovanni Mendoza committed
941
942
943
944
945
946
947
948
949
950
951
952
953
954

  QUnit.test('should close modal when escape key is pressed with keyboard = true and backdrop is static', function (assert) {
    assert.expect(1)
    var done = assert.async()
    var $modal = $('<div class="modal" data-backdrop="static" data-keyboard="true"><div class="modal-dialog" /></div>').appendTo('#qunit-fixture')

    $modal.on('shown.bs.modal', function () {
      $modal.trigger($.Event('keydown', {
        which: 27
      }))

      setTimeout(function () {
        var modal = $modal.data('bs.modal')

XhmikosR's avatar
XhmikosR committed
955
        assert.false(modal._isShown)
Giovanni Mendoza's avatar
Giovanni Mendoza committed
956
957
958
959
960
961
962
963
964
        done()
      }, 10)
    })
      .bootstrapModal({
        backdrop: 'static',
        keyboard: true
      })
  })

Rohit Sharma's avatar
Rohit Sharma committed
965
  QUnit.test('should not close modal when escape key is pressed with keyboard = false', function (assert) {
Giovanni Mendoza's avatar
Giovanni Mendoza committed
966
967
    assert.expect(1)
    var done = assert.async()
Rohit Sharma's avatar
Rohit Sharma committed
968
    var $modal = $('<div class="modal"><div class="modal-dialog" /></div>').appendTo('#qunit-fixture')
Giovanni Mendoza's avatar
Giovanni Mendoza committed
969
970
971
972
973
974
975
976
977

    $modal.on('shown.bs.modal', function () {
      $modal.trigger($.Event('keydown', {
        which: 27
      }))

      setTimeout(function () {
        var modal = $modal.data('bs.modal')

XhmikosR's avatar
XhmikosR committed
978
        assert.true(modal._isShown)
Giovanni Mendoza's avatar
Giovanni Mendoza committed
979
980
981
982
        done()
      }, 10)
    })
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
983
        assert.false(true, 'should not hide the modal')
Giovanni Mendoza's avatar
Giovanni Mendoza committed
984
985
986
987
988
      })
      .bootstrapModal({
        keyboard: false
      })
  })
ysds's avatar
ysds committed
989
990
991
992
993
994
995
996
997
998
999
1000

  QUnit.test('should not overflow when clicking outside of modal-content if backdrop = static', function (assert) {
    assert.expect(1)
    var done = assert.async()
    var $modal = $('<div class="modal" data-backdrop="static"><div class="modal-dialog" style="transition-duration: 20ms;"/></div>').appendTo('#qunit-fixture')

    $modal.on('shown.bs.modal', function () {
      $modal.trigger('click')
      setTimeout(function () {
        assert.strictEqual($modal[0].clientHeight, $modal[0].scrollHeight)
        done()
      }, 20)
For faster browsing, not all history is shown. View entire blame