Repository: manusa/electronim
Pull request #217 (2022-11-20 06:59:29)

✨ settings sandbox toggle uses Material 3 switch

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=217)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=217&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=217&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=217&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=217&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=217&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=217&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=217&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=217&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=manusa_electronim&pullRequest=217&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=217&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=217&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=217&resolved=false&types=CODE_SMELL)

[![87.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '87.5%')](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=217&metric=new_coverage&view=list) [87.5% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=217&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=217&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=217&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=217)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=217&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=217&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=217&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=217&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=217&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=217&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=217&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=217&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=manusa_electronim&pullRequest=217&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=217&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=217&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=217&resolved=false&types=CODE_SMELL)

[![87.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '87.5%')](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=217&metric=new_coverage&view=list) [87.5% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=217&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=217&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=217&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=217)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=217&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=217&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=217&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=217&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=217&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=217&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=217&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=217&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=manusa_electronim&pullRequest=217&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=217&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=217&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=217&resolved=false&types=CODE_SMELL)

[![87.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '87.5%')](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=217&metric=new_coverage&view=list) [87.5% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=217&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=217&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=217&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/src/components/index.mjs b/src/components/index.mjs
index d23805e..c4fe75e 100644
--- a/src/components/index.mjs
+++ b/src/components/index.mjs
@@ -23,5 +23,6 @@ export {IconButton} from './icon-button.mjs';
 export {Logo} from './electronim.mjs';
 export {Menu} from './menu.mjs';
 export {NavigationRail} from './navigation-rail.mjs';
+export {Switch} from './switch.mjs';
 export {TextField} from './text-field.mjs';
 export {TopAppBar} from './top-app-bar.mjs';
diff --git a/src/components/switch.mjs b/src/components/switch.mjs
new file mode 100644
index 0000000..fa3e6bb
--- /dev/null
+++ b/src/components/switch.mjs
@@ -0,0 +1,34 @@
+/*
+   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 Switch = ({
+  checked = false,
+  onClick,
+  ...properties
+}) => {
+  let switchClass = 'material3 switch';
+  if (checked) {
+    switchClass += ' switch--checked';
+  }
+  return html`
+    <div class=${switchClass} onClick=${onClick} ...${properties}>
+      <span class='switch__track' />
+      <span class='switch__thumb' />
+      <input type='checkbox' class='switch__input' checked=${checked} />
+    </div>
+  `;
+};
diff --git a/src/components/text-field.mjs b/src/components/text-field.mjs
index 3fc82a4..c8c165c 100644
--- a/src/components/text-field.mjs
+++ b/src/components/text-field.mjs
@@ -15,6 +15,16 @@
  */
 import {html, useState} from './index.mjs';
 
+/**
+ A text field based on Material design (3) guidelines.
+ * @param type the text field type (one of outlined or filled)
+ * @param label the text field label
+ * @param placeholder the text to be displayed as a hint when the text field is empty
+ * @param onInput the function to be called when the text field value changes
+ * @param onKeyDown the function to be called when a key is pressed
+ * @param value the text field value
+ * @param hasError whether the text field has an error
+ */
 export const TextField = ({
   type = TextField.types.outlined,
   label,
diff --git a/src/settings/settings.browser.css b/src/settings/settings.browser.css
index 5cc6209..0f5192a 100644
--- a/src/settings/settings.browser.css
+++ b/src/settings/settings.browser.css
@@ -41,8 +41,7 @@
 }
 
 .settings__new-tab,
-.settings__tab-main,
-.settings__tab-advanced.container {
+.settings__tab-main {
     display: flex;
     align-items: center;
     justify-content: flex-start;
@@ -50,19 +49,23 @@
     width: 100%;
 }
 
-.settings__tab .settings__tab-advanced.container {
+.settings__tab .settings__tab-advanced {
+    position: relative;
     transform: scaleY(0);
     height: 0;
-    margin: 0;
-    transition: margin-top 0.3s  ease-out, transform 0.3s ease-out;
+    --x-margin: calc(-1 * var(--material3-card-padding));
+    margin: 0 var(--x-margin);
+    transition:
+        margin-top 0.3s  ease-out,
+        height 0.3s  ease-out,
+        transform 0.3s ease-out;
     overflow: hidden;
 }
 
-.settings__tab--expanded .settings__tab-advanced.container {
+.settings__tab--expanded .settings__tab-advanced {
     transform: scaleY(1);
     height: 100%;
-    --margin: calc(2 * var(--default-spacing));
-    margin: var(--margin) var(--margin) var(--default-spacing) var(--margin);
+    margin-top: var(--material3-card-spacing) ;
 }
 
 .settings__tab .expand-button {
@@ -78,6 +81,26 @@
     flex: 1;
 }
 
+.settings__tab .settings__option {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    height: 56px;
+    padding: 0 16px;
+    font-family: var(--md-sys-typescale-body-large-font);
+    font-size: var(--md-sys-typescale-body-large-size);
+    line-height: var(--md-sys-typescale-body-large-line-height);
+    user-select: none;
+    cursor: pointer;
+}
+.settings__tab .settings__option:hover {
+    background-color: var(--md-sys-color-surface-variant);
+}
+
+.settings__tab .settings__option-label {
+    flex: 1;
+}
+
 .settings__dictionaries {
     column-count: 1;
 }
diff --git a/src/settings/settings.services.browser.mjs b/src/settings/settings.services.browser.mjs
index ac0bd44..5159500 100644
--- a/src/settings/settings.services.browser.mjs
+++ b/src/settings/settings.services.browser.mjs
@@ -13,12 +13,12 @@
    See the License for the specific language governing permissions and
    limitations under the License.
  */
-import {html, Card, Checkbox, IconButton, TextField} from '../components/index.mjs';
+import {html, Card, IconButton, Switch, TextField} from '../components/index.mjs';
 import {ACTIONS, addTab, isPaneActive, setTabProperty, toggleTabProperty} from './settings.reducer.browser.mjs';
 
 const disabledIcon = disabled => (disabled === true ? '\ue8f5' : '\ue8f4');
 const notificationIcon = disabled => (disabled === true ? '\ue7f6' : '\ue7f4');
-const sandboxedIcon = sandboxed => (sandboxed === true ? 'fa-lock' : 'fa-lock-open');
+const sandboxedIcon = sandboxed => (sandboxed === true ? '\ue88d' : '\ue898');
 
 const ExpandButton = ({dispatch, id, expanded = false}) => {
   const properties = {
@@ -34,15 +34,27 @@ const ExpandButton = ({dispatch, id, expanded = false}) => {
 
 const TabAdvancedSettings = (
   {dispatch, id, sandboxed = false}
-) => (html`
-  <div class="settings__tab-advanced container">
-    <${Checkbox} label="Sandbox" checked=${sandboxed} value=${id}
-      title='Use an isolated/sandboxed session for this tab'
-      icon=${sandboxedIcon(sandboxed)}
-      onClick=${toggleTabProperty(dispatch, 'sandboxed', id)}
-    />
-  </div>
-`);
+) => {
+  const onSandboxClick = event => {
+    event.stopPropagation();
+    toggleTabProperty(dispatch, 'sandboxed', id)();
+  };
+  return html`
+    <div class='settings__tab-advanced'>
+      <div
+        class='settings__option sandboxed-toggle' onClick=${onSandboxClick}
+        title='Use an isolated/sandboxed session for this tab'
+      >
+        <span class='material-icon'>${sandboxedIcon(sandboxed)}</span>
+        <span class='settings__option-label'>Sandbox</span>
+        <${Switch}
+          checked=${sandboxed} onClick=${onSandboxClick}
+          title='Use an isolated/sandboxed session for this tab'
+        />
+      </div>
+    </div>
+  `;
+};
 
 const TabEntry = ({
   dispatch, invalidTabs, id, expanded, url, disabled, disableNotifications = false, ...tab
@@ -99,7 +111,7 @@ export const ServicesPane = ({dispatch, state}) => {
         />
         <${IconButton} icon='\ue145' onClick=${onAddTab} disabled=${!state.newTabValid} />
       </div>
-      <div class="settings__tabs container field">
+      <div class='settings__tabs'>
         ${state.tabs.map(tab => (html`
           <${TabEntry}
             dispatch=${dispatch} expanded=${state.expandedTabs.includes(tab.id)} invalidTabs=${state.invalidTabs}
diff --git a/src/styles/base/material3/typography.css b/src/styles/base/material3/typography.css
index 867bf6d..e3369fc 100644
--- a/src/styles/base/material3/typography.css
+++ b/src/styles/base/material3/typography.css
@@ -522,3 +522,7 @@
   text-transform: var(--md-sys-typescale-display-large-text-transform);
   text-decoration: var(--md-sys-typescale-display-large-text-decoration);
 }
+
+.material-icon {
+  font-family: 'Material Symbols Outlined', sans-serif;
+}
diff --git a/src/styles/components/card.css b/src/styles/components/card.css
index e779c5b..399123b 100644
--- a/src/styles/components/card.css
+++ b/src/styles/components/card.css
@@ -15,6 +15,7 @@
  */
 :root {
   --material3-card-padding: 16px;
+  --material3-card-spacing: 8px;
 }
 
 .electronim .material3.card {
@@ -55,6 +56,6 @@
 }
 
 .electronim .material3.card .card__divider {
-  margin: 8px calc(-1 * var(--material3-card-padding));
+  margin: var(--material3-card-spacing) calc(-1 * var(--material3-card-padding));
   border-bottom: 1px solid var(--md-sys-color-outline-variant);
 }
diff --git a/src/styles/components/index.css b/src/styles/components/index.css
index 7e404fd..80c32aa 100644
--- a/src/styles/components/index.css
+++ b/src/styles/components/index.css
@@ -18,5 +18,6 @@
 @import './icon-button.css';
 @import './menu.css';
 @import './navigation-rail.css';
+@import './switch.css';
 @import './text-field.css';
 @import './top-bar.css';
diff --git a/src/styles/components/switch.css b/src/styles/components/switch.css
new file mode 100644
index 0000000..5a4291b
--- /dev/null
+++ b/src/styles/components/switch.css
@@ -0,0 +1,85 @@
+/*
+   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.
+ */
+
+:root {
+  --material3-switch-height: 32px;
+  --material3-switch-width: 52px;
+  --material3-switch-thumb-size: 16px;
+  --material3-switch-checked-thumb-size: 24px;
+  --material3-switch-transition-duration: 0.8s;
+}
+
+.electronim .material3.switch {
+  display: inline-flex;
+  align-items: center;
+  justify-content: flex-start;
+  position: relative;
+  height: var(--material3-switch-height);
+  width: var(--material3-switch-width);
+  padding: 6px;
+  cursor: pointer;
+  user-select: none;
+}
+
+.electronim .material3.switch--checked {
+  padding: 4px;
+}
+
+.electronim .material3.switch .switch__input {
+  display: none;
+}
+
+.electronim .material3.switch .switch__track {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 0;
+  border-radius: var(--material3-switch-height);
+  background-color: var(--md-sys-color-surface-variant);
+  border: 2px solid var(--md-sys-color-outline);
+  transition:
+      background var(--material3-switch-transition-duration) ease,
+      border var(--material3-switch-transition-duration) ease;
+}
+
+.electronim .material3.switch--checked .switch__track {
+  background-color: var(--md-sys-color-primary);
+  border-color: var(--md-sys-color-primary);
+}
+
+.electronim .material3.switch .switch__thumb {
+  z-index: 1;
+  display: inline-block;
+  height: var(--material3-switch-thumb-size);
+  width: var(--material3-switch-thumb-size);
+  border-radius: 100%;
+  background-color: var(--md-sys-color-outline);
+  transition:
+      transform var(--material3-switch-transition-duration) ease,
+      background var(--material3-switch-transition-duration) ease,
+      border var(--material3-switch-transition-duration) ease,
+      height var(--material3-switch-transition-duration) ease,
+      width var(--material3-switch-transition-duration) ease;
+}
+
+.electronim .material3.switch--checked .switch__thumb {
+  height: var(--material3-switch-checked-thumb-size);
+  width: var(--material3-switch-checked-thumb-size);
+  transform: translateX(20px);
+  background-color: var(--md-sys-color-on-primary);
+}


Test patch:
diff --git a/src/settings/__tests__/settings.browser.test.mjs b/src/settings/__tests__/settings.browser.test.mjs
index 5a603f8..e3d1401 100644
--- a/src/settings/__tests__/settings.browser.test.mjs
+++ b/src/settings/__tests__/settings.browser.test.mjs
@@ -268,31 +268,71 @@ describe('Settings in Browser test suite', () => {
         expect($expandedTab.classList.contains('settings__tab--expanded')).toBe(false);
       });
       describe('Advanced settings', () => {
-        test('Sandbox checkbox click, sandboxed session, should unlock', async () => {
-          // Given
-          const $toggleIcon = document.querySelector('.settings__tab[data-id="1"] .expand-button');
-          fireEvent.click($toggleIcon);
-          await waitFor(() => expect($toggleIcon.title).toEqual('Collapse'));
-          const $lockCheckBox = document.querySelector('.settings__tab-advanced .checkbox .fa-lock');
-          // When
-          fireEvent.click($lockCheckBox);
-          // Then
-          await waitFor(() =>
-            expect($lockCheckBox.classList.contains('fa-lock')).toBe(false));
-          expect($lockCheckBox.classList.contains('fa-lock-open')).toBe(true);
+        describe('with sandboxed session', () => {
+          let $settingsTab;
+          let $toggleIcon;
+          let $sandboxedEntry;
+          beforeEach(async () => {
+            $settingsTab = document.querySelector('.settings__tab[data-id="1"]');
+            $toggleIcon = $settingsTab.querySelector('.expand-button');
+            $sandboxedEntry = $settingsTab.querySelector('.sandboxed-toggle');
+            if ($toggleIcon.title.startsWith('Expand')) {
+              fireEvent.click($toggleIcon);
+              // eslint-disable-next-line jest/no-standalone-expect
+              await waitFor(() => expect($toggleIcon.title).toEqual('Collapse'));
+            }
+          });
+          test('click on label, should unlock', async () => {
+            // Given
+            const $lockLabel = getByText($sandboxedEntry, 'Sandbox');
+            // When
+            fireEvent.click($lockLabel);
+            // Then
+            await waitFor(() => expect($settingsTab.textContent).not.toContain('\ue88d'));
+            expect($settingsTab.textContent).toContain('\ue898');
+          });
+          test('click on switch, should unlock', async () => {
+            // Given
+            const $switch = $sandboxedEntry.querySelector('.material3.switch');
+            // When
+            fireEvent.click($switch);
+            // Then
+            await waitFor(() => expect($settingsTab.textContent).not.toContain('\ue88d'));
+            expect($settingsTab.textContent).toContain('\ue898');
+          });
         });
-        test('Sandbox checkbox click, non-sandboxed session, should lock', async () => {
-          // Given
-          const $toggleIcon = document.querySelector('.settings__tab[data-id="2"] .expand-button');
-          fireEvent.click($toggleIcon);
-          await waitFor(() => expect($toggleIcon.title).toEqual('Collapse'));
-          const $lockCheckBox = document.querySelector('.settings__tab-advanced .checkbox .fa-lock-open');
-          // When
-          fireEvent.click($lockCheckBox);
-          // Then
-          await waitFor(() =>
-            expect($lockCheckBox.classList.contains('fa-lock-open')).toBe(false));
-          expect($lockCheckBox.classList.contains('fa-lock')).toBe(true);
+        describe('with non-sandboxed session', () => {
+          let $settingsTab;
+          let $toggleIcon;
+          let $sandboxedEntry;
+          beforeEach(async () => {
+            $settingsTab = document.querySelector('.settings__tab[data-id="2"]');
+            $toggleIcon = $settingsTab.querySelector('.expand-button');
+            $sandboxedEntry = $settingsTab.querySelector('.sandboxed-toggle');
+            if ($toggleIcon.title.startsWith('Expand')) {
+              fireEvent.click($toggleIcon);
+              // eslint-disable-next-line jest/no-standalone-expect
+              await waitFor(() => expect($toggleIcon.title).toEqual('Collapse'));
+            }
+          });
+          test('click on label, should unlock', async () => {
+            // Given
+            const $lockLabel = getByText($sandboxedEntry, 'Sandbox');
+            // When
+            fireEvent.click($lockLabel);
+            // Then
+            await waitFor(() => expect($settingsTab.textContent).not.toContain('\ue898'));
+            expect($settingsTab.textContent).toContain('\ue88d');
+          });
+          test('click on switch, should unlock', async () => {
+            // Given
+            const $switch = $sandboxedEntry.querySelector('.material3.switch');
+            // When
+            fireEvent.click($switch);
+            // Then
+            await waitFor(() => expect($settingsTab.textContent).not.toContain('\ue898'));
+            expect($settingsTab.textContent).toContain('\ue88d');
+          });
         });
       });
     });