diff --git a/src/app/shared/utils.spec.ts b/src/app/shared/utils.spec.ts index 6eb08cf..532137c 100644 --- a/src/app/shared/utils.spec.ts +++ b/src/app/shared/utils.spec.ts @@ -1,8 +1,21 @@ -import { limitText } from './utils' +import { filterObject, limitText } from './utils' describe('utils', () => { it('should limit text if text too long', () => { const result = limitText('textData', 4) expect(result).toBe('text...') }) + + it('should exclude props', () => { + const obj = { + name: 'John', + surname: 'Doe', + isVisible: true + } + const result = filterObject(obj, ['surname']) + expect(result).toEqual({ + name: 'John', + isVisible: true + }) + }) }) diff --git a/src/app/test/mocks/auth-mock.service.ts b/src/app/test/mocks/auth-mock.service.ts deleted file mode 100644 index 424c944..0000000 --- a/src/app/test/mocks/auth-mock.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { IAuthService, UserProfile } from '@onecx/portal-integration-angular' -import { BehaviorSubject } from 'rxjs' - -export class IAuthMockService implements IAuthService { - currentUser$ = new BehaviorSubject(undefined) - getCurrentUser(): UserProfile | null { - return null - } - logout(): void { - // logout - } - hasPermission(permissionKey: string): boolean { - return false - } - getAuthProviderName(): string { - return 'mock' - } - hasRole(role: string | string[]): boolean { - return false - } - init(): Promise { - return new Promise((resolve, reject) => { - resolve(false) - }) - } - getUserRoles(): string[] { - return [] - } -} diff --git a/src/app/theme/theme-designer/theme-designer.component.spec.ts b/src/app/theme/theme-designer/theme-designer.component.spec.ts index 59c21e6..a7e129e 100644 --- a/src/app/theme/theme-designer/theme-designer.component.spec.ts +++ b/src/app/theme/theme-designer/theme-designer.component.spec.ts @@ -131,7 +131,7 @@ describe('ThemeDesignerComponent', () => { expect(Object.keys(component.sidebarForm.controls).length).toBe(themeVariables.sidebar.length) }) - it('should load translations', () => { + it('should load translations', (done: DoneFn) => { const translateService = TestBed.inject(TranslateService) const actionsTranslations = { 'ACTIONS.CANCEL': 'actionCancel', @@ -147,27 +147,28 @@ describe('ThemeDesignerComponent', () => { component = fixture.componentInstance fixture.detectChanges() - expect(component.actions.length).toBe(3) - const cancelAction = component.actions.filter( - (a) => a.label === 'actionCancel' && a.title === 'actionTooltipsCancelAndClose' - )[0] - spyOn(component, 'close') - cancelAction.actionCallback() - expect(component['close']).toHaveBeenCalledTimes(1) - - const saveAction = component.actions.filter( - (a) => a.label === 'actionsSave' && a.title === 'actionsTooltipsSave' - )[0] - spyOn(component, 'updateTheme') - saveAction.actionCallback() - expect(component['updateTheme']).toHaveBeenCalledTimes(1) - - const saveAsAction = component.actions.filter( - (a) => a.label === 'actionSaveAs' && a.title === 'actionTooltipsSaveAs' - )[0] - spyOn(component, 'saveAsNewPopup') - saveAsAction.actionCallback() - expect(component.saveAsNewPopup).toHaveBeenCalledTimes(1) + // simulate async pipe + component.actions$?.subscribe((actions) => { + expect(actions.length).toBe(3) + const cancelAction = actions.filter( + (a) => a.label === 'actionCancel' && a.title === 'actionTooltipsCancelAndClose' + )[0] + spyOn(component, 'close') + cancelAction.actionCallback() + expect(component['close']).toHaveBeenCalledTimes(1) + + const saveAction = actions.filter((a) => a.label === 'actionsSave' && a.title === 'actionsTooltipsSave')[0] + spyOn(component, 'updateTheme') + saveAction.actionCallback() + expect(component['updateTheme']).toHaveBeenCalledTimes(1) + + const saveAsAction = actions.filter((a) => a.label === 'actionSaveAs' && a.title === 'actionTooltipsSaveAs')[0] + spyOn(component, 'saveAsNewPopup') + saveAsAction.actionCallback() + expect(component.saveAsNewPopup).toHaveBeenCalledTimes(1) + + done() + }) }) it('should update document style on form changes', fakeAsync(() => { @@ -331,24 +332,32 @@ describe('ThemeDesignerComponent', () => { ]) }) - it('should navigate back on close', () => { + it('should navigate back on close', (done: DoneFn) => { const router = TestBed.inject(Router) spyOn(router, 'navigate') - component.actions[0].actionCallback() + component.actions$?.subscribe((actions) => { + const closeAction = actions[0] + closeAction.actionCallback() + expect(router.navigate).toHaveBeenCalledOnceWith(['./..'], jasmine.any(Object)) - expect(router.navigate).toHaveBeenCalledOnceWith(['./..'], jasmine.any(Object)) + done() + }) }) - it('should display error when updating theme with invalid form', () => { + it('should display error when updating theme with invalid form', (done: DoneFn) => { spyOnProperty(component.propertiesForm, 'invalid').and.returnValue(true) - component.actions[1].actionCallback() + component.actions$?.subscribe((actions) => { + const updateThemeAction = actions[1] + updateThemeAction.actionCallback() + expect(msgServiceSpy.error).toHaveBeenCalledOnceWith({ summaryKey: 'ACTIONS.EDIT.MESSAGE.CHANGE_NOK' }) - expect(msgServiceSpy.error).toHaveBeenCalledOnceWith({ summaryKey: 'ACTIONS.EDIT.MESSAGE.CHANGE_NOK' }) + done() + }) }) - it('should display error when updating theme call fails', () => { + it('should display error when updating theme call fails', (done: DoneFn) => { const themeData = { id: 'id', description: 'desc', @@ -370,12 +379,16 @@ describe('ThemeDesignerComponent', () => { themeApiSpy.getThemeById.and.returnValue(of(themeResponse) as any) themeApiSpy.updateTheme.and.returnValue(throwError(() => new Error())) - component.actions[1].actionCallback() + component.actions$?.subscribe((actions) => { + const updateThemeAction = actions[1] + updateThemeAction.actionCallback() + expect(msgServiceSpy.error).toHaveBeenCalledOnceWith({ summaryKey: 'ACTIONS.EDIT.MESSAGE.CHANGE_NOK' }) - expect(msgServiceSpy.error).toHaveBeenCalledOnceWith({ summaryKey: 'ACTIONS.EDIT.MESSAGE.CHANGE_NOK' }) + done() + }) }) - it('should only update properties and base theme data and show success when updating theme call is successful', () => { + it('should only update properties and base theme data and show success when updating theme call is successful', (done: DoneFn) => { component.themeId = 'id' const themeData = { id: 'id', @@ -413,27 +426,32 @@ describe('ThemeDesignerComponent', () => { themeApiSpy.updateTheme.and.returnValue(of({}) as any) - component.actions[1].actionCallback() - expect(msgServiceSpy.success).toHaveBeenCalledOnceWith({ summaryKey: 'ACTIONS.EDIT.MESSAGE.CHANGE_OK' }) - expect(themeApiSpy.updateTheme).toHaveBeenCalledTimes(1) - const updateArgs = themeApiSpy.updateTheme.calls.mostRecent().args[0] - expect(updateArgs.updateThemeRequest?.resource.name).toBe(newBasicData.name) - expect(updateArgs.updateThemeRequest?.resource.description).toBe(newBasicData.description) - expect(updateArgs.updateThemeRequest?.resource.logoUrl).toBe(newBasicData.logoUrl) - expect(updateArgs.updateThemeRequest?.resource.faviconUrl).toBe(newBasicData.faviconUrl) - expect(updateArgs.updateThemeRequest?.resource.properties).toEqual( - jasmine.objectContaining({ - font: jasmine.objectContaining({ - 'font-family': 'updatedFont' - }), - general: jasmine.objectContaining({ - 'primary-color': 'rgb(255,255,255)' + component.actions$?.subscribe((actions) => { + const updateThemeAction = actions[1] + updateThemeAction.actionCallback() + expect(msgServiceSpy.success).toHaveBeenCalledOnceWith({ summaryKey: 'ACTIONS.EDIT.MESSAGE.CHANGE_OK' }) + expect(themeApiSpy.updateTheme).toHaveBeenCalledTimes(1) + const updateArgs = themeApiSpy.updateTheme.calls.mostRecent().args[0] + expect(updateArgs.updateThemeRequest?.resource.name).toBe(newBasicData.name) + expect(updateArgs.updateThemeRequest?.resource.description).toBe(newBasicData.description) + expect(updateArgs.updateThemeRequest?.resource.logoUrl).toBe(newBasicData.logoUrl) + expect(updateArgs.updateThemeRequest?.resource.faviconUrl).toBe(newBasicData.faviconUrl) + expect(updateArgs.updateThemeRequest?.resource.properties).toEqual( + jasmine.objectContaining({ + font: jasmine.objectContaining({ + 'font-family': 'updatedFont' + }), + general: jasmine.objectContaining({ + 'primary-color': 'rgb(255,255,255)' + }) }) - }) - ) + ) + + done() + }) }) - it('should apply changes when updating current theme is successful', () => { + it('should apply changes when updating current theme is successful', (done: DoneFn) => { component.themeId = 'id' const themeData = { id: 'id', @@ -464,9 +482,13 @@ describe('ThemeDesignerComponent', () => { component.themeIsCurrentUsedTheme = true - component.actions[1].actionCallback() + component.actions$?.subscribe((actions) => { + const updateThemeAction = actions[1] + updateThemeAction.actionCallback() + expect(themeServiceSpy.apply).toHaveBeenCalledOnceWith(updateThemeData as any) - expect(themeServiceSpy.apply).toHaveBeenCalledOnceWith(updateThemeData as any) + done() + }) }) it('should display theme already exists message on theme save failure', () => { @@ -597,12 +619,16 @@ describe('ThemeDesignerComponent', () => { ) }) - it('should display save as new popup on save as click', () => { + it('should display save as new popup on save as click', (done: DoneFn) => { component.saveAsNewPopupDisplay = false - component.actions[2].actionCallback() + component.actions$?.subscribe((actions) => { + const saveAction = actions[2] + saveAction.actionCallback() + expect(component.saveAsNewPopupDisplay).toBe(true) - expect(component.saveAsNewPopupDisplay).toBe(true) + done() + }) }) it('should use form theme name in save as dialog while in NEW mode', () => { diff --git a/src/app/theme/theme-designer/theme-designer.component.ts b/src/app/theme/theme-designer/theme-designer.component.ts index b9dd0da..8302e1f 100644 --- a/src/app/theme/theme-designer/theme-designer.component.ts +++ b/src/app/theme/theme-designer/theme-designer.component.ts @@ -21,7 +21,6 @@ export class ThemeDesignerComponent implements OnInit { @ViewChild('selectedFileInputLogo') selectedFileInputLogo: ElementRef | undefined @ViewChild('selectedFileInputFavicon') selectedFileInputFavicon: ElementRef | undefined - public actions: Action[] = [] // TODO: remove if tests are using actions$ public actions$: Observable | undefined public themes: Theme[] = [] public theme: Theme | undefined @@ -67,19 +66,6 @@ export class ThemeDesignerComponent implements OnInit { this.themeId = route.snapshot.paramMap.get('id') this.themeIsCurrentUsedTheme = this.themeId === this.appStateService.currentPortal$.getValue()?.themeId this.prepareActionButtons() - /* TODO: remove this */ - this.translate - .get([ - 'ACTIONS.CANCEL', - 'ACTIONS.TOOLTIPS.CANCEL_AND_CLOSE', - 'ACTIONS.SAVE', - 'ACTIONS.TOOLTIPS.SAVE', - 'ACTIONS.SAVE_AS', - 'ACTIONS.TOOLTIPS.SAVE_AS' - ]) - .subscribe((data) => { - this.prepareActionButtons_old(data) - }) this.fontForm = new FormGroup({}) this.topbarForm = new FormGroup({}) @@ -175,38 +161,6 @@ export class ThemeDesignerComponent implements OnInit { this.loadThemeTemplates() } - /* TODO: remove this */ - private prepareActionButtons_old(data: any): void { - this.actions = [] // provoke change event - this.actions.push( - { - label: data['ACTIONS.CANCEL'], - title: data['ACTIONS.TOOLTIPS.CANCEL_AND_CLOSE'], - actionCallback: () => this.close(), - icon: 'pi pi-times', - show: 'always', - permission: 'THEME#VIEW' - }, - { - label: data['ACTIONS.SAVE'], - title: data['ACTIONS.TOOLTIPS.SAVE'], - actionCallback: () => this.updateTheme(), - icon: 'pi pi-save', - show: 'always', - conditional: true, - showCondition: this.mode === 'EDIT', - permission: 'THEME#SAVE' - }, - { - label: data['ACTIONS.SAVE_AS'], - title: data['ACTIONS.TOOLTIPS.SAVE_AS'], - actionCallback: () => this.saveAsNewPopup(), - icon: 'pi pi-plus-circle', - show: 'always', - permission: 'THEME#CREATE' - } - ) - } private prepareActionButtons(): void { this.actions$ = this.translate .get([ diff --git a/src/app/theme/theme-detail/theme-detail.component.spec.ts b/src/app/theme/theme-detail/theme-detail.component.spec.ts index 260f2a1..d392557 100644 --- a/src/app/theme/theme-detail/theme-detail.component.spec.ts +++ b/src/app/theme/theme-detail/theme-detail.component.spec.ts @@ -405,7 +405,7 @@ describe('ThemeDetailComponent', () => { ) expect(FileSaver.saveAs).toHaveBeenCalledOnceWith(jasmine.any(Blob), 'themeName_Theme.json') }) - /* + it('should display error on theme export fail', () => { themesApiSpy.exportThemes.and.returnValue(throwError(() => new Error())) component.theme = { @@ -414,5 +414,4 @@ describe('ThemeDetailComponent', () => { component.onExportTheme() expect(msgServiceSpy.error).toHaveBeenCalledOnceWith({ summaryKey: 'ACTIONS.EXPORT.EXPORT_THEME_FAIL' }) }) - */ }) diff --git a/src/app/theme/theme-import/theme-import.component.ts b/src/app/theme/theme-import/theme-import.component.ts index 076fc3e..6f29efd 100644 --- a/src/app/theme/theme-import/theme-import.component.ts +++ b/src/app/theme/theme-import/theme-import.component.ts @@ -98,7 +98,7 @@ export class ThemeImportComponent implements OnInit { private isThemeImportRequestDTO(obj: unknown): obj is ThemeSnapshot { const dto = obj as ThemeSnapshot - return !!(typeof dto === 'object' && dto && dto.themes) + return !!(typeof dto === 'object' && dto?.themes) } private getThemes(emit: boolean): void {