Commit 298f168e by Chris Rodriguez

AC-486 updating tabbed_view to be accessible

parent 7c647e5e
......@@ -4,9 +4,12 @@
define(['jquery',
'underscore',
'backbone',
'common/js/components/views/tabbed_view'
'common/js/components/views/tabbed_view',
'jquery.simulate'
],
function($, _, Backbone, TabbedView) {
var keys = $.simulate.keyCode;
var view,
TestSubview = Backbone.View.extend({
initialize: function (options) {
......@@ -22,7 +25,7 @@
return view.$('.page-content-nav');
},
activeTabPanel = function () {
return view.$('.tabpanel[aria-expanded="true"]');
return view.$('.tabpanel[aria-hidden="false"]');
};
describe('TabbedView component', function () {
......@@ -39,21 +42,10 @@
}],
viewLabel: 'Tabs',
}).render();
// _.defer() is used to make calls to
// jQuery.focus() work in Chrome. _.defer()
// delays the execution of a function until the
// current call stack is clear. That behavior
// will cause tests to fail, so we'll instead
// make _.defer() immediately invoke its
// argument.
spyOn(_, 'defer').and.callFake(function (func) {
func();
});
});
it('can render itself', function () {
expect(view.$el.html()).toContain('<nav class="page-content-nav"');
expect(view.$el.html()).toContain('<div class="page-content-nav"');
});
it('shows its first tab by default', function () {
......@@ -73,66 +65,100 @@
});
it('marks the active tab as selected using aria attributes', function () {
expect(view.$('.nav-item[data-index=0]')).toHaveAttr('aria-expanded', 'true');
expect(view.$('.nav-item[data-index=1]')).toHaveAttr('aria-expanded', 'false');
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
'aria-expanded': 'true',
'aria-selected': 'true',
'tabindex': '0'
});
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
'aria-expanded': 'false',
'aria-selected': 'false',
'tabindex': '-1'
});
view.$('.nav-item[data-index=1]').click();
expect(view.$('.nav-item[data-index=0]')).toHaveAttr('aria-expanded', 'false');
expect(view.$('.nav-item[data-index=1]')).toHaveAttr('aria-expanded', 'true');
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
'aria-expanded': 'false',
'aria-selected': 'false',
'tabindex': '-1'
});
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
'aria-expanded': 'true',
'aria-selected': 'true',
'tabindex': '0'
});
});
it('sets focus for screen readers', function () {
spyOn($.fn, 'focus');
view.$('.nav-item[data-url="test-2"]').click();
expect(view.$('.sr-is-focusable.test-2').focus).toHaveBeenCalled();
it('works with keyboard navigation RIGHT and ENTER', function() {
view.$('.nav-item[data-index=0]').focus();
view.$('.nav-item[data-index=0]')
.simulate("keydown", { keyCode: keys.RIGHT })
.simulate("keydown", { keyCode: keys.ENTER });
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
'aria-expanded': 'false',
'aria-selected': 'false',
'tabindex': '-1'
});
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
'aria-expanded': 'true',
'aria-selected': 'true',
'tabindex': '0'
});
});
describe('history', function() {
beforeEach(function () {
spyOn(Backbone.history, 'navigate').and.callThrough();
view = new TabbedView({
tabs: [{
url: 'test 1',
title: 'Test 1',
view: new TestSubview({text: 'this is test text'})
}, {
url: 'test 2',
title: 'Test 2',
view: new TestSubview({text: 'other text'})
}],
router: new Backbone.Router({
routes: {
'test 1': function () {
view.setActiveTab(0);
},
'test 2': function () {
view.setActiveTab(1);
}
}
})
}).render();
Backbone.history.start();
it('works with keyboard navigation DOWN and ENTER', function() {
view.$('.nav-item[data-index=0]').focus();
view.$('.nav-item[data-index=0]')
.simulate("keydown", { keyCode: keys.DOWN })
.simulate("keydown", { keyCode: keys.ENTER });
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
'aria-expanded': 'false',
'aria-selected': 'false',
'tabindex': '-1'
});
afterEach(function () {
view.router.navigate('');
Backbone.history.stop();
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
'aria-expanded': 'true',
'aria-selected': 'true',
'tabindex': '0'
});
it('updates the page URL on tab switches without adding to browser history', function () {
view.$('.nav-item[data-index=1]').click();
expect(Backbone.history.navigate).toHaveBeenCalledWith(
'test 2',
{replace: true}
);
});
it('works with keyboard navigation LEFT and ENTER', function() {
view.$('.nav-item[data-index=1]').focus();
view.$('.nav-item[data-index=1]')
.simulate("keydown", { keyCode: keys.LEFT })
.simulate("keydown", { keyCode: keys.ENTER });
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
'aria-expanded': 'false',
'aria-selected': 'false',
'tabindex': '-1'
});
it('changes tabs on URL navigation', function () {
expect(view.$('.nav-item.is-active').data('index')).toEqual(0);
Backbone.history.navigate('test 2', {trigger: true});
expect(view.$('.nav-item.is-active').data('index')).toEqual(1);
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
'aria-expanded': 'true',
'aria-selected': 'true',
'tabindex': '0'
});
});
it('works with keyboard navigation UP and ENTER', function() {
view.$('.nav-item[data-index=1]').focus();
view.$('.nav-item[data-index=1]')
.simulate("keydown", { keyCode: keys.UP })
.simulate("keydown", { keyCode: keys.ENTER });
expect(view.$('.nav-item[data-index=1]')).toHaveAttr({
'aria-expanded': 'false',
'aria-selected': 'false',
'tabindex': '-1'
});
expect(view.$('.nav-item[data-index=0]')).toHaveAttr({
'aria-expanded': 'true',
'aria-selected': 'true',
'tabindex': '0'
});
});
});
});
}).call(this, define || RequireJS.define);
<button class="nav-item tab" data-url="<%= url %>" data-index="<%= index %>" is-active="false" aria-expanded="false" aria-controls="<%= tabPanelId %>"><%= title %></button>
<button role="tab" class="nav-item tab" data-url="<%= url %>" data-index="<%= index %>" aria-selected="false" aria-expanded="false" aria-controls="<%= tabPanelId %>" tabindex="-1" id="tab-<%= index %>"><%= title %></button>
<nav class="page-content-nav" aria-label="<%- viewLabel %>"></nav>
<div class="page-content-nav" role="tablist"></div>
<div class="page-content-main">
<div class="tabs"></div>
</div>
<div class="tabpanel is-hidden" id="<%= tabId %>" aria-expanded="false">
<div class="sr-is-focusable <%= tabId %>" tabindex="-1"></div>
</div>
<div role="tabpanel" class="tabpanel is-hidden" id="<%= tabId %>" aria-labelledby="tab-<%= index %>" aria-hidden="true" tabindex="0"></div>
......@@ -826,6 +826,7 @@ class LearnerProfileA11yTest(LearnerProfileTestMixin, WebAppTest):
"ignore": [
'section', # TODO: AC-491
'link-href', # TODO: AC-231
'color-contrast', # TODO: AC-231
],
})
profile_page.display_accomplishments()
......
......@@ -71,9 +71,9 @@ define([
expect(teamsTabView.$('.breadcrumbs').length).toBe(0);
});
it('does not interfere with anchor links to #content', function() {
it('does not interfere with anchor links to #main', function () {
var teamsTabView = createTeamsTabView(this);
teamsTabView.router.navigate('#content', {trigger: true});
teamsTabView.router.navigate('#main', {trigger: true});
expect(teamsTabView.$('.wrapper-msg')).toHaveClass('is-hidden');
});
......
......@@ -68,7 +68,7 @@
router = this.router = new Backbone.Router();
_.each([
[':default', _.bind(this.routeNotFound, this)],
['content', _.bind(function () {
['main', _.bind(function () {
// The backbone router unfortunately usurps the
// default behavior of in-page-links. This hack
// prevents the screen reader in-page-link from
......@@ -580,7 +580,7 @@
/**
* Set up the tabbed view and switch tabs.
*/
goToTab: function (tab) {
goToTab: function(tab) {
this.mainView = this.tabbedView;
// Note that `render` should be called first so
// that the tabbed view's element is set
......
<div class="sr-is-focusable sr-teams-view" tabindex="-1"></div>
<div class="sr-is-focusable sr-teams-view" tabindex="-1" aria-label="Tab content"></div>
<div class="teams-paging-header"></div>
<div class="teams-list"></div>
<div class="teams-paging-footer"></div>
<div class="sr-is-focusable sr-topics-view" tabindex="-1"></div>
<div class="sr-is-focusable sr-topics-view" tabindex="-1" aria-label="Tab content"></div>
<div class="topics-paging-header"></div>
<div class="topics-list"></div>
<div class="topics-paging-footer"></div>
......@@ -22,8 +22,10 @@ from openedx.core.djangolib.js_utils import (
<div class="container">
<div class="teams-wrapper">
<section class="teams-content">
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="teams-content">
</section>
</main>
</div>
</div>
......
......@@ -8,6 +8,7 @@
// +utility navigation
// +toggling utilities
// +case - calculator spacing
// +tabs
// +notes:
// --------------------
......@@ -128,26 +129,43 @@
%page-content-nav {
margin-bottom: $baseline;
border-bottom: 3px solid $gray-l5;
border-bottom: 1px solid $gray-l5;
.nav-item {
@extend %button-reset;
display: inline-block;
margin-bottom: -3px; // to match the border
border-bottom: 3px solid $gray-l5;
padding: ($baseline*.75);
color: $gray-d2;
&.is-active {
border-bottom: 3px solid $gray-d2;
border-bottom: 4px solid $link-color;
color: $gray-d2;
}
// STATE: hover and focus
&:hover,
&:focus {
border-bottom: 3px solid $link-color;
border-bottom: 4px solid $link-color;
color: $link-color;
}
}
}
// +tabs - styles for tabs and tabpanels (teams and learner profile, currently)
// --------------------
.page-content-nav {
.tab {
}
}
.page-content-main {
.tabs {
.tabpanel {
outline: none;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment