Skip to content

Commit

Permalink
Further tabs work and testing
Browse files Browse the repository at this point in the history
  • Loading branch information
ediblecode committed Sep 23, 2016
1 parent c949d34 commit c829041
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 47 deletions.
43 changes: 32 additions & 11 deletions src/javascripts/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const KeyCodes = {

// Generate unique id amongst tabs
// See http://stackoverflow.com/a/20302361
var uid = function (i) {
const uid = function (i) {
return function () {
return "tabs-" + (++i);
};
Expand All @@ -38,7 +38,8 @@ var uid = function (i) {

/**
* @class Tabs
* Tabs description here
* Follows W3 design for tab panels with aria attributes.
* @link https://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#tabpanel
*/
export default class Tabs {

Expand Down Expand Up @@ -72,7 +73,7 @@ export default class Tabs {
});

this._bindEvents();
this.activate(0);
this.activate(0, false);
}

/// Gets the 0-based index of the currently selected tab
Expand All @@ -85,8 +86,10 @@ export default class Tabs {

/// Activates a tab with the given index
/// @param {integer} index The index of the tab to activate
activate(index: number) {
this._getTabs()
/// @param {boolean} focus Whether to give focus to the active tab btn
activate(index: number, focus:?boolean = true) {
var $selectedTabBtn =
this._getTabs()
.removeClass(this.options.tabActiveClass)
.find(`.${ this.options.tabButtonClass }`)
.attr("aria-expanded", false)
Expand All @@ -95,10 +98,13 @@ export default class Tabs {
.eq(index)
.addClass(this.options.tabActiveClass)
.find(`.${ this.options.tabButtonClass }`)
.focus()
.attr("aria-expanded", true)
.attr("aria-selected", true);

if(focus === true) {
$selectedTabBtn.focus();
}

this._getTabPanes()
.removeClass(this.options.tabPaneActiveClass)
.attr("aria-hidden", true)
Expand All @@ -107,18 +113,22 @@ export default class Tabs {
.attr("aria-hidden", false);
}

/// Activates the next tab
/// Activates the next tab, or the first we're at the end
next() {
var currentIndex = this.getCurrentIndex();
if(currentIndex < this._getTabs().length - 1) {
if(currentIndex === this._getTabs().length - 1) {
this.first();
} else {
this.activate(currentIndex + 1);
}
}

/// Activates the previous tab
previous() {
/// Activates the previous tab, or the last tab if we're at the start
previous(loop) {
var currentIndex = this.getCurrentIndex();
if(currentIndex > 0) {
if(currentIndex === 0) {
this.last();
} else {
this.activate(currentIndex - 1);
}
}
Expand Down Expand Up @@ -155,6 +165,17 @@ export default class Tabs {
this.activate(index);
});

// Focus the current tab btn on a ctrl+up or ctrl+left when in a tab pane
this.$el.on("keydown", `.${ this.options.tabPaneClass }`, e => {
if($.inArray(e.which, [KeyCodes.UpArrow, KeyCodes.LeftArrow]) > -1 && e.ctrlKey) {
e.preventDefault();
e.stopPropagation();

var tabId = $(e.currentTarget).attr("aria-labelledby");
$(`#${ tabId }`).focus();
}
});

// Enable keyboard control of the tabs
this.$el.on("keydown", `.${ this.options.tabButtonClass }`, e => {
switch(e.which)
Expand Down
148 changes: 113 additions & 35 deletions test/unit/tabs.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
const should = require("should"),
jsdom = require("jsdom");

const KeyCodes = {
Enter: 13,
Space: 32,
End: 35,
Home: 36,
LeftArrow: 37,
UpArrow: 38,
RightArrow: 39,
DownArrow: 40
};


describe("Tabs", function() {

var $,
Expand Down Expand Up @@ -76,39 +88,19 @@ describe("Tabs", function() {
$tabs[0].should.equal($el[0], "collection contains element");
});

it("tab can be activated by index", function() {
var $el = $(tabsHTML).tabs();
$el.tabs("activate", 1);
($el.tabs("getCurrentIndex")).should.equal(1);
});

it("last tab can be activated", function() {
var $el = $(tabsHTML).tabs();
$el.tabs("last");
($el.tabs("getCurrentIndex")).should.equal(2);
});

it("first tab can be activated", function() {
it("getter can be called", function() {
var $el = $(tabsHTML).tabs();
$el.tabs("last").tabs("first");
($el.tabs("getCurrentIndex")).should.equal(0);
});

it("next tab can be activated", function() {
var $el = $(tabsHTML).tabs();
$el.tabs("next");
($el.tabs("getCurrentIndex")).should.equal(1);
});

it("previous tab can be activated", function() {
it("method can be called", function() {
var $el = $(tabsHTML).tabs();
$el.tabs("last").tabs("previous");
$el.tabs("activate", 1);
($el.tabs("getCurrentIndex")).should.equal(1);
});
});


describe("Tabs", function() {
describe("Initialization", function() {

it("have defaults", function() {
Tabs.should.have.property("defaults");
Expand All @@ -118,6 +110,10 @@ describe("Tabs", function() {
should.throws(() => new Tabs);
});

});

describe("Tab selection", function() {

it("first tab selected by default", function() {
var $el = $(tabsHTML);
var t = new Tabs($el);
Expand All @@ -130,29 +126,111 @@ describe("Tabs", function() {
t.activate(1);
t.getCurrentIndex().should.equal(1);
});

it("last tab can be activated", function() {
var $el = $(tabsHTML);
var t = new Tabs($el);
t.last();
(t.getCurrentIndex()).should.equal(2);
});

it("first tab can be activated", function() {
var $el = $(tabsHTML);
var t = new Tabs($el);
t.last();
t.first();
(t.getCurrentIndex()).should.equal(0);
});

it("next tab can be activated", function() {
var $el = $(tabsHTML);
var t = new Tabs($el);
t.next();
(t.getCurrentIndex()).should.equal(1);
});

it("next tab activates first from last", function() {
var $el = $(tabsHTML);
var t = new Tabs($el);
t.last();
t.next();
(t.getCurrentIndex()).should.equal(0);
});

it("previous tab can be activated", function() {
var $el = $(tabsHTML);
var t = new Tabs($el);
t.last();
t.previous();
(t.getCurrentIndex()).should.equal(1);
});

it("previous tab activates last from first", function() {
var $el = $(tabsHTML);
var t = new Tabs($el);
t.previous();
(t.getCurrentIndex()).should.equal(2);
});
});


describe("Accessibility", function() {

it("active tab is aria-expanded", function() {
var $tabs = $(tabsHTML);
$tabs.tabs();
$("[role='tab']:eq(0)", $tabs).attr("aria-expanded").should.equal("true");
$("[role='tab']:eq(0)", $tabs).attr("aria-selected").should.equal("true");
it("active tab is aria-selected", function() {
var $el = $(tabsHTML);
var t = new Tabs($el);
$("[role='tab']:eq(0)", $el).attr("aria-expanded").should.equal("true");
$("[role='tab']:eq(0)", $el).attr("aria-selected").should.equal("true");
});

it("inactive tabs are not aria-expanded", function() {
var $tabs = $(tabsHTML);
$tabs.tabs();
$("[role='tab']:gt(0)", $tabs).attr("aria-expanded").should.equal("false");
$("[role='tab']:gt(0)", $tabs).attr("aria-selected").should.equal("false");
it("inactive tabs are not aria-selected", function() {
var $el = $(tabsHTML);
var t = new Tabs($el);
$("[role='tab']:gt(0)", $el).attr("aria-expanded").should.equal("false");
$("[role='tab']:gt(0)", $el).attr("aria-selected").should.equal("false");
});

});

describe("Keyboard", function() {
describe("Keyboard control", function() {

it("left/up keydown on tab selects previous tab", function() {
var $el = $(tabsHTML);
var t = new Tabs($el);

t.getCurrentIndex().should.equal(0);

$("[role='tab']:eq(0)", $el)
.focus()
.trigger($.Event("keydown", { which: KeyCodes.LeftArrow } ));

t.getCurrentIndex().should.equal(2);

$("[role='tab']:eq(2)", $el)
.focus()
.trigger($.Event("keydown", { which: KeyCodes.UpArrow } ));

t.getCurrentIndex().should.equal(1);
});

it("down/right keydown on tab selects next tab", function() {
var $el = $(tabsHTML);
var t = new Tabs($el);

t.getCurrentIndex().should.equal(0);

$("[role='tab']:eq(0)", $el)
.focus()
.trigger($.Event("keydown", { which: KeyCodes.RightArrow } ));

t.getCurrentIndex().should.equal(1);

$("[role='tab']:eq(1)", $el)
.focus()
.trigger($.Event("keydown", { which: KeyCodes.DownArrow } ));

t.getCurrentIndex().should.equal(2);
});

});
});
12 changes: 11 additions & 1 deletion web/server/views/components/tabs.njk
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,20 @@
<section id="basics">
<h2>Basics</h2>

<ul>
<li>ARIA attributes are applied via JS</li>
<li>Keyboard controls are enabled:
<ul>
<li>Left/up and down/right changes the selected tab</li>
<li>Home/end moves to first/last tab</li>
</ul>
</li>
</ul>

{% set tabsContent = [
{
title: "Tab one",
content: "<h2>Tab one content</h2><p>This is the content for tab 1</p>"
content: "<h2>Tab one content</h2><p>This is the content for tab 1<a href=\"#\">A link</a></p>"
},
{
title: "Tab two",
Expand Down

0 comments on commit c829041

Please sign in to comment.