diff --git a/src/app/core/cache/server-sync-buffer.effects.spec.ts b/src/app/core/cache/server-sync-buffer.effects.spec.ts index 37ad0e6346..c2aa7b14f9 100644 --- a/src/app/core/cache/server-sync-buffer.effects.spec.ts +++ b/src/app/core/cache/server-sync-buffer.effects.spec.ts @@ -51,6 +51,14 @@ describe('ServerSyncBufferEffects', () => { _links: { self: { href: link } } }); return observableOf(object); + }, + getBySelfLink: (link) => { + const object = Object.assign(new DSpaceObject(), { + _links: { + self: { href: link } + } + }); + return observableOf(object); } } }, diff --git a/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.spec.ts b/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.spec.ts new file mode 100644 index 0000000000..7aeb33d84d --- /dev/null +++ b/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.spec.ts @@ -0,0 +1,142 @@ +import { ProfilePageMetadataFormComponent } from './profile-page-metadata-form.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { VarDirective } from '../../shared/utils/var.directive'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; +import { EPerson } from '../../core/eperson/models/eperson.model'; +import { GLOBAL_CONFIG } from '../../../config'; +import { FormBuilderService } from '../../shared/form/builder/form-builder.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { EPersonDataService } from '../../core/eperson/eperson-data.service'; +import { cloneDeep } from 'lodash'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils'; + +describe('ProfilePageMetadataFormComponent', () => { + let component: ProfilePageMetadataFormComponent; + let fixture: ComponentFixture; + + const config = { + languages: [{ + code: 'en', + label: 'English', + active: true, + }, { + code: 'de', + label: 'Deutsch', + active: true, + }] + } as any; + + const user = Object.assign(new EPerson(), { + email: 'example@gmail.com', + metadata: { + 'eperson.firstname': [ + { + value: 'John', + language: null + } + ], + 'eperson.lastname': [ + { + value: 'Doe', + language: null + } + ], + 'eperson.language': [ + { + value: 'de', + language: null + } + ] + } + }); + + const epersonService = jasmine.createSpyObj('epersonService', { + update: createSuccessfulRemoteDataObject$(user) + }); + const notificationsService = jasmine.createSpyObj('notificationsService', { + success: {}, + error: {}, + warning: {} + }); + const translate = { + instant: () => 'translated', + onLangChange: new EventEmitter() + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ProfilePageMetadataFormComponent, VarDirective], + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], + providers: [ + { provide: GLOBAL_CONFIG, useValue: config }, + { provide: EPersonDataService, useValue: epersonService }, + { provide: TranslateService, useValue: translate }, + { provide: NotificationsService, useValue: notificationsService }, + FormBuilderService + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ProfilePageMetadataFormComponent); + component = fixture.componentInstance; + component.user = user; + fixture.detectChanges(); + }); + + it('should automatically fill in the user\'s email in the correct field', () => { + expect(component.formGroup.get('email').value).toEqual(user.email); + }); + + it('should automatically fill the present metadata values and leave missing ones empty', () => { + expect(component.formGroup.get('firstname').value).toEqual('John'); + expect(component.formGroup.get('lastname').value).toEqual('Doe'); + expect(component.formGroup.get('phone').value).toBeUndefined(); + expect(component.formGroup.get('language').value).toEqual('de'); + }); + + describe('updateProfile', () => { + describe('when no values changed', () => { + let result; + + beforeEach(() => { + result = component.updateProfile(); + }); + + it('should return false', () => { + expect(result).toEqual(false); + }); + + it('should not call epersonService.update', () => { + expect(epersonService.update).not.toHaveBeenCalled(); + }); + }); + + describe('when a form value changed', () => { + let result; + let newUser; + + beforeEach(() => { + newUser = cloneDeep(user); + newUser.metadata['eperson.firstname'][0].value = 'Johnny'; + setModelValue('firstname', 'Johnny'); + result = component.updateProfile(); + }); + + it('should return true', () => { + expect(result).toEqual(true); + }); + + it('should call epersonService.update', () => { + expect(epersonService.update).toHaveBeenCalledWith(newUser); + }); + }); + }); + + function setModelValue(id: string, value: string) { + component.formModel.filter((model) => model.id === id).forEach((model) => (model as any).value = value); + } +}); diff --git a/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.ts b/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.ts index 0b06b3f076..b44faa8c4a 100644 --- a/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.ts +++ b/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, Inject, Input, OnInit } from '@angular/core'; import { DynamicFormControlModel, DynamicFormService, DynamicFormValueControlModel, @@ -6,7 +6,6 @@ import { } from '@ng-dynamic-forms/core'; import { FormGroup } from '@angular/forms'; import { EPerson } from '../../core/eperson/models/eperson.model'; -import { Location } from '@angular/common'; import { TranslateService } from '@ngx-translate/core'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; @@ -14,9 +13,7 @@ import { LangConfig } from '../../../config/lang-config.interface'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { cloneDeep } from 'lodash'; import { getRemoteDataPayload, getSucceededRemoteData } from '../../core/shared/operators'; -import { FormService } from '../../shared/form/form.service'; import { FormBuilderService } from '../../shared/form/builder/form-builder.service'; -import { FormComponent } from '../../shared/form/form.component'; import { NotificationsService } from '../../shared/notifications/notifications.service'; @Component({ @@ -106,7 +103,6 @@ export class ProfilePageMetadataFormComponent implements OnInit { activeLangs: LangConfig[]; constructor(@Inject(GLOBAL_CONFIG) protected config: GlobalConfig, - protected location: Location, protected formBuilderService: FormBuilderService, protected translate: TranslateService, protected epersonService: EPersonDataService, diff --git a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.spec.ts b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.spec.ts new file mode 100644 index 0000000000..da0ad049c4 --- /dev/null +++ b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.spec.ts @@ -0,0 +1,106 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { EPerson } from '../../core/eperson/models/eperson.model'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { VarDirective } from '../../shared/utils/var.directive'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { EPersonDataService } from '../../core/eperson/eperson-data.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { FormBuilderService } from '../../shared/form/builder/form-builder.service'; +import { ProfilePageSecurityFormComponent } from './profile-page-security-form.component'; +import { of as observableOf } from 'rxjs'; +import { RestResponse } from '../../core/cache/response.models'; + +describe('ProfilePageSecurityFormComponent', () => { + let component: ProfilePageSecurityFormComponent; + let fixture: ComponentFixture; + + const user = new EPerson(); + + const epersonService = jasmine.createSpyObj('epersonService', { + immediatePatch: observableOf(new RestResponse(true, 200, 'OK')) + }); + const notificationsService = jasmine.createSpyObj('notificationsService', { + success: {}, + error: {}, + warning: {} + }); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ProfilePageSecurityFormComponent, VarDirective], + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], + providers: [ + { provide: EPersonDataService, useValue: epersonService }, + { provide: NotificationsService, useValue: notificationsService }, + FormBuilderService + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ProfilePageSecurityFormComponent); + component = fixture.componentInstance; + component.user = user; + fixture.detectChanges(); + }); + + describe('updateSecurity', () => { + describe('when no values changed', () => { + let result; + + beforeEach(() => { + result = component.updateSecurity(); + }); + + it('should return false', () => { + expect(result).toEqual(false); + }); + + it('should not call epersonService.immediatePatch', () => { + expect(epersonService.immediatePatch).not.toHaveBeenCalled(); + }); + }); + + describe('when password is filled in, but the confirm field is empty', () => { + let result; + + beforeEach(() => { + setModelValue('password', 'test'); + result = component.updateSecurity(); + }); + + it('should return true', () => { + expect(result).toEqual(true); + }); + }); + + describe('when both password fields are filled in and equal', () => { + let result; + let operations; + + beforeEach(() => { + setModelValue('password', 'test'); + setModelValue('passwordrepeat', 'test'); + operations = [{ op: 'replace', path: '/password', value: 'test' }]; + result = component.updateSecurity(); + }); + + it('should return true', () => { + expect(result).toEqual(true); + }); + + it('should return call epersonService.immediatePatch', () => { + expect(epersonService.immediatePatch).toHaveBeenCalledWith(user, operations); + }); + }); + }); + + function setModelValue(id: string, value: string) { + component.formGroup.patchValue({ + [id]: value + }); + component.formGroup.markAllAsTouched(); + } +}); diff --git a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts index 2c02027b4a..5885cc48db 100644 --- a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts +++ b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts @@ -1,12 +1,12 @@ import { Component, Input, OnInit } from '@angular/core'; import { DynamicFormControlModel, - DynamicFormService, DynamicFormValueControlModel, + DynamicFormService, DynamicInputModel } from '@ng-dynamic-forms/core'; import { TranslateService } from '@ngx-translate/core'; import { FormGroup } from '@angular/forms'; -import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; +import { isEmpty, isNotEmpty } from '../../shared/empty.util'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { ErrorResponse, RestResponse } from '../../core/cache/response.models'; diff --git a/src/app/profile-page/profile-page.component.spec.ts b/src/app/profile-page/profile-page.component.spec.ts new file mode 100644 index 0000000000..5992012be9 --- /dev/null +++ b/src/app/profile-page/profile-page.component.spec.ts @@ -0,0 +1,129 @@ +import { ProfilePageComponent } from './profile-page.component'; +import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing'; +import { VarDirective } from '../shared/utils/var.directive'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { EPerson } from '../core/eperson/models/eperson.model'; +import { createPaginatedList, createSuccessfulRemoteDataObject$ } from '../shared/testing/utils'; +import { Store, StoreModule } from '@ngrx/store'; +import { AppState } from '../app.reducer'; +import { AuthTokenInfo } from '../core/auth/models/auth-token-info.model'; +import { EPersonDataService } from '../core/eperson/eperson-data.service'; +import { NotificationsService } from '../shared/notifications/notifications.service'; +import { authReducer } from '../core/auth/auth.reducer'; + +describe('ProfilePageComponent', () => { + let component: ProfilePageComponent; + let fixture: ComponentFixture; + + const user = Object.assign(new EPerson(), { + groups: createSuccessfulRemoteDataObject$(createPaginatedList([])) + }); + const authState = { + authenticated: true, + loaded: true, + loading: false, + authToken: new AuthTokenInfo('test_token'), + user: user + }; + + const epersonService = jasmine.createSpyObj('epersonService', { + findById: createSuccessfulRemoteDataObject$(user) + }); + const notificationsService = jasmine.createSpyObj('notificationsService', { + success: {}, + error: {}, + warning: {} + }); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ProfilePageComponent, VarDirective], + imports: [StoreModule.forRoot(authReducer), TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], + providers: [ + { provide: EPersonDataService, useValue: epersonService }, + { provide: NotificationsService, useValue: notificationsService } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(inject([Store], (store: Store) => { + store + .subscribe((state) => { + (state as any).core = Object.create({}); + (state as any).core.auth = authState; + }); + + fixture = TestBed.createComponent(ProfilePageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + describe('updateProfile', () => { + describe('when the metadata form returns false and the security form returns true', () => { + beforeEach(() => { + component.metadataForm = jasmine.createSpyObj('metadataForm', { + updateProfile: false + }); + component.securityForm = jasmine.createSpyObj('securityForm', { + updateSecurity: true + }); + component.updateProfile(); + }); + + it('should not display a warning', () => { + expect(notificationsService.warning).not.toHaveBeenCalled(); + }); + }); + + describe('when the metadata form returns true and the security form returns false', () => { + beforeEach(() => { + component.metadataForm = jasmine.createSpyObj('metadataForm', { + updateProfile: true + }); + component.securityForm = jasmine.createSpyObj('securityForm', { + updateSecurity: false + }); + component.updateProfile(); + }); + + it('should not display a warning', () => { + expect(notificationsService.warning).not.toHaveBeenCalled(); + }); + }); + + describe('when the metadata form returns true and the security form returns true', () => { + beforeEach(() => { + component.metadataForm = jasmine.createSpyObj('metadataForm', { + updateProfile: true + }); + component.securityForm = jasmine.createSpyObj('securityForm', { + updateSecurity: true + }); + component.updateProfile(); + }); + + it('should not display a warning', () => { + expect(notificationsService.warning).not.toHaveBeenCalled(); + }); + }); + + describe('when the metadata form returns false and the security form returns false', () => { + beforeEach(() => { + component.metadataForm = jasmine.createSpyObj('metadataForm', { + updateProfile: false + }); + component.securityForm = jasmine.createSpyObj('securityForm', { + updateSecurity: false + }); + component.updateProfile(); + }); + + it('should display a warning', () => { + expect(notificationsService.warning).toHaveBeenCalled(); + }); + }); + }); +});