* fix: eslint operator-linebreak issue * fix: eslint quotes issue * fix: react jsx indent and props issues * fix: eslint trailing spaces issues * fix: eslint line around directives issue * fix: eslint semi rule * fix: eslint newline per chain rule * fix: eslint space infix ops rule * fix: eslint space-in-parens issue * fix: eslint space before function paren issue * fix: eslint space before blocks issue * fix: eslint arrow body style issue * fix: eslint dot-location issue * fix: eslint quotes issue * fix: eslint quote props issue * fix: eslint operator assignment issue * fix: eslint new line after import issue * fix: indent issues * fix: operator assignment issue * fix: all autofixable eslint issues * fix: all react related fixable issues * fix: autofixable eslint issues * chore: remove all template literals * fix: remaining autofixable issues * chore: apply amnesty on all existing issues * fix: failing xss-lint issues * refactor: apply amnesty on remaining issues * refactor: apply amnesty on new issues * fix: remove file level suppressions * refactor: apply amnesty on new issues
329 lines
14 KiB
JavaScript
329 lines
14 KiB
JavaScript
define([
|
|
'jquery', 'underscore', 'annotator_1.2.9', 'logger', 'js/edxnotes/views/notes_factory'
|
|
], function($, _, Annotator, Logger, NotesFactory) {
|
|
'use strict';
|
|
|
|
describe('EdxNotes Accessibility Plugin', function() {
|
|
function keyDownEvent(key) {
|
|
return $.Event('keydown', {keyCode: key});
|
|
}
|
|
|
|
function tabBackwardEvent() {
|
|
return $.Event('keydown', {keyCode: $.ui.keyCode.TAB, shiftKey: true});
|
|
}
|
|
|
|
function tabForwardEvent() {
|
|
return $.Event('keydown', {keyCode: $.ui.keyCode.TAB, shiftKey: false});
|
|
}
|
|
|
|
function enterMetaKeyEvent() {
|
|
return $.Event('keydown', {keyCode: $.ui.keyCode.ENTER, metaKey: true});
|
|
}
|
|
|
|
function enterControlKeyEvent() {
|
|
return $.Event('keydown', {keyCode: $.ui.keyCode.ENTER, ctrlKey: true});
|
|
}
|
|
|
|
beforeEach(function() {
|
|
this.KEY = $.ui.keyCode;
|
|
loadFixtures('js/fixtures/edxnotes/edxnotes_wrapper.html');
|
|
this.annotator = NotesFactory.factory(
|
|
$('div#edx-notes-wrapper-123').get(0), {
|
|
endpoint: 'http://example.com/'
|
|
}
|
|
);
|
|
this.plugin = this.annotator.plugins.Accessibility;
|
|
spyOn(Logger, 'log');
|
|
});
|
|
|
|
afterEach(function() {
|
|
while (Annotator._instances.length > 0) {
|
|
Annotator._instances[0].destroy();
|
|
}
|
|
});
|
|
|
|
describe('destroy', function() {
|
|
it('should unbind all events', function() {
|
|
spyOn($.fn, 'off');
|
|
spyOn(this.annotator, 'unsubscribe').and.callThrough();
|
|
this.plugin.destroy();
|
|
expect(this.annotator.unsubscribe).toHaveBeenCalledWith(
|
|
'annotationViewerTextField', this.plugin.addAriaAttributes
|
|
);
|
|
expect(this.annotator.unsubscribe).toHaveBeenCalledWith(
|
|
'annotationsLoaded', this.plugin.addDescriptions
|
|
);
|
|
expect(this.annotator.unsubscribe).toHaveBeenCalledWith(
|
|
'annotationCreated', this.plugin.addDescriptions
|
|
);
|
|
expect(this.annotator.unsubscribe).toHaveBeenCalledWith(
|
|
'annotationCreated', this.plugin.focusOnHighlightedText
|
|
);
|
|
expect(this.annotator.unsubscribe).toHaveBeenCalledWith(
|
|
'annotationDeleted', this.plugin.removeDescription
|
|
);
|
|
expect($.fn.off).toHaveBeenCalledWith('.accessibility');
|
|
});
|
|
});
|
|
|
|
describe('a11y attributes', function() {
|
|
var highlight, annotation, $note;
|
|
|
|
beforeEach(function() {
|
|
highlight = $('<span class="annotator-hl" tabindex="0"/>').appendTo(this.annotator.element);
|
|
annotation = {
|
|
id: '01',
|
|
text: 'Test text',
|
|
highlights: [highlight.get(0)]
|
|
};
|
|
});
|
|
|
|
it('should be added to highlighted text and associated note', function() {
|
|
this.annotator.viewer.load([annotation]);
|
|
$note = $('.annotator-note');
|
|
expect($note).toExist();
|
|
expect($note).toHaveAttr('tabindex', '-1');
|
|
expect($note).toHaveAttr('role', 'note');
|
|
expect($note).toHaveAttr('class', 'annotator-note');
|
|
});
|
|
|
|
it('should create aria-descriptions when annotations are loaded', function() {
|
|
this.annotator.publish('annotationsLoaded', [[annotation]]);
|
|
expect(highlight).toHaveAttr('aria-describedby', 'aria-note-description-01');
|
|
expect($('#aria-note-description-01')).toContainText('Test text');
|
|
});
|
|
|
|
it('should create aria-description when new annotation is created', function() {
|
|
this.annotator.publish('annotationCreated', [annotation]);
|
|
expect(highlight).toHaveAttr('aria-describedby', 'aria-note-description-01');
|
|
expect($('#aria-note-description-01')).toContainText('Test text');
|
|
});
|
|
|
|
it('should remove aria-description when the annotation is removed', function() {
|
|
this.annotator.publish('annotationDeleted', [annotation]);
|
|
expect($('#aria-note-description-01')).not.toExist();
|
|
});
|
|
});
|
|
|
|
describe('keydown events on highlighted text', function() {
|
|
var highlight, annotation, note;
|
|
|
|
beforeEach(function() {
|
|
highlight = $('<span class="annotator-hl" tabindex="0"/>').appendTo(this.annotator.element);
|
|
annotation = {
|
|
id: '01',
|
|
text: 'Test text',
|
|
highlights: [highlight.get(0)]
|
|
};
|
|
highlight.data('annotation', annotation);
|
|
spyOn(this.annotator, 'showViewer').and.callThrough();
|
|
spyOn(this.annotator.viewer, 'hide').and.callThrough();
|
|
spyOn(this.plugin, 'focusOnGrabber').and.callThrough();
|
|
});
|
|
|
|
it('should open the viewer on SPACE keydown and focus on note', function() {
|
|
highlight.trigger(keyDownEvent(this.KEY.SPACE));
|
|
expect(this.annotator.showViewer).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should open the viewer on ENTER keydown and focus on note', function() {
|
|
highlight.trigger(keyDownEvent(this.KEY.ENTER));
|
|
expect(this.annotator.showViewer).toHaveBeenCalled();
|
|
});
|
|
|
|
// This happens only when coming from notes page
|
|
it('should open focus on viewer on TAB keydown if viewer is opened', function() {
|
|
this.annotator.viewer.load([annotation]);
|
|
highlight.trigger(keyDownEvent(this.KEY.TAB));
|
|
expect(this.annotator.element.find('.annotator-listing')).toBeFocused();
|
|
});
|
|
|
|
it('should focus highlighted text after closing', function() {
|
|
// eslint-disable-next-line no-shadow
|
|
var note;
|
|
highlight.trigger(keyDownEvent(this.KEY.ENTER));
|
|
note = this.annotator.element.find('.annotator-edit');
|
|
note.trigger(keyDownEvent(this.KEY.ESCAPE));
|
|
expect(highlight).toBeFocused();
|
|
});
|
|
|
|
it('should focus on grabber after being deleted', function() {
|
|
highlight.trigger(keyDownEvent(this.KEY.ENTER));
|
|
this.annotator.publish('annotationDeleted', {});
|
|
expect(this.plugin.focusGrabber).toBeFocused();
|
|
});
|
|
|
|
it('should not focus on grabber when the viewer is hidden', function() {
|
|
this.annotator.publish('annotationDeleted', {});
|
|
expect(this.plugin.focusGrabber).not.toBeFocused();
|
|
});
|
|
});
|
|
|
|
describe('keydown events on viewer', function() {
|
|
var highlight, annotation, listing, note, edit, del, close;
|
|
|
|
beforeEach(function() {
|
|
highlight = $('<span class="annotator-hl" tabindex="0"/>').appendTo(this.annotator.element);
|
|
annotation = {
|
|
id: '01',
|
|
text: 'Test text',
|
|
highlights: [highlight.get(0)]
|
|
};
|
|
highlight.data('annotation', annotation);
|
|
this.annotator.viewer.load([annotation]);
|
|
listing = this.annotator.element.find('.annotator-listing').first();
|
|
note = this.annotator.element.find('.annotator-note').first();
|
|
edit = this.annotator.element.find('.annotator-edit').first();
|
|
del = this.annotator.element.find('.annotator-delete').first();
|
|
close = this.annotator.element.find('.annotator-close').first();
|
|
spyOn(this.annotator.viewer, 'hide').and.callThrough();
|
|
});
|
|
|
|
it('should give focus to Note on Listing TAB keydown', function() {
|
|
listing.focus();
|
|
listing.trigger(tabForwardEvent());
|
|
expect(note).toBeFocused();
|
|
});
|
|
|
|
it('should give focus to Close on Listing SHIFT + TAB keydown', function() {
|
|
listing.focus();
|
|
listing.trigger(tabBackwardEvent());
|
|
expect(close).toBeFocused();
|
|
});
|
|
|
|
it('should cycle forward through Note, Edit, Delete, and Close on TAB keydown', function() {
|
|
note.focus();
|
|
note.trigger(tabForwardEvent());
|
|
expect(edit).toBeFocused();
|
|
edit.trigger(tabForwardEvent());
|
|
expect(del).toBeFocused();
|
|
del.trigger(tabForwardEvent());
|
|
expect(close).toBeFocused();
|
|
close.trigger(tabForwardEvent());
|
|
expect(note).toBeFocused();
|
|
});
|
|
|
|
it('should cycle backward through Note, Edit, Delete, and Close on SHIFT + TAB keydown', function() {
|
|
note.focus();
|
|
note.trigger(tabBackwardEvent());
|
|
expect(close).toBeFocused();
|
|
close.trigger(tabBackwardEvent());
|
|
expect(del).toBeFocused();
|
|
del.trigger(tabBackwardEvent());
|
|
expect(edit).toBeFocused();
|
|
edit.trigger(tabBackwardEvent());
|
|
expect(note).toBeFocused();
|
|
});
|
|
|
|
it('should hide on ESCAPE keydown', function() {
|
|
var tabControls = [listing, note, edit, del, close];
|
|
|
|
_.each(tabControls, function(control) {
|
|
control.focus();
|
|
control.trigger(keyDownEvent(this.KEY.ESCAPE));
|
|
}, this);
|
|
expect(this.annotator.viewer.hide.calls.count()).toBe(5);
|
|
});
|
|
});
|
|
|
|
describe('keydown events on editor', function() {
|
|
var highlight, annotation, form, annotatorItems, textArea, tags, save, cancel;
|
|
|
|
beforeEach(function() {
|
|
highlight = $('<span class="annotator-hl" tabindex="0"/>').appendTo(this.annotator.element);
|
|
annotation = {
|
|
id: '01',
|
|
text: 'Test text',
|
|
highlights: [highlight.get(0)]
|
|
};
|
|
highlight.data('annotation', annotation);
|
|
this.annotator.editor.show(annotation, {left: 0, top: 0});
|
|
form = this.annotator.element.find('form.annotator-widget');
|
|
annotatorItems = this.annotator.element.find('.annotator-item');
|
|
textArea = annotatorItems.first().children('textarea');
|
|
tags = annotatorItems.first().next().children('input');
|
|
save = this.annotator.element.find('.annotator-save');
|
|
cancel = this.annotator.element.find('.annotator-cancel');
|
|
spyOn(this.annotator.editor, 'submit').and.callThrough();
|
|
spyOn(this.annotator.editor, 'hide').and.callThrough();
|
|
});
|
|
|
|
it('should give focus to TextArea on Form TAB keydown', function() {
|
|
form.focus();
|
|
form.trigger(tabForwardEvent());
|
|
expect(textArea).toBeFocused();
|
|
});
|
|
|
|
it('should give focus to Cancel on Form SHIFT + TAB keydown', function() {
|
|
form.focus();
|
|
form.trigger(tabBackwardEvent());
|
|
expect(cancel).toBeFocused();
|
|
});
|
|
|
|
it('should cycle forward through textarea, tags, save, and cancel on TAB keydown', function() {
|
|
textArea.focus();
|
|
textArea.trigger(tabForwardEvent());
|
|
expect(tags).toBeFocused();
|
|
tags.trigger(tabForwardEvent());
|
|
expect(save).toBeFocused();
|
|
save.trigger(tabForwardEvent());
|
|
expect(cancel).toBeFocused();
|
|
cancel.trigger(tabForwardEvent());
|
|
expect(textArea).toBeFocused();
|
|
});
|
|
|
|
it('should cycle back through textarea, tags, save, and cancel on SHIFT + TAB keydown', function() {
|
|
textArea.focus();
|
|
textArea.trigger(tabBackwardEvent());
|
|
expect(cancel).toBeFocused();
|
|
cancel.trigger(tabBackwardEvent());
|
|
expect(save).toBeFocused();
|
|
save.trigger(tabBackwardEvent());
|
|
expect(tags).toBeFocused();
|
|
tags.trigger(tabBackwardEvent());
|
|
expect(textArea).toBeFocused();
|
|
});
|
|
|
|
it('should submit if target is Save on ENTER or SPACE keydown', function() {
|
|
save.focus();
|
|
save.trigger(keyDownEvent(this.KEY.ENTER));
|
|
expect(this.annotator.editor.submit).toHaveBeenCalled();
|
|
this.annotator.editor.submit.calls.reset();
|
|
save.focus();
|
|
save.trigger(keyDownEvent(this.KEY.SPACE));
|
|
expect(this.annotator.editor.submit).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should submit on META or CONTROL + ENTER keydown', function() {
|
|
textArea.focus();
|
|
textArea.trigger(enterMetaKeyEvent());
|
|
expect(this.annotator.editor.submit).toHaveBeenCalled();
|
|
this.annotator.editor.submit.calls.reset();
|
|
textArea.focus();
|
|
textArea.trigger(enterControlKeyEvent());
|
|
expect(this.annotator.editor.submit).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should hide if target is Cancel on ENTER or SPACE keydown', function() {
|
|
cancel.focus();
|
|
cancel.trigger(keyDownEvent(this.KEY.ENTER));
|
|
expect(this.annotator.editor.hide).toHaveBeenCalled();
|
|
this.annotator.editor.hide.calls.reset();
|
|
cancel.focus();
|
|
save.trigger(keyDownEvent(this.KEY.SPACE));
|
|
expect(this.annotator.editor.hide).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should hide on ESCAPE keydown', function() {
|
|
var tabControls = [textArea, save, cancel];
|
|
|
|
_.each(tabControls, function(control) {
|
|
control.focus();
|
|
control.trigger(keyDownEvent(this.KEY.ESCAPE));
|
|
}, this);
|
|
expect(this.annotator.editor.hide.calls.count()).toBe(3);
|
|
});
|
|
});
|
|
});
|
|
});
|