Skip to content
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

Add tests for sticky-header #6074

Merged
merged 28 commits into from
Aug 4, 2017
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ff4c1a4
# This is a combination of 9 commits.
sllethe Jun 16, 2017
096b553
# This is a combination of 5 commits.
sllethe Jun 27, 2017
2848f2c
# This is a combination of 16 commits.
sllethe Jul 19, 2017
fbf6350
add lib files for sticky-header
sllethe Jul 24, 2017
b0172d1
added tests for sticky-header
sllethe Jul 26, 2017
d354ef0
deleted
sllethe Jul 26, 2017
6e58b23
removed 'deps' in providers
sllethe Jul 27, 2017
32b1b93
put provider in index
sllethe Jul 27, 2017
a555a23
optimize provider
sllethe Jul 27, 2017
469036d
Removed unused 'fixture.detectChanges()' in test
sllethe Jul 27, 2017
4100311
fix
sllethe Jul 27, 2017
44201f0
You can remove when sticky positioning is not supported since it's in…
sllethe Jul 27, 2017
4aa498a
stickyHeaderDir.stickyParent
sllethe Jul 27, 2017
4adcd52
remove commented code
sllethe Jul 27, 2017
783274c
rename stickyHeaderDir to stickyHeader
sllethe Jul 31, 2017
9c28f34
Changed to use 'ngFor' to expand content instead of typing tons of '<…
sllethe Jul 31, 2017
b037d83
Add a comment that explains what these inline styles are for? Any rea…
sllethe Jul 31, 2017
13d7aca
Added explainations on styles
sllethe Jul 31, 2017
824b305
Added /** @docs-private */
sllethe Jul 31, 2017
72706d8
Added comments to explaining why tick(100) is needed. Changed 'tick(…
sllethe Jul 31, 2017
886b622
nit
sllethe Jul 31, 2017
089a0ca
explained why need 'tick(100)'
sllethe Jul 31, 2017
3391650
expect(/sticky/i.test(position!)).toBe(true);
sllethe Jul 31, 2017
54dccf6
change CSS indent to +2
sllethe Jul 31, 2017
cc53d28
nit
sllethe Jul 31, 2017
0abc5ee
Added test for sticky-header without StickyRegion
sllethe Aug 1, 2017
b943a62
change 'toBe(null)' to 'toBeNull()'
sllethe Aug 2, 2017
94e6474
Cool ! tick(debounce time)
sllethe Aug 4, 2017
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
253 changes: 253 additions & 0 deletions src/lib/sticky-header/sticky-header.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
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);
});
});

describe('test sticky-header without StickyRegion', () => {
let fixture: ComponentFixture<StickyHeaderTestNoStickyRegion>;
let testComponent: StickyHeaderTestNoStickyRegion;
let stickyElement: DebugElement;
let stickyHeader: CdkStickyHeader;

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

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

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

Choose a reason for hiding this comment

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

You can use toBeNull()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 Changed 'toBe(null)' to 'toBeNull()';

expect(p!.id).toBe('default-region');
});
});

@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');
}
}

@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 id="default-region">
<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 StickyHeaderTestNoStickyRegion {
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?'},
];
}
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