modal.js 28.7 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
    } catch (err) {
48
49
50
51
      assert.strictEqual(err.message, 'No method named "noMethod"')
    }
  })

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()
fat's avatar
fat committed
56
57
    assert.ok($modal instanceof $, 'returns jquery collection')
    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 () {
fat's avatar
fat committed
71
        assert.notEqual($('#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
  })

fat's avatar
fat committed
105
106
  QUnit.test('should hide modal when hide is called', function (assert) {
    assert.expect(3)
107
    var done = assert.async()
XhmikosR's avatar
XhmikosR committed
108

Heinrich Fenkart's avatar
Heinrich Fenkart committed
109
    $('<div id="modal-test"/>')
XhmikosR's avatar
XhmikosR committed
110
      .on('shown.bs.modal', function () {
fat's avatar
fat committed
111
112
        assert.ok($('#modal-test').is(':visible'), 'modal visible')
        assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom')
113
        $(this).bootstrapModal('hide')
XhmikosR's avatar
XhmikosR committed
114
115
      })
      .on('hidden.bs.modal', function () {
fat's avatar
fat committed
116
        assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
117
        done()
XhmikosR's avatar
XhmikosR committed
118
      })
119
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
120
121
  })

fat's avatar
fat committed
122
123
  QUnit.test('should toggle when toggle is called', function (assert) {
    assert.expect(3)
124
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
125
126

    $('<div id="modal-test"/>')
XhmikosR's avatar
XhmikosR committed
127
      .on('shown.bs.modal', function () {
fat's avatar
fat committed
128
129
        assert.ok($('#modal-test').is(':visible'), 'modal visible')
        assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom')
Heinrich Fenkart's avatar
Heinrich Fenkart committed
130
        $(this).bootstrapModal('toggle')
XhmikosR's avatar
XhmikosR committed
131
132
      })
      .on('hidden.bs.modal', function () {
fat's avatar
fat committed
133
        assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
134
        done()
XhmikosR's avatar
XhmikosR committed
135
      })
136
      .bootstrapModal('toggle')
XhmikosR's avatar
XhmikosR committed
137
138
  })

fat's avatar
fat committed
139
140
  QUnit.test('should remove from dom when click [data-dismiss="modal"]', function (assert) {
    assert.expect(3)
141
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
142
143

    $('<div id="modal-test"><span class="close" data-dismiss="modal"/></div>')
XhmikosR's avatar
XhmikosR committed
144
      .on('shown.bs.modal', function () {
fat's avatar
fat committed
145
146
147
        assert.ok($('#modal-test').is(':visible'), 'modal visible')
        assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom')
        $(this).find('.close').trigger('click')
XhmikosR's avatar
XhmikosR committed
148
149
      })
      .on('hidden.bs.modal', function () {
fat's avatar
fat committed
150
        assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
151
        done()
XhmikosR's avatar
XhmikosR committed
152
      })
153
      .bootstrapModal('toggle')
XhmikosR's avatar
XhmikosR committed
154
155
  })

fat's avatar
fat committed
156
157
  QUnit.test('should allow modal close with "backdrop:false"', function (assert) {
    assert.expect(2)
158
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
159
160

    $('<div id="modal-test" data-backdrop="false"/>')
XhmikosR's avatar
XhmikosR committed
161
      .on('shown.bs.modal', function () {
fat's avatar
fat committed
162
        assert.ok($('#modal-test').is(':visible'), 'modal visible')
Heinrich Fenkart's avatar
Heinrich Fenkart committed
163
        $(this).bootstrapModal('hide')
XhmikosR's avatar
XhmikosR committed
164
165
      })
      .on('hidden.bs.modal', function () {
fat's avatar
fat committed
166
        assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
167
        done()
XhmikosR's avatar
XhmikosR committed
168
      })
169
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
170
171
  })

fat's avatar
fat committed
172
173
  QUnit.test('should close modal when clicking outside of modal-content', function (assert) {
    assert.expect(3)
174
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
175
176

    $('<div id="modal-test"><div class="contents"/></div>')
177
      .on('shown.bs.modal', function () {
fat's avatar
fat committed
178
179
180
181
        assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom')
        $('.contents').trigger('click')
        assert.ok($('#modal-test').is(':visible'), 'modal visible')
        $('#modal-test').trigger('click')
XhmikosR's avatar
XhmikosR committed
182
      })
183
      .on('hidden.bs.modal', function () {
fat's avatar
fat committed
184
        assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
185
        done()
XhmikosR's avatar
XhmikosR committed
186
      })
187
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
188
189
  })

190
191
192
193
194
195
196
197
198
199
200
201
202
  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')
        assert.ok($('#modal-test').is(':visible'), 'modal not hidden')
        done()
      })
      .bootstrapModal('show')
  })

fat's avatar
fat committed
203
204
  QUnit.test('should close modal when escape key is pressed via keydown', function (assert) {
    assert.expect(3)
205
    var done = assert.async()
206

fat's avatar
fat committed
207
208
    var $div = $('<div id="modal-test"/>')
    $div
209
      .on('shown.bs.modal', function () {
210
        assert.ok($('#modal-test').length, 'modal inserted into dom')
fat's avatar
fat committed
211
        assert.ok($('#modal-test').is(':visible'), 'modal visible')
212
213
214
215
216
217

        var evt = document.createEvent('HTMLEvents')
        evt.initEvent('keydown', true, true)
        evt.which = 27

        $div[0].dispatchEvent(evt)
218
219

        setTimeout(function () {
fat's avatar
fat committed
220
221
          assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
          $div.remove()
222
          done()
223
224
225
226
227
        }, 0)
      })
      .bootstrapModal('show')
  })

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

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

        setTimeout(function () {
fat's avatar
fat committed
242
243
          assert.ok($div.is(':visible'), 'modal still visible')
          $div.remove()
244
          done()
245
246
247
248
249
        }, 0)
      })
      .bootstrapModal('show')
  })

fat's avatar
fat committed
250
251
  QUnit.test('should trigger hide event once when clicking outside of modal-content', function (assert) {
    assert.expect(1)
252
    var done = assert.async()
XhmikosR's avatar
XhmikosR committed
253
254
255

    var triggered

Heinrich Fenkart's avatar
Heinrich Fenkart committed
256
    $('<div id="modal-test"><div class="contents"/></div>')
257
      .on('shown.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
258
        triggered = 0
fat's avatar
fat committed
259
        $('#modal-test').trigger('click')
XhmikosR's avatar
XhmikosR committed
260
      })
261
      .on('hide.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
262
        triggered += 1
fat's avatar
fat committed
263
        assert.strictEqual(triggered, 1, 'modal hide triggered once')
264
        done()
XhmikosR's avatar
XhmikosR committed
265
      })
266
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
267
268
  })

269
270
271
272
273
274
275
276
277
278
279
280
281
  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 () {
        assert.notOk($('#modal-test').is('[aria-hidden]'), 'aria-hidden attribute removed')
        $(this).bootstrapModal('hide')
      })
      .on('hidden.bs.modal', function () {
        assert.ok($('#modal-test').is('[aria-hidden]'), 'aria-hidden attribute added')
        assert.strictEqual($('#modal-test').attr('aria-hidden'), 'true', 'correct aria-hidden="true" added')
        done()
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
      })
      .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 () {
        assert.ok($('#modal-test').is('[aria-modal]'), 'aria-modal attribute added')
        assert.strictEqual($('#modal-test').attr('aria-modal'), 'true', 'correct aria-modal="true" added')
        $(this).bootstrapModal('hide')
      })
      .on('hidden.bs.modal', function () {
        assert.notOk($('#modal-test').is('[aria-modal]'), 'aria-modal attribute removed')
        done()
299
300
301
302
      })
      .bootstrapModal('show')
  })

fat's avatar
fat committed
303
304
  QUnit.test('should close reopened modal with [data-dismiss="modal"] click', function (assert) {
    assert.expect(2)
305
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
306
307

    $('<div id="modal-test"><div class="contents"><div id="close" data-dismiss="modal"/></div></div>')
308
      .one('shown.bs.modal', function () {
fat's avatar
fat committed
309
        $('#close').trigger('click')
XhmikosR's avatar
XhmikosR committed
310
311
      })
      .one('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
312
        // After one open-close cycle
fat's avatar
fat committed
313
        assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
314
315
316
317
318
319
320
321
322
323
324
325
326

        var $this = $(this)
        setTimeout(function () {
          $this
            .one('shown.bs.modal', function () {
              $('#close').trigger('click')
            })
            .one('hidden.bs.modal', function () {
              assert.ok(!$('#modal-test').is(':visible'), 'modal hidden')
              done()
            })
            .bootstrapModal('show')
        }, 0)
XhmikosR's avatar
XhmikosR committed
327
      })
328
      .bootstrapModal('show')
XhmikosR's avatar
XhmikosR committed
329
  })
Chris Rebert's avatar
Chris Rebert committed
330

fat's avatar
fat committed
331
332
  QUnit.test('should restore focus to toggling element when modal is hidden after having been opened via data-api', function (assert) {
    assert.expect(1)
333
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
334
335
336
337

    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
338
      .on('hidden.bs.modal', function () {
Heinrich Fenkart's avatar
Heinrich Fenkart committed
339
        setTimeout(function () {
fat's avatar
fat committed
340
          assert.ok($(document.activeElement).is($toggleBtn), 'toggling element is once again focused')
341
          done()
Chris Rebert's avatar
Chris Rebert committed
342
343
344
        }, 0)
      })
      .on('shown.bs.modal', function () {
fat's avatar
fat committed
345
        $('#close').trigger('click')
Chris Rebert's avatar
Chris Rebert committed
346
347
      })
      .appendTo('#qunit-fixture')
Heinrich Fenkart's avatar
Heinrich Fenkart committed
348

fat's avatar
fat committed
349
    $toggleBtn.trigger('click')
Chris Rebert's avatar
Chris Rebert committed
350
351
  })

fat's avatar
fat committed
352
353
  QUnit.test('should not restore focus to toggling element if the associated show event gets prevented', function (assert) {
    assert.expect(1)
354
    var done = assert.async()
Heinrich Fenkart's avatar
Heinrich Fenkart committed
355
356
357
358
    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
359
360
      .one('show.bs.modal', function (e) {
        e.preventDefault()
fat's avatar
fat committed
361
        $otherBtn.trigger('focus')
Heinrich Fenkart's avatar
Heinrich Fenkart committed
362
363
364
        setTimeout($.proxy(function () {
          $(this).bootstrapModal('show')
        }, this), 0)
Chris Rebert's avatar
Chris Rebert committed
365
366
      })
      .on('hidden.bs.modal', function () {
Heinrich Fenkart's avatar
Heinrich Fenkart committed
367
        setTimeout(function () {
fat's avatar
fat committed
368
          assert.ok($(document.activeElement).is($otherBtn), 'focus returned to toggling element')
369
          done()
Chris Rebert's avatar
Chris Rebert committed
370
371
372
        }, 0)
      })
      .on('shown.bs.modal', function () {
fat's avatar
fat committed
373
        $('#close').trigger('click')
Chris Rebert's avatar
Chris Rebert committed
374
375
      })
      .appendTo('#qunit-fixture')
Heinrich Fenkart's avatar
Heinrich Fenkart committed
376

fat's avatar
fat committed
377
378
379
    $toggleBtn.trigger('click')
  })

David Bailey's avatar
David Bailey committed
380
381
382
383
384
385
386
387
388
389
390
391
392
393
  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')
  })

394
  QUnit.test('should adjust the inline body padding when opening and restore when closing', function (assert) {
fat's avatar
fat committed
395
396
397
    assert.expect(2)
    var done = assert.async()
    var $body = $(document.body)
398
    var originalPadding = $body.css('padding-right')
fat's avatar
fat committed
399
400
401

    $('<div id="modal-test"/>')
      .on('hidden.bs.modal', function () {
402
403
        var currentPadding = $body.css('padding-right')
        assert.strictEqual(currentPadding, originalPadding, 'body padding should be reset after closing')
fat's avatar
fat committed
404
405
406
407
        $body.removeAttr('style')
        done()
      })
      .on('shown.bs.modal', function () {
408
        var expectedPadding = parseFloat(originalPadding) + $(this).getScrollbarWidth() + 'px'
409
        var currentPadding = $body.css('padding-right')
410
        assert.strictEqual(currentPadding, expectedPadding, 'body padding should be adjusted while opening')
fat's avatar
fat committed
411
412
413
414
415
        $(this).bootstrapModal('hide')
      })
      .bootstrapModal('show')
  })

416
417
  QUnit.test('should store the original body padding in data-padding-right before showing', function (assert) {
    assert.expect(2)
fat's avatar
fat committed
418
419
    var done = assert.async()
    var $body = $(document.body)
420
421
    var originalPadding = '0px'
    $body.css('padding-right', originalPadding)
fat's avatar
fat committed
422
423
424

    $('<div id="modal-test"/>')
      .on('hidden.bs.modal', function () {
XhmikosR's avatar
XhmikosR committed
425
        assert.strictEqual(typeof $body.data('padding-right'), 'undefined', 'data-padding-right should be cleared after closing')
426
        $body.removeAttr('style')
fat's avatar
fat committed
427
428
429
        done()
      })
      .on('shown.bs.modal', function () {
430
        assert.strictEqual($body.data('padding-right'), originalPadding, 'original body padding should be stored in data-padding-right')
fat's avatar
fat committed
431
432
433
434
435
        $(this).bootstrapModal('hide')
      })
      .bootstrapModal('show')
  })

David Bailey's avatar
David Bailey committed
436
437
438
439
440
441
  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
442
    // Hide scrollbars to prevent the body overflowing
XhmikosR's avatar
XhmikosR committed
443
444
    $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
445

David Bailey's avatar
David Bailey committed
446
447
448
449
450
451
    $('<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
452
        // Restore scrollbars
David Bailey's avatar
David Bailey committed
453
454
        $body.css('overflow', 'auto')
        $('html').css('padding-right', '16px')
David Bailey's avatar
David Bailey committed
455
456
457
458
459
        done()
      })
      .bootstrapModal('show')
  })

460
  QUnit.test('should adjust the inline padding of fixed elements when opening and restore when closing', function (assert) {
461
462
    assert.expect(2)
    var done = assert.async()
463
464
    var $element = $('<div class="fixed-top"></div>').appendTo('#qunit-fixture')
    var originalPadding = $element.css('padding-right')
465
466

    $('<div id="modal-test"/>')
467
468
469
470
471
472
      .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()
      })
473
      .on('shown.bs.modal', function () {
474
        var expectedPadding = parseFloat(originalPadding) + $(this).getScrollbarWidth() + 'px'
475
        var currentPadding = $element.css('padding-right')
476
        assert.strictEqual(currentPadding, expectedPadding, 'fixed element padding should be adjusted while opening')
477
478
479
480
481
482
483
484
485
486
487
488
489
490
        $(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
491
        assert.strictEqual(typeof $element.data('padding-right'), 'undefined', 'data-padding-right should be cleared after closing')
492
        $element.remove()
493
494
        done()
      })
495
496
497
498
      .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')
      })
499
500
501
      .bootstrapModal('show')
  })

502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
  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')
539
540
        $(this).bootstrapModal('hide')
      })
541
542
543
544
545
546
547
548
549
550
      .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"/>')
551
      .on('hidden.bs.modal', function () {
David Bailey's avatar
David Bailey committed
552
        assert.strictEqual($body.attr('style').indexOf('padding-right'), -1, 'body does not have inline padding set')
553
        $style.remove()
554
555
        done()
      })
556
557
558
      .on('shown.bs.modal', function () {
        $(this).bootstrapModal('hide')
      })
559
560
561
      .bootstrapModal('show')
  })

fat's avatar
fat committed
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
  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
601
  })
602
603
604
605
606
607
608
609
610
611

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

    $('<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')

612
613
614
615
616
617
    // We need to use CustomEvent here to have a working preventDefault in IE tests.
    var evt = new CustomEvent('click', {
      bubbles: true,
      cancelable: true
    })

618
619
    $('#test')
      .on('click.bs.modal.data-api', function (event) {
620
        assert.notOk(event.defaultPrevented, 'navigating to href will happen')
621
      })
622
623
624

    $('#test')[0].dispatchEvent(evt)
    assert.ok(evt.defaultPrevented, 'model shown instead of navigating to href')
625
  })
626
627
628
629
630

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

631
632
    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')
633

634
635
    $toggleBtn.trigger('click')
    setTimeout(function () {
636
637
      assert.strictEqual($('#modal-test').length, 0, 'target has not been parsed and added to the document')
      done()
638
    }, 0)
639
640
641
642
643
644
  })

  QUnit.test('should not execute js from target', function (assert) {
    assert.expect(0)
    var done = assert.async()

645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
    // 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)
662
  })
lucascono's avatar
lucascono committed
663
664
665
666
667
668
669
670
671
672
673

  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
674
675
676
677
    })
      .bootstrapModal('show')
      .bootstrapModal('show')
      .bootstrapModal('hide')
lucascono's avatar
lucascono committed
678
  })
679
680

  QUnit.test('transition duration should be the modal-dialog duration before triggering shown event', function (assert) {
681
    assert.expect(1)
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
    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')
707
708
    var expectedTransitionDuration = 300
    var spy = sinon.spy(Util, 'getTransitionDurationFromElement')
709
710

    $modal.on('shown.bs.modal', function () {
711
      assert.ok(spy.returned(expectedTransitionDuration))
712
      $style.remove()
713
      spy.restore()
714
715
716
717
      done()
    })
      .bootstrapModal('show')
  })
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737

  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
738
739
740
741
742
743
744
      var modalDataApiEvent = []
      $._data(document, 'events').click
        .forEach(function (e) {
          if (e.namespace === 'bs.data-api.modal') {
            modalDataApiEvent.push(e)
          }
        })
745
746
747
748
749

      assert.ok(typeof $(this).data('bs.modal') === 'undefined', 'modal data object was disposed')

      assert.ok(spy.callCount === 4, '`jQuery.off` was called')

Johann-S's avatar
Johann-S committed
750
      assert.ok(modalDataApiEvent.length === 1, '`Event.CLICK_DATA_API` on `document` was not removed')
751
752
753
754
755

      $.fn.off.restore()
      done()
    }).bootstrapModal('show')
  })
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796

  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 () {
      assert.ok(spy.called, '_enforceFocus called')
      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 () {
        assert.ok(spyFocus.called)
        done()
      })

      $(document).trigger(event)
    })
      .bootstrapModal('show')
  })
Shohei Yoshida's avatar
Shohei Yoshida committed
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

  QUnit.test('should scroll to top of the modal body if the modal has .modal-dialog-scrollable class', function (assert) {
    assert.expect(2)
    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)
    assert.strictEqual($modalBody.scrollTop(), 100)

    $modal.on('shown.bs.modal', function () {
      assert.strictEqual($modalBody.scrollTop(), 0, 'modal body scrollTop should be 0 when opened')
      done()
    })
      .bootstrapModal('show')
  })
824
})