Unit Test Resolve Block in Angular component - angularjs

Does anyone know how to unit test resolve block items in a routed component? It would be great if anyone can test courseDetails in the resolve block as an example.
(function() {
'use strict';
angular
.module('writingsolutionsComponents')
.component('courseSettings', {
bindings: {
newCourseForm: '<',
courseDetails: '<',
timezones: '<',
citations: '<',
disciplines: '<'
},
templateUrl: 'course-settings/course-settings.html',
controller: 'CourseSettingsController'
})
.config(stateConfig);
stateConfig.$inject = ['$stateProvider'];
function stateConfig($stateProvider, $urlRouterProvider) {
$stateProvider.state('course-settings', {
parent: 'app',
url: '/:courseId/course-settings',
data: {
pageTitle: "Hello World"
},
views: {
'content#': {
component: 'courseSettings'
}
},
resolve: {
courseDetails: function(CourseService, $stateParams) {
return CourseService.get($stateParams.courseId);
},
timezones: function(TimezoneService) {
return TimezoneService.getTimeZones();
},
citations: function(CitationService) {
return CitationService.getCitations();
},
disciplines: function(DisciplineService) {
return DisciplineService.getAllDisciplines();
}
}
}
});
}
})();
I tried following and it didn't workout for me.
CourseServiceMock = {
get: function () {
return $q.resolve({courseId: "32432535", title: "Communication"});
}
};
spyOn(CourseServiceMock , 'get').and.callThrough();
expect(CourseServiceMock .get).toHaveBeenCalled()

Do not test your framework(s). Here that means do not test ui-router, it is known to work correctly. Specifically, we know it is going to call the registered resolve functions.
If you wish to test the resolve function itself, it is actually quite simple. We just need to make the function available to test. We can actually test this function without launching a web browser or doing anything fancy.
Here is an example that uses a test library called blue-tape* to run the test using NodeJS, you can adapt it to use Jasmine if you must.
// tests.js
const test = require("blue-tape");
test("courseDetails resolve retrieves the course based on route/:courseId", async t => {
// arrange
const courses = [{courseId: "32432535", title: "Communication"}];
const mockCourseService = {
get(id) {
return Promise.resolve(courses.find(course => course.id === id));
}
};
const mock$stateParams = {courseId: "32432535"};
// act
const result = await courseDetails(mockCourseService, mock$stateParams);
// assert
t.equal(result.courseId, courses[0].courseId);
});
To get this working, run
npm install --save-dev blue-tape
And then run
node test.js
The benefit is that your test does not depend on ui-router or even AngularJS and that it just tests what you care about, your business logic.
Note, this test itself is a bit arbitrary.
*I am using blue-tape instead of tape here because it makes testing asynchronous functions very simple as it automatically handles any function that returns a thennable.

Related

Implementing component require property in Angular 1.5 components

I am having no joy with implementing require: {} property as part of an angular component. Allow me to demonstrate with an example I have.
This is the component/directive that supposed to fetch a list of judgements. Nothing very fancy, just a simple factory call.
// judgements.component.js
function JudgementsController(GetJudgements) {
var ctrl = this;
ctrl.Get = function () {
GetJudgements.get().$promise.then(
function (data) {
ctrl.Judgements = data.judgements;
}, function (error) {
// show error message
});
}
ctrl.$onInit = function () {
ctrl.Get();
};
}
angular
.module('App')
//.component('cJudgements', {
// controller: JudgementsController,
//});
.directive('cJudgements', function () {
return {
scope: true,
controller: 'JudgementsController',
//bindToController: true,
};
});
I am trying to implement component require property to give me access to ctrl.Judgements from the above component/directive as follows:
// list.component.js
function ListController(GetList, GetJudgements) {
var ctrl = this;
ctrl.list = [];
ctrl.Get = function () {
GetList.get().$promise.then(
function (data) {
ctrl.list = data.list;
}, function (error) {
// show error message
});
};
//ctrl.GetJudgements = function () {
// GetJudgements.get().$promise.then(
// function (data) {
// ctrl.Judgements = data.judgements;
// }, function (error) {
// // show error message
// });
//}
ctrl.$onInit = function () {
ctrl.Get();
//ctrl.GetJudgements();
};
}
angular
.module('App')
.component('cTheList', {
bindings: {
listid: '<',
},
controller: ListController,
controllerAs: 'ctrl',
require: {
jCtrl: 'cJudgements',
},
template: `
<c-list-item ng-repeat="item in ctrl.list"
item="item"
judgements="ctrl.Judgements"></c-list-item>
<!--
obviously the reference to judgements here needs to change
or even better to be moved into require of cListItem component
-->
`,
});
Nice and simple no magic involved. A keen reader probably noticed GetJudgement service call in the ListController. This is what I am trying to remove from TheList component using require property.
The reason? Is actually simple. I want to stop database being hammered by Judgement requests as much as possible. It's a static list and there is really no need to request it more than once per instance of the app.
So far I have only been successful with receiving the following error message:
Error: $compile:ctreq
Missing Required Controller
Controller 'cJudgements', required by directive 'cTheList', can't be found!
Can anyone see what I am doing wrong?
PS: I am using angular 1.5
PSS: I do not mind which way cJudgement is implemented (directive or component).
PSSS: If someone wonders I have tried using jCtrl: '^cJudgements'.
PSSSS: And multiple ^s for that matter just in case.
Edit
#Kindzoku posted a link to the article that I have read before posting the question. I hope this also helps someone in understanding $onInit and require in Angular 1.5+.
Plunker
Due to popular demand I made a plunker example.
You should use required components in this.$onInit = function(){}
Here is a good article https://toddmotto.com/on-init-require-object-syntax-angular-component/
The $onInit in your case should be written like this:
ctrl.$onInit = function () {
ctrl.jCtrl.Get();
};
#iiminov has the right answer. No parent HTML c-judgements was defined.
Working plunker.

Angular 1.5 component unit testing with webpack

I'm trying to test a component with controller, with some bindings:
class AppSpecificController {
constructor() {
this.text = this.initialText ? this.initialText : 'No initial text has been specified.';
}
}
export const AppSpecificComponent = {
bindings: {
'initialText': '<bindInitialText'
},
templateUrl: '/app/components/app-specific/app-specific.html',
controller: AppSpecificController,
controllerAs: 'appSpecific'
};
export default AppSpecificComponent;
In my unit test file I don't want to load the complete application, just the things I need. So I figured to mock a module or just create a new one named something with mock, add the component to that module, and load that module:
import {AppSpecificComponent} from './app-specific.component';
describe('AppSpecificComponent', () => {
let controller;
let scope;
let $componentController;
beforeEach(() => {
angular.module('mock-module', [])
.component('appSpecific', AppSpecificComponent);
// this fails
module('mock-module');
inject((_$componentController_, $rootScope) => {
scope = $rootScope.$new();
$componentController = _$componentController_;
});
controller = $componentController('appSpecific', {$scope: scope}, {initialText: 'Some text'});
});
it('should test something', () => {
expect(true).toBeTruthy();
});
});
Creating the module mock-module is fine, but loading it fails, and obviously injecting stuff in the not-so-much-loaded-module fails, as well as creating a controller on which I can start testing. It would be nice to be able to test the components individually, separate from the application in which it runs.
Just using the new operator for the class AppSpecificController doesn't work since then the bindings you receive from the component are not there:
// fails, no bindings available in controller
controller = new AppSpecificController();
I found the answer somewhere else on StackOverflow, not sure where anymore. The answer however I was looking for:
angular.mock.module($provide => {
$provide.controller('SomeController', () => { ... });
$provide.constant('someConstant', 'some constant');
});

Is it the recommend way to wrap providers/modules in factorys?

For example I use
https://github.com/alexcrack/angular-ui-notification
It is recommend / cleaner to wrap such modules in seperate factorys?
Thanks!
Here is a example with toastr where its wrapped in a factory:
app.factory('notificationFactory', function () {
return {
success: function (text) {
toastr.success(text,"Success");
},
error: function (text) {
toastr.error(text, "Error");
}
};
});
Are there some benefits instead of using it directly in the controller:
toastr.success("Everything fine!","Success");

Mongoose and Angular Populate Issue

I am new to mongoose and Angular and I am having an issue with mongoose's populate method. I have the following two mongoose schemas
var JobSchema = new mongoose.Schema({
jobName: String,
jobType: String,
status: String,
examples: [{type: mongoose.Schema.Types.ObjectId, ref: 'Example'}]
});
mongoose.model('Job', JobSchema);
and
var ExampleSchema = new mongoose.Schema({
content: String,
job: {type: mongoose.Schema.Types.ObjectId, ref: 'Job'}
});
mongoose.model('Example', ExampleSchema);
So basically the Job schema contains Example's. I also have the following Express route for getting the examples from a particular Job. I used this tutorial to figure out how to do this.
var Job = mongoose.model('Job');
var Example = mongoose.model('Example');
router.get('/jobs/:job', function (req, res) {
req.job.populate('examples', function (err, job) {
if (err) {return next(err);}
res.json(job);
});
});
Also, I am using the following to automatically retrieve the job from mongo and attach it to req.
router.param('job', function (req, res, next, id) {
var query = Job.findById(id);
query.exec(function (err, job) {
if (err) {
return next(err);
}
if (!job) {
return next(new Error('can\'t find job'));
}
req.job = job;
return next();
});
});
I also have the following Angular factory that uses this route
app.factory('jobs', ['$http', function ($http) {
var o = {
jobs: []
};
o.get = function (id) {
return $http.get('/jobs/' + id).then(function (res) {
return res.data;
});
};
return o;
}]);
I also created the following state which is supposed to immediately populate the examples for a given Job id using the above factory.
.state('jobs', {
url: '/jobs/{id}',
templateUrl: '/jobs.html',
controller: 'NerCtrl',
resolve: {
post: ['$stateParams', 'jobs', function ($stateParams, jobs) {
return jobs.get($stateParams.id);
}]
}
});
The problem comes when I actually try to show the examples using a controller.
app.controller('NerCtrl', [
'$scope',
'job',
function ($scope, job) {
$scope.examples = job.examples;
}]);
The view that tries to use $scope.examples just displays {{examples}} rather than the actual content of the scope variable. In fact, nothing in the controller seems to work with the `job` injection (not even simple 'alerts').
It looks the problem comes from the `job` injection in the controller. This is supposed to refer to the job that is retrieved in the resolve given the id but it doesn't look like this is working.
In addition, I have curled an example record's url (eg. curl http://localhost:3000/jobs/56920a1329cda48f16fc0815) and it does return the desired Job record, so it does look like the route part is working correctly. I suspect the problem is somewhere in the 'resolve' or the way in which I am injecting the result of the resolve into the controller.
Ok this was a silly mistake. The post inside the Job state should have been job. i.e.
.state('jobs', {
url: '/jobs/{id}',
templateUrl: '/jobs.html',
controller: 'NerCtrl',
resolve: {
job: ['$stateParams', 'jobs', function ($stateParams, jobs) {
return jobs.get($stateParams.id);
}]
}
});
In my inexperience, I did not know what post was referring to, but I suppose it refers to the job that is returned from jobs.get($stateParams.id) which is then the name that gets injected in the controller. So obviously the name in resolve must be consistent with what is injected in the controller.

Inject module dynamically, only if required

I'm using Require.js in combination with Angular.js.
Some controllers need huge external dependencies which others don't need, for example, FirstController requires Angular UI Codemirror. That's a extra 135 kb, at least:
require([
"angular",
"angular.ui.codemirror" // requires codemirror itself
], function(angular) {
angular.module("app", [ ..., "ui.codemirror" ]).controller("FirstController", [ ... ]);
});
I don't want to have to include the directive and the Codemirror lib everytime my page loads just to make Angular happy.
That's why I'm right now loading the controller only when the route is hit, like what's done here.
However, when I need something like
define([
"app",
"angular.ui.codemirror"
], function(app) {
// ui-codemirror directive MUST be available to the view of this controller as of now
app.lazy.controller("FirstController", [
"$scope",
function($scope) {
// ...
}
]);
});
How can I tell Angular to inject ui.codemirror module (or any other module) in the app module aswell?
I don't care if it's a hackish way to accomplish this, unless it involves modifying the code of external dependencies.
If it's useful: I'm running Angular 1.2.0.
I have been trying to mix requirejs+Angular for some time now. I published a little project in Github (angular-require-lazy) with my effort so far, since the scope is too large for inline code or fiddles. The project demonstrates the following points:
AngularJS modules are lazy loaded.
Directives can be lazy loaded too.
There is a "module" discovery and metadata mechanism (see my other pet project: require-lazy)
The application is split into bundles automatically (i.e. building with r.js works)
How is it done:
The providers (e.g. $controllerProvider, $compileProvider) are captured from a config function (technique I first saw in angularjs-requirejs-lazy-controllers).
After bootstraping, angular is replaced by our own wrapper that can handle lazy loaded modules.
The injector is captured and provided as a promise.
AMD modules can be converted to Angular modules.
This implementation satisfies your needs: it can lazy-load Angular modules (at least the ng-grid I am using), is definitely hackish :) and does not modify external libraries.
Comments/opinions are more than welcome.
(EDIT) The differentiation of this solution from others is that it does not do dynamic require() calls, thus can be built with r.js (and my require-lazy project). Other than that the ideas are more or less convergent across the various solutions.
Good luck to all!
Attention: use the solution by Nikos Paraskevopoulos, as it's more reliable (I'm using it), and has way more examples.
Okay, I have finally found out how to achieve this with a brief help with this answer.
As I said in my question, this has come to be a very hacky way. It envolves applying each function in the _invokeQueue array of the depended module in the context of the app module.
It's something like this (pay more attention in the moduleExtender function please):
define([ "angular" ], function( angular ) {
// Returns a angular module, searching for its name, if it's a string
function get( name ) {
if ( typeof name === "string" ) {
return angular.module( name );
}
return name;
};
var moduleExtender = function( sourceModule ) {
var modules = Array.prototype.slice.call( arguments );
// Take sourceModule out of the array
modules.shift();
// Parse the source module
sourceModule = get( sourceModule );
if ( !sourceModule._amdDecorated ) {
throw new Error( "Can't extend a module which hasn't been decorated." );
}
// Merge all modules into the source module
modules.forEach(function( module ) {
module = get( module );
module._invokeQueue.reverse().forEach(function( call ) {
// call is in format [ provider, function, args ]
var provider = sourceModule._lazyProviders[ call[ 0 ] ];
// Same as for example $controllerProvider.register("Ctrl", function() { ... })
provider && provider[ call[ 1 ] ].apply( provider, call[ 2 ] );
});
});
};
var moduleDecorator = function( module ) {
module = get( module );
module.extend = moduleExtender.bind( null, module );
// Add config to decorate with lazy providers
module.config([
"$compileProvider",
"$controllerProvider",
"$filterProvider",
"$provide",
function( $compileProvider, $controllerProvider, $filterProvider, $provide ) {
module._lazyProviders = {
$compileProvider: $compileProvider,
$controllerProvider: $controllerProvider,
$filterProvider: $filterProvider,
$provide: $provide
};
module.lazy = {
// ...controller, directive, etc, all functions to define something in angular are here, just like the project mentioned in the question
};
module._amdDecorated = true;
}
]);
};
// Tadaaa, all done!
return {
decorate: moduleDecorator
};
});
After this has been done, I just need, for example, to do this:
app.extend( "ui.codemirror" ); // ui.codemirror module will now be available in my application
app.controller( "FirstController", [ ..., function() { });
The key to this is that any modules your app module depends on also needs to be a lazy loading module as well. This is because the provider and instance caches that angular uses for its $injector service are private and they do not expose a method to register new modules after initialization is completed.
So the 'hacky' way to do this would be to edit each of the modules you wish to lazy load to require a lazy loading module object (In the example you linked, the module is located in the file 'appModules.js'), then edit each of the controller, directive, factory etc calls to use app.lazy.{same call} instead.
After that, you can continue to follow the sample project you've linked to by looking at how app routes are lazily loaded (the 'appRoutes.js' file shows how to do this).
Not too sure if this helps, but good luck.
There is a directive that will do this:
https://github.com/AndyGrom/loadOnDemand
example:
<div load-on-demand="'module_name'"></div>
The problem with existing lazy load techniques is that they do things which I want to do by myself.
For example, using requirejs, I would like to just call:
require(['tinymce', function() {
// here I would like to just have tinymce module loaded and working
});
However it doesn't work in that way. Why? As I understand, AngularJS just marks the module as 'to be loaded in the future', and if, for example, I will wait a bit, it will work - I will be able to use it. So in the function above I would like to call some function like loadPendingModules();
In my project I created simple provider ('lazyLoad') which does exactly this thing and nothing more, so now, if I need to have some module completely loaded, I can do the following:
myApp.controller('myController', ['$scope', 'lazyLoad', function($scope, lazyLoad) {
// ........
$scope.onMyButtonClicked = function() {
require(['tinymce', function() {
lazyLoad.loadModules();
// and here I can work with the modules as they are completely loaded
}]);
};
// ........
});
here is link to the source file (MPL license):
https://github.com/lessmarkup/less-markup/blob/master/LessMarkup/UserInterface/Scripts/Providers/lazyload.js
I am sending you sample code. It is working fine for me. So please check this:
var myapp = angular.module('myapp', ['ngRoute']);
/* Module Creation */
var app = angular.module('app', ['ngRoute']);
app.config(['$routeProvider', '$controllerProvider', function ($routeProvider, $controllerProvider) {
app.register = {
controller: $controllerProvider.register,
//directive: $compileProvider.directive,
//filter: $filterProvider.register,
//factory: $provide.factory,
//service: $provide.service
};
// so I keep a reference from when I ran my module config
function registerController(moduleName, controllerName) {
// Here I cannot get the controller function directly so I
// need to loop through the module's _invokeQueue to get it
var queue = angular.module(moduleName)._invokeQueue;
for (var i = 0; i < queue.length; i++) {
var call = queue[i];
if (call[0] == "$controllerProvider" &&
call[1] == "register" &&
call[2][0] == controllerName) {
app.register.controller(controllerName, call[2][1]);
}
}
}
var tt = {
loadScript:
function (path) {
var result = $.Deferred(),
script = document.createElement("script");
script.async = "async";
script.type = "text/javascript";
script.src = path;
script.onload = script.onreadystatechange = function (_, isAbort) {
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
if (isAbort)
result.reject();
else {
result.resolve();
}
}
};
script.onerror = function () { result.reject(); };
document.querySelector(".shubham").appendChild(script);
return result.promise();
}
}
function stripScripts(s) {
var div = document.querySelector(".shubham");
div.innerHTML = s;
var scripts = div.getElementsByTagName('script');
var i = scripts.length;
while (i--) {
scripts[i].parentNode.removeChild(scripts[i]);
}
return div.innerHTML;
}
function loader(arrayName) {
return {
load: function ($q) {
stripScripts(''); // This Function Remove javascript from Local
var deferred = $q.defer(),
map = arrayName.map(function (obj) {
return tt.loadScript(obj.path)
.then(function () {
registerController(obj.module, obj.controller);
})
});
$q.all(map).then(function (r) {
deferred.resolve();
});
return deferred.promise;
}
};
};
$routeProvider
.when('/first', {
templateUrl: '/Views/foo.html',
resolve: loader([{ controller: 'FirstController', path: '/MyScripts/FirstController.js', module: 'app' },
{ controller: 'SecondController', path: '/MyScripts/SecondController.js', module: 'app' }])
})
.when('/second', {
templateUrl: '/Views/bar.html',
resolve: loader([{ controller: 'SecondController', path: '/MyScripts/SecondController.js', module: 'app' },
{ controller: 'A', path: '/MyScripts/anotherModuleController.js', module: 'myapp' }])
})
.otherwise({
redirectTo: document.location.pathname
});
}])
And in HTML Page:
<body ng-app="app">
<div class="container example">
<!--ng-controller="testController"-->
<h3>Hello</h3>
<table>
<tr>
<td>First Page </td>
<td>Second Page</td>
</tr>
</table>
<div id="ng-view" class="wrapper_inside" ng-view>
</div>
<div class="shubham">
</div>
</div>

Resources