From 6a674fbc53c265e27b87f86e4db032433733f2f8 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Mon, 21 Sep 2020 21:09:01 +0900
Subject: Introduce styled-components on form fields

---
 .../components/form/PartialBlacklistForm.tsx       | 117 +++++++++++++--------
 1 file changed, 76 insertions(+), 41 deletions(-)

(limited to 'src/settings/components/form/PartialBlacklistForm.tsx')

diff --git a/src/settings/components/form/PartialBlacklistForm.tsx b/src/settings/components/form/PartialBlacklistForm.tsx
index 95beee8..4c6bd35 100644
--- a/src/settings/components/form/PartialBlacklistForm.tsx
+++ b/src/settings/components/form/PartialBlacklistForm.tsx
@@ -1,9 +1,36 @@
-import "./PartialBlacklistForm.scss";
+import React from "react";
+import styled from "styled-components";
 import AddButton from "../ui/AddButton";
 import DeleteButton from "../ui/DeleteButton";
-import React from "react";
 import Blacklist, { BlacklistItem } from "../../../shared/settings/Blacklist";
 
+const Grid = styled.div``;
+
+const GridRow = styled.div`
+  display: flex;
+`;
+
+const GridCell = styled.div<{ grow?: number }>`
+  &:nth-child(1) {
+    flex-grow: 5;
+  }
+
+  &:nth-child(2) {
+    flex-shrink: 1;
+    min-width: 20%;
+    max-width: 20%;
+  }
+
+  &:nth-child(3) {
+    flex-shrink: 1;
+  }
+`;
+
+const Input = styled.input`
+  width: 100%;
+  box-sizing: border-box;
+`;
+
 interface Props {
   value: Blacklist;
   onChange: (value: Blacklist) => void;
@@ -19,50 +46,58 @@ class PartialBlacklistForm extends React.Component<Props> {
 
   render() {
     return (
-      <div className="form-partial-blacklist-form">
-        <div className="form-partial-blacklist-form-header">
-          <div className="column-url">URL</div>
-          <div className="column-keys">Keys</div>
-        </div>
-        {this.props.value.items.map((item, index) => {
-          if (!item.partial) {
-            return null;
-          }
-          return (
-            <div key={index} className="form-partial-blacklist-form-row">
-              <input
-                data-index={index}
-                type="text"
-                name="url"
-                className="column-url"
-                value={item.pattern}
-                onChange={this.bindValue.bind(this)}
-                onBlur={this.props.onBlur}
-              />
-              <input
-                data-index={index}
-                type="text"
-                name="keys"
-                className="column-keys"
-                value={item.keys.join(",")}
-                onChange={this.bindValue.bind(this)}
-                onBlur={this.props.onBlur}
-              />
-              <DeleteButton
-                data-index={index}
-                name="delete"
-                onClick={this.bindValue.bind(this)}
-                onBlur={this.props.onBlur}
-              />
-            </div>
-          );
-        })}
+      <>
+        <Grid>
+          <GridRow>
+            <GridCell>URL</GridCell>
+            <GridCell>Keys</GridCell>
+          </GridRow>
+          {this.props.value.items.map((item, index) => {
+            if (!item.partial) {
+              return null;
+            }
+            return (
+              <GridRow key={index}>
+                <GridCell>
+                  <Input
+                    data-index={index}
+                    type="text"
+                    name="url"
+                    value={item.pattern}
+                    placeholder="example.com/mail/*"
+                    onChange={this.bindValue.bind(this)}
+                    onBlur={this.props.onBlur}
+                  />
+                </GridCell>
+                <GridCell>
+                  <Input
+                    data-index={index}
+                    type="text"
+                    name="keys"
+                    value={item.keys.join(",")}
+                    placeholder="j,k,<C-d>,<C-u>"
+                    onChange={this.bindValue.bind(this)}
+                    onBlur={this.props.onBlur}
+                  />
+                </GridCell>
+                <GridCell>
+                  <DeleteButton
+                    data-index={index}
+                    name="delete"
+                    onClick={this.bindValue.bind(this)}
+                    onBlur={this.props.onBlur}
+                  />
+                </GridCell>
+              </GridRow>
+            );
+          })}
+        </Grid>
         <AddButton
           name="add"
           style={{ float: "right" }}
           onClick={this.bindValue.bind(this)}
         />
-      </div>
+      </>
     );
   }
 
-- 
cgit v1.2.3


From e1e7c2d4d86d7aeb40357add27c76a99a18350e7 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Mon, 21 Sep 2020 21:15:46 +0900
Subject: Introduce styled-components on top of form

---
 .../components/form/PartialBlacklistForm.tsx       |  9 +-
 src/settings/components/form/SearchForm.tsx        |  9 +-
 src/settings/components/index.tsx                  | 96 +++++++++++++---------
 src/settings/components/site.scss                  | 21 -----
 4 files changed, 72 insertions(+), 63 deletions(-)
 delete mode 100644 src/settings/components/site.scss

(limited to 'src/settings/components/form/PartialBlacklistForm.tsx')

diff --git a/src/settings/components/form/PartialBlacklistForm.tsx b/src/settings/components/form/PartialBlacklistForm.tsx
index 4c6bd35..dcdd00c 100644
--- a/src/settings/components/form/PartialBlacklistForm.tsx
+++ b/src/settings/components/form/PartialBlacklistForm.tsx
@@ -6,6 +6,11 @@ import Blacklist, { BlacklistItem } from "../../../shared/settings/Blacklist";
 
 const Grid = styled.div``;
 
+const GridHeader = styled.div`
+  display: flex;
+  font-weight: bold;
+`;
+
 const GridRow = styled.div`
   display: flex;
 `;
@@ -48,10 +53,10 @@ class PartialBlacklistForm extends React.Component<Props> {
     return (
       <>
         <Grid>
-          <GridRow>
+          <GridHeader>
             <GridCell>URL</GridCell>
             <GridCell>Keys</GridCell>
-          </GridRow>
+          </GridHeader>
           {this.props.value.items.map((item, index) => {
             if (!item.partial) {
               return null;
diff --git a/src/settings/components/form/SearchForm.tsx b/src/settings/components/form/SearchForm.tsx
index 3ba0299..cc7061a 100644
--- a/src/settings/components/form/SearchForm.tsx
+++ b/src/settings/components/form/SearchForm.tsx
@@ -6,6 +6,11 @@ import { FormSearch } from "../../../shared/SettingData";
 
 const Grid = styled.div``;
 
+const GridHeader = styled.div`
+  display: flex;
+  font-weight: bold;
+`;
+
 const GridRow = styled.div`
   display: flex;
 `;
@@ -50,11 +55,11 @@ class SearchForm extends React.Component<Props> {
     return (
       <>
         <Grid>
-          <GridRow>
+          <GridHeader>
             <GridCell>Name</GridCell>
             <GridCell>URL</GridCell>
             <GridCell>Default</GridCell>
-          </GridRow>
+          </GridHeader>
           {value.engines.map((engine, index) => {
             return (
               <GridRow key={index}>
diff --git a/src/settings/components/index.tsx b/src/settings/components/index.tsx
index d204210..2e2ff52 100644
--- a/src/settings/components/index.tsx
+++ b/src/settings/components/index.tsx
@@ -1,6 +1,6 @@
-import "./site.scss";
 import React from "react";
 import { connect } from "react-redux";
+import styled from "styled-components";
 import TextArea from "./ui/TextArea";
 import Radio from "./ui/Radio";
 import SearchForm from "./form/SearchForm";
@@ -19,6 +19,28 @@ import { State as AppState } from "../reducers/setting";
 import Properties from "../../shared/settings/Properties";
 import Blacklist from "../../shared/settings/Blacklist";
 
+const Container = styled.form`
+  padding: 2px;
+  font-family: system-ui;
+`;
+
+const Fieldset = styled.fieldset`
+  margin: 0;
+  padding: 0;
+  border: none;
+  margin-top: 1rem;
+
+  &:first-of-type {
+    margin-top: 1rem;
+  }
+`;
+
+const Legend = styled.legend`
+  font-size: 1.5rem;
+  padding: 0.5rem 0;
+  font-weight: bold;
+`;
+
 const DO_YOU_WANT_TO_CONTINUE =
   "Some settings in JSON can be lost when migrating.  " +
   "Do you want to continue?";
@@ -41,47 +63,47 @@ class SettingsComponent extends React.Component<Props> {
   renderFormFields(form: FormSettings) {
     return (
       <div>
-        <fieldset>
-          <legend>Keybindings</legend>
+        <Fieldset>
+          <Legend>Keybindings</Legend>
           <KeymapsForm
             value={form.keymaps}
             onChange={this.bindKeymapsForm.bind(this)}
             onBlur={this.save.bind(this)}
           />
-        </fieldset>
-        <fieldset>
-          <legend>Search Engines</legend>
+        </Fieldset>
+        <Fieldset>
+          <Legend>Search Engines</Legend>
           <SearchForm
             value={form.search}
             onChange={this.bindSearchForm.bind(this)}
             onBlur={this.save.bind(this)}
           />
-        </fieldset>
-        <fieldset>
-          <legend>Blacklist</legend>
+        </Fieldset>
+        <Fieldset>
+          <Legend>Blacklist</Legend>
           <BlacklistForm
             value={form.blacklist}
             onChange={this.bindBlacklistForm.bind(this)}
             onBlur={this.save.bind(this)}
           />
-        </fieldset>
-        <fieldset>
-          <legend>Partial blacklist</legend>
+        </Fieldset>
+        <Fieldset>
+          <Legend>Partial blacklist</Legend>
           <PartialBlacklistForm
             value={form.blacklist}
             onChange={this.bindBlacklistForm.bind(this)}
             onBlur={this.save.bind(this)}
           />
-        </fieldset>
-        <fieldset>
-          <legend>Properties</legend>
+        </Fieldset>
+        <Fieldset>
+          <Legend>Properties</Legend>
           <PropertiesForm
             types={Properties.types()}
             value={form.properties.toJSON()}
             onChange={this.bindPropertiesForm.bind(this)}
             onBlur={this.save.bind(this)}
           />
-        </fieldset>
+        </Fieldset>
       </div>
     );
   }
@@ -111,30 +133,28 @@ class SettingsComponent extends React.Component<Props> {
       fields = this.renderJsonFields(this.props.json!, this.props.error);
     }
     return (
-      <div>
+      <Container>
         <h1>Configure Vim-Vixen</h1>
-        <form className="vimvixen-settings-form">
-          <Radio
-            id="setting-source-form"
-            name="source"
-            label="Use form"
-            checked={this.props.source === "form"}
-            value="form"
-            onValueChange={this.bindSource.bind(this)}
-            disabled={disabled}
-          />
+        <Radio
+          id="setting-source-form"
+          name="source"
+          label="Use form"
+          checked={this.props.source === "form"}
+          value="form"
+          onValueChange={this.bindSource.bind(this)}
+          disabled={disabled}
+        />
 
-          <Radio
-            name="source"
-            label="Use plain JSON"
-            checked={this.props.source === "json"}
-            value="json"
-            onValueChange={this.bindSource.bind(this)}
-            disabled={disabled}
-          />
-          {fields}
-        </form>
-      </div>
+        <Radio
+          name="source"
+          label="Use plain JSON"
+          checked={this.props.source === "json"}
+          value="json"
+          onValueChange={this.bindSource.bind(this)}
+          disabled={disabled}
+        />
+        {fields}
+      </Container>
     );
   }
 
diff --git a/src/settings/components/site.scss b/src/settings/components/site.scss
deleted file mode 100644
index e8415e8..0000000
--- a/src/settings/components/site.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-.vimvixen-settings-form {
-  padding: 2px;
-  font-family: system-ui;
-
-  fieldset {
-    margin: 0;
-    padding: 0;
-    border: none;
-    margin-top: 1rem;
-
-    fieldset:first-of-type {
-      margin-top: 1rem;
-    }
-
-    legend {
-      font-size: 1.5rem;
-      padding: .5rem 0;
-      font-weight: bold;
-    }
-  }
-}
-- 
cgit v1.2.3


From 738a699259345e47a81cba8d14beb5b9fd2d8b53 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Tue, 22 Sep 2020 10:52:13 +0900
Subject: Fix e2e test on option

---
 e2e/lib/FormOptionPage.ts                          | 177 +++++++++++----------
 e2e/lib/JSONOptionPage.ts                          |   4 +-
 src/settings/components/form/BlacklistForm.tsx     |   3 +
 .../components/form/PartialBlacklistForm.tsx       |   8 +-
 src/settings/components/form/SearchForm.tsx        |  10 +-
 5 files changed, 107 insertions(+), 95 deletions(-)

(limited to 'src/settings/components/form/PartialBlacklistForm.tsx')

diff --git a/e2e/lib/FormOptionPage.ts b/e2e/lib/FormOptionPage.ts
index 5551684..666bac7 100644
--- a/e2e/lib/FormOptionPage.ts
+++ b/e2e/lib/FormOptionPage.ts
@@ -1,5 +1,5 @@
 import { Lanthan } from "lanthan";
-import { WebDriver, By, until } from "selenium-webdriver";
+import { WebDriver, WebElement, By, error } from "selenium-webdriver";
 
 export default class FormOptionPage {
   private webdriver: WebDriver;
@@ -9,15 +9,15 @@ export default class FormOptionPage {
   }
 
   async setBlacklist(nth: number, url: string): Promise<void> {
-    const selector = ".form-blacklist-form-row > .column-url";
-    const inputs = await this.webdriver.findElements(By.css(selector));
-    if (inputs.length <= nth) {
+    const fieldset = await this.getFieldsetByLegend("Blacklist");
+    const rows = await fieldset.findElements(By.css("[role=listitem]"));
+    if (rows.length <= nth) {
       throw new RangeError("Index out of range to set a blacklist");
     }
-    await inputs[nth].sendKeys(url);
-    await this.webdriver.executeScript(
-      `document.querySelectorAll('${selector}')[${nth}].blur()`
-    );
+
+    const input = rows[nth].findElement(By.css("[aria-label=URL]"));
+    await input.sendKeys(url);
+    await this.blurActiveElement();
   }
 
   async setPartialBlacklist(
@@ -25,121 +25,122 @@ export default class FormOptionPage {
     url: string,
     keys: string
   ): Promise<void> {
-    let selector = ".form-partial-blacklist-form-row > .column-url";
-    let inputs = await this.webdriver.findElements(By.css(selector));
-    if (inputs.length <= nth) {
+    const fieldset = await this.getFieldsetByLegend("Partial blacklist");
+    const rows = await fieldset.findElements(By.css("[role=listitem]"));
+    if (rows.length <= nth) {
       throw new RangeError("Index out of range to set a partial blacklist");
     }
-    await inputs[nth].sendKeys(url);
-    await this.webdriver.executeScript(
-      `document.querySelectorAll('${selector}')[${nth}].blur()`
-    );
 
-    selector = ".form-partial-blacklist-form-row > .column-keys";
-    inputs = await this.webdriver.findElements(By.css(selector));
-    if (inputs.length <= nth) {
-      throw new RangeError("Index out of range to set a partial blacklist");
-    }
-    await inputs[nth].sendKeys(keys);
-    await this.webdriver.executeScript(
-      `document.querySelectorAll('${selector}')[${nth}].blur()`
-    );
+    const urlInput = rows[nth].findElement(By.css("[aria-label=URL]"));
+    await urlInput.sendKeys(url);
+    await this.blurActiveElement();
+
+    const keysInput = rows[nth].findElement(By.css("[aria-label=Keys]"));
+    await keysInput.sendKeys(keys);
+    await this.blurActiveElement();
   }
 
   async setSearchEngine(nth: number, name: string, url: string) {
-    let selector = ".form-search-form-row > .column-name";
-    let inputs = await this.webdriver.findElements(By.css(selector));
-    if (inputs.length <= nth) {
+    const fieldset = await this.getFieldsetByLegend("Search Engines");
+    const rows = await fieldset.findElements(By.css("[role=listitem]"));
+    if (rows.length <= nth) {
       throw new RangeError("Index out of range to set a search engine");
     }
-    await inputs[nth].sendKeys(name);
-    await this.webdriver.executeScript(
-      `document.querySelectorAll('${selector}')[${nth}].blur()`
-    );
 
-    selector = ".form-search-form-row > .column-url";
-    inputs = await this.webdriver.findElements(By.css(selector));
-    if (inputs.length <= nth) {
-      throw new RangeError("Index out of range to set a search engine");
-    }
-    await inputs[nth].sendKeys(url);
-    await this.webdriver.executeScript(
-      `document.querySelectorAll('${selector}')[${nth}].blur()`
-    );
+    const nameInput = rows[nth].findElement(By.css("[aria-label=Name"));
+    await nameInput.sendKeys(name);
+    await this.blurActiveElement();
+
+    const urlInput = rows[nth].findElement(By.css("[aria-label=URL]"));
+    await urlInput.sendKeys(url);
+    await this.blurActiveElement();
   }
 
   async addBlacklist(): Promise<void> {
-    const rows = await this.webdriver.findElements(
-      By.css(`.form-blacklist-form-row`)
-    );
-    const button = await this.webdriver.findElement(
-      By.css(".form-blacklist-form .ui-add-button")
-    );
-    await button.click();
-    await this.webdriver.wait(
-      until.elementLocated(
-        By.css(`.form-blacklist-form-row:nth-child(${rows.length + 1})`)
-      )
-    );
+    const fieldset = await this.getFieldsetByLegend("Blacklist");
+    const rows = await fieldset.findElements(By.css("[role=listitem]"));
+    const addButton = await fieldset.findElement(By.css("[aria-label=Add]"));
+
+    await addButton.click();
+    await this.webdriver.wait(async () => {
+      const newRows = await fieldset.findElements(By.css("[role=listitem]"));
+      return newRows.length == rows.length + 1;
+    });
   }
 
   async addPartialBlacklist(): Promise<void> {
-    const rows = await this.webdriver.findElements(
-      By.css(`.form-partial-blacklist-form-row`)
-    );
-    const button = await this.webdriver.findElement(
-      By.css(".form-partial-blacklist-form .ui-add-button")
-    );
-    await button.click();
-    await this.webdriver.wait(
-      until.elementLocated(
-        By.css(`.form-partial-blacklist-form-row:nth-child(${rows.length + 2})`)
-      )
-    );
+    const fieldset = await this.getFieldsetByLegend("Partial blacklist");
+    const addButton = await fieldset.findElement(By.css("[aria-label=Add]"));
+    const rows = await fieldset.findElements(By.css("[role=listitem]"));
+
+    await addButton.click();
+    await this.webdriver.wait(async () => {
+      const newRows = await fieldset.findElements(By.css("[role=listitem]"));
+      return newRows.length == rows.length + 1;
+    });
   }
 
   async removeBlackList(nth: number): Promise<void> {
-    const buttons = await this.webdriver.findElements(
-      By.css(".form-blacklist-form-row .ui-delete-button")
+    const fieldset = await this.getFieldsetByLegend("Blacklist");
+    const deleteButtons = await fieldset.findElements(
+      By.css("[aria-label=Delete]")
     );
-    if (buttons.length <= nth) {
+    if (deleteButtons.length <= nth) {
       throw new RangeError("Index out of range to remove blacklist");
     }
-    await buttons[nth].click();
+    await deleteButtons[nth].click();
   }
 
   async removePartialBlackList(nth: number): Promise<void> {
-    const buttons = await this.webdriver.findElements(
-      By.css(".form-partial-blacklist-form-row .ui-delete-button")
-    );
-    if (buttons.length <= nth) {
-      throw new RangeError("Index out of range to remove partial blacklist");
+    const fieldset = await this.getFieldsetByLegend("Partial blacklist");
+    const deleteButtons = await fieldset.findElements(
+      By.css("[aria-label=Delete]")
+    );
+    if (deleteButtons.length <= nth) {
+      throw new RangeError(
+        `Index out of range ${deleteButtons.length} to remove partial blacklist ${nth}`
+      );
     }
-    await buttons[nth].click();
+    await deleteButtons[nth].click();
   }
 
   async addSearchEngine(): Promise<void> {
-    const rows = await this.webdriver.findElements(
-      By.css(`.form-search-form-row > .column-name`)
-    );
-    const button = await this.webdriver.findElement(
-      By.css(".form-search-form > .ui-add-button")
-    );
-    await button.click();
-    await this.webdriver.wait(
-      until.elementLocated(
-        By.css(`.form-search-form-row:nth-child(${rows.length + 1})`)
-      )
-    );
+    const fieldset = await this.getFieldsetByLegend("Search Engines");
+    const rows = await fieldset.findElements(By.css("[role=listitem]"));
+    const addButton = await fieldset.findElement(By.css("[aria-label=Add]"));
+
+    await addButton.click();
+    await this.webdriver.wait(async () => {
+      const newRows = await fieldset.findElements(By.css("[role=listitem]"));
+      return newRows.length == rows.length + 1;
+    });
   }
 
   async setDefaultSearchEngine(nth: number): Promise<void> {
-    const radios = await this.webdriver.findElements(
-      By.css(".form-search-form-row input[type=radio]")
+    const fieldset = await this.getFieldsetByLegend("Search Engines");
+    const radios = await fieldset.findElements(
+      By.css("[name=default][type=radio]")
     );
     if (radios.length <= nth) {
       throw new RangeError("Index out of range to set a default search engine");
     }
     await radios[nth].click();
   }
+
+  private async getFieldsetByLegend(legendText: string): Promise<WebElement> {
+    const fieldsets = await this.webdriver.findElements(By.tagName("fieldset"));
+    for (const fieldset of fieldsets) {
+      const legend = await fieldset.findElement(By.tagName("legend"));
+      if ((await legend.getText()) === legendText) {
+        return fieldset;
+      }
+    }
+    throw new error.NoSuchElementError(
+      `Unable to locate fieldset with legend: ` + legendText
+    );
+  }
+
+  private async blurActiveElement(): Promise<void> {
+    await this.webdriver.executeScript(`document.activeElement.blur()`);
+  }
 }
diff --git a/e2e/lib/JSONOptionPage.ts b/e2e/lib/JSONOptionPage.ts
index 0f2b0a7..2d8147e 100644
--- a/e2e/lib/JSONOptionPage.ts
+++ b/e2e/lib/JSONOptionPage.ts
@@ -20,9 +20,7 @@ export default class JSONOptionPage {
   }
 
   async getErrorMessage(): Promise<string> {
-    const error = await this.webdriver.findElement(
-      By.css(".settings-ui-input-error")
-    );
+    const error = await this.webdriver.findElement(By.css("p[role=alert]"));
     return error.getText();
   }
 }
diff --git a/src/settings/components/form/BlacklistForm.tsx b/src/settings/components/form/BlacklistForm.tsx
index d301f2c..6fb9eca 100644
--- a/src/settings/components/form/BlacklistForm.tsx
+++ b/src/settings/components/form/BlacklistForm.tsx
@@ -52,6 +52,7 @@ class BlacklistForm extends React.Component<Props> {
                     data-index={index}
                     type="text"
                     name="url"
+                    aria-label="URL"
                     value={item.pattern}
                     placeholder="example.com/mail/*"
                     onChange={this.bindValue.bind(this)}
@@ -64,6 +65,7 @@ class BlacklistForm extends React.Component<Props> {
                     name="delete"
                     onClick={this.bindValue.bind(this)}
                     onBlur={this.props.onBlur}
+                    aria-label="Delete"
                   />
                 </GridCell>
               </GridRow>
@@ -72,6 +74,7 @@ class BlacklistForm extends React.Component<Props> {
         </Grid>
         <AddButton
           name="add"
+          aria-label="Add"
           style={{ float: "right" }}
           onClick={this.bindValue.bind(this)}
         />
diff --git a/src/settings/components/form/PartialBlacklistForm.tsx b/src/settings/components/form/PartialBlacklistForm.tsx
index dcdd00c..b2da2bb 100644
--- a/src/settings/components/form/PartialBlacklistForm.tsx
+++ b/src/settings/components/form/PartialBlacklistForm.tsx
@@ -52,7 +52,7 @@ class PartialBlacklistForm extends React.Component<Props> {
   render() {
     return (
       <>
-        <Grid>
+        <Grid role="list">
           <GridHeader>
             <GridCell>URL</GridCell>
             <GridCell>Keys</GridCell>
@@ -62,12 +62,13 @@ class PartialBlacklistForm extends React.Component<Props> {
               return null;
             }
             return (
-              <GridRow key={index}>
+              <GridRow key={index} role="listitem">
                 <GridCell>
                   <Input
                     data-index={index}
                     type="text"
                     name="url"
+                    aria-label="URL"
                     value={item.pattern}
                     placeholder="example.com/mail/*"
                     onChange={this.bindValue.bind(this)}
@@ -79,6 +80,7 @@ class PartialBlacklistForm extends React.Component<Props> {
                     data-index={index}
                     type="text"
                     name="keys"
+                    aria-label="Keys"
                     value={item.keys.join(",")}
                     placeholder="j,k,<C-d>,<C-u>"
                     onChange={this.bindValue.bind(this)}
@@ -89,6 +91,7 @@ class PartialBlacklistForm extends React.Component<Props> {
                   <DeleteButton
                     data-index={index}
                     name="delete"
+                    aria-label="Delete"
                     onClick={this.bindValue.bind(this)}
                     onBlur={this.props.onBlur}
                   />
@@ -99,6 +102,7 @@ class PartialBlacklistForm extends React.Component<Props> {
         </Grid>
         <AddButton
           name="add"
+          aria-label="Add"
           style={{ float: "right" }}
           onClick={this.bindValue.bind(this)}
         />
diff --git a/src/settings/components/form/SearchForm.tsx b/src/settings/components/form/SearchForm.tsx
index cc7061a..4bf0e02 100644
--- a/src/settings/components/form/SearchForm.tsx
+++ b/src/settings/components/form/SearchForm.tsx
@@ -54,7 +54,7 @@ class SearchForm extends React.Component<Props> {
     const value = this.props.value.toJSON();
     return (
       <>
-        <Grid>
+        <Grid role="list">
           <GridHeader>
             <GridCell>Name</GridCell>
             <GridCell>URL</GridCell>
@@ -62,12 +62,13 @@ class SearchForm extends React.Component<Props> {
           </GridHeader>
           {value.engines.map((engine, index) => {
             return (
-              <GridRow key={index}>
+              <GridRow key={index} role="listitem">
                 <GridCell>
                   <Input
                     data-index={index}
                     type="text"
                     name="name"
+                    aria-label="Name"
                     value={engine[0]}
                     onChange={this.bindValue.bind(this)}
                     onBlur={this.props.onBlur}
@@ -78,6 +79,7 @@ class SearchForm extends React.Component<Props> {
                     data-index={index}
                     type="text"
                     name="url"
+                    aria-label="URL"
                     placeholder="http://example.com/?q={}"
                     value={engine[1]}
                     onChange={this.bindValue.bind(this)}
@@ -89,11 +91,14 @@ class SearchForm extends React.Component<Props> {
                     data-index={index}
                     type="radio"
                     name="default"
+                    aria-label="Default"
                     checked={value.default === engine[0]}
                     onChange={this.bindValue.bind(this)}
                   />
+                  a
                   <DeleteButton
                     data-index={index}
+                    aria-label="Delete"
                     name="delete"
                     onClick={this.bindValue.bind(this)}
                   />
@@ -104,6 +109,7 @@ class SearchForm extends React.Component<Props> {
         </Grid>
         <AddButton
           name="add"
+          aria-label="Add"
           style={{ float: "right" }}
           onClick={this.bindValue.bind(this)}
         />
-- 
cgit v1.2.3

