TypeError: Cannot read property ‘modelReplicationStartDate’ of undefined

  angular, jasmine, ngrx

When running Angular unit tests with Jasmine/Karma, I am getting the following error:

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

My test suite is a simple one:

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();
  });
});

As you can see, I have tried creating a mock store with the initial state. Relevant portions of the state code:

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',
};

The debugger shows that "state" is undefined:

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

I have followed the suggestions in Uncaught TypeError: Cannot read property 'coSearchCriteria' of undefined thrown – Angular Karma/Jasmine and the documentation in https://ngrx.io/guide/store/testing

Please let me the reason and the fix for the error in the subject. Thanks.

EDIT #1- Component code

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);
    }
}

Source: Angular Questions

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.