Skip to content

Commit

Permalink
feat: add testing utilities for components
Browse files Browse the repository at this point in the history
* Moves existing testing helpers to a testing folder in the core package.
* The testing utilities currently consist of functions to create dom events and shorthands to dispatch them.
* Also fixes that **releases** include components `spec.d.ts` files.

The `core/testing` utilities are not includes in releases for now. It could be possible to expose them as part of the Component Toolkit.

Closes angular#2902
  • Loading branch information
devversion committed Feb 24, 2017
1 parent c203589 commit ecdab95
Show file tree
Hide file tree
Showing 14 changed files with 181 additions and 220 deletions.
44 changes: 15 additions & 29 deletions src/lib/autocomplete/autocomplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler';
import {MdAutocomplete} from './autocomplete';
import {MdInputContainer} from '../input/input-container';
import {dispatchFakeEvent} from '../core/testing/dispatch-events';

describe('MdAutocomplete', () => {
let overlayContainerElement: HTMLElement;
Expand Down Expand Up @@ -61,7 +62,7 @@ describe('MdAutocomplete', () => {
expect(fixture.componentInstance.trigger.panelOpen)
.toBe(false, `Expected panel state to start out closed.`);

dispatchEvent('focus', input);
dispatchFakeEvent(input, 'focus');
fixture.detectChanges();

expect(fixture.componentInstance.trigger.panelOpen)
Expand All @@ -88,11 +89,11 @@ describe('MdAutocomplete', () => {
});

it('should close the panel when blurred', async(() => {
dispatchEvent('focus', input);
dispatchFakeEvent(input, 'focus');
fixture.detectChanges();

fixture.whenStable().then(() => {
dispatchEvent('blur', input);
dispatchFakeEvent(input, 'blur');
fixture.detectChanges();

expect(fixture.componentInstance.trigger.panelOpen)
Expand All @@ -103,7 +104,7 @@ describe('MdAutocomplete', () => {
}));

it('should close the panel when an option is clicked', async(() => {
dispatchEvent('focus', input);
dispatchFakeEvent(input, 'focus');
fixture.detectChanges();

fixture.whenStable().then(() => {
Expand All @@ -119,7 +120,7 @@ describe('MdAutocomplete', () => {
}));

it('should close the panel when a newly created option is clicked', async(() => {
dispatchEvent('focus', input);
dispatchFakeEvent(input, 'focus');
fixture.detectChanges();

fixture.whenStable().then(() => {
Expand Down Expand Up @@ -164,7 +165,7 @@ describe('MdAutocomplete', () => {
});

it('should hide the panel when the options list is empty', async(() => {
dispatchEvent('focus', input);
dispatchFakeEvent(input, 'focus');

fixture.whenStable().then(() => {
fixture.detectChanges();
Expand Down Expand Up @@ -423,7 +424,7 @@ describe('MdAutocomplete', () => {
expect(fixture.componentInstance.stateCtrl.touched)
.toBe(false, `Expected control to start out untouched.`);

dispatchEvent('blur', input);
dispatchFakeEvent(input, 'blur');
fixture.detectChanges();

expect(fixture.componentInstance.stateCtrl.touched)
Expand All @@ -443,8 +444,8 @@ describe('MdAutocomplete', () => {
fixture.detectChanges();

input = fixture.debugElement.query(By.css('input')).nativeElement;
DOWN_ARROW_EVENT = new FakeKeyboardEvent(DOWN_ARROW) as KeyboardEvent;
ENTER_EVENT = new FakeKeyboardEvent(ENTER) as KeyboardEvent;
DOWN_ARROW_EVENT = new MockKeyboardEvent(DOWN_ARROW) as KeyboardEvent;
ENTER_EVENT = new MockKeyboardEvent(ENTER) as KeyboardEvent;

fixture.componentInstance.trigger.openPanel();
fixture.detectChanges();
Expand Down Expand Up @@ -505,7 +506,7 @@ describe('MdAutocomplete', () => {
const optionEls =
overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;

const UP_ARROW_EVENT = new FakeKeyboardEvent(UP_ARROW) as KeyboardEvent;
const UP_ARROW_EVENT = new MockKeyboardEvent(UP_ARROW) as KeyboardEvent;
fixture.componentInstance.trigger._handleKeydown(UP_ARROW_EVENT);

fixture.whenStable().then(() => {
Expand Down Expand Up @@ -571,7 +572,7 @@ describe('MdAutocomplete', () => {
typeInElement('New', input);
fixture.detectChanges();

const SPACE_EVENT = new FakeKeyboardEvent(SPACE) as KeyboardEvent;
const SPACE_EVENT = new MockKeyboardEvent(SPACE) as KeyboardEvent;
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
fixture.componentInstance.trigger._handleKeydown(SPACE_EVENT);
fixture.detectChanges();
Expand Down Expand Up @@ -680,7 +681,7 @@ describe('MdAutocomplete', () => {
expect(input.hasAttribute('aria-activedescendant'))
.toBe(false, 'Expected aria-activedescendant to be absent if no active item.');

const DOWN_ARROW_EVENT = new FakeKeyboardEvent(DOWN_ARROW) as KeyboardEvent;
const DOWN_ARROW_EVENT = new MockKeyboardEvent(DOWN_ARROW) as KeyboardEvent;
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
fixture.detectChanges();

Expand Down Expand Up @@ -905,21 +906,6 @@ class AutocompleteWithoutForms {

}

/**
* TODO: Move this to core testing utility until Angular has event faking
* support.
*
* Dispatches an event from an element.
* @param eventName Name of the event
* @param element The element from which the event will be dispatched.
*/
function dispatchEvent(eventName: string, element: HTMLElement): void {
let event = document.createEvent('Event');
event.initEvent(eventName, true, true);
element.dispatchEvent(event);
}


/**
* Focuses an input, sets its value and dispatches
* the `input` event, simulating the user typing.
Expand All @@ -929,11 +915,11 @@ function dispatchEvent(eventName: string, element: HTMLElement): void {
function typeInElement(value: string, element: HTMLInputElement) {
element.focus();
element.value = value;
dispatchEvent('input', element);
dispatchFakeEvent(element, 'input');
}

/** This is a mock keyboard event to test keyboard events in the autocomplete. */
class FakeKeyboardEvent {
class MockKeyboardEvent {
constructor(public keyCode: number) {}
preventDefault() {}
}
5 changes: 2 additions & 3 deletions src/lib/core/overlay/scroll/scroll-dispatcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {NgModule, Component, ViewChild, ElementRef} from '@angular/core';
import {ScrollDispatcher} from './scroll-dispatcher';
import {OverlayModule} from '../overlay-directives';
import {Scrollable} from './scrollable';
import {dispatchFakeEvent} from '../../testing/dispatch-events';

describe('Scroll Dispatcher', () => {

Expand Down Expand Up @@ -53,9 +54,7 @@ describe('Scroll Dispatcher', () => {
// Emit a scroll event from the scrolling element in our component.
// This event should be picked up by the scrollable directive and notify.
// The notification should be picked up by the service.
const scrollEvent = document.createEvent('UIEvents');
scrollEvent.initUIEvent('scroll', true, true, window, 0);
fixture.componentInstance.scrollingElement.nativeElement.dispatchEvent(scrollEvent);
dispatchFakeEvent(fixture.componentInstance.scrollingElement.nativeElement, 'scroll');

// The scrollable directive should have notified the service immediately.
expect(hasDirectiveScrollNotified).toBe(true);
Expand Down
63 changes: 25 additions & 38 deletions src/lib/core/ripple/ripple.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Component, ViewChild} from '@angular/core';
import {MdRipple, MdRippleModule} from './ripple';
import {ViewportRuler} from '../overlay/position/viewport-ruler';
import {RIPPLE_FADE_OUT_DURATION, RIPPLE_FADE_IN_DURATION} from './ripple-renderer';
import {dispatchMouseEvent} from '../testing/dispatch-events';


/** Creates a DOM mouse event. */
Expand Down Expand Up @@ -65,15 +66,6 @@ describe('MdRipple', () => {
document.body.style.margin = originalBodyMargin;
});

function dispatchMouseEvent(type: string, offsetX = 0, offsetY = 0) {
let mouseEvent = createMouseEvent(type, {
clientX: rippleTarget.clientLeft + offsetX,
clientY: rippleTarget.clientTop + offsetY
});

rippleTarget.dispatchEvent(mouseEvent);
}

describe('basic ripple', () => {
let rippleDirective: MdRipple;

Expand All @@ -89,20 +81,20 @@ describe('MdRipple', () => {
});

it('creates ripple on mousedown', () => {
dispatchMouseEvent('mousedown');
dispatchMouseEvent('mouseup');
dispatchMouseEvent(rippleTarget, 'mousedown');
dispatchMouseEvent(rippleTarget, 'mouseup');

expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);

dispatchMouseEvent('mousedown');
dispatchMouseEvent('mouseup');
dispatchMouseEvent(rippleTarget, 'mousedown');
dispatchMouseEvent(rippleTarget, 'mouseup');

expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(2);
});

it('removes ripple after timeout', fakeAsync(() => {
dispatchMouseEvent('mousedown');
dispatchMouseEvent('mouseup');
dispatchMouseEvent(rippleTarget, 'mousedown');
dispatchMouseEvent(rippleTarget, 'mouseup');

expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);

Expand Down Expand Up @@ -140,8 +132,8 @@ describe('MdRipple', () => {
let elementRect = rippleTarget.getBoundingClientRect();

// Dispatch a ripple at the following relative coordinates (X: 50| Y: 75)
dispatchMouseEvent('mousedown', 50, 75);
dispatchMouseEvent('mouseup');
dispatchMouseEvent(rippleTarget, 'mousedown', 50, 75);
dispatchMouseEvent(rippleTarget, 'mouseup');

// Calculate distance from the click to farthest edge of the ripple target.
let maxDistanceX = TARGET_WIDTH - 50;
Expand Down Expand Up @@ -174,8 +166,8 @@ describe('MdRipple', () => {
fixture.componentInstance.isDestroyed = true;
fixture.detectChanges();

dispatchMouseEvent('mousedown');
dispatchMouseEvent('mouseup');
dispatchMouseEvent(rippleTarget, 'mousedown');
dispatchMouseEvent(rippleTarget, 'mouseup');

expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
});
Expand Down Expand Up @@ -245,15 +237,10 @@ describe('MdRipple', () => {
rippleTarget.style.top = `${elementTop}px`;

// Simulate a keyboard-triggered click by setting event coordinates to 0.
let clickEvent = createMouseEvent('mousedown', {
clientX: left + elementLeft - pageScrollLeft,
clientY: top + elementTop - pageScrollTop,
screenX: left + elementLeft,
screenY: top + elementTop
});

rippleTarget.dispatchEvent(clickEvent);
dispatchMouseEvent('mouseup');
dispatchMouseEvent(rippleTarget, 'mousedown',
left + elementLeft - pageScrollLeft,
top + elementTop - pageScrollTop
);

let expectedRadius = Math.sqrt(250 * 250 + 125 * 125);
let expectedLeft = left - expectedRadius;
Expand Down Expand Up @@ -298,8 +285,8 @@ describe('MdRipple', () => {
controller.color = backgroundColor;
fixture.detectChanges();

dispatchMouseEvent('mousedown');
dispatchMouseEvent('mouseup');
dispatchMouseEvent(rippleTarget, 'mousedown');
dispatchMouseEvent(rippleTarget, 'mouseup');

let ripple = rippleTarget.querySelector('.mat-ripple-element');
expect(window.getComputedStyle(ripple).backgroundColor).toBe(backgroundColor);
Expand All @@ -309,16 +296,16 @@ describe('MdRipple', () => {
controller.disabled = true;
fixture.detectChanges();

dispatchMouseEvent('mousedown');
dispatchMouseEvent('mouseup');
dispatchMouseEvent(rippleTarget, 'mousedown');
dispatchMouseEvent(rippleTarget, 'mouseup');

expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);

controller.disabled = false;
fixture.detectChanges();

dispatchMouseEvent('mousedown');
dispatchMouseEvent('mouseup');
dispatchMouseEvent(rippleTarget, 'mousedown');
dispatchMouseEvent(rippleTarget, 'mouseup');

expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
});
Expand Down Expand Up @@ -352,8 +339,8 @@ describe('MdRipple', () => {
let elementRect = rippleTarget.getBoundingClientRect();

// Click the ripple element 50 px to the right and 75px down from its upper left.
dispatchMouseEvent('mousedown', 50, 75);
dispatchMouseEvent('mouseup');
dispatchMouseEvent(rippleTarget, 'mousedown', 50, 75);
dispatchMouseEvent(rippleTarget, 'mouseup');

// Because the centered input is true, the center of the ripple should be the midpoint of the
// bounding rect. The ripple should expand to cover the rect corners, which are 150px
Expand All @@ -379,8 +366,8 @@ describe('MdRipple', () => {
let elementRect = rippleTarget.getBoundingClientRect();

// Click the ripple element 50 px to the right and 75px down from its upper left.
dispatchMouseEvent('mousedown', 50, 75);
dispatchMouseEvent('mouseup');
dispatchMouseEvent(rippleTarget, 'mousedown', 50, 75);
dispatchMouseEvent(rippleTarget, 'mouseup');

let expectedLeft = elementRect.left + 50 - customRadius;
let expectedTop = elementRect.top + 75 - customRadius;
Expand Down
Loading

0 comments on commit ecdab95

Please sign in to comment.