From a70da16f6fb8c665f22b78b49a1dff998f1da8a7 Mon Sep 17 00:00:00 2001
From: Steven Bassett <steven.j.bassett@gmail.com>
Date: Thu, 8 May 2014 20:19:12 -0700
Subject: [PATCH] Adds aria described by to tooltip plugin for accessibility

Generates a unique id for tooltip and adds [aria-describedby] to the element
it is called on. Resolves issue #13480

- set up test
- linted the code styles
- passed the tests
- integrated feedback from @cvrebert
---
 dist/js/bootstrap.js     | 12 ++++++++++++
 js/tests/unit/tooltip.js | 30 ++++++++++++++++++++++++++++++
 js/tooltip.js            | 12 ++++++++++++
 3 files changed, 54 insertions(+)

diff --git a/dist/js/bootstrap.js b/dist/js/bootstrap.js
index ea770c9567..eb7f7ca0bc 100644
--- a/dist/js/bootstrap.js
+++ b/dist/js/bootstrap.js
@@ -1233,7 +1233,11 @@ if (typeof jQuery === 'undefined') { throw new Error('Bootstrap\'s JavaScript re
 
       var $tip = this.tip()
 
+      var tipId = this.getUID(this.type)
+
       this.setContent()
+      $tip.attr('id', tipId)
+      this.$element.attr('aria-describedby', tipId)
 
       if (this.options.animation) $tip.addClass('fade')
 
@@ -1356,6 +1360,8 @@ if (typeof jQuery === 'undefined') { throw new Error('Bootstrap\'s JavaScript re
     var $tip = this.tip()
     var e    = $.Event('hide.bs.' + this.type)
 
+    this.$element.removeAttr('aria-describedby')
+
     function complete() {
       if (that.hoverState != 'in') $tip.detach()
       that.$element.trigger('hidden.bs.' + that.type)
@@ -1447,6 +1453,12 @@ if (typeof jQuery === 'undefined') { throw new Error('Bootstrap\'s JavaScript re
     return title
   }
 
+  Tooltip.prototype.getUID = function (prefix) {
+    do prefix += ~~(Math.random() * 1000000)
+    while (document.getElementById(prefix))
+    return prefix
+  }
+
   Tooltip.prototype.tip = function () {
     return this.$tip = this.$tip || $(this.options.template)
   }
diff --git a/js/tests/unit/tooltip.js b/js/tests/unit/tooltip.js
index e579a9ef7f..e95b91cbb1 100644
--- a/js/tests/unit/tooltip.js
+++ b/js/tests/unit/tooltip.js
@@ -41,6 +41,36 @@ $(function () {
     equal(tooltip.attr('data-original-title'), 'Another tooltip', 'original title preserved in data attribute')
   })
 
+  test('should add set set aria describedby to the element called on show', function() {
+    var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>').bootstrapTooltip()
+      .appendTo('#qunit-fixture')
+      .bootstrapTooltip('show')
+    ok(tooltip.attr('aria-describedby'), 'has the right attributes')
+    var id = $('.tooltip').attr('id')
+
+    ok($('#' + id).length == 1, 'has a unique id')
+    ok($('.tooltip').attr('aria-describedby') === tooltip.attr('id'), 'they match!')
+    ok(tooltip.attr('aria-describedby') !== undefined, 'has the right attributes')
+  })
+
+  test('should remove the aria-describedby attributes on hide', function() {
+    var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>').bootstrapTooltip()
+      .appendTo('#qunit-fixture')
+      .bootstrapTooltip('show')
+    ok(tooltip.attr('aria-describedby'), 'has the right attributes')
+    tooltip.bootstrapTooltip('hide')
+    ok(!tooltip.attr('aria-describedby'), 'removed the attributes on hide')
+  })
+
+  test('should assign a unique id tooltip element', function () {
+    $('<a href="#" rel="tooltip" title="Another tooltip"></a>')
+      .appendTo('#qunit-fixture')
+      .bootstrapTooltip('show'),
+    id = $('.tooltip').attr('id')
+
+    ok( $('#' + id).length == 1 && id.indexOf('tooltip') === 0, 'generated prefixed and unique tooltip id')
+  })
+
   test('should place tooltips relative to placement option', function () {
     $.support.transition = false
     var tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"></a>')
diff --git a/js/tooltip.js b/js/tooltip.js
index d985f96e34..936424830a 100644
--- a/js/tooltip.js
+++ b/js/tooltip.js
@@ -150,7 +150,11 @@
 
       var $tip = this.tip()
 
+      var tipId = this.getUID(this.type)
+
       this.setContent()
+      $tip.attr('id', tipId)
+      this.$element.attr('aria-describedby', tipId)
 
       if (this.options.animation) $tip.addClass('fade')
 
@@ -273,6 +277,8 @@
     var $tip = this.tip()
     var e    = $.Event('hide.bs.' + this.type)
 
+    this.$element.removeAttr('aria-describedby')
+
     function complete() {
       if (that.hoverState != 'in') $tip.detach()
       that.$element.trigger('hidden.bs.' + that.type)
@@ -364,6 +370,12 @@
     return title
   }
 
+  Tooltip.prototype.getUID = function (prefix) {
+    do prefix += ~~(Math.random() * 1000000)
+    while (document.getElementById(prefix))
+    return prefix
+  }
+
   Tooltip.prototype.tip = function () {
     return this.$tip = this.$tip || $(this.options.template)
   }
-- 
GitLab