簡體   English   中英

TypeError:無法讀取未定義的屬性“modelReplicationStartDate”

[英]TypeError: Cannot read property 'modelReplicationStartDate' of undefined

使用 Jasmine/Karma 運行 Angular 單元測試時,出現以下錯誤:

TypeError: Cannot read property 'modelReplicationStartDate' of undefined
error properties: Object({ longStack: 'TypeError: Cannot read property 'modelReplicationStartDate' of undefined
    at http://localhost:9876/_karma_webpack_/main.js:11595:146
    at http://localhost:9876/_karma_webpack_/vendor.js:145322:30
    at memoized (http://localhost:9876/_karma_webpack_/vendor.js:145203:39)
    at defaultStateFn (http://localhost:9876/_karma_webpack_/vendor.js:145226:43)
    at http://localhost:9876/_karma_webpack_/vendor.js:145325:36
    at memoized (http://localhost:9876/_karma_webpack_/vendor.js:145203:39)
    at MapSubscriber.project (http://localhost:9876/_karma_webpack_/vendor.js:145150:107)
    at MapSubscriber._next (http://localhost:9876/_karma_webpack_/vendor.js:133260:35)
    at MapSubscriber.next (http://localhost:9876/_karma_webpack_/vendor.js:39429:18)
    at MockState._subscribe (http://localhost:9876/_karma_webpack_/vendor.js:31954:24)
    at ____________________Elapsed_40_ms__At__Fri_Jun_11_2021_13_51_42_GMT_0400__Eastern_Daylight_Time_ (http://localhost)
    ...
TypeError: Cannot read property 'modelReplicationStartDate' of undefined
    at http://localhost:9876/_karma_webpack_/main.js:11595:146
    at http://localhost:9876/_karma_webpack_/vendor.js:145322:30
    at memoized (http://localhost:9876/_karma_webpack_/vendor.js:145203:39)
    at defaultStateFn (http://localhost:9876/_karma_webpack_/vendor.js:145226:43)
    at http://localhost:9876/_karma_webpack_/vendor.js:145325:36
    at memoized (http://localhost:9876/_karma_webpack_/vendor.js:145203:39)
    at MapSubscriber.project (http://localhost:9876/_karma_webpack_/vendor.js:145150:107)
    at MapSubscriber._next (http://localhost:9876/_karma_webpack_/vendor.js:133260:35)
    at MapSubscriber.next (http://localhost:9876/_karma_webpack_/vendor.js:39429:18)
    at MockState._subscribe (http://localhost:9876/_karma_webpack_/vendor.js:31954:24)
    at <Jasmine>
    at Object.onScheduleTask (http://localhost:9876/_karma_webpack_/vendor.js:89765:26)
    at ZoneDelegate.scheduleTask (http://localhost:9876/_karma_webpack_/polyfills.js:3727:55)
    at Object.onScheduleTask (http://localhost:9876/_karma_webpack_/polyfills.js:3621:69)
    at ZoneDelegate.scheduleTask (http://localhost:9876/_karma_webpack_/polyfills.js:3727:55)
    at Zone.scheduleTask (http://localhost:9876/_karma_webpack_/polyfills.js:3559:47)
    at Zone.scheduleMacroTask (http://localhost:9876/_karma_webpack_/polyfills.js:3582:29)
    at scheduleMacroTaskWithCurrentZone (http://localhost:9876/_karma_webpack_/polyfills.js:4483:29)
    at http://localhost:9876/_karma_webpack_/polyfills.js:5935:34

我的測試套件很簡單:

describe('ReplicateFilterComponent', () => {
  let component: ReplicateFilterComponent;
  let fixture: ComponentFixture<ReplicateFilterComponent>;
  let store: MockStore;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        FormsModule,
        RouterTestingModule.withRoutes([]),
        ReactiveFormsModule,
        BsDatepickerModule.forRoot(),
        HttpClientTestingModule,
        StoreModule.forRoot({
          upload: uploadReducer,
          [managementServiceFeatureKey]: managementServiceReducer,
          assets: modelReducer
        })
      ],
      declarations: [ 
        ReplicateFilterComponent,
        TranslatePipe
      ],
      providers: [
        DatePipe,
        DecimalPipe,
        TranslatePipe,
        TextSubstitutePipe,
        RuntimeConfig,
        provideMockStore({ 
          initialState
        })
      ]
    })
    .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(ReplicateFilterComponent);
    component = fixture.componentInstance;
    TestBed.inject(MockStore);
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

如您所見,我嘗試使用初始 state 創建一個模擬商店。 state 代碼的相關部分:

export interface IReplicateState {
  sortReplicateBy: SortReplicateOptions;
  modelName: string;
  modelReplicationStartDate: ReplicationStartDateValue;
  modelReplicationStartDate1: string; // 'YYYY-MM-DD'
  modelReplicationStartDate2: string; // ''YYYY-MM-DD'
  filterTab: FilterTabValue;
}
...
export const initialState: IReplicateState = {
  sortReplicateBy: 'nameaz',
  modelName: null,
  modelReplicationStartDate: 'Between',
  modelReplicationStartDate1: null,
  modelReplicationStartDate2: null,
  filterTab: 'all',
};

調試器顯示“狀態”未定義:

const getModelReplicationStartDate = Object(_ngrx_store__WEBPACK_IMPORTED_MODULE_0__["createSelector"])(getReplicateFeatureState, state => state.modelReplicationStartDate);

我遵循了Uncaught TypeError: Cannot read property 'coSearchCriteria' of undefined throw - Angular Karma/Jasmine中的建議和https://ngrx.io/guide/store/testing中的文檔

請告訴我主題中錯誤的原因和修復方法。 謝謝。

編輯#1-組件代碼

import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { select, Store } from '@ngrx/store';
import moment from 'moment';
import { BsDatepickerConfig } from 'ngx-bootstrap/datepicker';
import { debounceTime } from 'rxjs/operators';
import { replicateModel, replicateActions, replicateSelectors } from 'src/app/replicate/state';
import { TranslateService } from 'src/app/core/services/translate.service';
import { BaseContainerComponent } from 'src/app/shared/components/base-container.component';
import { Filter } from 'src/app/shared/types/filter.type';
import { IReplication } from 'src/app/shared/interfaces/replication.interface';
import { FilterTabValue, ReplicationStartDateValue } from '../../state/replicate.state';

@Component({
  selector: 'app-replicate-filter',
  templateUrl: './replicate-filter.component.html',
  styleUrls: ['./replicate-filter.component.scss']
})
export class ReplicateFilterComponent extends BaseContainerComponent implements OnInit {
  static readonly DEBOUNCE_TIME = 500;

  private readonly filterByReplicationStartBetweenText = 'models_list_filter_key_start_date_between';
  private readonly filterByReplicationStartBeforeText = 'models_list_filter_key_start_date_before';
  private readonly filterByReplicationStartAfterText = 'models_list_filter_key_start_date_after';
  
  @Output() filters: EventEmitter<Array<Filter<IReplication>>> = new EventEmitter<Array<Filter<IReplication>>>();
  replicationKeyStartList: any[];
  filterTab: FilterTabValue;
  modelFilterForm: FormGroup;
  
  filterReplicationNameControl = new FormControl('');
  filterReplicationStatusControl = new FormControl('All');
  filterReplicationStartDateControl = new FormControl('Between');
  filterReplicationStartDate1Control = new FormControl(null);
  filterReplicationStartDate2Control = new FormControl(null);
  dpConfig: Partial<BsDatepickerConfig>;

  minReplicationStartDate = new Date('01/01/2001');
  maxReplicationStartDate = new Date('01/01/2025');
  
  constructor(private fb: FormBuilder,
    private translateService: TranslateService,
    private store: Store<replicateModel.AppState>) { 
      super();
    }

  ngOnInit(): void {
    this.replicationKeyStartList = this.getReplicationKeyStartList();

    this.modelFilterForm = this.fb.group({
      filterReplicationName: this.filterReplicationNameControl,
      filterReplicationStatus: this.filterReplicationStatusControl,
      filterReplicationStartDate: this.filterReplicationStartDateControl,
      filterReplicationStartDate1: this.filterReplicationStartDate1Control,
      filterReplicationStartDate2: this.filterReplicationStartDate2Control
    });
    this.subs.add(
      this.translateService.translationChanged().subscribe(locale => {
        this.dpConfig = Object.assign({},
        {
            dateInputFormat: this.translateService.translations.datePlaceholder,
            minDate: this.minReplicationStartDate,
            maxDate: this.maxReplicationStartDate,
            showWeekNumbers: false,
            containerClass: 'theme-wtw'
        });
      }),
      this.store.pipe(select(replicateSelectors.getFilterTab)).subscribe(
        filtered => {
          this.filterTab = filtered;
          this.performFilter();
        }
      ),
      this.store.pipe(debounceTime(ReplicateFilterComponent.DEBOUNCE_TIME), select(replicateSelectors.getModelName)).subscribe(
        filtered => {
          this.filterReplicationNameControl.setValue(filtered);
          this.performFilter();
        }
      ),
      this.store.pipe(select(replicateSelectors.getModelReplicationStartDate)).subscribe(
        filtered => {
          this.filterReplicationStartDateControl.setValue(filtered);
          this.performFilter();
        }
      ),
      this.store.pipe(select(replicateSelectors.getModelReplicationStartDate1)).subscribe(
        filtered => {
          if (filtered) {
            const value = moment(filtered, 'YYYY-MM-DD').toDate();
            this.filterReplicationStartDate1Control.setValue(value);
          } else {
            this.filterReplicationStartDate1Control.setValue(null);
          }
          this.performFilter();
        }
      ),
      this.store.pipe(select(replicateSelectors.getModelReplicationStartDate2)).subscribe(
        filtered => {
          if (filtered) {
            const value = moment(filtered, 'YYYY-MM-DD').toDate();
            this.filterReplicationStartDate2Control.setValue(value);
          } else {
            this.filterReplicationStartDate2Control.setValue(null);
          }
          this.performFilter();
        }
      ),
      this.filterReplicationNameControl.valueChanges.subscribe(
        (value: string) => {
          if (value) {
            this.store.dispatch(replicateActions.setModelName(value));
          } else if (value === '') {
            this.store.dispatch(replicateActions.setModelName(null));
          }
        }
      ),
      this.filterReplicationStatusControl.valueChanges.subscribe(
        value => {
          this.store.dispatch(replicateActions.setModelStatus(value));
        }
      ),
      this.filterReplicationStartDateControl.valueChanges.subscribe(
        value => {
          this.store.dispatch(replicateActions.setModelReplicationStartDate(value));
        }
      ),
      this.filterReplicationStartDate1Control.valueChanges.subscribe(
        value => {
          if (value) {
            const date = moment(value).format('YYYY-MM-DD');
            this.store.dispatch(replicateActions.setModelReplicationStartDate1(date));
          } else {
            this.store.dispatch(replicateActions.setModelReplicationStartDate1(null));
          }
        }
      ),
      this.filterReplicationStartDate2Control.valueChanges.subscribe(
        value => {
          if (value) {
            const date = moment(value).format('YYYY-MM-DD');
            this.store.dispatch(replicateActions.setModelReplicationStartDate2(date));
          } else {
            this.store.dispatch(replicateActions.setModelReplicationStartDate2(null));
          }
        }
      )
    );

    this.dpConfig = Object.assign({},
    {
      dateInputFormat: this.translateService.translations.datePlaceholder,
      minDate: this.minReplicationStartDate,
      maxDate: this.maxReplicationStartDate,
      showWeekNumbers: false,
      containerClass: 'theme-wtw',
    });
  }

   /**
   * Get All replication status name
   */
  getReplicationKeyStartList(): Array<{ label: string, value: ReplicationStartDateValue }> {
    return [
      { label: this.filterByReplicationStartBetweenText, value: 'Between' },
      { label: this.filterByReplicationStartBeforeText, value: 'Before' },
      { label: this.filterByReplicationStartAfterText, value: 'After' },
    ];
  }

    /**
   * When a date is selected, sets the focus in the date's input field.
   *
   * @param input calendar input field
   */
     onCalendarHidden(input: HTMLInputElement): void {
      input.focus();
    }

      /**
   * Clears the filter.
   */
  clearFilter(): void {
    this.store.dispatch(replicateActions.setModelName(null));
    this.store.dispatch(replicateActions.setModelStatus('All'));
    this.store.dispatch(replicateActions.setModelReplicationStartDate('Between'));
    this.store.dispatch(replicateActions.setModelReplicationStartDate1(null));
    this.store.dispatch(replicateActions.setModelReplicationStartDate2(null));
  }

    /**
   * Returns true if the filter tab 'All' or 'Models' is selected and the Replication Type filter 'All' or 'Radar Model' is selected.
   *
   * @returns true if the filter tab 'All' or 'Models' is selected and the Replication Type filter 'All' or 'Radar Model' is selected.
   */
     isTypeFilteredOnAllOrRadar(): boolean {
      return (this.filterTab === 'all' || this.filterTab === 'models');
    }
  
  
    /**
   * Form Filter function
   */
     performFilter(): void {
      const filters: Array<Filter<IReplication>> = [];
  
      if (this.filterReplicationNameControl.value) {
        filters.push((replication: IReplication) =>
          replication.asset.name.toLocaleLowerCase().indexOf(this.filterReplicationNameControl.value.toLocaleLowerCase()) !== -1
        );
      }
      if (this.isTypeFilteredOnAllOrRadar()) {
  
        if (this.filterReplicationStartDateControl.value === 'Between') {
          let date1 = this.filterReplicationStartDate1Control.value;
          let date2 = this.filterReplicationStartDate2Control.value;
          // If date2 is before date1 swap the dates
          if (moment(date2).isBefore(date1)) {
            const temp = date2;
            date2 = date1;
            date1 = temp;
          }
          if (date1 && date2) {
            filters.push((model: IReplication) => {
              const d1 = moment(date1).format('YYYY-MM-DD');
              const d2 = moment(date2).format('YYYY-MM-DD');
              const ks = moment(model.replication.requestedTimestampUtc).format('YYYY-MM-DD');
              return moment(ks).isBetween(d1, d2, null, '[]');
            });
          }
        } else if (this.filterReplicationStartDateControl.value === 'Before') {
          const date1 = this.filterReplicationStartDate1Control.value;
          if (date1) {
            filters.push((model: IReplication) => {
              const d1 = moment(date1).format('YYYY-MM-DD');
              const ks = moment(model.replication.requestedTimestampUtc).format('YYYY-MM-DD');
              return  moment(ks).isSameOrBefore(d1);
            });
          }
        } else if (this.filterReplicationStartDateControl.value === 'After') {
          const date1 = this.filterReplicationStartDate1Control.value;
          if (date1) {
            filters.push((model: IReplication) => {
              const d1 = moment(date1).format('YYYY-MM-DD');
              const ks = moment(model.replication.requestedTimestampUtc).format('YYYY-MM-DD');
              return  moment(ks).isSameOrAfter(d1);
            });
          }
        }
      }
      this.filters.emit(filters);
    }
}

編輯#2 - 僅使用根存儲並記錄 state:

  beforeEach(async () => {
    await TestBed.configureTestingModule({
...
        StoreModule.forRoot({
          replication: replicationReducer
        })
      ],
...
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(ReplicateFilterComponent);
    component = fixture.componentInstance;
    store = TestBed.inject(Store);
    fixture.detectChanges();
    store.subscribe(state => {
      console.log(state);
    });
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

我可以看到作為 state 的一部分記錄的“modelReplicationStartDate”屬性:

{replication: {…}}
replication:
filterTab: "all"
modelName: null
modelReplicationStartDate: "Between"
modelReplicationStartDate1: null
modelReplicationStartDate2: null
sortReplicateBy: "nameaz"
__proto__: Object
__proto__: Object

您同時提供了一個實際商店和一個模擬商店。 我對實際商店( StoreModule.forRoot() )有更多經驗。 我會擺脫mockStore

擺脫這個:

provideMockStore({ 
          initialState
        })

在提供者數組中。

然后試試這個:

let store: Store<any>; // instead of MockStore
....
component = fixture.componentInstance;
store = TestBed.inject(Store); // instead of MockStore
fixture.detectChanges();
store.subscribe(state => {
  // see the state of your store
  console.log(state);
});

您遇到的問題很可能是您在組件中使用的選擇器未正確映射到 state 在商店中的位置。 查看console.log(state)並查看您的選擇器如何從商店中獲取 state,我敢打賭,這就是Cannot read property modelReplicationStartDate of undefined來源。

編輯:

問題很可能是這一行:

this.store.pipe(select(replicateSelectors.getModelReplicationStartDate)).subscribe(
        filtered => {
          this.filterReplicationStartDateControl.setValue(filtered);
          this.performFilter();
        }
      ),

與商店結構不正確的選擇器一起使用。

我認為您的商店必須是這樣的:

StoreModule.forRoot({
          upload: uploadReducer,
          [managementServiceFeatureKey]: managementServiceReducer,
          assets: modelReducer,
          // you need another feature here
          replicate: replicateReducer,
        })

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM