Skip to content

Commit

Permalink
Add unit tests for searching and editing products (#8)
Browse files Browse the repository at this point in the history
* feat: add first tests to search

* feat: finish search tests

* feat: prepare detail and props files

* feat: finish search tests

* feat: almost finish props tests

* feat: finish tests for shared files

* feat: finish all tests

* feat: put back fieldType variable, needed in html

* feat: fix file upload test

---------

Co-authored-by: Christian Badura <[email protected]>
  • Loading branch information
cbadura and Christian Badura authored Jan 18, 2024
1 parent 1007d23 commit f798303
Show file tree
Hide file tree
Showing 12 changed files with 867 additions and 28 deletions.
26 changes: 14 additions & 12 deletions src/app/product-store/product-detail/product-detail.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -52,10 +56,6 @@ export class ProductDetailComponent implements OnInit {
}
}

ngOnInit(): void {
console.log('product detail ngOnInit()')
}

private loadProduct() {
this.loading = true
this.productApi
Expand All @@ -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)
Expand All @@ -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',
Expand Down Expand Up @@ -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()
Expand All @@ -210,7 +212,7 @@ export class ProductDetailComponent implements OnInit {
this.close()
}
}
private onSave() {
public onSave() {
this.productPropsComponent.onSubmit()
}
public onCreate(data: any) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
<div class="w-full sm:w-10 md:w-9 lg:w-6 px-3">
<span class="p-float-label" [title]="'PRODUCT.TOOLTIPS.VERSION' | translate" controlErrorAnchor>
<input pInputText type="text" class="w-full" id="product_detail_item_version" formControlName="version" />
<label for="product_detail_item_version">{{ 'PRODUCT.VERSION' | translate }}</label>
<label class="ocx-required-label" for="product_detail_item_version">
{{ 'PRODUCT.VERSION' | translate }}</label
>
</span>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -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<ProductPropertyComponent>

const msgServiceSpy = jasmine.createSpyObj<PortalMessageService>('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<ProductDetailForm>({
id: new FormControl<string | null>('id'),
name: new FormControl<string | null>('name'),
operator: new FormControl<boolean | null>(null),
version: new FormControl<string | null>('version'),
description: new FormControl<string | null>(null),
imageUrl: new FormControl<string | null>(null),
basePath: new FormControl<string | null>('path'),
displayName: new FormControl<string | null>('display'),
iconName: new FormControl<string | null>('icon'),
classifications: new FormControl<string[] | null>(null)
})
component.formGroup = formGroup as FormGroup<ProductDetailForm>
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<ProductDetailForm>({
id: new FormControl<string | null>('id'),
name: new FormControl<string | null>('name'),
operator: new FormControl<boolean | null>(null),
version: new FormControl<string | null>('version'),
description: new FormControl<string | null>(null),
imageUrl: new FormControl<string | null>(null),
basePath: new FormControl<string | null>('path'),
displayName: new FormControl<string | null>('display'),
iconName: new FormControl<string | null>('icon'),
classifications: new FormControl<string[] | null>(null)
})
component.formGroup = formGroup as FormGroup<ProductDetailForm>
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<ProductDetailForm>({
id: new FormControl<string | null>('id'),
name: new FormControl<string | null>('name'),
operator: new FormControl<boolean | null>(null),
version: new FormControl<string | null>('version'),
description: new FormControl<string | null>(null),
imageUrl: new FormControl<string | null>(null),
basePath: new FormControl<string | null>('path'),
displayName: new FormControl<string | null>('display'),
iconName: new FormControl<string | null>('icon'),
classifications: new FormControl<string[] | null>(null)
})
component.formGroup = formGroup as FormGroup<ProductDetailForm>
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<ProductDetailForm>({
id: new FormControl<string | null>('id'),
name: new FormControl<string | null>('name'),
operator: new FormControl<boolean | null>(null),
version: new FormControl<string | null>('version'),
description: new FormControl<string | null>(null),
imageUrl: new FormControl<string | null>(null),
basePath: new FormControl<string | null>('path'),
displayName: new FormControl<string | null>('display'),
iconName: new FormControl<string | null>('icon'),
classifications: new FormControl<string[] | null>(null)
})
component.formGroup = formGroup as FormGroup<ProductDetailForm>
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<ProductDetailForm>({
id: new FormControl<string | null>(null, Validators.required),
name: new FormControl<string | null>('name'),
operator: new FormControl<boolean | null>(null),
version: new FormControl<string | null>('version'),
description: new FormControl<string | null>(null),
imageUrl: new FormControl<string | null>(null),
basePath: new FormControl<string | null>('path'),
displayName: new FormControl<string | null>('display'),
iconName: new FormControl<string | null>('icon'),
classifications: new FormControl<string[] | null>(null)
})
component.formGroup = formGroup as FormGroup<ProductDetailForm>

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()
})
})
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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<string | null>
name: FormControl<string | null>
operator: FormControl<boolean | null>
Expand Down Expand Up @@ -42,15 +42,15 @@ 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<ProductDetailForm>({
id: new FormControl(null),
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)]),
Expand All @@ -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
Expand Down Expand Up @@ -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' })
}
})
}
Expand All @@ -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' })
}
})
}
Expand Down
Loading

0 comments on commit f798303

Please sign in to comment.