import { Observable, of } from 'rxjs'; import { CommonModule } from '@angular/common'; import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { Router } from '@angular/router'; import { ActivatedRoute } from '@angular/router'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { WptButtonModule } from '@arrow/warpaint/button'; import { WptTableModule } from '@arrow/warpaint/table'; import { WptTooltipModule } from '@arrow/warpaint/tooltip'; import { WptProgressEllipsisModule } from '@arrow/warpaint/progress-ellipsis'; import { WptCheckboxModule } from '@arrow/warpaint/checkbox'; import { WptIconModule } from '@arrow/warpaint/icon'; import { WptSelectModule } from '@arrow/warpaint/select'; import { WptPaginatorModule } from '@arrow/warpaint/paginator'; import { ListModule, Row } from '@arrow/bom/list'; import { ControlsModule } from '@arrow/bom/controls'; import { SharedModule } from '@arrow/bom/shared'; import { LoggerService } from '@arrow/bom/logger'; import { CoreModule, ModalService, AnalyticsService, SocketsService, ArrowService, Utils, PermissionsService, CartHandoverService, DialogService, BomService, BomItemService, Entity2textPipe, AppSessionService, UserService, ListItemModel, ItemSelection, TestUtils, } from '@arrow/bom/core'; import { ConfigService } from '@arrow/bom/config'; import { HooksService } from '@arrow/bom/hooks'; import { WptDialog, WptDialogRef, WptDialogContainer, WptDialogConfig } from '@arrow/warpaint/dialog'; import { ListService } from '@arrow/bom/list'; import { FeatureFlagService } from '@arrow/bom/feature-flag'; import { ViewComponent } from './view.component'; import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SimpleChange, SimpleChanges } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { Cart2Service } from '@arrow/bom/cart2'; import { OverlayRef } from '@angular/cdk/overlay'; let testUtils: TestUtils; const createBomRowsArray = (size: number, data: any = {}) => { const items = testUtils.createBomListItemModelArray(size, data); return items.map(item => { return { item, loading: false } as Row; }); }; describe('ViewComponent', () => { let component: ViewComponent; let fixture: ComponentFixture; const mockConfig: Partial = { getTarget: () => { return ''; } }; const mockArrowService: Partial = {}; const mockBomService: Partial = { setDataUrl: () => {}, setPageNumber: () => {}, getPartInfoHttp: () => { return of() } }; const mockBomItemService: Partial = { deleteItemClick$: new Observable(), newItemAdded$: new Observable(), selectAllToggle$: new Observable(), indexShifted$: new Observable<{ startIndex: number; shiftAmount: number; }>(), userChangedSelection$: new Observable(), selectionChanged$: new Observable(), setItemLoading: () => {}, updateSelection: () => {}, deleteItemClicked: () => {}, getSelection: () => {}, isPartialSelection: () => { return false } }; const mockModalService: Partial = { noModalConfirmation$: new Observable() }; const mockCart2Service: Partial = { add: () => { return of(); }, }; const mockAnalyticsService: Partial = { googleTrackEvent: () => {}, track: () => { return of(); } }; const mockSockets: Partial = { observeAction: subject => { switch (subject) { case 'rowDeleted': return of(0); break; default: return new Observable(); } }, emitAction: (actionName: string, params?: any) => { return of({}); } }; const mockUtils: Partial = { debounce: () => { return () => {}; } }; const mockPs: Partial = { getPermissions: () => { return of(); } }; const mockCartHandoverService: Partial = {}; const mockDialogService: Partial = { alert: () => { return of(); } }; const mockFfService: Partial = {}; const mockListService: Partial = { getObservable: () => { return of([]); }, getBOptObservable: () => { return of(); }, setMultiplier: () => {} }; const mockE2t: Partial = { transform: () => { return ''; } }; const mockLog: Partial = { info: () => {}, warn: () => {} }; const mockAppSessionService: Partial = {}; const mockUserService: Partial = { isReady: () => { return of(); }, requestFreshAuth: () => {}, getBomChangesSinceHistoryLastViewed: () => { return of(); } }; const mockWptDialog: Partial = { open: () => { const config = new WptDialogConfig(); config.ariaLabelledBy = 'some label'; const test = new WptDialogContainer(null, null, null, null, config); return new WptDialogRef({ detachments: () => of(), keydownEvents: () => of(), backdropClick: () => of(), } as OverlayRef, test); } }; const mockHooksService: Partial = {}; const mockRouter = { navigate: jasmine.createSpy('navigate') }; const mockTranslate: Partial = { instant: (key) => { key } }; beforeEach(async(() => { TestBed.configureTestingModule({ schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA], declarations: [ViewComponent], imports: [ WptIconModule, WptTooltipModule, WptSelectModule, WptPaginatorModule, FormsModule, ReactiveFormsModule, CommonModule, WptTableModule, WptButtonModule, WptProgressEllipsisModule, ListModule, ControlsModule, SharedModule, CoreModule, WptCheckboxModule ], providers: [ { provide: ConfigService, useValue: mockConfig }, { provide: ArrowService, useValue: mockArrowService }, { provide: BomService, useValue: mockBomService }, { provide: BomItemService, useValue: mockBomItemService }, { provide: ModalService, useValue: mockModalService }, { provide: Cart2Service, useValue: mockCart2Service }, { provide: AnalyticsService, useValue: mockAnalyticsService }, { provide: SocketsService, useValue: mockSockets }, { provide: Utils, useValue: mockUtils }, { provide: PermissionsService, useValue: mockPs }, { provide: CartHandoverService, useValue: mockCartHandoverService }, { provide: DialogService, useValue: mockDialogService }, { provide: FeatureFlagService, useValue: mockFfService }, { provide: ListService, useValue: mockListService }, { provide: Entity2textPipe, useValue: mockE2t }, { provide: LoggerService, useValue: mockLog }, { provide: AppSessionService, useValue: mockAppSessionService }, { provide: UserService, useValue: mockUserService }, { provide: WptDialog, useValue: mockWptDialog }, { provide: HooksService, useValue: mockHooksService }, { provide: Router, useValue: mockRouter }, { provide: ActivatedRoute, useValue: { params: of({ id: '1' }) } }, { provide: TranslateService, useValue: mockTranslate }, TestUtils ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ViewComponent); component = fixture.componentInstance; testUtils = TestBed.inject(TestUtils); // testUtils = fixture.debugElement.injector.get(TestUtils); //fixture.detectChanges(); }); afterEach(() => { component.ngOnDestroy(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should emit getPagesCounts', () => { const socketsService = fixture.debugElement.injector.get(SocketsService); const socketSpy = spyOn(socketsService, 'emitAction').and.callThrough(); component.updateBomTotals(); expect(socketSpy.calls.any()).toBe( true, 'socketsService.emitAction was not called' ); expect(socketSpy.calls.allArgs()[0][0]).toBe( 'getPageCounts', 'socket called a different subject' ); }); it('should set remaining loading items to loaded', () => { const rows = testUtils.createBomListItemModelArray(10); rows[5].setItemModelLoading(true); rows[5].setPriceItemModelLoading(true); rows[8].setItemModelLoading(true); rows[8].setPriceItemModelLoading(true); component.bom.rows = rows; component.setRemainingItemsToLoaded(); const pendingToLoadRows = component.bom.rows.filter( row => row.isLoading || row.priceSourceLoading ); expect(pendingToLoadRows.length).toBe(0, 'Remaining rows were not set to loaded'); }); it('should set bom loading as complete', () => { component.onBomLoadingComplete(); expect(component.isPendingAddMulti).toBe(false); }); [ { feColumnName: 'user uploaded data', beColumnName: 'MPN' }, { feColumnName: 'part number', beColumnName: 'MPN' }, { feColumnName: 'price', beColumnName: 'PRICE' }, { feColumnName: 'quantity', beColumnName: 'QTY' }, { feColumnName: 'stock', beColumnName: 'STOCK' }, { feColumnName: 'other', beColumnName: 'NOT DEFINED' }, ].forEach((testCase, index) => { it('should return BE columnName by FE columnName', () => { expect(component.getColumnByName(testCase.feColumnName)).toBe(testCase.beColumnName, 'invalid back end column name'); }); }); [ { stock: 0, expectValidQuantity: true }, { stock: 110, expectValidQuantity: false }, ].forEach((testCase, index) => { it('should validate Quantity in the list item model for Arrow.com', () => { const item = testUtils.createBomListItemModelArray(1)[0]; item.quantity = 1; item.inCart = 1; item.stock = testCase.stock; item.increment = 1; item.minQuantity = 0; item.priceSourceLoading = false; // const configService = fixture.debugElement.injector.get(ConfigService); // spyOn(configService, 'getTarget').and.returnValue('myarrow'); component.siteTarget = 'arrowcom'; const result = component.isQuantityValid(item); expect(result).toBe(testCase.expectValidQuantity, 'incorrect quantity validation result'); }); }); [ { increment: 1, expectValidQuantity: false }, { increment: 3, expectValidQuantity: true }, ].forEach((testCase, index) => { it('should validate Quantity in the list item model for MyArrow', () => { const item = testUtils.createBomListItemModelArray(1)[0]; item.quantity = 1; item.inCart = 1; item.stock = -1; item.increment = testCase.increment; item.minQuantity = 0; item.priceSourceLoading = false; // const configService = fixture.debugElement.injector.get(ConfigService); // spyOn(configService, 'getTarget').and.returnValue('myarrow'); component.siteTarget = 'myarrow'; const result = component.isQuantityValid(item); expect(result).toBe(testCase.expectValidQuantity, 'incorrect quantity validation result'); }); }); it('should not fail when calling resetFormControls for a null row', () => { const row: Row = null; component.resetFormControls(row); }); it('should reset all the row form controls values', () => { const item = testUtils.createBomListItemModelArray(1)[0]; item.quantity = 2; item.stock = 10; item.minQuantity = 1; item.increment = 1; const row: Row = { item }; component.resetFormControls(row); expect(row.control).toBeTruthy(); }); it('should reset the row form control values on error', () => { const quantityOriginalValue = 2; const partNameOriginalValue = 'Test'; const item = testUtils.createBomListItemModelArray(1)[0]; item.quantity = quantityOriginalValue; item.stock = 10; item.minQuantity = 1; item.increment = 1; item.originalName = partNameOriginalValue; const row: Row = { item, id: '0' }; row.control = new FormGroup({ quantity: new FormControl(999), partName: new FormControl('test value') }); component.onRowUpdateError(row, { action: 'updateRow', rowId: '0' }); expect(row.control.get('quantity').value).toBe( quantityOriginalValue, 'quantity value not reseted' ); expect(row.control.get('partName').value).toBe( partNameOriginalValue, 'part name value not reseted' ); }); // TODO: check test validity it('should set form control quantity and do not send update row request', () => { const expectedValue = 200; const item = testUtils.createBomListItemModelArray(1)[0]; item.quantity = 2; item.multiplier = 2; item.minQuantity = 1; item.increment = 1; const row: Row = { item, id: '0', // control: new FormGroup({ // quantity: new FormControl(999) // }) }; const socketsService = fixture.debugElement.injector.get(SocketsService); const socketSpy = spyOn(socketsService, 'emitAction').and.callThrough(); component.setQuantity({ row, quantity: 100 }); expect(row.item.quantity).toBe(expectedValue, 'quantity value not updated'); expect(socketSpy.calls.any()).toBe(false,'socket action updateRow should have not been called'); }); it('should set form control quantity and send update row request', () => { const expectedValue = 200; const item = testUtils.createBomListItemModelArray(1)[0]; item.quantity = 2; item.multiplier = 2; item.minQuantity = 1; item.increment = 1; item.fullPart = 'testValue'; const row: Row = { item, id: '0', // control: new FormGroup({ // quantity: new FormControl(999) // }) }; const socketsService = fixture.debugElement.injector.get(SocketsService); const socketSpy = spyOn(socketsService, 'emitAction').and.callThrough(); component.setQuantity({ row, quantity: 100 }); expect(row.item.quantity).toBe(expectedValue, 'quantity value not updated'); expect(socketSpy.calls.any()).toBe(true, 'socket action updateRow should have not been called'); }); it('should not set form control quantity and do not send update row request', () => { const expectedValue = 2; const item = testUtils.createBomListItemModelArray(1)[0]; item.quantity = expectedValue; item.multiplier = 1; item.minQuantity = 1; item.increment = 1; const row: Row = { item, id: '0', // control: new FormGroup({ // quantity: new FormControl(999) // }) }; const socketsService = fixture.debugElement.injector.get(SocketsService); const socketSpy = spyOn(socketsService, 'emitAction').and.callThrough(); // component.setQuantity(row, 'invalid value'); component.setQuantity({ row, quantity: 'invalid value' }); // expect(row.control.get('quantity').value).toBe(expectedValue,'quantity value not updated'); expect(row.item.quantity).toBe(expectedValue, 'quantity value not updated'); // expect(setBomLoadingSpy.calls.any()).toBe(false,'eventService.setBomLoading should have not been called'); // expect(setCartLoadingSpy.calls.any()).toBe(false,'eventService.setCartLoading should have not been called'); // expect(setFiltersLoadingSpy.calls.any()).toBe(false,'eventService.setFiltersLoading should have not been called'); expect(socketSpy.calls.any()).toBe(false,'socket action updateRow should have not been called'); }); it('should clear the time out for cancel', () => { component.autoCloseTimer = 10; component.cancelAutoTimeout(); expect(component.autoCloseTimer).toBe( null, 'the timer for auto close should have been cleared' ); }); // it('should start the time out for cancel', () => { // component.autoCloseTimer = null; // component.startAutoCloseHandler(); // expect(component.autoCloseTimer).toBeTruthy(); // }); it('should start the time out for cancel', () => { component.autoCloseTimer = null; component.startAutoCloseHandler(); expect(component.autoCloseTimer).toBeTruthy(); }); it('should call bomItemService.updateSelection', () => { const item = testUtils.createBomListItemModelArray(1)[0]; item.quantity = 2; item.stock = 10; item.minQuantity = 1; item.increment = 1; const row: Row = { item }; const bomItemService = fixture.debugElement.injector.get(BomItemService); const spy = spyOn(bomItemService, 'updateSelection').and.callThrough(); // component.updateSelection({ status: false, rowId: row.id }); component.updateSelection(row); expect(spy.calls.any()).toBe(true); }); it('should not delete row when delete key is pressed', () => { const bomItemService = fixture.debugElement.injector.get(BomItemService); const spy = spyOn(bomItemService, 'deleteItemClicked').and.callThrough(); const item = testUtils.createBomListItemModelArray(1)[0]; item.isLoading = true; expect(spy.calls.any()).toBe(false); }); it('should not delete row when row is loading', () => { const bomItemService = fixture.debugElement.injector.get(BomItemService); const spy = spyOn(bomItemService, 'deleteItemClicked').and.callThrough(); const item = testUtils.createBomListItemModelArray(1)[0]; item.isLoading = true; expect(spy.calls.any()).toBe(false); }); it('should delete row when delete key is pressed', () => { const bomItemService = fixture.debugElement.injector.get(BomItemService); const spy = spyOn(bomItemService, 'deleteItemClicked').and.callThrough(); const item = testUtils.createBomListItemModelArray(1)[0]; expect(spy.calls.any()).toBe(false); }); it('should delete multiple rows', () => { //Hack to write value in a private property //TODO: make list an argument of deleteMultiple const rows = createBomRowsArray(10); component['_list'] = rows; const notDeleted = rows.find(row => { return row.deleted === false; }); expect(!!notDeleted).toBe(false); }); it('should not call delete item clicked when item is loading', () => { const bomItemService = fixture.debugElement.injector.get(BomItemService); const spy = spyOn(bomItemService, 'deleteItemClicked').and.callThrough(); const item = testUtils.createBomListItemModelArray(1)[0]; item.isLoading = true; component.onDelete(item); expect(spy.calls.any()).toBe(false); }); it('should call delete item clicked', () => { const bomItemService = fixture.debugElement.injector.get(BomItemService); const spy = spyOn(bomItemService, 'deleteItemClicked').and.callThrough(); const item = testUtils.createBomListItemModelArray(1)[0]; item.isLoading = false; component.onDelete(item); expect(spy.calls.any()).toBe(true); }); it('should not open the buying options modal when multiplier is 1', () => { const wptDialog = fixture.debugElement.injector.get(WptDialog); const spy = spyOn(wptDialog, 'open').and.callThrough(); const item = testUtils.createBomListItemModelArray(1)[0]; item.multiplier = 1; const row: Row = { item }; component.editMultiplier(row); expect(spy.calls.any()).toBe(false); }); it('should not open the buying options modal when price source is loading', () => { const wptDialog = fixture.debugElement.injector.get(WptDialog); const spy = spyOn(wptDialog, 'open').and.callThrough(); const item = testUtils.createBomListItemModelArray(1)[0]; item.multiplier = 2; item.priceSourceLoading = true; const row: Row = { item }; component.editMultiplier(row); expect(spy.calls.any()).toBe(false); }); it('should not open the buying options modal when item is loading', () => { const wptDialog = fixture.debugElement.injector.get(WptDialog); const spy = spyOn(wptDialog, 'open').and.callThrough(); const item = testUtils.createBomListItemModelArray(1)[0]; item.multiplier = 2; item.priceSourceLoading = false; item.isLoading = true; const row: Row = { item }; component.clickedBuy(row); expect(spy.calls.any()).toBe(false); }); it('should open the buying options modal', () => { const wptDialog = fixture.debugElement.injector.get(WptDialog); const spy = spyOn(wptDialog, 'open').and.callThrough(); const item = testUtils.createBomListItemModelArray(1)[0]; item.multiplier = 2; item.priceSourceLoading = false; item.isLoading = false; const row: Row = { item }; component.editMultiplier(row); expect(spy.calls.any()).toBe(true); }); it('should call cartService to add an item', () => { const cart2Service = fixture.debugElement.injector.get(Cart2Service); const spy = spyOn(cart2Service, 'add').and.callThrough(); const item = testUtils.createBomListItemModelArray(1)[0]; const row: Row = { item }; row.id = '999' component.bomLoading = true; component.clickedBuy(row); expect(spy.calls.any()).toBe(true); expect(spy.calls.allArgs()[0][1].rowIds[0]).toBe('999'); }); });