问题描述
我正在尝试在单元测试中模拟一个选择器,如下所示:
describe('Device List Component',() => {
let component: ListComponent;
let fixture: ComponentFixture<ListComponent>;
let deviceServiceMock: any;
let mockStore: MockStore<any>;
let devices;
beforeEach(async(() => {
deviceServiceMock = jasmine.createSpyObj('DevicesService',['fetchDevices']);
deviceServiceMock.fetchDevices.and.returnValue(of(deviceState()));
Testbed.configureTestingModule({
declarations: [
ListComponent,MockComponent(DataGridComponent),],imports: [
RouterTestingModule,MockModule(SharedModule),ToastrModule.forRoot({
preventDuplicates: true,closeButton: true,progressBar: true,}),TranslateModule.forRoot({
loader: { provide: TranslateLoader,useClass: JsonTranslationLoader },providers: [
{ provide: DevicesService,useValue: deviceServiceMock },{ provide: ColumnApi,useClass: MockColumnApi },provideMockStore(),}).compileComponents();
}));
beforeEach(() => {
fixture = Testbed.createComponent(ListComponent);
component = fixture.componentInstance;
mockStore = Testbed.get(MockStore);
component.columnApi = Testbed.get(ColumnApi);
devices = mockStore.overrideselector('devices',deviceState());
fixture.detectChanges();
});
});
这是组件文件
export class ListComponent implements OnInit,OnDestroy {
columnDefs: DeviceColumns[];
defaultColumnDefs: any;
gridApi: any;
columnApi: any;
overlaynorowstemplate: string;
rowData: DeviceData[] = [];
pagination: DevicePagination;
globalSearch: string;
hasFloatingFilter: boolean;
frameworkComponents: any;
dropdownSettings: any = {};
dropDownList: Columns[] = [];
selectedItems: Columns[] = [];
shuffledColumns: any = [];
selectedRows: DeviceData[] = [];
rowSelection: string;
bsModalRef: BsModalRef;
isColumnsSorting: boolean;
hasRowAnimation = true;
multiSortKey = 'ctrl';
changeDetectorRef: ChangeDetectorRef;
deviceDeleteSubscription: Subscription;
currentPage = new Subject<number>();
search = new Subject<string>();
subscription: Subscription;
constructor(
private router: Router,private devicesService: DevicesService,public store: Store<any>,private toast: ToastrService,private ngzone: ngzone,private translateService: TranslateService,private globalTranslate: GlobalLanguageService,private modalService: BsModalService,changeDetectorRef: ChangeDetectorRef
) {
this.translateService.stream(['DEVICES.LIST','MULTISELECT']).subscribe((translations) => {
const listTranslations = translations['DEVICES.LIST'];
const multiSelectTranslations = translations['MULTISELECT'];
this.overlaynorowstemplate = `<span class="ag-overlay-loading-center">${listTranslations.NODEVICE}</span>`;
this.dropdownSettings.selectAllText = multiSelectTranslations.SELECTALL;
this.dropdownSettings.unSelectAllText = multiSelectTranslations.deselectALL;
});
this.changeDetectorRef = changeDetectorRef;
this.translateService.onLangChange.subscribe(() => {
this.gridApi && this.gridApi.refreshHeader();
});
}
ngOnInit() {
this.loadStore();
this.initializeColumns();
this.pageSearch();
this.dropdownSettings = {
singleSelection: false,idField: 'field',textField: 'key',selectAllText: 'Select All',unSelectAllText: 'UnSelect All',itemsShowLimit: 3,allowSearchFilter: true,enableCheckAll: true,};
this.store.dispatch(new DevicesActions.ClearCurretDevice());
}
loadStore() {
this.store.pipe(select('devices')).subscribe((val) => {
const deviceList = val.devices.map((d) => {
return {
...d,is_online: d.is_online ? 'Active' : 'Inactive',};
});
this.rowData = deviceList;
this.pagination = val.pagination;
this.globalSearch = val.globalSearch;
this.hasFloatingFilter = val.hasFloatingFilter;
this.dropDownList = val.shuffledColumns;
this.selectedItems = val.shuffledColumns.filter((column: Columns) => !column.hide);
this.selectedRows = val.selectedRows;
});
}
initializeColumns() {
this.columnDefs = [
{
headerName: 'S.No',translateKey: 'DEVICES.LIST.SNO',width: 100,resizable: false,sortable: false,suppressSizetoFit: true,valueGetter: (args) => this.getId(args),checkBoxSelection: (params) => {
console.log('params.columnApi.getRowGroupColumns()',params.columnApi.getRowGroupColumns());
return params.columnApi.getRowGroupColumns().length === 0;
},headerCheckBoxSelection: (params) => {
return params.columnApi.getRowGroupColumns().length === 0;
},},...gridColumns,];
this.columnDefs = map(this.columnDefs,(columnDef) => {
return extend({},columnDef,{ headerValueGetter: this.localizeHeader.bind(this) });
});
this.shuffledColumns.push(this.columnDefs[0]);
this.dropDownList.forEach((column,colIndex) => {
this.columnDefs.forEach((data) => {
if (data.field === column.field) {
data.hide = column.hide;
data.sort = column.sort;
data.width = column.width;
data.minWidth = column.minWidth;
this.shuffledColumns.splice(colIndex + 1,data);
}
});
});
this.columnDefs = this.shuffledColumns;
this.rowSelection = 'multiple';
this.defaultColumnDefs = {
suppressMenu: true,suppressMovable: true,sortable: true,resizable: true,};
this.frameworkComponents = { FloatingFilterComponent: FloatingFilterComponent };
}
localizeHeader(params: any) {
return this.globalTranslate.getTranslation(params.colDef.translateKey);
}
getId(args: any): any {
return (
this.pagination.per_page * this.pagination.prev_page + parseInt(args.node.rowIndex,10) + 1
);
}
pageSearch() {
this.subscription = this.search.subscribe((value) => {
this.store.dispatch(new DevicesActions.GlobalSearch(value));
if (value.length === 0) {
this.clearSelectedRows();
this.loadData();
}
});
}
OnGridReady(params) {
this.gridApi = params.api;
this.columnApi = params.columnApi;
this.loadData();
}
loadData() {
this.devicesService.fetchDevices(this.gridApi);
}
gotoAddDevice() {
this.router.navigate(['/devices/new']);
}
searchDevices() {
this.store.dispatch(new DevicesActions.UpdateCurrentPage(1));
this.clearSelectedRows();
this.loadData();
}
clearSelectedRows() {
this.store.dispatch(new DevicesActions.ClearSelectedRows());
}
onItemSelect(item: DropDownColumns) {
this.store.dispatch(new DevicesActions.ColumnSelect(item.field));
this.columnApi.setColumnVisible(item.field,true);
}
onSelectAll(items: any) {
this.store.dispatch(new DevicesActions.ColumnsSelectAll());
items.map((item) => this.columnApi.setColumnVisible(item.field,true));
}
onItemUnSelect(item: DropDownColumns) {
this.store.dispatch(new DevicesActions.Columndeselect(item.field));
this.columnApi.setColumnVisible(item.field,false);
}
ondeselectAll() {
this.store.dispatch(new DevicesActions.ColumnsdeselectAll());
this.dropDownList.map((item) => this.columnApi.setColumnVisible(item.field,false));
}
SortedColumns(params: SortedColumns[]) {
const columnsId = [];
params.map((param: SortedColumns) => {
columnsId.push(param.id);
});
const shuffledColumns = columnsId.map((columnId) =>
this.dropDownList.find((data) => data.field === columnId)
);
this.store.dispatch(new DevicesActions.ShuffledColumns(shuffledColumns));
this.columnApi.moveColumns(columnsId,1);
}
hasDevices() {
return this.rowData.length > 0 ? true : false;
}
updatePage() {
if (this.pagination.current_page.toString() === '') {
this.pagination.current_page = 1;
}
this.store.dispatch(new DevicesActions.UpdateCurrentPage(this.pagination.current_page));
this.clearSelectedRows();
this.loadData();
}
prevIoUsPage() {
this.store.dispatch(new DevicesActions.UpdateCurrentPage(this.pagination.prev_page));
this.clearSelectedRows();
this.loadData();
}
firstPage() {
this.store.dispatch(new DevicesActions.UpdateCurrentPage(1));
this.clearSelectedRows();
this.loadData();
}
lastPage() {
this.store.dispatch(new DevicesActions.UpdateCurrentPage(this.pagination.total_pages));
this.clearSelectedRows();
this.loadData();
}
nextPage() {
if (!this.pagination.is_last_page) {
this.store.dispatch(new DevicesActions.UpdateCurrentPage(this.pagination.next_page));
this.clearSelectedRows();
this.loadData();
}
}
toggleFloatingFilter() {
this.hasFloatingFilter = !this.hasFloatingFilter;
this.store.dispatch(new DevicesActions.UpdateFloatingFilter(this.hasFloatingFilter));
this.clearSelectedRows();
this.gridApi.setRowData(this.rowData);
if (!this.hasFloatingFilter) {
this.gridApi.setFilterModel(null);
this.store.dispatch(new DevicesActions.ClearColumnSearch());
this.loadData();
}
setTimeout(() => {
this.gridApi.refreshHeader();
},0);
window.location.reload();
}
isSortingEnabled() {
this.isColumnsSorting = this.dropDownList.some((column) => column.sort !== '');
return this.isColumnsSorting;
}
setSortingBackgroundColor() {
return this.isColumnsSorting ? COLOR_PRIMARY : COLOR_SECONDARY;
}
setSortingIconColor() {
return this.isColumnsSorting ? ICON_ENABLED : ICON_disABLED;
}
clearSort() {
this.gridApi.setSortModel(null);
this.store.dispatch(new DevicesActions.ClearColumnsSort());
this.loadData();
}
resizeColumns() {
const allColumnIds = [];
this.columnApi.getColumnState().forEach((column) => {
allColumnIds.push(column.colId);
});
this.columnApi.autoSizeColumns(allColumnIds,false);
}
onRowDataChanged() {
if (this.gridApi) {
this.gridApi.forEachNode((node: any) => {
const selectNode = this.selectedRows.some((row) => row.id === node.data.id);
if (selectNode) {
node.setSelected(true);
}
});
}
}
onSelectionChanged() {
this.selectedRows = this.gridApi.getSelectedRows();
console.log('selected rows',this.selectedRows);
this.store.dispatch(new DevicesActions.UpdateSelectedRows(this.selectedRows));
this.changeDetectorRef.detectChanges();
}
onSortChanged(params) {
this.store.dispatch(new DevicesActions.UpdateColumnsSort(params));
this.clearSelectedRows();
this.loadData();
}
onColumnResized() {
const updatedColumns: ColumnWidth[] = [];
this.columnApi.getColumnState().forEach((column) => {
updatedColumns.push({ field: column.colId,width: column.width });
});
this.store.dispatch(new DevicesActions.UpdateColumnWidth(updatedColumns));
}
gotoDetailView(params: any) {
const id = params.id;
this.ngzone.run(() => this.router.navigate(['/devices',id]));
}
isDeleteEnabled() {
return this.selectedRows.length === 0 ? true : false;
}
setDeleteBackgroundColor() {
return this.selectedRows.length !== 0 ? COLOR_PRIMARY : COLOR_SECONDARY;
}
setDeleteIconColor() {
return this.selectedRows.length !== 0 ? ICON_ENABLED : ICON_disABLED;
}
openModal() {
const initialState = {
title: 'Delete Device',message: 'Do you really want to delete the device? This process cannot be undone',};
if (this.selectedRows.length > 1) {
(initialState.title = 'Delete Devices'),(initialState.message = `Do you really want to delete ${this.selectedRows.length} devices? This process cannot be undone`);
}
this.bsModalRef = this.modalService.show(ModalDeleteComponent,{ initialState });
this.bsModalRef.content.delete.subscribe((canDelete: boolean) => {
if (canDelete) {
this.deleteDevices();
}
this.bsModalRef.hide();
});
}
ngOnDestroy() {
this.deviceDeleteSubscription?.unsubscribe();
}
deleteDevices() {
const selectedIds = this.selectedRows.map((row) => row.id).toString();
const params = {
ids: selectedIds,};
this.deviceDeleteSubscription = this.devicesService.deleteDevices(params).subscribe(
(data) => {
const ids = selectedIds.split(',').map(Number);
this.clearSelectedRows();
this.store.dispatch(new DevicesActions.DeleteDevices(ids));
if (this.rowData.length === 0) {
this.store.dispatch(new DevicesActions.UpdateCurrentPage(1));
this.loadData();
}
this.toast.success('Deleted successfully');
setTimeout(() => {
window.location.reload();
},500);
},(error) => {
this.toast.error(error.message);
}
);
}
editConfiguration() {
this.store.dispatch(new DevicesActions.SetEditDevice(this.selectedRows[0]));
this.router.navigate(['/devices','edit',this.selectedRows[0].id]);
}
isEditEnabled() {
return this.selectedRows.length !== 1 ? true : false;
}
setEditBackgroundColor() {
return this.selectedRows.length === 1 ? COLOR_PRIMARY : COLOR_SECONDARY;
}
setEditIconColor() {
return this.selectedRows.length === 1 ? ICON_ENABLED : ICON_disABLED;
}
}
但是当我运行规范时,我得到的错误是
TypeError:无法读取未定义的属性“ length”
解决方法
我认为问题在于使用provideMockStore
嘲笑选择器。我可以看到您使用了很多this.selectedRows.length
(例如在setEditBackgroundColor()
中),这些是基于ngRx选择器设置的。
providers: [
{ provide: DevicesService,useValue: deviceServiceMock },{ provide: ColumnApi,useClass: MockColumnApi },provideMockStore({
selectors: [
{
selector: selectLoginPagePending,value: true
}
]
})
],
用于选择器,例如:
export const selectLoginPagePending = createSelector(
selectLoginPageState,(state: State) => state.pending;
);
按照select('devices')
的预期输出进行尝试,我认为它应该可以工作。
请注意,不要像在setEditBackgroundColor()
和其他程序中那样从HTML进行函数调用,它会影响性能,并且会在每个ChangeDetection周期中调用(尝试放入console.log
在这种方法中)。他们将被多次呼叫。最好使用一些map
设置对象属性,然后在HTML上呈现
我没有使用选择器,而是使用以下初始值设置了商店
return BigList.Where(u => SmallList.Contains(u.UserName))
.OrderBy(u => u.UserName.Length)
.GroupBy(u => u.EmailAddress)
.Select(g => g.FirstOrDefault())
.Select(u => u.UserName).ToList();
哪个通过测试。通过初始状态,我不需要初始化selectedRows