How to call a route function in Ember js controller function test? - ember.js

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

Need to show/hide a button depending on the page

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!

Create fake routes to test component's link-to

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
});

Correct Way to Move Mutate() from ApolloClient to React Component?

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);

Unit Testing No provider for ChangeDetectorRef

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.

Ionic 2: How to call Provider function in Controller class

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);
});
}