Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions lib/commons/aria/get-aria-value.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { nodeLookup } from '../../core/utils';
import { nodeLookup, getElementInternals } from '../../core/utils';
import standards from '../../standards';

const idrefTypes = ['idref', 'idrefs'];
Expand Down Expand Up @@ -36,11 +36,10 @@ export default function getAriaValue(node, attrName, options = {}) {
}

const { type } = attrStandard;
const { vNode } = nodeLookup(node);
const { lowercase } = options;

for (const { source, getValue } of sources) {
let value = getValue(vNode, attrStandard, attrName);
let value = getValue(node, attrStandard, attrName);
if (value === null || value === undefined) {
continue;
}
Expand Down Expand Up @@ -69,9 +68,10 @@ export default function getAriaValue(node, attrName, options = {}) {
return null;
}

function getAttributeValue(vNode, attrStandard, attrName) {
function getAttributeValue(node, attrStandard, attrName) {
const { vNode, domNode } = nodeLookup(node);
const { type } = attrStandard;
const value = vNode.attr(attrName);
const value = vNode ? vNode.attr(attrName) : domNode.getAttribute(attrName);
Comment thread
straker marked this conversation as resolved.

// setting an ARIA idref(s) prop value can result in empty attribute values, so we'll need extra processing for idref(s)
// e.g. el.ariaLabelledByElements = [label]; el.getAttribute('aria-labelledby') === ''
Expand All @@ -90,12 +90,22 @@ function getAttributeValue(vNode, attrStandard, attrName) {
return propEmpty ? null : value;
}

function getPropertyValue(vNode, attrStandard) {
function getPropertyValue(node, attrStandard) {
const { domNode } = nodeLookup(node);
const { prop } = attrStandard;
return prop && vNode.actualNode ? vNode.actualNode[prop] : null;
return prop && domNode ? domNode[prop] : null;
}

function getInternalValue(vNode, attrStandard) {
function getInternalValue(node, attrStandard) {
const { vNode, domNode } = nodeLookup(node);
const { prop } = attrStandard;
return prop && vNode.elementInternals ? vNode.elementInternals[prop] : null;
if (!prop) {
return null;
}

Comment thread
straker marked this conversation as resolved.
const internals = vNode
? vNode.elementInternals
: getElementInternals(domNode);

return internals ? internals[prop] : null;
}
32 changes: 19 additions & 13 deletions lib/commons/aria/has-aria-value.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { nodeLookup } from '../../core/utils';
import { nodeLookup, getElementInternals } from '../../core/utils';
import standards from '../../standards';

/**
Expand All @@ -16,31 +16,37 @@ export default function hasAriaValue(node, attrName) {
throw new TypeError(`Attribute ${attrName} is not an ARIA attribute`);
}

const { vNode } = nodeLookup(node);
Comment thread
straker marked this conversation as resolved.
return (
hasAttributeValue(vNode, attrName) ||
hasPropertyValue(vNode, attrStandard) ||
hasInternalValue(vNode, attrStandard)
hasAttributeValue(node, attrName) ||
hasPropertyValue(node, attrStandard) ||
hasInternalValue(node, attrStandard)
);
}

function hasAttributeValue(vNode, attrName) {
return vNode.hasAttr(attrName);
function hasAttributeValue(node, attrName) {
const { vNode, domNode } = nodeLookup(node);
return vNode ? vNode.hasAttr(attrName) : domNode.hasAttribute(attrName);
}

function hasPropertyValue(vNode, attrStandard) {
function hasPropertyValue(node, attrStandard) {
const { prop } = attrStandard;
if (prop && vNode.actualNode) {
const propValue = vNode.actualNode[prop];
const { domNode } = nodeLookup(node);
if (prop && domNode) {
const propValue = domNode[prop];
return propValue !== null && propValue !== undefined;
}
return false;
}

function hasInternalValue(vNode, attrStandard) {
function hasInternalValue(node, attrStandard) {
const { vNode, domNode } = nodeLookup(node);
const { prop } = attrStandard;
if (prop && vNode.elementInternals) {
const internalsValue = vNode.elementInternals[prop];
const internals = vNode
? vNode.elementInternals
: getElementInternals(domNode);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, need to put the feature flag gate in. Or maybe put that into getElementInternals directly?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't want to put it directly into getElementInternals as it's used by the gather-internals script and that couldn't really use a global axe due to the extensions needing it.


if (prop && internals) {
const internalsValue = internals[prop];
return internalsValue !== null && internalsValue !== undefined;
}
return false;
Expand Down
25 changes: 25 additions & 0 deletions test/commons/aria/get-aria-value.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,31 @@ describe('aria.getAriaValue', () => {
assert.isNull(getAriaValue(vNode, 'aria-label'));
});

it('returns attribute from an element not in the tree', () => {
fixture.innerHTML = html`<div id="target" aria-label="hello"></div>`;
const node = fixture.querySelector('#target');

const result = getAriaValue(node, 'aria-label');
assert.deepEqual(result, {
value: 'hello',
source: 'attribute'
});
});

it('returns element internals from an element not in the tree', () => {
fixture.innerHTML = html`<testutils-element
id="target"
with-aria-label="hello"
></testutils-element>`;
const node = fixture.querySelector('#target');

const result = getAriaValue(node, 'aria-label');
assert.deepEqual(result, {
value: 'hello',
source: 'internals'
});
});

describe('idref', () => {
it('returns the attribute value over the property value', () => {
const vNode = queryFixture(
Expand Down
19 changes: 18 additions & 1 deletion test/commons/aria/has-aria-value.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
describe('aria.hasAriaValue', () => {
const { queryFixture, html } = axe.testUtils;
const { queryFixture, fixture, html } = axe.testUtils;
const hasAriaValue = axe.commons.aria.hasAriaValue;
const SerialVirtualNode = axe.SerialVirtualNode;

Expand Down Expand Up @@ -78,6 +78,23 @@ describe('aria.hasAriaValue', () => {
assert.isFalse(hasAriaValue(vNode, 'aria-label'));
});

it('returns true if element not in the tree has attribute', () => {
fixture.innerHTML = html`<div id="target" aria-label="hello"></div>`;
const node = fixture.querySelector('#target');

assert.isTrue(hasAriaValue(node, 'aria-label'));
});

it('returns true if element not in the tree has elementInternals', () => {
fixture.innerHTML = html`<testutils-element
id="target"
with-aria-label="hello"
></testutils-element>`;
const node = fixture.querySelector('#target');

assert.isTrue(hasAriaValue(node, 'aria-label'));
});

describe('SerialVirtualNode', () => {
it('returns true if element has attribute', () => {
// SerialVirtualNode will not support `props` or `elementInternals` so everything must be part of the `attributes` property
Expand Down
Loading