Merge "Get rid of DIPolymerElement and KeyboardShortcutMixin"
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
index c33a50e..35b1751 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
@@ -29,8 +29,7 @@
 import {sharedStyles} from '../../../styles/shared-styles';
 import {LitElement, PropertyValues, html, css, nothing} from 'lit';
 import {customElement, property, state} from 'lit/decorators';
-import {ShortcutController} from '../../lit/shortcut-controller';
-import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
+import {Shortcut, ShortcutController} from '../../lit/shortcut-controller';
 import {queryAll} from '../../../utils/common-util';
 import {GrChangeListSection} from '../gr-change-list-section/gr-change-list-section';
 import {Execution} from '../../../constants/reporting';
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index 4257e60..8149f5a 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -34,10 +34,6 @@
 import '../../checks/gr-checks-tab';
 import {ChangeStarToggleStarDetail} from '../../shared/gr-change-star/gr-change-star';
 import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {
-  Shortcut,
-  ShortcutSection,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
 import {GrEditConstants} from '../../edit/gr-edit-constants';
 import {pluralize} from '../../../utils/string-util';
 import {querySelectorAll, windowLocationReload} from '../../../utils/dom-util';
@@ -160,7 +156,11 @@
   getRemovedByReason,
   hasAttention,
 } from '../../../utils/attention-set-util';
-import {shortcutsServiceToken} from '../../../services/shortcuts/shortcuts-service';
+import {
+  Shortcut,
+  ShortcutSection,
+  shortcutsServiceToken,
+} from '../../../services/shortcuts/shortcuts-service';
 import {LoadingStatus} from '../../../models/change/change-model';
 import {commentsModelToken} from '../../../models/comments/comments-model';
 import {resolve} from '../../../models/dependency';
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
index 762a90a..209c3b7 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
@@ -28,15 +28,15 @@
 import {GrDiffModeSelector} from '../../../embed/diff/gr-diff-mode-selector/gr-diff-mode-selector';
 import {GrButton} from '../../shared/gr-button/gr-button';
 import {fireEvent} from '../../../utils/event-util';
-import {
-  Shortcut,
-  ShortcutSection,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
 import {css, html, LitElement} from 'lit';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {when} from 'lit/directives/when';
 import {ifDefined} from 'lit/directives/if-defined';
-import {shortcutsServiceToken} from '../../../services/shortcuts/shortcuts-service';
+import {
+  Shortcut,
+  ShortcutSection,
+  shortcutsServiceToken,
+} from '../../../services/shortcuts/shortcuts-service';
 import {resolve} from '../../../models/dependency';
 import {getAppContext} from '../../../services/app-context';
 import {subscribe} from '../../lit/subscription-controller';
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
index 14a87ec..32bd07a 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
@@ -8,10 +8,6 @@
 import '../../shared/gr-icons/gr-icons';
 import '../gr-message/gr-message';
 import '../../../styles/gr-paper-styles';
-import {
-  Shortcut,
-  ShortcutSection,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
 import {parseDate} from '../../../utils/date-util';
 import {MessageTag} from '../../../constants/constants';
 import {getAppContext} from '../../../services/app-context';
@@ -43,7 +39,11 @@
 import {paperStyles} from '../../../styles/gr-paper-styles';
 import {when} from 'lit/directives/when';
 import {ifDefined} from 'lit/directives/if-defined';
-import {shortcutsServiceToken} from '../../../services/shortcuts/shortcuts-service';
+import {
+  Shortcut,
+  ShortcutSection,
+  shortcutsServiceToken,
+} from '../../../services/shortcuts/shortcuts-service';
 
 /**
  * The content of the enum is also used in the UI for the button text.
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
index 73e5da3..de1fd3e 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
@@ -12,8 +12,6 @@
 import {
   ShortcutSection,
   SectionView,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
-import {
   shortcutsServiceToken,
   ShortcutViewListener,
 } from '../../../services/shortcuts/shortcuts-service';
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.ts b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.ts
index 4099979..e04c48c 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.ts
@@ -9,7 +9,7 @@
 import {
   SectionView,
   ShortcutSection,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
+} from '../../../services/shortcuts/shortcuts-service';
 
 const basicFixture = fixtureFromElement('gr-keyboard-shortcuts-dialog');
 
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index 5de2190..ab65b57 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -21,10 +21,9 @@
   state,
   query as queryDec,
 } from 'lit/decorators';
-import {ShortcutController} from '../../lit/shortcut-controller';
+import {Shortcut, ShortcutController} from '../../lit/shortcut-controller';
 import {query as queryUtil} from '../../../utils/common-util';
 import {assertIsDefined} from '../../../utils/common-util';
-import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
 
 // Possible static search options for auto complete, without negations.
 const SEARCH_OPERATORS: ReadonlyArray<string> = [
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 835f7c4..be55097 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -23,10 +23,6 @@
 import '../../change/gr-download-dialog/gr-download-dialog';
 import '../../shared/gr-overlay/gr-overlay';
 import {
-  Shortcut,
-  ShortcutSection,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
-import {
   GeneratedWebLink,
   GerritNav,
 } from '../../core/gr-navigation/gr-navigation';
@@ -101,7 +97,11 @@
 import {isFalse, throttleWrap, until} from '../../../utils/async-util';
 import {filter, take, switchMap} from 'rxjs/operators';
 import {combineLatest} from 'rxjs';
-import {shortcutsServiceToken} from '../../../services/shortcuts/shortcuts-service';
+import {
+  Shortcut,
+  ShortcutSection,
+  shortcutsServiceToken,
+} from '../../../services/shortcuts/shortcuts-service';
 import {LoadingStatus} from '../../../models/change/change-model';
 import {DisplayLine} from '../../../api/diff';
 import {GrDownloadDialog} from '../../change/gr-download-dialog/gr-download-dialog';
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index 15ff34e..381451f 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -26,7 +26,6 @@
 import './settings/gr-registration-dialog/gr-registration-dialog';
 import './settings/gr-settings-view/gr-settings-view';
 import {getBaseUrl} from '../utils/url-util';
-import {Shortcut} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
 import {GerritNav} from './core/gr-navigation/gr-navigation';
 import {getAppContext} from '../services/app-context';
 import {GrRouter} from './core/gr-router/gr-router';
@@ -62,7 +61,7 @@
 import {sharedStyles} from '../styles/shared-styles';
 import {LitElement, PropertyValues, html, css, nothing} from 'lit';
 import {customElement, property, query, state} from 'lit/decorators';
-import {ShortcutController} from './lit/shortcut-controller';
+import {Shortcut, ShortcutController} from './lit/shortcut-controller';
 import {cache} from 'lit/directives/cache';
 import {assertIsDefined} from '../utils/common-util';
 import './gr-css-mixins';
diff --git a/polygerrit-ui/app/elements/lit/shortcut-controller.ts b/polygerrit-ui/app/elements/lit/shortcut-controller.ts
index 9d39cd7..b315529 100644
--- a/polygerrit-ui/app/elements/lit/shortcut-controller.ts
+++ b/polygerrit-ui/app/elements/lit/shortcut-controller.ts
@@ -9,6 +9,7 @@
 import {Shortcut} from '../../services/shortcuts/shortcuts-config';
 import {resolve} from '../../models/dependency';
 
+export {Shortcut};
 interface ShortcutListener {
   binding: Binding;
   listener: (e: KeyboardEvent) => void;
diff --git a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
deleted file mode 100644
index 7596804..0000000
--- a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {property} from '@polymer/decorators';
-import {check, Constructor} from '../../utils/common-util';
-import {
-  Shortcut,
-  ShortcutSection,
-  SPECIAL_SHORTCUT,
-} from '../../services/shortcuts/shortcuts-config';
-import {
-  SectionView,
-  ShortcutListener,
-  shortcutsServiceToken,
-} from '../../services/shortcuts/shortcuts-service';
-import {DIPolymerElement, resolve} from '../../models/dependency';
-
-export {
-  Shortcut,
-  ShortcutSection,
-  SPECIAL_SHORTCUT,
-  ShortcutListener,
-  SectionView,
-};
-
-export const KeyboardShortcutMixin = <T extends Constructor<DIPolymerElement>>(
-  superClass: T
-) => {
-  /**
-   * @polymer
-   * @mixinClass
-   */
-  class Mixin extends superClass {
-    // This enables `Shortcut` to be used in the html template.
-    Shortcut = Shortcut;
-
-    // This enables `ShortcutSection` to be used in the html template.
-    ShortcutSection = ShortcutSection;
-
-    private readonly getShortcutsService = resolve(this, shortcutsServiceToken);
-
-    /** Used to disable shortcuts when the element is not visible. */
-    private observer?: IntersectionObserver;
-
-    /**
-     * Enabling shortcuts only when the element is visible (see `observer`
-     * above) is a great feature, but often what you want is for the *page* to
-     * be visible, not the specific child element that registers keyboard
-     * shortcuts. An example is the FileList in the ChangeView. So we allow
-     * a broader observer target to be specified here, and fall back to
-     * `this` as the default.
-     */
-    @property({type: Object})
-    observerTarget: Element = this;
-
-    /** Are shortcuts currently enabled? True only when element is visible. */
-    private bindingsEnabled = false;
-
-    override connectedCallback() {
-      super.connectedCallback();
-      this.createVisibilityObserver();
-      this.enableBindings();
-    }
-
-    override disconnectedCallback() {
-      this.destroyVisibilityObserver();
-      this.disableBindings();
-      super.disconnectedCallback();
-    }
-
-    /**
-     * Creates an intersection observer that enables bindings when the
-     * element is visible and disables them when the element is hidden.
-     */
-    private createVisibilityObserver() {
-      if (!this.hasKeyboardShortcuts()) return;
-      if (this.observer) return;
-      this.observer = new IntersectionObserver(entries => {
-        check(entries.length === 1, 'Expected one observer entry.');
-        const isVisible = entries[0].isIntersecting;
-        if (isVisible) {
-          this.enableBindings();
-        } else {
-          this.disableBindings();
-        }
-      });
-      this.observer.observe(this.observerTarget);
-    }
-
-    private destroyVisibilityObserver() {
-      if (this.observer) this.observer.unobserve(this.observerTarget);
-    }
-
-    /**
-     * Enables all the shortcuts returned by keyboardShortcuts().
-     * This is a private method being called when the element becomes
-     * connected or visible.
-     */
-    private enableBindings() {
-      if (!this.hasKeyboardShortcuts()) return;
-      if (this.bindingsEnabled) return;
-      this.bindingsEnabled = true;
-
-      this.getShortcutsService().attachHost(this, this.keyboardShortcuts());
-    }
-
-    /**
-     * Disables all the shortcuts returned by keyboardShortcuts().
-     * This is a private method being called when the element becomes
-     * disconnected or invisible.
-     */
-    private disableBindings() {
-      if (!this.bindingsEnabled) return;
-      this.bindingsEnabled = false;
-      this.getShortcutsService().detachHost(this);
-    }
-
-    private hasKeyboardShortcuts() {
-      return this.keyboardShortcuts().length > 0;
-    }
-
-    keyboardShortcuts(): ShortcutListener[] {
-      return [];
-    }
-  }
-
-  return Mixin as T & Constructor<KeyboardShortcutMixinInterface>;
-};
-
-/** The interface corresponding to KeyboardShortcutMixin */
-export interface KeyboardShortcutMixinInterface {
-  keyboardShortcuts(): ShortcutListener[];
-}
diff --git a/polygerrit-ui/app/models/dependency.ts b/polygerrit-ui/app/models/dependency.ts
index ae9cc64..5499db2 100644
--- a/polygerrit-ui/app/models/dependency.ts
+++ b/polygerrit-ui/app/models/dependency.ts
@@ -4,7 +4,6 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 import {ReactiveController, ReactiveControllerHost} from 'lit';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
 
 /**
  * This module provides the ability to do dependency injection in components.
@@ -180,49 +179,6 @@
 }
 
 /**
- * Because Polymer doesn't (yet) depend on ReactiveControllerHost, this adds a
- * work-around base-class to make this work for Polymer.
- */
-export class DIPolymerElement
-  extends PolymerElement
-  implements ReactiveControllerHost
-{
-  private readonly ___controllers: ReactiveController[] = [];
-
-  override connectedCallback() {
-    for (const c of this.___controllers) {
-      c.hostConnected?.();
-    }
-    super.connectedCallback();
-  }
-
-  override disconnectedCallback() {
-    super.disconnectedCallback();
-    for (const c of this.___controllers) {
-      c.hostDisconnected?.();
-    }
-  }
-
-  addController(controller: ReactiveController) {
-    this.___controllers.push(controller);
-
-    if (this.isConnected) controller.hostConnected?.();
-  }
-
-  removeController(controller: ReactiveController) {
-    const idx = this.___controllers.indexOf(controller);
-    if (idx < 0) return;
-    this.___controllers?.splice(idx, 1);
-  }
-
-  requestUpdate() {}
-
-  get updateComplete(): Promise<boolean> {
-    return Promise.resolve(true);
-  }
-}
-
-/**
  * A callback for a value.
  */
 type Callback<T> = (value: T) => void;
diff --git a/polygerrit-ui/app/models/dependency_test.ts b/polygerrit-ui/app/models/dependency_test.ts
index 76d5dfd..49e8580 100644
--- a/polygerrit-ui/app/models/dependency_test.ts
+++ b/polygerrit-ui/app/models/dependency_test.ts
@@ -3,10 +3,8 @@
  * Copyright 2021 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import {define, provide, resolve, DIPolymerElement} from './dependency';
+import {define, provide, resolve} from './dependency';
 import {html, LitElement} from 'lit';
-import {customElement as polyCustomElement} from '@polymer/decorators';
-import {html as polyHtml} from '@polymer/polymer/lib/utils/html-tag';
 import {customElement, property, query} from 'lit/decorators';
 import '../test/common-test-setup-karma.js';
 
@@ -55,33 +53,11 @@
   }
 }
 
-@polyCustomElement('polymer-foo-provider')
-export class PolymerFooProviderElement extends DIPolymerElement {
-  bar() {
-    return this.$.bar as BarProviderElement;
-  }
-
-  override connectedCallback() {
-    provide(this, fooToken, () => new FooImpl('foo'));
-    super.connectedCallback();
-  }
-
-  static get template() {
-    return polyHtml`<bar-provider id="bar"></bar-provider>`;
-  }
-}
-
 @customElement('bar-provider')
 export class BarProviderElement extends LitElement {
   @query('leaf-lit-element')
   litChild?: LeafLitElement;
 
-  @query('leaf-polymer-element')
-  polymerChild?: LeafPolymerElement;
-
-  @property({type: Boolean})
-  public showLit = true;
-
   override connectedCallback() {
     super.connectedCallback();
     provide(this, barToken, () => this.create());
@@ -94,11 +70,7 @@
   }
 
   override render() {
-    if (this.showLit) {
-      return html`<leaf-lit-element></leaf-lit-element>`;
-    } else {
-      return html`<leaf-polymer-element></leaf-polymer-element>`;
-    }
+    return html`<leaf-lit-element></leaf-lit-element>`;
   }
 }
 
@@ -116,20 +88,6 @@
   }
 }
 
-@polyCustomElement('leaf-polymer-element')
-export class LeafPolymerElement extends DIPolymerElement {
-  readonly barRef = resolve(this, barToken);
-
-  override connectedCallback() {
-    super.connectedCallback();
-    assert.isDefined(this.barRef());
-  }
-
-  static get template() {
-    return polyHtml`Hello`;
-  }
-}
-
 suite('Dependency', () => {
   test('It instantiates', async () => {
     const fixture = fixtureFromElement('lit-foo-provider');
@@ -138,13 +96,6 @@
     assert.isDefined(element.bar?.litChild?.barRef());
   });
 
-  test('It instantiates in polymer', async () => {
-    const fixture = fixtureFromElement('polymer-foo-provider');
-    const element = fixture.instantiate();
-    await element.bar().updateComplete;
-    assert.isDefined(element.bar().litChild?.barRef());
-  });
-
   test('It works by connecting and reconnecting', async () => {
     const fixture = fixtureFromElement('lit-foo-provider');
     const element = fixture.instantiate();
@@ -159,29 +110,12 @@
     await element.updateComplete;
     assert.isDefined(element.bar?.litChild?.barRef());
   });
-
-  test('It works by connecting and reconnecting Polymer', async () => {
-    const fixture = fixtureFromElement('lit-foo-provider');
-    const element = fixture.instantiate();
-    await element.updateComplete;
-
-    const beta = element.bar;
-    assert.isDefined(beta);
-    assert.isNotNull(beta);
-    assert.isDefined(element.bar?.litChild?.barRef());
-
-    beta!.showLit = false;
-    await element.updateComplete;
-    assert.isDefined(element.bar?.polymerChild?.barRef());
-  });
 });
 
 declare global {
   interface HTMLElementTagNameMap {
     'lit-foo-provider': LitFooProviderElement;
-    'polymer-foo-provider': PolymerFooProviderElement;
     'bar-provider': BarProviderElement;
     'leaf-lit-element': LeafLitElement;
-    'leaf-polymer-element': LeafPolymerElement;
   }
 }
diff --git a/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts b/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts
index c27da15..7ca429b 100644
--- a/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts
+++ b/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts
@@ -26,6 +26,8 @@
 import {FlagsService} from '../flags/flags';
 import {define} from '../../models/dependency';
 
+export {Shortcut, ShortcutSection};
+
 export type SectionView = Array<{binding: string[][]; text: string}>;
 
 export interface ShortcutListener {
@@ -67,12 +69,6 @@
    */
   private readonly activeShortcuts = new Set<Shortcut>();
 
-  /**
-   * Keeps track of cleanup callbacks (which remove keyboard listeners) that
-   * have to be invoked when a component unregisters itself.
-   */
-  private readonly cleanupsPerHost = new Map<HTMLElement, (() => void)[]>();
-
   /** Static map built in the constructor by iterating over the config. */
   private readonly bindings = new Map<Shortcut, Binding[]>();
 
@@ -239,23 +235,6 @@
     };
   }
 
-  /**
-   * Being called by the Polymer specific KeyboardShortcutMixin.
-   */
-  attachHost(host: HTMLElement, shortcuts: ShortcutListener[]) {
-    const cleanups: (() => void)[] = [];
-    for (const s of shortcuts) {
-      cleanups.push(this.addShortcutListener(s.shortcut, s.listener));
-    }
-    this.cleanupsPerHost.set(host, cleanups);
-  }
-
-  detachHost(host: HTMLElement) {
-    const cleanups = this.cleanupsPerHost.get(host);
-    for (const cleanup of cleanups ?? []) cleanup();
-    return true;
-  }
-
   addListener(listener: ShortcutViewListener) {
     this.listeners.add(listener);
     listener(this.directoryView());
diff --git a/polygerrit-ui/app/services/shortcuts/shortcuts-service_test.ts b/polygerrit-ui/app/services/shortcuts/shortcuts-service_test.ts
index 9e978a7..69c371e 100644
--- a/polygerrit-ui/app/services/shortcuts/shortcuts-service_test.ts
+++ b/polygerrit-ui/app/services/shortcuts/shortcuts-service_test.ts
@@ -120,9 +120,7 @@
     test('active shortcuts by section', () => {
       assert.deepEqual(mapToObject(service.activeShortcutsBySection()), {});
 
-      service.attachHost(document.createElement('div'), [
-        {shortcut: Shortcut.NEXT_FILE, listener: _ => {}},
-      ]);
+      service.addShortcutListener(Shortcut.NEXT_FILE, _ => {});
       assert.deepEqual(mapToObject(service.activeShortcutsBySection()), {
         [ShortcutSection.NAVIGATION]: [
           {
@@ -132,10 +130,7 @@
           },
         ],
       });
-
-      service.attachHost(document.createElement('div'), [
-        {shortcut: Shortcut.NEXT_LINE, listener: _ => {}},
-      ]);
+      service.addShortcutListener(Shortcut.NEXT_LINE, _ => {});
       assert.deepEqual(mapToObject(service.activeShortcutsBySection()), {
         [ShortcutSection.DIFFS]: [
           {
@@ -156,10 +151,8 @@
         ],
       });
 
-      service.attachHost(document.createElement('div'), [
-        {shortcut: Shortcut.SEARCH, listener: _ => {}},
-        {shortcut: Shortcut.GO_TO_OPENED_CHANGES, listener: _ => {}},
-      ]);
+      service.addShortcutListener(Shortcut.SEARCH, _ => {});
+      service.addShortcutListener(Shortcut.GO_TO_OPENED_CHANGES, _ => {});
       assert.deepEqual(mapToObject(service.activeShortcutsBySection()), {
         [ShortcutSection.DIFFS]: [
           {
@@ -196,13 +189,11 @@
     test('directory view', () => {
       assert.deepEqual(mapToObject(service.directoryView()), {});
 
-      service.attachHost(document.createElement('div'), [
-        {shortcut: Shortcut.GO_TO_OPENED_CHANGES, listener: _ => {}},
-        {shortcut: Shortcut.NEXT_FILE, listener: _ => {}},
-        {shortcut: Shortcut.NEXT_LINE, listener: _ => {}},
-        {shortcut: Shortcut.SAVE_COMMENT, listener: _ => {}},
-        {shortcut: Shortcut.SEARCH, listener: _ => {}},
-      ]);
+      service.addShortcutListener(Shortcut.GO_TO_OPENED_CHANGES, _ => {});
+      service.addShortcutListener(Shortcut.NEXT_FILE, _ => {});
+      service.addShortcutListener(Shortcut.NEXT_LINE, _ => {});
+      service.addShortcutListener(Shortcut.SAVE_COMMENT, _ => {});
+      service.addShortcutListener(Shortcut.SEARCH, _ => {});
       assert.deepEqual(mapToObject(service.directoryView()), {
         [ShortcutSection.DIFFS]: [
           {binding: [['j'], ['↓']], text: 'Go to next line'},