Skip to content

Commit

Permalink
feat(#315): ngMocks.trigger and ngMocks.click
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Mar 27, 2021
1 parent 93d5813 commit 7aa7deb
Show file tree
Hide file tree
Showing 9 changed files with 550 additions and 0 deletions.
5 changes: 5 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/events/mock-helper.click.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import mockHelperTrigger from './mock-helper.trigger';

export default (selector: any, payload?: object) => {
mockHelperTrigger(selector, 'click', payload);
};
54 changes: 54 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/events/mock-helper.trigger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { DebugElement } from '@angular/core';

import mockHelperStub from '../mock-helper.stub';

/**
* @see https://developer.mozilla.org/de/docs/Web/Events
*/
const preventBubble = ['focus', 'blur', 'load', 'unload', 'change', 'reset', 'scroll'];

const toEventObj = (
event: string | UIEvent | KeyboardEvent | MouseEvent | TouchEvent | Event,
debugElement?: DebugElement | null,
): Event => {
const eventObj =
typeof event === 'string'
? new Event(event, {
bubbles: preventBubble.indexOf(event) === -1,
cancelable: true,
})
: event;
if (!eventObj.target && debugElement) {
mockHelperStub(eventObj, {
target: debugElement.nativeElement,
});
}

return eventObj;
};

export default (
debugElement: DebugElement | undefined | null,
eventName: string | UIEvent | KeyboardEvent | MouseEvent | TouchEvent | Event,
payload?: Partial<UIEvent | KeyboardEvent | MouseEvent | TouchEvent | Event>,
) => {
if (!debugElement) {
throw new Error(
`Cannot trigger ${typeof eventName === 'string' ? eventName : eventName.type} event undefined element`,
);
}

// nothing to emit on disabled elements
if (debugElement.nativeElement.disabled) {
return;
}

const callback = (event: Event) => {
if (payload) {
mockHelperStub(event, payload);
}
};
debugElement.nativeElement.addEventListener(eventName, callback, true);
debugElement.nativeElement.dispatchEvent(toEventObj(eventName, debugElement));
debugElement.nativeElement.removeEventListener(eventName, callback, true);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const normalizeValue = (html: string | undefined): string =>
? html
.replace(new RegExp('\\s+', 'mg'), ' ')
.replace(new RegExp('<!--.*?-->', 'mg'), '')
.replace(new RegExp('<!--.*?', 'mg'), '')
.replace(new RegExp('\\s+', 'mg'), ' ')
.replace(new RegExp('>\\s+<', 'mg'), '><')
: '';
Expand Down
4 changes: 4 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import mockHelperReveal from './crawl/mock-helper.reveal';
import mockHelperRevealAll from './crawl/mock-helper.reveal-all';
import mockHelperChange from './cva/mock-helper.change';
import mockHelperTouch from './cva/mock-helper.touch';
import mockHelperClick from './events/mock-helper.click';
import mockHelperTrigger from './events/mock-helper.trigger';
import mockHelperFormatHtml from './format/mock-helper.format-html';
import mockHelperFormatText from './format/mock-helper.format-text';
import mockHelperAutoSpy from './mock-helper.auto-spy';
Expand Down Expand Up @@ -34,6 +36,7 @@ import mockHelperFindTemplateRefs from './template-ref/mock-helper.find-template
export default {
autoSpy: mockHelperAutoSpy,
change: mockHelperChange,
click: mockHelperClick,
crawl: mockHelperCrawl,
defaultMock: mockHelperDefaultMock,
faster: mockHelperFaster,
Expand Down Expand Up @@ -64,4 +67,5 @@ export default {
stubMember: mockHelperStubMember,
throwOnConsole: mockHelperThrowOnConsole,
touch: mockHelperTouch,
trigger: mockHelperTrigger,
};
19 changes: 19 additions & 0 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export const ngMocks: {
*/
change(el: DebugNode, value: any): void;

/**
* @see https://ng-mocks.sudo.eu/api/ngMocks/click
*/
click(debugElement: MockedDebugElement | undefined | null, payload?: Partial<MouseEvent>): void;

/**
* @see https://ng-mocks.sudo.eu/api/ngMocks/crawl
*/
Expand Down Expand Up @@ -492,4 +497,18 @@ export const ngMocks: {
* @see https://ng-mocks.sudo.eu/api/ngMocks/touch
*/
touch(el: DebugNode): void;

/**
* @see https://ng-mocks.sudo.eu/api/ngMocks/trigger
*/
trigger(debugElement: MockedDebugElement | undefined | null, event: Event): void;

/**
* @see https://ng-mocks.sudo.eu/api/ngMocks/trigger
*/
trigger(
debugElement: MockedDebugElement | undefined | null,
event: string,
payload?: Partial<UIEvent | KeyboardEvent | MouseEvent | TouchEvent>,
): void;
} = mockHelperObject;
139 changes: 139 additions & 0 deletions tests/ng-mocks-trigger/blur.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {
Component,
ElementRef,
HostListener,
NgModule,
OnDestroy,
ViewChild,
} from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import {
MockBuilder,
MockRender,
MockService,
ngMocks,
} from 'ng-mocks';
import { fromEvent, Subscription } from 'rxjs';
import { tap } from 'rxjs/operators';

@Component({
selector: 'target',
template: `
<input
[formControl]="control"
(blur)="blurTag = $event"
#element
/>
`,
})
class TargetComponent implements OnDestroy {
public blurFromEvent: any;
public blurListener: any;
public blurTag: any;

public readonly control = new FormControl(null, {
updateOn: 'blur',
});

private subscription?: Subscription;

@ViewChild('element')
public set element(value: ElementRef) {
this.subscription?.unsubscribe();
this.subscription = fromEvent(value.nativeElement, 'blur')
.pipe(
tap(event => {
this.blurFromEvent = event;
}),
)
.subscribe();
}

@HostListener('blur', ['$event'])
public hostListenerClick(event: any) {
this.blurListener = event;
}

public ngOnDestroy(): void {
this.subscription?.unsubscribe();
}
}

@NgModule({
declarations: [TargetComponent],
imports: [ReactiveFormsModule],
})
class TargetModule {}

describe('ng-mocks-trigger:blur', () => {
beforeEach(() => MockBuilder(TargetComponent).keep(TargetModule));

it('is able to blur for all subscribers via ngMocks.trigger with string', () => {
const fixture = MockRender(TargetComponent);
const component = fixture.point.componentInstance;
expect(component.blurFromEvent).toBeUndefined();
expect(component.blurTag).toBeUndefined();

const debugElement = ngMocks.find('input');
ngMocks.trigger(debugElement, 'blur', {
x: 666,
y: 777,
});
expect(component.blurTag).toEqual(
jasmine.objectContaining({
x: 666,
y: 777,
}),
);
expect(component.blurFromEvent).toBe(component.blurTag);

expect(component.blurListener).toBeUndefined();
ngMocks.trigger(fixture.point, 'blur');
expect(component.blurListener).toBeDefined();
});

it('is able to blur for all subscribers via ngMocks.trigger with event', () => {
const fixture = MockRender(TargetComponent);
const component = fixture.point.componentInstance;
expect(component.blurFromEvent).toBeUndefined();
expect(component.blurTag).toBeUndefined();

const debugElement = ngMocks.find('input');
const event = new MouseEvent('blur', {
bubbles: false,
});
ngMocks.stub(event, {
x: 666,
y: 777,
});
ngMocks.trigger(debugElement, event);
expect(component.blurTag).toEqual(
jasmine.objectContaining({
x: 666,
y: 777,
}),
);
expect(component.blurFromEvent).toBe(component.blurTag);

expect(component.blurListener).toBeUndefined();
ngMocks.trigger(fixture.point, event);
expect(component.blurListener).toBeDefined();
});

it('behaves right with input and blur', () => {
const fixture = MockRender(TargetComponent);
const component = fixture.point.componentInstance;
expect(component.control.value).toEqual(null);

const debugElement = ngMocks.find('input');
ngMocks.trigger(debugElement, 'input', {
target: MockService(HTMLInputElement, {
value: '666',
}),
});
expect(component.control.value).toEqual(null);

ngMocks.trigger(debugElement, 'blur');
expect(component.control.value).toEqual('666');
});
});
128 changes: 128 additions & 0 deletions tests/ng-mocks-trigger/click.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { CommonModule } from '@angular/common';
import {
Component,
ElementRef,
HostListener,
NgModule,
OnDestroy,
ViewChild,
} from '@angular/core';
import { MockBuilder, MockRender, ngMocks } from 'ng-mocks';
import { fromEvent, Subscription } from 'rxjs';
import { tap } from 'rxjs/operators';

@Component({
selector: 'target',
template: ` <div (click)="clickTag = $event" #element></div> `,
})
class TargetComponent implements OnDestroy {
public clickFromEvent: any;
public clickListener: any;
public clickTag: any;

private subscription?: Subscription;

@ViewChild('element')
public set element(value: ElementRef) {
this.subscription?.unsubscribe();
this.subscription = fromEvent(value.nativeElement, 'click')
.pipe(
tap(event => {
this.clickFromEvent = event;
}),
)
.subscribe();
}

@HostListener('click', ['$event'])
public hostListenerClick(event: any) {
this.clickListener = event;
}

public ngOnDestroy(): void {
this.subscription?.unsubscribe();
}
}

@NgModule({
declarations: [TargetComponent],
imports: [CommonModule],
})
class TargetModule {}

describe('ng-mocks-trigger:click', () => {
beforeEach(() => MockBuilder(TargetComponent).keep(TargetModule));

it('is able to click for all subscribers via ngMocks.click', () => {
const component = MockRender(TargetComponent).point
.componentInstance;
expect(component.clickFromEvent).toBeUndefined();
expect(component.clickListener).toBeUndefined();
expect(component.clickTag).toBeUndefined();

const div = ngMocks.find('div');
ngMocks.click(div, {
x: 666,
y: 777,
});

expect(component.clickFromEvent).toEqual(
jasmine.objectContaining({
x: 666,
y: 777,
}),
);
expect(component.clickListener).toBe(component.clickFromEvent);
expect(component.clickTag).toBe(component.clickFromEvent);
});

it('is able to click for all subscribers via ngMocks.touch with string', () => {
const component = MockRender(TargetComponent).point
.componentInstance;
expect(component.clickFromEvent).toBeUndefined();
expect(component.clickListener).toBeUndefined();
expect(component.clickTag).toBeUndefined();

const div = ngMocks.find('div');
ngMocks.trigger(div, 'click', {
x: 666,
y: 777,
});

expect(component.clickFromEvent).toEqual(
jasmine.objectContaining({
x: 666,
y: 777,
}),
);
expect(component.clickListener).toBe(component.clickFromEvent);
expect(component.clickTag).toBe(component.clickFromEvent);
});

it('is able to click for all subscribers via ngMocks.touch with event', () => {
const component = MockRender(TargetComponent).point
.componentInstance;
expect(component.clickFromEvent).toBeUndefined();
expect(component.clickListener).toBeUndefined();
expect(component.clickTag).toBeUndefined();

const div = ngMocks.find('div');
const event = new MouseEvent('click', {
bubbles: true,
});
ngMocks.stub(event, {
x: 666,
y: 777,
});
ngMocks.trigger(div, event);

expect(component.clickFromEvent).toEqual(
jasmine.objectContaining({
x: 666,
y: 777,
}),
);
expect(component.clickListener).toBe(component.clickFromEvent);
expect(component.clickTag).toBe(component.clickFromEvent);
});
});
Loading

0 comments on commit 7aa7deb

Please sign in to comment.