Repository: ymrl/a11y-visualizer
Pull request #148 (2024-06-23 01:16:32)

show nestedInteractive error when interactive element is children


Patch:
diff --git a/src/content/dom/tips/buttonTips.ts b/src/content/dom/tips/buttonTips.ts
index bf1c3b2..dc1d893 100644
--- a/src/content/dom/tips/buttonTips.ts
+++ b/src/content/dom/tips/buttonTips.ts
@@ -35,6 +35,8 @@ export const buttonTips = (
   name: string = computeAccessibleName(el),
 ): ElementTip[] => {
   const result: ElementTip[] = [];
+  const tagName = el.tagName.toLowerCase();
+  const role = el.getAttribute("role");
 
   if (isButton(el)) {
     if (!name && !isAriaHidden(el)) {
@@ -43,7 +45,17 @@ export const buttonTips = (
     if (!isFocusable(el)) {
       result.push({ type: "error", content: "messages.notFocusable" });
     }
-    if (hasInteractiveDescendant(el) || hasTabIndexDescendant(el)) {
+    if (
+      (tagName === "button" || role === "button") &&
+      (hasInteractiveDescendant(el) || hasTabIndexDescendant(el))
+    ) {
+      result.push({ type: "error", content: "messages.nestedInteractive" });
+    }
+    if (
+      (tagName === "button" || role === "button") &&
+      el.parentElement &&
+      el.parentElement.closest('a, button, [role="button"]')
+    ) {
       result.push({ type: "error", content: "messages.nestedInteractive" });
     }
   }
diff --git a/src/content/dom/tips/formTips.ts b/src/content/dom/tips/formTips.ts
index 2707756..5265dd0 100644
--- a/src/content/dom/tips/formTips.ts
+++ b/src/content/dom/tips/formTips.ts
@@ -86,6 +86,13 @@ export const formTips = (
     if (!isFocusable(el)) {
       result.push({ type: "error", content: "messages.notFocusable" });
     }
+
+    if (
+      el.parentElement &&
+      el.parentElement.closest('a, button, [role="button"]')
+    ) {
+      result.push({ type: "error", content: "messages.nestedInteractive" });
+    }
   }
   if (tagName === "input" && typeAttr === "radio") {
     const nameAttr = el.getAttribute("name");
diff --git a/src/content/dom/tips/linkTips.ts b/src/content/dom/tips/linkTips.ts
index e0a1fc1..b9a7792 100644
--- a/src/content/dom/tips/linkTips.ts
+++ b/src/content/dom/tips/linkTips.ts
@@ -32,9 +32,8 @@ export const linkTips = (
       result.push({ type: "error", content: "messages.noName" });
     }
     if (
-      hasInteractiveDescendant(el) ||
-      hasTabIndexDescendant(el) ||
-      (el.tagName.toLowerCase() === "a" && el.querySelector("a"))
+      el.parentElement &&
+      el.parentElement.closest('a, button, [role="button"]')
     ) {
       result.push({ type: "error", content: "messages.nestedInteractive" });
     }
@@ -46,6 +45,13 @@ export const linkTips = (
     if (target) {
       result.push({ type: "linkTarget", content: target });
     }
+    if (
+      hasInteractiveDescendant(el) ||
+      hasTabIndexDescendant(el) ||
+      (el.tagName.toLowerCase() === "a" && el.querySelector("a"))
+    ) {
+      result.push({ type: "error", content: "messages.nestedInteractive" });
+    }
   }
   if (hasRole && !isFocusable(el)) {
     result.push({ type: "error", content: "messages.notFocusable" });


Test patch:
diff --git a/src/content/dom/tips/buttonTips.test.ts b/src/content/dom/tips/buttonTips.test.ts
index 595e5aa..0c64e6f 100644
--- a/src/content/dom/tips/buttonTips.test.ts
+++ b/src/content/dom/tips/buttonTips.test.ts
@@ -277,4 +277,18 @@ describe("buttonTips()", () => {
     expect(result[0].type).toBe("error");
     expect(result[0].content).toBe("messages.nestedInteractive");
   });
+
+  test("nested interactive parent", () => {
+    const link = document.createElement("a");
+    link.textContent = "Hello";
+    link.href = "http://example.com";
+    const button = document.createElement("button");
+    button.textContent = "world";
+    link.appendChild(button);
+    document.body.appendChild(link);
+    const result = buttonTips(button);
+    expect(result).toHaveLength(1);
+    expect(result[0].type).toBe("error");
+    expect(result[0].content).toBe("messages.nestedInteractive");
+  });
 });
diff --git a/src/content/dom/tips/formTips.test.ts b/src/content/dom/tips/formTips.test.ts
index 558aa37..cb9e582 100644
--- a/src/content/dom/tips/formTips.test.ts
+++ b/src/content/dom/tips/formTips.test.ts
@@ -167,4 +167,20 @@ describe("formTips()", () => {
       ),
     ).toBeDefined();
   });
+
+  test("nested interactive", () => {
+    const element = document.createElement("input");
+    document.body.appendChild(element);
+    element.setAttribute("aria-label", "Name");
+    const parent = document.createElement("a");
+    parent.appendChild(element);
+    document.body.appendChild(parent);
+    const result = formTips(element);
+    expect(result).toHaveLength(1);
+    expect(
+      result.find(
+        (t) => t.type === "error" && t.content === "messages.nestedInteractive",
+      ),
+    ).toBeDefined();
+  });
 });
diff --git a/src/content/dom/tips/linkTips.test.ts b/src/content/dom/tips/linkTips.test.ts
index 4fa5ea1..cf624c5 100644
--- a/src/content/dom/tips/linkTips.test.ts
+++ b/src/content/dom/tips/linkTips.test.ts
@@ -189,4 +189,22 @@ describe("linkTips()", () => {
       ),
     ).toBeDefined();
   });
+
+  test("nested a in parent", () => {
+    const parent = document.createElement("a");
+    parent.textContent = "Hello";
+    parent.href = "https://example.com";
+    const child = document.createElement("a");
+    child.textContent = "world";
+    child.href = "https://example.com";
+    parent.appendChild(child);
+    document.body.appendChild(parent);
+    const result = linkTips(child);
+    expect(result).toHaveLength(1);
+    expect(
+      result.find(
+        (t) => t.type === "error" && t.content === "messages.nestedInteractive",
+      ),
+    ).toBeDefined();
+  });
 });