chore: remove the leaf node notion (#33249)

This commit is contained in:
Pavel Feldman 2024-10-23 17:34:21 -07:00 committed by GitHub
parent 24cafbc8cb
commit 9a0a6cec10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 46 additions and 56 deletions

View file

@ -16,7 +16,7 @@
import { escapeWithQuotes } from '@isomorphic/stringUtils'; import { escapeWithQuotes } from '@isomorphic/stringUtils';
import * as roleUtils from './roleUtils'; import * as roleUtils from './roleUtils';
import { isElementVisible, isElementStyleVisibilityVisible, getElementComputedStyle } from './domUtils'; import { getElementComputedStyle } from './domUtils';
import type { AriaRole } from './roleUtils'; import type { AriaRole } from './roleUtils';
type AriaProps = { type AriaProps = {
@ -29,7 +29,7 @@ type AriaProps = {
}; };
type AriaNode = AriaProps & { type AriaNode = AriaProps & {
role: AriaRole | 'fragment' | 'text'; role: AriaRole | 'fragment';
name: string; name: string;
children: (AriaNode | string)[]; children: (AriaNode | string)[];
}; };
@ -56,22 +56,10 @@ export function generateAriaTree(rootElement: Element): AriaNode {
if (roleUtils.isElementHiddenForAria(element)) if (roleUtils.isElementHiddenForAria(element))
return; return;
const visible = isElementVisible(element); const childAriaNode = toAriaNode(element);
const hasVisibleChildren = isElementStyleVisibilityVisible(element); if (childAriaNode)
ariaNode.children.push(childAriaNode);
if (!hasVisibleChildren) processChildNodes(childAriaNode || ariaNode, element);
return;
if (visible) {
const childAriaNode = toAriaNode(element);
const isHiddenContainer = childAriaNode && hiddenContainerRoles.has(childAriaNode.ariaNode.role);
if (childAriaNode && !isHiddenContainer)
ariaNode.children.push(childAriaNode.ariaNode);
if (isHiddenContainer || !childAriaNode?.isLeaf)
processChildNodes(childAriaNode?.ariaNode || ariaNode, element);
} else {
processChildNodes(ariaNode, element);
}
}; };
function processChildNodes(ariaNode: AriaNode, element: Element) { function processChildNodes(ariaNode: AriaNode, element: Element) {
@ -101,6 +89,9 @@ export function generateAriaTree(rootElement: Element): AriaNode {
if (treatAsBlock) if (treatAsBlock)
ariaNode.children.push(treatAsBlock); ariaNode.children.push(treatAsBlock);
if (ariaNode.children.length === 1 && ariaNode.name === ariaNode.children[0])
ariaNode.children = [];
} }
roleUtils.beginAriaCaches(); roleUtils.beginAriaCaches();
@ -115,19 +106,13 @@ export function generateAriaTree(rootElement: Element): AriaNode {
return ariaRoot; return ariaRoot;
} }
function toAriaNode(element: Element): { ariaNode: AriaNode, isLeaf: boolean } | null { function toAriaNode(element: Element): AriaNode | null {
const role = roleUtils.getAriaRole(element); const role = roleUtils.getAriaRole(element);
if (!role) if (!role)
return null; return null;
const name = roleUtils.getElementAccessibleName(element, false) || ''; const name = roleUtils.getElementAccessibleName(element, false) || '';
const isLeaf = leafRoles.has(role);
const result: AriaNode = { role, name, children: [] }; const result: AriaNode = { role, name, children: [] };
if (isLeaf && !name) {
const text = roleUtils.accumulatedElementText(element);
if (text)
result.children = [text];
}
if (roleUtils.kAriaCheckedRoles.includes(role)) if (roleUtils.kAriaCheckedRoles.includes(role))
result.checked = roleUtils.getAriaChecked(element); result.checked = roleUtils.getAriaChecked(element);
@ -147,7 +132,7 @@ function toAriaNode(element: Element): { ariaNode: AriaNode, isLeaf: boolean } |
if (roleUtils.kAriaSelectedRoles.includes(role)) if (roleUtils.kAriaSelectedRoles.includes(role))
result.selected = roleUtils.getAriaSelected(element); result.selected = roleUtils.getAriaSelected(element);
return { isLeaf, ariaNode: result }; return result;
} }
export function renderedAriaTree(rootElement: Element): string { export function renderedAriaTree(rootElement: Element): string {
@ -178,21 +163,12 @@ function normalizeStringChildren(rootA11yNode: AriaNode) {
} }
flushChildren(buffer, normalizedChildren); flushChildren(buffer, normalizedChildren);
ariaNode.children = normalizedChildren.length ? normalizedChildren : []; ariaNode.children = normalizedChildren.length ? normalizedChildren : [];
if (ariaNode.children.length === 1 && ariaNode.children[0] === ariaNode.name)
ariaNode.children = [];
}; };
visit(rootA11yNode); visit(rootA11yNode);
} }
const hiddenContainerRoles = new Set(['none', 'presentation']);
const leafRoles = new Set<AriaRole>([
'alert', 'blockquote', 'button', 'caption', 'checkbox', 'code', 'columnheader',
'definition', 'deletion', 'emphasis', 'generic', 'heading', 'img', 'insertion',
'link', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'meter', 'option',
'progressbar', 'radio', 'rowheader', 'scrollbar', 'searchbox', 'separator',
'slider', 'spinbutton', 'strong', 'subscript', 'superscript', 'switch', 'tab', 'term',
'textbox', 'time', 'tooltip'
]);
const normalizeWhitespaceWithin = (text: string) => text.replace(/[\s\t\r\n]+/g, ' '); const normalizeWhitespaceWithin = (text: string) => text.replace(/[\s\t\r\n]+/g, ' ');
function matchesText(text: string | undefined, template: RegExp | string | undefined) { function matchesText(text: string | undefined, template: RegExp | string | undefined) {
@ -305,15 +281,7 @@ export function renderAriaTree(ariaNode: AriaNode, options?: { noText?: boolean
if (ariaNode.selected === true) if (ariaNode.selected === true)
line += ` [selected]`; line += ` [selected]`;
const stringValue = !ariaNode.children.length || (ariaNode.children?.length === 1 && typeof ariaNode.children[0] === 'string'); lines.push(line + (ariaNode.children.length ? ':' : ''));
if (stringValue) {
if (!options?.noText && ariaNode.children.length)
line += ': ' + quoteYamlString(ariaNode.children?.[0] as string);
lines.push(line);
return;
}
lines.push(line + ':');
for (const child of ariaNode.children || []) for (const child of ariaNode.children || [])
visit(child, indent + ' '); visit(child, indent + ' ');
}; };

View file

@ -64,8 +64,10 @@ it('should snapshot list with accessible name', async ({ page }) => {
`); `);
await checkAndMatchSnapshot(page.locator('body'), ` await checkAndMatchSnapshot(page.locator('body'), `
- list "my list": - list "my list":
- listitem: "one" - listitem:
- listitem: "two" - text: "one"
- listitem:
- text: "two"
`); `);
}); });
@ -105,7 +107,8 @@ it('should snapshot details visibility', async ({ page }) => {
`); `);
await checkAndMatchSnapshot(page.locator('body'), ` await checkAndMatchSnapshot(page.locator('body'), `
- group: "Summary" - group:
- text: "Summary"
`); `);
}); });
@ -148,7 +151,8 @@ it('should snapshot integration', async ({ page }) => {
- text: "Open source projects and samples from Microsoft" - text: "Open source projects and samples from Microsoft"
- list: - list:
- listitem: - listitem:
- group: "Verified" - group:
- text: "Verified"
- listitem: - listitem:
- link "Sponsor" - link "Sponsor"
`); `);
@ -164,13 +168,15 @@ it('should support multiline text', async ({ page }) => {
`); `);
await checkAndMatchSnapshot(page.locator('body'), ` await checkAndMatchSnapshot(page.locator('body'), `
- paragraph: "Line 1 Line 2 Line 3" - paragraph:
- text: "Line 1 Line 2 Line 3"
`); `);
await expect(page.locator('body')).toMatchAriaSnapshot(` await expect(page.locator('body')).toMatchAriaSnapshot(`
- paragraph: | - paragraph:
Line 1 - text: |
Line 2 Line 1
Line 3 Line 2
Line 3
`); `);
}); });
@ -382,6 +388,22 @@ it('should include pseudo codepoints', async ({ page, server }) => {
`); `);
await checkAndMatchSnapshot(page.locator('body'), ` await checkAndMatchSnapshot(page.locator('body'), `
- paragraph: "\ueab2hello" - paragraph:
- text: "\ueab2hello"
`);
});
it('check aria-hidden text', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`
<p>
<span>hello</span>
<span aria-hidden="true">world</span>
</p>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- paragraph:
- text: "hello"
`); `);
}); });