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
};
}
}
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]);
}
}
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);
});
});
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 { }
The Implementation
On a button or div, add the following ...
(konami)="openEasterEgg()"
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.