Repository: manusa/electronim
Pull request #160 (2022-10-13 05:11:40)

✨ app-menu with icons

sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=manusa_electronim&pullRequest=160)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=manusa_electronim&pullRequest=160&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=manusa_electronim&pullRequest=160&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=manusa_electronim&pullRequest=160&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=CODE_SMELL)

[![75.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '75.0%')](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=160&metric=new_coverage&view=list) [75.0% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=160&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=160&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=160&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=manusa_electronim&pullRequest=160)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=manusa_electronim&pullRequest=160&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=manusa_electronim&pullRequest=160&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=manusa_electronim&pullRequest=160&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=160&resolved=false&types=CODE_SMELL)

[![100.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100-16px.png '100.0%')](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=160&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=160&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=160&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=160&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/src/app-menu/app-menu.browser.mjs b/src/app-menu/app-menu.browser.mjs
index 7e3007b..edd3c44 100644
--- a/src/app-menu/app-menu.browser.mjs
+++ b/src/app-menu/app-menu.browser.mjs
@@ -13,7 +13,7 @@
    See the License for the specific language governing permissions and
    limitations under the License.
  */
-import {html, render, DropDown} from '../components/index.mjs';
+import {html, render, DropDown, Icon} from '../components/index.mjs';
 
 const {close, helpOpenDialog, settingsOpenDialog} = window.electron;
 
@@ -30,8 +30,12 @@ const AppMenu = () => {
       <div class='scrim'>
         <${DropDown} active=${true}>
         <${DropDown.Menu}>
-          <${DropDown.Item} onClick=${noBubbling(helpOpenDialog)}>Help</${DropDown.Item}>
-          <${DropDown.Item} onClick=${noBubbling(settingsOpenDialog)}>Settings</${DropDown.Item}>
+          <${DropDown.Item} data-testid='help-menu-entry' onClick=${noBubbling(helpOpenDialog)}>
+            <${Icon} icon='fas fa-question-circle'>Help</${Icon}>
+          </${DropDown.Item}>
+          <${DropDown.Item} data-testid='settings-menu-entry' onClick=${noBubbling(settingsOpenDialog)}>
+            <${Icon} icon='fas fa-cog'>Settings</${Icon}>
+          </${DropDown.Item}>
         </${DropDown.Menu}>
         </${DropDown}>
       </div>
diff --git a/src/app-menu/index.html b/src/app-menu/index.html
index 2d157ef..1519412 100644
--- a/src/app-menu/index.html
+++ b/src/app-menu/index.html
@@ -27,5 +27,6 @@ limitations under the License.
     <script src="./app-menu.browser.mjs" type="module"></script>
     <script src="../../bundles/lib/bulma/css/bulma.css.js"></script>
     <script src="../../bundles/lib/bulma/bulma.dark.scss.js"></script>
+    <script src="../../bundles/lib/@fortawesome/fontawesome-free/css/all.css.js"></script>
   </body>
 </html>
diff --git a/src/chrome-tabs/chrome-tabs.browser.mjs b/src/chrome-tabs/chrome-tabs.browser.mjs
index bdbc884..c313b65 100644
--- a/src/chrome-tabs/chrome-tabs.browser.mjs
+++ b/src/chrome-tabs/chrome-tabs.browser.mjs
@@ -14,7 +14,7 @@
    limitations under the License.
  */
 /* eslint-disable no-undef */
-import {html, render, useLayoutEffect, useReducer, useState} from '../components/index.mjs';
+import {html, render, useLayoutEffect, useReducer, useState, Icon} from '../components/index.mjs';
 
 const shouldUseDarkColors = () => window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
 const getTabContainer = () => document.querySelector('.tab-container');
@@ -248,9 +248,7 @@ const Menu = () => html`
         class="menu__button button"
         onClick=${openMenu}
       >
-         <span class='icon'>
-           <em class='fas fa-bars'></em>
-         </span>
+        <${Icon} icon='fas fa-bars'></${Icon}>
      </button>
  </div>
 `;
diff --git a/src/components/drop-down.mjs b/src/components/drop-down.mjs
index f350926..a25989c 100644
--- a/src/components/drop-down.mjs
+++ b/src/components/drop-down.mjs
@@ -28,7 +28,7 @@ DropDown.Menu = ({children}) => (html`
   </div>
 `);
 
-DropDown.Item = ({onClick, children}) => {
+DropDown.Item = ({onClick, children, ...properties}) => {
   const [hover, setHover] = useState(false);
   const onMouseOver = () => setHover(true);
   const onMouseOut = () => setHover(false);
@@ -39,6 +39,7 @@ DropDown.Item = ({onClick, children}) => {
       onClick=${onClick}
       onMouseOver=${onMouseOver}
       onMouseOut=${onMouseOut}
+      ...${properties}
     >
       ${children}
     </a>
diff --git a/src/components/icon.mjs b/src/components/icon.mjs
new file mode 100644
index 0000000..f9725d4
--- /dev/null
+++ b/src/components/icon.mjs
@@ -0,0 +1,29 @@
+/*
+   Copyright 2022 Marc Nuri San Felix
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ */
+import {html} from './index.mjs';
+
+export const Icon = ({icon, children}) => {
+  const theIcon = html`<span class='icon'><i class=${icon}></i></span>`;
+  if (!children) {
+    return theIcon;
+  }
+  return (html`
+    <span class='icon-text'>
+      ${theIcon}
+      <span>${children}</span>
+    </span>
+  `);
+};
diff --git a/src/components/index.mjs b/src/components/index.mjs
index 7de91a9..e6a3508 100644
--- a/src/components/index.mjs
+++ b/src/components/index.mjs
@@ -18,5 +18,6 @@ export {html, render, useLayoutEffect, useReducer, useState} from '../../bundles
 
 export {Checkbox, Field, HorizontalField, Select, sizes} from './form/index.mjs';
 export {DropDown} from './drop-down.mjs';
+export {Icon} from './icon.mjs';
 export {Panel} from './panel.mjs';
 export {TopBar} from './top-bar.mjs';


Test patch:
diff --git a/src/app-menu/__tests__/app-menu.browser.test.mjs b/src/app-menu/__tests__/app-menu.browser.test.mjs
index b2b392e..d6812a9 100644
--- a/src/app-menu/__tests__/app-menu.browser.test.mjs
+++ b/src/app-menu/__tests__/app-menu.browser.test.mjs
@@ -15,7 +15,7 @@
  */
 import {jest} from '@jest/globals';
 import {loadDOM} from '../../__tests__/index.mjs';
-import {getByText, fireEvent, waitFor} from '@testing-library/dom';
+import {getByTestId, fireEvent, waitFor} from '@testing-library/dom';
 
 describe('App Menu in Browser test suite', () => {
   beforeEach(async () => {
@@ -36,11 +36,12 @@ describe('App Menu in Browser test suite', () => {
   describe('Settings entry', () => {
     let settings;
     beforeEach(() => {
-      settings = getByText(document, 'Settings');
+      settings = getByTestId(document, 'settings-menu-entry');
     });
     test('should be visible', () => {
       expect(settings.getAttribute('class'))
         .toMatch(/^dropdown-item*/);
+      expect(settings.textContent).toBe('Settings');
     });
     test('click, should open settings dialog', () => {
       // When
@@ -57,13 +58,18 @@ describe('App Menu in Browser test suite', () => {
     });
   });
   describe('Help entry', () => {
+    let help;
+    beforeEach(() => {
+      help = getByTestId(document, 'help-menu-entry');
+    });
     test('should be visible', () => {
-      expect(getByText(document, 'Help').getAttribute('class'))
+      expect(help.getAttribute('class'))
         .toMatch(/^dropdown-item*/);
+      expect(help.textContent).toBe('Help');
     });
     test('click, should open settings dialog', () => {
       // When
-      fireEvent.click(getByText(document, 'Help'));
+      fireEvent.click(help);
       // Then
       expect(window.electron.helpOpenDialog).toHaveBeenCalledTimes(1);
     });