diff --git a/src/app/product-store/product-detail/product-detail.component.ts b/src/app/product-store/product-detail/product-detail.component.ts
index 8b4e679..2f42df8 100644
--- a/src/app/product-store/product-detail/product-detail.component.ts
+++ b/src/app/product-store/product-detail/product-detail.component.ts
@@ -43,6 +43,10 @@ export class ProductDetailComponent implements OnInit {
) {
this.dateFormat = this.config.lang === 'de' ? 'dd.MM.yyyy HH:mm:ss' : 'medium'
this.productName = this.route.snapshot.paramMap.get('name') || ''
+ }
+
+ ngOnInit(): void {
+ console.log('product detail ngOnInit()')
if (this.productName !== '') {
this.changeMode = 'VIEW'
this.loadProduct()
@@ -52,10 +56,6 @@ export class ProductDetailComponent implements OnInit {
}
}
- ngOnInit(): void {
- console.log('product detail ngOnInit()')
- }
-
private loadProduct() {
this.loading = true
this.productApi
@@ -75,15 +75,16 @@ export class ProductDetailComponent implements OnInit {
this.prepareTranslations()
},
error: (err: any) => {
+ console.log('ERR')
this.msgService.error({
- summaryKey: 'DIALOG.LOAD_ERROR',
- detailKey: err.error.indexOf('was not found') > 1 ? 'DIALOG.NOT_FOUND' : err.error
+ summaryKey: 'DIALOG.LOAD_ERROR'
+ // detailKey: err.error.indexOf('was not found') > 1 ? 'DIALOG.NOT_FOUND' : err.error
})
this.close()
}
})
}
- private getProduct() {
+ public getProduct() {
this.loading = true
this.productApi
.getProduct({ id: this.product?.id } as GetProductRequestParams)
@@ -104,11 +105,12 @@ export class ProductDetailComponent implements OnInit {
})
}
- private prepareTranslations(): void {
+ public prepareTranslations(): void {
this.translate
.get([
'ACTIONS.DELETE.LABEL',
'ACTIONS.DELETE.TOOLTIP',
+ 'ACTIONS.DELETE.MESSAGE',
'ACTIONS.EDIT.LABEL',
'ACTIONS.EDIT.TOOLTIP',
'ACTIONS.CANCEL',
@@ -189,18 +191,18 @@ export class ProductDetailComponent implements OnInit {
}
}
- private close(): void {
+ public close(): void {
this.router.navigate(['./..'], { relativeTo: this.route })
}
public onClose() {
this.close()
}
- private onEdit() {
+ public onEdit() {
this.getProduct()
this.changeMode = 'EDIT'
this.prepareTranslations()
}
- private onCancel() {
+ public onCancel() {
if (this.changeMode === 'EDIT') {
this.changeMode = 'VIEW'
this.getProduct()
@@ -210,7 +212,7 @@ export class ProductDetailComponent implements OnInit {
this.close()
}
}
- private onSave() {
+ public onSave() {
this.productPropsComponent.onSubmit()
}
public onCreate(data: any) {
diff --git a/src/app/product-store/product-detail/product-props/product-props.component.html b/src/app/product-store/product-detail/product-props/product-props.component.html
index 8730859..f08f9a1 100644
--- a/src/app/product-store/product-detail/product-props/product-props.component.html
+++ b/src/app/product-store/product-detail/product-props/product-props.component.html
@@ -10,7 +10,9 @@
-
+
diff --git a/src/app/product-store/product-detail/product-props/product-props.component.spec.ts b/src/app/product-store/product-detail/product-props/product-props.component.spec.ts
new file mode 100644
index 0000000..5726b9e
--- /dev/null
+++ b/src/app/product-store/product-detail/product-props/product-props.component.spec.ts
@@ -0,0 +1,218 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core'
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'
+import { HttpClient } from '@angular/common/http'
+import { HttpClientTestingModule } from '@angular/common/http/testing'
+import { RouterTestingModule } from '@angular/router/testing'
+import { TranslateLoader, TranslateModule } from '@ngx-translate/core'
+import { of, throwError } from 'rxjs'
+import { FormControl, FormGroup, Validators } from '@angular/forms'
+
+import { PortalMessageService } from '@onecx/portal-integration-angular'
+import { HttpLoaderFactory } from 'src/app/shared/shared.module'
+import { ProductPropertyComponent, ProductDetailForm } from './product-props.component'
+import { ProductsAPIService } from 'src/app/generated'
+
+describe('ProductPropertyComponent', () => {
+ let component: ProductPropertyComponent
+ let fixture: ComponentFixture
+
+ const msgServiceSpy = jasmine.createSpyObj('PortalMessageService', ['success', 'error', 'info'])
+ const apiServiceSpy = {
+ createProduct: jasmine.createSpy('createProduct').and.returnValue(of({})),
+ updateProduct: jasmine.createSpy('updateProduct').and.returnValue(of({}))
+ }
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [ProductPropertyComponent],
+ imports: [
+ HttpClientTestingModule,
+ RouterTestingModule,
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useFactory: HttpLoaderFactory,
+ deps: [HttpClient]
+ }
+ })
+ ],
+ schemas: [NO_ERRORS_SCHEMA],
+ providers: [
+ { provide: ProductsAPIService, useValue: apiServiceSpy },
+ { provide: PortalMessageService, useValue: msgServiceSpy }
+ ]
+ }).compileComponents()
+ }))
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ProductPropertyComponent)
+ component = fixture.componentInstance
+ fixture.detectChanges()
+ })
+
+ afterEach(() => {
+ msgServiceSpy.success.calls.reset()
+ msgServiceSpy.error.calls.reset()
+ msgServiceSpy.info.calls.reset()
+ apiServiceSpy.createProduct.calls.reset()
+ apiServiceSpy.updateProduct.calls.reset()
+ })
+
+ it('should create', () => {
+ expect(component).toBeTruthy()
+ })
+
+ it('should patchValue in formGroup onChanges if product', () => {
+ const product = {
+ id: 'id',
+ name: 'name',
+ basePath: 'path'
+ }
+ component.product = product
+ spyOn(component.formGroup, 'patchValue')
+
+ component.ngOnChanges()
+
+ expect(component.formGroup.patchValue).toHaveBeenCalledWith({ ...product })
+ expect(component.product.name).toEqual(product.name)
+ })
+
+ it('should reset formGroup onChanges if no product', () => {
+ spyOn(component.formGroup, 'reset')
+
+ component.ngOnChanges()
+
+ expect(component.formGroup.reset).toHaveBeenCalled()
+ })
+
+ it('should call createProduct onSubmit in new mode', () => {
+ apiServiceSpy.createProduct.and.returnValue(of({}))
+ const formGroup = new FormGroup({
+ id: new FormControl('id'),
+ name: new FormControl('name'),
+ operator: new FormControl(null),
+ version: new FormControl('version'),
+ description: new FormControl(null),
+ imageUrl: new FormControl(null),
+ basePath: new FormControl('path'),
+ displayName: new FormControl('display'),
+ iconName: new FormControl('icon'),
+ classifications: new FormControl(null)
+ })
+ component.formGroup = formGroup as FormGroup
+ component.changeMode = 'NEW'
+
+ component.onSubmit()
+
+ expect(apiServiceSpy.createProduct).toHaveBeenCalled()
+ expect(msgServiceSpy.success).toHaveBeenCalledWith({ summaryKey: 'ACTIONS.CREATE.MESSAGE.PRODUCT_OK' })
+ })
+
+ it('should call updateProduct onSubmit in view mode', () => {
+ apiServiceSpy.updateProduct.and.returnValue(of({}))
+ const formGroup = new FormGroup({
+ id: new FormControl('id'),
+ name: new FormControl('name'),
+ operator: new FormControl(null),
+ version: new FormControl('version'),
+ description: new FormControl(null),
+ imageUrl: new FormControl(null),
+ basePath: new FormControl('path'),
+ displayName: new FormControl('display'),
+ iconName: new FormControl('icon'),
+ classifications: new FormControl(null)
+ })
+ component.formGroup = formGroup as FormGroup
+ component.changeMode = 'VIEW'
+
+ component.onSubmit()
+
+ expect(apiServiceSpy.updateProduct).toHaveBeenCalled()
+ expect(msgServiceSpy.success).toHaveBeenCalledWith({ summaryKey: 'ACTIONS.EDIT.MESSAGE.PRODUCT_OK' })
+ })
+
+ it('should display error if searchProducts fails', () => {
+ apiServiceSpy.updateProduct.and.returnValue(throwError(() => new Error()))
+ const formGroup = new FormGroup({
+ id: new FormControl('id'),
+ name: new FormControl('name'),
+ operator: new FormControl(null),
+ version: new FormControl('version'),
+ description: new FormControl(null),
+ imageUrl: new FormControl(null),
+ basePath: new FormControl('path'),
+ displayName: new FormControl('display'),
+ iconName: new FormControl('icon'),
+ classifications: new FormControl(null)
+ })
+ component.formGroup = formGroup as FormGroup
+ component.changeMode = 'VIEW'
+
+ component.onSubmit()
+
+ expect(component.formGroup.valid).toBeTrue()
+ expect(msgServiceSpy.error).toHaveBeenCalledWith({
+ summaryKey: 'ACTIONS.EDIT.MESSAGE.PRODUCT_NOK'
+ })
+ })
+
+ it('should display error if searchProducts fails', () => {
+ apiServiceSpy.createProduct.and.returnValue(throwError(() => new Error()))
+ const formGroup = new FormGroup({
+ id: new FormControl('id'),
+ name: new FormControl('name'),
+ operator: new FormControl(null),
+ version: new FormControl('version'),
+ description: new FormControl(null),
+ imageUrl: new FormControl(null),
+ basePath: new FormControl('path'),
+ displayName: new FormControl('display'),
+ iconName: new FormControl('icon'),
+ classifications: new FormControl(null)
+ })
+ component.formGroup = formGroup as FormGroup
+ component.changeMode = 'NEW'
+
+ component.onSubmit()
+
+ expect(component.formGroup.valid).toBeTrue()
+ expect(msgServiceSpy.error).toHaveBeenCalledWith({
+ summaryKey: 'ACTIONS.CREATE.MESSAGE.PRODUCT_NOK'
+ })
+ })
+
+ it('should display error onSubmit if formGroup invalid', () => {
+ const formGroup = new FormGroup({
+ id: new FormControl(null, Validators.required),
+ name: new FormControl('name'),
+ operator: new FormControl(null),
+ version: new FormControl('version'),
+ description: new FormControl(null),
+ imageUrl: new FormControl(null),
+ basePath: new FormControl('path'),
+ displayName: new FormControl('display'),
+ iconName: new FormControl('icon'),
+ classifications: new FormControl(null)
+ })
+ component.formGroup = formGroup as FormGroup
+
+ component.onSubmit()
+
+ expect(component.formGroup.valid).toBeFalse()
+ expect(msgServiceSpy.error).toHaveBeenCalledWith({
+ summaryKey: 'VALIDATION.FORM_INVALID'
+ })
+ })
+
+ it('should display error onSubmit if formGroup invalid', () => {
+ const event = {
+ target: {
+ files: ['file']
+ }
+ }
+
+ component.onFileUpload(event as any, 'logo')
+
+ expect(component.formGroup.valid).toBeFalse()
+ })
+})
diff --git a/src/app/product-store/product-detail/product-props/product-props.component.ts b/src/app/product-store/product-detail/product-props/product-props.component.ts
index 6d7efa7..bd1bdb5 100644
--- a/src/app/product-store/product-detail/product-props/product-props.component.ts
+++ b/src/app/product-store/product-detail/product-props/product-props.component.ts
@@ -1,6 +1,6 @@
-import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'
+import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'
import { FormControl, FormGroup, Validators } from '@angular/forms'
-import { TranslateService } from '@ngx-translate/core'
+// import { TranslateService } from '@ngx-translate/core'
import { SelectItem } from 'primeng/api'
import { PortalMessageService } from '@onecx/portal-integration-angular'
@@ -9,7 +9,7 @@ import { IconService } from '../../../shared/iconservice'
import { dropDownSortItemsByLabel } from 'src/app/shared/utils'
// import { setFetchUrls } from '../../../shared/utils'
-interface ProductDetailForm {
+export interface ProductDetailForm {
id: FormControl
name: FormControl
operator: FormControl
@@ -42,7 +42,7 @@ export class ProductPropertyComponent implements OnChanges {
constructor(
private icon: IconService,
private productApi: ProductsAPIService,
- private translate: TranslateService,
+ // private translate: TranslateService,
private msgService: PortalMessageService
) {
this.formGroup = new FormGroup({
@@ -50,7 +50,7 @@ export class ProductPropertyComponent implements OnChanges {
name: new FormControl(null, [Validators.required, Validators.minLength(2), Validators.maxLength(255)]),
displayName: new FormControl(null, [Validators.required, Validators.minLength(2), Validators.maxLength(255)]),
operator: new FormControl(null),
- version: new FormControl(null, [Validators.maxLength(255)]),
+ version: new FormControl(null, [Validators.required, Validators.maxLength(255)]),
description: new FormControl(null, [Validators.maxLength(255)]),
imageUrl: new FormControl(null, [Validators.maxLength(255)]),
basePath: new FormControl(null, [Validators.required, Validators.maxLength(255)]),
@@ -61,7 +61,7 @@ export class ProductPropertyComponent implements OnChanges {
this.iconItems.sort(dropDownSortItemsByLabel)
}
- ngOnChanges(changes: SimpleChanges): void {
+ ngOnChanges(): void {
if (this.product) {
this.formGroup.patchValue({
...this.product
@@ -96,16 +96,19 @@ export class ProductPropertyComponent implements OnChanges {
})
.subscribe({
next: (data) => {
+ console.log('NEXT')
this.msgService.success({ summaryKey: 'ACTIONS.CREATE.MESSAGE.PRODUCT_OK' })
this.productCreated.emit(data)
},
error: (err) => {
- err.error.key && err.error.key === 'PERSIST_ENTITY_FAILED'
+ // console.log('ERR', err)
+ /* err.error.key && err.error.key === 'PERSIST_ENTITY_FAILED'
? this.msgService.error({
summaryKey: 'ACTIONS.CREATE.MESSAGE.PRODUCT_NOK',
detailKey: 'VALIDATION.PRODUCT.UNIQUE_CONSTRAINT'
})
- : this.msgService.error({ summaryKey: 'ACTIONS.CREATE.MESSAGE.PRODUCT_NOK' })
+ : */
+ this.msgService.error({ summaryKey: 'ACTIONS.CREATE.MESSAGE.PRODUCT_NOK' })
}
})
}
@@ -131,12 +134,13 @@ export class ProductPropertyComponent implements OnChanges {
this.productNameChanged.emit(this.productName !== this.formGroup.value['name'])
},
error: (err) => {
- err.error.key && err.error.key === 'PERSIST_ENTITY_FAILED'
+ /* err.error.key && err.error.key === 'PERSIST_ENTITY_FAILED'
? this.msgService.error({
summaryKey: 'ACTIONS.EDIT.MESSAGE.PRODUCT_NOK',
detailKey: 'VALIDATION.PRODUCT.UNIQUE_CONSTRAINT'
})
- : this.msgService.error({ summaryKey: 'ACTIONS.EDIT.MESSAGE.PRODUCT_NOK' })
+ : */
+ this.msgService.error({ summaryKey: 'ACTIONS.EDIT.MESSAGE.PRODUCT_NOK' })
}
})
}
diff --git a/src/app/product-store/product-detail/product.detail.component.spec.ts b/src/app/product-store/product-detail/product.detail.component.spec.ts
new file mode 100644
index 0000000..c2be4f3
--- /dev/null
+++ b/src/app/product-store/product-detail/product.detail.component.spec.ts
@@ -0,0 +1,217 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core'
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'
+import { HttpClient } from '@angular/common/http'
+import { HttpClientTestingModule } from '@angular/common/http/testing'
+import { RouterTestingModule } from '@angular/router/testing'
+import { TranslateLoader, TranslateModule } from '@ngx-translate/core'
+import { of, throwError } from 'rxjs'
+
+import { PortalMessageService } from '@onecx/portal-integration-angular'
+import { HttpLoaderFactory } from 'src/app/shared/shared.module'
+import { ProductDetailComponent } from './product-detail.component'
+import { ProductsAPIService } from 'src/app/generated'
+
+describe('ProductDetailComponent', () => {
+ let component: ProductDetailComponent
+ let fixture: ComponentFixture
+
+ const msgServiceSpy = jasmine.createSpyObj('PortalMessageService', ['success', 'error', 'info'])
+ const apiServiceSpy = {
+ searchProducts: jasmine.createSpy('searchProducts').and.returnValue(of({})),
+ getProduct: jasmine.createSpy('getProduct').and.returnValue(of({}))
+ }
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [ProductDetailComponent],
+ imports: [
+ HttpClientTestingModule,
+ RouterTestingModule,
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useFactory: HttpLoaderFactory,
+ deps: [HttpClient]
+ }
+ })
+ ],
+ schemas: [NO_ERRORS_SCHEMA],
+ providers: [
+ { provide: ProductsAPIService, useValue: apiServiceSpy },
+ { provide: PortalMessageService, useValue: msgServiceSpy }
+ ]
+ }).compileComponents()
+ }))
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ProductDetailComponent)
+ component = fixture.componentInstance
+ fixture.detectChanges()
+ })
+
+ afterEach(() => {
+ msgServiceSpy.success.calls.reset()
+ msgServiceSpy.error.calls.reset()
+ msgServiceSpy.info.calls.reset()
+ apiServiceSpy.searchProducts.calls.reset()
+ apiServiceSpy.getProduct.calls.reset()
+ })
+
+ it('should create', () => {
+ expect(component).toBeTruthy()
+ })
+
+ it('should be set up correctly onInit if no product name', () => {
+ component.productName = 'name'
+
+ component.ngOnInit()
+
+ expect(component.changeMode).toEqual('VIEW')
+ })
+
+ it('should search products onInit', () => {
+ const productPageResult = {
+ stream: [
+ {
+ id: 'id',
+ name: 'name',
+ basePath: 'path'
+ }
+ ]
+ }
+ apiServiceSpy.searchProducts.and.returnValue(of(productPageResult))
+ apiServiceSpy.getProduct.and.returnValue(of({ id: 'id' }))
+ component.productName = 'name'
+
+ component.ngOnInit()
+
+ expect(component.product?.id).toEqual(productPageResult.stream[0].id)
+ })
+
+ it('should display error if searchProducts fails', () => {
+ const errorMsg = 'Product was not found'
+ const mockError = new Error(errorMsg)
+ apiServiceSpy.searchProducts.and.returnValue(throwError(() => mockError))
+ component.productName = 'name'
+
+ component.ngOnInit()
+
+ expect(msgServiceSpy.error).toHaveBeenCalledWith({
+ summaryKey: 'DIALOG.LOAD_ERROR'
+ })
+ })
+
+ it('should prepare action buttons callbacks on init: close', () => {
+ spyOn(component, 'close')
+
+ component.ngOnInit()
+
+ const action = component.actions[0]
+ action.actionCallback()
+
+ expect(component.close).toHaveBeenCalled()
+ })
+
+ it('should prepare action buttons on init: onEdit', () => {
+ spyOn(component, 'onEdit')
+
+ component.ngOnInit()
+
+ const action = component.actions[1]
+ action.actionCallback()
+
+ expect(component.onEdit).toHaveBeenCalled()
+ })
+
+ it('should prepare action buttons on init: onCancel', () => {
+ spyOn(component, 'onCancel')
+
+ component.ngOnInit()
+
+ const action = component.actions[2]
+ action.actionCallback()
+
+ expect(component.onCancel).toHaveBeenCalled()
+ })
+
+ it('should prepare action buttons on init: onSave', () => {
+ spyOn(component, 'onSave')
+
+ component.ngOnInit()
+
+ const action = component.actions[3]
+ action.actionCallback()
+
+ expect(component.onSave).toHaveBeenCalled()
+ })
+
+ it('should call close() onClose', () => {
+ spyOn(component, 'close')
+
+ component.onClose()
+
+ expect(component.close).toHaveBeenCalled()
+ })
+
+ it('should behave correctly onEdit', () => {
+ spyOn(component, 'getProduct')
+
+ component.onEdit()
+
+ expect(component.changeMode).toEqual('EDIT')
+ expect(component.getProduct).toHaveBeenCalled()
+ })
+
+ it('should behave correctly onCancel in edit mode', () => {
+ spyOn(component, 'getProduct')
+ spyOn(component, 'prepareTranslations')
+ component.changeMode = 'EDIT'
+
+ component.onCancel()
+
+ expect(component.changeMode).toEqual('VIEW')
+ expect(component.getProduct).toHaveBeenCalled()
+ expect(component.prepareTranslations).toHaveBeenCalled()
+ })
+
+ it('should behave correctly onCancel in new mode', () => {
+ spyOn(component, 'close')
+ component.changeMode = 'NEW'
+
+ component.onCancel()
+
+ expect(component.close).toHaveBeenCalled()
+ })
+
+ xit('should behave correctly onSave', () => {
+ spyOn(component.productPropsComponent, 'onSubmit')
+
+ component.onSave()
+
+ expect(component.productPropsComponent.onSubmit).toHaveBeenCalled()
+ })
+
+ it('should behave correctly onCreate', () => {
+ const data: any = { id: 'id ' }
+
+ component.onCreate(data)
+
+ expect(component.product).toEqual(data)
+ })
+
+ it('should behave correctly onNameChange if change true', () => {
+ spyOn(component, 'close')
+
+ component.onNameChange(true)
+
+ expect(component.close).toHaveBeenCalled()
+ })
+
+ it('should behave correctly onNameChange if change false', () => {
+ spyOn(component, 'getProduct')
+
+ component.onNameChange(false)
+
+ expect(component.getProduct).toHaveBeenCalled()
+ })
+})
diff --git a/src/app/product-store/product-search/product-search.component.spec.ts b/src/app/product-store/product-search/product-search.component.spec.ts
index ca5cede..bfa3f25 100644
--- a/src/app/product-store/product-search/product-search.component.spec.ts
+++ b/src/app/product-store/product-search/product-search.component.spec.ts
@@ -3,7 +3,9 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'
import { HttpClient } from '@angular/common/http'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { RouterTestingModule } from '@angular/router/testing'
+import { Router, ActivatedRoute } from '@angular/router'
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'
+import { of } from 'rxjs'
import { PortalMessageService } from '@onecx/portal-integration-angular'
import { HttpLoaderFactory } from 'src/app/shared/shared.module'
@@ -12,8 +14,11 @@ import { ProductSearchComponent } from './product-search.component'
describe('ProductSearchComponent', () => {
let component: ProductSearchComponent
let fixture: ComponentFixture
+ let router: Router
+ let routeSpy: jasmine.SpyObj
const msgServiceSpy = jasmine.createSpyObj('PortalMessageService', ['success', 'error', 'info'])
+ const translateServiceSpy = jasmine.createSpyObj('TranslateService', ['get'])
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
@@ -30,13 +35,17 @@ describe('ProductSearchComponent', () => {
})
],
schemas: [NO_ERRORS_SCHEMA],
- providers: [{ provide: PortalMessageService, useValue: msgServiceSpy }]
+ providers: [
+ { provide: PortalMessageService, useValue: msgServiceSpy },
+ { provide: ActivatedRoute, useValue: routeSpy }
+ ]
}).compileComponents()
}))
beforeEach(() => {
fixture = TestBed.createComponent(ProductSearchComponent)
component = fixture.componentInstance
+ router = TestBed.inject(Router)
fixture.detectChanges()
})
@@ -49,4 +58,73 @@ describe('ProductSearchComponent', () => {
it('should create', () => {
expect(component).toBeTruthy()
})
+
+ it('should prepare action buttons on init', () => {
+ translateServiceSpy.get.and.returnValue(of({ 'ACTIONS.CREATE.PRODUCT': 'Create' }))
+ spyOn(component, 'onNewProduct')
+
+ component.ngOnInit()
+
+ const action = component.actions[0]
+ action.actionCallback()
+
+ expect(component.onNewProduct).toHaveBeenCalled()
+ })
+
+ it('should set correct value onLayoutChange', () => {
+ const viewMode = 'EDIT'
+
+ component.onLayoutChange(viewMode)
+
+ expect(component.viewMode).toEqual('EDIT')
+ })
+
+ it('should set correct values onFilterChange', () => {
+ const filter = 'filter'
+
+ component.onFilterChange(filter)
+
+ expect(component.filter).toEqual(filter)
+ })
+
+ it('should set correct value onSortChange', () => {
+ const sortField = 'field'
+
+ component.onSortChange(sortField)
+
+ expect(component.sortField).toEqual(sortField)
+ })
+
+ it('should set correct value onSortDirChange', () => {
+ const asc = true
+
+ component.onSortDirChange(asc)
+
+ expect(component.sortOrder).toEqual(-1)
+ })
+
+ it('should call loadProducts onSearch', () => {
+ translateServiceSpy.get.and.returnValue(of({ 'ACTIONS.CREATE.PRODUCT': 'Create' }))
+ spyOn(component, 'loadProducts')
+
+ component.onSearch()
+
+ expect(component.loadProducts).toHaveBeenCalled()
+ })
+
+ it('should reset productSearchCriteriaGroup onSearchReset', () => {
+ spyOn(component.productSearchCriteriaGroup, 'reset')
+
+ component.onSearchReset()
+
+ expect(component.productSearchCriteriaGroup.reset).toHaveBeenCalled()
+ })
+
+ it('should navigate to new product on onNewProduct', () => {
+ const routerSpy = spyOn(router, 'navigate')
+
+ component.onNewProduct()
+
+ expect(routerSpy).toHaveBeenCalledWith(['./new'], { relativeTo: routeSpy })
+ })
})
diff --git a/src/app/shared/can-active-guard.service.spec.ts b/src/app/shared/can-active-guard.service.spec.ts
new file mode 100644
index 0000000..7e7156c
--- /dev/null
+++ b/src/app/shared/can-active-guard.service.spec.ts
@@ -0,0 +1,74 @@
+import { BehaviorSubject, Observable, of } from 'rxjs'
+import { CanActivateGuard } from './can-active-guard.service'
+
+let canActivateGuard: CanActivateGuard
+
+describe('CanActivateGuard', () => {
+ const translateServiceSpy = jasmine.createSpyObj('TranslateService', ['setDefaultLang', 'use'])
+
+ const configSpy = jasmine.createSpyObj('ConfigurationService', [], { lang$: new BehaviorSubject(undefined) })
+
+ const activatedRouteSpy = jasmine.createSpyObj('ActivatedRouteSnapshot', [], {
+ routeConfig: {
+ path: 'path'
+ }
+ })
+ const routerStateeSpy = jasmine.createSpyObj('RouterStateSnapshot', [], {
+ routeConfig: {
+ path: 'path'
+ }
+ })
+
+ beforeEach(async () => {
+ canActivateGuard = new CanActivateGuard(translateServiceSpy, configSpy)
+ translateServiceSpy.setDefaultLang.calls.reset()
+ translateServiceSpy.use.calls.reset()
+ })
+
+ it('should use default language if current not supported and return true', (doneFn: DoneFn) => {
+ const langSpy = Object.getOwnPropertyDescriptor(configSpy, 'lang$')?.get as jasmine.Spy<
+ () => BehaviorSubject
+ >
+ langSpy.and.returnValue(new BehaviorSubject('pl'))
+ // spyOn(console, 'log')
+ translateServiceSpy.use.and.returnValue(of({}))
+
+ const resultObs = canActivateGuard.canActivate(activatedRouteSpy, routerStateeSpy) as Observable
+ resultObs.subscribe({
+ next: (result) => {
+ expect(result).toBe(true)
+ doneFn()
+ },
+ error: () => {
+ doneFn.fail
+ }
+ })
+
+ expect(translateServiceSpy.setDefaultLang).toHaveBeenCalledWith('en')
+ expect(translateServiceSpy.use).toHaveBeenCalledWith('en')
+ })
+
+ it('should use provided language if current supported and return true', (doneFn: DoneFn) => {
+ const langSpy = Object.getOwnPropertyDescriptor(configSpy, 'lang$')?.get as jasmine.Spy<
+ () => BehaviorSubject
+ >
+ langSpy.and.returnValue(new BehaviorSubject('de'))
+ // spyOn(console, 'log')
+ translateServiceSpy.use.and.returnValue(of({}))
+
+ const resultObs = canActivateGuard.canActivate(activatedRouteSpy, routerStateeSpy) as Observable
+ resultObs.subscribe({
+ next: (result) => {
+ expect(result).toBe(true)
+ doneFn()
+ },
+ error: () => {
+ doneFn.fail
+ }
+ })
+
+ expect(translateServiceSpy.setDefaultLang).toHaveBeenCalledWith('en')
+ // expect(console.log).toHaveBeenCalledOnceWith('user profile GUARD path')
+ expect(translateServiceSpy.use).toHaveBeenCalledWith('de')
+ })
+})
diff --git a/src/app/shared/image-container/image-container.component.spec.ts b/src/app/shared/image-container/image-container.component.spec.ts
new file mode 100644
index 0000000..262b97d
--- /dev/null
+++ b/src/app/shared/image-container/image-container.component.spec.ts
@@ -0,0 +1,63 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'
+import { ImageContainerComponent } from './image-container.component'
+
+describe('ImageContainerComponent', () => {
+ let component: ImageContainerComponent
+ let fixture: ComponentFixture
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [ImageContainerComponent]
+ }).compileComponents()
+ }))
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ImageContainerComponent)
+ component = fixture.componentInstance
+ fixture.detectChanges()
+ })
+
+ it('should create', () => {
+ expect(component).toBeTruthy()
+ })
+
+ describe('ngOnChanges', () => {
+ it('should prepend apiPrefix to imageUrl if not starting with http/https and not already prefixed', () => {
+ const testUrl = 'path/to/image.jpg'
+ const expectedUrl = component['apiPrefix'] + testUrl
+
+ component.imageUrl = testUrl
+ component.ngOnChanges({
+ imageUrl: {
+ currentValue: testUrl,
+ previousValue: null,
+ firstChange: true,
+ isFirstChange: () => true
+ }
+ })
+
+ expect(component.imageUrl).toBe(expectedUrl)
+ })
+
+ it('should not modify imageUrl if it starts with http/https', () => {
+ const testUrl = 'http://path/to/image.jpg'
+ component.imageUrl = testUrl
+ component.ngOnChanges({
+ imageUrl: {
+ currentValue: testUrl,
+ previousValue: null,
+ firstChange: true,
+ isFirstChange: () => true
+ }
+ })
+
+ expect(component.imageUrl).toBe(testUrl)
+ })
+ })
+
+ it('onImageError should set displayPlaceHolder to true', () => {
+ component.onImageError()
+
+ expect(component.displayPlaceHolder).toBeTrue()
+ })
+})
diff --git a/src/app/shared/label.resolver.spec.ts b/src/app/shared/label.resolver.spec.ts
new file mode 100644
index 0000000..97b5285
--- /dev/null
+++ b/src/app/shared/label.resolver.spec.ts
@@ -0,0 +1,43 @@
+import { LabelResolver } from './label.resolver'
+
+let labelResolver: LabelResolver
+
+describe('LabelResolver', () => {
+ const translateServiceSpy = jasmine.createSpyObj('TranslateService', ['instant'])
+
+ const activatedRouteSpy = jasmine.createSpyObj('ActivatedRouteSnapshot', [], {
+ routeConfig: {
+ path: 'path'
+ },
+ data: {}
+ })
+
+ const routerStateSpy = jasmine.createSpyObj('RouterStateSnapshot', [''])
+
+ beforeEach(async () => {
+ labelResolver = new LabelResolver(translateServiceSpy)
+ translateServiceSpy.instant.calls.reset()
+ const dataSpy = Object.getOwnPropertyDescriptor(activatedRouteSpy, 'data')?.get as jasmine.Spy<() => {}>
+ dataSpy.and.returnValue({})
+ })
+
+ it('should translate if breadcrumb is present', () => {
+ const dataSpy = Object.getOwnPropertyDescriptor(activatedRouteSpy, 'data')?.get as jasmine.Spy<() => {}>
+ dataSpy.and.returnValue({
+ breadcrumb: 'defined'
+ })
+ translateServiceSpy.instant.and.returnValue('translation')
+
+ const result = labelResolver.resolve(activatedRouteSpy, routerStateSpy)
+
+ expect(result).toBe('translation')
+ expect(translateServiceSpy.instant).toHaveBeenCalledOnceWith('defined')
+ })
+
+ it('should use route path if breadcrumb is not present', () => {
+ const result = labelResolver.resolve(activatedRouteSpy, routerStateSpy)
+
+ expect(result).toBe('path')
+ expect(translateServiceSpy.instant).toHaveBeenCalledTimes(0)
+ })
+})
diff --git a/src/app/shared/shared.module.spec.ts b/src/app/shared/shared.module.spec.ts
new file mode 100644
index 0000000..6cbfe53
--- /dev/null
+++ b/src/app/shared/shared.module.spec.ts
@@ -0,0 +1,46 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core'
+import { TestBed } from '@angular/core/testing'
+import { HttpClient } from '@angular/common/http'
+import { HttpClientTestingModule } from '@angular/common/http/testing'
+
+import { MfeInfo, TranslateCombinedLoader } from '@onecx/portal-integration-angular'
+import { basePathProvider, HttpLoaderFactory } from './shared.module'
+
+describe('SharedModule', () => {
+ let httpClient: HttpClient
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpClientTestingModule],
+ schemas: [NO_ERRORS_SCHEMA]
+ })
+
+ httpClient = TestBed.inject(HttpClient)
+ })
+
+ it('should return the correct basePath with mfeInfo', () => {
+ const mfeInfo: MfeInfo = {
+ mountPath: '',
+ remoteBaseUrl: 'http://localhost:4200/',
+ baseHref: '',
+ shellName: ''
+ }
+
+ const result = basePathProvider(mfeInfo)
+
+ expect(result).toEqual('http://localhost:4200/product-store-bff')
+ })
+
+ it('should return a translate loader', () => {
+ const mfeInfo: MfeInfo = {
+ mountPath: '',
+ remoteBaseUrl: 'http://localhost:4200/',
+ baseHref: '',
+ shellName: ''
+ }
+
+ const result = HttpLoaderFactory(httpClient, mfeInfo)
+
+ expect(result).toBeInstanceOf(TranslateCombinedLoader)
+ })
+})
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index efbe245..15771ce 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -44,16 +44,16 @@ import { CanActivateGuard } from './can-active-guard.service'
import { ImageContainerComponent } from './image-container/image-container.component'
export const basePathProvider = (mfeInfo: MfeInfo) => {
- console.log(
+ /* console.log(
'Base path provider: ' + (mfeInfo ? mfeInfo.remoteBaseUrl + '' + environment.apiPrefix : '' + environment.apiPrefix)
- )
+ ) */
return mfeInfo ? mfeInfo.remoteBaseUrl + '' + environment.apiPrefix : '' + environment.apiPrefix
}
export function HttpLoaderFactory(http: HttpClient, mfeInfo: MfeInfo) {
- if (mfeInfo) {
+ /* if (mfeInfo) {
console.log(`Configuring translation loader ${mfeInfo?.remoteBaseUrl}`)
- }
+ } */
// if running standalone then load the app assets directly from remote base URL
const appAssetPrefix = mfeInfo && mfeInfo.remoteBaseUrl ? mfeInfo.remoteBaseUrl : './'
return new TranslateCombinedLoader(
diff --git a/src/app/shared/utils.spec.ts b/src/app/shared/utils.spec.ts
new file mode 100644
index 0000000..2caf2a6
--- /dev/null
+++ b/src/app/shared/utils.spec.ts
@@ -0,0 +1,92 @@
+import { SelectItem } from 'primeng/api'
+
+import {
+ limitText,
+ setFetchUrls,
+ dropDownSortItemsByLabel,
+ dropDownGetLabelByValue,
+ sortByLocale,
+ filterObject
+} from './utils'
+
+describe('util functions', () => {
+ describe('limitText', () => {
+ it('should truncate text that exceeds the specified limit', () => {
+ const result = limitText('hello', 4)
+
+ expect(result).toEqual('hell...')
+ })
+
+ it('should return the original text if it does not exceed the limit', () => {
+ const result = limitText('hello', 6)
+
+ expect(result).toEqual('hello')
+ })
+
+ it('should return an empty string for undefined input', () => {
+ const str: any = undefined
+ const result = limitText(str, 5)
+
+ expect(result).toEqual('')
+ })
+ })
+
+ describe('setFetchUrls', () => {
+ it('should prepend apiPrefix to a relative URL', () => {
+ const result = setFetchUrls('ahm-api', '/am')
+
+ expect(result).toEqual('ahm-api/am')
+ })
+
+ it('should return the original URL if it is absolute', () => {
+ const result = setFetchUrls('ahm-api', 'http://am')
+
+ expect(result).toEqual('http://am')
+ })
+ })
+
+ describe('dropDownSortItemsByLabel', () => {
+ it('should correctly sort items by label', () => {
+ const items: SelectItem[] = [
+ { label: 'label2', value: 2 },
+ { label: 'label1', value: 1 }
+ ]
+
+ const sortedItems = items.sort(dropDownSortItemsByLabel)
+
+ expect(sortedItems[0].label).toEqual('label1')
+ })
+ })
+
+ describe('dropDownGetLabelByValue', () => {
+ it('should return the label corresponding to the value', () => {
+ const items: SelectItem[] = [
+ { label: 'label2', value: 2 },
+ { label: 'label1', value: 1 }
+ ]
+
+ const result = dropDownGetLabelByValue(items, '1')
+
+ expect(result).toEqual('label1')
+ })
+ })
+
+ describe('sortByLocale', () => {
+ it('should sort strings based on locale', () => {
+ const strings: string[] = ['str2', 'str1']
+
+ const sortedStrings = strings.sort(sortByLocale)
+
+ expect(sortedStrings[0]).toEqual('str1')
+ })
+ })
+
+ describe('filterObject', () => {
+ it('should exclude specified properties from the object', () => {
+ const obj = { prop1: 'value1', prop2: 'value2', prop3: 'value3' }
+ const exProps = ['prop2', 'prop3']
+ const result = filterObject(obj, exProps)
+ expect(result).toEqual({ prop1: 'value1' })
+ })
+ })
+})