user6162768
user6162768

Reputation:

Angular2: Jasmine test failing 'cannot read property length of undefined', seems like instance of my class not being created but not sure why

I am learning angular 2 and following along with this tutorial:

Angular 2 Tutorial

To me this is all because an instance of my Todo object is not being created for some reason and so it can't get it's properties. I cannot figure out why it would not be created though.

I have a jasmine test that is failing, this is the only one failing:

it('should display "Todos" in h1 tag', async(() => {
  let fixture = TestBed.createComponent(AppComponent);
  fixture.detectChanges();
  let compiled = fixture.debugElement.nativeElement;
  expect(compiled.querySelector('h1').textContent).toContain('Todos');
}));

This is my HTML Template:

    <section class="todoapp">
  <header class="header">
    <h1>Todos</h1>
    <input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodo.title" (keyup.enter)="addTodo()">
  </header>
  <section class="main" *ngIf="todos.length > 0">
      <ul class="todo-list">
        <li *ngFor="let todo of todos" [class.completed]="todo.complete">
          <div class="view">
            <input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
            <label>{{todo.title}}</label>
            <button class="destroy" (click)="removeTodo(todo)"></button>
          </div>
        </li>
      </ul>
  </section>
  <footer class="footer" *ngIf="todos.length > 0">
    <span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span>
  </footer>
</section>

This is my class where some of the code may be causing the error?

import { Component } from '@angular/core';
import { Todo } from './todo';
import { TodoDataService } from './todo-data.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [ TodoDataService ]
})
export class AppComponent {

  newTodo: Todo = new Todo();

  constructor(private todoDataService: TodoDataService) {
  }

  addTodo() {
    this.todoDataService.addTodo(this.newTodo);
    this.newTodo = new Todo();
  }

  toggleTodoComplete(todo) {
    this.todoDataService.toggleTodoComplete(todo);
  }

  removeTodo(todo) {
    this.todoDataService.deleteTodoById(todo.id);
  }

  getTodos() {
    return this.todoDataService.getAllTodos();
  }

}

This is part of the Jasmine output where I think it's trying to tell me what's wrong.

Failed: Error in ./AppComponent class AppComponent - inline template:5:24 caused by: Cannot read property 'length' of undefined
        Error: Error in ./AppComponent class AppComponent - inline template:5:24 caused by: Cannot read property 'length' of undefined

Here is my service code:

import { Injectable } from '@angular/core';
import { Todo } from './todo';

@Injectable()
export class TodoDataService {

  // Placeholder for last id so we can simulate
  // automatic incrementing of id's
  lastId: number = 0;

  // Placeholder for todo's
  todos: Todo[] = [];

  constructor() { }

  // Simulate POST /todos
  addTodo(todo: Todo): TodoDataService {
    if (!todo.id) {
      todo.id = ++this.lastId;
    }
    this.todos.push(todo);
    return this;
  }

  // Simulate DELETE /todos/:id
  deleteTodoById(id: number): TodoDataService {
    this.todos = this.todos.filter(todo => todo.id !== id);
    return this;
  }

  // Simulate PUT /todos/:id
  updateTodoById(id: number, values: Object = {}): Todo {
    let todo = this.getTodoById(id);
    if (!todo) {
      return null;
    }
    Object.assign(todo, values);
    return todo;
  }

  // Simulate GET /todos
  getAllTodos(): Todo[] {
    return this.todos;
  }

  // Simulate GET /todos/:id
  getTodoById(id: number): Todo {
    return this.todos.filter(todo => todo.id === id).pop();
  }

  // Toggle todo complete
  toggleTodoComplete(todo: Todo) {
    let updatedTodo = this.updateTodoById(todo.id, {
      complete: !todo.complete
    });
    return updatedTodo;
  }

}

Upvotes: 0

Views: 2086

Answers (1)

snorkpete
snorkpete

Reputation: 14564

In your TodoComponent template, you're referring to a todos array. But there isn't a todos array member on that TodoComponent. Hence, in your template, todos will be undefined.

 <section class="main" *ngIf="todos.length > 0">   <---  that todos doesn't exist on your component

Now, you have a TodoDataService that seems to encapsulate your list of todos. So, what makes the most sense is to expose that list of Todos from your TodoDataService (via something like a getTodos() method that returns your list of todos) and then you can use that list of todos in your template.

Upvotes: 3

Related Questions