I'm using teaspoon as the test runner, which load all template files (using angular-rails-templates), which populate the templates module with stuff like this:
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("directives/sample/tmpl.html", '<span>Hey</span>')
}]);
And my directive coffee is like this:
angular.module 'myApp'
.directive 'sample', () ->
restrict: 'E'
templateUrl: 'directives/sample/tmpl.html'
scope:
address: "="
controllerAs: 'vm'
bindToController: true
controller: () ->
And my test coffee
describe "sample directive", ->
element = undefined
beforeEach ->
module("myApp", "templates")
# Store references to $rootScope and $compile
# so they are available to all tests in this describe block
beforeEach ->
inject ($compile, $rootScope) ->
scope = $rootScope.$new()
scope.address = "abcdef"
element = $compile("<div><sample address='address'/></div>")(scope)
scope.$digest()
it "Replaces the element with the appropriate content", ->
expect(element.html()).toContain "Hey"
The directive will never render with this. If I replace the templateUrl with template content then it runs fine. But that is not useful at all. What am I missing to let the directive render during test with templateUrl?
You haven't closed the <sample> tag in $compile
Try changing:
element = $compile("<div><sample address='address'</div>")(scope)
To
element = $compile("<div><sample address='address'></sample></div>")(scope)
Related
I'm trying to make a link to create new object when no results were found. I'm using Rails 4. I've included gem gem "select2-rails", added //= require select2 in application.js and
*= require select2
*= require select2-bootstrap
in application.js. Here's my coffeescript which should work fine (ajax working well) but function formatNoMatches seems to do nothing.
$('#invoice_client_id').select2
theme: 'bootstrap'
formatNoMatches: (term) ->
return "<a href='#' onclick='alert('" + term + "');'" + "id='newClient'>Add New Client</a>";
ajax:
url: '/clients.json'
dataType: 'json'
processResults: (data) ->
return {
results: $.map(data, (client, i) ->
{
id: client.id
text: client.name
}
)}
I have found in documentation no mentions of formatNoMatches but in a lot of user examples it's used to do same thing like here.
EDIT
Link or custom text doesn't appear at all.
I looked around a bit and found this way of doing it in the 4.0+ versions.
$('select').select2
language:
noResults: ->
return "<a href='http://google.com'>Add</a>";
escapeMarkup: (markup) ->
return markup;
Let me know if this works for you!
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.
I'm very new to Backbone and I'm making a to do list app to learn the basics. Right now you can add a task and it is rendered in the to do list. Each task has the properties name (string) and complete(boolean). I'd like to make it so that when the checkbox (.toggle) is checked, the 'complete' property is changed to true. I also have an 'x' button (.destroy), that when clicked should remove the task from the database. I'm having trouble getting the markComplete and clear events to work correctly. Here's my tasks_index.js.coffee view:
class Backbonetodo.Views.TasksIndex extends Backbone.View
template: JST['tasks/index']
events:
'submit #new_task': 'createTask'
'click .toggle': 'markComplete'
'click .destroy': 'clear'
#I'd like this to change complete to true and put a line through the list item
markComplete: ->
#collection.set(complete:true)
initialize: ->
#collection.on('reset', #render, this)
#collection.on('add', #appendTask, this)
render: ->
$(#el).html(#template())
#collection.each(#appendTask)
this
appendTask: (task) ->
view = new Backbonetodo.Views.Task(model: task)
$('#tasks').append(view.render().el)
createTask: (task) ->
event.preventDefault()
attributes = name: $('#new_task_name').val()
#collection.create attributes,
wait: true
success: -> $('#new_task')[0].reset()
error: #handleError
handleError: (task, response) ->
if response.status == 422
errors = $.parseJSON(response.responseText).errors
for attribute, messages of errors
alert "#{attribute} #{message}" for message in messages
#This should remove the selected task from the database
clear: (task) ->
event.preventDefault()
#collection.remove()
This might also help. Here's my task.js.coffee view:
class Backbonetodo.Views.Task extends Backbone.View
template: JST['tasks/task']
tagName: 'li'
render: ->
$(#el).html(#template(task: #model))
this
markComplete: (event) ->
# You need to get somehow specific task id
# Example: id = $(event.currentTarget).data('id'), if you have the id in the DOM
#collection.get(id).set(complete:true)
# You have here a parameter task which is actually event
clear: (task) ->
event.preventDefault() # This is task :p
# Same as before: get your task or task id
# Example:
# taskId = $(event.currentTarget).data('id')
# task = #collection.get(taskId)
#collection.remove(task)
My examples depends on your template.
Suggestion: try to use #$ instead of $, to scope elements to your view's element, by the way I used $
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
};
}]);
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()