[ADF-3374] Comments - add/view multiline comments (#3607)

* multiline comment

* add comment e2e

* fix locator syntax

* lint

* clear textarea on ESCAPE event

* multiline comment

* add comment e2e

* fix locator syntax

* lint

* clear textarea on ESCAPE event
This commit is contained in:
Cilibiu Bogdan 2018-07-27 22:24:29 +03:00 committed by Eugenio Romano
parent 61dff96e8b
commit f36f9fa862
7 changed files with 121 additions and 24 deletions

View File

@ -51,6 +51,9 @@
"TITLE": "Metadata" "TITLE": "Metadata"
} }
}, },
"COMMENTS": {
"ADD_COMMENT": "ADD"
},
"APP_LAYOUT": { "APP_LAYOUT": {
"APP_NAME": "ADF Demo Application", "APP_NAME": "ADF Demo Application",
"HOME": "Home", "HOME": "Home",

View File

@ -30,7 +30,8 @@ var TaskDetailsPage = function () {
var descriptionField = element(by.css("span[data-automation-id*='description'] span")); var descriptionField = element(by.css("span[data-automation-id*='description'] span"));
var dueDateField = element(by.css("span[data-automation-id*='dueDate'] span")); var dueDateField = element(by.css("span[data-automation-id*='dueDate'] span"));
var activitiesTitle = element(by.css("div[class*='adf-info-drawer-layout-header-title'] div")); var activitiesTitle = element(by.css("div[class*='adf-info-drawer-layout-header-title'] div"));
var commentField = element(by.css("input[id='comment-input']")); var commentField = element(by.id("comment-input"));
var addCommentButton = element(by.css("[data-automation-id='comments-input-add']"));
var activityTab = element(by.cssContainingText("div[class*='mat-tab-label ']", "Activity")); var activityTab = element(by.cssContainingText("div[class*='mat-tab-label ']", "Activity"));
var detailsTab = element(by.cssContainingText("div[class*='mat-tab-label ']", "Details")); var detailsTab = element(by.cssContainingText("div[class*='mat-tab-label ']", "Details"));
var involvePeopleButton = element(by.css("div[class*='add-people']")); var involvePeopleButton = element(by.css("div[class*='add-people']"));
@ -116,6 +117,12 @@ var TaskDetailsPage = function () {
this.addComment = function (comment) { this.addComment = function (comment) {
Util.waitUntilElementIsVisible(commentField); Util.waitUntilElementIsVisible(commentField);
commentField.sendKeys(comment); commentField.sendKeys(comment);
addCommentButton.click();
return this;
};
this.clearComment = function (comment) {
Util.waitUntilElementIsVisible(commentField);
commentField.sendKeys(protractor.Key.ENTER); commentField.sendKeys(protractor.Key.ENTER);
return this; return this;
}; };

View File

@ -21,9 +21,7 @@
<div matLine id="comment-user" class="adf-comment-user-name"> <div matLine id="comment-user" class="adf-comment-user-name">
{{comment.createdBy?.firstName}} {{comment.createdBy?.lastName}} {{comment.createdBy?.firstName}} {{comment.createdBy?.lastName}}
</div> </div>
<div matLine id="comment-message" class="adf-comment-message"> <div matLine id="comment-message" class="adf-comment-message" [innerHTML]="comment.message"></div>
{{comment.message}}
</div>
<div matLine id="comment-time" class="adf-comment-message-time"> <div matLine id="comment-time" class="adf-comment-message-time">
{{ comment.created | adfTimeAgo: currentLocale }} {{ comment.created | adfTimeAgo: currentLocale }}
</div> </div>

View File

@ -4,12 +4,23 @@
</div> </div>
<div class="adf-comments-input-container" *ngIf="!isReadOnly()"> <div class="adf-comments-input-container" *ngIf="!isReadOnly()">
<mat-form-field class="adf-full-width"> <mat-form-field class="adf-full-width">
<input matInput id="comment-input" placeholder="{{'COMMENTS.ADD' | translate}}" [(ngModel)]="message" (keyup.enter)="add()" (keyup.esc)="clear()"> <textarea (keyup.escape)="clear()" matInput id="comment-input" placeholder="{{'COMMENTS.ADD' | translate}}" [(ngModel)]="message"></textarea>
</mat-form-field> </mat-form-field>
<div class="adf-comments-input-actions">
<button mat-button
class="adf-comments-input-add"
data-automation-id="comments-input-add"
color="primary"
(click)="add()"
[disabled]="!message">
{{ 'COMMENTS.ADD_COMMENT' | translate }}
</button>
</div>
</div> </div>
<div *ngIf="comments.length > 0"> <div *ngIf="comments.length > 0">
<adf-comment-list [comments]="comments"> <adf-comment-list [comments]="comments">
</adf-comment-list> </adf-comment-list>
</div> </div>
</div> </div>

View File

@ -20,6 +20,16 @@
width: calc(100% - 30px); width: calc(100% - 30px);
padding-top: 8px; padding-top: 8px;
border-bottom: $header-border; border-bottom: $header-border;
textarea {
resize: vertical;
}
}
.adf-comments-input-actions {
display: flex;
justify-content: flex-end;
margin-bottom: 10px;
} }
.adf-full-width { .adf-full-width {

View File

@ -219,11 +219,43 @@ describe('CommentsComponent', () => {
fixture.whenStable(); fixture.whenStable();
})); }));
it('should call service to add a comment when enter key is pressed', async(() => { it('should sanitize comment when user input contains html elements', async(() => {
let event = new KeyboardEvent('keyup', {'key': 'Enter'}); let element = fixture.nativeElement.querySelector('.adf-comments-input-add');
let element = fixture.nativeElement.querySelector('#comment-input'); component.message = '<div class="text-class"><button onclick=""><h1>action</h1></button></div>';
element.dispatchEvent(new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(addProcessCommentSpy).toHaveBeenCalledWith('123', 'action');
});
}));
it('should normalize comment when user input contains spaces sequence', async(() => {
let element = fixture.nativeElement.querySelector('.adf-comments-input-add');
component.message = 'test comment';
element.dispatchEvent(new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(addProcessCommentSpy).toHaveBeenCalledWith('123', 'test comment');
});
}));
it('should add break lines to comment when user input contains new line characters', async(() => {
let element = fixture.nativeElement.querySelector('.adf-comments-input-add');
component.message = 'these\nare\nparagraphs\n';
element.dispatchEvent(new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(addProcessCommentSpy).toHaveBeenCalledWith('123', 'these<br/>are<br/>paragraphs');
});
}));
it('should call service to add a comment when add button is pressed', async(() => {
let element = fixture.nativeElement.querySelector('.adf-comments-input-add');
component.message = 'Test Comment'; component.message = 'Test Comment';
element.dispatchEvent(event); element.dispatchEvent(new Event('click'));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
@ -235,10 +267,9 @@ describe('CommentsComponent', () => {
})); }));
it('should not call service to add a comment when comment is empty', async(() => { it('should not call service to add a comment when comment is empty', async(() => {
let event = new KeyboardEvent('keyup', {'key': 'Enter'}); let element = fixture.nativeElement.querySelector('.adf-comments-input-add');
let element = fixture.nativeElement.querySelector('#comment-input');
component.message = ''; component.message = '';
element.dispatchEvent(event); element.dispatchEvent(new Event('click'));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
@ -249,7 +280,6 @@ describe('CommentsComponent', () => {
it('should clear comment when escape key is pressed', async(() => { it('should clear comment when escape key is pressed', async(() => {
let event = new KeyboardEvent('keyup', {'key': 'Escape'}); let event = new KeyboardEvent('keyup', {'key': 'Escape'});
let element = fixture.nativeElement.querySelector('#comment-input'); let element = fixture.nativeElement.querySelector('#comment-input');
component.message = 'Test comment';
element.dispatchEvent(event); element.dispatchEvent(event);
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
@ -277,11 +307,10 @@ describe('CommentsComponent', () => {
fixture.whenStable(); fixture.whenStable();
})); }));
it('should call service to add a comment when enter key is pressed', async(() => { it('should call service to add a comment when add button is pressed', async(() => {
let event = new KeyboardEvent('keyup', {'key': 'Enter'}); let element = fixture.nativeElement.querySelector('.adf-comments-input-add');
let element = fixture.nativeElement.querySelector('#comment-input');
component.message = 'Test Comment'; component.message = 'Test Comment';
element.dispatchEvent(event); element.dispatchEvent(new Event('click'));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
@ -292,11 +321,43 @@ describe('CommentsComponent', () => {
}); });
})); }));
it('should sanitize comment when user input contains html elements', async(() => {
let element = fixture.nativeElement.querySelector('.adf-comments-input-add');
component.message = '<div class="text-class"><button onclick=""><h1>action</h1></button></div>';
element.dispatchEvent(new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(addContentCommentSpy).toHaveBeenCalledWith('123', 'action');
});
}));
it('should normalize comment when user input contains spaces sequence', async(() => {
let element = fixture.nativeElement.querySelector('.adf-comments-input-add');
component.message = 'test comment';
element.dispatchEvent(new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(addContentCommentSpy).toHaveBeenCalledWith('123', 'test comment');
});
}));
it('should add break lines to comment when user input contains new line characters', async(() => {
let element = fixture.nativeElement.querySelector('.adf-comments-input-add');
component.message = 'these\nare\nparagraphs\n';
element.dispatchEvent(new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(addContentCommentSpy).toHaveBeenCalledWith('123', 'these<br/>are<br/>paragraphs');
});
}));
it('should not call service to add a comment when comment is empty', async(() => { it('should not call service to add a comment when comment is empty', async(() => {
let event = new KeyboardEvent('keyup', {'key': 'Enter'}); let element = fixture.nativeElement.querySelector('.adf-comments-input-add');
let element = fixture.nativeElement.querySelector('#comment-input');
component.message = ''; component.message = '';
element.dispatchEvent(event); element.dispatchEvent(new Event('click'));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
@ -307,7 +368,6 @@ describe('CommentsComponent', () => {
it('should clear comment when escape key is pressed', async(() => { it('should clear comment when escape key is pressed', async(() => {
let event = new KeyboardEvent('keyup', {'key': 'Escape'}); let event = new KeyboardEvent('keyup', {'key': 'Escape'});
let element = fixture.nativeElement.querySelector('#comment-input'); let element = fixture.nativeElement.querySelector('#comment-input');
component.message = 'Test comment';
element.dispatchEvent(event); element.dispatchEvent(event);
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {

View File

@ -126,9 +126,11 @@ export class CommentsComponent implements OnChanges {
add(): void { add(): void {
if (this.message && this.message.trim() && !this.beingAdded) { if (this.message && this.message.trim() && !this.beingAdded) {
const comment = this.sanitize(this.message);
this.beingAdded = true; this.beingAdded = true;
if (this.isATask()) { if (this.isATask()) {
this.commentProcessService.addTaskComment(this.taskId, this.message) this.commentProcessService.addTaskComment(this.taskId, comment)
.subscribe( .subscribe(
(res: CommentModel) => { (res: CommentModel) => {
this.comments.unshift(res); this.comments.unshift(res);
@ -144,7 +146,7 @@ export class CommentsComponent implements OnChanges {
} }
if (this.isANode()) { if (this.isANode()) {
this.commentContentService.addNodeComment(this.nodeId, this.message) this.commentContentService.addNodeComment(this.nodeId, comment)
.subscribe( .subscribe(
(res: CommentModel) => { (res: CommentModel) => {
this.comments.unshift(res); this.comments.unshift(res);
@ -176,4 +178,10 @@ export class CommentsComponent implements OnChanges {
isANode(): boolean { isANode(): boolean {
return this.nodeId ? true : false; return this.nodeId ? true : false;
} }
private sanitize(input: string) {
return input.replace(/<[^>]+>/g, '')
.replace(/^\s+|\s+$|\s+(?=\s)/g, '')
.replace(/\r?\n/g, '<br/>');
}
} }