Refactoring/cleanup and other minor changes

This commit is contained in:
Matthew Ross 2020-05-12 15:47:36 -04:00
parent 66c06c01e8
commit ad17f51d71
25 changed files with 263 additions and 176 deletions

View File

@ -195,11 +195,11 @@ Because I like seeing the numbers.
Language | Files | Blank | Comment | Code
-----------|--------:|---------:|---------:|---------:
TypeScript | 66 | 949 | 119 | 4058
PHP | 18 | 660 | 24 | 2034
HTML | 21 | 255 | 0 | 1533
SASS | 14 | 295 | 10 | 1333
**SUM:** | **119** | **2159** | **153** | **8958**
TypeScript | 66 | 959 | 120 | 4060
PHP | 18 | 665 | 24 | 2043
HTML | 21 | 257 | 0 | 1538
SASS | 14 | 296 | 10 | 1338
**SUM:** | **119** | **2177** | **154** | **8978**
Command: `cloc --exclude-dir=vendor --exclude-ext=json,svg,ini src/`
@ -207,8 +207,8 @@ Command: `cloc --exclude-dir=vendor --exclude-ext=json,svg,ini src/`
Language | Files | Blank | Comment | Code
-----------|-------:|---------:|---------:|---------:
TypeScript | 38 | 995 | 7 | 3442
TypeScript | 38 | 1002 | 8 | 3481
PHP | 11 | 793 | 16 | 2338
**SUM:** | **49** | **1788** | **23** | **5780**
**SUM:** | **49** | **1795** | **24** | **5819**
Command: `cloc --exclude-ext=xml test/`

View File

@ -40,6 +40,7 @@
},
"configurations": {
"production": {
"aot": true,
"optimization": true,
"outputHashing": "all",
"sourceMap": false,

79
src/api/composer.lock generated
View File

@ -166,16 +166,16 @@
},
{
"name": "gabordemooij/redbean",
"version": "v5.4.2",
"version": "v5.5",
"source": {
"type": "git",
"url": "https://github.com/gabordemooij/redbean.git",
"reference": "58d366ac135cf14c7b952cda075a7723dfb425a7"
"reference": "8b8326f7dbe8023675b0d1936df4800fe0cce0b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/gabordemooij/redbean/zipball/58d366ac135cf14c7b952cda075a7723dfb425a7",
"reference": "58d366ac135cf14c7b952cda075a7723dfb425a7",
"url": "https://api.github.com/repos/gabordemooij/redbean/zipball/8b8326f7dbe8023675b0d1936df4800fe0cce0b3",
"reference": "8b8326f7dbe8023675b0d1936df4800fe0cce0b3",
"shasum": ""
},
"require": {
@ -203,7 +203,7 @@
"keywords": [
"orm"
],
"time": "2019-12-26T17:14:58+00:00"
"time": "2020-04-26T13:31:18+00:00"
},
{
"name": "jeremeamia/superclosure",
@ -794,24 +794,21 @@
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.0.0",
"version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
"reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a"
"reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a",
"reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b",
"reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "~6"
},
"type": "library",
"extra": {
"branch-alias": {
@ -842,7 +839,7 @@
"reflection",
"static analysis"
],
"time": "2018-08-07T13:53:10+00:00"
"time": "2020-04-27T09:25:28+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
@ -1336,16 +1333,16 @@
},
{
"name": "phpunit/phpunit",
"version": "8.5.3",
"version": "8.5.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "67750516bc02f300e2742fed2f50177f8f37bedf"
"reference": "8474e22d7d642f665084ba5ec780626cbd1efd23"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/67750516bc02f300e2742fed2f50177f8f37bedf",
"reference": "67750516bc02f300e2742fed2f50177f8f37bedf",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8474e22d7d642f665084ba5ec780626cbd1efd23",
"reference": "8474e22d7d642f665084ba5ec780626cbd1efd23",
"shasum": ""
},
"require": {
@ -1425,7 +1422,7 @@
"type": "github"
}
],
"time": "2020-03-31T08:52:04+00:00"
"time": "2020-04-23T04:39:42+00:00"
},
{
"name": "psr/container",
@ -2433,16 +2430,16 @@
},
{
"name": "slim/psr7",
"version": "1.0.0",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Slim-Psr7.git",
"reference": "7ca5b5d96687b7c563238715cc80b12675d8b895"
"reference": "3c76899e707910779f13d7af95fde12310b0a5ae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/7ca5b5d96687b7c563238715cc80b12675d8b895",
"reference": "7ca5b5d96687b7c563238715cc80b12675d8b895",
"url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/3c76899e707910779f13d7af95fde12310b0a5ae",
"reference": "3c76899e707910779f13d7af95fde12310b0a5ae",
"shasum": ""
},
"require": {
@ -2503,7 +2500,7 @@
"psr-7",
"psr7"
],
"time": "2020-01-04T23:05:43+00:00"
"time": "2020-05-01T14:24:20+00:00"
},
{
"name": "slim/slim",
@ -2600,16 +2597,16 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.15.0",
"version": "v1.16.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14"
"reference": "1aab00e39cebaef4d8652497f46c15c1b7e45294"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14",
"reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1aab00e39cebaef4d8652497f46c15c1b7e45294",
"reference": "1aab00e39cebaef4d8652497f46c15c1b7e45294",
"shasum": ""
},
"require": {
@ -2621,7 +2618,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.15-dev"
"dev-master": "1.16-dev"
}
},
"autoload": {
@ -2668,20 +2665,20 @@
"type": "tidelift"
}
],
"time": "2020-02-27T09:26:54+00:00"
"time": "2020-05-08T16:50:20+00:00"
},
{
"name": "symfony/polyfill-php56",
"version": "v1.15.0",
"version": "v1.16.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php56.git",
"reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d"
"reference": "14b55b0e88c67c6a14526799abaea197dde78911"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/d51ec491c8ddceae7dca8dd6c7e30428f543f37d",
"reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d",
"url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/14b55b0e88c67c6a14526799abaea197dde78911",
"reference": "14b55b0e88c67c6a14526799abaea197dde78911",
"shasum": ""
},
"require": {
@ -2691,7 +2688,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.15-dev"
"dev-master": "1.16-dev"
}
},
"autoload": {
@ -2738,20 +2735,20 @@
"type": "tidelift"
}
],
"time": "2020-03-09T19:04:49+00:00"
"time": "2020-05-08T16:50:20+00:00"
},
{
"name": "symfony/polyfill-util",
"version": "v1.15.0",
"version": "v1.16.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-util.git",
"reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027"
"reference": "fa1fdaf94e8a60932d8821692eb1ed07efc52db2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-util/zipball/d8e76c104127675d0ea3df3be0f2ae24a8619027",
"reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027",
"url": "https://api.github.com/repos/symfony/polyfill-util/zipball/fa1fdaf94e8a60932d8821692eb1ed07efc52db2",
"reference": "fa1fdaf94e8a60932d8821692eb1ed07efc52db2",
"shasum": ""
},
"require": {
@ -2760,7 +2757,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.15-dev"
"dev-master": "1.16-dev"
}
},
"autoload": {
@ -2804,7 +2801,7 @@
"type": "tidelift"
}
],
"time": "2020-03-02T11:55:35+00:00"
"time": "2020-05-02T14:56:09+00:00"
},
{
"name": "theseer/tokenizer",

View File

@ -1,4 +1,6 @@
<?php
use Psr\Http\Message\ServerRequestInterface;
use Slim\Factory\AppFactory;
use Slim\Exception\HttpNotFoundException;
use Slim\Psr7\Response;
@ -35,12 +37,24 @@ $container->set('logger', function() {
return $logger;
});
$container->set('strings', function() {
$json = file_get_contents('../json/en.json');
return json_decode($json);
});
$errorMiddleware->setErrorHandler(HttpNotFoundException::class,
function () {
function (ServerRequestInterface $request) {
$response = new Response();
$message = 'Matching API call not found.';
if (strpos($request->getUri()->getPath(), 'uploads')) {
$message = 'File not found.';
}
$response->withHeader('Content-Type', 'application/json')
->getBody()->write('{ message: "Matching API call not found." }');
->getBody()->write('{ message: "' . $message . '" }');
return $response->withStatus(404);
}

View File

@ -20,10 +20,12 @@ export class ApiInterceptor implements HttpInterceptor {
constructor(private router: Router) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
intercept(request: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
const headers = (request.body instanceof FormData)
? { }
: { 'Content-Type': 'application/json' };
? { }
: { 'Content-Type': 'application/json' };
const token = sessionStorage.getItem(this.JWT_KEY);
if (token !== null) {

View File

@ -84,6 +84,8 @@
</div>
<div class="board" cdkDropListGroup *ngIf="activeBoard">
<div class="loading" *ngIf="loading">{{ strings['loading'] }}...</div>
<tb-column class="column" [id]="column.id" [column]="column"
*ngFor="let column of activeBoard.columns"
(on-update-boards)="updateBoards()"></tb-column>

View File

@ -34,14 +34,14 @@ export class BoardService {
this.initMarked();
}
convertMarkdown(markdown: string, callback = this.defaultCallback, doCount = false): MarkedReturn {
async convertMarkdown(markdown: string, callback = this.defaultCallback,
doCount = false): Promise<MarkedReturn> {
this.checkCounts.total = 0;
this.checkCounts.complete = 0;
const retVal: MarkedReturn = { html: '', counts: {} };
retVal.html = marked(markdown);
callback(false, retVal.html);
retVal.html = callback(false, marked(markdown));
if (doCount) {
retVal.counts = this.checkCounts;

View File

@ -77,6 +77,7 @@
<div class="tasks" *ngIf="columnData" cdkDropList
[cdkDropListData]="columnData.tasks"
(cdkDropListDropped)="drop($event, +columnData.id - 1)">
<tb-task class="task-container" [id]="task.id" cdkDrag
*ngFor="let task of columnData.tasks"
[task]="task" [add-task]="getShowModalFunction()"
@ -212,7 +213,7 @@
<h3>{{ strings['boards_taskComments'] }}</h3>
<div class="comment" *ngFor="let comment of viewModalProps.comments">
<div [innerHTML]="getComment(comment.text)" *ngIf="!comment.isEdit"></div>
<div [innerHTML]="comment.html" *ngIf="!comment.isEdit"></div>
<textarea *ngIf="comment.isEdit" [(ngModel)]="commentEdit.text"
(keyup.control.enter)="comment.isEdit = false;editComment()"
@ -298,7 +299,10 @@
{{ strings['boards_taskTitle'] }}
<input type="text" name="title" #focusMe [(ngModel)]="modalProps.title"
[placeholder]="strings['boards_taskTitlePlaceholder']">
[placeholder]="strings['boards_taskTitlePlaceholder']"
(keyup.control.enter)="modalProps.id === 0 ? addTask()
: updateTask()"
(keyup.enter)="preventEnter($event)">
</label>
<label>
@ -321,7 +325,7 @@
</select>
</label>
<label>
<label *ngIf="modalProps.categories.length">
{{ strings['boards_taskCategories'] }}
<select name="categories" multiple [ngModel]="modalProps.categories"

View File

@ -110,6 +110,7 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
this.modalProps = new Task();
this.viewModalProps = new Task();
// These are kept locally to allow override in tests.
this.moveItemInArray = moveItemInArray;
this.transferArrayItem = transferArrayItem;
@ -175,7 +176,9 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
}
userName(id: number) {
return this.activeBoard.users.find(u => u.id === id).username;
const user = this.activeBoard.users.find(u => u.id === id);
return user ? user.username : this.strings.none;
}
sortTasks() {
@ -582,11 +585,6 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
this.modal.open(this.MODAL_ID + this.columnData.id);
}
getComment(text: string) {
const data = this.boardService.convertMarkdown(text, this.markedCallback);
return this.sanitizer.bypassSecurityTrustHtml(data.html);
}
getUserName(userId: number) {
const user = this.activeBoard.users
.find((test: User) => test.id === +userId);
@ -603,6 +601,22 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
this.updateTaskActivity(taskId);
this.boardService
.convertMarkdown(viewTask.description, this.markedCallback)
.then(data => {
data.html.replace(/(\{)([^}]+)(\})/g, '{{ "{" }}$2{{ "}" }}');
this.viewModalProps.html =
this.sanitizer.bypassSecurityTrustHtml(data.html);
});
viewTask.comments.forEach(comment => {
this.boardService.convertMarkdown(comment.text, this.markedCallback)
.then(data => {
comment.html = this.sanitizer.bypassSecurityTrustHtml(data.html);
});
});
this.newComment = '';
this.viewModalProps = Object.assign({}, viewTask);
this.checkDueDate();
@ -663,22 +677,17 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
private convertToTask(updatedTask: any) {
const task = new Task(updatedTask.id,
updatedTask.title,
updatedTask.description,
updatedTask.color,
updatedTask.due,
updatedTask.points,
updatedTask.position,
updatedTask.column_id,
updatedTask.ownComment,
updatedTask.ownAttachment,
updatedTask.sharedUser,
updatedTask.sharedCategory);
const data = this.boardService.convertMarkdown(task.description,
(_, text) => text, true);
task.html = data.html;
updatedTask.title,
updatedTask.description,
updatedTask.color,
updatedTask.due,
updatedTask.points,
updatedTask.position,
updatedTask.column_id,
updatedTask.ownComment,
updatedTask.ownAttachment,
updatedTask.sharedUser,
updatedTask.sharedCategory);
return task;
}
@ -718,24 +727,19 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
this.activeBoard.issue_trackers.forEach(tracker => {
const re = new RegExp(tracker.regex, 'ig');
const replacements = new Array<any>();
let result = re.exec(text);
while (result !== null) {
const link = '<a href="' +
tracker.url.replace(/%BUGID%/g, result[1]) +
'" target="tb_external" rel="noreferrer">' +
result[0] + '</a>';
const link = '<a href="' + tracker.url.replace(/%BUGID%/g, result[1]) +
'" target="tb_external" rel="noreferrer">' + result[0] + '</a>';
replacements.push({
str: result[0],
link
});
replacements.push({ str: result[0], link });
result = re.exec(text);
}
for (let i = replacements.length - 1; i >= 0; --i) {
text = text.replace(replacements[i].str,
replacements[i].link);
text = text.replace(replacements[i].str, replacements[i].link);
}
});

View File

@ -141,7 +141,7 @@ export class TaskDisplayComponent implements OnInit {
taskData.column_id = boardData.columns[0].id;
this.boardService.addTask(taskData)
this.boardService.addTask(taskData as any)
.subscribe((response: ApiResponse) => {
if (response.status === 'success') {
this.notes.add(
@ -201,16 +201,17 @@ export class TaskDisplayComponent implements OnInit {
return;
}
const data = this.boardService.convertMarkdown(
this.taskData.description, this.markedCallback, true
);
this.boardService
.convertMarkdown(this.taskData.description, this.markedCallback, true)
.then(data => {
data.html.replace(/(\{)([^}]+)(\})/g, '{{ "{" }}$2{{ "}" }}');
data.html.replace(/(\{)([^}]+)(\})/g, '{{ "{" }}$2{{ "}" }}');
if (data.counts.total) {
this.percentComplete = data.counts.complete / data.counts.total;
}
if (data.counts.total) {
this.percentComplete = data.counts.complete / data.counts.total;
}
this.taskData.html = this.sanitizer.bypassSecurityTrustHtml(data.html);
this.taskData.html = this.sanitizer.bypassSecurityTrustHtml(data.html);
});
}
private checkDueDate() {
@ -241,31 +242,22 @@ export class TaskDisplayComponent implements OnInit {
// Needs anonymous function for proper `this` context.
private markedCallback = (_: any, text: string) => {
if (!this.activeBoard.issue_trackers) {
return;
}
this.activeBoard.issue_trackers.forEach(tracker => {
const re = new RegExp(tracker.regex, 'ig');
const replacements = new Array<any>();
let result = re.exec(text);
while (result !== null) {
const link = '<a href="' +
tracker.url.replace(/%BUGID%/g, result[1]) +
'" target="tb_external" rel="noreferrer">' +
result[0] + '</a>';
const link = '<a href="' + tracker.url.replace(/%BUGID%/g, result[1]) +
'" target="tb_external" rel="noreferrer">' + result[0] + '</a>';
replacements.push({
str: result[0],
link
});
replacements.push({ str: result[0], link });
result = re.exec(text);
}
for (let i = replacements.length - 1; i >= 0; --i) {
text = text.replace(replacements[i].str,
replacements[i].link);
text = text.replace(replacements[i].str, replacements[i].link);
}
});

View File

@ -28,12 +28,12 @@ export class LoginComponent implements OnInit {
}
ngOnInit(): void {
this.authService.authenticate()
.subscribe(isAuth => {
if (isAuth) {
this.router.navigate(['/boards']);
}
});
this.authService.authenticate(undefined, true)
.subscribe(isAuth => {
if (isAuth) {
this.router.navigate(['/boards']);
}
});
}
login(): void {
@ -53,6 +53,15 @@ export class LoginComponent implements OnInit {
});
if (response.status === 'success') {
if (this.authService.attemptedRoute?.length) {
this.router.navigate([this.authService.attemptedRoute]);
this.authService.attemptedRoute = undefined;
this.isSubmitted = false;
return;
}
this.router.navigate(['/boards']);
}

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { CanActivate, RouterStateSnapshot } from '@angular/router';
import { AuthService } from './auth.service';
@ -8,8 +8,8 @@ export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) {
}
canActivate() {
return this.authService.authenticate();
canActivate(_: any, state: RouterStateSnapshot) {
return this.authService.authenticate(state.url);
}
}

View File

@ -20,6 +20,8 @@ export class AuthService {
public userOptions: UserOptions = null;
public userChanged = this.activeUser.asObservable();
public attemptedRoute: string;
constructor(public constants: Constants, private http: HttpClient,
public router: Router, private strings: StringsService) {
}
@ -33,7 +35,11 @@ export class AuthService {
this.activeUser.next(user);
}
authenticate(): Observable<boolean> {
authenticate(url: string, isLogin = false): Observable<boolean> {
if (!isLogin) {
this.attemptedRoute = url;
}
return this.http.post('api/authenticate', null)
.pipe(
map((response: ApiResponse) => {
@ -46,24 +52,20 @@ export class AuthService {
login(username: string, password: string,
remember: boolean): Observable<ApiResponse> {
const json = JSON.stringify({
username,
password,
remember
});
const json = JSON.stringify({ username, password, remember });
return this.http.post('api/login', json)
.pipe(
map((response: ApiResponse) => {
this.updateUser(response.data[1], response.data[2]);
return response;
}),
catchError((err, _) => {
this.updateUser(null, null);
return of(err.error as ApiResponse);
})
);
}
return this.http.post('api/login', json)
.pipe(
map((response: ApiResponse) => {
this.updateUser(response.data[1], response.data[2]);
return response;
}),
catchError((err, _) => {
this.updateUser(null, null);
return of(err.error as ApiResponse);
})
);
}
logout(): Observable<ApiResponse> {
return this.http.post('api/logout', null)

View File

@ -1,4 +1,8 @@
import { SafeHtml } from '@angular/platform-browser';
export class Comment {
public html: SafeHtml;
constructor(public id: number = 0,
public text: string = '',
public user_id: number = 0, // tslint:disable-line

View File

@ -6,6 +6,7 @@
"save": "Save",
"cancel": "Cancel",
"sortBy": "Sort By",
"loading": "Loading",
"dashboard": "Dashboard",
"boards": "Boards",

View File

@ -6,6 +6,7 @@
"save": "Guardar",
"cancel": "Cancelar",
"sortBy": "Ordenar por",
"loading": "Cargando",
"dashboard": "Salpicadero",
"boards": "Tableros",

View File

@ -6,6 +6,7 @@
"save": "Enregistrer",
"cancel": "Annuler",
"sortBy": "Trier par ",
"loading": "Chargement",
"dashboard": "Tableau de bord",
"boards": "Tableaux",

View File

@ -29,6 +29,11 @@
white-space: nowrap;
width: 100%;
.loading {
font-size: 24px;
width: 100%;
}
.icon-minus-squared-alt,
.icon-plus-squared-alt {
cursor: pointer;
@ -366,7 +371,7 @@
.description {
max-height: 20em;
overflow: auto;
padding: 1em;
padding: 0 1em;
.checklist {
list-style: none;
@ -380,6 +385,7 @@
margin-top: 12px;
button {
margin: 0;
width: 45%;
}
}

View File

@ -60,9 +60,10 @@ describe('BoardService', () => {
});
it('converts markdown', () => {
const actual = service.convertMarkdown('# Test');
expect(actual.html).toEqual('<h1 id="test">Test</h1>\n');
service.convertMarkdown('# Test')
.then(actual => {
expect(actual.html).toEqual('<h1 id="test">Test</h1>\n');
});
});
it('handles errors when getting all boards', () => {

View File

@ -558,14 +558,6 @@ describe('ColumnDisplay', () => {
expect(component.viewModalProps.column_id).toEqual(1);
});
it('gets a comment converted from markdown', () => {
component.activeBoard = { issue_trackers: [] } as any;
const comment = component.getComment('# Testing');
expect(comment).toEqual('<h1 id="testing">Testing</h1>\n');
});
it('gets a username by user id', () => {
component.activeBoard = {
users: [{ id: 1, username: 'test' } as any]

View File

@ -78,48 +78,77 @@ describe('TaskDisplay', () => {
expect(component.taskData.id).toEqual(1);
});
it('parses task description markdown into text', () => {
it('parses task description markdown into text', done => {
setupBoard();
component.taskData.description = '# Make this HTML';
component.activeBoard.issue_trackers = [
{ regex: 'test', url: '%BUGID%' }
] as any;
{ id: 1, regex: 'test', url: '%BUGID%' }
];
component.ngOnInit();
// tslint:disable-next-line
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
.toEqual('<h1 id="make-this-html">Make this HTML</h1>\n');
setTimeout(() => {
// tslint:disable-next-line
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
.toEqual('<h1 id="make-this-html">Make this HTML</h1>\n');
done();
}, 100);
});
it('handles checklists in markdown', () => {
it('handles checklists in markdown', done => {
setupBoard();
component.taskData.description = ' - [x] One\n - [ ] Two';
component.ngOnInit();
// tslint:disable-next-line
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
.toEqual('<ul>\n<li><i class="icon icon-check"></i>One</li>\n' +
'<li><i class="icon icon-check-empty"></i>Two</li>\n</ul>\n');
setTimeout(() => {
// tslint:disable-next-line
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
.toEqual('<ul>\n<li><i class="icon icon-check"></i>One</li>\n' +
'<li><i class="icon icon-check-empty"></i>Two</li>\n</ul>\n');
done();
}, 100);
});
it('adds attributes to links in markdown', () => {
it('adds attributes to links in markdown', done => {
setupBoard();
component.taskData.description = '[link](google.com)';
component.ngOnInit();
// tslint:disable-next-line
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
.toContain('target="tb_external" rel="noreferrer"');
setTimeout(() => {
// tslint:disable-next-line
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
.toContain('target="tb_external" rel="noreferrer"');
done();
}, 100);
});
it('provides a custom style for percentage of task completed', () => {
it('handles issue trackers in markdown', done => {
setupBoard();
component.activeBoard.issue_trackers = [
{ id: 1, url: 'TaskBoard/issues/%BUGID%', regex: '(?:Issue)?#(\\d+)' }
];
component.taskData.description = '#123';
component.ngOnInit();
setTimeout(() => {
// tslint:disable-next-line
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
.toContain('href="TaskBoard/issues/123"');
done();
}, 100);
});;
it('provides a custom style for percentage of task completed', done => {
component.percentComplete = .5;
const actual = component.getPercentStyle();
// tslint:disable-next-line
expect((actual as any)['changingThisBreaksApplicationSecurity'])
.toContain('width: 50%;');
setTimeout(() => {
// tslint:disable-next-line
expect((actual as any)['changingThisBreaksApplicationSecurity'])
.toContain('width: 50%;');
done();
}, 100);
});
it('provides a custom title for percentage of task completed', () => {

View File

@ -82,6 +82,11 @@ describe('Login', () => {
component.login();
expect(spy).toHaveBeenCalledWith(['/boards']);
component.authService.attemptedRoute = '/boards/2';
component.login();
expect(spy).toHaveBeenCalledWith(['/boards/2']);
});
});

View File

@ -132,6 +132,25 @@ describe('UserAdmin', () => {
expect(called).toEqual(true);
});
it('shows a modal', () => {
component.showModal();
expect(component.modalProps.prefix).toEqual(true);
const user = new UserDisplay(1);
user.board_access = [1, 2];
component.showModal(false, user);
expect(component.modalProps.prefix).toEqual(false);
});
it('shows a confirmation modal', () => {
const user = new UserDisplay(1);
component.showConfirmModal(user);
expect(component.userToRemove).toEqual(user);
});
it('validates modal user data', () => {
let called = false;

View File

@ -28,7 +28,8 @@ describe('AuthGuard', () => {
it('implements CanActivate', () => {
let actual = false;
service.canActivate().subscribe(value => actual = value);
service.canActivate(null, { url: '' } as any)
.subscribe(value => actual = value);
expect(actual).toEqual(true);
});

View File

@ -52,7 +52,7 @@ describe('AuthService', () => {
});
it('authenticates a user', () => {
service.authenticate().subscribe(response => {
service.authenticate('').subscribe(response => {
expect(response).toEqual(true);
});
@ -60,7 +60,7 @@ describe('AuthService', () => {
});
it('handles errors on authenticate', () => {
service.authenticate().subscribe(response => {
service.authenticate('').subscribe(response => {
expect(response).toEqual(false);
});
@ -91,7 +91,7 @@ describe('AuthService', () => {
testCall('api/logout', 'POST');
});
const testCall = (url, method, isError = false) => {
const testCall = (url: string, method: string, isError = false) => {
const req = httpMock.expectOne({ method, url });
expect(req.request.method).toEqual(method);