0

I am running Angular unit test with Jasmine/Karma. Currently getting the below error:

TypeError: Cannot read property 'name' of undefined
    at ReplicateListComponent_Template (ng:///ReplicateListComponent.js:221:64)
    at executeTemplate (http://localhost:9876/_karma_webpack_/vendor.js:49857:9)
    at refreshView (http://localhost:9876/_karma_webpack_/vendor.js:49723:13)
    at refreshComponent (http://localhost:9876/_karma_webpack_/vendor.js:50894:13)
    at refreshChildComponents (http://localhost:9876/_karma_webpack_/vendor.js:49520:9)

Line 221 of ReplicateListComponent.js:

        jit___property_12('title',jit___pipeBind1_9(13,28,'upload_replicate_tooltip'))('routerLink',
            jit___pureFunction1_10(32,_c1,ctx.managementConfig.name));
        jit___advance_6(3);

ctx.managementConfig.name - ctx does not have a variable named managementConfig - see screenshot.

enter image description here

Unit test code:

  let component: ReplicateViewComponent;
  let fixture: ComponentFixture<ReplicateViewComponent>;
  let params: Subject<Params>;
  let queryParams: Subject<Params>;
  let mockManagementService: jasmine.SpyObj<ManagementConfigService>;
  let getManagementConfig: Subject<IManagementConfig>;
  let environmentSwitched: Subject<string>;

  beforeEach(async () => {
    params = new Subject<Params>();
    queryParams = new Subject<Params>();
    mockManagementService = jasmine.createSpyObj<ManagementConfigService>([ 'getManagementConfig', 'getCurrentManagementConfig', 'findManagementConfig', 'environmentSwitched' ]);
    getManagementConfig = new Subject<IManagementConfig>();
    environmentSwitched = new Subject<string>();
...
    await TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule,
        ToastrModule.forRoot({}),
        RouterTestingModule.withRoutes([
          { path: ':env/replicates', component: DummyComponent },
        ]),
...
      ],
      declarations: [
        ReplicateViewComponent,
...
        ReplicateListComponent,
...
      ],
      providers: [
        { provide: ManagementConfigService, useValue: mockManagementService },
        { provide: ActivatedRoute, useValue: ACTIVATED_ROUTE },
        { provide: RuntimeConfig, useValue: RUNTIME_CONFIG },
...
      ],
    })
    .compileComponents();

    mockManagementService.getManagementConfig.and.returnValue(getManagementConfig);
    mockManagementService.getCurrentManagementConfig.and.returnValue(RUNTIME_CONFIG.environments[0]);
    mockManagementService.findManagementConfig.and.returnValue(RUNTIME_CONFIG.environments[0]);
    mockManagementService.environmentSwitched.and.returnValue(environmentSwitched);
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(ReplicateViewComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    component.list.managementConfig = RUNTIME_CONFIG.environments[0];
    component.list.replicates = REPLICATIONS;
    component.details.replicate = REPLICATIONS[0];

    const router = TestBed.inject(Router);
    router.initialNavigation();

    getManagementConfig.next(RUNTIME_CONFIG.environments[0]);
    environmentSwitched.next(RUNTIME_CONFIG.environments[0].name);
    params.next({ id: '11111111-1111-1111-1111-111111111111' });
  });

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

Reason why am I expecting there to be a variable named managementConfig :

export class ReplicateListComponent extends BaseContainerComponent implements OnInit {

  managementConfig: IManagementConfig;
...
  ngOnInit(): void {
    this.subs.add(
      this.route.params.subscribe(
        (params: Params) => {
          this.managementConfig = this.managementService.findManagementConfig(params.env);

Offending section of ReplicateListComponent's HTML - see the routerLink

    <div class="my-auto ml-auto">
      <a data-cy="batchReplicateLink" [title]="'upload_replicate_tooltip' | translate" [routerLink]="[ '/', managementConfig.name, 'replicates', 'batch']" queryParamsHandling="preserve">
        <i class="icons8-sm icons8-Copy"></i>{{ 'replicate_batch_link' | translate }}
      </a>
    </div>

ReplicateListComponent.js is very similar to AssetListComponent.js which does not have the error. As you can see from the screenshot there is a managementConfig within the context of AssetListComponent:

enter image description here

The routerLink HTML of AssetListComponent is essentially the same:

      <a
        data-cy="uploadLink"
        [title]="'upload_asset_tooltip' | translate"
        [routerLink]="[ '/', managementConfig.name, 'assets', 'upload']"
        [queryParamsHandling]="'preserve'"
        *ngIf="managementConfig?.upload"
      >
        <i class="icons8-sm icons8-Upload"></i>{{ 'asset_list_upload_link' | translate }}
      </a>

Backend code of AssetListComponent:

export class AssetListComponent extends BaseContainerComponent implements OnInit {

  managementConfig: IManagementConfig;
...
  ngOnInit(): void {
    this.subs.add(
      this.route.params.subscribe(
        (params: Params) => {
          this.managementConfig = this.managementService.findManagementConfig(params.env);

  1. What caused the error?
  2. What is the fix?
  3. Why is the error in ReplicateListComponent but not AssetListComponent?

Thanks.

1 Answer 1

1

1.) The error is caused because the first fixture.detectChanges() you call, managementConfig is undefined at that point in time. Your other component, there is an *ngIf where this issue doesn't happen where it will be explained in #3.

2.) The fix would be to hide this HTML until managementConfig is truthy:

<div class="my-auto ml-auto" *ngIf="managementConfig">
      <a data-cy="batchReplicateLink" [title]="'upload_replicate_tooltip' | translate" [routerLink]="[ '/', managementConfig.name, 'replicates', 'batch']" queryParamsHandling="preserve">
        <i class="icons8-sm icons8-Copy"></i>{{ 'replicate_batch_link' | translate }}
      </a>
    </div>

3.) The error is in one place but not the other because in the second one you have an *ngIf:

The *ngIf="managementConfig?.upload" first checks if managementConfig exists (with the question mark) and if it does, does upload exist in the object, if it does, go ahead and show this HTML mark up.

If you remove this *ngIf, I think the error would happen here as well.

<a
        data-cy="uploadLink"
        [title]="'upload_asset_tooltip' | translate"
        [routerLink]="[ '/', managementConfig.name, 'assets', 'upload']"
        [queryParamsHandling]="'preserve'"
        *ngIf="managementConfig?.upload"
      >
        <i class="icons8-sm icons8-Upload"></i>{{ 'asset_list_upload_link' | translate }}
      </a>
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.