DEV Community

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

Posted on

9 1

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.

Image of AssemblyAI tool

Transforming Interviews into Publishable Stories with AssemblyAI

Insightview is a modern web application that streamlines the interview workflow for journalists. By leveraging AssemblyAI's LeMUR and Universal-2 technology, it transforms raw interview recordings into structured, actionable content, dramatically reducing the time from recording to publication.

Key Features:
🎥 Audio/video file upload with real-time preview
🗣️ Advanced transcription with speaker identification
⭐ Automatic highlight extraction of key moments
✍️ AI-powered article draft generation
📤 Export interview's subtitles in VTT format

Read full post

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay