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

4
  QUnit.module('scrollspy plugin')
5

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

11
  QUnit.module('scrollspy', {
12
    beforeEach: function () {
13
14
15
      // 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()
    },
16
    afterEach: function () {
17
18
19
20
21
      $.fn.scrollspy = $.fn.bootstrapScrollspy
      delete $.fn.bootstrapScrollspy
    }
  })

22
  QUnit.test('should provide no conflict', function (assert) {
23
    assert.expect(1)
24
    assert.strictEqual($.fn.scrollspy, undefined, 'scrollspy was set back to undefined (org value)')
25
26
  })

27
28
29
30
31
32
33
34
35
36
37
38
  QUnit.test('should throw explicit error on undefined method', function (assert) {
    assert.expect(1)
    var $el = $('<div/>')
    $el.bootstrapScrollspy()
    try {
      $el.bootstrapScrollspy('noMethod')
    }
    catch (err) {
      assert.strictEqual(err.message, 'No method named "noMethod"')
    }
  })

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

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

Heinrich Fenkart's avatar
Heinrich Fenkart committed
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    var sectionHTML = '<div id="root" class="active">'
        + '<div class="topbar">'
        + '<div class="topbar-inner">'
        + '<div class="container" id="ss-target">'
        + '<ul class="nav">'
        + '<li><a href="#masthead">Overview</a></li>'
        + '<li><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
77
    var $section = $(sectionHTML).appendTo('#qunit-fixture')
78
79

    var $scrollspy = $section
80
81
82
      .show()
      .find('#scrollspy-example')
      .bootstrapScrollspy({ target: '#ss-target' })
83

84
    $scrollspy.on('scroll.bs.scrollspy', function () {
85
      assert.ok($section.hasClass('active'), '"active" class still on root node')
86
      done()
87
    })
88
89

    $scrollspy.scrollTop(350)
90
91
  })

fat's avatar
fat committed
92
93
94
95
96
97
98
99
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
126
  QUnit.test('should only switch "active" class on current target specified w element', function (assert) {
    assert.expect(1)
    var done = assert.async()

    var sectionHTML = '<div id="root" class="active">'
        + '<div class="topbar">'
        + '<div class="topbar-inner">'
        + '<div class="container" id="ss-target">'
        + '<ul class="nav">'
        + '<li><a href="#masthead">Overview</a></li>'
        + '<li><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>'
    var $section = $(sectionHTML).appendTo('#qunit-fixture')

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

    $scrollspy.on('scroll.bs.scrollspy', function () {
      assert.ok($section.hasClass('active'), '"active" class still on root node')
      done()
    })

    $scrollspy.scrollTop(350)
  })

137
  QUnit.test('should correctly select middle navigation option when large offset is used', function (assert) {
138
    assert.expect(3)
139
    var done = assert.async()
140

Heinrich Fenkart's avatar
Heinrich Fenkart committed
141
142
143
    var sectionHTML = '<div id="header" style="height: 500px;"></div>'
        + '<nav id="navigation" class="navbar">'
        + '<ul class="nav navbar-nav">'
144
145
146
        + '<li class="active"><a class="nav-link" id="one-link" href="#one">One</a></li>'
        + '<li><a class="nav-link" id="two-link" href="#two">Two</a></li>'
        + '<li><a class="nav-link" id="three-link" href="#three">Three</a></li>'
Heinrich Fenkart's avatar
Heinrich Fenkart committed
147
148
149
150
151
152
153
        + '</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
154
    var $section = $(sectionHTML).appendTo('#qunit-fixture')
155
    var $scrollspy = $section
156
157
      .show()
      .filter('#content')
158

159
160
161
    $scrollspy.bootstrapScrollspy({ target: '#navigation', offset: $scrollspy.position().top })

    $scrollspy.on('scroll.bs.scrollspy', function () {
162
163
164
      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')
165
      done()
166
    })
167
168

    $scrollspy.scrollTop(550)
Julian Thilo's avatar
Julian Thilo committed
169
  })
170

171
  QUnit.test('should add the active class to the correct element', function (assert) {
172
    assert.expect(2)
173
    var navbarHtml =
174
        '<nav class="navbar">'
fat's avatar
fat committed
175
      + '<ul class="nav">'
176
177
      + '<li><a class="nav-link" id="a-1" href="#div-1">div 1</a></li>'
      + '<li><a class="nav-link" id="a-2" href="#div-2">div 2</a></li>'
fat's avatar
fat committed
178
      + '</ul>'
179
      + '</nav>'
180
    var contentHtml =
fat's avatar
fat committed
181
182
183
184
        '<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>'
185
186
187
188

    $(navbarHtml).appendTo('#qunit-fixture')
    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
Mark Otto's avatar
Mark Otto committed
189
      .bootstrapScrollspy({ offset: 0, target: '.navbar' })
190
191
192

    var testElementIsActiveAfterScroll = function (element, target) {
      var deferred = $.Deferred()
fat's avatar
fat committed
193
      var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top)
194
      var done = assert.async()
195
      $content.one('scroll', function () {
196
        assert.ok($(element).hasClass('active'), 'target:' + target + ', element' + element)
197
        done()
198
199
200
201
202
203
        deferred.resolve()
      })
      $content.scrollTop(scrollHeight)
      return deferred.promise()
    }

204
205
    $.when(testElementIsActiveAfterScroll('#a-1', '#div-1'))
      .then(function () { return testElementIsActiveAfterScroll('#a-2', '#div-2') })
206
207
  })

208
  QUnit.test('should add the active class correctly when there are nested elements at 0 scroll offset', function (assert) {
209
    assert.expect(6)
210
211
212
213
    var times = 0
    var done = assert.async()
    var navbarHtml = '<nav id="navigation" class="navbar">'
      + '<ul class="nav">'
214
      + '<li><a id="a-1" class="nav-link" href="#div-1">div 1</a>'
215
      + '<ul>'
216
      + '<li><a id="a-2" class="nav-link" href="#div-2">div 2</a></li>'
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
      + '</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>'

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

    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
      .bootstrapScrollspy({ offset: 0, target: '#navigation' })

    !function testActiveElements() {
      if (++times > 3) return done()

      $content.one('scroll', function () {
238
239
        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')
240
241
242
243
244
245
246
        testActiveElements()
      })

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

247
  QUnit.test('should clear selection if above the first section', function (assert) {
248
    assert.expect(3)
249
    var done = assert.async()
250
251
252
253

    var sectionHTML = '<div id="header" style="height: 500px;"></div>'
        + '<nav id="navigation" class="navbar">'
        + '<ul class="nav navbar-nav">'
254
255
256
        + '<li><a id="one-link"   class="nav-link active" href="#one">One</a></li>'
        + '<li><a id="two-link"   class="nav-link" href="#two">Two</a></li>'
        + '<li><a id="three-link" class="nav-link" href="#three">Three</a></li>'
257
258
        + '</ul>'
        + '</nav>'
259
    $(sectionHTML).appendTo('#qunit-fixture')
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275

    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>'
    var $scrollspy = $(scrollspyHTML).appendTo('#qunit-fixture')

    $scrollspy
      .bootstrapScrollspy({
        target: '#navigation',
        offset: $scrollspy.position().top
      })
      .one('scroll.bs.scrollspy', function () {
276
        assert.strictEqual($('.active').length, 1, '"active" class on only one element present')
277
        assert.strictEqual($('.active').is('#two-link'), true, '"active" class on second section')
278
279
        $scrollspy
          .one('scroll.bs.scrollspy', function () {
280
            assert.strictEqual($('.active').length, 0, 'selection cleared')
281
            done()
282
283
284
285
286
287
          })
          .scrollTop(0)
      })
      .scrollTop(201)
  })

288
289
290
291
292
  QUnit.test('should correctly select navigation element on backward scrolling when each target section height is 100%', function (assert) {
    assert.expect(5)
    var navbarHtml =
        '<nav class="navbar">'
      + '<ul class="nav">'
293
294
295
296
297
      + '<li><a id="li-100-1" class="nav-link" href="#div-100-1">div 1</a></li>'
      + '<li><a id="li-100-2" class="nav-link" href="#div-100-2">div 2</a></li>'
      + '<li><a id="li-100-3" class="nav-link" href="#div-100-3">div 3</a></li>'
      + '<li><a id="li-100-4" class="nav-link" href="#div-100-4">div 4</a></li>'
      + '<li><a id="li-100-5" class="nav-link" href="#div-100-5">div 5</a></li>'
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
      + '</ul>'
      + '</nav>'
    var contentHtml =
        '<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>'

    $(navbarHtml).appendTo('#qunit-fixture')
    var $content = $(contentHtml)
      .appendTo('#qunit-fixture')
      .bootstrapScrollspy({ offset: 0, target: '.navbar' })

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

    $.when(testElementIsActiveAfterScroll('#li-100-5', '#div-100-5'))
      .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') })
  })

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

fat's avatar
fat committed
337
338
339
340
341
    var testOffsetMethod = function (type) {
      var deferred = $.Deferred()
      var navbarHtml =
          '<nav class="navbar"' + (type === 'data' ? ' id="navbar-offset-method-menu"' : '') + '>'
        + '<ul class="nav">'
342
343
344
        + '<li><a id="li-' + type + 'm-1" class="nav-link" href="#div-' + type + 'm-1">div 1</a></li>'
        + '<li><a id="li-' + type + 'm-2" class="nav-link" href="#div-' + type + 'm-2">div 2</a></li>'
        + '<li><a id="li-' + type + 'm-3" class="nav-link" href="#div-' + type + 'm-3">div 3</a></li>'
fat's avatar
fat committed
345
346
347
348
349
350
351
352
        + '</ul>'
        + '</nav>'
      var contentHtml =
          '<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>'
353
354


fat's avatar
fat committed
355
356
357
      $(navbarHtml).appendTo('#qunit-fixture')
      var $content = $(contentHtml)
        .appendTo('#qunit-fixture')
358

fat's avatar
fat committed
359
360
      if (type === 'js') $content.bootstrapScrollspy({ target: '.navbar', offset: 0, method: 'offset' })
      else if (type === 'data') $(window).trigger('load.bs.scrollspy.data-api')
361

fat's avatar
fat committed
362
363
      var $target = $('#div-' + type + 'm-2')
      var scrollspy = $content.data('bs.scrollspy')
364

fat's avatar
fat committed
365
366
      assert.ok(scrollspy._offsets[1] === $target.offset().top, 'offsed method with ' + type + ' option')
      assert.ok(scrollspy._offsets[1] !== $target.position().top, 'position method with ' + type + ' option')
367

fat's avatar
fat committed
368
      deferred.resolve()
369

fat's avatar
fat committed
370
371
      return deferred.promise()
    }
372

fat's avatar
fat committed
373
374
375
    $.when(testOffsetMethod('js'))
      .then(function () { testOffsetMethod('data') })
  })
376

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

fat's avatar
fat committed
380
381
382
383
384
    var testOffsetMethod = function (type) {
      var deferred = $.Deferred()
      var navbarHtml =
          '<nav class="navbar"' + (type === 'data' ? ' id="navbar-offset-method-menu"' : '') + '>'
        + '<ul class="nav">'
385
386
387
        + '<li><a class="nav-link" id="li-' + type + 'm-1" href="#div-' + type + 'm-1">div 1</a></li>'
        + '<li><a class="nav-link" id="li-' + type + 'm-2" href="#div-' + type + 'm-2">div 2</a></li>'
        + '<li><a class="nav-link" id="li-' + type + 'm-3" href="#div-' + type + 'm-3">div 3</a></li>'
fat's avatar
fat committed
388
389
390
391
392
393
394
395
        + '</ul>'
        + '</nav>'
      var contentHtml =
          '<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>'
396
397


fat's avatar
fat committed
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
      $(navbarHtml).appendTo('#qunit-fixture')
      var $content = $(contentHtml)
        .appendTo('#qunit-fixture')

      if (type === 'js') $content.bootstrapScrollspy({ target: '.navbar', offset: 0, method: 'position' })
      else if (type === 'data') $(window).trigger('load.bs.scrollspy.data-api')

      var $target = $('#div-' + type + 'm-2')
      var scrollspy = $content.data('bs.scrollspy')

      assert.ok(scrollspy._offsets[1] !== $target.offset().top, 'offsed method with ' + type + ' option')
      assert.ok(scrollspy._offsets[1] === $target.position().top, 'position method with ' + type + ' option')

      deferred.resolve()

      return deferred.promise()
    }

    $.when(testOffsetMethod('js'))
      .then(function () { testOffsetMethod('data') })
  })
419

420
})