Skip to content

Commit 243d086

Browse files
committed
Add test. Use viewport ruler
1 parent 333474e commit 243d086

File tree

9 files changed

+184
-12
lines changed

9 files changed

+184
-12
lines changed

src/lib/button/button.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {async, TestBed, ComponentFixture} from '@angular/core/testing';
22
import {Component} from '@angular/core';
33
import {By} from '@angular/platform-browser';
44
import {MdButtonModule} from './button';
5+
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
56

67

78
describe('MdButton', () => {
@@ -10,6 +11,9 @@ describe('MdButton', () => {
1011
TestBed.configureTestingModule({
1112
imports: [MdButtonModule.forRoot()],
1213
declarations: [TestApp],
14+
providers: [
15+
{provide: ViewportRuler, useClass: FakeViewportRuler},
16+
]
1317
});
1418

1519
TestBed.compileComponents();
@@ -210,3 +214,15 @@ class TestApp {
210214
this.clickCount++;
211215
}
212216
}
217+
218+
class FakeViewportRuler {
219+
getViewportRect() {
220+
return {
221+
left: 0, top: 0, width: 1014, height: 686, bottom: 686, right: 1014
222+
};
223+
}
224+
225+
getViewportScrollPosition() {
226+
return {top: 0, left: 0};
227+
}
228+
}

src/lib/checkbox/checkbox.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import {Component, DebugElement} from '@angular/core';
1313
import {By} from '@angular/platform-browser';
1414
import {MdCheckbox, MdCheckboxChange, MdCheckboxModule} from './checkbox';
15+
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
1516

1617

1718
// TODO: Implement E2E tests for spacebar/click behavior for checking/unchecking
@@ -32,6 +33,9 @@ describe('MdCheckbox', () => {
3233
CheckboxWithNameAttribute,
3334
CheckboxWithChangeEvent,
3435
],
36+
providers: [
37+
{provide: ViewportRuler, useClass: FakeViewportRuler},
38+
]
3539
});
3640

3741
TestBed.compileComponents();
@@ -660,3 +664,15 @@ class CheckboxWithNameAttribute {}
660664
class CheckboxWithChangeEvent {
661665
lastEvent: MdCheckboxChange;
662666
}
667+
668+
class FakeViewportRuler {
669+
getViewportRect() {
670+
return {
671+
left: 0, top: 0, width: 1014, height: 686, bottom: 686, right: 1014
672+
};
673+
}
674+
675+
getViewportScrollPosition() {
676+
return {top: 0, left: 0};
677+
}
678+
}

src/lib/core/overlay/position/viewport-ruler.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,8 @@ export class ViewportRuler {
5050
// `scrollTop` and `scrollLeft` is inconsistent. However, using the bounding rect of
5151
// `document.documentElement` works consistently, where the `top` and `left` values will
5252
// equal negative the scroll position.
53-
const top = documentRect.top < 0 && document.body.scrollTop == 0 ?
54-
-documentRect.top :
55-
document.body.scrollTop;
56-
const left = documentRect.left < 0 && document.body.scrollLeft == 0 ?
57-
-documentRect.left :
58-
document.body.scrollLeft;
53+
const top = -documentRect.top || document.body.scrollTop || window.scrollY || 0;
54+
const left = -documentRect.left || document.body.scrollLeft || window.scrollX || 0;
5955

6056
return {top, left};
6157
}

src/lib/core/ripple/ripple.spec.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ describe('MdRipple', () => {
185185
expect(pxStringToFloat(ripple.style.height)).toBeCloseTo(2 * expectedRadius, 1);
186186
});
187187

188+
188189
it('cleans up the event handlers when the container gets destroyed', () => {
189190
fixture = TestBed.createComponent(RippleContainerWithNgIf);
190191
fixture.detectChanges();
@@ -197,7 +198,97 @@ describe('MdRipple', () => {
197198

198199
rippleElement.dispatchEvent(createMouseEvent('mousedown'));
199200
expect(rippleBackground.classList).not.toContain('md-ripple-active');
201+
202+
it('create ripple with correct position when page is scrolled', () => {
203+
let elementTop = 600;
204+
let elementLeft = 750;
205+
let pageScrollTop = 500;
206+
let pageScrollLeft = 500;
207+
let left = 50;
208+
let top = 75;
209+
210+
// Add a very large element to make the page scroll
211+
let veryLargeElement = document.createElement('div');
212+
veryLargeElement.style.width = '4000px';
213+
veryLargeElement.style.height = '4000px';
214+
document.body.appendChild(veryLargeElement);
215+
document.body.scrollTop = pageScrollTop;
216+
document.body.scrollLeft = pageScrollLeft;
217+
218+
rippleElement.style.position = 'absolute';
219+
rippleElement.style.left = `${elementLeft}px`;
220+
rippleElement.style.top = `${elementTop}px`;
221+
222+
// Simulate a keyboard-triggered click by setting event coordinates to 0.
223+
const clickEvent = createMouseEvent('click', {
224+
clientX: left + elementLeft - pageScrollLeft,
225+
clientY: top + elementTop - pageScrollTop,
226+
screenX: left + elementLeft,
227+
screenY: top + elementTop
228+
});
200229
});
230+
231+
describe('when page is scrolled', () => {
232+
var veryLargeElement: HTMLDivElement = document.createElement('div');
233+
var pageScrollTop = 500;
234+
var pageScrollLeft = 500;
235+
236+
beforeEach(() => {
237+
// Add a very large element to make the page scroll
238+
veryLargeElement.style.width = '4000px';
239+
veryLargeElement.style.height = '4000px';
240+
document.body.appendChild(veryLargeElement);
241+
document.body.scrollTop = pageScrollTop;
242+
document.body.scrollLeft = pageScrollLeft;
243+
// Firefox
244+
document.documentElement.scrollLeft = pageScrollLeft;
245+
document.documentElement.scrollTop = pageScrollTop;
246+
// Mobile safari
247+
window.scrollTo(pageScrollLeft, pageScrollTop);
248+
});
249+
250+
afterEach(() => {
251+
document.body.removeChild(veryLargeElement);
252+
document.body.scrollTop = 0;
253+
document.body.scrollLeft = 0;
254+
// Firefox
255+
document.documentElement.scrollLeft = 0;
256+
document.documentElement.scrollTop = 0;
257+
// Mobile safari
258+
window.scrollTo(0, 0);
259+
});
260+
261+
it('create ripple with correct position', () => {
262+
let elementTop = 600;
263+
let elementLeft = 750;
264+
let left = 50;
265+
let top = 75;
266+
267+
rippleElement.style.position = 'absolute';
268+
rippleElement.style.left = `${elementLeft}px`;
269+
rippleElement.style.top = `${elementTop}px`;
270+
271+
// Simulate a keyboard-triggered click by setting event coordinates to 0.
272+
const clickEvent = createMouseEvent('click', {
273+
clientX: left + elementLeft - pageScrollLeft,
274+
clientY: top + elementTop - pageScrollTop,
275+
screenX: left + elementLeft,
276+
screenY: top + elementTop
277+
});
278+
rippleElement.dispatchEvent(clickEvent);
279+
280+
const expectedRadius = Math.sqrt(250 * 250 + 125 * 125);
281+
const expectedLeft = left - expectedRadius;
282+
const expectedTop = top - expectedRadius;
283+
284+
const ripple = <HTMLElement>rippleElement.querySelector('.md-ripple-foreground');
285+
expect(pxStringToFloat(ripple.style.left)).toBeCloseTo(expectedLeft, 1);
286+
expect(pxStringToFloat(ripple.style.top)).toBeCloseTo(expectedTop, 1);
287+
expect(pxStringToFloat(ripple.style.width)).toBeCloseTo(2 * expectedRadius, 1);
288+
expect(pxStringToFloat(ripple.style.height)).toBeCloseTo(2 * expectedRadius, 1);
289+
});
290+
});
291+
201292
});
202293

203294
describe('configuring behavior', () => {

src/lib/core/ripple/ripple.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
ForegroundRipple,
1616
ForegroundRippleState,
1717
} from './ripple-renderer';
18+
import {ViewportRuler} from '../overlay/position/viewport-ruler';
1819

1920

2021
@Directive({
@@ -60,14 +61,16 @@ export class MdRipple implements OnInit, OnDestroy, OnChanges {
6061
@HostBinding('class.md-ripple-unbounded') @Input('md-ripple-unbounded') unbounded: boolean;
6162

6263
private _rippleRenderer: RippleRenderer;
64+
_ruler: ViewportRuler;
6365

64-
constructor(_elementRef: ElementRef) {
66+
constructor(_elementRef: ElementRef, _ruler: ViewportRuler) {
6567
// These event handlers are attached to the element that triggers the ripple animations.
6668
const eventHandlers = new Map<string, (e: Event) => void>();
6769
eventHandlers.set('mousedown', (event: MouseEvent) => this._mouseDown(event));
6870
eventHandlers.set('click', (event: MouseEvent) => this._click(event));
6971
eventHandlers.set('mouseleave', (event: MouseEvent) => this._mouseLeave(event));
7072
this._rippleRenderer = new RippleRenderer(_elementRef, eventHandlers);
73+
this._ruler = _ruler;
7174
}
7275

7376
/** TODO: internal */
@@ -161,7 +164,10 @@ export class MdRipple implements OnInit, OnDestroy, OnChanges {
161164
// FIXME: This fails on IE11, which still sets pageX/Y and screenX/Y on keyboard clicks.
162165
const isKeyEvent =
163166
(event.screenX === 0 && event.screenY === 0 && event.pageX === 0 && event.pageY === 0);
164-
this.end(event.pageX, event.pageY, isKeyEvent);
167+
168+
this.end(event.pageX - this._ruler.getViewportScrollPosition().left,
169+
event.pageY - this._ruler.getViewportScrollPosition().top,
170+
isKeyEvent);
165171
}
166172
}
167173

@@ -185,7 +191,7 @@ export class MdRippleModule {
185191
static forRoot(): ModuleWithProviders {
186192
return {
187193
ngModule: MdRippleModule,
188-
providers: []
194+
providers: [ViewportRuler]
189195
};
190196
}
191197
}

src/lib/radio/radio.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {NgControl, FormsModule} from '@angular/forms';
33
import {Component, DebugElement} from '@angular/core';
44
import {By} from '@angular/platform-browser';
55
import {MdRadioGroup, MdRadioButton, MdRadioChange, MdRadioModule} from './radio';
6+
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
67

78

89
describe('MdRadio', () => {
@@ -15,6 +16,9 @@ describe('MdRadio', () => {
1516
RadioGroupWithNgModel,
1617
StandaloneRadioButtons,
1718
],
19+
providers: [
20+
{provide: ViewportRuler, useClass: FakeViewportRuler},
21+
]
1822
});
1923

2024
TestBed.compileComponents();
@@ -593,3 +597,15 @@ function dispatchEvent(eventName: string, element: HTMLElement): void {
593597
event.initEvent(eventName, true, true);
594598
element.dispatchEvent(event);
595599
}
600+
601+
class FakeViewportRuler {
602+
getViewportRect() {
603+
return {
604+
left: 0, top: 0, width: 1014, height: 686, bottom: 686, right: 1014
605+
};
606+
}
607+
608+
getViewportScrollPosition() {
609+
return {top: 0, left: 0};
610+
}
611+
}

src/lib/tabs/tab-group.spec.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {Component, ViewChild} from '@angular/core';
77
import {By} from '@angular/platform-browser';
88
import {Observable} from 'rxjs/Observable';
99
import {LayoutDirection, Dir} from '../core/rtl/dir';
10+
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
1011

1112

1213
describe('MdTabGroup', () => {
@@ -24,7 +25,8 @@ describe('MdTabGroup', () => {
2425
providers: [
2526
{provide: Dir, useFactory: () => {
2627
return {value: dir};
27-
}}
28+
}},
29+
{provide: ViewportRuler, useClass: FakeViewportRuler},
2830
]
2931
});
3032

@@ -493,3 +495,15 @@ class TabGroupWithSimpleApi {
493495
otherContent = 'Apples, grapes';
494496
@ViewChild('legumes') legumes: any;
495497
}
498+
499+
class FakeViewportRuler {
500+
getViewportRect() {
501+
return {
502+
left: 0, top: 0, width: 1014, height: 686, bottom: 686, right: 1014
503+
};
504+
}
505+
506+
getViewportScrollPosition() {
507+
return {top: 0, left: 0};
508+
}
509+
}

src/lib/tabs/tab-nav-bar/tab-nav-bar.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
22
import {MdTabsModule} from '../tabs';
33
import {Component} from '@angular/core';
44
import {By} from '@angular/platform-browser';
5+
import {ViewportRuler} from '../../core/overlay/position/viewport-ruler';
56

67

78
describe('MdTabNavBar', () => {
@@ -13,6 +14,9 @@ describe('MdTabNavBar', () => {
1314
SimpleTabNavBarTestApp,
1415
TabLinkWithNgIf,
1516
],
17+
providers: [
18+
{provide: ViewportRuler, useClass: FakeViewportRuler},
19+
]
1620
});
1721

1822
TestBed.compileComponents();
@@ -84,3 +88,15 @@ class SimpleTabNavBarTestApp {
8488
class TabLinkWithNgIf {
8589
isDestroyed = false;
8690
}
91+
92+
class FakeViewportRuler {
93+
getViewportRect() {
94+
return {
95+
left: 0, top: 0, width: 1014, height: 686, bottom: 686, right: 1014
96+
};
97+
}
98+
99+
getViewportScrollPosition() {
100+
return {top: 0, left: 0};
101+
}
102+
}

src/lib/tabs/tab-nav-bar/tab-nav-bar.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010

1111
import {MdInkBar} from '../ink-bar';
1212
import {MdRipple} from '../../core/ripple/ripple';
13+
import {ViewportRuler} from '../../core/overlay/position/viewport-ruler';
1314

1415
/**
1516
* Navigation component matching the styles of the tab group header.
@@ -60,8 +61,8 @@ export class MdTabLink {
6061
selector: '[md-tab-link]',
6162
})
6263
export class MdTabLinkRipple extends MdRipple implements OnDestroy {
63-
constructor(private _element: ElementRef) {
64-
super(_element);
64+
constructor(private _element: ElementRef, _ruler: ViewportRuler) {
65+
super(_element, _ruler);
6566
}
6667

6768
// In certain cases the parent destroy handler

0 commit comments

Comments
 (0)