scrollspy.js 27.1 KB
Newer Older
Jacob Thornton's avatar
Jacob Thornton committed
1
$(function () {
2
  'use strict'
Jacob Thornton's avatar
Jacob Thornton committed
3

XhmikosR's avatar
XhmikosR committed
4
  var ScrollSpy = typeof window.bootstrap === 'undefined' ? window.ScrollSpy : window.bootstrap.ScrollSpy
5

6
  QUnit.module('scrollspy plugin')
7

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

13
  QUnit.module('scrollspy', {
14
    beforeEach: function () {
15
16
17
      // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
      $.fn.bootstrapScrollspy = $.fn.scrollspy.noConflict()
    },
18
    afterEach: function () {
19
20
      $.fn.scrollspy = $.fn.bootstrapScrollspy
      delete $.fn.bootstrapScrollspy
21
      $('#qunit-fixture').html('')
22
23
24
    }
  })

25
  QUnit.test('should provide no conflict', function (assert) {
26
    assert.expect(1)
XhmikosR's avatar
XhmikosR committed
27
    assert.strictEqual(typeof $.fn.scrollspy, 'undefined', 'scrollspy was set back to undefined (org value)')
28
29
  })

30
31
  QUnit.test('should throw explicit error on undefined method', function (assert) {
    assert.expect(1)
32
    var $el = $('<div/>').appendTo('#qunit-fixture')
33
34
35
    $el.bootstrapScrollspy()
    try {
      $el.bootstrapScrollspy('noMethod')
XhmikosR's avatar
XhmikosR committed
36
37
    } catch (error) {
      assert.strictEqual(error.message, 'No method named "noMethod"')
38
39
40
    }
  })

41
  QUnit.test('should return jquery collection containing the element', function (assert) {
42
    assert.expect(2)
43
    var $el = $('<div/>').appendTo('#qunit-fixture')
44
    var $scrollspy = $el.bootstrapScrollspy()
45
46
    assert.ok($scrollspy instanceof $, 'returns jquery collection')
    assert.strictEqual($scrollspy[0], $el[0], 'collection contains element')
XhmikosR's avatar
XhmikosR committed
47
  })
Jacob Thornton's avatar
Jacob Thornton committed
48

49
  QUnit.test('should only switch "active" class on current target', function (assert) {
50
    assert.expect(1)
51
    var done = assert.async()
52

XhmikosR's avatar
XhmikosR committed
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    var sectionHTML = '<div id="root" class="active">' +
        '<div class="topbar">' +
        '<div class="topbar-inner">' +
        '<div class="container" id="ss-target">' +
        '<ul class="nav">' +
        '<li class="nav-item"><a href="#masthead">Overview</a></li>' +
        '<li class="nav-item"><a href="#detail">Detail</a></li>' +
        '</ul>' +
        '</div>' +
        '</div>' +
        '</div>' +
        '<div id="scrollspy-example" style="height: 100px; overflow: auto;">' +
        '<div style="height: 200px;">' +
        '<h4 id="masthead">Overview</h4>' +
        '<p style="height: 200px">' +
        'Ad leggings keytar, brunch id art party dolor labore.' +
        '</p>' +
        '</div>' +
        '<div style="height: 200px;">' +
        '<h4 id="detail">Detail</h4>' +
        '<p style="height: 200px">' +
        'Veniam marfa mustache skateboard, adipisicing fugiat velit pitchfork beard.' +
        '</p>' +
        '</div>' +
        '</div>' +
        '</div>'
XhmikosR's avatar
XhmikosR committed
79
    var $section = $(sectionHTML).appendTo('#qunit-fixture')
80
81

    var $scrollspy = $section
82
83
      .show()
      .find('#scrollspy-example')
XhmikosR's avatar
XhmikosR committed
84
      .bootstrapScrollspy({
85
        target: 'ss-target'
XhmikosR's avatar
XhmikosR committed
86
      })
87

88
    $scrollspy.one('scroll', function () {
89
      assert.ok($section.hasClass('active'), '"active" class still on root node')
90
      done()
91
    })
92
93

    $scrollspy.scrollTop(350)
94
95
  })

fat's avatar
fat committed
96
97
98
99
  QUnit.test('should only switch "active" class on current target specified w element', function (assert) {
    assert.expect(1)
    var done = assert.async()

XhmikosR's avatar
XhmikosR committed
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
    var sectionHTML = '<div id="root" class="active">' +
        '<div class="topbar">' +
        '<div class="topbar-inner">' +
        '<div class="container" id="ss-target">' +
        '<ul class="nav">' +
        '<li class="nav-item"><a href="#masthead">Overview</a></li>' +
        '<li class="nav-item"><a href="#detail">Detail</a></li>' +
        '</ul>' +
        '</div>' +
        '</div>' +
        '</div>' +
        '<div id="scrollspy-example" style="height: 100px; overflow: auto;">' +
        '<div style="height: 200px;">' +
        '<h4 id="masthead">Overview</h4>' +
        '<p style="height: 200px">' +
        'Ad leggings keytar, brunch id art party dolor labore.' +
        '</p>' +
        '</div>' +
        '<div style="height: 200px;">' +
        '<h4 id="detail">Detail</h4>' +
        '<p style="height: 200px">' +
        'Veniam marfa mustache skateboard, adipisicing fugiat velit pitchfork beard.' +
        '</p>' +
        '</div>' +
        '</div>' +
        '</div>'
fat's avatar
fat committed
126
127
128
129
130
    var $section = $(sectionHTML).appendTo('#qunit-fixture')

    var $scrollspy = $section
      .show()
      .find('#scrollspy-example')
XhmikosR's avatar
XhmikosR committed
131
      .bootstrapScrollspy({
132
        target: document.getElementById('ss-target')
XhmikosR's avatar
XhmikosR committed
133
      })
fat's avatar
fat committed
134

135
    $scrollspy.one('scroll', function () {
fat's avatar
fat committed
136
137
138
139
140
141
142
      assert.ok($section.hasClass('active'), '"active" class still on root node')
      done()
    })

    $scrollspy.scrollTop(350)
  })

143
  QUnit.test('should correctly select middle navigation option when large offset is used', function (assert) {
144
    assert.expect(3)
145
    var done = assert.async()
146

XhmikosR's avatar
XhmikosR committed
147
148
149
150
151
152
153
154
155
156
157
158
159
    var sectionHTML = '<div id="header" style="height: 500px;"></div>' +
        '<nav id="navigation" class="navbar">' +
        '<ul class="navbar-nav">' +
        '<li class="nav-item active"><a class="nav-link" id="one-link" href="#one">One</a></li>' +
        '<li class="nav-item"><a class="nav-link" id="two-link" href="#two">Two</a></li>' +
        '<li class="nav-item"><a class="nav-link" id="three-link" href="#three">Three</a></li>' +
        '</ul>' +
        '</nav>' +
        '<div id="content" style="height: 200px; overflow-y: auto;">' +
        '<div id="one" style="height: 500px;"></div>' +
        '<div id="two" style="height: 300px;"></div>' +
        '<div id="three" style="height: 10px;"></div>' +
        '</div>'
XhmikosR's avatar
XhmikosR committed
160
    var $section = $(sectionHTML).appendTo('#qunit-fixture')
161
    var $scrollspy = $section
162
163
      .show()
      .filter('#content')
164

XhmikosR's avatar
XhmikosR committed
165
166
167
168
    $scrollspy.bootstrapScrollspy({
      target: '#navigation',
      offset: $scrollspy.position().top
    })
169

170
    $scrollspy.one('scroll', function () {
171
172
173
      assert.ok(!$section.find('#one-link').hasClass('active'), '"active" class removed from first section')
      assert.ok($section.find('#two-link').hasClass('active'), '"active" class on middle section')
      assert.ok(!$section.find('#three-link').hasClass('active'), '"active" class not on last section')
174
      done()
175
    })
176
177

    $scrollspy.scrollTop(550)
Julian Thilo's avatar
Julian Thilo committed
178
  })
179

180
  QUnit.test('should add the active class to the correct element', function (assert) {
181
    assert.expect(2)
182
    var navbarHtml =
XhmikosR's avatar
XhmikosR committed
183
184
185
186
187
188
      '<nav class="navbar">' +
      '<ul class="nav">' +
      '<li class="nav-item"><a class="nav-link" id="a-1" href="#div-1">div 1</a></li>' +
      '<li class="nav-item"><a class="nav-link" id="a-2" href="#div-2">div 2</a></li>' +
      '</ul>' +
      '</nav>'
189
    var contentHtml =
XhmikosR's avatar
XhmikosR committed
190
191
192
193
        '<div class="content" style="overflow: auto; height: 50px">' +
      '<div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>' +
      '<div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>' +
      '</div>'
194
195
196
197

    $(navbarHtml).appendTo('#qunit-fixture')
    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
198
199
200
201
      .bootstrapScrollspy({
        offset: 0,
        target: '.navbar'
      })
202

203
    var done = assert.async()
204
205
    var testElementIsActiveAfterScroll = function (element, target) {
      var deferred = $.Deferred()
Johann-S's avatar
Johann-S committed
206
207
208
      // add top padding to fix Chrome on Android failures
      var paddingTop = 5
      var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top) + paddingTop
209
      $content.one('scroll', function () {
210
        assert.ok($(element).hasClass('active'), 'target:' + target + ', element' + element)
211
212
213
214
215
216
        deferred.resolve()
      })
      $content.scrollTop(scrollHeight)
      return deferred.promise()
    }

217
    $.when(testElementIsActiveAfterScroll('#a-1', '#div-1'))
XhmikosR's avatar
XhmikosR committed
218
219
220
221
222
223
      .then(function () {
        return testElementIsActiveAfterScroll('#a-2', '#div-2')
      })
      .then(function () {
        done()
      })
224
225
  })

226
227
228
  QUnit.test('should add the active class to the correct element (nav markup)', function (assert) {
    assert.expect(2)
    var navbarHtml =
XhmikosR's avatar
XhmikosR committed
229
230
231
232
233
234
      '<nav class="navbar">' +
      '<nav class="nav">' +
      '<a class="nav-link" id="a-1" href="#div-1">div 1</a>' +
      '<a class="nav-link" id="a-2" href="#div-2">div 2</a>' +
      '</nav>' +
      '</nav>'
235
    var contentHtml =
XhmikosR's avatar
XhmikosR committed
236
237
238
239
        '<div class="content" style="overflow: auto; height: 50px">' +
      '<div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>' +
      '<div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>' +
      '</div>'
240
241
242
243

    $(navbarHtml).appendTo('#qunit-fixture')
    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
244
245
246
247
      .bootstrapScrollspy({
        offset: 0,
        target: '.navbar'
      })
248
249
250
251

    var done = assert.async()
    var testElementIsActiveAfterScroll = function (element, target) {
      var deferred = $.Deferred()
Johann-S's avatar
Johann-S committed
252
253
254
      // add top padding to fix Chrome on Android failures
      var paddingTop = 5
      var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top) + paddingTop
255
256
257
258
259
260
261
262
263
      $content.one('scroll', function () {
        assert.ok($(element).hasClass('active'), 'target:' + target + ', element' + element)
        deferred.resolve()
      })
      $content.scrollTop(scrollHeight)
      return deferred.promise()
    }

    $.when(testElementIsActiveAfterScroll('#a-1', '#div-1'))
XhmikosR's avatar
XhmikosR committed
264
265
266
267
268
269
      .then(function () {
        return testElementIsActiveAfterScroll('#a-2', '#div-2')
      })
      .then(function () {
        done()
      })
270
271
272
273
274
  })

  QUnit.test('should add the active class to the correct element (list-group markup)', function (assert) {
    assert.expect(2)
    var navbarHtml =
XhmikosR's avatar
XhmikosR committed
275
276
277
278
279
280
      '<nav class="navbar">' +
      '<div class="list-group">' +
      '<a class="list-group-item" id="a-1" href="#div-1">div 1</a>' +
      '<a class="list-group-item" id="a-2" href="#div-2">div 2</a>' +
      '</div>' +
      '</nav>'
281
    var contentHtml =
XhmikosR's avatar
XhmikosR committed
282
283
284
285
        '<div class="content" style="overflow: auto; height: 50px">' +
      '<div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>' +
      '<div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>' +
      '</div>'
286
287
288
289

    $(navbarHtml).appendTo('#qunit-fixture')
    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
290
291
292
293
      .bootstrapScrollspy({
        offset: 0,
        target: '.navbar'
      })
294
295
296
297

    var done = assert.async()
    var testElementIsActiveAfterScroll = function (element, target) {
      var deferred = $.Deferred()
Johann-S's avatar
Johann-S committed
298
299
300
      // add top padding to fix Chrome on Android failures
      var paddingTop = 5
      var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top) + paddingTop
301
302
303
304
305
306
307
308
309
      $content.one('scroll', function () {
        assert.ok($(element).hasClass('active'), 'target:' + target + ', element' + element)
        deferred.resolve()
      })
      $content.scrollTop(scrollHeight)
      return deferred.promise()
    }

    $.when(testElementIsActiveAfterScroll('#a-1', '#div-1'))
XhmikosR's avatar
XhmikosR committed
310
311
312
313
314
315
      .then(function () {
        return testElementIsActiveAfterScroll('#a-2', '#div-2')
      })
      .then(function () {
        done()
      })
316
317
  })

318
  QUnit.test('should add the active class correctly when there are nested elements at 0 scroll offset', function (assert) {
319
    assert.expect(6)
320
321
    var times = 0
    var done = assert.async()
XhmikosR's avatar
XhmikosR committed
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
    var navbarHtml = '<nav id="navigation" class="navbar">' +
      '<ul class="nav">' +
      '<li class="nav-item"><a id="a-1" class="nav-link" href="#div-1">div 1</a>' +
      '<ul class="nav">' +
      '<li class="nav-item"><a id="a-2" class="nav-link" href="#div-2">div 2</a></li>' +
      '</ul>' +
      '</li>' +
      '</ul>' +
      '</nav>'

    var contentHtml = '<div class="content" style="position: absolute; top: 0px; overflow: auto; height: 50px">' +
      '<div id="div-1" style="padding: 0; margin: 0">' +
      '<div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>' +
      '</div>' +
      '</div>'
337
338
339
340
341

    $(navbarHtml).appendTo('#qunit-fixture')

    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
342
343
344
345
      .bootstrapScrollspy({
        offset: 0,
        target: '#navigation'
      })
346

347
    function testActiveElements() {
XhmikosR's avatar
XhmikosR committed
348
349
350
      if (++times > 3) {
        return done()
      }
351
352

      $content.one('scroll', function () {
353
        assert.ok($('#a-1').hasClass('active'), 'nav item for outer element has "active" class')
354
355
356
357
358
359
360
361
362
363
364
365
366
367
        assert.ok($('#a-2').hasClass('active'), 'nav item for inner element has "active" class')
        testActiveElements()
      })

      $content.scrollTop($content.scrollTop() + 10)
    }

    testActiveElements()
  })

  QUnit.test('should add the active class correctly when there are nested elements (nav markup)', function (assert) {
    assert.expect(6)
    var times = 0
    var done = assert.async()
XhmikosR's avatar
XhmikosR committed
368
369
370
371
372
373
374
375
376
377
378
379
380
381
    var navbarHtml = '<nav id="navigation" class="navbar">' +
      '<nav class="nav">' +
      '<a id="a-1" class="nav-link" href="#div-1">div 1</a>' +
      '<nav class="nav">' +
      '<a id="a-2" class="nav-link" href="#div-2">div 2</a>' +
      '</nav>' +
      '</nav>' +
      '</nav>'

    var contentHtml = '<div class="content" style="position: absolute; top: 0px; overflow: auto; height: 50px">' +
      '<div id="div-1" style="padding: 0; margin: 0">' +
      '<div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>' +
      '</div>' +
      '</div>'
382
383
384
385
386

    $(navbarHtml).appendTo('#qunit-fixture')

    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
387
388
389
390
      .bootstrapScrollspy({
        offset: 0,
        target: '#navigation'
      })
391
392

    function testActiveElements() {
XhmikosR's avatar
XhmikosR committed
393
394
395
      if (++times > 3) {
        return done()
      }
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412

      $content.one('scroll', function () {
        assert.ok($('#a-1').hasClass('active'), 'nav item for outer element has "active" class')
        assert.ok($('#a-2').hasClass('active'), 'nav item for inner element has "active" class')
        testActiveElements()
      })

      $content.scrollTop($content.scrollTop() + 10)
    }

    testActiveElements()
  })

  QUnit.test('should add the active class correctly when there are nested elements (nav nav-item markup)', function (assert) {
    assert.expect(6)
    var times = 0
    var done = assert.async()
XhmikosR's avatar
XhmikosR committed
413
414
415
416
417
418
419
420
421
422
423
424
425
426
    var navbarHtml = '<nav id="navigation" class="navbar">' +
      '<ul class="nav">' +
      '<li class="nav-item"><a id="a-1" class="nav-link" href="#div-1">div 1</a></li>' +
      '<ul class="nav">' +
      '<li class="nav-item"><a id="a-2" class="nav-link" href="#div-2">div 2</a></li>' +
      '</ul>' +
      '</ul>' +
      '</nav>'

    var contentHtml = '<div class="content" style="position: absolute; top: 0px; overflow: auto; height: 50px">' +
      '<div id="div-1" style="padding: 0; margin: 0">' +
      '<div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>' +
      '</div>' +
      '</div>'
427
428
429
430
431

    $(navbarHtml).appendTo('#qunit-fixture')

    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
432
433
434
435
      .bootstrapScrollspy({
        offset: 0,
        target: '#navigation'
      })
436
437

    function testActiveElements() {
XhmikosR's avatar
XhmikosR committed
438
439
440
      if (++times > 3) {
        return done()
      }
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457

      $content.one('scroll', function () {
        assert.ok($('#a-1').hasClass('active'), 'nav item for outer element has "active" class')
        assert.ok($('#a-2').hasClass('active'), 'nav item for inner element has "active" class')
        testActiveElements()
      })

      $content.scrollTop($content.scrollTop() + 10)
    }

    testActiveElements()
  })

  QUnit.test('should add the active class correctly when there are nested elements (list-group markup)', function (assert) {
    assert.expect(6)
    var times = 0
    var done = assert.async()
XhmikosR's avatar
XhmikosR committed
458
459
460
461
462
463
464
465
466
467
468
469
470
471
    var navbarHtml = '<nav id="navigation" class="navbar">' +
      '<div class="list-group">' +
      '<a id="a-1" class="list-group-item" href="#div-1">div 1</a>' +
      '<div class="list-group">' +
      '<a id="a-2" class="list-group-item" href="#div-2">div 2</a>' +
      '</div>' +
      '</div>' +
      '</nav>'

    var contentHtml = '<div class="content" style="position: absolute; top: 0px; overflow: auto; height: 50px">' +
      '<div id="div-1" style="padding: 0; margin: 0">' +
      '<div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>' +
      '</div>' +
      '</div>'
472
473
474
475
476

    $(navbarHtml).appendTo('#qunit-fixture')

    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
477
478
479
480
      .bootstrapScrollspy({
        offset: 0,
        target: '#navigation'
      })
481
482

    function testActiveElements() {
XhmikosR's avatar
XhmikosR committed
483
484
485
      if (++times > 3) {
        return done()
      }
486
487
488

      $content.one('scroll', function () {
        assert.ok($('#a-1').hasClass('active'), 'nav item for outer element has "active" class')
489
        assert.ok($('#a-2').hasClass('active'), 'nav item for inner element has "active" class')
490
491
492
493
        testActiveElements()
      })

      $content.scrollTop($content.scrollTop() + 10)
494
495
496
    }

    testActiveElements()
497
498
  })

499
  QUnit.test('should clear selection if above the first section', function (assert) {
500
    assert.expect(3)
501
    var done = assert.async()
502

XhmikosR's avatar
XhmikosR committed
503
504
505
506
507
508
509
510
    var sectionHTML = '<div id="header" style="height: 500px;"></div>' +
        '<nav id="navigation" class="navbar">' +
        '<ul class="navbar-nav">' +
        '<li class="nav-item"><a id="one-link"   class="nav-link active" href="#one">One</a></li>' +
        '<li class="nav-item"><a id="two-link"   class="nav-link" href="#two">Two</a></li>' +
        '<li class="nav-item"><a id="three-link" class="nav-link" href="#three">Three</a></li>' +
        '</ul>' +
        '</nav>'
511
    $(sectionHTML).appendTo('#qunit-fixture')
512

XhmikosR's avatar
XhmikosR committed
513
514
515
516
517
518
519
    var scrollspyHTML = '<div id="content" style="height: 200px; overflow-y: auto;">' +
        '<div id="spacer" style="height: 100px;"/>' +
        '<div id="one" style="height: 100px;"/>' +
        '<div id="two" style="height: 100px;"/>' +
        '<div id="three" style="height: 100px;"/>' +
        '<div id="spacer" style="height: 100px;"/>' +
        '</div>'
520
521
522
523
524
525
526
    var $scrollspy = $(scrollspyHTML).appendTo('#qunit-fixture')

    $scrollspy
      .bootstrapScrollspy({
        target: '#navigation',
        offset: $scrollspy.position().top
      })
527
      .one('scroll', function () {
528
        assert.strictEqual($('.active').length, 1, '"active" class on only one element present')
529
        assert.strictEqual($('.active').is('#two-link'), true, '"active" class on second section')
530
        $scrollspy
531
          .one('scroll', function () {
532
            assert.strictEqual($('.active').length, 0, 'selection cleared')
533
            done()
534
535
536
537
538
539
          })
          .scrollTop(0)
      })
      .scrollTop(201)
  })

540
541
542
543
  QUnit.test('should NOT clear selection if above the first section and first section is at the top', function (assert) {
    assert.expect(4)
    var done = assert.async()

XhmikosR's avatar
XhmikosR committed
544
545
546
547
548
549
550
551
    var sectionHTML = '<div id="header" style="height: 500px;"></div>' +
        '<nav id="navigation" class="navbar">' +
        '<ul class="navbar-nav">' +
        '<li class="nav-item"><a id="one-link"   class="nav-link active" href="#one">One</a></li>' +
        '<li class="nav-item"><a id="two-link"   class="nav-link" href="#two">Two</a></li>' +
        '<li class="nav-item"><a id="three-link" class="nav-link" href="#three">Three</a></li>' +
        '</ul>' +
        '</nav>'
552
553
554
555
556
    $(sectionHTML).appendTo('#qunit-fixture')

    var negativeHeight = -10
    var startOfSectionTwo = 101

XhmikosR's avatar
XhmikosR committed
557
558
559
560
561
562
    var scrollspyHTML = '<div id="content" style="height: 200px; overflow-y: auto;">' +
        '<div id="one" style="height: 100px;"/>' +
        '<div id="two" style="height: 100px;"/>' +
        '<div id="three" style="height: 100px;"/>' +
        '<div id="spacer" style="height: 100px;"/>' +
        '</div>'
563
564
565
566
567
    var $scrollspy = $(scrollspyHTML).appendTo('#qunit-fixture')

    $scrollspy
      .bootstrapScrollspy({
        target: '#navigation',
568
        offset: $scrollspy[0].offsetTop
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
      })
      .one('scroll', function () {
        assert.strictEqual($('.active').length, 1, '"active" class on only one element present')
        assert.strictEqual($('.active').is('#two-link'), true, '"active" class on second section')
        $scrollspy
          .one('scroll', function () {
            assert.strictEqual($('.active').length, 1, '"active" class on only one element present')
            assert.strictEqual($('.active').is('#one-link'), true, '"active" class on first section')
            done()
          })
          .scrollTop(negativeHeight)
      })
      .scrollTop(startOfSectionTwo)
  })

584
585
586
  QUnit.test('should correctly select navigation element on backward scrolling when each target section height is 100%', function (assert) {
    assert.expect(5)
    var navbarHtml =
XhmikosR's avatar
XhmikosR committed
587
588
589
590
591
592
593
594
595
      '<nav class="navbar">' +
      '<ul class="nav">' +
      '<li class="nav-item"><a id="li-100-1" class="nav-link" href="#div-100-1">div 1</a></li>' +
      '<li class="nav-item"><a id="li-100-2" class="nav-link" href="#div-100-2">div 2</a></li>' +
      '<li class="nav-item"><a id="li-100-3" class="nav-link" href="#div-100-3">div 3</a></li>' +
      '<li class="nav-item"><a id="li-100-4" class="nav-link" href="#div-100-4">div 4</a></li>' +
      '<li class="nav-item"><a id="li-100-5" class="nav-link" href="#div-100-5">div 5</a></li>' +
      '</ul>' +
      '</nav>'
596
    var contentHtml =
XhmikosR's avatar
XhmikosR committed
597
598
599
600
601
602
603
        '<div class="content" style="position: relative; overflow: auto; height: 100px">' +
      '<div id="div-100-1" style="position: relative; height: 100%; padding: 0; margin: 0">div 1</div>' +
      '<div id="div-100-2" style="position: relative; height: 100%; padding: 0; margin: 0">div 2</div>' +
      '<div id="div-100-3" style="position: relative; height: 100%; padding: 0; margin: 0">div 3</div>' +
      '<div id="div-100-4" style="position: relative; height: 100%; padding: 0; margin: 0">div 4</div>' +
      '<div id="div-100-5" style="position: relative; height: 100%; padding: 0; margin: 0">div 5</div>' +
      '</div>'
604
605
606
607

    $(navbarHtml).appendTo('#qunit-fixture')
    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
608
609
610
611
      .bootstrapScrollspy({
        offset: 0,
        target: '.navbar'
      })
612
613
614

    var testElementIsActiveAfterScroll = function (element, target) {
      var deferred = $.Deferred()
Johann-S's avatar
Johann-S committed
615
616
617
      // add top padding to fix Chrome on Android failures
      var paddingTop = 5
      var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top) + paddingTop
618
619
620
621
622
623
624
625
      $content.one('scroll', function () {
        assert.ok($(element).hasClass('active'), 'target:' + target + ', element: ' + element)
        deferred.resolve()
      })
      $content.scrollTop(scrollHeight)
      return deferred.promise()
    }

626
    var done = assert.async()
627
    $.when(testElementIsActiveAfterScroll('#li-100-5', '#div-100-5'))
XhmikosR's avatar
XhmikosR committed
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
      .then(function () {
        return testElementIsActiveAfterScroll('#li-100-4', '#div-100-4')
      })
      .then(function () {
        return testElementIsActiveAfterScroll('#li-100-3', '#div-100-3')
      })
      .then(function () {
        return testElementIsActiveAfterScroll('#li-100-2', '#div-100-2')
      })
      .then(function () {
        return testElementIsActiveAfterScroll('#li-100-1', '#div-100-1')
      })
      .then(function () {
        done()
      })
643
644
  })

645
  QUnit.test('should allow passed in option offset method: offset', function (assert) {
fat's avatar
fat committed
646
    assert.expect(4)
647

fat's avatar
fat committed
648
    var testOffsetMethod = function (type) {
649
      var $navbar = $(
XhmikosR's avatar
XhmikosR committed
650
651
652
653
654
655
656
        '<nav class="navbar"' + (type === 'data' ? ' id="navbar-offset-method-menu"' : '') + '>' +
        '<ul class="nav">' +
        '<li class="nav-item"><a id="li-' + type + 'm-1" class="nav-link" href="#div-' + type + 'm-1">div 1</a></li>' +
        '<li class="nav-item"><a id="li-' + type + 'm-2" class="nav-link" href="#div-' + type + 'm-2">div 2</a></li>' +
        '<li class="nav-item"><a id="li-' + type + 'm-3" class="nav-link" href="#div-' + type + 'm-3">div 3</a></li>' +
        '</ul>' +
        '</nav>'
657
658
      )
      var $content = $(
XhmikosR's avatar
XhmikosR committed
659
660
661
662
663
        '<div class="content"' + (type === 'data' ? ' data-spy="scroll" data-target="#navbar-offset-method-menu" data-offset="0" data-method="offset"' : '') + ' style="position: relative; overflow: auto; height: 100px">' +
        '<div id="div-' + type + 'm-1" style="position: relative; height: 200px; padding: 0; margin: 0">div 1</div>' +
        '<div id="div-' + type + 'm-2" style="position: relative; height: 150px; padding: 0; margin: 0">div 2</div>' +
        '<div id="div-' + type + 'm-3" style="position: relative; height: 250px; padding: 0; margin: 0">div 3</div>' +
        '</div>'
664
      )
665

666
667
      $navbar.appendTo('#qunit-fixture')
      $content.appendTo('#qunit-fixture')
668

669
      if (type === 'js') {
XhmikosR's avatar
XhmikosR committed
670
671
672
673
674
675
        $content.bootstrapScrollspy({
          target: '.navbar',
          offset: 0,
          method: 'offset'
        })
      } else if (type === 'data') {
676
        window.dispatchEvent(new Event('load'))
677
      }
678

fat's avatar
fat committed
679
      var $target = $('#div-' + type + 'm-2')
680
      var scrollspy = ScrollSpy._getInstance($content[0])
681

682
      assert.ok(scrollspy._offsets[1] === $target.offset().top, 'offset method with ' + type + ' option')
fat's avatar
fat committed
683
      assert.ok(scrollspy._offsets[1] !== $target.position().top, 'position method with ' + type + ' option')
684
685
      $navbar.remove()
      $content.remove()
fat's avatar
fat committed
686
    }
687

688
689
    testOffsetMethod('js')
    testOffsetMethod('data')
fat's avatar
fat committed
690
  })
691

fat's avatar
fat committed
692
693
  QUnit.test('should allow passed in option offset method: position', function (assert) {
    assert.expect(4)
694

fat's avatar
fat committed
695
    var testOffsetMethod = function (type) {
696
      var $navbar = $(
XhmikosR's avatar
XhmikosR committed
697
698
699
700
701
702
703
        '<nav class="navbar"' + (type === 'data' ? ' id="navbar-offset-method-menu"' : '') + '>' +
        '<ul class="nav">' +
        '<li class="nav-item"><a class="nav-link" id="li-' + type + 'm-1" href="#div-' + type + 'm-1">div 1</a></li>' +
        '<li class="nav-item"><a class="nav-link" id="li-' + type + 'm-2" href="#div-' + type + 'm-2">div 2</a></li>' +
        '<li class="nav-item"><a class="nav-link" id="li-' + type + 'm-3" href="#div-' + type + 'm-3">div 3</a></li>' +
        '</ul>' +
        '</nav>'
704
705
      )
      var $content = $(
XhmikosR's avatar
XhmikosR committed
706
707
708
709
710
        '<div class="content"' + (type === 'data' ? ' data-spy="scroll" data-target="#navbar-offset-method-menu" data-offset="0" data-method="position"' : '') + ' style="position: relative; overflow: auto; height: 100px">' +
        '<div id="div-' + type + 'm-1" style="position: relative; height: 200px; padding: 0; margin: 0">div 1</div>' +
        '<div id="div-' + type + 'm-2" style="position: relative; height: 150px; padding: 0; margin: 0">div 2</div>' +
        '<div id="div-' + type + 'm-3" style="position: relative; height: 250px; padding: 0; margin: 0">div 3</div>' +
        '</div>'
711
      )
712

713
714
      $navbar.appendTo('#qunit-fixture')
      $content.appendTo('#qunit-fixture')
fat's avatar
fat committed
715

XhmikosR's avatar
XhmikosR committed
716
717
718
719
720
721
722
      if (type === 'js') {
        $content.bootstrapScrollspy({
          target: '.navbar',
          offset: 0,
          method: 'position'
        })
      } else if (type === 'data') {
723
        window.dispatchEvent(new Event('load'))
XhmikosR's avatar
XhmikosR committed
724
      }
fat's avatar
fat committed
725
726

      var $target = $('#div-' + type + 'm-2')
727
      var scrollspy = ScrollSpy._getInstance($content[0])
fat's avatar
fat committed
728

729
      assert.ok(scrollspy._offsets[1] !== $target.offset().top, 'offset method with ' + type + ' option')
fat's avatar
fat committed
730
      assert.ok(scrollspy._offsets[1] === $target.position().top, 'position method with ' + type + ' option')
731
732
      $navbar.remove()
      $content.remove()
fat's avatar
fat committed
733
734
    }

735
736
    testOffsetMethod('js')
    testOffsetMethod('data')
fat's avatar
fat committed
737
  })
738
739
740
741
742

  QUnit.test('should return the version', function (assert) {
    assert.expect(1)
    assert.strictEqual(typeof ScrollSpy.VERSION, 'string')
  })
743
})