Add Keyboard Shortcuts to your Vue App ⌨️
Tags: vue, typescript, javascript, tutorial
Published: 2020-09-01 22:17:00

I recently had to add some support for global key shortcuts in a Vue application I am working on. Vue has built-in support for listening to keys when you are in an input element. What is not supported directly are global shortcuts. For instance, if I am viewing an email in Gmail, I can hit 'a' to answer that email.

To accomplish this in Vue with either need to use low-level JavaScript event listeners or a plugin, like [vue-shortkey](https://www.npmjs.com/package/vue-shortkey). The approaches are not actually that different since vue-shortkey is, not surprisingly, just wrapping event listeners. It is straight forward to write your own event listener in a component so I didn't see a huge amount of value in using a plugin. There are a [few blog posts covering event listeners in Vue already](https://shubhamjain.co/til/vue-shortcuts/), but I am going to take a stab a showing a more complete example here, including how to test the component.

## Implementing

Let's say we wanted to create a component that displays a message whenever the escape key is pressed.

Our Template block:

```html
<div>{{ message }}</div>
```

Our Script block (Typescript):

```typescript
import Vue from "vue";

export default Vue.component("Demo", {
  created() {
    window.addEventListener("keydown", this.escapeListener);
  },
  // make sure you remove the listener when the component is no longer visible
  destroyed() {
    window.removeEventListener("keydown", this.escapeListener);
  },
  data: function() {
    return {
      message: ""
    };
  },
  methods: {
    escapeListener(event: KeyboardEvent) {
      if (event.key === "Escape") {
        this.message = "Escape has been pressed";
      }
    }
  }
});
```

If you prefer to use class syntax, change the script block to the following:

```typescript
export default class Demo extends Vue {
  private message = "";

  private created() {
    window.addEventListener("keydown", this.escapeListener);
  }

  private destroyed() {
    window.removeEventListener("keydown", this.escapeListener);
  }

  private escapeListener(event: KeyboardEvent) {
    if (event.key === "Escape") {
      this.message = "Escape has been pressed";
    }
  }
}
```

## Testing

This is all well and good, but it was not immediately clear how you test this behavior. After a few false starts, I stumbled upon a [Github issue thread with the solution](https://github.com/vuejs/vue-test-utils/issues/230#issuecomment-350457377).

The magic I was missing was to use the Vue testing utils `attachToDocument` option when mounting or shallow mounting the component under test. Once we attach our component to a document, we can use `wrapper.trigger` to simulate our keypresses.

```typescript
describe("Demo.vue", function() {
  it("Displays a message when escape is pressed", function() {
    const wrapper = shallowMount(Demo, { attachToDocument: true });

    // the browser will add 'key' to the event,
    // but when testing we need to add it manually
    wrapper.trigger("keydown.esc", { key: "Escape" });

    expect(wrapper.text()).to.include("Escape has been pressed");

    // always make sure to destroy when using attachToDocument
    wrapper.destroy();
  });
});
```

And that is it, a straightforward way to test-drive our global shortcuts as we add them to our component.