DEV Community

Cover image for Angular Konami Code
bob.js
bob.js

Posted on

Angular Konami Code

I've had the pleasure of working on several projects where small easter eggs were allowed.

In the most recent, I build an Angular Directive, created a Module, and actually built Unit Tests.

Here's the code.

The Module

File: konami.module.ts

import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { KonamiDirective } from './konami.directive';

export * from './konami.directive';

@NgModule({
  imports: [ CommonModule ],
  declarations: [ KonamiDirective ],
  exports: [ KonamiDirective ]
})
export class KonamiModule {
  static forRoot(): ModuleWithProviders<KonamiModule> {
    return {
      ngModule: KonamiModule
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

Clearly, this is in the same folder as the Directive.

The Directive

File: konami.directive.ts

import { Directive, EventEmitter, HostListener, Output } from '@angular/core';

@Directive({
  selector: '[konami]'
})
export class KonamiDirective {

  @Output() private konami: EventEmitter<void>;

  private sequence: string[];

  private konamiCode: string[];

  constructor() {
    this.konami = new EventEmitter<void>();
    this.sequence = [];
    this.konamiCode = [
      'arrowup', 'arrowup',
      'arrowdown', 'arrowdown',
      'arrowleft', 'arrowright',
      'arrowleft', 'arrowright',
      'b', 'a'
    ];
  }

  @HostListener('window:keydown', ['$event'])
  handleKeyboardEvent(event: KeyboardEvent) {
    if (event.key) {
      this.sequence.push(event.key.toLowerCase());

      if (this.sequence.length > this.konamiCode.length) {
        this.sequence.shift();
      }

      if (this.isKonamiCode()) {
        this.konami.emit();
      }
    }
  }

  isKonamiCode(): boolean {
    return this.konamiCode.every((code: string, index: number) => code === this.sequence[index]);
  }
}
Enter fullscreen mode Exit fullscreen mode

And, all this gets tested ...

The Unit Tests

File: konami.directive.spec.ts

import { KonamiDirective } from './konami.directive';

describe('Konami2irective', () => {
  let directive;

  beforeEach(() => {
    directive = new KonamiDirective();
  });

  afterEach(() => {
    directive = null;
  });

  it('should create an instance', () => {
    expect(directive).toBeTruthy();
  });

  it('expects "handleKeyboardEvent" to add keydown to sequence', () => {
    spyOn(directive.konami, 'emit').and.stub();
    spyOn(directive, 'isKonamiCode').and.callThrough();
    const keyEvent = new KeyboardEvent('keydown', { key: 'ArrowUp' });
    directive.sequence = [];

    directive.handleKeyboardEvent(keyEvent);
    expect(directive.sequence).toEqual([ 'arrowup' ]);
    expect(directive.isKonamiCode).toHaveBeenCalled();
    expect(directive.konami.emit).not.toHaveBeenCalled();
  });

  it('expects "handleKeyboardEvent" to trigger a konami emit', () => {
    spyOn(directive.konami, 'emit').and.stub();
    spyOn(directive, 'isKonamiCode').and.callThrough();
    const keyEvent = new KeyboardEvent('keydown', { key: 'A' });
    directive.sequence = ['arrowup', 'arrowup', 'arrowdown', 'arrowdown', 'arrowleft', 'arrowright', 'arrowleft', 'arrowright', 'b'];

    directive.handleKeyboardEvent(keyEvent);
    expect(directive.isKonamiCode).toHaveBeenCalled();
    expect(directive.konami.emit).toHaveBeenCalled();
  });

  it('expects "handleKeyboardEvent" to not work if event had no key detail', () => {
    spyOn(directive.konami, 'emit').and.stub();
    spyOn(directive, 'isKonamiCode').and.callThrough();
    const keyEvent = new KeyboardEvent('keydown', { key: '' });
    directive.sequence = ['arrowup', 'arrowup', 'arrowdown', 'arrowdown', 'arrowleft', 'arrowright', 'arrowleft', 'arrowright', 'a'];

    directive.handleKeyboardEvent(keyEvent);
    expect(directive.isKonamiCode).not.toHaveBeenCalled();
    expect(directive.konami.emit).not.toHaveBeenCalled();
  });

  it('expects "handleKeyboardEvent" to add to the sequence, removing from the front', () => {
    spyOn(directive.konami, 'emit').and.stub();
    spyOn(directive, 'isKonamiCode').and.callThrough();
    directive.sequence = ['arrowup', 'arrowup', 'arrowdown', 'arrowdown', 'arrowleft', 'arrowright', 'arrowleft', 'arrowright', 'b', 'a'];
    const result =  ['arrowup', 'arrowdown', 'arrowdown', 'arrowleft', 'arrowright', 'arrowleft', 'arrowright', 'b', 'a', 'c'];
    const keyEvent = new KeyboardEvent('keydown', { key: 'C' });

    directive.handleKeyboardEvent(keyEvent);
    expect(directive.sequence).toEqual(result);
  });

  it('expects "isKonamiCode" to return true with a correct sequence', () => {
    spyOn(directive.konami, 'emit').and.stub();
    spyOn(directive, 'isKonamiCode').and.callThrough();
    directive.sequence = ['arrowup', 'arrowup', 'arrowdown', 'arrowdown', 'arrowleft', 'arrowright', 'arrowleft', 'arrowright', 'b', 'a'];

    expect(directive.isKonamiCode()).toEqual(true);
  });

});
Enter fullscreen mode Exit fullscreen mode

Now, with all this in place it needs to be added to the application.

Adding To Angular

File: app.module.ts

import { KonamiModule } from '@shared/konami/konami.module';

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
    KonamiModule,
    ...
  ],
  providers: [],
  bootstrap: [
    ...
  ]
})
export class AppModule { } 
Enter fullscreen mode Exit fullscreen mode

The Implementation

On a button or div, add the following ...

(konami)="openEasterEgg()"
Enter fullscreen mode Exit fullscreen mode

Clearly, you would want the openEasterEgg function to do something. I generally have it open a Modal with some reference to the design team.

Enjoy.

Discussion (0)