test/unit/core.browser-compatibility.test.js
define(function(require) {
'use strict';
var bdd = require('intern!bdd');
var expect = require('intern/chai!expect');
var FocusableTestFrame = require('../helper/test-iframe-browser-data');
var focusableTestData = require('../helper/browser-focusable-data');
var platform = require('platform');
var isFocusRelevant = require('ally/is/focus-relevant');
var isFocusable = require('ally/is/focusable');
var isTabbable = require('ally/is/tabbable');
var isOnlyTabbable = require('ally/is/only-tabbable');
var getFocusRedirectTarget = require('ally/get/focus-redirect-target');
var queryTabsequence = require('ally/query/tabsequence');
bdd.describe('core: Focusable Browser Compatibility', function() {
var framed = new FocusableTestFrame();
var data = focusableTestData(platform);
bdd.before(function() {
if (!data) {
this.skip('No data available to compare against');
}
return framed.initialize(document.body);
});
bdd.after(function() {
framed.terminate();
});
// This test is a hack to print some data
bdd.it('should compare against the correct data', function() {
var ident = data.platform.name + ' ' + data.platform.version;
if (data.platform.product) {
ident = data.platform.product + ' ' + ident;
}
this.skip(ident);
});
if (!data) {
return;
}
var ignoreTabsequencePattern = /svg/;
var ignoreTabsequenceFocusablePattern = null;
var skipTabsequence = {};
var ignorePattern = /(^|-> )(ignore|html|body|embed|param)/;
var skipUntestable = {
// known mismatch
'iframe': true,
'iframe[src=svg]': true,
'keygen': true,
'keygen[tabindex=-1]': true,
};
if (data.platform.layout === 'Blink' || data.platform.layout === 'WebKit') {
// SVG elements made focusable by focus event listener
skipUntestable['svg rect[onfocus]'] = true;
skipUntestable['svg{use} defs g[onfocus]'] = true;
skipUntestable['svg{use} defs g{focusable} circle[onfocus]'] = true;
skipUntestable['svg{use} g rect[onfocus]'] = true;
// <use> element made focusable by referencing focusable content
// https://bugs.chromium.org/p/chromium/issues/detail?id=665121
skipUntestable['svg{use:foreign} use'] = true;
skipUntestable['svg{use} use{inline-shape}'] = true;
skipUntestable['svg{use} use{shape-with-focus-content:1}'] = true;
skipUntestable['svg{use} use{shape-with-focus-content:2}'] = true;
skipUntestable['svg{use} use{shape-with-focus:1}'] = true;
skipUntestable['svg{use} use{shape-with-focus:2}'] = true;
}
if (data.platform.name === 'Firefox') {
skipUntestable['map.object area'] = true;
skipUntestable['map.object area[href].upper'] = true;
skipUntestable['map.object area[href].lower'] = true;
// "label[tabindex=0]" is in the browser's tabsequence
// (it's considered to redirect focus - scriptFocus and keyboardFocus behavior do not align!)
skipTabsequence['label[tabindex=0]'] = true;
// "img[usemap].duplicate area[href]" is in the browser's tabsequence once, in ally's twice
// caused by https://bugzilla.mozilla.org/show_bug.cgi?id=1116126
skipTabsequence['img[usemap].duplicate area[href]'] = true;
skipUntestable['firefox-bug-1116126'] = true;
// <use> content should be in a shadow tree
// https://svgwg.org/svg2-draft/single-page.html#struct-TermUseElementShadowTree
skipTabsequence['svg{use} defs g{focusable} link'] = true;
skipTabsequence['svg{use} g link'] = true;
// "div{flexbox} > span{order:1} > input" and "div{flexbox} > span{order:2} > input" are not reordered in ally
skipTabsequence['div{flexbox} > span{order:1} > input'] = true;
skipTabsequence['div{flexbox} > span{order:2} > input'] = true;
// In Firefox ShadowDOM is behind a flag
if (!document.body.createShadowRoot) {
ignoreTabsequencePattern = /svg|shadow-host/;
ignoreTabsequenceFocusablePattern = /shadow-host/;
}
}
if (data.platform.name === 'Firefox' && parseInt(data.platform.version, 10) >= 52) {
// With the introduction of tabindex support on SVG elements, Firefox broke a few things
skipUntestable['details object[src=svg] -> file:svg'] = true;
skipUntestable['iframe[src=svg] -> file:svg'] = true;
skipUntestable['iframe[src=svg][tabindex=-1] -> file:svg'] = true;
skipUntestable['object[src=svg]'] = true;
skipUntestable['object[src=svg] -> file:svg'] = true;
skipUntestable['object[src=svg][height=0]'] = true;
skipUntestable['object[src=svg][height=0] -> file:svg'] = true;
skipUntestable['object[src=svg][tabindex=-1] -> file:svg'] = true;
skipUntestable['object[src=svg][tabindex=0]'] = true;
skipUntestable['object[src=svg][tabindex=0] -> file:svg'] = true;
skipUntestable['object[src=svg]{hidden} -> file:svg'] = true;
skipUntestable['object[src=svg]{none} -> file:svg'] = true;
}
if (data.platform.layout === 'Trident' || data.platform.layout === 'EdgeHTML') {
// IE and Edge do not forward focus upon script-focus, but does on pointer-focus,
// we'll act as if script-focus worked just like pointer-focus
data.elements['label:has(input)'].scriptFocus.redirected = 'label:has(input) input';
data.elements['label[for=label-target-focusable]'].scriptFocus.redirected = 'input[type=text][tabindex=-1]';
data.elements['label[for=label-target]'].scriptFocus.redirected = 'input[type=text]';
}
if (data.platform.layout === 'Trident' && parseInt(data.platform.version, 10) === 10) {
// these elements were removed from IE10's manual test
skipUntestable['object[src=swf]'] = true;
skipUntestable['object[src=swf][height=0]'] = true;
skipUntestable['object[src=swf][tabindex=0]'] = true;
}
if (data.platform.layout === 'EdgeHTML' && parseInt(data.platform.version, 10) >= 14) {
skipUntestable['iframe[src=svg] -> file:svg'] = true;
// cannot detect tabbables in an iframe
// is.tabbable.test will have to suffice
skipUntestable['svg a[xlink|href][tabindex=-1]'] = true;
skipUntestable['svg rect[tabindex=-1]'] = true;
skipUntestable['svg rect[tabindex=-1]{viewbox}'] = true;
skipUntestable['svg{tiny} a[tabindex=-1]'] = true;
}
if (data.platform.product === 'iPhone' && data.platform.name === 'Safari') {
[
'[contenteditable]',
'[contenteditable]:empty',
'[hidden]{displayed} input',
'canvas > input',
'div{flexbox} > span{order:1} > input',
'div{flexbox} > span{order:2} > input',
'fieldset input',
'fieldset:has(select) select',
'fieldset:has(textarea) textarea',
'firefox-bug-1116126',
'form input',
'form[disabled] input',
'form[disabled][tabindex=-1] input',
'form[disabled][tabindex=0] input',
'form[tabindex=-1] input',
'form[tabindex=0] input',
'input[tabindex=1]',
'input[tabindex=2]',
'input[tabindex=hello]',
'input[type=password]',
'input[type=text]',
'label:has(input) input',
'select',
'span{user-modify}',
'textarea',
].forEach(function(ident) {
// cannot detect tabbables in an iframe
// is.tabbable.test will have to suffice
data.elements[ident].tabbable = false;
});
// cannot detect tabbables in an iframe
// query.tabsequence.test will have to suffice
data.tabsequence = [];
}
function generateTest(label) {
return function() {
// static result state
var element = data.elements[label] || {};
var focusable = Boolean(element.focusable);
var tabbable = focusable && Boolean(element.tabbable);
var onlyTabbable = !focusable && Boolean(element.tabbable);
var focusRelevant = Boolean(focusable || onlyTabbable
|| element.scriptFocus && element.scriptFocus.redirected);
// evaluated state
var _element = framed.getElement(label);
if (!_element) {
this.skip('element not found');
}
var _focusRelevant = isFocusRelevant(_element);
var _focusable = isFocusable(_element);
var _tabbable = _focusable && isTabbable(_element);
var _onlyTabbable = isOnlyTabbable(_element);
// focus-relevant is allowed to produce false-positives
// as it is only used as a pre-filter
var okFocusRelevant = _focusRelevant === focusRelevant || _focusRelevant && !focusRelevant;
expect(okFocusRelevant).to.equal(true, 'is/focus-relevant');
expect(_focusable).to.equal(focusable, 'is/focusable');
expect(_tabbable).to.equal(tabbable, 'is/tabbable');
expect(_onlyTabbable).to.equal(onlyTabbable, 'is/only-tabbable');
var redirectTarget = element.scriptFocus && element.scriptFocus.redirected || null;
var _redirectTarget = getFocusRedirectTarget({context: _element});
var _redirectTargetLabel = _redirectTarget && _redirectTarget.getAttribute('data-label') || null;
expect(_redirectTargetLabel).to.equal(redirectTarget, 'get/focus-redirect-target');
};
}
bdd.describe('focusable state', function() {
Object.keys(data.elements).forEach(function(label) {
if (skipUntestable[label] || label.match(ignorePattern)) {
// silently skip what we know we can't test
return;
}
bdd.it('should match for ' + label, generateTest(label));
});
});
bdd.describe('tabbing sequence', function() {
function extractLabel(element) {
return element.getAttribute('data-label');
}
bdd.it('should match for script focusable elements', function() {
var ignored = function(label) {
return !skipUntestable[label]
&& !skipTabsequence[label]
&& !label.match(ignorePattern)
&& !label.match(ignoreTabsequencePattern)
&& label.indexOf(' -> ') === -1;
};
var expected = data.tabsequence.filter(ignored);
var sequence = queryTabsequence({
context: framed.document.body,
strategy: 'strict',
}).map(extractLabel).filter(ignored);
expect(sequence).to.deep.equal(expected);
});
bdd.it('should match when including onlyTabbable', function() {
var ignored = function(label) {
return !skipUntestable[label]
&& !skipTabsequence[label]
&& !label.match(ignorePattern)
&& (!ignoreTabsequenceFocusablePattern || !label.match(ignoreTabsequenceFocusablePattern))
&& label.indexOf(' -> ') === -1;
};
var expected = data.tabsequence.filter(ignored);
var sequence = queryTabsequence({
context: framed.document.body,
includeOnlyTabbable: true,
strategy: 'strict',
}).map(extractLabel).filter(ignored);
expect(sequence).to.deep.equal(expected);
});
});
});
});