From 0c04506bdb13893151c1434d23252986a12d2b65 Mon Sep 17 00:00:00 2001
From: Vignesh Sankar Iyer
<68676914+vigneshsankariyer1234567890@users.noreply.github.com>
Date: Sun, 5 Mar 2023 14:39:54 +0800
Subject: [PATCH] Merge master into release for V3.4.5 (#1150)
Merge master into release to release V3.4.5
---
browserslist => .browserslistrc | 0
.github/dependabot.yml | 7 +
.github/workflows/coverage-report.yml | 2 +-
.github/workflows/github-actions.yml | 3 -
README.md | 6 +
angular.json | 21 +-
electron-utils/oauth.ts | 4 +-
package.json | 68 +++---
src/app/app.component.ts | 6 +-
src/app/app.module.ts | 3 +-
src/app/auth/auth.component.ts | 14 +-
src/app/auth/auth.module.ts | 3 +-
.../confirm-login/confirm-login.component.ts | 10 +-
.../json-parse-error-dialog.component.ts | 2 +-
src/app/auth/profiles/profiles.component.html | 1 +
src/app/auth/profiles/profiles.component.ts | 2 +-
.../session-selection.component.ts | 2 +-
.../form-disable-control.directive.ts | 2 +-
.../user-confirmation.component.ts | 2 +-
src/app/core/models/issue.model.ts | 7 +-
.../issue-dispute-section-parser.model.ts | 2 +-
.../moderation-section-parser.model.ts | 2 +-
.../tester-response-section-parser.model.ts | 2 +-
.../core/models/templates/template.model.ts | 8 +-
src/app/core/services/auth.service.ts | 9 +-
src/app/core/services/dialog.service.ts | 20 +-
.../core/services/error-handling.service.ts | 6 +-
src/app/core/services/github.service.ts | 20 +-
src/app/core/services/githubevent.service.ts | 4 +-
src/app/core/services/issue.service.ts | 31 ++-
src/app/core/services/label.service.ts | 15 +-
.../core/services/mocks/mock.auth.service.ts | 2 +-
.../core/services/mocks/mock.issue.service.ts | 10 +-
src/app/core/services/phase.service.ts | 4 +-
src/app/core/services/repo-creator.service.ts | 12 +-
.../session-fix-confirmation.component.ts | 2 +-
src/app/core/services/user.service.ts | 4 +-
.../action-toasters/action-toasters.module.ts | 3 +-
.../undo-action/undo-action.component.ts | 2 +-
.../comment-editor.component.html | 2 +
.../comment-editor.component.ts | 130 ++++++++++-
.../comment-editor/comment-editor.module.ts | 8 +-
.../markdown-toolbar.component.css | 0
.../markdown-toolbar.component.html | 66 ++++++
.../markdown-toolbar.component.ts | 13 ++
.../comment-editor/upload-text-insertor.ts | 2 +-
.../error-toasters/error-toaster.module.ts | 3 +-
.../form-error/form-error.component.ts | 2 +-
.../general-message-error.component.ts | 2 +-
.../invalid-credentials-error.component.ts | 2 +-
.../toaster/toaster.component.ts | 2 +-
.../shared/issue-tables/IssuesDataTable.ts | 7 +-
.../shared/issue-tables/issue-paginator.ts | 2 +-
src/app/shared/issue-tables/issue-sorter.ts | 2 +-
.../issue-tables/issue-tables-columns.ts | 2 +-
.../issue-tables/issue-tables.component.html | 10 +-
.../issue-tables/issue-tables.component.ts | 22 +-
.../issue/assignee/assignee.component.ts | 2 +-
.../conflict-dialog.component.ts | 3 +-
.../description/description.component.html | 9 +-
.../description/description.component.ts | 15 +-
.../duplicateOf/duplicate-of.component.ts | 5 +-
.../shared/issue/issue-components.module.ts | 5 +-
.../shared/issue/title/title.component.css | 1 +
.../shared/issue/title/title.component.html | 9 +-
src/app/shared/issue/title/title.component.ts | 10 +
.../label-definition-popup.component.ts | 2 +-
src/app/shared/layout/header.component.ts | 6 +-
src/app/shared/lib/marked.ts | 2 -
src/app/shared/material.module.ts | 104 +++++----
.../issue-dispute/issue-dispute.component.ts | 6 +-
.../conflict-dialog.component.html | 6 +-
.../conflict-dialog.component.ts | 3 +-
.../new-team-response.component.html | 4 +-
.../new-team-response.component.ts | 12 +-
.../new-team-response.module.ts | 3 +-
.../team-response.component.html | 9 +-
.../team-response/team-response.component.ts | 38 +++-
.../conflict-dialog.component.ts | 2 +-
.../tester-response.component.html | 2 +-
.../tester-response.component.ts | 59 ++++-
.../tester-response/tester-response.module.ts | 1 -
.../view-issue/view-issue.component.html | 2 +-
src/polyfills.ts | 5 +
src/test.ts | 2 +-
src/tsconfig.app.json | 3 +-
src/typings.d.ts | 2 +-
.../auth/profiles/profiles.component.spec.ts | 106 +++++++++
.../session-selection.component.spec.ts | 102 +++++++++
.../issues-faulty.component.spec.ts | 2 +-
.../issues-pending.component.spec.ts | 2 +-
.../issues-responded.component.spec.ts | 2 +-
.../comment-editor.component.spec.ts | 202 ++++++++++++++++++
.../upload-text-insertor.spec.ts | 169 +++++++++++++++
.../issue-tables/issue-paginator.spec.ts | 2 +-
.../shared/issue-tables/issue-sorter.spec.ts | 2 +-
.../shared/issue-tables/search-filter.spec.ts | 4 +-
.../issue/assignee/assignee.component.spec.ts | 4 +-
.../description/description.component.spec.ts | 2 +-
.../duplicated-issues.component.spec.ts | 4 +-
.../issue/title/title.component.spec.ts | 2 +-
tests/constants/error.constants.ts | 22 ++
tests/constants/githubcomment.constants.ts | 10 +-
tests/constants/githubissue.constants.ts | 6 +-
tests/constants/label.constants.ts | 2 +-
...issue-dispute-section-parser.model.spec.ts | 2 +-
.../moderation-section-parser.model.spec.ts | 3 +-
...ster-response-section-parser.model.spec.ts | 8 +-
tests/services/error-handling.service.spec.ts | 74 +++++++
tests/services/label.service.spec.ts | 14 +-
tests/services/logging.service.spec.ts | 70 +++---
tests/services/permissions.service.spec.ts | 2 +-
tests/services/repo-creator.service.spec.ts | 2 +-
tests/services/user.service.spec.ts | 6 +-
tsconfig.json | 2 +-
115 files changed, 1440 insertions(+), 320 deletions(-)
rename browserslist => .browserslistrc (100%)
create mode 100644 .github/dependabot.yml
create mode 100644 src/app/shared/comment-editor/markdown-toolbar/markdown-toolbar.component.css
create mode 100644 src/app/shared/comment-editor/markdown-toolbar/markdown-toolbar.component.html
create mode 100644 src/app/shared/comment-editor/markdown-toolbar/markdown-toolbar.component.ts
create mode 100644 tests/app/auth/profiles/profiles.component.spec.ts
create mode 100644 tests/app/auth/session-selection/session-selection.component.spec.ts
create mode 100644 tests/app/shared/comment-editor/comment-editor.component.spec.ts
create mode 100644 tests/app/shared/comment-editor/upload-text-insertor.spec.ts
create mode 100644 tests/constants/error.constants.ts
create mode 100644 tests/services/error-handling.service.spec.ts
diff --git a/browserslist b/.browserslistrc
similarity index 100%
rename from browserslist
rename to .browserslistrc
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..067368c9a
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,7 @@
+version: 2
+updates:
+ - package-ecosystem: 'npm'
+ directory: '/'
+ schedule:
+ interval: 'weekly'
+ open-pull-requests-limit: 0
diff --git a/.github/workflows/coverage-report.yml b/.github/workflows/coverage-report.yml
index 63f8d3191..9cc7bb4fc 100644
--- a/.github/workflows/coverage-report.yml
+++ b/.github/workflows/coverage-report.yml
@@ -23,7 +23,7 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- - run: npm run test -- "--code-coverage"
+ - run: npm run test -- "--code-coverage" "--source-map=false"
- uses: codecov/codecov-action@v3
with:
directory: ./tests/coverage
diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml
index 6f3a739f9..1fa667fd4 100644
--- a/.github/workflows/github-actions.yml
+++ b/.github/workflows/github-actions.yml
@@ -24,7 +24,6 @@ jobs:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm update
- - run: npm run electron:linux
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: npm run lint
@@ -46,7 +45,6 @@ jobs:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm update
- - run: npm run electron:mac
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: npm run lint
@@ -65,6 +63,5 @@ jobs:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm update
- - run: npm run electron:windows
- run: npm run lint
- run: npm test -- "--karma-config=./tests/karma.ci.conf.js"
diff --git a/README.md b/README.md
index bec98c299..8b7fe73c1 100644
--- a/README.md
+++ b/README.md
@@ -8,3 +8,9 @@
* [Direct link to the **User Guide**](https://catcher-org.github.io/ug/)
* [Direct link to the **Developer Guide**](https://catcher-org.github.io/dg/)
+
+This project is based in [NUS School of Computing](https://www.comp.nus.edu.sg/), and part of the [NUS-OSS initiative](https://nus-oss.github.io/).
+
+Licence: MIT
+
+Contact: `seer@comp.nus.edu.sg`
diff --git a/angular.json b/angular.json
index 5e9d63983..e1d0a42eb 100644
--- a/angular.json
+++ b/angular.json
@@ -11,6 +11,7 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
+ "aot": true,
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
@@ -39,6 +40,12 @@
},
"configurations": {
"staging": {
+ "budgets": [
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "6kb"
+ }
+ ],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
@@ -56,6 +63,12 @@
]
},
"production": {
+ "budgets": [
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "6kb"
+ }
+ ],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
@@ -73,6 +86,12 @@
]
},
"test": {
+ "budgets": [
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "6kb"
+ }
+ ],
"optimization": false,
"outputHashing": "all",
"sourceMap": true,
@@ -145,7 +164,7 @@
"schematics": {
"@schematics/angular:component": {
"prefix": "app",
- "styleext": "css"
+ "style": "css"
},
"@schematics/angular:directive": {
"prefix": "app"
diff --git a/electron-utils/oauth.ts b/electron-utils/oauth.ts
index f0229fc0b..0aea0337a 100644
--- a/electron-utils/oauth.ts
+++ b/electron-utils/oauth.ts
@@ -2,8 +2,8 @@ import { BrowserWindow, shell } from 'electron';
import { v4 as uuid } from 'uuid';
const nodeUrl = require('url');
-const fetch = require('node-fetch');
const Logger = require('electron-log');
+const fetch = require('node-fetch');
const CLIENT_ID = '6750652c0c9001314434';
const BASE_URL = 'https://github.com';
@@ -24,7 +24,7 @@ export function getAccessToken(window: BrowserWindow, repoPermissionLevel: strin
const accessTokenUrl = `${ACCESS_TOKEN_URL}/${code}`;
return fetch(accessTokenUrl)
.then((res) => res.json())
- .then((data) => {
+ .then((data: { error }) => {
if (data.error) {
throw new Error(data.error);
}
diff --git a/package.json b/package.json
index 2987e56ec..097af5188 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "CATcher",
- "version": "3.4.4",
+ "version": "3.4.5",
"main": "main.js",
"scripts": {
"postinstall": "npm run postinstall:electron && electron-builder install-app-deps",
@@ -40,16 +40,18 @@
}
},
"dependencies": {
- "@angular/animations": "^8.2.14",
- "@angular/cdk": "^7.3.7",
- "@angular/common": "^8.2.14",
- "@angular/compiler": "^8.2.14",
- "@angular/core": "^8.2.14",
- "@angular/forms": "^8.2.14",
- "@angular/material": "^7.3.7",
- "@angular/platform-browser": "^8.2.14",
- "@angular/platform-browser-dynamic": "^8.2.14",
- "@angular/router": "^8.2.14",
+ "@angular/animations": "^10.2.5",
+ "@angular/cdk": "^10.2.7",
+ "@angular/common": "^10.2.5",
+ "@angular/compiler": "^10.2.5",
+ "@angular/core": "^10.2.5",
+ "@angular/forms": "^10.2.5",
+ "@angular/localize": "^10.2.5",
+ "@angular/material": "^10.2.7",
+ "@angular/platform-browser": "^10.2.5",
+ "@angular/platform-browser-dynamic": "^10.2.5",
+ "@angular/router": "^10.2.5",
+ "@github/markdown-toolbar-element": "^2.1.1",
"@octokit/rest": "^16.37.0",
"ajv": "^6.11.0",
"apollo-angular": "^1.9.1",
@@ -67,20 +69,20 @@
"graphql-tag": "2.11.0",
"karma-spec-reporter": "0.0.32",
"moment": "^2.24.0",
- "ngx-markdown": "^8.2.1",
- "ngx-mat-select-search": "^1.8.0",
- "node-fetch": "^2.6.0",
- "rxjs": "6.5.3",
- "tslib": "^1.9.0",
+ "ngx-markdown": "^9.1.1",
+ "ngx-mat-select-search": "^3.3.3",
+ "node-fetch": "^2.6.8",
+ "rxjs": "6.6.7",
+ "tslib": "^2.0.0",
"uuid": "7.0.3",
- "zone.js": "~0.9.1"
+ "zone.js": "~0.10.2"
},
"devDependencies": {
- "@angular-devkit/build-angular": "~0.803.29",
- "@angular/cli": "^8.3.29",
- "@angular/compiler-cli": "^8.2.14",
- "@angular/language-service": "^8.2.14",
- "@graphql-codegen/cli": "1.17.7",
+ "@angular-devkit/build-angular": "~0.1002.4",
+ "@angular/cli": "^10.2.4",
+ "@angular/compiler-cli": "^10.2.5",
+ "@angular/language-service": "^10.2.5",
+ "@graphql-codegen/cli": "^2.6.4",
"@graphql-codegen/typescript": "1.17.7",
"@graphql-codegen/typescript-document-nodes": "1.17.7",
"@graphql-codegen/typescript-operations": "^1.18.4",
@@ -91,30 +93,30 @@
"@types/jasminewd2": "2.0.8",
"@types/node": "^15.6.1",
"angular-cli-ghpages": "^1.0.0-rc.2",
- "codelyzer": "^5.0.1",
+ "codelyzer": "^5.1.2",
"electron": "11.4.8",
"electron-builder": "22.2.0",
"electron-reload": "1.5.0",
"graphql-codegen-fragment-matcher": "^0.18.2",
"husky": "^4.2.5",
"jasmine": "^3.9.0",
- "jasmine-core": "3.5.0",
- "jasmine-spec-reporter": "4.2.1",
- "karma": "^4.4.1",
- "karma-chrome-launcher": "3.1.0",
- "karma-coverage-istanbul-reporter": "2.1.1",
+ "jasmine-core": "~3.5.0",
+ "jasmine-spec-reporter": "~5.0.0",
+ "karma": "~5.0.0",
+ "karma-chrome-launcher": "~3.1.0",
+ "karma-coverage-istanbul-reporter": "~3.0.2",
"karma-firefox-launcher": "^2.1.1",
- "karma-jasmine": "3.1.0",
- "karma-jasmine-html-reporter": "1.5.1",
+ "karma-jasmine": "~4.0.0",
+ "karma-jasmine-html-reporter": "^1.5.0",
"npm-run-all": "4.1.5",
"prettier": "2.2.1",
"pretty-quick": "^3.1.1",
- "protractor": "5.4.2",
+ "protractor": "~7.0.0",
"scuri": "^0.9.4",
"ts-node": "^7.0.1",
- "tslint": "5.20.1",
+ "tslint": "~6.1.0",
"tslint-config-prettier": "^1.18.0",
- "typescript": "3.5.3",
+ "typescript": "4.0.8",
"wait-on": "3.3.0",
"webdriver-manager": "12.1.7"
}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index f9ff2842a..42419f9bf 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -13,12 +13,12 @@ export class AppComponent implements AfterViewInit {
NOT_CONNECTED_ERROR: Error = new Error('You are not connected to the internet.');
constructor(public electronService: ElectronService, logger: LoggingService, public errorHandlingService: ErrorHandlingService) {
- logger.info('AppConfig', AppConfig);
+ logger.info('AppComponent: AppConfig', AppConfig);
if (electronService.isElectron()) {
- logger.info('Mode electron');
+ logger.info('AppComponent: Mode electron');
} else {
- logger.info('Mode web');
+ logger.info('AppComponent: Mode web');
}
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 5989b1162..b86441cc8 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -94,8 +94,7 @@ import { SharedModule } from './shared/shared.module';
useClass: ErrorHandlingService
}
],
- bootstrap: [AppComponent],
- entryComponents: [UserConfirmationComponent, SessionFixConfirmationComponent, LabelDefinitionPopupComponent]
+ bootstrap: [AppComponent]
})
export class AppModule {
constructor(
diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts
index 31e5a1d79..5195127d2 100644
--- a/src/app/auth/auth.component.ts
+++ b/src/app/auth/auth.component.ts
@@ -1,7 +1,7 @@
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, Subscription } from 'rxjs';
-import { filter, flatMap, map } from 'rxjs/operators';
+import { filter, map, mergeMap } from 'rxjs/operators';
import { AppConfig } from '../../environments/environment';
import { GithubUser } from '../core/models/github-user.model';
import { ApplicationService } from '../core/services/application.service';
@@ -72,7 +72,7 @@ export class AuthComponent implements OnInit, OnDestroy {
// runs upon receiving oauthCode from the redirect
this.authService.changeAuthState(AuthState.AwaitingAuthentication);
this.restoreOrgDetailsFromLocalStorage();
- this.logger.info('Obtained authorisation code from Github');
+ this.logger.info('AuthComponent: Obtained authorisation code from Github');
this.fetchAccessToken(oauthCode, state);
}
}
@@ -84,11 +84,11 @@ export class AuthComponent implements OnInit, OnDestroy {
*/
fetchAccessToken(oauthCode: string, state: string) {
if (!this.authService.isReturnedStateSame(state)) {
- this.logger.info(`Received incorrect state ${state}, continue waiting for correct state`);
+ this.logger.info(`AuthComponent: Received incorrect state ${state}, continue waiting for correct state`);
return;
}
- this.logger.info('Retrieving access token from Github');
+ this.logger.info('AuthComponent: Retrieving access token from Github');
const accessTokenUrl = `${AppConfig.accessTokenUrl}/${oauthCode}/client_id/${AppConfig.clientId}`;
fetch(accessTokenUrl)
@@ -98,10 +98,10 @@ export class AuthComponent implements OnInit, OnDestroy {
throw new Error(data.error);
}
this.authService.storeOAuthAccessToken(data.token);
- this.logger.info('Sucessfully obtained access token');
+ this.logger.info('AuthComponent: Sucessfully obtained access token');
})
.catch((err) => {
- this.logger.info(`Error in data fetched from access token URL: ${err}`);
+ this.logger.info(`AuthComponent: Error in data fetched from access token URL: ${err}`);
this.errorHandlingService.handleError(err);
this.authService.changeAuthState(AuthState.NotAuthenticated);
});
@@ -189,7 +189,7 @@ export class AuthComponent implements OnInit, OnDestroy {
this.accessTokenSubscription = this.authService.accessToken
.pipe(
filter((token: string) => !!token),
- flatMap(() => this.userService.getAuthenticatedUser())
+ mergeMap(() => this.userService.getAuthenticatedUser())
)
.subscribe((user: GithubUser) => {
this.ngZone.run(() => {
diff --git a/src/app/auth/auth.module.ts b/src/app/auth/auth.module.ts
index 1b7cdb094..901bc3916 100644
--- a/src/app/auth/auth.module.ts
+++ b/src/app/auth/auth.module.ts
@@ -10,7 +10,6 @@ import { SessionSelectionComponent } from './session-selection/session-selection
@NgModule({
imports: [AuthRoutingModule, SharedModule, CommonModule],
- declarations: [AuthComponent, ProfilesComponent, JsonParseErrorDialogComponent, ConfirmLoginComponent, SessionSelectionComponent],
- entryComponents: [JsonParseErrorDialogComponent]
+ declarations: [AuthComponent, ProfilesComponent, JsonParseErrorDialogComponent, ConfirmLoginComponent, SessionSelectionComponent]
})
export class AuthModule {}
diff --git a/src/app/auth/confirm-login/confirm-login.component.ts b/src/app/auth/confirm-login/confirm-login.component.ts
index b4350d800..71011d0cb 100644
--- a/src/app/auth/confirm-login/confirm-login.component.ts
+++ b/src/app/auth/confirm-login/confirm-login.component.ts
@@ -1,6 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
-import { flatMap } from 'rxjs/operators';
+import { mergeMap } from 'rxjs/operators';
import { AuthService, AuthState } from '../../core/services/auth.service';
import { ElectronService } from '../../core/services/electron.service';
import { ErrorHandlingService } from '../../core/services/error-handling.service';
@@ -37,7 +37,7 @@ export class ConfirmLoginComponent implements OnInit {
}
logIntoAnotherAccount() {
- this.logger.info('Logging into another account');
+ this.logger.info('ConfirmLoginComponent: Logging into another account');
this.electronService.clearCookies();
this.authService.startOAuthProcess();
}
@@ -60,8 +60,8 @@ export class ConfirmLoginComponent implements OnInit {
this.userService
.createUserModel(this.username)
.pipe(
- flatMap(() => this.phaseService.sessionSetup()),
- flatMap(() => this.githubEventService.setLatestChangeEvent())
+ mergeMap(() => this.phaseService.sessionSetup()),
+ mergeMap(() => this.githubEventService.setLatestChangeEvent())
)
.subscribe(
() => {
@@ -70,7 +70,7 @@ export class ConfirmLoginComponent implements OnInit {
(error) => {
this.authService.changeAuthState(AuthState.NotAuthenticated);
this.errorHandlingService.handleError(error);
- this.logger.info(`Completion of login process failed with an error: ${error}`);
+ this.logger.info(`ConfirmLoginComponent: Completion of login process failed with an error: ${error}`);
}
);
}
diff --git a/src/app/auth/profiles/json-parse-error-dialog/json-parse-error-dialog.component.ts b/src/app/auth/profiles/json-parse-error-dialog/json-parse-error-dialog.component.ts
index b297194f6..923d3cb02 100644
--- a/src/app/auth/profiles/json-parse-error-dialog/json-parse-error-dialog.component.ts
+++ b/src/app/auth/profiles/json-parse-error-dialog/json-parse-error-dialog.component.ts
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
-import { MatDialogRef } from '@angular/material';
+import { MatDialogRef } from '@angular/material/dialog';
import { ProfilesComponent } from '../profiles.component';
/**
diff --git a/src/app/auth/profiles/profiles.component.html b/src/app/auth/profiles/profiles.component.html
index ec5535f7f..74cac42e6 100644
--- a/src/app/auth/profiles/profiles.component.html
+++ b/src/app/auth/profiles/profiles.component.html
@@ -14,6 +14,7 @@
mat-icon-button
(click)="this.fileSelectorInitiation(fileInput)"
disableRipple="true"
+ matTooltip="Configure your custom settings"
(mousedown)="this.animationActivated = true"
(mouseleave)="this.animationActivated = false"
>
diff --git a/src/app/auth/profiles/profiles.component.ts b/src/app/auth/profiles/profiles.component.ts
index f10dec9a7..1abfe13a9 100644
--- a/src/app/auth/profiles/profiles.component.ts
+++ b/src/app/auth/profiles/profiles.component.ts
@@ -1,6 +1,6 @@
import { animate, state, style, transition, trigger } from '@angular/animations';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
-import { MatDialog } from '@angular/material';
+import { MatDialog } from '@angular/material/dialog';
import { isValidProfile, Profile } from '../../core/models/profile.model';
import { ErrorHandlingService } from '../../core/services/error-handling.service';
import { MALFORMED_PROFILES_ERROR, ProfileService } from '../../core/services/profile.service';
diff --git a/src/app/auth/session-selection/session-selection.component.ts b/src/app/auth/session-selection/session-selection.component.ts
index 59e8a62fd..fd825aba2 100644
--- a/src/app/auth/session-selection/session-selection.component.ts
+++ b/src/app/auth/session-selection/session-selection.component.ts
@@ -57,7 +57,7 @@ export class SessionSelectionComponent implements OnInit {
window.localStorage.setItem('dataRepo', dataRepo);
this.githubService.storeOrganizationDetails(org, dataRepo);
- this.logger.info(`Selected Settings Repo: ${sessionInformation}`);
+ this.logger.info(`SessionSelectionComponent: Selected Settings Repo: ${sessionInformation}`);
this.phaseService.storeSessionData().subscribe(
() => {
diff --git a/src/app/core/directives/form-disable-control.directive.ts b/src/app/core/directives/form-disable-control.directive.ts
index 911d131db..181ebdd1e 100644
--- a/src/app/core/directives/form-disable-control.directive.ts
+++ b/src/app/core/directives/form-disable-control.directive.ts
@@ -6,7 +6,7 @@ import { NgControl } from '@angular/forms';
})
export class FormDisableControlDirective {
@Input() set disableControl(condition: boolean) {
- condition ? this.ngControl.control.disable() : this.ngControl.control.enable();
+ condition ? this.ngControl.control?.disable() : this.ngControl.control?.enable();
}
constructor(private ngControl: NgControl) {}
diff --git a/src/app/core/guards/user-confirmation/user-confirmation.component.ts b/src/app/core/guards/user-confirmation/user-confirmation.component.ts
index 6f3ae9dd0..4070b5bdc 100644
--- a/src/app/core/guards/user-confirmation/user-confirmation.component.ts
+++ b/src/app/core/guards/user-confirmation/user-confirmation.component.ts
@@ -1,5 +1,5 @@
import { Component, Inject, OnInit } from '@angular/core';
-import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { CanDeactivateIssueGuard } from '../can-deactivate-issue-guard.service';
/**
diff --git a/src/app/core/models/issue.model.ts b/src/app/core/models/issue.model.ts
index 5511074e7..3a68e6bb0 100644
--- a/src/app/core/models/issue.model.ts
+++ b/src/app/core/models/issue.model.ts
@@ -28,7 +28,7 @@ export class Issue {
/** Fields derived from Labels */
severity: string;
type: string;
- responseTag?: string;
+ response?: string; // all instance of this should be renamed to response
duplicated?: boolean;
status?: string;
pending?: string;
@@ -52,6 +52,7 @@ export class Issue {
/** Fields for error messages during parsing of Github's issue description */
teamResponseError: boolean;
testerResponseError: boolean;
+ parseError: string;
/**
* Formats the text to create space at the end of the user input to prevent any issues with
@@ -117,7 +118,7 @@ export class Issue {
/** Fields derived from Labels */
this.severity = githubIssue.findLabel(GithubLabel.LABELS.severity);
this.type = githubIssue.findLabel(GithubLabel.LABELS.type);
- this.responseTag = githubIssue.findLabel(GithubLabel.LABELS.response);
+ this.response = githubIssue.findLabel(GithubLabel.LABELS.response);
this.duplicated = !!githubIssue.findLabel(GithubLabel.LABELS.duplicated, false);
this.status = githubIssue.findLabel(GithubLabel.LABELS.status);
this.pending = githubIssue.findLabel(GithubLabel.LABELS.pending);
@@ -136,6 +137,7 @@ export class Issue {
issue.assignees = githubIssue.assignees.map((assignee) => assignee.login);
issue.teamResponseError = template.parseFailure;
+ issue.parseError = template.parseError;
issue.issueComment = template.comment;
issue.teamResponse = template.teamResponse;
issue.duplicateOf = template.duplicateOf;
@@ -151,6 +153,7 @@ export class Issue {
issue.githubComments = githubIssue.comments;
issue.testerResponseError = testerResponseTemplate.parseFailure && teamAcceptedTemplate.parseFailure;
+ issue.parseError = testerResponseTemplate.parseError;
issue.teamAccepted = teamAcceptedTemplate.teamAccepted;
issue.issueComment = testerResponseTemplate.comment;
issue.teamResponse = testerResponseTemplate.teamResponse;
diff --git a/src/app/core/models/templates/section-parsers/issue-dispute-section-parser.model.ts b/src/app/core/models/templates/section-parsers/issue-dispute-section-parser.model.ts
index 554e19b13..fec1f6900 100644
--- a/src/app/core/models/templates/section-parsers/issue-dispute-section-parser.model.ts
+++ b/src/app/core/models/templates/section-parsers/issue-dispute-section-parser.model.ts
@@ -4,7 +4,7 @@ const { coroutine, everyCharUntil, optionalWhitespace, str } = require('arcsecon
const SECTION_TITLE_PREFIX = '## :question: ';
const TEAM_SAYS_HEADER = '### Team says:';
-const LINE_SEPARATOR = '-------------------';
+const LINE_SEPARATOR = '
';
export const IssueDisputeSectionParser = coroutine(function* () {
yield str(SECTION_TITLE_PREFIX);
diff --git a/src/app/core/models/templates/section-parsers/moderation-section-parser.model.ts b/src/app/core/models/templates/section-parsers/moderation-section-parser.model.ts
index 8b06501d9..20e7a7363 100644
--- a/src/app/core/models/templates/section-parsers/moderation-section-parser.model.ts
+++ b/src/app/core/models/templates/section-parsers/moderation-section-parser.model.ts
@@ -6,7 +6,7 @@ const { coroutine, everyCharUntil, lookAhead, optionalWhitespace, str, whitespac
const SECTION_TITLE_PREFIX = '## :question: ';
const DONE_CHECKBOX_DESCRIPTION = 'Done';
-const LINE_SEPARATOR = '-------------------';
+const LINE_SEPARATOR = '
';
export const DoneCheckboxParser = buildCheckboxParser(DONE_CHECKBOX_DESCRIPTION);
diff --git a/src/app/core/models/templates/section-parsers/tester-response-section-parser.model.ts b/src/app/core/models/templates/section-parsers/tester-response-section-parser.model.ts
index 9c7ef10db..c42ef42b1 100644
--- a/src/app/core/models/templates/section-parsers/tester-response-section-parser.model.ts
+++ b/src/app/core/models/templates/section-parsers/tester-response-section-parser.model.ts
@@ -18,7 +18,7 @@ const TEAM_CHOSE_PREFIX = 'Team chose ';
const TESTER_CHOSE_PREFIX = 'Originally ';
const DISAGREE_CHECKBOX_DESCRIPTION = 'I disagree';
const DISAGREEMENT_REASON_PREFIX = '**Reason for disagreement:** ';
-const LINE_SEPARATOR = '-------------------';
+const LINE_SEPARATOR = '
';
const DUPLICATE_STATUS_MESSAGE =
"Team chose to mark this issue as a duplicate of another issue (as explained in the _**Team's response**_ above)";
diff --git a/src/app/core/models/templates/template.model.ts b/src/app/core/models/templates/template.model.ts
index 02576535d..8df67538b 100644
--- a/src/app/core/models/templates/template.model.ts
+++ b/src/app/core/models/templates/template.model.ts
@@ -3,6 +3,7 @@ import { GithubComment } from '../github/github-comment.model';
export abstract class Template {
parser;
parseResult;
+ parseError: string;
parseFailure: boolean;
protected constructor(parser) {
@@ -14,9 +15,10 @@ export abstract class Template {
*/
findConformingComment(githubComments: GithubComment[]): GithubComment {
let templateConformingComment: GithubComment;
+ let parsed: any;
for (const comment of githubComments) {
- const parsed = this.parser.run(comment.body);
+ parsed = this.parser.run(comment.body);
if (!parsed.isError) {
this.parseResult = parsed.result;
templateConformingComment = comment;
@@ -26,6 +28,10 @@ export abstract class Template {
if (templateConformingComment === undefined) {
this.parseFailure = true;
+
+ if (parsed) {
+ this.parseError = parsed.error;
+ }
}
return templateConformingComment;
}
diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts
index 0f9e0d6ad..730236e90 100644
--- a/src/app/core/services/auth.service.ts
+++ b/src/app/core/services/auth.service.ts
@@ -61,6 +61,7 @@ export class AuthService {
}
reset(): void {
+ this.logger.info('AuthService: Clearing access token and setting AuthState to NotAuthenticated.');
this.accessToken.next(undefined);
this.changeAuthState(AuthState.NotAuthenticated);
this.ngZone.run(() => this.router.navigate(['']));
@@ -82,12 +83,14 @@ export class AuthService {
setTitleWithPhaseDetail(): void {
const appSetting = require('../../../../package.json');
const title = `${appSetting.name} ${appSetting.version} - ${this.phaseService.getPhaseDetail()}`;
+ this.logger.info(`AuthService: Setting Title as ${title}`);
this.titleService.setTitle(title);
}
setLandingPageTitle(): void {
const appSetting = require('../../../../package.json');
const title = `${appSetting.name} ${appSetting.version}`;
+ this.logger.info(`AuthService: Setting LandingPageTitle as ${title}`);
this.titleService.setTitle(title);
}
@@ -99,7 +102,7 @@ export class AuthService {
if (newAuthState === AuthState.Authenticated) {
const sessionId = generateSessionId();
this.issueService.setSessionId(sessionId);
- this.logger.info(`Successfully authenticated with session: ${sessionId}`);
+ this.logger.info(`AuthService: Successfully authenticated with session: ${sessionId}`);
}
this.authStateSource.next(newAuthState);
}
@@ -121,7 +124,7 @@ export class AuthService {
* Will start the Github OAuth web flow process.
*/
startOAuthProcess() {
- this.logger.info('Starting authentication');
+ this.logger.info('AuthService: Starting authentication');
const githubRepoPermission = this.phaseService.githubRepoPermissionLevel();
this.changeAuthState(AuthState.AwaitingAuthentication);
@@ -134,7 +137,7 @@ export class AuthService {
`${AppConfig.githubUrl}/login/oauth/authorize?client_id=${AppConfig.clientId}&scope=${githubRepoPermission},read:user&state=${this.state}`
)
);
- this.logger.info('Redirecting for Github authentication');
+ this.logger.info('AuthService: Redirecting for Github authentication');
}
}
diff --git a/src/app/core/services/dialog.service.ts b/src/app/core/services/dialog.service.ts
index cd0dc929b..ad26a6ef2 100644
--- a/src/app/core/services/dialog.service.ts
+++ b/src/app/core/services/dialog.service.ts
@@ -1,7 +1,9 @@
import { Injectable } from '@angular/core';
-import { MatDialog } from '@angular/material';
+import { FormGroup } from '@angular/forms';
+import { MatDialog } from '@angular/material/dialog';
import { LabelDefinitionPopupComponent } from '../../shared/label-definition-popup/label-definition-popup.component';
import { UserConfirmationComponent } from '../guards/user-confirmation/user-confirmation.component';
+import { Issue } from '../models/issue.model';
@Injectable({
providedIn: 'root'
@@ -27,4 +29,20 @@ export class DialogService {
}
});
}
+
+ checkIfFieldIsModified(form: FormGroup, initialField: string, formField: string, issue: Issue) {
+ const issueTitleInitialValue = issue[initialField] || '';
+ const isModified = form.get(formField).value !== issueTitleInitialValue;
+ return isModified;
+ }
+
+ performActionIfModified(isModified: boolean, actionIfModified: () => void, actionIfNotModified: () => void) {
+ if (isModified) {
+ // if the field has been edited, request user to confirm the cancellation
+ actionIfModified();
+ } else {
+ // if no changes have been made, simply cancel edit mode without getting confirmation
+ actionIfNotModified();
+ }
+ }
}
diff --git a/src/app/core/services/error-handling.service.ts b/src/app/core/services/error-handling.service.ts
index 3c7930236..e0f87ba42 100644
--- a/src/app/core/services/error-handling.service.ts
+++ b/src/app/core/services/error-handling.service.ts
@@ -1,6 +1,6 @@
import { HttpErrorResponse } from '@angular/common/http';
import { ErrorHandler, Injectable } from '@angular/core';
-import { MatSnackBar } from '@angular/material';
+import { MatSnackBar } from '@angular/material/snack-bar';
import { RequestError } from '@octokit/request-error';
import { FormErrorComponent } from '../../shared/error-toasters/form-error/form-error.component';
import { GeneralMessageErrorComponent } from '../../shared/error-toasters/general-message-error/general-message-error.component';
@@ -17,9 +17,9 @@ export class ErrorHandlingService implements ErrorHandler {
constructor(private snackBar: MatSnackBar, private logger: LoggingService) {}
handleError(error: HttpErrorResponse | Error | RequestError, actionCallback?: () => void) {
- this.logger.error(error);
+ this.logger.error('ErrorHandlingService: ' + error);
if (error instanceof Error) {
- this.logger.debug(this.cleanStack(error.stack));
+ this.logger.debug('ErrorHandlingService: ' + this.cleanStack(error.stack));
}
if (error instanceof HttpErrorResponse) {
this.handleHttpError(error, actionCallback);
diff --git a/src/app/core/services/github.service.ts b/src/app/core/services/github.service.ts
index 61268c248..76167fdf1 100644
--- a/src/app/core/services/github.service.ts
+++ b/src/app/core/services/github.service.ts
@@ -4,7 +4,7 @@ import { Apollo, QueryRef } from 'apollo-angular';
import { ApolloQueryResult } from 'apollo-client';
import { DocumentNode } from 'graphql';
import { forkJoin, from, Observable, of, throwError } from 'rxjs';
-import { catchError, filter, flatMap, map, throwIfEmpty } from 'rxjs/operators';
+import { catchError, filter, map, mergeMap, throwIfEmpty } from 'rxjs/operators';
import {
FetchIssue,
FetchIssueQuery,
@@ -75,11 +75,12 @@ export class GithubService {
return `Token ${accessToken}`;
},
log: {
- debug: (message, ...otherInfo) => this.logger.debug(message, ...otherInfo),
+ debug: (message, ...otherInfo) => this.logger.debug('GithubService: ' + message, ...otherInfo),
// Do not log info for HTTP response 304 due to repeated polling
- info: (message, ...otherInfo) => (/304 in \d+ms$/.test(message) ? undefined : this.logger.info(message, ...otherInfo)),
- warn: (message, ...otherInfo) => this.logger.warn(message, ...otherInfo),
- error: (message, ...otherInfo) => this.logger.error(message, ...otherInfo)
+ info: (message, ...otherInfo) =>
+ /304 in \d+ms$/.test(message) ? undefined : this.logger.info('GithubService: ' + message, ...otherInfo),
+ warn: (message, ...otherInfo) => this.logger.warn('GithubService: ' + message, ...otherInfo),
+ error: (message, ...otherInfo) => this.logger.error('GithubService: ' + message, ...otherInfo)
}
});
}
@@ -106,7 +107,7 @@ export class GithubService {
const graphqlFilter = issuesFilter.convertToGraphqlFilter();
return this.toFetchIssues(issuesFilter).pipe(
filter((toFetch) => toFetch),
- flatMap(() => {
+ mergeMap(() => {
return this.fetchGraphqlList(
FetchIssuesByTeam,
{
@@ -134,7 +135,7 @@ export class GithubService {
const graphqlFilter = issuesFilter.convertToGraphqlFilter();
return this.toFetchIssues(issuesFilter).pipe(
filter((toFetch) => toFetch),
- flatMap(() => {
+ mergeMap(() => {
return this.fetchGraphqlList(
FetchIssues,
{ owner: ORG_NAME, name: REPO, filter: graphqlFilter },
@@ -158,7 +159,7 @@ export class GithubService {
responseInFirstPage = response;
return getNumberOfPages(response);
}),
- flatMap((numOfPages: number) => {
+ mergeMap((numOfPages: number) => {
const apiCalls: Observable>[] = [];
for (let i = 2; i <= numOfPages; i++) {
apiCalls.push(this.getIssuesAPICall(filter, i));
@@ -227,7 +228,7 @@ export class GithubService {
const queryRef = this.issueQueryRefs.get(id);
return this.toFetchIssue(id).pipe(
filter((toFetch) => toFetch),
- flatMap(() => from(queryRef.refetch())),
+ mergeMap(() => from(queryRef.refetch())),
map((value: ApolloQueryResult) => {
return new GithubGraphqlIssue(value.data.repository.issue);
}),
@@ -434,6 +435,7 @@ export class GithubService {
}
reset(): void {
+ this.logger.info(`GithubService: Resetting issues cache`);
this.issuesCacheManager.clear();
this.issuesLastModifiedManager.clear();
this.issueQueryRefs.clear();
diff --git a/src/app/core/services/githubevent.service.ts b/src/app/core/services/githubevent.service.ts
index 9cc83dc7f..c085e45bf 100644
--- a/src/app/core/services/githubevent.service.ts
+++ b/src/app/core/services/githubevent.service.ts
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
-import { flatMap, map } from 'rxjs/operators';
+import { map, mergeMap } from 'rxjs/operators';
import { GithubService } from './github.service';
import { IssueService } from './issue.service';
@@ -37,7 +37,7 @@ export class GithubEventService {
*/
reloadPage(): Observable {
return this.githubService.fetchEventsForRepo().pipe(
- flatMap((response: any[]) => {
+ mergeMap((response: any[]) => {
if (response.length === 0) {
return of(false);
}
diff --git a/src/app/core/services/issue.service.ts b/src/app/core/services/issue.service.ts
index 1121c5028..26a81a15a 100644
--- a/src/app/core/services/issue.service.ts
+++ b/src/app/core/services/issue.service.ts
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, EMPTY, forkJoin, Observable, of, Subscription, throwError, timer } from 'rxjs';
-import { catchError, exhaustMap, finalize, flatMap, map } from 'rxjs/operators';
+import { catchError, exhaustMap, finalize, map, mergeMap } from 'rxjs/operators';
import { IssueComment } from '../models/comment.model';
import { GithubComment } from '../models/github/github-comment.model';
import RestGithubIssueFilter from '../models/github/github-issue-filter.model';
@@ -146,7 +146,7 @@ export class IssueService {
return this.createIssueModel(response);
}),
catchError((err) => {
- this.logger.error(err); // Log full details of error first
+ this.logger.error('IssueService: ', err); // Log full details of error first
return throwError(err.response.data.message); // More readable error message
})
);
@@ -154,7 +154,7 @@ export class IssueService {
updateIssueWithComment(issue: Issue, issueComment: IssueComment): Observable {
return this.githubService.updateIssueComment(issueComment).pipe(
- flatMap((updatedComment: GithubComment) => {
+ mergeMap((updatedComment: GithubComment) => {
issue.githubComments = [updatedComment, ...issue.githubComments.filter((c) => c.id !== updatedComment.id)];
return this.updateIssue(issue);
})
@@ -192,7 +192,7 @@ export class IssueService {
createTeamResponse(issue: Issue): Observable {
const teamResponse = issue.createGithubTeamResponse();
return this.githubService.createIssueComment(issue.id, teamResponse).pipe(
- flatMap((githubComment: GithubComment) => {
+ mergeMap((githubComment: GithubComment) => {
issue.githubComments = [githubComment, ...issue.githubComments.filter((c) => c.id !== githubComment.id)];
return this.updateIssue(issue);
})
@@ -422,8 +422,8 @@ export class IssueService {
result.push(this.createLabel('type', issue.type));
}
- if (issue.responseTag) {
- result.push(this.createLabel('response', issue.responseTag));
+ if (issue.response) {
+ result.push(this.createLabel('response', issue.response));
}
if (issue.duplicated) {
@@ -456,18 +456,29 @@ export class IssueService {
}
private createIssueModel(githubIssue: GithubIssue): Issue {
+ let issue: Issue;
+
switch (this.phaseService.currentPhase) {
case Phase.phaseBugReporting:
- return Issue.createPhaseBugReportingIssue(githubIssue);
+ issue = Issue.createPhaseBugReportingIssue(githubIssue);
+ break;
case Phase.phaseTeamResponse:
- return Issue.createPhaseTeamResponseIssue(githubIssue, this.dataService.getTeam(this.extractTeamIdFromGithubIssue(githubIssue)));
+ issue = Issue.createPhaseTeamResponseIssue(githubIssue, this.dataService.getTeam(this.extractTeamIdFromGithubIssue(githubIssue)));
+ break;
case Phase.phaseTesterResponse:
- return Issue.createPhaseTesterResponseIssue(githubIssue);
+ issue = Issue.createPhaseTesterResponseIssue(githubIssue);
+ break;
case Phase.phaseModeration:
- return Issue.createPhaseModerationIssue(githubIssue, this.dataService.getTeam(this.extractTeamIdFromGithubIssue(githubIssue)));
+ issue = Issue.createPhaseModerationIssue(githubIssue, this.dataService.getTeam(this.extractTeamIdFromGithubIssue(githubIssue)));
+ break;
default:
return;
}
+
+ if (issue.parseError) {
+ this.logger.error('IssueService: ' + issue.parseError);
+ }
+ return issue;
}
setIssueTeamFilter(filterValue: string) {
diff --git a/src/app/core/services/label.service.ts b/src/app/core/services/label.service.ts
index 06b7125d6..50c56fe8b 100644
--- a/src/app/core/services/label.service.ts
+++ b/src/app/core/services/label.service.ts
@@ -1,9 +1,10 @@
import { Injectable } from '@angular/core';
import { Observable, pipe, UnaryFunction } from 'rxjs';
-import { flatMap, map } from 'rxjs/operators';
+import { map, mergeMap } from 'rxjs/operators';
import { GithubLabel } from '../models/github/github-label.model';
import { Label } from '../models/label.model';
import { GithubService } from './github.service';
+import { LoggingService } from './logging.service';
/* The threshold to decide if color is dark or light.
A higher threshold value will result in more colors determined to be "dark".
@@ -147,7 +148,7 @@ export class LabelService {
type: LabelService.typeLabels
};
- constructor(private githubService: GithubService) {}
+ constructor(private githubService: GithubService, private logger: LoggingService) {}
public static getRequiredLabelsAsArray(needAllLabels: boolean): Label[] {
let requiredLabels: Label[] = [];
@@ -175,7 +176,7 @@ export class LabelService {
* with the remote repository.
*/
syncLabels(needAllLabels: boolean): UnaryFunction, Observable> {
- return pipe(flatMap(() => this.synchronizeRemoteLabels(needAllLabels)));
+ return pipe(mergeMap(() => this.synchronizeRemoteLabels(needAllLabels)));
}
/**
@@ -202,10 +203,11 @@ export class LabelService {
return LabelService.severityLabels;
case 'type':
return LabelService.typeLabels;
- case 'responseTag':
+ // case 'responseTag':
case 'response':
return LabelService.responseLabels;
}
+ this.logger.info(`LabelService: Unfiltered Attribute ${attributeName} in getLabelList`);
}
/**
@@ -218,9 +220,10 @@ export class LabelService {
return DISPLAY_NAME_SEVERITY;
case 'type':
return DISPLAY_NAME_BUG_TYPE;
- case 'responseTag':
+ case 'response':
return DISPLAY_NAME_RESPONSE;
}
+ this.logger.info(`LabelService: Unfiltered Attribute ${attributeName} in getLabelTitle`);
}
/**
@@ -230,6 +233,8 @@ export class LabelService {
*/
getColorOfLabel(labelCategory: LabelCategory, labelValue: string): string {
if (labelValue === '' || !LabelService.allLabelArrays[labelCategory]) {
+ this.logger.info(`LabelService: Unfiltered Attribute, ${labelValue}: ${labelCategory} in getColorOfLabel`);
+
return COLOR_WHITE;
}
diff --git a/src/app/core/services/mocks/mock.auth.service.ts b/src/app/core/services/mocks/mock.auth.service.ts
index 4ebc2a276..b70e2b2d9 100644
--- a/src/app/core/services/mocks/mock.auth.service.ts
+++ b/src/app/core/services/mocks/mock.auth.service.ts
@@ -75,7 +75,7 @@ export class MockAuthService {
if (newAuthState === AuthState.Authenticated) {
const sessionId = `${Date.now()}-${uuid()}`;
this.issueService.setSessionId(sessionId);
- this.logger.info(`Successfully authenticated with session: ${sessionId}`);
+ this.logger.info(`MockAuthService: Successfully authenticated with session: ${sessionId}`);
}
this.authStateSource.next(newAuthState);
}
diff --git a/src/app/core/services/mocks/mock.issue.service.ts b/src/app/core/services/mocks/mock.issue.service.ts
index 0b4c01ee4..09868b073 100644
--- a/src/app/core/services/mocks/mock.issue.service.ts
+++ b/src/app/core/services/mocks/mock.issue.service.ts
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, forkJoin, Observable, of, Subscription } from 'rxjs';
-import { catchError, flatMap, map } from 'rxjs/operators';
+import { catchError, map, mergeMap } from 'rxjs/operators';
import { generateIssueWithRandomData } from '../../../../../tests/constants/githubissue.constants';
import { IssueComment } from '../../models/comment.model';
import { GithubComment } from '../../models/github/github-comment.model';
@@ -107,7 +107,7 @@ export class MockIssueService {
updateIssueWithComment(issue: Issue, issueComment: IssueComment): Observable {
return this.githubService.updateIssueComment(issueComment).pipe(
- flatMap((updatedComment: GithubComment) => {
+ mergeMap((updatedComment: GithubComment) => {
issue.githubComments = [updatedComment, ...issue.githubComments.filter((c) => c.id !== updatedComment.id)];
return this.updateIssue(issue);
})
@@ -145,7 +145,7 @@ export class MockIssueService {
createTeamResponse(issue: Issue): Observable {
const teamResponse = issue.createGithubTeamResponse();
return this.githubService.createIssueComment(issue.id, teamResponse).pipe(
- flatMap((githubComment: GithubComment) => {
+ mergeMap((githubComment: GithubComment) => {
issue.githubComments = [githubComment, ...issue.githubComments.filter((c) => c.id !== githubComment.id)];
return this.updateIssue(issue);
})
@@ -299,8 +299,8 @@ export class MockIssueService {
result.push(this.createLabel('type', issue.type));
}
- if (issue.responseTag) {
- result.push(this.createLabel('response', issue.responseTag));
+ if (issue.response) {
+ result.push(this.createLabel('response', issue.response));
}
if (issue.duplicated) {
diff --git a/src/app/core/services/phase.service.ts b/src/app/core/services/phase.service.ts
index a8e0b247c..ada3c9bb4 100644
--- a/src/app/core/services/phase.service.ts
+++ b/src/app/core/services/phase.service.ts
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { Observable, pipe } from 'rxjs';
-import { flatMap, map, retry, tap } from 'rxjs/operators';
+import { map, mergeMap, retry, tap } from 'rxjs/operators';
import { throwIfFalse } from '../../shared/lib/custom-ops';
import { Phase } from '../models/phase.model';
import { assertSessionDataIntegrity, SessionData } from '../models/session.model';
@@ -135,7 +135,7 @@ export class PhaseService {
return this.fetchSessionData().pipe(
assertSessionDataIntegrity(),
- flatMap((sessionData: SessionData) => {
+ mergeMap((sessionData: SessionData) => {
this.updateSessionParameters(sessionData);
return this.verifySessionAvailability(sessionData);
}),
diff --git a/src/app/core/services/repo-creator.service.ts b/src/app/core/services/repo-creator.service.ts
index 032063c6d..d484782fe 100644
--- a/src/app/core/services/repo-creator.service.ts
+++ b/src/app/core/services/repo-creator.service.ts
@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
-import { MatDialog, MatDialogRef } from '@angular/material';
+import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Observable, of, pipe, UnaryFunction } from 'rxjs';
-import { flatMap, tap } from 'rxjs/operators';
+import { mergeMap, tap } from 'rxjs/operators';
import { Phase } from '../models/phase.model';
import { UserRole } from '../models/user.model';
import { GithubService } from './github.service';
@@ -29,7 +29,7 @@ export class RepoCreatorService {
phaseRepo: string
): UnaryFunction, Observable> {
return pipe(
- flatMap((isRepoPresent: boolean) => {
+ mergeMap((isRepoPresent: boolean) => {
if (!isRepoPresent && currentPhase === Phase.phaseBugReporting) {
return this.openRepoCreationConfirmation(phaseRepo);
} else {
@@ -85,13 +85,13 @@ export class RepoCreatorService {
*/
public attemptRepoCreation(phaseRepo: string): UnaryFunction, Observable> {
return pipe(
- flatMap((repoCreationPermission: boolean | null) => {
+ mergeMap((repoCreationPermission: boolean | null) => {
if (repoCreationPermission === null) {
// No Session Fix Necessary
return of(null);
} else {
this.githubService.createRepository(phaseRepo);
- return new Observable((subscriber) => {
+ return new Observable((subscriber) => {
setTimeout(() => subscriber.next(true), 1000);
});
}
@@ -106,7 +106,7 @@ export class RepoCreatorService {
*/
public verifyRepoCreation(phaseOwner: string, phaseRepo: string): UnaryFunction, Observable> {
return pipe(
- flatMap((isFixAttempted: boolean | null) => {
+ mergeMap((isFixAttempted: boolean | null) => {
if (!isFixAttempted) {
// If no fix has been attempted, there is no need to verify fix outcome.
return of(true);
diff --git a/src/app/core/services/session-fix-confirmation/session-fix-confirmation.component.ts b/src/app/core/services/session-fix-confirmation/session-fix-confirmation.component.ts
index c659a7c6f..57057c6fd 100644
--- a/src/app/core/services/session-fix-confirmation/session-fix-confirmation.component.ts
+++ b/src/app/core/services/session-fix-confirmation/session-fix-confirmation.component.ts
@@ -1,5 +1,5 @@
import { Component, Inject, OnInit } from '@angular/core';
-import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
export interface RepositoryData {
user: string;
diff --git a/src/app/core/services/user.service.ts b/src/app/core/services/user.service.ts
index a8c66159d..7e8eddf53 100644
--- a/src/app/core/services/user.service.ts
+++ b/src/app/core/services/user.service.ts
@@ -6,6 +6,7 @@ import { Team } from '../models/team.model';
import { User, UserRole } from '../models/user.model';
import { DataService } from './data.service';
import { GithubService } from './github.service';
+import { LoggingService } from './logging.service';
@Injectable({
providedIn: 'root'
@@ -17,7 +18,7 @@ import { GithubService } from './github.service';
export class UserService {
public currentUser: User;
- constructor(private githubService: GithubService, private dataService: DataService) {}
+ constructor(private githubService: GithubService, private dataService: DataService, private logger: LoggingService) {}
/**
* Get the authenticated user if it exist.
@@ -42,6 +43,7 @@ export class UserService {
}
reset() {
+ this.logger.info('UserService: Clearing current user');
this.currentUser = undefined;
}
diff --git a/src/app/shared/action-toasters/action-toasters.module.ts b/src/app/shared/action-toasters/action-toasters.module.ts
index 7cc53f6b2..a1cd51831 100644
--- a/src/app/shared/action-toasters/action-toasters.module.ts
+++ b/src/app/shared/action-toasters/action-toasters.module.ts
@@ -6,7 +6,6 @@ import { UndoActionComponent } from './undo-action/undo-action.component';
@NgModule({
imports: [CommonModule, MaterialModule],
declarations: [UndoActionComponent],
- exports: [UndoActionComponent],
- entryComponents: [UndoActionComponent]
+ exports: [UndoActionComponent]
})
export class ActionToasterModule {}
diff --git a/src/app/shared/action-toasters/undo-action/undo-action.component.ts b/src/app/shared/action-toasters/undo-action/undo-action.component.ts
index dbd34dc1c..418edeb05 100644
--- a/src/app/shared/action-toasters/undo-action/undo-action.component.ts
+++ b/src/app/shared/action-toasters/undo-action/undo-action.component.ts
@@ -1,5 +1,5 @@
import { Component, Inject } from '@angular/core';
-import { MAT_SNACK_BAR_DATA, MatSnackBarRef } from '@angular/material';
+import { MatSnackBarRef, MAT_SNACK_BAR_DATA } from '@angular/material/snack-bar';
@Component({
selector: 'app-undo-action',
diff --git a/src/app/shared/comment-editor/comment-editor.component.html b/src/app/shared/comment-editor/comment-editor.component.html
index b9a7e9364..7fa8cd4db 100644
--- a/src/app/shared/comment-editor/comment-editor.component.html
+++ b/src/app/shared/comment-editor/comment-editor.component.html
@@ -9,10 +9,12 @@
(drop)="onDrop($event)"
(dragover)="enableFileDrop($event)"
>
+
-
+
Response
-
- {{ data.responseTag || '-' }}
+
+ {{ data.response || '-' }}
diff --git a/src/app/shared/view-issue/new-team-response/conflict-dialog/conflict-dialog.component.ts b/src/app/shared/view-issue/new-team-response/conflict-dialog/conflict-dialog.component.ts
index 66f515444..17436904a 100644
--- a/src/app/shared/view-issue/new-team-response/conflict-dialog/conflict-dialog.component.ts
+++ b/src/app/shared/view-issue/new-team-response/conflict-dialog/conflict-dialog.component.ts
@@ -1,5 +1,6 @@
import { Component, Inject } from '@angular/core';
-import { MAT_DIALOG_DATA, MatDialogRef, MatTabChangeEvent } from '@angular/material';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatTabChangeEvent } from '@angular/material/tabs';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { Issue } from '../../../../core/models/issue.model';
import { IssueService } from '../../../../core/services/issue.service';
diff --git a/src/app/shared/view-issue/new-team-response/new-team-response.component.html b/src/app/shared/view-issue/new-team-response/new-team-response.component.html
index 82bee3bfb..83db54f65 100644
--- a/src/app/shared/view-issue/new-team-response/new-team-response.component.html
+++ b/src/app/shared/view-issue/new-team-response/new-team-response.component.html
@@ -76,8 +76,8 @@
diff --git a/src/app/shared/view-issue/new-team-response/new-team-response.component.ts b/src/app/shared/view-issue/new-team-response/new-team-response.component.ts
index e35b23566..ba03db678 100644
--- a/src/app/shared/view-issue/new-team-response/new-team-response.component.ts
+++ b/src/app/shared/view-issue/new-team-response/new-team-response.component.ts
@@ -1,9 +1,9 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, NgForm, Validators } from '@angular/forms';
-import { MatDialog } from '@angular/material';
import { MatCheckboxChange } from '@angular/material/checkbox';
+import { MatDialog } from '@angular/material/dialog';
import { Observable, ReplaySubject, Subject, throwError } from 'rxjs';
-import { finalize, first, flatMap, map, takeUntil } from 'rxjs/operators';
+import { finalize, first, map, mergeMap, takeUntil } from 'rxjs/operators';
import { IssueComment } from '../../../core/models/comment.model';
import { Conflict } from '../../../core/models/conflict/conflict.model';
import { Issue, STATUS } from '../../../core/models/issue.model';
@@ -60,7 +60,7 @@ export class NewTeamResponseComponent implements OnInit, OnDestroy {
description: [''],
severity: [this.issue.severity, Validators.required],
type: [this.issue.type, Validators.required],
- responseTag: [this.issue.responseTag, Validators.required],
+ response: [this.issue.response, Validators.required],
assignees: [this.issue.assignees.map((a) => a.toLowerCase())],
duplicated: [false],
duplicateOf: ['']
@@ -108,7 +108,7 @@ export class NewTeamResponseComponent implements OnInit, OnDestroy {
this.isSafeToSubmit()
.pipe(
- flatMap((isSaveToSubmit: boolean) => {
+ mergeMap((isSaveToSubmit: boolean) => {
const newCommentDescription = latestIssue.createGithubTeamResponse();
if (isSaveToSubmit) {
return this.issueService.createTeamResponse(latestIssue);
@@ -162,12 +162,12 @@ export class NewTeamResponseComponent implements OnInit, OnDestroy {
clone.severity = duplicatedIssue.severity;
clone.type = duplicatedIssue.type;
clone.assignees = duplicatedIssue.assignees;
- clone.responseTag = duplicatedIssue.responseTag;
+ clone.response = duplicatedIssue.response;
} else {
clone.severity = this.severity.value;
clone.type = this.type.value;
clone.assignees = this.assignees.value;
- clone.responseTag = this.responseTag.value;
+ clone.response = this.responseTag.value;
}
clone.status = STATUS.Done;
clone.teamResponse = Issue.updateTeamResponse(this.description.value);
diff --git a/src/app/shared/view-issue/new-team-response/new-team-response.module.ts b/src/app/shared/view-issue/new-team-response/new-team-response.module.ts
index 46cfc7922..f8bda2011 100644
--- a/src/app/shared/view-issue/new-team-response/new-team-response.module.ts
+++ b/src/app/shared/view-issue/new-team-response/new-team-response.module.ts
@@ -20,7 +20,6 @@ import { NewTeamResponseComponent } from './new-team-response.component';
LabelDropdownModule,
MarkdownModule.forChild(),
NgxMatSelectSearchModule
- ],
- entryComponents: [ConflictDialogComponent]
+ ]
})
export class NewTeamResponseModule {}
diff --git a/src/app/shared/view-issue/team-response/team-response.component.html b/src/app/shared/view-issue/team-response/team-response.component.html
index 2fe4f6ff7..122940f32 100644
--- a/src/app/shared/view-issue/team-response/team-response.component.html
+++ b/src/app/shared/view-issue/team-response/team-response.component.html
@@ -49,7 +49,14 @@
Team's Response
>
{{ submitButtonText }}
-
diff --git a/src/app/shared/view-issue/team-response/team-response.component.ts b/src/app/shared/view-issue/team-response/team-response.component.ts
index 3f8a03024..e2fdc5039 100644
--- a/src/app/shared/view-issue/team-response/team-response.component.ts
+++ b/src/app/shared/view-issue/team-response/team-response.component.ts
@@ -1,11 +1,12 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, NgForm } from '@angular/forms';
-import { MatDialog } from '@angular/material';
+import { MatDialog } from '@angular/material/dialog';
import { Observable, throwError } from 'rxjs';
-import { finalize, flatMap, map } from 'rxjs/operators';
+import { finalize, map, mergeMap } from 'rxjs/operators';
import { IssueComment } from '../../../core/models/comment.model';
import { Conflict } from '../../../core/models/conflict/conflict.model';
import { Issue, STATUS } from '../../../core/models/issue.model';
+import { DialogService } from '../../../core/services/dialog.service';
import { ErrorHandlingService } from '../../../core/services/error-handling.service';
import { IssueService } from '../../../core/services/issue.service';
import { PermissionService } from '../../../core/services/permission.service';
@@ -30,13 +31,19 @@ export class TeamResponseComponent implements OnInit {
@Output() issueUpdated = new EventEmitter();
@Output() updateEditState = new EventEmitter();
+ // Messages for the modal popup window upon cancelling edit
+ private readonly cancelEditModalMessages = ['Do you wish to cancel?', 'Your changes will be discarded.'];
+ private readonly yesButtonModalMessage = 'Cancel';
+ private readonly noButtonModalMessage = 'Continue editing';
+
constructor(
private issueService: IssueService,
private formBuilder: FormBuilder,
private errorHandlingService: ErrorHandlingService,
private permissions: PermissionService,
private dialog: MatDialog,
- private phaseService: PhaseService
+ private phaseService: PhaseService,
+ private dialogService: DialogService
) {}
ngOnInit() {
@@ -67,7 +74,7 @@ export class TeamResponseComponent implements OnInit {
this.isSafeToUpdate()
.pipe(
- flatMap((isSaveToUpdate: boolean) => {
+ mergeMap((isSaveToUpdate: boolean) => {
if (isSaveToUpdate || this.submitButtonText === SUBMIT_BUTTON_TEXT.OVERWRITE) {
return this.issueService.updateIssueWithComment(updatedIssue, updatedIssueComment);
} else if (this.isUpdatingDeletedResponse()) {
@@ -141,6 +148,29 @@ export class TeamResponseComponent implements OnInit {
});
}
+ openCancelDialogIfModified(): void {
+ const isModified = this.dialogService.checkIfFieldIsModified(this.responseForm, 'teamResponse', 'description', this.issue);
+ this.dialogService.performActionIfModified(
+ isModified,
+ () => this.openCancelDialog(),
+ () => this.cancelEditMode()
+ );
+ }
+
+ openCancelDialog(): void {
+ const dialogRef = this.dialogService.openUserConfirmationModal(
+ this.cancelEditModalMessages,
+ this.yesButtonModalMessage,
+ this.noButtonModalMessage
+ );
+
+ dialogRef.afterClosed().subscribe((res) => {
+ if (res) {
+ this.cancelEditMode();
+ }
+ });
+ }
+
private getUpdatedIssue(): Issue {
const clone = this.issue.clone(this.phaseService.currentPhase);
clone.teamResponse = Issue.updateTeamResponse(this.responseForm.get('description').value);
diff --git a/src/app/shared/view-issue/tester-response/conflict-dialog/conflict-dialog.component.ts b/src/app/shared/view-issue/tester-response/conflict-dialog/conflict-dialog.component.ts
index c66634113..a965164c0 100644
--- a/src/app/shared/view-issue/tester-response/conflict-dialog/conflict-dialog.component.ts
+++ b/src/app/shared/view-issue/tester-response/conflict-dialog/conflict-dialog.component.ts
@@ -1,5 +1,5 @@
import { Component, Inject } from '@angular/core';
-import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { Conflict } from '../../../../core/models/conflict/conflict.model';
import { TesterResponse } from '../../../../core/models/tester-response.model';
diff --git a/src/app/shared/view-issue/tester-response/tester-response.component.html b/src/app/shared/view-issue/tester-response/tester-response.component.html
index ec52ef1d3..ab4d976ff 100644
--- a/src/app/shared/view-issue/tester-response/tester-response.component.html
+++ b/src/app/shared/view-issue/tester-response/tester-response.component.html
@@ -80,7 +80,7 @@ Tester's Response
*ngIf="!this.isNewResponse()"
mat-stroked-button
color="warn"
- (click)="cancelEditMode()"
+ (click)="openCancelDialogIfModified()"
>
Cancel
diff --git a/src/app/shared/view-issue/tester-response/tester-response.component.ts b/src/app/shared/view-issue/tester-response/tester-response.component.ts
index 55f90d765..4eb1a16b4 100644
--- a/src/app/shared/view-issue/tester-response/tester-response.component.ts
+++ b/src/app/shared/view-issue/tester-response/tester-response.component.ts
@@ -1,12 +1,13 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
-import { MatDialog } from '@angular/material';
+import { MatDialog } from '@angular/material/dialog';
import { Observable, throwError } from 'rxjs';
-import { finalize, flatMap, map } from 'rxjs/operators';
+import { finalize, map, mergeMap } from 'rxjs/operators';
import { IssueComment } from '../../../core/models/comment.model';
import { Issue } from '../../../core/models/issue.model';
import { TesterResponse } from '../../../core/models/tester-response.model';
import { UserRole } from '../../../core/models/user.model';
+import { DialogService } from '../../../core/services/dialog.service';
import { ErrorHandlingService } from '../../../core/services/error-handling.service';
import { IssueService } from '../../../core/services/issue.service';
import { PhaseService } from '../../../core/services/phase.service';
@@ -30,7 +31,12 @@ export class TesterResponseComponent implements OnInit, OnChanges {
@Input() isEditing: boolean;
@Output() issueUpdated = new EventEmitter();
@Output() updateEditState = new EventEmitter();
- @ViewChild(CommentEditorComponent, { static: false }) commentEditor: CommentEditorComponent;
+ @ViewChild(CommentEditorComponent) commentEditor: CommentEditorComponent;
+
+ // Messages for the modal popup window upon cancelling edit
+ private readonly cancelEditModalMessages = ['Do you wish to cancel?', 'Your changes will be discarded.'];
+ private readonly yesButtonModalMessage = 'Cancel';
+ private readonly noButtonModalMessage = 'Continue editing';
private readonly responseRadioIdentifier = 'response-radio';
private readonly responseTextIdentifier = 'tester-response';
@@ -41,7 +47,8 @@ export class TesterResponseComponent implements OnInit, OnChanges {
public userService: UserService,
private errorHandlingService: ErrorHandlingService,
private dialog: MatDialog,
- private phaseService: PhaseService
+ private phaseService: PhaseService,
+ private dialogService: DialogService
) {}
ngOnInit() {
@@ -67,7 +74,7 @@ export class TesterResponseComponent implements OnInit, OnChanges {
this.isSafeToSubmit()
.pipe(
- flatMap((isSaveToSubmit: boolean) => {
+ mergeMap((isSaveToSubmit: boolean) => {
if (isSaveToSubmit || this.isUpdatingDeletedResponse() || this.submitButtonText === SUBMIT_BUTTON_TEXT.OVERWRITE) {
return this.issueService.updateTesterResponse(this.issue, {
...this.issue.issueComment,
@@ -132,6 +139,48 @@ export class TesterResponseComponent implements OnInit, OnChanges {
this.updateEditState.emit(true);
}
+ openCancelDialogIfModified(): void {
+ const reasonForDisagreementIsModified = this.issue.testerResponses
+ .filter((t: TesterResponse, index: number) => this.isResponseDisagreed(index))
+ .map((t: TesterResponse, index: number) => {
+ const currentValue = this.getTesterResponseText(index);
+ const initialValue = t.reasonForDisagreement || '';
+
+ return currentValue !== initialValue;
+ })
+ .reduce((a, b) => a || b, false);
+
+ const disagreementIsModified = this.issue.testerResponses
+ .map((t: TesterResponse, index: number) => {
+ const currentValue = this.isResponseDisagreed(index);
+ const initialValue = t.isDisagree();
+
+ return currentValue !== initialValue;
+ })
+ .reduce((a, b) => a || b, false);
+
+ const isModified = reasonForDisagreementIsModified || disagreementIsModified;
+
+ this.dialogService.performActionIfModified(
+ isModified,
+ () => this.openCancelDialog(),
+ () => this.cancelEditMode()
+ );
+ }
+
+ openCancelDialog(): void {
+ const dialogRef = this.dialogService.openUserConfirmationModal(
+ this.cancelEditModalMessages,
+ this.yesButtonModalMessage,
+ this.noButtonModalMessage
+ );
+ dialogRef.afterClosed().subscribe((res) => {
+ if (res) {
+ this.cancelEditMode();
+ }
+ });
+ }
+
cancelEditMode() {
this.issueService.getIssue(this.issue.id).subscribe((issue: Issue) => {
this.issueUpdated.emit(issue);
diff --git a/src/app/shared/view-issue/tester-response/tester-response.module.ts b/src/app/shared/view-issue/tester-response/tester-response.module.ts
index f856370ff..638e3d097 100644
--- a/src/app/shared/view-issue/tester-response/tester-response.module.ts
+++ b/src/app/shared/view-issue/tester-response/tester-response.module.ts
@@ -11,7 +11,6 @@ import { TesterResponseComponent } from './tester-response.component';
@NgModule({
exports: [TesterResponseComponent],
declarations: [TesterResponseComponent, ConflictDialogComponent],
- entryComponents: [ConflictDialogComponent],
imports: [CommonModule, CommentEditorModule, SharedModule, IssueComponentsModule, LabelDropdownModule, MarkdownModule.forChild()]
})
export class TesterResponseModule {}
diff --git a/src/app/shared/view-issue/view-issue.component.html b/src/app/shared/view-issue/view-issue.component.html
index 0d2153024..0fe08c777 100644
--- a/src/app/shared/view-issue/view-issue.component.html
+++ b/src/app/shared/view-issue/view-issue.component.html
@@ -93,7 +93,7 @@
diff --git a/src/polyfills.ts b/src/polyfills.ts
index 2f258e56c..2c905618f 100644
--- a/src/polyfills.ts
+++ b/src/polyfills.ts
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
+ */
+import '@angular/localize/init';
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
@@ -60,3 +64,4 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
+(window as any).global = window;
diff --git a/src/test.ts b/src/test.ts
index c4163e646..290c154b4 100644
--- a/src/test.ts
+++ b/src/test.ts
@@ -1,8 +1,8 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+import 'zone.js/dist/zone-testing'; // This import must be at the top
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
-import 'zone.js/dist/zone-testing';
declare const require: any;
diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json
index d3253d16d..9edafdaed 100644
--- a/src/tsconfig.app.json
+++ b/src/tsconfig.app.json
@@ -9,5 +9,6 @@
"core-js/es6/": ["../node_modules/core-js/es/"]
}
},
- "exclude": ["**/*.spec.ts"]
+ "files": ["main.ts", "polyfills.ts"],
+ "include": ["src/**/*.d.ts"]
}
diff --git a/src/typings.d.ts b/src/typings.d.ts
index 78708ff3e..6f504bd8c 100644
--- a/src/typings.d.ts
+++ b/src/typings.d.ts
@@ -4,7 +4,7 @@ interface NodeModule {
id: string;
}
-declare var window: Window;
+declare var window: Window & typeof globalThis;
interface Window {
process: any;
require: any;
diff --git a/tests/app/auth/profiles/profiles.component.spec.ts b/tests/app/auth/profiles/profiles.component.spec.ts
new file mode 100644
index 000000000..4857ba1d1
--- /dev/null
+++ b/tests/app/auth/profiles/profiles.component.spec.ts
@@ -0,0 +1,106 @@
+import { Component, DebugElement } from '@angular/core';
+import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+
+import { ProfilesComponent } from '../../../../src/app/auth/profiles/profiles.component';
+import { Profile } from '../../../../src/app/core/models/profile.model';
+import { ErrorHandlingService } from '../../../../src/app/core/services/error-handling.service';
+import { ProfileService } from '../../../../src/app/core/services/profile.service';
+import { SharedModule } from '../../../../src/app/shared/shared.module';
+
+@Component({
+ template: ` `
+})
+class TestHostComponent {
+ selectedProfile: Profile | undefined;
+ onSelected(profile: Profile) {
+ this.selectedProfile = profile;
+ }
+}
+
+describe('ProfilesComponent', () => {
+ let fixture: ComponentFixture;
+ let debugElement: DebugElement;
+ let nativeElement: HTMLElement;
+ let testHost: TestHostComponent;
+ let profilesEl: HTMLElement;
+
+ const profileService = jasmine.createSpyObj('ProfileService', ['fetchExternalProfiles']);
+ const errorHandlingService = jasmine.createSpyObj('ErrorHandlingService', ['handleError']);
+
+ const testProfiles: Profile[] = [
+ {
+ profileName: 'testProfile1',
+ repoName: 'test-org1/pe'
+ },
+ {
+ profileName: 'testProfile2',
+ repoName: 'test-org2/pe'
+ },
+ {
+ profileName: 'testProfile3',
+ repoName: 'test-org3/pe'
+ }
+ ];
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [BrowserAnimationsModule, SharedModule],
+ declarations: [ProfilesComponent, TestHostComponent],
+ providers: [
+ { provide: ProfileService, useValue: profileService },
+ { provide: ErrorHandlingService, useValue: errorHandlingService }
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(fakeAsync(() => {
+ profileService.fetchExternalProfiles.and.returnValue(Promise.resolve(testProfiles));
+
+ fixture = TestBed.createComponent(TestHostComponent);
+ testHost = fixture.componentInstance;
+
+ debugElement = fixture.debugElement;
+ nativeElement = debugElement.nativeElement;
+ profilesEl = nativeElement.querySelector('app-profiles');
+
+ fixture.detectChanges(); // onInit()
+ tick(); // wait for profiles to be loaded
+ }));
+
+ it('should display the correct profiles from AppConfig', () => {
+ openMatSelect();
+ const displayedOptions = getOptions();
+
+ displayedOptions.slice(1).forEach((el, i) => {
+ const optionTextEl = el.querySelector('.mat-option-text');
+ if (!optionTextEl) {
+ fail('optionTextEl should not be null');
+ }
+ const profileName = optionTextEl.innerText;
+ expect(profileName).toBe(testProfiles[i].profileName);
+ });
+ });
+
+ it('should emit the correct profile through selectProfile when mat-option is clicked', () => {
+ openMatSelect();
+ const displayedOptions = getOptions();
+
+ displayedOptions[3].click();
+ expect(testHost.selectedProfile).toEqual(testProfiles[2]);
+ fixture.detectChanges();
+ });
+
+ function openMatSelect(): void {
+ const select = profilesEl.querySelector('.mat-select');
+ if (!select) {
+ fail('Select should not be null');
+ }
+ select.click();
+ fixture.detectChanges();
+ }
+
+ function getOptions(): HTMLElement[] {
+ return Array.from(document.querySelectorAll('.mat-option'));
+ }
+});
diff --git a/tests/app/auth/session-selection/session-selection.component.spec.ts b/tests/app/auth/session-selection/session-selection.component.spec.ts
new file mode 100644
index 000000000..2014ac57a
--- /dev/null
+++ b/tests/app/auth/session-selection/session-selection.component.spec.ts
@@ -0,0 +1,102 @@
+import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core';
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+
+import { ProfilesComponent } from '../../../../src/app/auth/profiles/profiles.component';
+import { SessionSelectionComponent } from '../../../../src/app/auth/session-selection/session-selection.component';
+import { Profile } from '../../../../src/app/core/models/profile.model';
+import { AuthService } from '../../../../src/app/core/services/auth.service';
+import { ErrorHandlingService } from '../../../../src/app/core/services/error-handling.service';
+import { GithubService } from '../../../../src/app/core/services/github.service';
+import { LoggingService } from '../../../../src/app/core/services/logging.service';
+import { PhaseService } from '../../../../src/app/core/services/phase.service';
+import { SharedModule } from '../../../../src/app/shared/shared.module';
+
+@Component({
+ selector: 'app-profiles',
+ template: ''
+})
+class ProfilesStubComponent implements Partial {
+ @Output() selectedProfileEmitter: EventEmitter = new EventEmitter();
+ @Input() urlEncodedSessionName: string;
+
+ selectProfile(profile: Profile): void {
+ this.selectedProfileEmitter.emit(profile);
+ }
+}
+
+describe('SessionSelectionComponent (unit tests)', () => {
+ let fixture: ComponentFixture;
+ let component: SessionSelectionComponent;
+ let debugElement: DebugElement;
+ let nativeElement: HTMLElement;
+ let profilesDebugEl: DebugElement;
+ let profilesComponent: ProfilesComponent;
+ let profileEmitter: EventEmitter;
+
+ const logger = jasmine.createSpyObj('LoggingService', ['info']);
+ const githubService = jasmine.createSpyObj('GithubService', ['storeOrganizationDetails']);
+ const phaseService = jasmine.createSpyObj('PhaseService', ['storeSessionData']);
+ const authService = jasmine.createSpyObj('AuthService', ['startOAuthProcess', 'changeAuthState']);
+ const errorHandlingService = jasmine.createSpyObj('ErrorHandlingService', ['handleError']);
+
+ const testProfile: Profile = {
+ profileName: 'testProfile',
+ repoName: 'testOrg/pe'
+ };
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [SharedModule, BrowserAnimationsModule],
+ declarations: [SessionSelectionComponent, ProfilesStubComponent],
+ providers: [
+ { provide: LoggingService, useValue: logger },
+ { provide: GithubService, useValue: githubService },
+ { provide: PhaseService, useValue: phaseService },
+ { provide: AuthService, useValue: authService },
+ { provide: ErrorHandlingService, useValue: errorHandlingService }
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SessionSelectionComponent);
+ fixture.detectChanges(); // onInit()
+ component = fixture.componentInstance;
+ debugElement = fixture.debugElement;
+ nativeElement = debugElement.nativeElement;
+ profilesDebugEl = debugElement.query(By.directive(ProfilesStubComponent));
+ profilesComponent = profilesDebugEl.componentInstance;
+ profileEmitter = profilesComponent.selectedProfileEmitter;
+ spyOn(component.sessionEmitter, 'emit');
+ });
+
+ it('renders without errors', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('renders the profiles component', () => {
+ expect(profilesComponent).toBeTruthy();
+ });
+
+ describe('when profile is selected', () => {
+ it('should emit the correct repo name', () => {
+ profileEmitter.emit(testProfile);
+ fixture.detectChanges();
+ expect(component.sessionEmitter.emit).toHaveBeenCalledWith(testProfile.repoName);
+ });
+
+ it('should update the session input correctly', () => {
+ profileEmitter.emit(testProfile);
+ fixture.detectChanges();
+ const sessionInput = nativeElement.querySelector('input[formcontrolname="session"]');
+
+ if (sessionInput == null) {
+ fail('sessionFieldEl should not be null');
+ return;
+ }
+ expect((sessionInput).value).toBe(testProfile.repoName);
+ });
+ });
+});
diff --git a/tests/app/phase-team-response/issues-faulty/issues-faulty.component.spec.ts b/tests/app/phase-team-response/issues-faulty/issues-faulty.component.spec.ts
index 83381a1a2..0159ec8ff 100644
--- a/tests/app/phase-team-response/issues-faulty/issues-faulty.component.spec.ts
+++ b/tests/app/phase-team-response/issues-faulty/issues-faulty.component.spec.ts
@@ -11,7 +11,7 @@ describe('IssuesFaultyComponent', () => {
const dummyIssue = Issue.createPhaseTeamResponseIssue(ISSUE_WITH_EMPTY_DESCRIPTION, dummyTeam);
let issueService: IssueService;
let issuesFaultyComponent: IssuesFaultyComponent;
- const userService = new UserService(null, null);
+ const userService = new UserService(null, null, null);
userService.currentUser = USER_Q;
const DUMMY_DUPLICATE_ISSUE_ID = 1;
const DUMMY_RESPONSE = 'dummy response';
diff --git a/tests/app/phase-team-response/issues-pending/issues-pending.component.spec.ts b/tests/app/phase-team-response/issues-pending/issues-pending.component.spec.ts
index b99657927..0c764f784 100644
--- a/tests/app/phase-team-response/issues-pending/issues-pending.component.spec.ts
+++ b/tests/app/phase-team-response/issues-pending/issues-pending.component.spec.ts
@@ -12,7 +12,7 @@ describe('IssuesPendingComponent', () => {
let dummyIssue: Issue;
let issuesPendingComponent: IssuesPendingComponent;
const issueService: IssueService = new IssueService(null, null, null, null, null, null);
- const userService: UserService = new UserService(null, null);
+ const userService: UserService = new UserService(null, null, null);
userService.currentUser = USER_Q;
const DUMMY_DUPLICATE_ISSUE_ID = 1;
const DUMMY_RESPONSE = 'dummy response';
diff --git a/tests/app/phase-team-response/issues-responded/issues-responded.component.spec.ts b/tests/app/phase-team-response/issues-responded/issues-responded.component.spec.ts
index 1b6d18802..dc71ce7f1 100644
--- a/tests/app/phase-team-response/issues-responded/issues-responded.component.spec.ts
+++ b/tests/app/phase-team-response/issues-responded/issues-responded.component.spec.ts
@@ -13,7 +13,7 @@ describe('IssuesRespondedComponent', () => {
let dummyIssue: Issue;
const issueService = new IssueService(null, null, null, null, null, null);
- const userService = new UserService(null, null);
+ const userService = new UserService(null, null, null);
userService.currentUser = USER_Q;
const issuesRespondedComponent = new IssuesRespondedComponent(issueService, userService);
issuesRespondedComponent.ngOnInit();
diff --git a/tests/app/shared/comment-editor/comment-editor.component.spec.ts b/tests/app/shared/comment-editor/comment-editor.component.spec.ts
new file mode 100644
index 000000000..35772973f
--- /dev/null
+++ b/tests/app/shared/comment-editor/comment-editor.component.spec.ts
@@ -0,0 +1,202 @@
+import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormControl, FormGroup, FormsModule } from '@angular/forms';
+import { By } from '@angular/platform-browser';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { Apollo } from 'apollo-angular';
+import { MarkdownModule } from 'ngx-markdown';
+
+import { CommentEditorComponent } from '../../../../src/app/shared/comment-editor/comment-editor.component';
+import { MarkdownToolbarComponent } from '../../../../src/app/shared/comment-editor/markdown-toolbar/markdown-toolbar.component';
+import { SharedModule } from '../../../../src/app/shared/shared.module';
+
+describe('CommentEditor', () => {
+ let fixture: ComponentFixture;
+ let debugElement: DebugElement;
+ let component: CommentEditorComponent;
+
+ const TEST_INITIAL_DESCRIPTION = 'abc';
+ const TEST_SUBMIT_BUTTON_TEXT = 'Submit';
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [CommentEditorComponent, MarkdownToolbarComponent],
+ imports: [FormsModule, SharedModule, MarkdownModule.forRoot(), BrowserAnimationsModule],
+ providers: [Apollo],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(CommentEditorComponent);
+ debugElement = fixture.debugElement;
+ component = fixture.componentInstance;
+
+ // initialize compulsory inputs
+ const commentField: FormControl = new FormControl('');
+ const commentForm: FormGroup = new FormGroup({
+ description: commentField
+ });
+ const id = 'description';
+
+ // manually inject inputs into the component
+ component.commentField = commentField;
+ component.commentForm = commentForm;
+ component.id = id;
+ component.submitButtonText = TEST_SUBMIT_BUTTON_TEXT;
+ });
+
+ describe('formatting toolbar is rendered correctly', () => {
+ const buttonsToTest = [
+ 'md-bold',
+ 'md-italic',
+ 'md-header',
+ 'md-quote',
+ 'md-code',
+ 'md-link',
+ 'md-image',
+ 'md-unordered-list',
+ 'md-ordered-list',
+ 'md-task-list',
+ 'md-mention',
+ 'md-ref'
+ ];
+
+ it('should render a formatting toolbar', () => {
+ fixture.detectChanges();
+
+ const toolBarDe: DebugElement = debugElement.query(By.css('app-markdown-toolbar'));
+ expect(toolBarDe).toBeTruthy();
+ });
+
+ it('should render all buttons in the formatting toolbar', () => {
+ fixture.detectChanges();
+
+ const toolBarDe: DebugElement = debugElement.query(By.css('app-markdown-toolbar'));
+
+ buttonsToTest.forEach((button) => {
+ expect(toolBarDe.query(By.css(button))).toBeTruthy();
+ });
+ });
+ });
+
+ describe('all buttons in the formatting toolbar add the correct markups when text box is empty', () => {
+ // key-value pair of button names and the formatting markups that they are supposed to
+ // add to the text input box when clicked
+ const buttonsToTest = {
+ 'md-bold': '****',
+ 'md-italic': '__',
+ 'md-header': '### ',
+ 'md-quote': '> ',
+ 'md-code': '``',
+ 'md-link': '[](url)',
+ 'md-image': '![](url)',
+ 'md-unordered-list': '- ',
+ 'md-ordered-list': '1. ',
+ 'md-task-list': `- [ ] `,
+ 'md-mention': '@',
+ 'md-ref': '#'
+ };
+
+ // simulate each button being clicked and check that the markups added to the text
+ // input box are correct
+ for (const [buttonName, expectedMarkup] of Object.entries(buttonsToTest)) {
+ it(`should add correct markups when the ${buttonName} button is pressed`, async () => {
+ fixture.detectChanges();
+
+ const toolbarDe: DebugElement = debugElement.query(By.css('app-markdown-toolbar'));
+ const buttonDe: any = toolbarDe.query(By.css(buttonName));
+ buttonDe.nativeElement.click();
+
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ const textBox: any = debugElement.query(By.css('textarea')).nativeElement;
+ expect(textBox.value).toEqual(expectedMarkup);
+ });
+ });
+ }
+ });
+
+ describe('all buttons in the formatting toolbar add the correct markups when some text is highlighted', () => {
+ const highlightedText = 'abc';
+ const highlightedTextStartPosition = 0;
+ const highlightedTextEndPosition = 3;
+
+ // key-value pair of button names and the formatting markups that they are supposed to
+ // add to the text input box when clicked
+ const buttonsToTest = {
+ 'md-bold': `**${highlightedText}**`,
+ 'md-italic': `_${highlightedText}_`,
+ 'md-header': `### ${highlightedText}`,
+ 'md-quote': `> ${highlightedText}`,
+ 'md-code': `\`${highlightedText}\``,
+ 'md-link': `[${highlightedText}](url)`,
+ 'md-image': `![${highlightedText}](url)`,
+ 'md-unordered-list': `- ${highlightedText}`,
+ 'md-ordered-list': `1. ${highlightedText}`,
+ 'md-task-list': `- [ ] ${highlightedText}`,
+ 'md-mention': `@${highlightedText}`,
+ 'md-ref': `#${highlightedText}`
+ };
+
+ // simulate each button being clicked and check that the markups added to the text
+ // input box are correct
+ for (const [buttonName, expectedMarkup] of Object.entries(buttonsToTest)) {
+ it(`should add correct markups when the ${buttonName} button is pressed`, async () => {
+ fixture.detectChanges();
+
+ const textBoxDe: DebugElement = debugElement.query(By.css('textarea'));
+ const toolbarDe: DebugElement = debugElement.query(By.css('app-markdown-toolbar'));
+ const buttonDe: any = toolbarDe.query(By.css(buttonName));
+
+ textBoxDe.nativeElement.value = highlightedText;
+ textBoxDe.nativeElement.dispatchEvent(new Event('input'));
+
+ textBoxDe.nativeElement.selectionStart = highlightedTextStartPosition;
+ textBoxDe.nativeElement.selectionEnd = highlightedTextEndPosition;
+ fixture.detectChanges();
+
+ buttonDe.nativeElement.click();
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ expect(textBoxDe.nativeElement.value).toEqual(expectedMarkup);
+ });
+ });
+ }
+ });
+
+ describe('text input box', () => {
+ it('should render', () => {
+ fixture.detectChanges();
+
+ const textBoxDe: DebugElement = debugElement.query(By.css('textarea'));
+ expect(textBoxDe).toBeTruthy();
+ });
+
+ it('should contain an empty string if no initial description is provided', () => {
+ fixture.detectChanges();
+
+ const textBox: any = debugElement.query(By.css('textarea')).nativeElement;
+ expect(textBox.value).toEqual('');
+ });
+
+ it('should contain an initial description if one is provided', () => {
+ component.initialDescription = TEST_INITIAL_DESCRIPTION;
+ fixture.detectChanges();
+
+ const textBox: any = debugElement.query(By.css('textarea')).nativeElement;
+ expect(textBox.value).toEqual(TEST_INITIAL_DESCRIPTION);
+ });
+
+ it('should allow users to input text', async () => {
+ fixture.detectChanges();
+
+ const textBox: any = debugElement.query(By.css('textarea')).nativeElement;
+ textBox.value = '123';
+ textBox.dispatchEvent(new Event('input'));
+
+ fixture.whenStable().then(() => {
+ expect(textBox.value).toEqual('123');
+ });
+ });
+ });
+});
diff --git a/tests/app/shared/comment-editor/upload-text-insertor.spec.ts b/tests/app/shared/comment-editor/upload-text-insertor.spec.ts
new file mode 100644
index 000000000..dcbaec42f
--- /dev/null
+++ b/tests/app/shared/comment-editor/upload-text-insertor.spec.ts
@@ -0,0 +1,169 @@
+import { DebugElement, ElementRef } from '@angular/core';
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { FormControl, FormGroup } from '@angular/forms';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+
+import { MarkdownModule } from 'ngx-markdown';
+
+import { ErrorHandlingService } from '../../../../src/app/core/services/error-handling.service';
+import { LoggingService } from '../../../../src/app/core/services/logging.service';
+import { UploadService } from '../../../../src/app/core/services/upload.service';
+import { CommentEditorComponent } from '../../../../src/app/shared/comment-editor/comment-editor.component';
+import {
+ DISPLAYABLE_CONTENT,
+ insertUploadingText,
+ insertUploadUrl,
+ insertUploadUrlVideo
+} from '../../../../src/app/shared/comment-editor/upload-text-insertor';
+import { SharedModule } from '../../../../src/app/shared/shared.module';
+
+function isDisplayable(filename) {
+ return DISPLAYABLE_CONTENT.includes(filename.split('.').pop().toLowerCase());
+}
+
+describe('UploadTextInsertor', () => {
+ let fixture: ComponentFixture;
+ let commentEditorComponent: CommentEditorComponent;
+ let form: FormGroup;
+ let commentField;
+ let commentTextArea: ElementRef;
+ let textAreaEl: HTMLTextAreaElement;
+
+ const uploadService = jasmine.createSpyObj(['isVideoFile', 'isSupportedFileType', 'uploadFile']);
+ const errorHandlingService = jasmine.createSpyObj(['handleError']);
+ const logger = jasmine.createSpyObj(['info']);
+
+ const uploadTextTemplate = (filename) => `${isDisplayable(filename) ? '!' : ''}[Uploading ${filename}...]\n`;
+ const uploadUrlTemplate = (filename, uploadUrl) => `${isDisplayable(filename) ? '!' : ''}[${filename}](${uploadUrl})\n`;
+ const uploadVideoTemplate = (uploadUrl) =>
+ `
video:${uploadUrl}\n`;
+
+ const testDisplayableFilename = 'test_file.jpg';
+ const testFilename = 'test_file.pdf';
+ const testVideo = 'test_vid.mp4';
+ const testUrl = 'testurl.com/test';
+ const dummyText = 'dummyText';
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [SharedModule, MarkdownModule.forChild(), BrowserAnimationsModule],
+ declarations: [CommentEditorComponent],
+ providers: [
+ { provide: UploadService, useValue: uploadService },
+ { provide: ErrorHandlingService, useValue: errorHandlingService },
+ { provide: LoggingService, useValue: logger }
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CommentEditorComponent);
+ form = new FormGroup({
+ description: new FormControl('')
+ });
+ commentField = form.get('description');
+ commentEditorComponent = fixture.componentInstance;
+ // Set up compulsory input fields
+ commentEditorComponent.commentField = commentField;
+ commentEditorComponent.commentForm = form;
+ commentEditorComponent.id = 'description';
+
+ commentTextArea = commentEditorComponent.commentTextArea;
+ textAreaEl = commentTextArea.nativeElement;
+ fixture.detectChanges();
+ });
+
+ it('should set up correctly', () => {
+ expect(fixture).toBeDefined();
+ expect(commentTextArea).toBeDefined();
+ expect(commentField).toBeDefined();
+ });
+
+ describe('insertUploadingText', () => {
+ describe('should set the correct value in the commentField', () => {
+ it('should insert a ! for displayable files', () => {
+ const expected = uploadTextTemplate(testDisplayableFilename);
+ insertUploadingText(testDisplayableFilename, commentField, commentTextArea);
+ expect(commentField.value).toBe(expected);
+ });
+
+ it('should not insert a ! for non-displayable files', () => {
+ const expected = uploadTextTemplate(testFilename);
+ insertUploadingText(testFilename, commentField, commentTextArea);
+ expect(commentField.value).toBe(expected);
+ });
+ });
+
+ it("should reposition the cursor by the uploading text's length", () => {
+ const initialPosition = textAreaEl.selectionEnd;
+ const expected = uploadTextTemplate(testDisplayableFilename);
+ insertUploadingText(testDisplayableFilename, commentField, commentTextArea);
+ expect(textAreaEl.selectionEnd).toBe(expected.length + initialPosition);
+ });
+ });
+
+ describe('replacePlaceholderString', () => {
+ beforeEach(() => {
+ insertUploadingText(testDisplayableFilename, commentField, commentTextArea);
+ });
+
+ describe('should position the cursor', () => {
+ it('should reposition to the end of the upload url if cursor is within the uploading text', () => {
+ insertUploadUrl(testDisplayableFilename, testUrl, commentField, commentTextArea);
+ const expected = uploadUrlTemplate(testDisplayableFilename, testUrl);
+ expect(textAreaEl.selectionEnd).toBe(expected.length);
+ });
+
+ it('should not reposition if the cursor is before the uploading text', () => {
+ // Insert text before the upload text and position cursor at the start
+ const startOfField = 0;
+ commentField.setValue(`${dummyText}${commentField.value}`);
+ textAreaEl.setSelectionRange(startOfField, startOfField);
+ insertUploadUrl(testDisplayableFilename, testUrl, commentField, commentTextArea);
+ expect(textAreaEl.selectionEnd).toBe(startOfField);
+ });
+
+ it('should reposition the cursor by the difference in length if cursor is after the uploading text', () => {
+ // Insert text after the upload text and position cursor at the end
+ commentField.setValue(`${commentField.value}${dummyText}`);
+ const endOfField = commentField.value.length;
+ textAreaEl.setSelectionRange(endOfField, endOfField);
+ insertUploadUrl(testDisplayableFilename, testUrl, commentField, commentTextArea);
+ const updatedEndOfField = commentField.value.length;
+ expect(textAreaEl.selectionEnd).toBe(updatedEndOfField);
+ });
+ });
+ });
+
+ describe('insertUploadUrl', () => {
+ beforeEach(() => {
+ insertUploadingText(testDisplayableFilename, commentField, commentTextArea);
+ });
+
+ it('should replace the uploading text with the filename and link', () => {
+ insertUploadUrl(testDisplayableFilename, testUrl, commentField, commentTextArea);
+ const expected = uploadUrlTemplate(testDisplayableFilename, testUrl);
+ expect(commentField.value).toBe(expected);
+ });
+
+ it('should replace only the uploading text, leaving the rest of the field unchanged', () => {
+ // insert dummy text before and after, should remain unchanged
+ commentField.setValue(`${dummyText}${commentField.value}${dummyText}`);
+ insertUploadUrl(testDisplayableFilename, testUrl, commentField, commentTextArea);
+ const expected = `${dummyText}${uploadUrlTemplate(testDisplayableFilename, testUrl)}${dummyText}`;
+ expect(commentField.value).toBe(expected);
+ });
+ });
+
+ describe('insertUploadUrlVideo', () => {
+ beforeEach(() => {
+ insertUploadingText(testVideo, commentField, commentTextArea);
+ });
+
+ it('should replace the uploading text with the correct HTML text', () => {
+ insertUploadUrlVideo(testVideo, testUrl, commentField, commentTextArea);
+ const expected = uploadVideoTemplate(testUrl);
+ expect(commentField.value).toBe(expected);
+ });
+ });
+});
diff --git a/tests/app/shared/issue-tables/issue-paginator.spec.ts b/tests/app/shared/issue-tables/issue-paginator.spec.ts
index 1eedba122..34cc4e62e 100644
--- a/tests/app/shared/issue-tables/issue-paginator.spec.ts
+++ b/tests/app/shared/issue-tables/issue-paginator.spec.ts
@@ -1,4 +1,4 @@
-import { MatPaginator } from '@angular/material';
+import { MatPaginator } from '@angular/material/paginator';
import { Issue } from '../../../../src/app/core/models/issue.model';
import { paginateData } from '../../../../src/app/shared/issue-tables/issue-paginator';
import { TEAM_4 } from '../../../constants/data.constants';
diff --git a/tests/app/shared/issue-tables/issue-sorter.spec.ts b/tests/app/shared/issue-tables/issue-sorter.spec.ts
index 2539de24d..278125aaf 100644
--- a/tests/app/shared/issue-tables/issue-sorter.spec.ts
+++ b/tests/app/shared/issue-tables/issue-sorter.spec.ts
@@ -1,4 +1,4 @@
-import { MatSort } from '@angular/material';
+import { MatSort } from '@angular/material/sort';
import { Issue } from '../../../../src/app/core/models/issue.model';
import { getSortedData } from '../../../../src/app/shared/issue-tables/issue-sorter';
import { TEAM_4 } from '../../../constants/data.constants';
diff --git a/tests/app/shared/issue-tables/search-filter.spec.ts b/tests/app/shared/issue-tables/search-filter.spec.ts
index e51fdbc39..078a11123 100644
--- a/tests/app/shared/issue-tables/search-filter.spec.ts
+++ b/tests/app/shared/issue-tables/search-filter.spec.ts
@@ -20,7 +20,7 @@ describe('search-filter', () => {
});
let searchKey: string;
const mediumSeverityIssueWithResponse: Issue = Issue.createPhaseTeamResponseIssue(ISSUE_WITH_EMPTY_DESCRIPTION, dummyTeam);
- mediumSeverityIssueWithResponse.responseTag = 'Accepted';
+ mediumSeverityIssueWithResponse.response = 'Accepted';
const mediumSeverityIssueWithAssigneee: Issue = Issue.createPhaseTeamResponseIssue(ISSUE_WITH_ASSIGNEES, dummyTeam);
const lowSeverityFeatureFlawIssue: Issue = Issue.createPhaseTeamResponseIssue(ISSUE_WITH_EMPTY_DESCRIPTION_LOW_SEVERITY, dummyTeam);
const highSeverityDocumentationBugIssue: Issue = Issue.createPhaseTeamResponseIssue(
@@ -83,7 +83,7 @@ describe('search-filter', () => {
expect(applySearchFilter(searchKey, displayedColumns, issueService, issuesList)).toEqual([lowSeverityFeatureFlawIssue]);
// Search by response of issue
- searchKey = mediumSeverityIssueWithResponse.responseTag;
+ searchKey = mediumSeverityIssueWithResponse.response;
expect(applySearchFilter(searchKey, displayedColumns, issueService, issuesList)).toEqual([mediumSeverityIssueWithResponse]);
});
});
diff --git a/tests/app/shared/issue/assignee/assignee.component.spec.ts b/tests/app/shared/issue/assignee/assignee.component.spec.ts
index 7c89a20fc..188dbe07e 100644
--- a/tests/app/shared/issue/assignee/assignee.component.spec.ts
+++ b/tests/app/shared/issue/assignee/assignee.component.spec.ts
@@ -1,7 +1,7 @@
import { of } from 'rxjs';
import { DebugElement } from '@angular/core';
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@@ -54,7 +54,7 @@ describe('AssigneeComponent', () => {
const issueService: any = jasmine.createSpyObj('IssueService', ['getDuplicateIssuesFor', 'getLatestIssue', 'updateIssue']);
const permissionsService: any = jasmine.createSpyObj('PermissionService', ['isIssueLabelsEditable']);
- beforeEach(async(() => {
+ beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [AssigneeComponent],
providers: [IssueService, ErrorHandlingService, PhaseService, PermissionService],
diff --git a/tests/app/shared/issue/description/description.component.spec.ts b/tests/app/shared/issue/description/description.component.spec.ts
index 58d5370eb..254141e69 100644
--- a/tests/app/shared/issue/description/description.component.spec.ts
+++ b/tests/app/shared/issue/description/description.component.spec.ts
@@ -1,5 +1,5 @@
import { FormBuilder, NgForm } from '@angular/forms';
-import { MatDialogRef } from '@angular/material';
+import { MatDialogRef } from '@angular/material/dialog';
import { of } from 'rxjs';
import { UserConfirmationComponent } from '../../../../../src/app/core/guards/user-confirmation/user-confirmation.component';
import { Issue } from '../../../../../src/app/core/models/issue.model';
diff --git a/tests/app/shared/issue/duplicatedIssues/duplicated-issues.component.spec.ts b/tests/app/shared/issue/duplicatedIssues/duplicated-issues.component.spec.ts
index 71e17d74b..ca046ad90 100644
--- a/tests/app/shared/issue/duplicatedIssues/duplicated-issues.component.spec.ts
+++ b/tests/app/shared/issue/duplicatedIssues/duplicated-issues.component.spec.ts
@@ -1,5 +1,5 @@
import { DebugElement } from '@angular/core';
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { By, HAMMER_LOADER } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { Observable, of } from 'rxjs';
@@ -39,7 +39,7 @@ describe('DuplicatedIssuesComponent', () => {
]);
const phaseService: any = jasmine.createSpyObj('PhaseService', [], { currentPhase: Phase.phaseModeration });
- beforeEach(async(() => {
+ beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [DuplicatedIssuesComponent],
providers: [
diff --git a/tests/app/shared/issue/title/title.component.spec.ts b/tests/app/shared/issue/title/title.component.spec.ts
index 90fa20d28..47cb1d4c6 100644
--- a/tests/app/shared/issue/title/title.component.spec.ts
+++ b/tests/app/shared/issue/title/title.component.spec.ts
@@ -1,5 +1,5 @@
import { FormBuilder, NgForm } from '@angular/forms';
-import { MatDialogRef } from '@angular/material';
+import { MatDialogRef } from '@angular/material/dialog';
import { of } from 'rxjs';
import { UserConfirmationComponent } from '../../../../../src/app/core/guards/user-confirmation/user-confirmation.component';
import { Issue } from '../../../../../src/app/core/models/issue.model';
diff --git a/tests/constants/error.constants.ts b/tests/constants/error.constants.ts
new file mode 100644
index 000000000..687ac1f2b
--- /dev/null
+++ b/tests/constants/error.constants.ts
@@ -0,0 +1,22 @@
+import { HttpErrorResponse } from '@angular/common/http';
+import { RequestError } from '@octokit/request-error';
+
+export const STANDARD_ERROR = new Error('This is a normal error');
+
+export const ERROR_WITH_NO_MESSAGE = new Error();
+
+export const HTTP_304_ERROR = new HttpErrorResponse({ status: 304 });
+
+export const HTTP_422_ERROR = new HttpErrorResponse({ status: 422 });
+
+export const HTTP_500_ERROR = new HttpErrorResponse({ status: 500 });
+
+export const HTTP_400_ERROR = new HttpErrorResponse({ status: 400 });
+
+export const HTTP_401_ERROR = new HttpErrorResponse({ status: 401 });
+
+export const HTTP_404_ERROR = new HttpErrorResponse({ status: 404 });
+
+export const OCTOKIT_REQUEST_ERROR = new RequestError('This is an octokit request error', 400, {
+ request: { method: 'GET', url: '', headers: {} }
+});
diff --git a/tests/constants/githubcomment.constants.ts b/tests/constants/githubcomment.constants.ts
index 19c1fcb54..aed093e8d 100644
--- a/tests/constants/githubcomment.constants.ts
+++ b/tests/constants/githubcomment.constants.ts
@@ -28,13 +28,13 @@ export const TEAM_RESPONSE_MULTIPLE_DISAGREEMENT = {
'Originally [`severity.High`]\n\n' +
'- [ ] I disagree\n\n' +
'**Reason for disagreement:** [replace this with your reason]\n\n' +
- '-------------------\n\n' +
+ '
\n\n' +
'## :question: Issue type\n\n' +
'Team chose [`type.DocumentationBug`]\n' +
'Originally [`type.FunctionalityBug`]\n\n' +
'- [ ] I disagree\n\n' +
'**Reason for disagreement:** [replace this with your reason]\n\n' +
- '-------------------',
+ '
',
created_at: '2021-06-29T17:15:11Z',
id: 870774171,
updated_at: '2021-06-29T17:15:11Z'
@@ -43,11 +43,11 @@ export const TEAM_RESPONSE_MULTIPLE_DISAGREEMENT = {
export const PENDING_TUTOR_MODERATION: GithubComment = {
body:
'# Tutor Moderation\n\n' +
- '## :question: Issue Type\n\n- [x] Done\n\ntest\n\n-------------------\n' +
+ '## :question: Issue Type\n\n- [x] Done\n\ntest\n\n
\n' +
'## :question: Issue Severity\n\n- [ ] Done\n\n' +
- '[replace this with your explanation]\n\n-------------------\n' +
+ '[replace this with your explanation]\n\n
\n' +
'## :question: Not Related Question\n\n- [ ] Done\n\n' +
- '[replace this with your explanation]\n\n-------------------\n',
+ '[replace this with your explanation]\n\n
\n',
created_at: '2020-08-15T06:39:24Z',
id: 674357972,
issue_url: 'https://api.github.com/repos/CATcher-org/pe-evaluation/issues/26',
diff --git a/tests/constants/githubissue.constants.ts b/tests/constants/githubissue.constants.ts
index 121663a7d..f889577e7 100644
--- a/tests/constants/githubissue.constants.ts
+++ b/tests/constants/githubissue.constants.ts
@@ -57,11 +57,11 @@ const ISSUE_BODY =
'# Issue Description\n{original issue description}\n' +
"# Team's Response\n{team's response}\n # Disputes\n\n" +
"## :question: Issue Type\n\n### Team says:\r\n{the team's action that is being disputed}\r\n\r\n" +
- "### Tester says:\r\n{tester's objection}\n\n-------------------\n## :question: Issue Severity\n\n" +
+ "### Tester says:\r\n{tester's objection}\n\n
\n## :question: Issue Severity\n\n" +
"### Team says:\r\n{the team's action that is being disputed}\r\n\r\n" +
- "### Tester says:\r\n{tester's objection}\n\n-------------------\n## :question: Not Related Question\n\n" +
+ "### Tester says:\r\n{tester's objection}\n\n
\n## :question: Not Related Question\n\n" +
"### Team says:\r\n{the team's action that is being disputed}\r\n\r\n" +
- "### Tester says:\r\n{tester's objection}\n\n-------------------\n\n";
+ "### Tester says:\r\n{tester's objection}\n\n
\n\n";
export const ISSUE_WITH_EMPTY_DESCRIPTION = new GithubIssue({
id: '574085971',
diff --git a/tests/constants/label.constants.ts b/tests/constants/label.constants.ts
index b760c9585..434b4b44b 100644
--- a/tests/constants/label.constants.ts
+++ b/tests/constants/label.constants.ts
@@ -1,5 +1,5 @@
import { Label } from '../../src/app/core/models/label.model';
-import { LABEL_DEFINITIONS, LabelService } from '../../src/app/core/services/label.service';
+import { LabelService, LABEL_DEFINITIONS } from '../../src/app/core/services/label.service';
// Label name constants
export const SEVERITY_VERY_LOW = 'Very Low';
diff --git a/tests/model/templates/sections/issue-dispute-section-parser.model.spec.ts b/tests/model/templates/sections/issue-dispute-section-parser.model.spec.ts
index dd7572134..4e11be8c8 100644
--- a/tests/model/templates/sections/issue-dispute-section-parser.model.spec.ts
+++ b/tests/model/templates/sections/issue-dispute-section-parser.model.spec.ts
@@ -6,7 +6,7 @@ const TYPE_DISPUTE =
"{the team's action that is being disputed}\n\n" +
'### Tester says:\n\n' +
"{tester's objection}\n\n" +
- '-------------------';
+ '
';
const EXPECTED_TITLE = 'Issue Type';
const EXPECTED_DESCRIPTION =
diff --git a/tests/model/templates/sections/moderation-section-parser.model.spec.ts b/tests/model/templates/sections/moderation-section-parser.model.spec.ts
index ae96cc74b..9133aca90 100644
--- a/tests/model/templates/sections/moderation-section-parser.model.spec.ts
+++ b/tests/model/templates/sections/moderation-section-parser.model.spec.ts
@@ -3,7 +3,8 @@ import {
ModerationSectionParser
} from '../../../../src/app/core/models/templates/section-parsers/moderation-section-parser.model';
-const TYPE_DISPUTE = '## :question: Issue Type\n\n' + '- [ ] Done\n\n' + '[replace this with your explanation]\n\n' + '-------------------';
+const TYPE_DISPUTE =
+ '## :question: Issue Type\n\n' + '- [ ] Done\n\n' + '[replace this with your explanation]\n\n' + '
';
const EMPTY_DONE_CHECKBOX = '- [ ] Done';
const FILLED_DONE_CHECKBOX = '- [x] Done';
diff --git a/tests/model/templates/sections/tester-response-section-parser.model.spec.ts b/tests/model/templates/sections/tester-response-section-parser.model.spec.ts
index bb64785d2..443b641ae 100644
--- a/tests/model/templates/sections/tester-response-section-parser.model.spec.ts
+++ b/tests/model/templates/sections/tester-response-section-parser.model.spec.ts
@@ -31,7 +31,7 @@ export const RESPONSE_DISAGREEMENT =
'Team chose [`response.Rejected`]\n\n' +
'- [ ] I disagree\n\n' +
'**Reason for disagreement:** [replace this with your reason]\n\n' +
- '-------------------';
+ '
';
export const SEVERITY_DISAGREEMENT =
'## :question: Issue severity\n\n' +
@@ -39,7 +39,7 @@ export const SEVERITY_DISAGREEMENT =
'Originally [`severity.High`]\n\n' +
'- [x] I disagree\n\n' +
'**Reason for disagreement:** I disagree!\n\n' +
- '-------------------';
+ '
';
export const TYPE_DISAGREEMENT =
'## :question: Issue type\n\n' +
@@ -47,14 +47,14 @@ export const TYPE_DISAGREEMENT =
'Originally [`type.FunctionalityBug`]\n\n' +
'- [ ] I disagree\n\n' +
'**Reason for disagreement:** [replace this with your reason]\n\n' +
- '-------------------';
+ '
';
export const DUPLICATE_DISAGREEMENT =
'## :question: Issue duplicate status\n\n' +
"Team chose to mark this issue as a duplicate of another issue (as explained in the _**Team's response**_ above)\n\n" +
'- [ ] I disagree\n\n' +
'**Reason for disagreement:** [replace this with your reason]\n\n' +
- '-------------------';
+ '
';
describe('DisagreeCheckboxParser', () => {
it('parses empty checkbox correctly', () => {
diff --git a/tests/services/error-handling.service.spec.ts b/tests/services/error-handling.service.spec.ts
new file mode 100644
index 000000000..8d63c04ef
--- /dev/null
+++ b/tests/services/error-handling.service.spec.ts
@@ -0,0 +1,74 @@
+import { ErrorHandlingService } from '../../src/app/core/services/error-handling.service';
+import { FormErrorComponent } from '../../src/app/shared/error-toasters/form-error/form-error.component';
+import { GeneralMessageErrorComponent } from '../../src/app/shared/error-toasters/general-message-error/general-message-error.component';
+import {
+ ERROR_WITH_NO_MESSAGE,
+ HTTP_304_ERROR,
+ HTTP_400_ERROR,
+ HTTP_401_ERROR,
+ HTTP_404_ERROR,
+ HTTP_422_ERROR,
+ HTTP_500_ERROR,
+ OCTOKIT_REQUEST_ERROR,
+ STANDARD_ERROR
+} from '../constants/error.constants';
+
+let errorHandlingService: ErrorHandlingService;
+let mockLoggingService;
+let mockSnackBar;
+
+describe('ErrorHandlingService', () => {
+ beforeEach(() => {
+ mockLoggingService = jasmine.createSpyObj('LoggingService', ['error', 'debug']);
+ mockSnackBar = jasmine.createSpyObj('MatSnackBar', ['openFromComponent']);
+ errorHandlingService = new ErrorHandlingService(mockSnackBar, mockLoggingService);
+ });
+
+ describe('ErrorHandlingService: handleError()', () => {
+ it('should log errors when handling errors', () => {
+ errorHandlingService.handleError(STANDARD_ERROR);
+ expect(mockLoggingService.error).toHaveBeenCalledWith('ErrorHandlingService: ' + STANDARD_ERROR);
+ });
+
+ it('should use the GeneralMessageErrorComponent when handling Errors', () => {
+ errorHandlingService.handleError(STANDARD_ERROR);
+ expect(mockSnackBar.openFromComponent).toHaveBeenCalledWith(GeneralMessageErrorComponent, {
+ data: { message: STANDARD_ERROR.message }
+ });
+ });
+
+ it('should stringify Errors if there is no message before displaying', () => {
+ errorHandlingService.handleError(ERROR_WITH_NO_MESSAGE);
+ expect(mockSnackBar.openFromComponent).toHaveBeenCalledWith(GeneralMessageErrorComponent, {
+ data: { message: JSON.stringify(ERROR_WITH_NO_MESSAGE) }
+ });
+ });
+
+ it('should not open the snackbar when handling http status 304 errors', () => {
+ errorHandlingService.handleError(HTTP_304_ERROR);
+ expect(mockSnackBar.openFromComponent).not.toHaveBeenCalled();
+ });
+
+ it('should use the FormErrorComponent when handling http status 422 errors', () => {
+ errorHandlingService.handleError(HTTP_422_ERROR);
+ expect(mockSnackBar.openFromComponent).toHaveBeenCalledWith(FormErrorComponent, { data: HTTP_422_ERROR });
+ });
+
+ it('should use the GeneralMessageErrorComponent when handling other http errors', () => {
+ errorHandlingService.handleError(HTTP_500_ERROR);
+ expect(mockSnackBar.openFromComponent).toHaveBeenCalledWith(GeneralMessageErrorComponent, { data: HTTP_500_ERROR });
+ errorHandlingService.handleError(HTTP_400_ERROR);
+ expect(mockSnackBar.openFromComponent).toHaveBeenCalledWith(GeneralMessageErrorComponent, { data: HTTP_400_ERROR });
+ errorHandlingService.handleError(HTTP_401_ERROR);
+ expect(mockSnackBar.openFromComponent).toHaveBeenCalledWith(GeneralMessageErrorComponent, { data: HTTP_401_ERROR });
+ errorHandlingService.handleError(HTTP_404_ERROR);
+ expect(mockSnackBar.openFromComponent).toHaveBeenCalledWith(GeneralMessageErrorComponent, { data: HTTP_404_ERROR });
+ expect(mockSnackBar.openFromComponent).toHaveBeenCalledTimes(4);
+ });
+
+ it('should treat octokit request errors as http errors', () => {
+ errorHandlingService.handleError(OCTOKIT_REQUEST_ERROR);
+ expect(mockSnackBar.openFromComponent).toHaveBeenCalledWith(GeneralMessageErrorComponent, { data: OCTOKIT_REQUEST_ERROR });
+ });
+ });
+});
diff --git a/tests/services/label.service.spec.ts b/tests/services/label.service.spec.ts
index 10e0aceb0..087c81826 100644
--- a/tests/services/label.service.spec.ts
+++ b/tests/services/label.service.spec.ts
@@ -1,7 +1,7 @@
import { of } from 'rxjs';
import { GithubLabel } from '../../src/app/core/models/github/github-label.model';
import { Label } from '../../src/app/core/models/label.model';
-import { LABEL_DEFINITIONS, LabelService } from '../../src/app/core/services/label.service';
+import { LabelService, LABEL_DEFINITIONS } from '../../src/app/core/services/label.service';
import * as GithubLabelConstant from '../constants/githublabel.constants';
import * as LabelConstant from '../constants/label.constants';
@@ -11,7 +11,7 @@ let githubService: any;
describe('LabelService', () => {
beforeEach(() => {
githubService = jasmine.createSpyObj('GithubService', ['fetchAllLabels', 'createLabel']);
- labelService = new LabelService(githubService);
+ labelService = new LabelService(githubService, null);
});
describe('.syncLabels()', () => {
@@ -70,7 +70,7 @@ describe('LabelService', () => {
describe('LabelService: toLabel()', () => {
beforeAll(() => {
- labelService = new LabelService(null);
+ labelService = new LabelService(null, null);
});
afterAll(() => {
@@ -100,7 +100,7 @@ describe('LabelService: toLabel()', () => {
describe('LabelService: isDarkColor()', () => {
beforeEach(() => {
- labelService = new LabelService(null);
+ labelService = new LabelService(null, null);
});
afterEach(() => {
@@ -118,7 +118,7 @@ describe('LabelService: isDarkColor()', () => {
describe('LabelService: setLabelStyle()', () => {
beforeEach(() => {
- labelService = new LabelService(null);
+ labelService = new LabelService(null, null);
});
afterEach(() => {
@@ -140,7 +140,7 @@ describe('LabelService: setLabelStyle()', () => {
describe('LabelService: getColorOfLabel()', () => {
beforeEach(() => {
- labelService = new LabelService(null);
+ labelService = new LabelService(null, null);
});
afterEach(() => {
@@ -158,7 +158,7 @@ describe('LabelService: getColorOfLabel()', () => {
describe('LabelService: getLabelDefinition()', () => {
beforeEach(() => {
- labelService = new LabelService(null);
+ labelService = new LabelService(null, null);
});
afterEach(() => {
diff --git a/tests/services/logging.service.spec.ts b/tests/services/logging.service.spec.ts
index 5f5c411b8..d5fe35613 100644
--- a/tests/services/logging.service.spec.ts
+++ b/tests/services/logging.service.spec.ts
@@ -1,7 +1,7 @@
import { LoggingService } from '../../src/app/core/services/logging.service';
import { MockLocalStorage } from '../helper/mock.local.storage';
-let loggingService: LoggingService;
+let logger: LoggingService;
let headerLog: string;
let sessionSeparator: string;
const mockDate = new Date(2021, 6, 27);
@@ -22,9 +22,9 @@ const mockDates = () => {
const initializeLoggingService = () => {
const electronService = jasmine.createSpyObj('ElectronService', ['isElectron']);
electronService.isElectron = jasmine.createSpy('isElectron', () => false);
- loggingService = new LoggingService(electronService);
- headerLog = `${loggingService.LOG_START_HEADER}\n${mockDate.toLocaleString()}`;
- sessionSeparator = loggingService.SESSION_LOG_SEPARATOR;
+ logger = new LoggingService(electronService);
+ headerLog = `${logger.LOG_START_HEADER}\n${mockDate.toLocaleString()}`;
+ sessionSeparator = logger.SESSION_LOG_SEPARATOR;
};
describe('LoggingService', () => {
@@ -36,7 +36,7 @@ describe('LoggingService', () => {
});
beforeEach(() => {
- loggingService.reset();
+ logger.reset();
localStorage.clear();
});
@@ -46,31 +46,31 @@ describe('LoggingService', () => {
describe('.startSession()', () => {
it('should successfully initialize logging session', () => {
- loggingService.startSession();
- const actualLog = loggingService.getCachedLog();
+ logger.startSession();
+ const actualLog = logger.getCachedLog();
const expectedLog = headerLog;
expect(actualLog).toEqual(expectedLog);
});
it('should successfully reinitialize logging session', () => {
- loggingService.startSession();
- loggingService.reset();
- loggingService.startSession();
- const actualLog = loggingService.getCachedLog();
+ logger.startSession();
+ logger.reset();
+ logger.startSession();
+ const actualLog = logger.getCachedLog();
const expectedLog = `${headerLog}${sessionSeparator}${headerLog}`;
expect(actualLog).toEqual(expectedLog);
});
it('should successfully reinitialize logging session when limit reached', () => {
- Array(loggingService.LOG_COUNT_LIMIT)
+ Array(logger.LOG_COUNT_LIMIT)
.fill(0)
.forEach(() => {
- loggingService.startSession();
- loggingService.reset();
+ logger.startSession();
+ logger.reset();
});
- loggingService.startSession();
- const actualLog = loggingService.getCachedLog();
- const expectedLog = Array(loggingService.LOG_COUNT_LIMIT)
+ logger.startSession();
+ const actualLog = logger.getCachedLog();
+ const expectedLog = Array(logger.LOG_COUNT_LIMIT)
.fill('')
.map((_) => headerLog)
.join(sessionSeparator);
@@ -80,19 +80,19 @@ describe('LoggingService', () => {
describe('.reset()', () => {
it('should do nothing if no session is ongoing', () => {
- loggingService.reset();
- const actualLog = loggingService.getCachedLog();
+ logger.reset();
+ const actualLog = logger.getCachedLog();
expect(actualLog).toBeNull();
});
it('should not tamper with existing log histories', () => {
let expectedLog = headerLog;
- for (let i = 0; i < loggingService.LOG_COUNT_LIMIT + 1; i += 1) {
- loggingService.startSession();
- loggingService.reset();
- const actualLog = loggingService.getCachedLog();
+ for (let i = 0; i < logger.LOG_COUNT_LIMIT + 1; i += 1) {
+ logger.startSession();
+ logger.reset();
+ const actualLog = logger.getCachedLog();
expect(actualLog).toEqual(expectedLog);
- if (i < loggingService.LOG_COUNT_LIMIT - 1) {
+ if (i < logger.LOG_COUNT_LIMIT - 1) {
expectedLog += `${sessionSeparator}${headerLog}`;
}
}
@@ -101,10 +101,10 @@ describe('LoggingService', () => {
describe('adding logs', () => {
it('should successfully add info logs', () => {
- loggingService.startSession();
- const initialLog = loggingService.getCachedLog();
- loggingService.info(infoLogMessage);
- const actualLog = loggingService.getCachedLog();
+ logger.startSession();
+ const initialLog = logger.getCachedLog();
+ logger.info(infoLogMessage);
+ const actualLog = logger.getCachedLog();
const expectedLog = `${initialLog}\n${infoLogMessage}`;
expect(actualLog).toEqual(expectedLog);
});
@@ -112,16 +112,16 @@ describe('LoggingService', () => {
describe('updating and trimming logs from sessions', () => {
it('should trim oldest log if number of sessions exceed session limit', () => {
- Array(loggingService.LOG_COUNT_LIMIT + 1)
+ Array(logger.LOG_COUNT_LIMIT + 1)
.fill(0)
.forEach(() => {
- loggingService.startSession();
- loggingService.info(infoLogMessage);
- loggingService.reset();
+ logger.startSession();
+ logger.info(infoLogMessage);
+ logger.reset();
});
- loggingService.startSession();
- const actualLog = loggingService.getCachedLog();
- const expectedLog = Array(loggingService.LOG_COUNT_LIMIT)
+ logger.startSession();
+ const actualLog = logger.getCachedLog();
+ const expectedLog = Array(logger.LOG_COUNT_LIMIT)
.fill('')
.map((_) => headerLog)
.join(`\n${infoLogMessage}${sessionSeparator}`);
diff --git a/tests/services/permissions.service.spec.ts b/tests/services/permissions.service.spec.ts
index 711d1b97c..a9a619a5b 100644
--- a/tests/services/permissions.service.spec.ts
+++ b/tests/services/permissions.service.spec.ts
@@ -19,7 +19,7 @@ const testAdmin = {
role: UserRole.Admin
};
-const mockUserService = new UserService(null, null);
+const mockUserService = new UserService(null, null, null);
const mockPhaseService = new PhaseService(null, null, null);
const permissionService = new PermissionService(mockUserService, mockPhaseService);
diff --git a/tests/services/repo-creator.service.spec.ts b/tests/services/repo-creator.service.spec.ts
index df907d2b5..ab4766ce2 100644
--- a/tests/services/repo-creator.service.spec.ts
+++ b/tests/services/repo-creator.service.spec.ts
@@ -19,7 +19,7 @@ let userService: UserService;
describe('RepoCreatorService', () => {
beforeEach(() => {
- userService = new UserService(null, null);
+ userService = new UserService(null, null, null);
githubService = jasmine.createSpyObj('GithubService', ['isRepositoryPresent', 'createRepository']);
matDialog = jasmine.createSpyObj('MatDialog', ['open']);
matDialogRef = jasmine.createSpyObj('MatDialogRef', ['afterClosed']);
diff --git a/tests/services/user.service.spec.ts b/tests/services/user.service.spec.ts
index 53262db75..c8c16f6af 100644
--- a/tests/services/user.service.spec.ts
+++ b/tests/services/user.service.spec.ts
@@ -29,7 +29,7 @@ describe('UserService', () => {
});
it('should authorize User despite loginId being of different casing', () => {
- const userService = new UserService(null, dataService);
+ const userService = new UserService(null, dataService, null);
userService.createUserModel(USER_JUNWEI.loginId).subscribe((user) => {
expect(user).toBeDefined();
});
@@ -40,7 +40,7 @@ describe('UserService', () => {
});
it('throws an error if the user is unauthorized', (done) => {
- const userService = new UserService(null, dataService);
+ const userService = new UserService(null, dataService, null);
userService.createUserModel('RandomUser').subscribe(
(user) => {
fail('This test case should have failed.');
@@ -56,7 +56,7 @@ describe('UserService', () => {
});
async function createAndVerifyUser(loginId: string, expectedUser: User) {
- const userService = new UserService(null, dataService);
+ const userService = new UserService(null, dataService, null);
const actualUser = await userService.createUserModel(loginId).toPromise();
expect(actualUser).toEqual(expectedUser);
}
diff --git a/tsconfig.json b/tsconfig.json
index a8eae2482..86edf4c6d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -3,7 +3,7 @@
"compilerOptions": {
"incremental": true,
"downlevelIteration": true,
- "module": "esnext",
+ "module": "es2020",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,