Skip to content

Add tests for sticky-header #6074

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Aug 4, 2017
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/lib/sticky-header/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {OverlayModule, MdCommonModule, PlatformModule} from '../core';
import {CdkStickyRegion, CdkStickyHeader} from './sticky-header';

import {
CdkStickyRegion,
CdkStickyHeader,
STICKY_HEADER_SUPPORT_STRATEGY_PROVIDER
} from './sticky-header';


@NgModule({
imports: [OverlayModule, MdCommonModule, CommonModule, PlatformModule],
declarations: [CdkStickyRegion, CdkStickyHeader],
exports: [CdkStickyRegion, CdkStickyHeader, MdCommonModule],
providers: [STICKY_HEADER_SUPPORT_STRATEGY_PROVIDER]
})
export class StickyHeaderModule {}

Expand Down
180 changes: 180 additions & 0 deletions src/lib/sticky-header/sticky-header.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import {async, ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
import {Component, DebugElement, ViewChild} from '@angular/core';
import {
StickyHeaderModule,
CdkStickyRegion,
CdkStickyHeader,
STICKY_HEADER_SUPPORT_STRATEGY
} from './index';
import {OverlayModule, Scrollable} from '../core/overlay/index';
import {PlatformModule} from '../core/platform/index';
import {By} from '@angular/platform-browser';
import {dispatchFakeEvent} from '@angular/cdk/testing';



describe('sticky-header with positioning not supported', () => {
let fixture: ComponentFixture<StickyHeaderTest>;
let testComponent: StickyHeaderTest;
let stickyElement: DebugElement;
let stickyParentElement: DebugElement;
let scrollableElement: DebugElement;
let stickyHeader: CdkStickyHeader;

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ OverlayModule, PlatformModule, StickyHeaderModule ],
declarations: [StickyHeaderTest],
providers: [
{provide: STICKY_HEADER_SUPPORT_STRATEGY, useValue: false},
],
});
TestBed.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(StickyHeaderTest);
fixture.detectChanges();
testComponent = fixture.debugElement.componentInstance;
stickyElement = fixture.debugElement.query(By.directive(CdkStickyHeader));
stickyParentElement = fixture.debugElement.query(By.directive(CdkStickyRegion));
stickyHeader = stickyElement.injector.get<CdkStickyHeader>(CdkStickyHeader);
scrollableElement = fixture.debugElement.query(By.directive(Scrollable));
});

it('should be able to find stickyParent', () => {
expect(stickyHeader.stickyParent).not.toBe(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test case when there's no user-specified cdkStickyRegion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Added new test for ' no user-specified cdkStickyRegion'

});

it('should be able to find scrollableContainer', () => {
expect(stickyHeader.upperScrollableContainer).not.toBe(null);
});

it('should stick in the right place when scrolled to the top of the container', fakeAsync(() => {
let scrollableContainerTop = stickyHeader.upperScrollableContainer
.getBoundingClientRect().top;
expect(stickyHeader.element.getBoundingClientRect().top).not.toBe(scrollableContainerTop);
tick();

// Scroll the scrollableContainer up to stick
fixture.componentInstance.scrollDown();
// wait till animation has finished
tick(100);

expect(stickyHeader.element.getBoundingClientRect().top).toBe(scrollableContainerTop);
}));

it('should unstuck when scrolled off the top of the container', fakeAsync(() => {
let scrollableContainerTop = stickyHeader.upperScrollableContainer
.getBoundingClientRect().top;
expect(stickyHeader.element.getBoundingClientRect().top).not.toBe(scrollableContainerTop);
tick();

// Scroll the scrollableContainer up to stick
fixture.componentInstance.scrollDown();
// wait till animation has finished
tick(100);

expect(stickyHeader.element.getBoundingClientRect().top).toBe(scrollableContainerTop);

// Scroll the scrollableContainer down to unstuck
fixture.componentInstance.scrollBack();
tick(100);

expect(stickyHeader.element.getBoundingClientRect().top).not.toBe(scrollableContainerTop);

}));
});

describe('sticky-header with positioning supported', () => {
let fixture: ComponentFixture<StickyHeaderTest>;
let testComponent: StickyHeaderTest;
let stickyElement: DebugElement;
let stickyParentElement: DebugElement;
let stickyHeader: CdkStickyHeader;

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ OverlayModule, PlatformModule, StickyHeaderModule ],
declarations: [StickyHeaderTest],
providers: [
{provide: STICKY_HEADER_SUPPORT_STRATEGY, useValue: true},
],
});
TestBed.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(StickyHeaderTest);
fixture.detectChanges();
testComponent = fixture.debugElement.componentInstance;
stickyElement = fixture.debugElement.query(By.directive(CdkStickyHeader));
stickyParentElement = fixture.debugElement.query(By.directive(CdkStickyRegion));
stickyHeader = stickyElement.injector.get<CdkStickyHeader>(CdkStickyHeader);
});

it('should find sticky positioning is applied', () => {
let position = window.getComputedStyle(stickyHeader.element).position;
expect(position).not.toBe(null);
expect(/sticky/i.test(position!)).toBe(true);
});
});

@Component({
// Use styles to define the style of scrollable container and header,
// which help test to make sure whether the header is stuck at the right position.
styles:[`
.scrollable-style {
text-align: center;
-webkit-appearance: none;
-moz-appearance: none;
height: 300px;
overflow: auto;
}
.heading-style {
background: whitesmoke;
padding: 5px;
}
`],
template: `
<div cdk-scrollable class="scrollable-style">
<p *ngFor="let item of items"> {{item.name}} : {{item.message}}</p>
<div cdkStickyRegion>
<div cdkStickyHeader class="heading-style">
<h2>Heading 1</h2>
</div>
<p *ngFor="let item of items"> {{item.name}} : {{item.message}}</p>
<p *ngFor="let item of items"> {{item.name}} : {{item.message}}</p>
<p *ngFor="let item of items"> {{item.name}} : {{item.message}}</p>
</div>
</div>
`})
class StickyHeaderTest {
@ViewChild(Scrollable) scrollingContainer: Scrollable;

items: any[] = [
{'name': 'Forrest', 'message': 'Life was like a box of chocolates'},
{'name': 'Gump', 'message': 'you never know what you are gonna get'},
{'name': 'Lion King', 'message': 'Everything you see exists together'},
{'name': 'Jack', 'message': 'in a delicate balance'},
{'name': 'Garfield', 'message': 'Save Water'},
{'name': 'Shawshank', 'message': 'There is something inside'},
{'name': 'Jone', 'message': 'Enough movies?'},
];

scrollDown() {
const scrollingContainerEl = this.scrollingContainer.getElementRef().nativeElement;
scrollingContainerEl.scrollTop = 300;

// Emit a scroll event from the scrolling element in our component.
dispatchFakeEvent(scrollingContainerEl, 'scroll');
}

scrollBack() {
const scrollingContainerEl = this.scrollingContainer.getElementRef().nativeElement;
scrollingContainerEl.scrollTop = 0;

// Emit a scroll event from the scrolling element in our component.
dispatchFakeEvent(scrollingContainerEl, 'scroll');
}
}
28 changes: 18 additions & 10 deletions src/lib/sticky-header/sticky-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, Input,
OnDestroy, AfterViewInit, ElementRef, Optional} from '@angular/core';
import {Platform} from '../core/platform';
OnDestroy, AfterViewInit, ElementRef, Optional,
InjectionToken, Injectable, Inject, Provider} from '@angular/core';
import {Platform} from '../core/platform/index';
import {Scrollable} from '../core/overlay/scroll/scrollable';
import {extendObject} from '../core/util/object-extend';
import {Subscription} from 'rxjs/Subscription';
Expand All @@ -32,7 +33,6 @@ export class CdkStickyRegion {
constructor(public readonly _elementRef: ElementRef) { }
}


/** Class applied when the header is "stuck" */
const STICK_START_CLASS = 'cdk-sticky-header-start';

Expand All @@ -46,6 +46,16 @@ const STICK_END_CLASS = 'cdk-sticky-header-end';
*/
const DEBOUNCE_TIME: number = 5;

export const STICKY_HEADER_SUPPORT_STRATEGY = new InjectionToken('sticky-header-support-strategy');

/** @docs-private
* Create a factory for sticky-positioning check to make code more testable
*/
export const STICKY_HEADER_SUPPORT_STRATEGY_PROVIDER: Provider = {
provide: STICKY_HEADER_SUPPORT_STRATEGY,
useFactory: isPositionStickySupported
};

/**
* Directive that marks an element as a sticky-header. Inside of a scrolling container (marked with
* cdkScrollable), this header will "stick" to the top of the scrolling viewport while its sticky
Expand All @@ -61,8 +71,6 @@ export class CdkStickyHeader implements OnDestroy, AfterViewInit {

/** boolean value to mark whether the current header is stuck*/
isStuck: boolean = false;
/** Whether the browser support CSS sticky positioning. */
private _isPositionStickySupported: boolean = false;

/** The element with the 'cdkStickyHeader' tag. */
element: HTMLElement;
Expand Down Expand Up @@ -97,7 +105,8 @@ export class CdkStickyHeader implements OnDestroy, AfterViewInit {
constructor(element: ElementRef,
scrollable: Scrollable,
@Optional() public parentRegion: CdkStickyRegion,
platform: Platform) {
platform: Platform,
@Inject(STICKY_HEADER_SUPPORT_STRATEGY) public _isPositionStickySupported) {
if (platform.isBrowser) {
this.element = element.nativeElement;
this.upperScrollableContainer = scrollable.getElementRef().nativeElement;
Expand Down Expand Up @@ -137,7 +146,6 @@ export class CdkStickyHeader implements OnDestroy, AfterViewInit {
* sticky positioning. If not, use the original implementation.
*/
private _setStrategyAccordingToCompatibility(): void {
this._isPositionStickySupported = isPositionStickySupported();
if (this._isPositionStickySupported) {
this.element.style.top = '0';
this.element.style.cssText += 'position: -webkit-sticky; position: sticky; ';
Expand Down Expand Up @@ -258,9 +266,9 @@ export class CdkStickyHeader implements OnDestroy, AfterViewInit {


/**
* '_applyStickyPositionStyles()' function contains the main logic of sticky-header. It decides when
* a header should be stick and when should it be unstuck by comparing the offsetTop
* of scrollable container with the top and bottom of the sticky region.
* '_applyStickyPositionStyles()' function contains the main logic of sticky-header.
* It decides when a header should be stick and when should it be unstuck by comparing
* the offsetTop of scrollable container with the top and bottom of the sticky region.
*/
_applyStickyPositionStyles(): void {
let currentPosition: number = this.upperScrollableContainer.offsetTop;
Expand Down