scrollspy.js 26.7 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()
fat's avatar
fat committed
206
      var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top)
207
      $content.one('scroll', function () {
208
        assert.ok($(element).hasClass('active'), 'target:' + target + ', element' + element)
209
210
211
212
213
214
        deferred.resolve()
      })
      $content.scrollTop(scrollHeight)
      return deferred.promise()
    }

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

224
225
226
  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
227
228
229
230
231
232
      '<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>'
233
    var contentHtml =
XhmikosR's avatar
XhmikosR committed
234
235
236
237
        '<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>'
238
239
240
241

    $(navbarHtml).appendTo('#qunit-fixture')
    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
242
243
244
245
      .bootstrapScrollspy({
        offset: 0,
        target: '.navbar'
      })
246
247
248
249
250
251
252
253
254
255
256
257
258
259

    var done = assert.async()
    var testElementIsActiveAfterScroll = function (element, target) {
      var deferred = $.Deferred()
      var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top)
      $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
260
261
262
263
264
265
      .then(function () {
        return testElementIsActiveAfterScroll('#a-2', '#div-2')
      })
      .then(function () {
        done()
      })
266
267
268
269
270
  })

  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
271
272
273
274
275
276
      '<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>'
277
    var contentHtml =
XhmikosR's avatar
XhmikosR committed
278
279
280
281
        '<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>'
282
283
284
285

    $(navbarHtml).appendTo('#qunit-fixture')
    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
286
287
288
289
      .bootstrapScrollspy({
        offset: 0,
        target: '.navbar'
      })
290
291
292
293
294
295
296
297
298
299
300
301
302
303

    var done = assert.async()
    var testElementIsActiveAfterScroll = function (element, target) {
      var deferred = $.Deferred()
      var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top)
      $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
304
305
306
307
308
309
      .then(function () {
        return testElementIsActiveAfterScroll('#a-2', '#div-2')
      })
      .then(function () {
        done()
      })
310
311
  })

312
  QUnit.test('should add the active class correctly when there are nested elements at 0 scroll offset', function (assert) {
313
    assert.expect(6)
314
315
    var times = 0
    var done = assert.async()
XhmikosR's avatar
XhmikosR committed
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
    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>'
331
332
333
334
335

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

    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
336
337
338
339
      .bootstrapScrollspy({
        offset: 0,
        target: '#navigation'
      })
340

341
    function testActiveElements() {
XhmikosR's avatar
XhmikosR committed
342
343
344
      if (++times > 3) {
        return done()
      }
345
346

      $content.one('scroll', function () {
347
        assert.ok($('#a-1').hasClass('active'), 'nav item for outer element has "active" class')
348
349
350
351
352
353
354
355
356
357
358
359
360
361
        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
362
363
364
365
366
367
368
369
370
371
372
373
374
375
    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>'
376
377
378
379
380

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

    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
381
382
383
384
      .bootstrapScrollspy({
        offset: 0,
        target: '#navigation'
      })
385
386

    function testActiveElements() {
XhmikosR's avatar
XhmikosR committed
387
388
389
      if (++times > 3) {
        return done()
      }
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406

      $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
407
408
409
410
411
412
413
414
415
416
417
418
419
420
    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>'
421
422
423
424
425

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

    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
426
427
428
429
      .bootstrapScrollspy({
        offset: 0,
        target: '#navigation'
      })
430
431

    function testActiveElements() {
XhmikosR's avatar
XhmikosR committed
432
433
434
      if (++times > 3) {
        return done()
      }
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451

      $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
452
453
454
455
456
457
458
459
460
461
462
463
464
465
    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>'
466
467
468
469
470

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

    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
471
472
473
474
      .bootstrapScrollspy({
        offset: 0,
        target: '#navigation'
      })
475
476

    function testActiveElements() {
XhmikosR's avatar
XhmikosR committed
477
478
479
      if (++times > 3) {
        return done()
      }
480
481
482

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

      $content.scrollTop($content.scrollTop() + 10)
488
489
490
    }

    testActiveElements()
491
492
  })

493
  QUnit.test('should clear selection if above the first section', function (assert) {
494
    assert.expect(3)
495
    var done = assert.async()
496

XhmikosR's avatar
XhmikosR committed
497
498
499
500
501
502
503
504
    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>'
505
    $(sectionHTML).appendTo('#qunit-fixture')
506

XhmikosR's avatar
XhmikosR committed
507
508
509
510
511
512
513
    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>'
514
515
516
517
518
519
520
    var $scrollspy = $(scrollspyHTML).appendTo('#qunit-fixture')

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

534
535
536
537
  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
538
539
540
541
542
543
544
545
    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>'
546
547
548
549
550
    $(sectionHTML).appendTo('#qunit-fixture')

    var negativeHeight = -10
    var startOfSectionTwo = 101

XhmikosR's avatar
XhmikosR committed
551
552
553
554
555
556
    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>'
557
558
559
560
561
    var $scrollspy = $(scrollspyHTML).appendTo('#qunit-fixture')

    $scrollspy
      .bootstrapScrollspy({
        target: '#navigation',
562
        offset: $scrollspy[0].offsetTop
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
      })
      .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)
  })

578
579
580
  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
581
582
583
584
585
586
587
588
589
      '<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>'
590
    var contentHtml =
XhmikosR's avatar
XhmikosR committed
591
592
593
594
595
596
597
        '<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>'
598
599
600
601

    $(navbarHtml).appendTo('#qunit-fixture')
    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
XhmikosR's avatar
XhmikosR committed
602
603
604
605
      .bootstrapScrollspy({
        offset: 0,
        target: '.navbar'
      })
606
607
608
609
610
611
612
613
614
615
616
617

    var testElementIsActiveAfterScroll = function (element, target) {
      var deferred = $.Deferred()
      var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top)
      $content.one('scroll', function () {
        assert.ok($(element).hasClass('active'), 'target:' + target + ', element: ' + element)
        deferred.resolve()
      })
      $content.scrollTop(scrollHeight)
      return deferred.promise()
    }

618
    var done = assert.async()
619
    $.when(testElementIsActiveAfterScroll('#li-100-5', '#div-100-5'))
XhmikosR's avatar
XhmikosR committed
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
      .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()
      })
635
636
  })

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

fat's avatar
fat committed
640
    var testOffsetMethod = function (type) {
641
      var $navbar = $(
XhmikosR's avatar
XhmikosR committed
642
643
644
645
646
647
648
        '<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>'
649
650
      )
      var $content = $(
XhmikosR's avatar
XhmikosR committed
651
652
653
654
655
        '<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>'
656
      )
657

658
659
      $navbar.appendTo('#qunit-fixture')
      $content.appendTo('#qunit-fixture')
660

661
      if (type === 'js') {
XhmikosR's avatar
XhmikosR committed
662
663
664
665
666
667
        $content.bootstrapScrollspy({
          target: '.navbar',
          offset: 0,
          method: 'offset'
        })
      } else if (type === 'data') {
668
        window.dispatchEvent(new Event('load'))
669
      }
670

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

674
      assert.ok(scrollspy._offsets[1] === $target.offset().top, 'offset method with ' + type + ' option')
fat's avatar
fat committed
675
      assert.ok(scrollspy._offsets[1] !== $target.position().top, 'position method with ' + type + ' option')
676
677
      $navbar.remove()
      $content.remove()
fat's avatar
fat committed
678
    }
679

680
681
    testOffsetMethod('js')
    testOffsetMethod('data')
fat's avatar
fat committed
682
  })
683

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

fat's avatar
fat committed
687
    var testOffsetMethod = function (type) {
688
      var $navbar = $(
XhmikosR's avatar
XhmikosR committed
689
690
691
692
693
694
695
        '<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>'
696
697
      )
      var $content = $(
XhmikosR's avatar
XhmikosR committed
698
699
700
701
702
        '<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>'
703
      )
704

705
706
      $navbar.appendTo('#qunit-fixture')
      $content.appendTo('#qunit-fixture')
fat's avatar
fat committed
707

XhmikosR's avatar
XhmikosR committed
708
709
710
711
712
713
714
      if (type === 'js') {
        $content.bootstrapScrollspy({
          target: '.navbar',
          offset: 0,
          method: 'position'
        })
      } else if (type === 'data') {
715
        window.dispatchEvent(new Event('load'))
XhmikosR's avatar
XhmikosR committed
716
      }
fat's avatar
fat committed
717
718

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

721
      assert.ok(scrollspy._offsets[1] !== $target.offset().top, 'offset method with ' + type + ' option')
fat's avatar
fat committed
722
      assert.ok(scrollspy._offsets[1] === $target.position().top, 'position method with ' + type + ' option')
723
724
      $navbar.remove()
      $content.remove()
fat's avatar
fat committed
725
726
    }

727
728
    testOffsetMethod('js')
    testOffsetMethod('data')
fat's avatar
fat committed
729
  })
730
731
732
733
734

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