add a11y checker plugin to rce

closes CNVS-39091

test plan:
- a Check Accessibility button should show on the right side of all
  editors
- try adding a empty table
- click the check a11y button
- should have two errors
  - missing caption
  - missing headers
- correct both errors
- should show no errors detected and close

Change-Id: I4028d98d082bdbdebc034facc529cccb67fc4dcf
Reviewed-on: https://gerrit.instructure.com/128951
Reviewed-by: Clay Diffrient <cdiffrient@instructure.com>
Tested-by: Jenkins
QA-Review: Brent Burgoyne <bburgoyne@instructure.com>
Product-Review: Brent Burgoyne <bburgoyne@instructure.com>
This commit is contained in:
Brent Burgoyne 2017-10-09 07:53:57 -06:00
parent c3a4e90334
commit 6a77f6fc27
9 changed files with 84 additions and 32 deletions

View File

@ -45,6 +45,7 @@ function loadLegacyTinyMCE (callback) {
legacyTinyMCELoaded = true
require('tinymce.editor_box')
require('compiled/tinymce')
require('tinymce-a11y-checker')
callback()
}, 'legacyTinymceAsyncChunk')
}

View File

@ -32,7 +32,7 @@ function getSidebarSource(source_name) {
return ret
}
let RCELoader = {
const RCELoader = {
preload() {
this.loadRCE(function(){})
},
@ -89,6 +89,7 @@ function getSidebarSource(source_name) {
require.ensure([], (require) => {
const first = !this.RCE
this.RCE = require('canvas-rce/lib/async')
require('tinymce-a11y-checker')
if (first) {
this.loadEventListeners()
this.loadingFlag = false

View File

@ -26,3 +26,7 @@
width: 100%;
box-sizing: border-box;
}
.mce-i-a11y:before {
content: "\e900";
}

View File

@ -60,6 +60,7 @@
"timezone": "1.0.6",
"tinycolor2": "1.4.1",
"tinymce": "4.5.7",
"tinymce-a11y-checker": "1.0.1",
"tinymce-light-skin": "1.3.1"
},
"devDependencies": {

View File

@ -65,7 +65,7 @@ export default class EditorConfig {
toolbar: this.toolbar(),
theme: 'modern',
skin: false,
plugins: 'autolink,media,paste,table,textcolor,link,directionality,lists',
plugins: 'autolink,media,paste,table,textcolor,link,directionality,lists,a11y_checker',
external_plugins: {
instructure_image: '/javascripts/tinymce_plugins/instructure_image/plugin.js',
instructure_links: '/javascripts/tinymce_plugins/instructure_links/plugin.js',
@ -143,7 +143,7 @@ export default class EditorConfig {
*/
formatBtnGroup = 'bold,italic,underline,forecolor,backcolor,removeformat,alignleft,aligncenter,alignright';
positionBtnGroup = 'outdent,indent,superscript,subscript,bullist,numlist';
fontBtnGroup = 'ltr,rtl,fontsizeselect,formatselect';
fontBtnGroup = 'ltr,rtl,fontsizeselect,formatselect,check_a11y';
/**

View File

@ -30,7 +30,7 @@ define [
toolbar2 = "outdent,indent,superscript,subscript,bullist,numlist,table," +
"media,instructure_links,unlink,instructure_image," +
"instructure_equation"
toolbar3 = "ltr,rtl,fontsizeselect,formatselect"
toolbar3 = "ltr,rtl,fontsizeselect,formatselect,check_a11y"
QUnit.module "EditorConfig",
setup: ->

View File

@ -23,6 +23,7 @@ define [
'helpers/fakeENV'
'helpers/editorUtils'
'helpers/fixtures'
'tinymce.editor_box'
], (RichContentEditor, RceCommandShim, RCELoader, Sidebar, fakeENV, editorUtils, fixtures) ->
QUnit.module 'RichContentEditor - helper function:'
@ -105,12 +106,15 @@ define [
ENV.RICH_CONTENT_SERVICE_ENABLED = false
sinon.stub(@$target, 'editorBox')
@$target.editorBox.onCall(0).returns(@$target)
RichContentEditor.loadNewEditor(@$target, {defaultContent: "content"})
Promise.resolve().then =>
ok @$target.editorBox.calledTwice
ok @$target.editorBox.firstCall.calledWith()
RichContentEditor.loadNewEditor(@$target, {defaultContent: "content"}, =>
try
ok @$target.editorBox.calledTwice, "called twice"
ok @$target.editorBox.firstCall.calledWith(), "first called with nothing"
ok @$target.editorBox.secondCall.calledWith('set_code', "content")
done()
catch err
done(err)
)
test 'skips instantiation when called with empty target', ->
RichContentEditor.loadNewEditor($("#fixtures .invalidTarget"), {})
@ -121,21 +125,27 @@ define [
# false so we don't have to stub out freshNode or RCELoader.loadOnTarget
ENV.RICH_CONTENT_SERVICE_ENABLED = false
sinon.stub(RceCommandShim, 'focus')
RichContentEditor.loadNewEditor(@$target, {focus: true})
Promise.resolve().then =>
RichContentEditor.loadNewEditor(@$target, {focus: true}, =>
try
ok RceCommandShim.focus.calledWith(@$target)
RceCommandShim.focus.restore()
done()
catch err
done(err)
)
test 'with focus:true tries to show sidebar', (assert) ->
done = assert.async()
# false so we don't have to stub out RCELoader.loadOnTarget
ENV.RICH_CONTENT_SERVICE_ENABLED = false
RichContentEditor.initSidebar()
RichContentEditor.loadNewEditor(@$target, {focus: true})
Promise.resolve().then =>
RichContentEditor.loadNewEditor(@$target, {focus: true}, =>
try
ok Sidebar.show.called
done()
catch err
done(err)
)
test 'hides resize handle when called', ->
$resize = fixtures.create('<div class="mce-resizehandle"></div>')

View File

@ -51,9 +51,7 @@ test('shows beginning info alert and adds styles to iframe', () => {
/>
)
wrapper.instance().openModal(event)
el = $('.ReactModalPortal')
const alert = el.find('.before_external_content_info_alert')
alert[0].focus()
wrapper.instance().handleAlertFocus({ target: { className: "before" } })
equal(wrapper.state().beforeExternalContentAlertClass, '')
deepEqual(wrapper.state().iframeStyle, { border: '2px solid #008EE2', width: '300px' })
})
@ -65,9 +63,7 @@ test('shows ending info alert and adds styles to iframe', () => {
/>
)
wrapper.instance().openModal(event)
el = $('.ReactModalPortal')
const alert = el.find('.after_external_content_info_alert')
alert[0].focus()
wrapper.instance().handleAlertFocus({ target: { className: "after" } })
equal(wrapper.state().afterExternalContentAlertClass, '')
deepEqual(wrapper.state().iframeStyle, { border: '2px solid #008EE2', width: '300px' })
})
@ -80,9 +76,7 @@ test('hides beginning info alert and adds styles to iframe', () => {
)
wrapper.instance().openModal(event)
el = $('.ReactModalPortal')
const alert = el.find('.before_external_content_info_alert')
alert[0].focus()
alert[0].blur()
wrapper.instance().handleAlertBlur({ target: { className: "before" } })
equal(wrapper.state().beforeExternalContentAlertClass, 'screenreader-only')
deepEqual(wrapper.state().iframeStyle, { border: 'none', width: '100%' })
})
@ -94,10 +88,7 @@ test('hides ending info alert and adds styles to iframe', () => {
/>
)
wrapper.instance().openModal(event)
el = $('.ReactModalPortal')
const alert = el.find('.after_external_content_info_alert')
alert[0].focus()
alert[0].blur()
wrapper.instance().handleAlertBlur({ target: { className: "after" } })
equal(wrapper.state().afterExternalContentAlertClass, 'screenreader-only')
deepEqual(wrapper.state().iframeStyle, { border: 'none', width: '100%' })
})

View File

@ -3679,6 +3679,10 @@ glob-stream@^3.1.5:
through2 "^0.6.1"
unique-stream "^1.0.0"
glob-to-regexp@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
glob-watcher@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b"
@ -5617,6 +5621,10 @@ marked@0.3.6:
version "0.3.6"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7"
material-colors@^1.2.1:
version "1.2.5"
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.5.tgz#5292593e6754cb1bcc2b98030e4e0d6a3afc9ea1"
math-expression-evaluator@^1.2.14:
version "1.2.17"
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
@ -7016,6 +7024,20 @@ react-apollo@^1.4.12:
object-assign "^4.0.1"
prop-types "^15.5.8"
react-aria-live@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/react-aria-live/-/react-aria-live-1.0.4.tgz#3f154670f156c5b64d9bd31f5dc620ca3760ab1c"
react-color@^2.13.4:
version "2.13.8"
resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.13.8.tgz#bcc58f79a722b9bfc37c402e68cd18f26970aee4"
dependencies:
lodash "^4.0.1"
material-colors "^1.2.1"
prop-types "^15.5.10"
reactcss "^1.2.0"
tinycolor2 "^1.4.1"
react-crop@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/react-crop/-/react-crop-4.0.2.tgz#c507841f72642d0c936b289f65fa543ac09778fd"
@ -7137,6 +7159,12 @@ react@0.14.9:
envify "^3.0.0"
fbjs "^0.6.1"
reactcss@^1.2.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd"
dependencies:
lodash "^4.0.1"
read-file-stdin@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/read-file-stdin/-/read-file-stdin-0.2.1.tgz#25eccff3a153b6809afacb23ee15387db9e0ee61"
@ -8372,6 +8400,18 @@ tinycolor2@1.4.1, tinycolor2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8"
tinymce-a11y-checker@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tinymce-a11y-checker/-/tinymce-a11y-checker-1.0.1.tgz#855dc6a13e0f59b9428d35c66b258d271f0990e4"
dependencies:
instructure-icons "4.x"
instructure-ui "3.x"
react "^0.14.8 || ^15.0.0"
react-aria-live "^1.0.4"
react-color "^2.13.4"
react-dom "^0.14.8 || ^15.0.0"
wcag-element-contrast "^1.0.1"
tinymce-light-skin@1.3.1, tinymce-light-skin@~1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/tinymce-light-skin/-/tinymce-light-skin-1.3.1.tgz#fcce7a8b5761716a381f756e1ac7f47c7a41ea88"
@ -8728,6 +8768,10 @@ watchpack@^1.4.0:
chokidar "^1.7.0"
graceful-fs "^4.1.2"
wcag-element-contrast@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/wcag-element-contrast/-/wcag-element-contrast-1.0.1.tgz#5d0570d771d4b374a967915e7f69be2daf7485ff"
webpack-cleanup-plugin@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/webpack-cleanup-plugin/-/webpack-cleanup-plugin-0.5.1.tgz#df2d706bd75364c06e65b051186316d674eb96af"