From 4685270af0d1f737d38a0c06b59137c967679227 Mon Sep 17 00:00:00 2001
From: Steven Lambert <2433219+straker@users.noreply.github.com>
Date: Fri, 3 Jun 2022 08:26:28 -0600
Subject: [PATCH] fix(aria-required-parent): allow nested group and
 presentational roles (#3492)

* fix(aria-required-parent): allow nested group and presentational roles

* update

* remove comments
---
 .../aria/aria-required-parent-evaluate.js     | 25 +++++++++----------
 test/checks/aria/required-parent.js           | 11 ++++++++
 .../aria-required-parent.html                 | 12 +++++++++
 .../aria-required-parent.json                 |  3 ++-
 4 files changed, 37 insertions(+), 14 deletions(-)

diff --git a/lib/checks/aria/aria-required-parent-evaluate.js b/lib/checks/aria/aria-required-parent-evaluate.js
index d29e0789a0..c985cedc27 100644
--- a/lib/checks/aria/aria-required-parent-evaluate.js
+++ b/lib/checks/aria/aria-required-parent-evaluate.js
@@ -18,31 +18,30 @@ function getMissingContext(
     return null;
   }
 
+  const allowsGroup = reqContext.includes('group');
   let vNode = includeElement ? virtualNode : virtualNode.parent;
+
   while (vNode) {
-    const parentRole = getRole(vNode);
+    const role = getRole(vNode, { noPresentational: true });
 
-    // if parent node has role=group and role=group is an allowed
-    // context, check next parent
-    if (reqContext.includes('group') && parentRole === 'group') {
+    // if parent node has no role or is presentational, or if role
+    // allows group, we keep parsing the parent tree.
+    // this means intermediate roles between a required parent and
+    // child will fail the check
+    if (!role) {
+      vNode = vNode.parent;
+    } else if (role === 'group' && allowsGroup) {
       // Allow the own role; i.e. tree > treeitem > group > treeitem
       if (ownGroupRoles.includes(explicitRole)) {
         reqContext.push(explicitRole);
       }
       reqContext = reqContext.filter(r => r !== 'group');
       vNode = vNode.parent;
-      continue;
-    }
-
-    // if parent node has a role that is not the required role and not
-    // presentational we will fail the check
-    if (reqContext.includes(parentRole)) {
+    } else if (reqContext.includes(role)) {
       return null;
-    } else if (parentRole && !['presentation', 'none'].includes(parentRole)) {
+    } else {
       return reqContext;
     }
-
-    vNode = vNode.parent;
   }
 
   return reqContext;
diff --git a/test/checks/aria/required-parent.js b/test/checks/aria/required-parent.js
index 1933be5992..adbb3ea33a 100644
--- a/test/checks/aria/required-parent.js
+++ b/test/checks/aria/required-parent.js
@@ -243,6 +243,17 @@ describe('aria-required-parent', function() {
     );
   });
 
+  it('should pass for multiple group and presentational roles', function() {
+    var params = checkSetup(
+      '<div role="list"><div role="none"><div role="group"><div role="none"><div role="group"><div role="listitem" id="target">Nothing here.</div></div></div></div></div></div>'
+    );
+    assert.isTrue(
+      axe.testUtils
+        .getCheckEvaluate('aria-required-parent')
+        .apply(checkContext, params)
+    );
+  });
+
   (shadowSupported ? it : xit)(
     'should pass when required parent is present across shadow boundary',
     function() {
diff --git a/test/integration/rules/aria-required-parent/aria-required-parent.html b/test/integration/rules/aria-required-parent/aria-required-parent.html
index be9ef5a4d9..d28fab7d80 100644
--- a/test/integration/rules/aria-required-parent/aria-required-parent.html
+++ b/test/integration/rules/aria-required-parent/aria-required-parent.html
@@ -64,3 +64,15 @@
     <div role="option" id="pass13">option</div>
   </div>
 </div>
+
+<div role="tree">
+  <ul role="group">
+    <li role="none">
+      <ul role="group">
+        <li role="none">
+          <div role="treeitem" id="pass14">tree item</div>
+        </li>
+      </ul>
+    </li>
+  </ul>
+</div>
diff --git a/test/integration/rules/aria-required-parent/aria-required-parent.json b/test/integration/rules/aria-required-parent/aria-required-parent.json
index 0092d17059..aa833134c7 100644
--- a/test/integration/rules/aria-required-parent/aria-required-parent.json
+++ b/test/integration/rules/aria-required-parent/aria-required-parent.json
@@ -23,6 +23,7 @@
     ["#pass10"],
     ["#pass11"],
     ["#pass12"],
-    ["#pass13"]
+    ["#pass13"],
+    ["#pass14"]
   ]
 }