Repository: ymrl/a11y-visualizer (ts)

Issue:
リンクのtargetを可視化する
新しいウインドウ/タブが開くことをテキストやアイコン等で表示している場合があるが、それが徹底できているかをリンクをクリックせずに判断したい（Webサイト内で、新しいウィンドウ/タブが開くリンクで、そういったテキストやアイコンがあるものと無いものが混在することが無いようにしたい）

a要素に対してtarget属性の値を表示することができるとよさそう

Pull request:
:+1: add linkTarget tip
close #141 

Patch:
diff --git a/src/content/components/Tip.css b/src/content/components/Tip.css
index 599a956..8ea8846 100644
--- a/src/content/components/Tip.css
+++ b/src/content/components/Tip.css
@@ -36,6 +36,9 @@
 .Tip--tablePosition {
   background-color: var(--base-beige);
 }
+.Tip--linkTarget {
+  background-color: var(--base-beige);
+}
 
 .Tip--description {
   background-color: var(--light-gray);
diff --git a/src/content/components/Tip.tsx b/src/content/components/Tip.tsx
index c471059..6d6121e 100644
--- a/src/content/components/Tip.tsx
+++ b/src/content/components/Tip.tsx
@@ -12,6 +12,7 @@ import {
   IoGridOutline,
   IoInformationCircle,
   IoLanguage,
+  IoOpenOutline,
   IoPin,
   IoPricetag,
   IoWarning,
@@ -117,6 +118,15 @@ const Icon = ({ type }: { type: TipType }) => {
         />
       );
 
+    case "linkTarget":
+      return (
+        <IoOpenOutline
+          className="Tip__icon"
+          role="img"
+          aria-label={t("tip.linkTarget")}
+        />
+      );
+
     case "pageTitle":
       return (
         <IoBrowsersOutline
diff --git a/src/content/dom/tips/linkTips.ts b/src/content/dom/tips/linkTips.ts
index 42c5705..f99ad50 100644
--- a/src/content/dom/tips/linkTips.ts
+++ b/src/content/dom/tips/linkTips.ts
@@ -23,6 +23,7 @@ export const linkTips = (
   const hasTag = hasLinkTag(el);
   const hasRole = hasLinkRole(el);
   const href = el.getAttribute("href");
+  const target = el.getAttribute("target");
 
   if (hasTag || hasRole) {
     if (!name && (hasRole || href) && !hidden) {
@@ -33,6 +34,9 @@ export const linkTips = (
     if (!el.hasAttribute("href")) {
       result.push({ type: "warning", content: "messages.noHref" });
     }
+    if (target) {
+      result.push({ type: "linkTarget", content: target });
+    }
   }
   if (hasRole && !isFocusable(el)) {
     result.push({ type: "error", content: "messages.notFocusable" });
diff --git a/src/content/types.ts b/src/content/types.ts
index 9d4f3ad..0a0e508 100644
--- a/src/content/types.ts
+++ b/src/content/types.ts
@@ -25,6 +25,7 @@ export type TipType =
   | "tableHeader"
   | "tableSize"
   | "tablePosition"
+  | "linkTarget"
   | "warning"
   | "error";
 export type ElementTip = {
diff --git a/src/i18n/en.json b/src/i18n/en.json
index a3a590e..665d789 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -46,6 +46,7 @@
     "tableHeader": "Table header",
     "tableSize": "Table size",
     "tablePosition": "Position",
+    "linkTarget": "Link Target",
     "lang": "Language",
     "warning": "Warning",
     "error": "Error"
diff --git a/src/i18n/ja.json b/src/i18n/ja.json
index 7c76006..f70b313 100644
--- a/src/i18n/ja.json
+++ b/src/i18n/ja.json
@@ -46,6 +46,7 @@
     "tableHeader": "表見出し",
     "tableSize": "表の大きさ",
     "tablePosition": "位置",
+    "linkTarget": "リンクのターゲット",
     "lang": "言語",
     "warning": "警告",
     "error": "エラー"


Test patch:
diff --git a/src/content/dom/tips/linkTips.test.ts b/src/content/dom/tips/linkTips.test.ts
index 6ace05d..c2b5a6b 100644
--- a/src/content/dom/tips/linkTips.test.ts
+++ b/src/content/dom/tips/linkTips.test.ts
@@ -51,6 +51,20 @@ describe("linkTips()", () => {
     expect(result).toHaveLength(0);
   });
 
+  test("a with target", () => {
+    const element = document.createElement("a");
+    element.textContent = "Hello";
+    element.href = "https://example.com";
+    element.target = "_blank";
+    document.body.appendChild(element);
+    const result = linkTips(element);
+    expect(result).toHaveLength(1);
+    expect(result.find((t) => t.type === "linkTarget")).toEqual({
+      type: "linkTarget",
+      content: "_blank",
+    });
+  });
+
   test("a without href", () => {
     const element = document.createElement("a");
     element.textContent = "Hello";