I have an ember js controller function onRefreshClicked which call a function inside the route refreshData.
I would like to run a unit/integration test for this controller's function, but then it cannot find the link to the route's function and error out.
How do I do this in ember v3.2.x?
addon/controllers/scan-monitor.js
import { action } from '#ember/object';
export default class ScanMonitorController extends Controller {
#action
onRefreshClicked(clickEvent) {
debugger;
clickEvent.preventDefault();
// Some extra logic
this.send('refreshData'); // ----> ERROR OUT!
}
}
addon/routes/scan-monitor.js
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
import { action } from '#ember/object';
export default class ScanMonitorRoute extends Route {
#service store;
#action
refreshData() {
debugger;
this.refresh();
}
}
tests/unit/controllers/scan-monitor-test.js
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Controller | scan-monitor', function (hooks) {
setupTest(hooks);
let scanMonitorController;
hooks.beforeEach(function () {
scanMonitorController = this.owner.lookup('controller:scan-monitor');
});
test('controllers.scan-monitor.onRefreshClicked', function (assert) {
debugger;
// Assigning or not assigning the route doesn't make any difference.
// scanMonitorController.route = this.owner.lookup('route:scan-monitor');
scanMonitorController.onRefreshClicked(new MouseEvent('click', {}));
assert.ok(scanMonitorController);
});
});
The error
TypeError: Cannot read property 'trigger' of undefined
at Router.send (http://localhost:7357/assets/vendor.js:31600:28)
at ScanMonitorController.send (http://localhost:7357/assets/vendor.js:34358:16)
at ScanMonitorController.onRefreshClicked (http://localhost:7357/assets/vendor.js:173217:12)
at Object.<anonymous> (http://localhost:7357/assets/tests.js:527:29)
My favorite technique is to define methods on the route's model data, like this:
async model() {
return {
myMethod: () => {}
};
}
Then, in your controller you can invoke via this.model.myMethod()
Related
I am trying to hide back button on site-header that takes me to dashboard. I am using pod structure that is something like this:
pod
component
site-header
template.hbs
component.js
main
dashboard
In the component.js I used computed to get current route
import Component from '#ember/component';
import { inject as service } from '#ember/service';
import { computed } from '#ember/object';
export default Component.extend({
router: service (),
dashboard:computed('currentRouteName',function(){
if(this.get('currentRouteName') === 'main.dashboard.index'){
return true;
}
return false;
})
})
In template.hbs I used the following code to check the link.
{{#unless dashboard}}
{{#link-to "main.dashboard" class="back-btn"}}{{t "goBackToDashboard"}}{{/link-to}}
{{/unless}}
Still it is the same by tweaking the if/else conditions also I either get the button on all pages or on none.
Any help will be appreciated.
app/route.js:
import EmberRouter from '#ember/routing/router';
import config from './config/environment';
import { inject } from '#ember/service';
import $ from 'jquery';
const Router = EmberRouter.extend({
location: config.locationType,
rootURL: config.rootURL,
ajax: inject('ajax'),
});
Router.map(function () {
this.route('login', { path: 'login' });
this.route('main', { path: '/' }, function () {
this.route('dashboard', { path: '' }, function () {});
this.route("review", { path: "/review/:docId" }, function() { // eslint-disable-line
this.route("edit", { path: "/edit/:bitId" }); // eslint-disable-line
this.route('window_edit');
});
}
You mention that the computed property is in the component.js, and you are doing this.get('currentRouteName'), but that property does not exist in components.
I believe you need to use the router service in your component.
I'm assuming you are using pre-Octane syntax, so it should look something like this:
import Component from '#ember/component';
import { inject as service } from '#ember/service';
import { computed } from '#ember/object';
export default Component.extend({
router: service(),
dashboard: computed('router.currentRouteName',function() {
if (this.get('router.currentRouteName') === 'main.dashboard.index') {
return true;
}
return false;
})
});
I don't remember which version RouterService was first available, but I hope this helps!
I have a component that gets menu items and renders navbar. So now I'm writing integration test, and I want to make sure, that component renders right links and labels. First of all, I added router initialization to make link-to display href prop:
moduleForComponent('main-menu', 'Integration | Component | main menu',
{
integration: true,
setup() {
const router = getOwner(this).lookup('router:main');
router.setupRouter();
}
});
Now I want to create some fake routes to test component, and to be independent from application router's setup. So I try to use map function:
moduleForComponent('main-menu', 'Integration | Component | main menu', {
integration: true,
setup() {
const router = getOwner(this).lookup('router:main');
router.map(function() {
this.route('link1');
this.route('link2');
});
router.setupRouter();
}
});
And I get Promise rejected before "it renders": router.map is not a function. So how should I implement "fake routes" for tests?
Ok, solved the problem. If someone will ever need something similar, here's how I did it:
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import { getOwner } from '#ember/application';
import CustomRouter from 'app/lib/router';
moduleForComponent('main-menu', 'Integration | Component | main menu', {
integration: true,
setup() {
const application = getOwner(this),
Router = CustomRouter.extend()
;
Router.map(function() {
this.route('link1');
this.route('link2');
});
application.register('router:main', Router.extend());
application.lookup('router:main').setupRouter();
}
});
test('some awesome tests', function(assert) {
const menuItems = [
{url: 'link1', label: 'link1', href: '/link1'},
{url: 'link2', label: 'link2', href: '/link2'},
]
;
this.set('items', menuItems);
this.render(hbs`{{main-menu items=items}}`);
// some cool tests that now can check href attributes of links
// and don't depend on app's router setup
});
Per the Apollo Docs, I'd like to get the mutate() function from ApolloClient into the props belonging to my react component. Is this the correct/preferred way to do it?
class myComponent extends React.Component {
constructor(props) {
super(props);
this.mutate = props.client.mutate();
};
}
If you want to use the apollo client to call a mutation dynamically than you could use it like so:
import { withApollo } from 'react-apollo';
class myComponent extends React.Component {
constructor(props) {
super(props);
this.mutate = props.client.mutate;
}
onClick = () => {
this.mutate({
mutation: gql `${<define your graphql mutation>}`,
variables: { ... },
}).then(...);
}
...
}
export default withApollo(MyComponent);
Else i suggest you define your mutation statically with graphql and just call the mutation:
class MyComponent extends React.Component {
onClick = () => {
this.props.mutate({ variables: { ... } }).then(....);
}
...
}
const yourGraphqlMutation = gql `${<define your graphql mutation>}`;
export default graphql(yourGraphqlMutation)(MyComponent);
version: Angular 4.
I am injecting a parent component into child component to access parent method through child component.
In parent-component I had to use ChangeDetectorRef because it has a property which will update runtime.
Now code is working fine but child-component spec is throwing error for "ChangeDetectorRef" which is injected in parent component.
Error: No provider for ChangeDetectorRef!
Parent Component
import { Component, AfterViewChecked, ChangeDetectorRef, ChangeDetectionStrategy } from '#angular/core';
#Component({
selector: 'parent-component',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ParentComponent implements AfterViewChecked {
stageWidth: number;
constructor(private ref: ChangeDetectorRef) {
this.ref = ref;
}
//call this function as many times as number of child components within parent-component.
parentMethod(childInterface: ChildInterface) {
this.childInterfaces.push(childInterface);
}
ngAfterViewChecked() {
this.elementWidth = this.updateElementWidthRuntime();
this.ref.detectChanges();
}
}
Child Component
import { Component, AfterViewInit } from '#angular/core';
import { ParentComponent } from '../carousel.component';
#Component({
selector: 'child-component',
templateUrl: './carousel-slide.component.html',
styleUrls: ['./carousel-slide.component.scss'],
})
export class ChildComponent implements AfterViewInit {
constructor(private parentComponent: ParentComponent) {
}
ngAfterViewInit() {
this.parentComponent.parentMethod(this);
}
}
app.html
<parent-component>
<child-component>
<div class="box">Content of any height and width</div>
</child-component>
<child-component>
<div class="box">Content of any height and width</div>
</child-component>
<child-component>
<div class="box">Content of any height and width</div>
</child-component>
</parent-component>
Unit test for Child Component
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { ParentComponent } from '../parent.component';
import { ChildComponent } from './child.component';
describe('ChildComponent', () => {
let component: ChildComponent;
let fixture: ComponentFixture<ChildComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ChildComponent],
providers: [ParentComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ChildComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create \'ChildComponent\'', () => {
expect(component).toBeTruthy();
});
});
When I am trying to add ChangeDetectorRef in providers in child spec, I am getting following error.
Failed: Encountered undefined provider! Usually this means you have a
circular dependencies (might be caused by using 'barrel' index.ts
files.)
After googling and trying many things I am unable to find solution. Any help appreciated.
You are using a component as a provider, which is probably not your intention. Change your test module to declare both your parent and child components.
TestBed.configureTestingModule({
declarations: [ChildComponent, ParentComponent],
providers: []
})
I am able to get rid of this error by making "ChangeDetectorRef" as #Optional dependency in my Parent Component.
constructor(#Optional() private ref: ChangeDetectorRef) {
this.ref = ref;
}
This way my child component's configureTestingModule will ignore ParentComponent provider.
I have created new Cordova Plugin only on my machine. Then I added it to my project. It is working fine when I call that plugin. Now, I tried to make a structured caller for my plugin. I created a Provider for it, but the problem is I don't know how to call my plugin function from my Controller class. Below is my sample code.
Provider: my-service.ts
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
declare let myPlugin: any;
#Injectable()
export class MyService {
constructor(public http: Http) {
console.log('Hello MyService Provider');
}
public myFunction() {
myPlugin.myPluginFunction(
(data) => {
return data;
},
(err) => {
return err;
});
}
}
Pages: my-page.ts
import { Component } from '#angular/core';
import { NavController, ViewController } from 'ionic-angular';
import { MyService } from '../../providers/my-service';
#Component({
selector: 'page-my-page-ionic',
templateUrl: 'hello-ionic.html'
})
export class MyPage {
constructor(private viewCtrl: ViewController, private myService: MyService) {}
ionViewWillEnter() {
//I tried to call like this
this.myService.myFunction().subscribe(
data => {
alert("success");
},
error => {
alert("error");
});
}
}
It returns me this error - Property 'subscribe' does not exist on type 'void'. I don't know how to call that function, since my provider returns me success or error.
I think since your myFunction() does not return any observable you cannot subscribe to it. It just returns data directly.
You can use it like this in this case:
var data = this.myService.myFunction();
console.log("Data from plugin is :", data);
If you want to use it as an Observable, return a new observable like this:
public myFunction() {
return Observable.create(observer => {
myPlugin.myPluginFunction(
(data) => {
observer.next(data);
},
(err) => {
observer.next(data);
});
},
(err) => {
observer.error(err);
});
}