Skip to content
This repository has been archived by the owner on Nov 3, 2021. It is now read-only.

Commit

Permalink
Merge pull request #24942 from yzen/bug-1069197
Browse files Browse the repository at this point in the history
Bug 1069197 - added screen reader support for the calendar for the month...
  • Loading branch information
yzen committed Oct 15, 2014
2 parents 758427b + 379cb5f commit f6a402a
Show file tree
Hide file tree
Showing 16 changed files with 275 additions and 13 deletions.
4 changes: 4 additions & 0 deletions apps/calendar/js/template.js
Expand Up @@ -72,6 +72,10 @@ Template.prototype = {
return navigator.mozL10n.get(value);
},

l10nId: function(a) {
return this.s(a).replace(/\s/g, '-');
},

/**
* Renders template with given slots.
*
Expand Down
21 changes: 15 additions & 6 deletions apps/calendar/js/templates/month.js
Expand Up @@ -35,12 +35,21 @@ module.exports = create({
},

day: function() {
return '<li role="gridcell" id="' + this.s('id') +
'" data-date="' + this.s('dateString') +
'" class="' + this.s('state') + '">' +
'<span class="day">' + this.h('date') + '</span>' +
'<div class="busy-indicator"></div>' +
'</li>';
var date = this.h('date');
var dateString = this.s('dateString');
var id = this.s('id');
var l10nStateId = this.l10nId('state');
var state = this.s('state');

return `<li role="gridcell" tabindex="0" id="${id}"
aria-describedby="${id}-busy-indicator ${id}-description"
data-date="${dateString}" class="${state}">
<span class="day" role="button">${date}</span>
<div id="${id}-busy-indicator"
class="busy-indicator" aria-hidden="true"></div>
<span id="${id}-description" aria-hidden="true"
data-l10n-id="${l10nStateId}"></span>
</li>`;
}
});

Expand Down
13 changes: 13 additions & 0 deletions apps/calendar/js/views/month.js
Expand Up @@ -35,13 +35,23 @@ Month.prototype = {
}
},

_onwheel: function() {
var didWheel = Parent.prototype._onwheel.apply(this, arguments);

// If we changed months, set the selected day to the 1st
if (didWheel) {
this.controller.selectedDay = this.date;
}
},

_clearSelectedDay: function() {
var day = this.element.querySelector(
this.selectors.selectedDay
);

if (day) {
day.classList.remove(this.SELECTED);
day.removeAttribute('aria-selected');
}
},

Expand All @@ -56,6 +66,9 @@ Month.prototype = {

if (el) {
el.classList.add(this.SELECTED);
el.setAttribute('aria-selected', true);
// Put the screen reader cursor onto the selected day.
el.focus();
this._selectedDay = date;
}
},
Expand Down
9 changes: 9 additions & 0 deletions apps/calendar/js/views/month_child.js
Expand Up @@ -120,6 +120,14 @@ Child.prototype = {

var difference = Math.min(3, count) - element.childNodes.length;

if (count > 0) {
element.setAttribute('aria-label', navigator.mozL10n.get('busy', {
n: count
}));
} else {
element.removeAttribute('aria-label');
}

if (difference === 0) {
return;
}
Expand Down Expand Up @@ -311,6 +319,7 @@ Child.prototype = {

element.classList.add('month');
element.setAttribute('role', 'grid');
element.setAttribute('aria-labelledby', 'current-month-year');
element.setAttribute('aria-readonly', true);
element.innerHTML = html;

Expand Down
22 changes: 19 additions & 3 deletions apps/calendar/js/views/time_parent.js
Expand Up @@ -43,6 +43,7 @@ TimeParent.prototype = {
_initEvents: function() {
this.app.timeController.on('purge', this);
this.element.addEventListener('swipe', this);
this.element.addEventListener('wheel', this);

this.gd = new GestureDetector(this.element);
this.gd.startDetecting();
Expand All @@ -53,16 +54,28 @@ TimeParent.prototype = {
return false;
}

var dir = data.direction;
this._move(data.direction === 'left');
return true;
},

_onwheel: function(event) {
if (event.deltaMode !== event.DOM_DELTA_PAGE || event.deltaX === 0) {
return false;
}

this._move(event.deltaX > 0);
return true;
},

_move: function(next) {
var controller = this.app.timeController;

// TODO: RTL
if (dir === 'left') {
if (next) {
controller.move(this._nextTime(this.date));
} else {
controller.move(this._previousTime(this.date));
}
return true;
},

handleEvent: function(e) {
Expand All @@ -74,6 +87,9 @@ TimeParent.prototype = {
case 'purge':
this.purgeFrames(e.data[0]);
break;
case 'wheel':
this._onwheel(e);
break;
}
},

Expand Down
18 changes: 18 additions & 0 deletions apps/calendar/locales/calendar.en-US.properties
Expand Up @@ -251,3 +251,21 @@ weekday-4-single-char = T
weekday-5-single-char = F
weekday-6-single-char = S

# The following strings are spoken by screen readers and not shown on the
# screen.
weekday-5-single-char.ariaLabel = Friday
weekday-4-single-char.ariaLabel = Thursday
weekday-3-single-char.ariaLabel = Wednesday
weekday-2-single-char.ariaLabel = Tuesday
weekday-1-single-char.ariaLabel = Monday
weekday-0-single-char.ariaLabel = Sunday
weekday-6-single-char.ariaLabel = Saturday
busy = {[ plural(n) ]}
busy[one] = {{n}} event
busy[two] = {{n}} events
busy[few] = {{n}} events
busy[many] = {{n}} events
busy[other] = {{n}} events
present.ariaLabel = Today
past-other-month = Other month
future-other-month = Other month
7 changes: 7 additions & 0 deletions apps/calendar/test/unit/template_test.js
Expand Up @@ -186,6 +186,13 @@ suite('calendar/template', function() {
assert.equal(output, '');
});

test('l10nId', function() {
var tpl = new Template(function() {
return this.l10nId('state');
});
assert.equal(tpl.render({state: 'past other-month'}), 'past-other-month');
});

suite('l10n', function() {
var realL10n;
var lookup = {
Expand Down
5 changes: 5 additions & 0 deletions apps/calendar/test/unit/templates/month_test.js
Expand Up @@ -75,13 +75,18 @@ suite('Templates.Month', function() {
dateString: add('dateStr'),
state: add('active'),
date: add('date1'),
ariaDescribedby: add('aria-describedby'),
idBusyIndicator: add('idme-busy-indicator'),
idDescription: add('idme-description'),
busy: a()
}
);

assert.ok(result);

assert.include(result, 'role="gridcell"');
assert.include(result, 'role="button"');
assert.include(result, 'tabindex="0"');
data.forEach(function(item) {
assert.include(result, item);
});
Expand Down
23 changes: 20 additions & 3 deletions apps/calendar/test/unit/views/month_child_test.js
Expand Up @@ -125,7 +125,7 @@ suite('Views.MonthChild', function() {
assert.equal(keys[0], id);

assert.include(
result, '<div class="busy-indicator"></div>',
result, 'class="busy-indicator" aria-hidden="true"></div>',
'has no indicators'
);
});
Expand Down Expand Up @@ -270,12 +270,19 @@ suite('Views.MonthChild', function() {
var result,
expected = subject._renderMonth();

function cleanExtraWhitespace(str) {
// Repalce multiple whitespaces with a single one.
return str.replace(/\s+/g, ' ');
}

result = subject.create();
assert.equal(subject.element, result);
assert.equal(subject.element.getAttribute('role'), 'grid');
assert.equal(subject.element.getAttribute('aria-labelledby'),
'current-month-year');
assert.equal(subject.element.getAttribute('aria-readonly'), 'true');
assert.equal(
result.innerHTML, expected,
cleanExtraWhitespace(result.innerHTML), cleanExtraWhitespace(expected),
'should render month'
);
});
Expand Down Expand Up @@ -437,7 +444,9 @@ suite('Views.MonthChild', function() {
var element = {
childNodes: [1, 2],
appendChild: function() {},
removeChild: function() {}
removeChild: function() {},
setAttribute: function() {},
removeAttribute: function() {}
};
mock = sinon.mock(element);
sinon
Expand All @@ -458,6 +467,10 @@ suite('Views.MonthChild', function() {
mock
.expects('removeChild')
.never();
mock
.expects('setAttribute')
.once()
.withArgs('aria-label');
subject._setBusyCount(dayId, 2);
mock.verify();
});
Expand All @@ -469,6 +482,10 @@ suite('Views.MonthChild', function() {
mock
.expects('removeChild')
.twice();
mock
.expects('removeAttribute')
.once()
.withArgs('aria-label');
subject._setBusyCount(dayId, 0);
mock.verify();
});
Expand Down
23 changes: 22 additions & 1 deletion apps/calendar/test/unit/views/month_test.js
Expand Up @@ -151,6 +151,25 @@ suite('Views.Month', function() {
);
});

test('#_onwheel', function() {
var date = new Date(2012, 4, 10);
var expected = new Date(2012, 5, 1);
subject.date = date;

subject._onwheel({
deltaMode: window.WheelEvent.DOM_DELTA_PAGE,
DOM_DELTA_PAGE: window.WheelEvent.DOM_DELTA_PAGE,
deltaX: 1,
deltaY: 0
});

assert.deepEqual(
subject.controller.selectedDay,
expected,
'selects first day of month'
);
});

test('#_createChild', function() {
var time = new Date(2012, 1, 1);
var child = subject._createChild(time);
Expand Down Expand Up @@ -204,6 +223,7 @@ suite('Views.Month', function() {
dayEl = dayEl[0];

assert.ok(dayEl.id, 'should have id');
assert.equal(dayEl.getAttribute('aria-selected'), 'true');
assert.include(dayEl.id, Calc.getDayId(
select
));
Expand All @@ -215,10 +235,11 @@ suite('Views.Month', function() {

var el = subject.element.querySelector('li');
el.classList.add('selected');
el.setAttribute('aria-selected', true);

assert.lengthOf(selected(), 1);
subject._clearSelectedDay();

assert.isNull(el.getAttribute('aria-selected'));
assert.lengthOf(selected(), 0);
});

Expand Down
42 changes: 42 additions & 0 deletions apps/calendar/test/unit/views/time_parent_test.js
Expand Up @@ -272,6 +272,48 @@ suite('Views.TimeParent', function() {
subject.app.timeController.emit('purge', span);
assert.equal(calledWith[0], span);
});

function fakeWheelEvent(deltaX, deltaY) {
return {
type: 'wheel',
deltaMode: window.WheelEvent.DOM_DELTA_PAGE,
DOM_DELTA_PAGE: window.WheelEvent.DOM_DELTA_PAGE,
deltaX: deltaX,
deltaY: deltaY
};
}

test('event: wheel', function() {
var evt = new CustomEvent('wheel');
var onWheelStub = this.sinon.stub(subject, '_onwheel');

subject.element.dispatchEvent(evt);
assert.isTrue(onWheelStub.called);
assert.isTrue(onWheelStub.calledWith(evt));
});

test('event: wheel up/down', function() {
var moveStub = this.sinon.stub(subject, '_move');

subject.handleEvent(fakeWheelEvent(0, 1));
sinon.assert.notCalled(moveStub);
});

test('event: wheel left', function() {
var moveStub = this.sinon.stub(subject, '_move');

subject.handleEvent(fakeWheelEvent(1, 0));
assert.isTrue(moveStub.called);
assert.isTrue(moveStub.calledWith(true));
});

test('event: wheel right', function() {
var moveStub = this.sinon.stub(subject, '_move');

subject.handleEvent(fakeWheelEvent(-1, 0));
assert.isTrue(moveStub.called);
assert.isTrue(moveStub.calledWith(false));
});
});

suite('#purgeFrames', function() {
Expand Down

0 comments on commit f6a402a

Please sign in to comment.