Skip to content
This repository has been archived by the owner on May 16, 2023. It is now read-only.

Commit

Permalink
Merge pull request #49 from rpaschoal/1.0.9
Browse files Browse the repository at this point in the history
Merge from 1.0.9
  • Loading branch information
rpaschoal authored Apr 15, 2018
2 parents 0929e17 + 4d767db commit e8bb34d
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 6 deletions.
2 changes: 1 addition & 1 deletion demo/aspnetcore_signalr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"bootstrap": "^3.3.7",
"core-js": "^2.4.1",
"jquery": "3.2.1",
"ng-chat": "^1.0.8",
"ng-chat": "^1.0.9",
"ng2-loading-bar": "0.0.6",
"reflect-metadata": "^0.1.10",
"rxjs": "5.4.2",
Expand Down
2 changes: 1 addition & 1 deletion demo/offline_bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"core-js": "^2.5.3",
"rxjs": "^5.5.6",
"zone.js": "^0.8.20",
"ng-chat": "^1.0.8"
"ng-chat": "^1.0.9"
},
"devDependencies": {
"@angular/cli": "^1.6.4",
Expand Down
2 changes: 1 addition & 1 deletion src/ng-chat/ng-chat.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</a>
<input id="ng-chat-search_friend" type="search" [placeholder]="localization.searchPlaceholder" [(ngModel)]="searchInput"/>
<ul id="ng-chat-users" *ngIf="!isCollapsed">
<li *ngFor="let user of filteredUsers" (click)="openChatWindow(user)">
<li *ngFor="let user of filteredUsers" (click)="openChatWindow(user, true)">
<div *ngIf="!user.avatar" class="icon-wrapper">
<i class="user-icon"></i>
</div>
Expand Down
54 changes: 52 additions & 2 deletions src/ng-chat/ng-chat.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ export class NgChat implements OnInit {

private users: User[];

private localStorageKey: string = "ng-chat-users";
private get localStorageKey(): string
{
return `ng-chat-users-${this.userId}`; // Appending the user id so the state is unique per user in a computer.
};

get filteredUsers(): User[]
{
Expand Down Expand Up @@ -230,7 +233,7 @@ export class NgChat implements OnInit {

// Opens a new chat whindow. Takes care of available viewport
// Returns => [Window: Window object reference, boolean: Indicates if this window is a new chat window]
private openChatWindow(user: User): [Window, boolean]
private openChatWindow(user: User, focusOnNewWindow: boolean = false): [Window, boolean]
{
// Is this window opened?
let openedWindow = this.windows.find(x => x.chattingTo.id == user.id);
Expand Down Expand Up @@ -265,6 +268,9 @@ export class NgChat implements OnInit {
}

this.updateWindowsState(this.windows);

if (focusOnNewWindow)
this.focusOnWindow(newChatWindow);

return [newChatWindow, true];
}
Expand All @@ -275,6 +281,23 @@ export class NgChat implements OnInit {
}
}

// Focus on the input element of the supplied window
private focusOnWindow(window: Window, callback: Function = () => {}) : void
{
let windowIndex = this.windows.indexOf(window);

if (windowIndex >= 0)
{
setTimeout(() => {
let messageInputToFocus = this.chatWindowInputs.toArray()[windowIndex];

messageInputToFocus.nativeElement.focus();

callback();
});
}
}

// Scrolls a chat window message flow to the bottom
private scrollChatWindowToBottom(window: Window): void
{
Expand Down Expand Up @@ -355,6 +378,21 @@ export class NgChat implements OnInit {
}
}

// Gets closest open window if any. Most recent opened has priority (Right)
private getClosestWindow(window: Window): Window | undefined
{
let index = this.windows.indexOf(window);

if (index > 0)
{
return this.windows[index - 1];
}
else if (index == 0 && this.windows.length > 1)
{
return this.windows[index + 1];
}
}

// Returns the total unread messages from a chat window. TODO: Could use some Angular pipes in the future
unreadMessagesTotal(window: Window): string
{
Expand Down Expand Up @@ -382,6 +420,7 @@ export class NgChat implements OnInit {
/* Monitors pressed keys on a chat window
- Dispatches a message when the ENTER key is pressed
- Tabs between windows on TAB or SHIFT + TAB
- Closes the current focused window on ESC
*/
onChatInputTyped(event: any, window: Window): void
{
Expand Down Expand Up @@ -420,6 +459,17 @@ export class NgChat implements OnInit {
messageInputToFocus.nativeElement.focus();

break;
case 27:
let closestWindow = this.getClosestWindow(window);

if (closestWindow)
{
this.focusOnWindow(closestWindow, () => { this.onCloseChatWindow(window); });
}
else
{
this.onCloseChatWindow(window);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ng-chat",
"version": "1.0.8",
"version": "1.0.9",
"peerDependencies": {
"@angular/common": "*",
"@angular/core": "*",
Expand Down
196 changes: 196 additions & 0 deletions src/spec/ng-chat.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ describe('NgChat', () => {
expect(this.subject.persistWindowsState).toBeTruthy();
});

it('Must use current user id as part of the localStorageKey identifier', () => {
expect(this.subject.localStorageKey).toBe(`ng-chat-users-${this.subject.userId}`);
});

it('Exercise users filter', () => {
this.subject.users = [{
id: 1,
Expand Down Expand Up @@ -200,6 +204,42 @@ describe('NgChat', () => {
expect(result[0].chattingTo.displayName).toEqual(user.displayName);
});

it('Must focus on the new window on a openChatWindow request when argument is supplied', () =>{
this.subject.historyEnabled = false;

let user: User = {
id: 999,
displayName: 'Test user',
status: 1,
avatar: ''
};

spyOn(this.subject, 'focusOnWindow');

let result = this.subject.openChatWindow(user, true);

expect(result).not.toBeUndefined();
expect(this.subject.focusOnWindow).toHaveBeenCalledTimes(1);
});

it('Must not focus on the new window on a openChatWindow request when argument is not supplied', () =>{
this.subject.historyEnabled = false;

let user: User = {
id: 999,
displayName: 'Test user',
status: 1,
avatar: ''
};

spyOn(this.subject, 'focusOnWindow');

let result = this.subject.openChatWindow(user);

expect(result).not.toBeUndefined();
expect(this.subject.focusOnWindow).not.toHaveBeenCalled();
});

it('Must load history from ChatAdapter when opening a window that is not yet opened', () =>{
let user: User = {
id: 999,
Expand Down Expand Up @@ -423,6 +463,65 @@ describe('NgChat', () => {
expect(sentMessage).toBeNull();
});

it('Must close the current window when the ESC key is pressed', () => {
let currentWindow = new Window();
let closedWindow: Window = null;
let event = {
keyCode: 27
};

spyOn(this.subject, 'onCloseChatWindow').and.callFake((window: Window) => {
closedWindow = window;
});

this.subject.onChatInputTyped(event, currentWindow);

expect(this.subject.onCloseChatWindow).toHaveBeenCalledTimes(1);
expect(closedWindow).not.toBeNull();
expect(closedWindow).toBe(currentWindow);
});

it('Must focus on closest window when the ESC key is pressed', () => {
let currentWindow = new Window();
let closestWindow = new Window();
let closedWindow: Window = null;
let event = {
keyCode: 27
};

spyOn(this.subject, 'onCloseChatWindow');

spyOn(this.subject, 'getClosestWindow').and.returnValue(closestWindow);

spyOn(this.subject, 'focusOnWindow').and.callFake((window: Window, callback: Function) => {
callback(); // This should invoke onCloseChatWindow
});;

this.subject.onChatInputTyped(event, currentWindow);

expect(this.subject.onCloseChatWindow).toHaveBeenCalledTimes(1);
expect(this.subject.getClosestWindow).toHaveBeenCalledTimes(1);
expect(this.subject.focusOnWindow).toHaveBeenCalledTimes(1);
});

it('Must not focus on closest window when the ESC key is pressed and there is no other window opened', () => {
let currentWindow = new Window();
let closedWindow: Window = null;
let event = {
keyCode: 27
};

spyOn(this.subject, 'onCloseChatWindow');
spyOn(this.subject, 'getClosestWindow').and.returnValue(undefined);
spyOn(this.subject, 'focusOnWindow');

this.subject.onChatInputTyped(event, currentWindow);

expect(this.subject.onCloseChatWindow).toHaveBeenCalledTimes(1);
expect(this.subject.getClosestWindow).toHaveBeenCalledTimes(1);
expect(this.subject.focusOnWindow).not.toHaveBeenCalled();
});

it('Must move to the next chat window when the TAB key is pressed', () => {
this.subject.windows = [
new Window(),
Expand Down Expand Up @@ -560,3 +659,100 @@ describe('NgChat', () => {
expect(this.subject.localization.statusDescription).not.toBe(this.subject.statusDescription);
});
});

it('FocusOnWindow exercise', () => {
this.subject.windows = [
new Window(),
new Window(),
new Window()
];

this.subject.chatWindowInputs = {
toArray: () => {}
};

let fakeChatInputs = [
{
nativeElement:
{
focus: () => {}
}
},
{
nativeElement:
{
focus: () => {}
}
},
{
nativeElement:
{
focus: () => {}
}
}
];

spyOn(fakeChatInputs[1].nativeElement, 'focus');
spyOn(this.subject.chatWindowInputs, 'toArray').and.returnValue(fakeChatInputs);

spyOn(window, 'setTimeout').and.callFake((fn) => {
fn();
});

this.subject.focusOnWindow(this.subject.windows[1]);

expect(fakeChatInputs[1].nativeElement.focus).toHaveBeenCalledTimes(1);
});

it('Must not focus on native element if a window is not found to focus when focusOnWindow is invoked', () => {
this.subject.windows = [];

this.subject.chatWindowInputs = {
toArray: () => {}
};

this.subject.focusOnWindow(new Window());
});

it('GetClosestWindow must return next relative right chat window', () => {
this.subject.windows = [
new Window(),
new Window(),
new Window()
];

let result = this.subject.getClosestWindow(this.subject.windows[2]);

expect(result).toBe(this.subject.windows[1]);
});

it('GetClosestWindow must return previous chat window on 0 based index', () => {
this.subject.windows = [
new Window(),
new Window(),
new Window()
];

let result = this.subject.getClosestWindow(this.subject.windows[0]);

expect(result).toBe(this.subject.windows[1]);
});

it('GetClosestWindow must return undefined when there is only one chat window opened on the chat', () => {
this.subject.windows = [
new Window()
];

let result = this.subject.getClosestWindow(this.subject.windows[0]);

expect(result).toBe(undefined);
});

it('GetClosestWindow must return undefined when there is no open chat window on the chat', () => {
this.subject.windows = [];

let result = this.subject.getClosestWindow(new Window());

expect(result).toBe(undefined);
});

0 comments on commit e8bb34d

Please sign in to comment.