Testing Material Design Angular components with jasmine - javascript

Edit2: Even more specific, using something like Material Button works fine. The Side Nav does not work well with the setup below, however.
Edit: I did more testing and found this is specifically related to Angular Material, as these errors don't occur without it. I'm still unsure how to fix it.
I'm trying to set up some basic tests for a new component, but I keep running into error after error. Specifically, after adding #angular/material to the project. The current error is:
Error: Found the synthetic listener #transform.start. Please include either "BrowserAnimationsModule" or "NoopAnimationsModule" in your application.
Which feels like a red herring to me.
Here is the spec:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { MatSidenavModule } from '#angular/material/sidenav';
import { AngularNavigationComponent } from './angular-navigation.component';
describe('AngularNavigationComponent', () => {
let component: AngularNavigationComponent;
let fixture: ComponentFixture<AngularNavigationComponent>;
beforeEach(
async(() => {
TestBed.configureTestingModule({
declarations: [AngularNavigationComponent],
imports: [RouterTestingModule, MatSidenavModule]
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(AngularNavigationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
And here is the component
import { Component, OnInit } from '#angular/core';
import { MatSidenavModule } from '#angular/material/sidenav';
import { RouterLink } from '#angular/router';
#Component({
selector: 'mysupercustomcat-navigation',
templateUrl: './angular-navigation.component.html',
styleUrls: ['./angular-navigation.component.scss']
})
export class AngularNavigationComponent implements OnInit {
constructor() {}
ngOnInit() {}
}
Can anyone tell me what I'm doing wrong, and also perhaps explain declarations vs. imports in the Testbed configuration area?
Edit: This is a hybrid app if that matters

I've added two key sections to avoid errors like this:
import { NO_ERRORS_SCHEMA } from '#angular/core';
is added at the top of the page and:
schemas: [NO_ERRORS_SCHEMA],
is added at the same level as declarations.
What does this do? This tells angular not to error on unknown elements or attributes. Now this works FOR THIS USE CASE because I am not super interested in integration testing. I believe that might cause problems in this case.
I am interested in unit testing, and ignoring template attributes is fine with me. Adding this lets me get to that level.
This answer is as complete as I could get it for the moment.

Related

Jasmine trying to read property that doesn't exist

I have an extremely simple component:
import { Component } from '#angular/core';
#Component({
selector: 'loading',
templateUrl: './loading.component.html',
styleUrls: ['./loading.component.scss']
})
export class LoadingComponent {
}
which just displays a static loading logo. No interactivity at all. I wrote a test just to test its instantiation:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { LoadingComponent } from './loading.component';
describe('LoadingComponent', () => {
let component: LoadingComponent;
let fixture: ComponentFixture<LoadingComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoadingComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoadingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should exist', () => {
expect(component).toBeTruthy();
});
});
In Firefox, this test throws an error: j is undefined, and in Chromium browsers I see Cannot read property 'startTime' of undefined. The template is simply:
Sample Text
for the sake of figuring out what's going on. So there's no mention of 'startTime' anywhere in those three files - what's going on??
Note
I do use startTime properties in other components, but I can't figure out how/why that would matter.
Edit
I've been able to get this error to disappear by just adding:
afterAll(() => {
TestBed.resetTestingModule();
});
to every single test in the project. I'm leaving the question open because I have no idea:
Why this works/what the underlying problem was
If I'm swinging a heavier axe than I need to - would adding it to only one test suffice?
The error you are seeing is definitely not coming from component and spec file you have posted. The error you are seeing is most likely coming from a component that does an async operation and the error occurs after the spec that instantiated it has already finished.
First off put an f in frontof the describes of this spec file.
fdescribe('LoadingComponent', () => {
This will cause the spec runner to only run this file. You will see that it passes fine.
Next you want to remove the f and the search your project for startTime, try running specs for components that use the property startTime one at a time.
If you can't locate it then put an x in front of the describes of them one at a time, eventually you will find which one is causing the error as it wont happen when you have an x on the spec that causes the error.
Any specs that do async function should use the async function so that it waits for all async activity to finish before moving on to the next spec.

Lazy Loading Angular / Ionic 3 Component AOT "is not a known element: Error"

This is driving me crazy, hopefully someone can shed some light on the problem. I am Lazy loading my Ionic components everything works fine in development, however when I go to compile AOT throws an error. I spent about 4 hours trying different ways to load this in I am lost, keep getting the same error.
From what I read and found in examples this should be correct. What am I missing here?
'tester' is not a known element: 1. If 'tester' is an Angular component, then verify that it is part of this
module. 2. To allow any element add 'NO_ERRORS_SCHEMA' to the '#NgModule.schemas' of this component. ("
<ion-list *ngIf="!id"> <ion-list-header> [ERROR -><tester></tester>
// components/tester/tester.ts
import { Component } from '#angular/core';
#Component({
selector: 'tester',
templateUrl: 'tester.html'
})
export class TesterComponent {
text: string;
constructor() {
console.log('Hello TesterComponent Component');
this.text = 'Hello World';
}
}
// components/components.module.ts
import { NgModule } from '#angular/core';
import { TesterComponent } from './tester/tester';
import {IonicModule} from "ionic-angular";
#NgModule({
declarations: [TesterComponent],
imports: [IonicModule],
exports: [TesterComponent,
]
})
export class ComponentsModule {}
// pages/faq/faq.module.ts
import { NgModule } from '#angular/core';
import { IonicPageModule } from 'ionic-angular';
import { FaqPage } from './faq';
import {ComponentsModule} from "../../components/components.module";
#NgModule({
declarations: [
FaqPage
],
imports: [
IonicPageModule.forChild(FaqPage), ComponentsModule
],
})
export class FaqPageModule {}
// pages/faq/faq.html
<tester></tester>
EDIT
Thanks to #Joel Joseph - Apparently the view needs to reside in the same directory as your parent component. I had the view .html file in a shared directory hence the problem.
templateUrl: '../shared/view/list.html'
changed to
templateUrl: 'list.html'
and it compiles fine now. Will leave this up incase anyone else has this issue.
Thanks to #Joel Joseph - Apparently the view needs to reside in the same directory as your parent component. I had the view .html file in a shared directory hence the problem.
templateUrl: '../shared/view/list.html'
changed to
templateUrl: 'list.html'
and it compiles fine now. Will leave this up incase anyone else has this issue.

Angular4/5 dependency injection docs not working

https://angular.io/guide/architecture#services
I'm following the docs on angular.io to inject dependencies like services, etc. I did everything they said and when I try to run it, the console keeps telling me:
Uncaught ReferenceError: LedgerService is not defined
I am doing nothing crazy except creating a simple component with a service where both constructors have console.log commands (constructors in both the component and service). I've done everything Angular says to do in their 2 paragraphs that details this feature of Angular.
The component itself is being injected into the main app module (with the service being injected into the component) and both the component and service were created with the Angular CLI. So there isn't much I've even done at all minus trying to inject the service. So I'm not sure where it is going wrong but it is definitely not working and just shows a blank page (when it previously had basic content by default).
I created both units, tried to specify providers in both the app.module and the component.ts file and neither works and yields the same error--when Angular claims either could work. I've also specified it as a private service within the constructor of the component.ts file.
Everything I've seen relating to this is always for Angular 1 or 2. Neither of which are even remotely similar to Angular 4/5.
If you really want to see this code, fine but it's literally just framework and nothing else:
bookkeeper.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-bookkeeper',
templateUrl: './bookkeeper.component.html',
styleUrls: ['./bookkeeper.component.css'],
providers: [LedgerServiceService]
})
export class BookkeeperComponent implements OnInit {
constructor(private service: LedgerServiceService) { }
ngOnInit() {
console.log("Ledger component works!");
}
}
app.module.ts:
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppComponent } from './app.component';
import { InterfaceComponent } from './interface/interface.component';
import { BookkeeperComponent } from './bookkeeper/bookkeeper.component';
#NgModule({
declarations: [
AppComponent,
InterfaceComponent,
BookkeeperComponent
],
imports: [
BrowserModule
],
providers: [
LedgerServiceService
],
bootstrap: [AppComponent]
})
export class AppModule { }
ledger-service.service.ts:
import { Injectable } from '#angular/core';
#Injectable()
export class LedgerServiceService {
constructor() {
console.log("wtf");
}
}
LedgerService is actually called LedgerServiceService because I initially created LedgerService manually and then tried to use the AngularCLI to generate a service and named it LedgerService and it created a service called LedgerServiceService. Naming is not what is wrong. I only initially called it simply LedgerService because I figured it would be confusing.
Your examples are missing the import.
Anywhere we use a custom type, we also need to import that type.
For that reason, in both the module and component you will need to add:
import { LedgerServiceService } from './your-path-here'
You can see this in the examples they give on https://angular.io/guide/dependency-injection

Angular2 Material ViewportRuler unit testing error

I have an Angular2 component, which contains a tab control from #angular/material.
I'm trying to test my component (see simplified code below - I get that there's no point in testing a component that's this simple), and am getting the following error:
Error: Error in ./MdTabHeader class MdTabHeader - inline template:0:0 caused by: No provider for ViewportRuler!
Error: No provider for ViewportRuler!
My assumption was to try and include ViewportRuler (https://github.com/angular/material2/blob/master/src/lib/core/overlay/position/viewport-ruler.ts) as a provider. When I do this (see commented out lines below), karma returns:
Uncaught SyntaxError: Unexpected token import
at http://localhost:9876/context.html:10
Which, from a bit of Googling, suggests that it's serving the .ts file to the browser, rather than the compiled .js. It's possible I'm referencing it from the wrong place.
My question is: how do I make my tests compile?
My code is:
my.component.ts:
#Component({
selector: 'test',
template: require('./test.component.html')
})
export class TestComponent {
items: any[];
constructor() {
this.items = [{ title: 'test 1', description: 'description 1' }, { title: 'test 2', description: 'description 2' }];
}
}
my.component.html:
<md-tab-group>
<md-tab *ngFor="let link of items">
<template md-tab-label>
<h4>{{link.title}}</h4>
</template>
{{link.description}}
</md-tab>
</md-tab-group>
my.component.spec.ts:
import { TestBed } from '#angular/core/testing';
import { Component} from '#angular/core';
import { MaterialModule } from '#angular/material';
import { ViewportRuler} from '#angular/material/core/overlay/position/viewport-ruler'
import { TestComponent } from './test.component';
describe("TestComponent",
() => {
let fixture, component;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [MaterialModule],
declarations: [TestComponent],
providers: [
//{ provide: ViewportRuler }
]
});
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
});
it('true = true', () => {
expect(true).toBe(true);
});
});
I've tried to include as much info as possible, but I'm new to the whole Angular world, so let me know if there's anything else I can provide.
Many thanks.
2.0.0-beta.3
MaterialModule is deprecated. Developers can either consume individual component modules and their dependencies as needed (e.g. MdButtonModule) or create their own custom module.
MaterialModule
MaterialModule (and MaterialRootModule) have been marked as
deprecated.
We've found that, with the current state of tree-shaking
in the world, that using an aggregate NgModule like MaterialModule
leads to tools not being able to eliminate code for components that
aren't used.
In order to ensure that users end up with the smallest code size
possible, we're deprecating MaterialModule, to be removed in the a
subsequent release.
To replace MaterialModule, users can create their own "Material" modul
within their application (e.g., GmailMaterialModule) that imports only
the set of components actually used in the application.
https://github.com/angular/material2/releases/tag/2.0.0-beta.3
2.0.0-beta.2
Material team removed .forRoot() making this a non-issue.
The use of Module forRoot has been deprecated and will be removed in the next release. Instead, just simply import MaterialModule directly:
#NgModule({
imports: [
...
MaterialModule,
...
]
...
});
https://github.com/angular/material2/releases/tag/2.0.0-beta.2
MaterialModule.forRoot() sets up the providers, which you'll need in a testing module. This should resolve errors like yours and similar ones like No provider for MdIconRegistry!.

navigate and navigateByUrl not working correctly in ngOnInit on 2.0.0-rc.1

Since the 2.0.0-rc.1 router.navigate or router.navigateByUrl doesn't seem to be working anymore correctly when used inside ngOnInit.
I don't get an error or anything, but it looks like the navigate happens too early, because right after it the HomeComponent is loaded and "replaces" the content of the LetterComponent. However the URL is correct (/letter).
If I put the navigate in a setTimeout with 1000ms it works again.
Do I need to use a different lifecycle event or am I doing something wrong? Or is it a bug?
Note that: normally the value which specifies where to navigate to, comes from a cookie (is dynamic).
Here's my simplified app component:
#Component({
selector: 'my-app',
templateUrl: './app/app.component.html',
directives: [ROUTER_DIRECTIVES]
})
#Routes([
{ path: '/', component: HomeComponent },
{ path: '/letter',component: LetterComponent },
{ path: '/cv', component: CVComponent },
{ path: '/attachment', component: AttachmentComponent }
])
export class AppComponent implements OnInit {
constructor(private _router: Router) {
}
ngOnInit() {
// todo: currently not working correctly (issue?)
//also doesn't work: this._router.navigateByUrl("/letter");
this._router.navigate(["/letter"]);
}
}
Here's a demo of what happens.
When I access the app without a path it should directly navigate to the /letter "page" - as you can see the URL changes, but the content is the wrong one (the content is the one of the home component).
Update:
Here are my imports, if they are of any relevance:
import {Component} from '#angular/core';
import {Router, Routes, ROUTER_DIRECTIVES} from '#angular/router';
import {OnInit} from '#angular/core';
Here's my bootstraping:
import { bootstrap } from '#angular/platform-browser-dynamic';
import {AppComponent} from './app.component';
import {ROUTER_PROVIDERS} from '#angular/router';
import 'rxjs/Rx';
bootstrap(AppComponent, [
ROUTER_PROVIDERS
]);
Regarding #eburgers post:
Here's what I did:
import {Component} from '#angular/core';
import {Routes, ROUTER_DIRECTIVES, Router, ROUTER_PROVIDERS, OnActivate} from '#angular/router';
import {OnInit} from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app/app.component.html',
directives: [ROUTER_DIRECTIVES]
})
#Routes([
{ path: '/', component: HomeComponent },
{ path: '/letter',component: LetterComponent },
{ path: '/cv', component: CVComponent },
{ path: '/attachment', component: AttachmentComponent }
])
export class AppComponent implements OnActivate {
constructor(private _router: Router) {
}
routerOnActivate() {
// todo: currently not working correctly (issue?)
//also doesn't work: this._router.navigateByUrl("/letter");
this._router.navigate(["/letter"]);
}
}
Probably you should do that trick
setTimeout(() => this._router.navigate(["/letter"]), 0);
UPDATE
So, I downloaded your code to see what the issue is.
the Router documentation suggests to use navigate() instead of
navigateByUrl(). So you might want to change that.
It seems that the root route is overriding your subroute.
To resolve the issue, you simply need to put the current root route into a separate subroute, eg. /home.
{ path: '/home', component: HomeComponent }
I could not find any documentation for this behavior, but for a reason similar to this, even the Angular tutorial at angular.io does not have a root route. It only has /dashboard, /heroes and /detail/:id.
PREVIOUS ANSWER
Looks like you are missing the ROUTER_PROVIDER in the #Component declaration.
Try adding that as:
#Component({
selector: 'my-app',
templateUrl: './app/app.component.html',
directives: [ROUTER_DIRECTIVES],
providers: [ROUTER_PROVIDERS]
})
Also, import the appropriate references:
import {Routes, Router, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '#angular/router';
Try moving it into routerOnActivate instead of ngOnInit.
You can do it by importing OnActivate and implementing it in your class.
Also make sure you are bootstraping your app with ROUTER_PROVIDERS.
I'm pretty sure it's a bug.
I opened a new issue on github: https://github.com/angular/angular/issues/8733
When I get an answer / it's fixed I'll update this answer here with the solution (if there is any).
In the meantime there is a workaround, see #funkycoder post. Even though the workaround is for most users probably also not satifying.
I don't know if that's a bug or you are doing anything wrong. But instead of using the timeout method, I would suggest following approach:
ngOnInit() {
this.redirectToPath();
}
redirectToPath() {
this._router.navigate(["/letter"]);
}
You have probably already resolved this by now, but I wonder if the order of your routes is the real problem. If I remember correctly, the router tries to match the path to the one given. So, in your listing, '/letter' matches the route '/' first, so it returns 'HomeComponent'.
I think if you had reordered your routes so that the route '/' was last, then it would have matched '/letter' correctly.
in other cases , probably you have a href="#" in the tag, wich is overriding after router.navigate

Categories