chore(snapshot): support aria-owns (#33404)
This commit is contained in:
		
							parent
							
								
									3cd753f6bb
								
							
						
					
					
						commit
						1d4650cea2
					
				|  | @ -50,7 +50,12 @@ export type AriaTemplateRoleNode = AriaProps & { | ||||||
| export type AriaTemplateNode = AriaTemplateRoleNode | AriaTemplateTextNode; | export type AriaTemplateNode = AriaTemplateRoleNode | AriaTemplateTextNode; | ||||||
| 
 | 
 | ||||||
| export function generateAriaTree(rootElement: Element): AriaNode { | export function generateAriaTree(rootElement: Element): AriaNode { | ||||||
|  |   const visited = new Set<Node>(); | ||||||
|   const visit = (ariaNode: AriaNode, node: Node) => { |   const visit = (ariaNode: AriaNode, node: Node) => { | ||||||
|  |     if (visited.has(node)) | ||||||
|  |       return; | ||||||
|  |     visited.add(node); | ||||||
|  | 
 | ||||||
|     if (node.nodeType === Node.TEXT_NODE && node.nodeValue) { |     if (node.nodeType === Node.TEXT_NODE && node.nodeValue) { | ||||||
|       const text = node.nodeValue; |       const text = node.nodeValue; | ||||||
|       if (text) |       if (text) | ||||||
|  | @ -65,13 +70,23 @@ export function generateAriaTree(rootElement: Element): AriaNode { | ||||||
|     if (roleUtils.isElementHiddenForAria(element)) |     if (roleUtils.isElementHiddenForAria(element)) | ||||||
|       return; |       return; | ||||||
| 
 | 
 | ||||||
|  |     const ariaChildren: Element[] = []; | ||||||
|  |     if (element.hasAttribute('aria-owns')) { | ||||||
|  |       const ids = element.getAttribute('aria-owns')!.split(/\s+/); | ||||||
|  |       for (const id of ids) { | ||||||
|  |         const ownedElement = rootElement.ownerDocument.getElementById(id); | ||||||
|  |         if (ownedElement) | ||||||
|  |           ariaChildren.push(ownedElement); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const childAriaNode = toAriaNode(element); |     const childAriaNode = toAriaNode(element); | ||||||
|     if (childAriaNode) |     if (childAriaNode) | ||||||
|       ariaNode.children.push(childAriaNode); |       ariaNode.children.push(childAriaNode); | ||||||
|     processChildNodes(childAriaNode || ariaNode, element); |     processElement(childAriaNode || ariaNode, element, ariaChildren); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   function processChildNodes(ariaNode: AriaNode, element: Element) { |   function processElement(ariaNode: AriaNode, element: Element, ariaChildren: Element[] = []) { | ||||||
|     // Surround every element with spaces for the sake of concatenated text nodes.
 |     // Surround every element with spaces for the sake of concatenated text nodes.
 | ||||||
|     const display = getElementComputedStyle(element)?.display || 'inline'; |     const display = getElementComputedStyle(element)?.display || 'inline'; | ||||||
|     const treatAsBlock = (display !== 'inline' || element.nodeName === 'BR') ? ' ' : ''; |     const treatAsBlock = (display !== 'inline' || element.nodeName === 'BR') ? ' ' : ''; | ||||||
|  | @ -94,6 +109,9 @@ export function generateAriaTree(rootElement: Element): AriaNode { | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     for (const child of ariaChildren) | ||||||
|  |       visit(ariaNode, child); | ||||||
|  | 
 | ||||||
|     ariaNode.children.push(roleUtils.getPseudoContent(element, '::after')); |     ariaNode.children.push(roleUtils.getPseudoContent(element, '::after')); | ||||||
| 
 | 
 | ||||||
|     if (treatAsBlock) |     if (treatAsBlock) | ||||||
|  |  | ||||||
|  | @ -421,3 +421,41 @@ it('should treat input value as text in templates', async ({ page }) => { | ||||||
|     - textbox: hello world |     - textbox: hello world | ||||||
|   `);
 |   `);
 | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | it('should respect aria-owns', async ({ page }) => { | ||||||
|  |   await page.setContent(` | ||||||
|  |     <a href='about:blank' aria-owns='input p'> | ||||||
|  |       <div role='region'>Link 1</div> | ||||||
|  |     </a> | ||||||
|  |     <a href='about:blank' aria-owns='input p'> | ||||||
|  |       <div role='region'>Link 2</div> | ||||||
|  |     </a> | ||||||
|  |     <input id='input' value='Value'> | ||||||
|  |     <p id='p'>Paragraph</p> | ||||||
|  |   `);
 | ||||||
|  | 
 | ||||||
|  |   // - Different from Chrome DevTools which attributes ownership to the last element.
 | ||||||
|  |   // - CDT also does not include non-owned children in accessible name.
 | ||||||
|  |   // - Disregarding these as aria-owns can't suggest multiple parts by spec.
 | ||||||
|  |   await checkAndMatchSnapshot(page.locator('body'), ` | ||||||
|  |     - link "Link 1 Value Paragraph": | ||||||
|  |       - region: Link 1 | ||||||
|  |       - textbox: Value | ||||||
|  |       - paragraph: Paragraph | ||||||
|  |     - link "Link 2 Value Paragraph": | ||||||
|  |       - region: Link 2 | ||||||
|  |   `);
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | it('should be ok with circular ownership', async ({ page }) => { | ||||||
|  |   await page.setContent(` | ||||||
|  |     <a href='about:blank' id='parent'> | ||||||
|  |       <div role='region' aria-owns='parent'>Hello</div> | ||||||
|  |     </a> | ||||||
|  |   `);
 | ||||||
|  | 
 | ||||||
|  |   await checkAndMatchSnapshot(page.locator('body'), ` | ||||||
|  |     - link "Hello": | ||||||
|  |       - region: Hello | ||||||
|  |   `);
 | ||||||
|  | }); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue