Skip to content

Commit d41694c

Browse files
committed
Add test. Use viewport ruler
1 parent 3fd3117 commit d41694c

File tree

9 files changed

+189
-11
lines changed

9 files changed

+189
-11
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
@@ -14,6 +14,7 @@ import {
1414
import {Component, DebugElement} from '@angular/core';
1515
import {By} from '@angular/platform-browser';
1616
import {MdCheckbox, MdCheckboxChange, MdCheckboxModule} from './checkbox';
17+
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
1718

1819

1920
// TODO: Implement E2E tests for spacebar/click behavior for checking/unchecking
@@ -35,6 +36,9 @@ describe('MdCheckbox', () => {
3536
CheckboxWithChangeEvent,
3637
CheckboxWithFormControl,
3738
],
39+
providers: [
40+
{provide: ViewportRuler, useClass: FakeViewportRuler},
41+
]
3842
});
3943

4044
TestBed.compileComponents();
@@ -719,3 +723,15 @@ class CheckboxWithChangeEvent {
719723
class CheckboxWithFormControl {
720724
formControl = new FormControl();
721725
}
726+
727+
class FakeViewportRuler {
728+
getViewportRect() {
729+
return {
730+
left: 0, top: 0, width: 1014, height: 686, bottom: 686, right: 1014
731+
};
732+
}
733+
734+
getViewportScrollPosition() {
735+
return {top: 0, left: 0};
736+
}
737+
}

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
@@ -16,6 +16,7 @@ import {
1616
ForegroundRippleState,
1717
} from './ripple-renderer';
1818
import {DefaultStyleCompatibilityModeModule} from '../compatibility/default-mode';
19+
import {ViewportRuler} from '../overlay/position/viewport-ruler';
1920

2021

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

6364
private _rippleRenderer: RippleRenderer;
65+
_ruler: ViewportRuler;
6466

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

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

@@ -187,7 +193,7 @@ export class MdRippleModule {
187193
static forRoot(): ModuleWithProviders {
188194
return {
189195
ngModule: MdRippleModule,
190-
providers: []
196+
providers: [ViewportRuler]
191197
};
192198
}
193199
}

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, ReactiveFormsModule, FormControl} from '@angular
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', () => {
@@ -16,6 +17,9 @@ describe('MdRadio', () => {
1617
RadioGroupWithFormControl,
1718
StandaloneRadioButtons,
1819
],
20+
providers: [
21+
{provide: ViewportRuler, useClass: FakeViewportRuler},
22+
]
1923
});
2024

2125
TestBed.compileComponents();
@@ -659,3 +663,15 @@ function dispatchEvent(eventName: string, element: HTMLElement): void {
659663
event.initEvent(eventName, true, true);
660664
element.dispatchEvent(event);
661665
}
666+
667+
class FakeViewportRuler {
668+
getViewportRect() {
669+
return {
670+
left: 0, top: 0, width: 1014, height: 686, bottom: 686, right: 1014
671+
};
672+
}
673+
674+
getViewportScrollPosition() {
675+
return {top: 0, left: 0};
676+
}
677+
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {Component, ViewChild} from '@angular/core';
66
import {By} from '@angular/platform-browser';
77
import {Observable} from 'rxjs/Observable';
88
import {MdTab} from './tab';
9+
import {LayoutDirection, Dir} from '../core/rtl/dir';
10+
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
911

1012

1113
describe('MdTabGroup', () => {
@@ -19,6 +21,12 @@ describe('MdTabGroup', () => {
1921
AsyncTabsTestApp,
2022
DisabledTabsTestApp,
2123
TabGroupWithSimpleApi,
24+
],
25+
providers: [
26+
{provide: Dir, useFactory: () => {
27+
return {value: dir};
28+
}},
29+
{provide: ViewportRuler, useClass: FakeViewportRuler},
2230
]
2331
});
2432

@@ -556,3 +564,15 @@ class TabGroupWithSimpleApi {
556564
otherContent = 'Apples, grapes';
557565
@ViewChild('legumes') legumes: any;
558566
}
567+
568+
class FakeViewportRuler {
569+
getViewportRect() {
570+
return {
571+
left: 0, top: 0, width: 1014, height: 686, bottom: 686, right: 1014
572+
};
573+
}
574+
575+
getViewportScrollPosition() {
576+
return {top: 0, left: 0};
577+
}
578+
}

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 '../tab-group';
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], [mat-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)