Angular Rails Karma testing Controller with factory (with backend api calls) dependency - ruby-on-rails

I have a projectFactory:
#app.factory "projectFactory", ['$http', ($http) ->
factory = {}
factory.loadProject = (projectId) ->
$http.get( endpoint(projectId) )
(endpoint is a method that generates the backend api url)
I then have a projectCtrl that is dependent on that factory:
#app.controller 'ProjectCtrl', ['$scope','$routeParams', 'projectFactory', ($scope, $routeParams, projectFactory) ->
$scope.projectId = $routeParams.projectId
$scope.loadProject = (projectId) ->
projectFactory.loadProject(projectId)
.success((data)->$scope.project = data.project)
I then have my project_control_spec test:
'use strict'
describe "ProjectCtrl", ->
beforeEach module 'app'
ProjectCtrl = {}
$scope = {}
projectFactory = {}
beforeEach ->
module($provide) ->
$provide.factory "projectFactory", projectFactory
module inject($controller, $rootScope) ->
$scope = $rootScope.$new()
ProjectCtrl = $controller 'ProjectCtrl', {
$scope : $scope,
$routeParams: {projectId: 1},
}
it "should instantiate a PC", ->
expect(ProjectCtrl).toBeDefined()
it "should have access to the projectId via the routeParams", ->
expect($scope.projectId).toEqual(1)
it "should have access to projectFactory", ->
expect($scope.projectFactory).toBeDefined()
it "should create $scope.project when calling loadProject", ->
expect($scope.project).toBeUndefined();
expect($scope.loadProject(1)).toBe(1)
expect($scope.project).toEqual({//a project object})
I am getting the error ReferenceError: Can't find variable: $provide, when trying to require my projectFactory

You cannot inject $provide on line module inject($controller, $rootScope, $provide) ->. It is also not used or needed in any case.
You should also test this case with $httpBackend. Check the first example.

What if you try this ?
(It is in javascript, sorry I can't write coffee yet)
beforeEach( inject (function( $injector ){
$provide = $injector.get('$provide');
// ...
}));

I'm not familiar with CoffeeScript, but this is how I would do it in plain old JS:
var projectFactory = {};
beforeEach(function () {
module('app', function ($provide) {
$provide.factory('projectFactory', projectFactory);
});
});
When taking some of your code and running it through a Coffee to JS interpreter, I get the following result:
describe("ProjectCtrl", function() {
var $scope, ProjectCtrl, projectFactory;
beforeEach(module('app'));
ProjectCtrl = {};
$scope = {};
projectFactory = {};
beforeEach(function() {
module($provide)(function() {
$provide.factory("projectFactory", projectFactory);
});
});
});
Basically you're trying to load a second module called $provide, when what you actually want to do is open up a config block for the first module (app) and inject $provide into the configuration block.
If you were using angular.module for your actual implementation of app, it'd look something like this:
angular.module('app', []).config(function ($provide) {
$provide.value('..', {});
$provide.constant('...', {});
/** and so on **/
});
Whereas in your specs when using angular-mocks, a config block gets set like this:
module('app', function ($provide) {
$provide.value('..', {});
/** and so on **/
});
I hope that helps. I'm not sure how you would actually write this in CoffeeScript, but I'm sure you can figure that one out. When taking the result of the Coffee to JS interpreter of your code, I received the same result - there is no variable $provide.

Related

Splitting Yeoman prompts into multiple separate groups

how would one split a yeoman prompt into parts?
I have a rather extended prompt that i'd like to split into parts with a title for each part.
CSS
- prompt1
HTML
-prompt 2
Something like this:
prompt1: function(){
var done = this.async();
condole.log('title 1');
var prompts = [{
name: 'prompt1',
message: 'Prompt 1:',
}]
},
prompt2: function(){
var done = this.async();
condole.log('title 2');
var prompts = [{
name: 'prompt2',
message: 'Prompt 2:',
}]
},
Thanks!
Update as #Deimyts notes in the comments, the original code stopped working. This is due to API changes in Inquirer.JS documented here.
The base API interface is now inquirer.prompt(questions).then(). There's no more callback function.
Any async question functions is taking a promise as return value instead of requiring this.async().
In a nutshell, instead of using the old var done = this.async() API and resolving the prompt inside the callback with done() just return a promise from the prompting functions (see docs).
prompt1: function() {
this.log("HTML")
return this.prompt([
// configure prompts as before
]).then(function (answers) {
// callback body as before, but no more calling done()
}.bind(this));
},
For more details see #Deimyts answer below.
Yeoman uses a run loop with certain predefined priorities that you can use to put your actions into. As mentioned in the ☞ docs you can group several methods at one priority. Here is a snippet to illustrate a generator with prompts split into two groups HTML and CSS:
'use strict';
var generators = require('yeoman-generator');
module.exports = generators.Base.extend({
constructor: function () {
generators.Base.apply(this, arguments);
},
prompting: {
prompt1: function() {
this.log("HTML")
var done = this.async();
this.prompt([{
type : 'input',
name : 'foo',
message : 'Foo',
}, {
type : 'input',
name : 'bar',
message : 'Bar'
}], function (answers) {
this.foo = answers.foo;
this.bar = answers.bar;
done();
}.bind(this));
},
prompt2: function() {
this.log("CSS")
var done = this.async();
this.prompt([{
type : 'input',
name : 'bam',
message : 'Bam',
}], function (answers) {
this.bam = answers.bam;
done();
}.bind(this));
}
},
configuring: function () {
console.log(this.foo, this.bar, this.bam);
}
});
Using this feature of Yeoman you could modularize your code even further, e.g. by putting your different prompts in separate code files and require / import them into your generator file. But basically the above snippet should do the trick.
Let me know if that helps.
The previous answer wasn't working for me until I made several modifications to the example code.
I can't be 100% certain, but I believe that the difference might be due to differing versions of the yeoman-generator module. So, I'm recording this here in case anyone else runs into the same issue.
For reference, I'm using yeoman-generator v0.23.4, yo v1.8.4, node v6.2.2, & npm v3.9.5.
Modifications:
Remove all instances of var done = this.async(); and done().
The async() function was causing the generator to exit after prompt1, and never run prompt2 or the configuring function.
Add return before calling this.prompt();
Removing async() causes the generator to rush through the prompts without waiting for an answer. Adding return fixes this.
Replace the callback function inside this.prompt() with .then().
Before making this change, the generator would run through the prompts correctly, but would not save the answers, and configuring would simply log undefined undefined undefined.
Original: this.prompt(prompts, callback(answers).bind(this))
Revised: this.prompt(prompts).then(callback(answers).bind(this));
Full Example:
'use strict';
var generators = require('yeoman-generator');
module.exports = generators.Base.extend({
constructor: function () {
generators.Base.apply(this, arguments);
},
prompting: {
prompt1: function() {
this.log("HTML")
return this.prompt([{
type : 'input',
name : 'foo',
message : 'Foo',
}, {
type : 'input',
name : 'bar',
message : 'Bar'
}]).then(function (answers) {
this.foo = answers.foo;
this.bar = answers.bar;
}.bind(this));
},
prompt2: function() {
this.log("CSS")
return this.prompt([{
type : 'input',
name : 'bam',
message : 'Bam',
}])
.then(function(answers) {
this.bam = answers.bam;
}.bind(this));
}
},
configuring: function () {
console.log(this.foo, this.bar, this.bam);
console.log('Config: ', this.config);
},
});

How can I save in Dalekjs terminal

How can I save the content of the 'test' object after its opened?
for example:
module.exports = {
'Page title is correct': function (test) {
test
.open('http://google.com')
// save content of test the HTML DOM
.assert.title().is('Google', 'It has title')
.done();
}
};
or maybe to interact directly with phantomJS
It is possible?
Even if it is not the main purpose of DalekJS to do things like this, it is technically possible to do it like so:
module.exports = {
'Can store html': function (test) {
test.open('http://google.com')
.execute(function () {
this.data('htmlData', window.document.getElementsByTagName('html')[0].outerHTML);
})
.log.message(function () {
var data = test.data('htmlData');
var fs = require('fs');
fs.writeFileSync('nameOfTheFile.html', data);
return data;
})
.assert.title().is('Google', 'It has title')
.done();
}
};
We execute some in browser JavaScript using the execute method & pipe it over to node using the data('key', 'value') storage method.
We access this data later in our log.message method which runs in the node context. This enables us to load the FileSystem module & finally to store the captured html into a file.

how can I make a $(document).ready using coffeescript to call my functions?

I am trying to find a way to "call" my functions, for example, I have this function
initSlider = ($element)->
$element.find('.slider').each ->
$this.slider
... some functions
that prints on my web this
initSlider();
root = typeof exports !== "undefined" && exports !== null ? exports : this;
root.foo = function() {
return function($element) {
$element.find('.slider').each(function() {
$this = $(this);
$this.slider({
... and so on
and what I basically want to do its something like initSlider() so, the function can be executed... I tried just like that initSlider() on my file, but does nothing...
Thanks
$ -> initSlider()
would result in
$(function(){ initSlider() })
that, of course, presume that initSlider is in the global scope
another option is doing something like
initSlider = ($elm) ->
// your code indented
$ -> initSlider($elm)

Error: Unknown provider: aProvider <- a

I'm using AngularJS in a Ruby on Rails 3.2.8 project with assets.
When I load up my form which is using AngularJS on my development machine I don't have a problem. However when I load the same form up on my production server I get this error in the Javascript console:
Error: Unknown provider: aProvider <- a
I've tracked it back to my coffeescript file where I setup AngularJS for use within a form:
$ (event) ->
$("#timesheet_description").autocomplete({source: '/autocomplete/work_descs'})
# Create AngularJS module
app = angular.module 'timesheetApp', []
# Create a AngularJS controller
app.controller "TimesheetCtrl", ($scope) ->
$scope.costed_amount = 0
# Bind my module to the global variables so I can use it.
angular.bootstrap document, ["timesheetApp"]
If I comment all this out the page will load without errors and without AngularJS abilities.
Is the problem due to Rails assets compiling and minify?
Is there a way to fix this and still use coffeescript and Rails assets?
AngularJS, when using the style you're using right now (called pretotyping), uses the function argument names to do dependency injection. So yes, minification does break this completely.
The fix is simple, though. In every case where you need injection (are using '$xxx') variables, do this:
app.controller "TimesheetCtrl", ['$scope', ($scope) ->
$scope.costed_amount = 0
]
Basically, replace all function definitions with an array. The last element should be the function definition itself, and the first ones are the $names of the objects you want injected.
There's some more (albeit not clear enough) info on the docs.
If you miss the array notation somewhere , to locate this we need to modify the angular code little bit, but its very quick solution.
change is console.log("Array Notation is Missing",fn); ( line no 11 from function start)
Find out annotate function in angular.js (non-minified)
function annotate(fn) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
console.log("Array Notation is Missing",fn);
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
arg.replace(FN_ARG, function(all, underscore, name){
$inject.push(name);
});
});
}
fn.$inject = $inject;
}
} else if (isArray(fn)) {
last = fn.length - 1;
assertArgFn(fn[last], 'fn');
$inject = fn.slice(0, last);
} else {
assertArgFn(fn, 'fn', true);
}
return $inject;
}
To minify angular all you need is to do is to change your declaration to the "array" declaration "mode" for example:
From:
var demoApp= angular.module('demoApp', []);
demoApp.controller(function demoCtrl($scope) {
} );
To
var demoApp= angular.module('demoApp', []);
demoApp.controller(["$scope",function demoCtrl($scope) {
}]);
How to declare factory services?
demoApp.factory('demoFactory', ['$q', '$http', function ($q, $http) {
return {
//some object
};
}]);

Backbone + Jasmine testing AJAX success callback in router

Say I have the following Backbone Router:
class App.Routers.ThingsRouter extends Backbone.Router
initialize: -> new App.Collections.ThingsCollection()
index: ->
that = this
#collection.fetch success: ->
view = new App.Views.ThingsIndex(collection: that.collection)
$('#app-container').html(view.render().el)
I need to write a Jasmine spy that can watch this and ensure that App.Views.ThingsIndex() is called. However, as it's AJAX, the following won't work:
describe 'index', ->
#router = new App.Routers.ThingsRouter()
spyOn(#router.collection, 'fetch')
fake = { render: -> '' }
#previewsIndexStub = spyOn(Periscope.Views, 'PreviewsIndex').andReturn(fake)
#router.index()
expect(#previewsIndexStub).toHaveBeenCalled()
Because Jasmine runs the expectation function before the AJAX call can complete. Is there a good way to test a callback like this?
Use jasmines built in waitsFor & runs method in order to wait for the ajax call to complete before executing your expect function. See Jasmine - Asynchronous specs for documentation on these two functions.
describe 'index', ->
#router = new App.Routers.ThingsRouter()
spyOn(#router.collection, 'fetch')
fake = {}; fake.render = -> '';
#previewsIndexStub = spyOn(Periscope.Views, 'PreviewsIndex').andReturn(fake)
#router.index()
waitsFor => #previewsIndexStub.wasCalled
###
Or if you want to wait for the method to be called more than once
use form waitsFor => #previewsIndexStub.callCount > 1
###
runs => expect(#previewsIndexStub).toHaveBeenCalled()

Resources