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!.
Related
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.
I am using Ionic 3 and angular 5. I have one feature module 'Settings':
import { NgModule } from '#angular/core';
import { IonicPageModule } from 'ionic-angular';
import { SettingsPage } from './test';
#NgModule({
declarations: [
SettingsPage,
],
imports: [
IonicPageModule.forChild(SettingsPage),
],
})
export class SettingsPageModule {}
Under Settings module I want to add 5 more pages.
I checked a lot of post and cannot found out how to do that. Is it even possible ?
The Ionic3 standard way, if you use lazy loading, is: one module per page.
If you use the CLI, ionic generate page command will do the job for you and create a folder with 4 files whenever you create a new page.
If you don't want pages to live each in a separate directory, one way could be to create the new page-related files in the same directory.
But, if you want multiple pages in one module, it's not going to work, at least in my experience - e.g. if you try to place two pages in the same directory and load them from the same module, you'll get this error message:
Error: /.../src/pages/pagegroup/pageX.ts has a #IonicPage decorator, but it does not have a corresponding "NgModule" at /.../src/pages/pagegroup/pageX.module.ts
Below I'm explaining a working solution to have multiple pages, each with its own module, in a single directory.
Let's assume you start from this:
src
settings
settings.html
settings.module.ts
settings.scss
settings.ts
...and you want to add a page named "CustomSettingsPage".
Depending on how complex this page is, you must add the following 2 to 4 files, in the same directory (src/settings) - the optional ones are between square braces:
[custom-settings.html]
custom-settings.module.ts
[custom-settings.scss]
custom-settings.ts
The module and page .ts files will have a similar structure:
custom-settings.module.ts
import { NgModule } from '#angular/core';
import { IonicPageModule } from 'ionic-angular';
import { CustomSettingsPage } from './custom-settings';
#NgModule({
declarations: [
CustomSettingsPage,
],
imports: [
IonicPageModule.forChild(CustomSettingsPage),
],
exports: [
CustomSettingsPage
]
})
export class CustomSettingsPageModule {}
custom-settings.ts
import { Component } from '#angular/core';
import { IonicPage } from 'ionic-angular';
#IonicPage()
#Component({
selector: 'page-custom-settings',
template: `<your html template here>`
})
export class CustomSettingsPage {
// your page code here
};
Instead of template: you could use templateURL: 'custom-settings.html' - in that case, you should create the corresponding custom-settings.html template file in the same directory.
If you want to add the CSS rules for this page, you can create an optional custom-settings.scss file like:
.page-custom-settings {
/* your rules here */
}
or, you can add those CSS rules to the existing settings.scss file: this is really up to you.
I believe what you want is "modals"
https://ionicframework.com/docs/components/#modals
You can add individual pages under your settings.
I'm late to this question but just in case someone needs it in the future!
I'm not sure if you are referring to this but you can have several pages into one module having a folder tree like, for example:
pages
settings
childPage 1
child1.page.(html|scss|ts)
childPage 1
child1.page.(html|scss|ts)
settings.module.ts
settings.page.(html|scss|ts)
then in your Setting Routing Module you do like
import ...
const routes: Routes = [{
path: '',
redirectTo: 'pathtoRedirect', // if you need it
pathMatch: 'full'
}, {
path: '',
component: SettingsPage
}, {
path: 'child-page-1',
component: ChildPage1
}, {
path: 'child-page-2',
component: ChildPage2
}]
#NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class SettingsPageModule {}
Then in your App Routing Module you can do something like:
import ...
const routes: Routes = [{
...
}, {
path: 'settings',
loadChildren: () => import('./pages/settings/settings.module').then(m => m.SettingsPageModule),
}, {
...
}]
#NgModule({
...
})
export class AppRoutingModule{}
This should work. What a component needs is for it to be in ANY module. It's true "module per page strategy" is included as best practice, however, sometimes I simply don't want to have many files that actually can be deleted and managed under the same module.
From this point, you can have a different folder tree if you feel like it, or even create a deeper nested route like
/settings
/settings/children/:id
/settings/children/otherchildren/childrenpage
Now it would be up to you and your project needs!
Hope this is useful for somebody!
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.
A button is displayed on the page. When the user selects the button the child component will appear, however, the following error appears - Error: Uncaught (in promise): Error: No component factory found for ModalComponent. Did you add it to #NgModule.entryComponents?
The structure I have set up is as follows and this is in conjunction with Ionic 3 -
app (folder)
- app.module
- app.component
components (folder)
- modal-component.ts
pages (folder)
- pageOne (folder)
- pageOne.module
- pageOne.ts
I put the modal component in the pageOne.module
pageOne.module
#NgModule({
declarations: [
pageOne,
modalComponent
],
entryComponents: [
modalComponent
],
imports: [
IonicPageModule.forChild(pageOne),
],
exports: [
pageOne,
]
})
export class pageOneModule {}
pageOne.ts
#IonicPage()
#Component({
selector: 'pageOne',
templateUrl: 'pageOne.html',
})
export class pageOne {}
Are you bootstrapping your module?
put this somewhere where it will load
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { pageOneModule } from './pages/pageOne/pageOne.module';
document.addEventListener('DOMContentLoaded', () => {
platformBrowserDynamic().bootstrapModule(pageOneModule)
.catch(err => console.log(err));
});
I would also suggest using the angular cli, It does this kind of stuff for you.
Your Page module isn't setup right for lazy loading. I'm not sure exactly what's causing the exact error but when you lazy load you don't export or declare the entry components. Change your page module to:
#NgModule({
declarations: [
pageOne,
modalComponent ],
imports: [ IonicPageModule.forChild(pageOne) ]
})
export class pageOneModule {}
Now, you didn't post your modal component code so I'm not sure if it's something you made or not. If it's something like the Material2 Modals, those need to be in entryComponents. Also, your Nav stacks should use the string name of the page now, not a import to make sure you are loading the same object vs cloning it.
I'm developing a github repository (with angular 7 and angular-cli), and I have some tests with Karma and Jasmine working in the master branch.
Now I'm trying to add lazy loading feature, the thing is, that the tests that before passed, now they do not. It's funny because only the tests from the lazy loading module are failing...
Here is the code and the error:
import {async, TestBed} from '#angular/core/testing';
import {APP_BASE_HREF} from '#angular/common';
import {AppModule} from '../../app.module';
import {HeroDetailComponent} from './hero-detail.component';
describe('HeroDetailComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [AppModule
],
providers: [
{provide: APP_BASE_HREF, useValue: '/'}
],
}).compileComponents();
}));
it('should create hero detail component', (() => {
const fixture = TestBed.createComponent(HeroDetailComponent);
const component = fixture.debugElement.componentInstance;
expect(component).toBeTruthy();
}));
});
The error is this:
Chrome 58.0.3029 (Mac OS X 10.12.6) HeroDetailComponent should create hero detail component FAILED
Error: Illegal state: Could not load the summary for directive HeroDetailComponent.
at syntaxError Users/ismael.ramos.silvan/WebstormProjects/angular4-example-app/~/#angular/compiler/#angular/compiler.es5.js:1690:22)
at CompileMetadataResolver.getDirectiveSummary Users/ismael.ramos.silvan/WebstormProjects/angular4-example-app/~/#angular/compiler/#angular/compiler.es5.js:15272:1)
at JitCompiler.getComponentFactory Users/ismael.ramos.silvan/WebstormProjects/angular4-example-app/~/#angular/compiler/#angular/compiler.es5.js:26733:26)
at TestingCompilerImpl.getComponentFactory Users/ismael.ramos.silvan/WebstormProjects/angular4-example-app/~/#angular/compiler/#angular/compiler/testing.es5.js:484:1)
at TestBed.createComponent Users/ismael.ramos.silvan/WebstormProjects/angular4-example-app/~/#angular/core/#angular/core/testing.es5.js:874:1)
at Function.TestBed.createComponent Users/ismael.ramos.silvan/WebstormProjects/angular4-example-app/~/#angular/core/#angular/core/testing.es5.js:652:1)
at UserContext.it Users/ismael.ramos.silvan/WebstormProjects/angular4-example-app/src/app/heroes/hero-detail/hero-detail.component.spec.ts:18:29)
at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke Users/ismael.ramos.silvan/WebstormProjects/angular4-example-app/~/zone.js/dist/zone.js:391:1)
at ProxyZoneSpec.onInvoke Users/ismael.ramos.silvan/WebstormProjects/angular4-example-app/~/zone.js/dist/proxy.js:79:1)
at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke Users/ismael.ramos.silvan/WebstormProjects/angular4-example-app/~/zone.js/dist/zone.js:390:1)
You can see the entire project, for more details if you need it.
UPDATE: added declaration like this:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
AppModule
],
declarations: [HeroDetailComponent],
providers: [
{provide: APP_BASE_HREF, useValue: '/'}
],
}).compileComponents();
}));
Now, new errors appears:
The pipe 'translate' could not be found ("<h1 class="section-title">{{[ERROR ->]'heroDetail' | translate}}</h1>
<md-progress-spinner *ngIf="!hero"
class="progre"): ng:///DynamicTestModule/HeroDetailComponent.html#0:28
Can't bind to 'color' since it isn't a known property of 'md-progress-spinner'.
And more... it's like all directives and components from angular material, and the pipe translate from ngx-translate/core do not appear to be included...
You passed HeroDetailComponent to TestBed.createComponent() without declaring the component first:
TestBed.configureTestingModule({
imports: [AppModule,
CommonModule,
FormsModule,
SharedModule,
HeroRoutingModule,
ReactiveFormsModule
],
providers: [
{provide: APP_BASE_HREF, useValue: '/'}
],
declarations: [HeroDetailComponent]
}).compileComponents();
Update for following errors in your test: Added some more imports (just take your HeroModule as a blueprint because that's basically what you want to import and provide).
You're missing the declarations, you need to add the class being tested into the declarations.
declarations: [component]
My coworker and I had this issue but the fix was way different than anything else on the internet.
We are using Visual Studio Code and the folder names are case insensitive. Because of that, we asked everyone to use a lowercase naming convention but eventually an uppercase name got into source control. We renamed it, in a roundabout way, and everything was fine.
A month later, my coworker started getting a specific unit test to break with this error message. Only his computer was breaking on that test. We literally commented out all the code that could possible be effecting the test and we still got the error. Finally, I globally searched for the class and we realized that the folder name had reverted back to the uppercase name. We renamed it back to a lowercase name, with no pending changes recognized might I add..., and the test worked.
Let that be a lesson to follow style guides. :)
For clarity, the fix was similar to changing the folder name FOO to foo.
This type of error raised due to missing adding component in declarations and services in provider of TestBed configuration.
beforeEach(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes([
{ path: 'home', component: DummyComponent },
{ path: 'patients/find', component: DummyComponent }
])],
declarations: [RoutingComponent, DummyComponent,BreadcrumbComponent],
providers : [BreadCrumbService]
});
you must import the component HeroDetailComponent in the right way. Notice that even case of letters is matter in paths. i.e ('#angular/forms' is correct, BUT'#angular/Forms' is not.
For those who are still having issues with this - I read a separate github issue that discussed changes the Angular team made to the beforeEach callback function.
Here is what I did:
beforeAll(async(() => {
TestBed.configureTestingModule({
declarations: [BannerNotificationComponent]
}).compileComponents()
fixture = TestBed.createComponent(BannerNotificationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
Using beforeAll fixes the issue. Hope this helps others as it took me about a day to get this resolve this obscure bug.
Answer copied out of question
The problem was that HeroesModule was not been imported anywhere. This works, because HeroesModule declares HeroDetailComponent, which was the initial problem:
import {async, TestBed} from '#angular/core/testing';
import {APP_BASE_HREF} from '#angular/common';
import {AppModule} from '../../app.module';
import {HeroDetailComponent} from './hero-detail.component';
import {HeroesModule} from '../heroes.module';
describe('HeroDetailComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
AppModule,
HeroesModule
],
providers: [
{provide: APP_BASE_HREF, useValue: '/'}
],
}).compileComponents();
}));
it('should create hero detail component', (() => {
const fixture = TestBed.createComponent(HeroDetailComponent);
const component = fixture.debugElement.componentInstance;
expect(component).toBeTruthy();
}));
});
If you want to test a component without compiling it then you can by declaring it as a provider:
beforeEach(() => {
TestBed.configureTestingModule({
// provide the component-under-test and dependent service
providers: [
WelcomeComponent,
{ provide: UserService, useClass: MockUserService }
]
});
// inject both the component and the dependent service.
comp = TestBed.get(WelcomeComponent);
userService = TestBed.get(UserService);
});
See: https://angular.io/guide/testing#component-test-basics
I imported the wrong module into the test suite! correcting which module was imported fixed this error.