Skip to content

Commit

Permalink
fix(frame-title): return incomplete for presentational iframe with em…
Browse files Browse the repository at this point in the history
…pty title (#3594)

* fix(frame-title): account for presentational role conflict

* remove frame

* fix virtual rule

* Update test/integration/rules/frame-title/frame-title.html

Co-authored-by: Wilco Fiers <WilcoFiers@users.noreply.github.com>

* report for iframe and title

* finalize

* enable act rule

* test

* test

Co-authored-by: Wilco Fiers <WilcoFiers@users.noreply.github.com>
  • Loading branch information
straker and WilcoFiers authored Sep 5, 2022
1 parent 2fb662f commit c2cfd84
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 101 deletions.
18 changes: 17 additions & 1 deletion lib/checks/shared/presentational-role-evaluate.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,25 @@ import { getGlobalAriaAttrs } from '../../commons/standards';
import { isFocusable } from '../../commons/dom';

export default function presentationalRoleEvaluate(node, options, virtualNode) {
const role = getRole(virtualNode);
const explicitRole = getExplicitRole(virtualNode);

// in JAWS, an iframe or frame with an accessible name and a presentational role gets announced
// as "group" rather than being ignored. aria-label is handled by role-conflict resolution and
// aria-labelledby only triggers the group role when it's valid (exists and has content)
if (
['presentation', 'none'].includes(explicitRole) &&
['iframe', 'frame'].includes(virtualNode.props.nodeName) &&
virtualNode.hasAttr('title')
) {
this.data({
messageKey: 'iframe',
nodeName: virtualNode.props.nodeName
});
return false;
}

const role = getRole(virtualNode);

if (['presentation', 'none'].includes(role)) {
this.data({ role });
return true;
Expand Down
3 changes: 2 additions & 1 deletion lib/checks/shared/presentational-role.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"default": "Element's default semantics were not overridden with role=\"none\" or role=\"presentation\"",
"globalAria": "Element's role is not presentational because it has a global ARIA attribute",
"focusable": "Element's role is not presentational because it is focusable",
"both": "Element's role is not presentational because it has a global ARIA attribute and is focusable"
"both": "Element's role is not presentational because it has a global ARIA attribute and is focusable",
"iframe": "Using the \"title\" attribute on an ${data.nodeName} element with a presentational role behaves inconsistently between screen readers"
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions test/act-mapping/frame-title.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "cae760",
"title": "Iframe element has non-empty accessible name",
"axeRules": ["frame-title"]
}
42 changes: 33 additions & 9 deletions test/checks/shared/presentational-role.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
describe('presentational-role', function() {
describe('presentational-role', function () {
'use strict';

var fixture = document.getElementById('fixture');
var queryFixture = axe.testUtils.queryFixture;
var checkEvaluate = axe.testUtils.getCheckEvaluate('presentational-role');
var checkContext = axe.testUtils.MockCheckContext();

afterEach(function() {
afterEach(function () {
fixture.innerHTML = '';
checkContext.reset();
});

it('should detect role="none" on the element', function() {
it('should detect role="none" on the element', function () {
var vNode = queryFixture('<div id="target" role="none"></div>');

assert.isTrue(checkEvaluate.call(checkContext, null, null, vNode));
assert.deepEqual(checkContext._data.role, 'none');
});

it('should detect role="presentation" on the element', function() {
it('should detect role="presentation" on the element', function () {
var vNode = queryFixture('<div id="target" role="presentation"></div>');

assert.isTrue(checkEvaluate.call(checkContext, null, null, vNode));
assert.deepEqual(checkContext._data.role, 'presentation');
});

it('should return false when role !== none', function() {
it('should return false when role !== none', function () {
var vNode = queryFixture('<div id="target" role="cats"></div>');

assert.isFalse(checkEvaluate.call(checkContext, null, null, vNode));
});

it('should return false when there is no role attribute', function() {
it('should return false when there is no role attribute', function () {
var vNode = queryFixture('<div id="target"></div>');

assert.isFalse(checkEvaluate.call(checkContext, null, null, vNode));
});

it('should return false when the element is focusable', function() {
it('should return false when the element is focusable', function () {
var vNode = queryFixture(
'<button id="target" role="none">Still a button</button>'
);
Expand All @@ -46,7 +46,7 @@ describe('presentational-role', function() {
assert.deepEqual(checkContext._data.messageKey, 'focusable');
});

it('should return false when the element has global aria attributes', function() {
it('should return false when the element has global aria attributes', function () {
var vNode = queryFixture(
'<img id="target" role="none" aria-live="assertive" />'
);
Expand All @@ -55,12 +55,36 @@ describe('presentational-role', function() {
assert.deepEqual(checkContext._data.messageKey, 'globalAria');
});

it('should return false when the element has global aria attributes and is focusable', function() {
it('should return false when the element has global aria attributes and is focusable', function () {
var vNode = queryFixture(
'<button id="target" role="none" aria-live="assertive">Still a button</button>'
);

assert.isFalse(checkEvaluate.call(checkContext, null, null, vNode));
assert.deepEqual(checkContext._data.messageKey, 'both');
});

it('should return false for iframe element with role=none and title', function () {
var vNode = queryFixture(
'<iframe id="target" role="none" title=" "></iframe>'
);

assert.isFalse(checkEvaluate.call(checkContext, null, null, vNode));
assert.deepEqual(checkContext._data, {
messageKey: 'iframe',
nodeName: 'iframe'
});
});

it('should return false for iframe element with role=presentation and title', function () {
var vNode = queryFixture(
'<iframe id="target" role="presentation" title=""></iframe>'
);

assert.isFalse(checkEvaluate.call(checkContext, null, null, vNode));
assert.deepEqual(checkContext._data, {
messageKey: 'iframe',
nodeName: 'iframe'
});
});
});
Loading

0 comments on commit c2cfd84

Please sign in to comment.