Skip to content

Commit

Permalink
Merge pull request #92 from kreuzerk/develop
Browse files Browse the repository at this point in the history
Preapre Release 2.5.x
  • Loading branch information
nivekcode authored Oct 31, 2017
2 parents bea6bfb + e372a2f commit d7a0abc
Show file tree
Hide file tree
Showing 8 changed files with 555 additions and 24 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ The advanced growl messages component has the following in- and outputs.
| life: number (default = 0) | A number that represents the lifetime of each growl message. If set to 3000 each message will be disappear after 3 seconds. If no life param is passed to the components the growl messages are sticky and do not disappear until you call clearMessages or click on the cancel x on a message |
|freezeMessagesOnHover: boolean (default: false)| This flag is only useful if you also pass a life time. When you pass this property to the component messages are freezed when you hover over them. Let's say you have for example 3 messages all with a lifetime of 3 seconds. When you hover after 2 seconds over the second message all messages on the screen are freezed and do not disappear. After you leave the messages each message will disappear after the specified lifetime. With the pauseOnlyHoveredMessage you can control if you want all messages or only the hoverd message to disappear|
|pauseOnlyHoveredMessage (default: false)| This flag indicates if only the hovered message should be paused. If set to true only the hoverd message will be paused. If set to false all messages will be paused on hover. Notice that this flag is only useful if you set a lifetime that is bigger than 0 and you set the freezeMessagesOnHover flag to true|
|messageSpots (default: 0 (unlimited))| This property gives you the possibility to limit the number of messages displayed on screen. This feature is very useful in combination with the life property. With this combination all messages that can currently not be displayed due to missing message spots will be cached. Those cached messages will appear as soon as a spot is available. Notice that a dynamic change of this property during runtime removes all currently displayed messages from screen|

#### Output

Expand Down
181 changes: 175 additions & 6 deletions lib/messages/adv-growl.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'rxjs/add/observable/never';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';
import {AdvPrimeMessage} from './adv-growl.model';
import {EventEmitter} from '@angular/core';
import {EventEmitter, SimpleChange} from '@angular/core';

describe('Message Component', () => {

Expand Down Expand Up @@ -74,8 +74,14 @@ describe('Message Component', () => {
observer.next(message);
});
});
spyOn(messagesService, 'getMessageStream').and.returnValue(messages$);
spyOn(messagesService, 'getCancelStream').and.returnValue(Observable.never());
spyOn(messagesService, 'getMessageStream')
spyOn(messagesService, 'getCancelStream').and.returnValue(Observable.never())
component.messageCache = {
getMessages: () => {
}
} as any
spyOn(component.messageCache, 'getMessages').and.returnValue(messages$)

// when
component.subscribeForMessages();
// then
Expand All @@ -96,13 +102,22 @@ describe('Message Component', () => {
observer.next(message);
});
});
spyOn(messagesService, 'getMessageStream').and.returnValue(messages$);
spyOn(messagesService, 'getMessageStream')
spyOn(component, 'getLifeTimeStream').and.returnValue(Observable.of(1));
spyOn(component, 'removeMessage');
component.messageCache = {
getMessages: () => {
},
deallocateMessageSpot: () => {
}
} as any
spyOn(component.messageCache, 'getMessages').and.returnValue(messages$)
spyOn(component.messageCache, 'deallocateMessageSpot')
// when
component.subscribeForMessages();
// then
expect(component.removeMessage).toHaveBeenCalledTimes(3);
expect(component.messageCache.deallocateMessageSpot).toHaveBeenCalledTimes(3)
})
);

Expand All @@ -121,7 +136,7 @@ describe('Message Component', () => {
observer.next(message);
});
});
spyOn(messagesService, 'getMessageStream').and.returnValue(messages$);
spyOn(messagesService, 'getMessageStream')
spyOn(messagesService, 'getCancelStream').and.callFake(() => {
if (numberOfCalls === 0) {
numberOfCalls++;
Expand All @@ -132,10 +147,23 @@ describe('Message Component', () => {
spyOn(component, 'getLifeTimeStream').and.returnValue(Observable.of(1));
spyOn(Array.prototype, 'shift');
spyOn(component, 'subscribeForMessages').and.callThrough();

component.messageCache = {
getMessages: () => messages$,
clearCache: () => {
},
deallocateMessageSpot: () => {
}
} as any

spyOn(component.messageCache, 'clearCache')
spyOn(component.messageCache, 'deallocateMessageSpot')

// when
component.subscribeForMessages();
// then
expect(component.subscribeForMessages).toHaveBeenCalledTimes(2);
expect(component.messageCache.clearCache).toHaveBeenCalled();
}));

it('should remove the message with the matching messageId', () => {
Expand Down Expand Up @@ -170,9 +198,30 @@ describe('Message Component', () => {
const errorMessage = 'Awful error';
const messages$ = Observable.throw(new Error(errorMessage));
spyOn(messagesService, 'getMessageStream').and.returnValue(messages$);
component.messageCache = {
getMessages: () => {
}
} as any
spyOn(component.messageCache, 'getMessages').and.returnValue(messages$)
// when then
expect(() => component.subscribeForMessages()).toThrowError(errorMessage);
}));
})
);

it('should clear the message cache and resubscribe for messages on spot changes', () => {
// given
component.messageCache = {
getMessages: () => Observable.of('Some message'),
clearCache: () => {
}
} as any
spyOn(component.messageCache, 'clearCache')
// when
component.subscribeForMessages()
component.messageSpotChange$.next()
// then
expect(component.messageCache.clearCache).toHaveBeenCalled()
})
})

describe('Get Life time streams', () => {
Expand Down Expand Up @@ -337,4 +386,124 @@ describe('Message Component', () => {
})
})
})

describe('Create message observer', () => {

it('should create a messageObserver that deallocates messages spots and remove messages on next', () => {
// given
component.ngOnInit()
const messageId = 12345
spyOn(component.messageCache, 'deallocateMessageSpot')
spyOn(component, 'removeMessage')
// when
const messageObserver = component.createMessageObserver()
messageObserver.next(messageId)
// then
expect(component.messageCache.deallocateMessageSpot).toHaveBeenCalled()
expect(component.removeMessage).toHaveBeenCalledWith(messageId)
})

it('should create a messageObserver that calls clear cache and resubscribes on complete', () => {
// given
component.ngOnInit()
spyOn(component.messageCache, 'clearCache')
spyOn(component, 'subscribeForMessages')
// when
const messageObserver = component.createMessageObserver()
messageObserver.complete()
// then
expect(component.messageCache.clearCache).toHaveBeenCalled()
expect(component.subscribeForMessages).toHaveBeenCalled()
})
})

describe('OnChange', () => {

describe('Have message spots changed', () => {

it('should return false if the currentValue is null', () => {
// given
const messageSpotChange = {
currentValue: null,
previousValue: 1,
firstChange: false
} as SimpleChange
// when
const hasChanged = component.haveMessageSpotsChanged(messageSpotChange)
// then
expect(hasChanged).toBeFalsy()
})

it('should return false if the currentValue is undefined', () => {
// given
const messageSpotChange = {
currentValue: undefined,
previousValue: 1,
firstChange: false
} as SimpleChange
// when
const hasChanged = component.haveMessageSpotsChanged(messageSpotChange)
// then
expect(hasChanged).toBeFalsy()
})

it('should return true if the currentValue is 0', () => {
// given
const messageSpotChange = {
currentValue: 0,
previousValue: 1,
firstChange: false
} as SimpleChange
// when
const hasChanged = component.haveMessageSpotsChanged(messageSpotChange)
// then
expect(hasChanged).toBeTruthy()
})

it('should return false if it is the first change', () => {
// given
const messageSpotChange = {
currentValue: 0,
previousValue: 1,
firstChange: true
} as SimpleChange
// when
const hasChanged = component.haveMessageSpotsChanged(messageSpotChange)
// then
expect(hasChanged).toBeFalsy()
})

it('should return false if it is not the first change but the value has not changed', () => {
// given
const messageSpotChange = {
currentValue: 1,
previousValue: 1,
firstChange: true
} as SimpleChange
// when
const hasChanged = component.haveMessageSpotsChanged(messageSpotChange)
// then
expect(hasChanged).toBeFalsy()
})
})

it('should stream a messageSpot change when the messageSpot has changed', done => {
// given
const messageSpotChange = {
currentValue: 1,
previousValue: 0,
firstChange: false
} as SimpleChange

const changes = {
messageSpots: messageSpotChange
}
// then
component.messageSpotChange$.subscribe(() => {
done()
})
// when
component.ngOnChanges(changes)
})
})
})
74 changes: 63 additions & 11 deletions lib/messages/adv-growl.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
/**
* Created by kevinkreuzer on 08.07.17.
*/
import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core';
import {
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChange,
SimpleChanges,
ViewChild
} from '@angular/core';
import 'rxjs/add/observable/timer';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/mapTo';
Expand All @@ -14,21 +25,26 @@ import {AdvPrimeMessage} from './adv-growl.model';
import {AdvGrowlService} from './adv-growl.service';
import {Subject} from 'rxjs/Subject';
import {AdvGrowlHoverHelper} from './adv-growl.hoverHelper';
import {AdvGrowlMessageCache} from './adv-growl.messageCache';
import {Observer} from 'rxjs/Observer';

const DEFAULT_LIFETIME = 0
const FREEZE_MESSAGES_DEFAULT = false
const PAUSE_ONLY_HOVERED_DEFAULT = false
const DEFAULT_MESSAGE_SPOTS = 0

@Component({
selector: 'adv-growl',
templateUrl: './adv-growl.component.html'
})
export class AdvGrowlComponent implements OnInit {
export class AdvGrowlComponent implements OnInit, OnChanges {


@Input() style: any
@Input() styleClass: any
@Input('life') lifeTime = DEFAULT_LIFETIME
@Input() freezeMessagesOnHover = FREEZE_MESSAGES_DEFAULT
@Input() messageSpots = DEFAULT_MESSAGE_SPOTS
@Input() pauseOnlyHoveredMessage = PAUSE_ONLY_HOVERED_DEFAULT;
@Output() onClose = new EventEmitter<AdvPrimeMessage>()
@Output() onClick = new EventEmitter<AdvPrimeMessage>()
Expand All @@ -38,33 +54,69 @@ export class AdvGrowlComponent implements OnInit {

public messages: Array<AdvPrimeMessage> = []
messageEnter$ = new Subject<string>()
messageSpotChange$ = new Subject()
hoverHelper: AdvGrowlHoverHelper;
messageCache: AdvGrowlMessageCache
private messageObserver: Observer<any>

constructor(private messageService: AdvGrowlService) {
this.messageObserver = this.createMessageObserver()
}

ngOnInit(): void {
const mouseLeave$ = Observable.fromEvent(this.growlMessage.nativeElement, 'mouseleave')
this.hoverHelper = new AdvGrowlHoverHelper(this.messageEnter$, mouseLeave$)
this.messageCache = new AdvGrowlMessageCache()
this.subscribeForMessages()
}

ngOnChanges(changes: SimpleChanges): void {
const messageSpotChange = changes.messageSpots
if (messageSpotChange != null && this.haveMessageSpotsChanged(messageSpotChange)) {
this.messageSpotChange$.next()
}
}

haveMessageSpotsChanged(messageSpotChange: SimpleChange) {
const currentValue = messageSpotChange.currentValue
const previousValue = messageSpotChange.previousValue
const firstChange = messageSpotChange.firstChange
const hasValueChanged = currentValue !== previousValue
if (currentValue != null && !firstChange && hasValueChanged) {
return true
}
return false
}

createMessageObserver(): Observer<any> {
return {
next: (messageId: string) => {
this.messageCache.deallocateMessageSpot()
this.removeMessage(messageId)
},
error: (error) => {
throw error;
},
complete: () => {
this.messageCache.clearCache()
this.subscribeForMessages()
}
}
}

public subscribeForMessages() {
this.messages = [];
this.messageService.getMessageStream()
this.messageCache.getMessages(this.messageService.getMessageStream(), this.messageSpots)
.do(message => {
this.messages.push(message);
this.onMessagesChanges.emit(this.messages);
})
.mergeMap(message => this.getLifeTimeStream(message.id))
.takeUntil(this.messageService.getCancelStream())
.subscribe(
messageId => this.removeMessage(messageId),
err => {
throw err;
},
() => this.subscribeForMessages()
);
.takeUntil(Observable.merge(
this.messageService.getCancelStream(),
this.messageSpotChange$)
)
.subscribe(this.messageObserver);
}

removeMessage(messageId: string) {
Expand Down
Loading

0 comments on commit d7a0abc

Please sign in to comment.