I'm starting to make the move over to requireJS for a project I'm building. I'm currently using jasminerice, rails 3.2 and the require-rails gem.
I've tried to implement http://ryantownsend.co.uk/post/31662285280/jasminerice-and-requirejs-rails-fix with little success, the specs don't run at all.
I am starting to think it maybe I might be better to use requirejs on it's own or maybe the jasmine gem?
I'm not sold on either jasminerice or require-rails gems, so does anyone have any advice on the best tools, and any tips on how to get it up and running/good tutorials?
Ok as I didn't get any response I managed to find a slightly hacky way of making it work.
If you create a file in your view folder jasminerice/spec/index.html.erb (or haml) and copy the html from the jasminerice gem. Replace the spec.js call with:
%script{"data-main"=>"/assets/#{#specenv}", src:"/assets/require.js"}
Then write your spec file like require template like so:
require.config {
paths:{
'jquery':'/assets/jquery'
'underscore': '/assets/underscore-min'
'sinon':'sinon-1.6.0'
'jasmine-sinon':'jasmine-sinon'
'my_js':'my_js'
'my_spec':'my_spec'
}
}
require ['sinon', 'jasmine-sinon', 'jquery', 'underscore', 'my_js', 'my_spec'], () ->
jasmine.getEnv().execute()
This will prevent jasminerice triggering the tests
jasmine.rice.autoExecute = false
Set up your tests with a beforeFilter similar to this(taken from http://kilon.org/blog/2012/08/testing-backbone-requirejs-applications-with-jasmine/)
describe "MySpec", ->
beforeEach ->
flag = false
#thing = ""
that = #
require ['myjs'], (Myjs) ->
flag = true
that.thing = new Myjs()
waitsFor ->
flag
it 'It should exsist', ->
expect(#thing).toBeDefined()
Hope that helps anyone with a similar issue and if anyone has a better solution please post! :)
I have the same setup, here's what I did (starting from the blog post mentioned in the original question):
1. Create a helper to load all spec files
In a file lib/jasminerice/spec_helper.rb, put the following code:
require "requirejs-rails"
module Jasminerice
module SpecHelper
include RequirejsHelper
def spec_files
Rails.application.assets.each_logical_path.select { |lp| lp =~ %r{^spec/.*\.js$} }
end
end
end
This will create a helper method spec_files which you can call in the Jasminerice runner view to automatically get all your specs, so you don't need to update the list of specs every time you add a new one.
2. Override default Jasminerice index view
Create a view named app/views/jasminerice/spec/index.html.erb with the following:
<!doctype html>
<head>
<title>Jasmine Spec Runner</title>
<%= stylesheet_link_tag "jasmine", "spec" %>
<%= requirejs_include_tag 'application' %>
<%= javascript_include_tag "jasminerice", "spec", :debug => true %>
<script>
jasmine.rice.autoExecute = false;
require([<%= spec_files.map { |f| "'#{f.sub(/\.js$/,'')}'" }.join(',').html_safe %>],
function() { jasmine.getEnv().execute() },
function(err) {
var failedId = err.requireModules && err.requireModules[0];
requirejs.undef(failedId);
define(failedId, function() { return function() { console.debug(failedId + ': ' + err); null }; });
require([ failedId ], function() {} );
});
</script>
<%= csrf_meta_tags %>
</head>
<body>
</body>
</html>
This will require all the specs before running Jasmine (with jasmine.getEnv().execute()). I have an ugly hack in there to take the array of spec paths and generate an array of module names in quotes to pass to require.
I've also included an error callback in case there's a problem loading a module -- if you don't do this, your specs will hang when a module load fails. That's especially a problem when you're running them on the command line through guard-jasmine, which is what I do.
Unfortunately I haven't found a very good way to handle such errors -- here I write some info to console.debug and then required the failed module, returning an anonymous function in its place. This allows the specs to run but produces unpredictable results (which is better than no results). I've been struggling to find a better way to deal with this situation, suggestions would be much appreciated.
3. Write some specs
My Jasmine specs take the form:
define (require) ->
MyModule = require 'my-module'
# any other dependencies needed to test
describe 'MyModule', ->
it 'exists', ->
expect(MyModule).toBeDefined()
etc. Note that all my testing dependencies (jasmine, sinon, jasmine-sinon, etc.) I load outside of require, in spec.js.coffee:
#=require sinon
#=require jasmine-sinon
#=require_tree ./helpers/
I put any other helper functions I need in the helpers directory.
4. Bonus
One other tip: if you have problems because your browser won't reload modules even when they change, I use a trick of adding a dummy argument with a timestamp so that the browser will always see a new file and correctly load it.
I created this function in ApplicationController which I load in a before filter:
before_filter :set_requirejs_config
def set_requirejs_config
opts = { :urlArgs => "bust=#{Time.now.to_i}" }) if Rails.env == "development"
Requirejs::Rails::Engine.config.requirejs.run_config.merge!(opts)
end
This adds a query param bust=... to the end of each module name if we're in development mode, so that we always reload modules and get the most up-to-date version. Somewhere there's a post on SO explaining how to do this in RequireJS, but to get it to work with requirejs-rails you have to put it into ApplicationController (and not config/requirejs.yml) so that it is loaded every time you load the page.
Hope that might provide some hints to anyone else using this configuration!
Related
So i'm working in ruby on rails, and I've using it with AngularJS 1 for the first time, and therefore am setting up the unit testing for angularjs inside rails for the first time. I can test rails with rspec no problem, but i've been struggling to test angularjs, currently I'm working with teaspoon-jasmine and angular-mocks to test a simple angular controller, but I run into several problems. The first file below is my test file, I currently have it using angular.mock.module/inject and the error i'm getting is "Failed to instantiate module angularrApp due to..." it's basically saying I haven't created or loaded angularApp anywhere, I though mock was supposed to create a test module? If I the module to "angular.module("name", [])" like I would normally create an angular app, I run into more problems such as the mainController hasn't been created. I'm just overall having issues getting this to work properly, any suggestions? I'm trying to get just the simple first expect to work before I move on to the actual tests. And I am requiring angular and angular-mocks in the spec_helper.js file, all the files below. Would it help to require the file that creates the angular app in my spec_helper.js? I've tried but can't get it to work since one is in the spec directory, while my angular app is in app/assets. Also if it helps, here is the link to the tutorial I based this code off of http://angular-rails.com/find_and_browse.html
mainController_spec.js (test file)
'use strict';
describe("mainController", function() {
var scope,
ctrl,
location,
resource;
var setupController = function() {
angular.mock.inject( function($rootScope, $resource, $controller) {
scope = $rootScope.$new();
resource = $resource;
ctrl = $controller('mainController', {$scope: scope, $resource: resource });
})
}
beforeEach(function(){
angular.mock.module("angularApp");
setupController();
})
it('should work', function(){
expect(true).toBe(true);
// expect(scope.games).toBe([])
})
});
mainController.js
'use strict';
myApp.controller('mainController', [ 'mainFactory', '$scope', '$resource', (factory, scope, resource) => {
scope.games = [];
factory.populateTable((data) => {
scope.games = data;
})
}]);
angular-app.js
'use strict';
const myApp = angular.module('angularApp', ['ngRoute', 'ngResource']);
// routes in another file
For those that are interested, the issue I was having was that either teaspoon-jasmine, phantomjs or both don't recognize javascript es6 syntax, if there is any es6 in a file that would normally be required, the entire file and it's contents are ignored without any error messages. Once I reverted my angularApp and controller code to ES5, my problem was solved.
So this seems like a weird error, i've just started using teaspoon and im trying to get it set up on a rails project.
I have a very simple function im trying it out on:
export function add(value,value2) {
return value+value2;
}
Simple right?
and im including it in my spec file as such:
//= require config/add
describe("add", function() {
var num;
it("add", function() {
num = add(2,3);
expect(num).toEqual(5);
});
});
Why do I get a "Can't find variable "Add" in http://127.0.0.1:8000/assets/test_spec.self.js?body=1 (line 5)"
Am I missing something? I know this project does have ES6 modules which I know need to be compiled and such, but....this doesn't use any ES6 syntax I believe.
This ended up being a problem with the function export. Which was resolved by using:
import {add} from 'subfolder/add';
Im guessing since it was using ES6 standards...
For my project, I am using Ruby on Rails and Angular and I have a haml file present in /project_name/app/assets/javascript folder. I want to call a ruby class from the haml file but unfortunately I am not able to do that.
.small-12.columns
.booking-time
%span.bold
- if ABC::D.data(:hide_hours_field) #ruby code
{{ item | timeformat }}
- else
{{ item | differentTimeFormat }}
Whenever I start the server, it's is showing it can't access the ruby class. I can easily access the ruby class in other haml files but not the one present in javascript folder. Can anyone help me here?
Disclaimer: I'm bad at Angular (and didn't even touch version 2). What follows is not a best practice or anything.
So, you need to configure your angular view with some knowledge from ruby side. You can't conditionally render it and you can't call ruby from angular controllers (obviously). I suggest smuggling data via window object.
In an appropriate view in your application, put this JS snippet.
<script type='text/javascript'>
window.jsEnv = {
hide_hours_field: <%= ABC::D.data[:hide_hours_field] %>
}
</script>
Then you can reference that via $window object in angular
Controller
function MyController($window) {
this.hideHours = function() {
return !!$window.jsEnv.hide_hours_field;
}
}
MyController.$inject = ['$window'];
angular.module('myApp').controller('MyController', MyController);
View
.small-12.columns(ng-controller='MyController as vm')
.booking-time
%span.bold(ng-if='vm.hideHours()')
{{ item | timeformat }}
%span.bold(ng-unless='vm.hideHours()')
{{ item | differentTimeFormat }}
I suggest you use angular constant instead of window then you can use it as service:
// index.html
<script type="text/javascript">
angular.module('myApp').constant('CURRENT_USER', #{current_user.to_json})
</script>
// controller
function ApiConsoleController($scope, CURRENT_USER) {
console.debug(CURRENT_USER)
}
Also, you can try to use angular-rails-templates
// application.rb
config.angular_templates.ignore_prefix = %w(
angular/components/
angular/shared/
angular/directives/
angular/validators/
)
I have Rails 4 Application with AngularJS using these gems:
gem 'angularjs-rails'
gem 'angular-rails-templates'
gem 'asset_sync'
It works great with a template like this:
<img ng-controller='LikePostController'
ng-dblclick='like(post);'
ng-src='{{post.photo.standard}}'
class='lazy post_photo pt_animate_heart'
id='post_{{post.id}}_image'
/>
The Image render correctly. However in my other js
petto.directive('ptAnimateHeart', ['Helper', function(Helper){
linkFunc = function(scope, element, attributes) {
$heartIcon = $("#heart_icon");
if($heartIcon.length == 0) {
$heartIcon = $("<img id='heart_icon' src='/assets/feed.icon.heart.png' alt='Like' /> ");
$(document.body).append($heartIcon);
}
element.on('dblclick', function(event){
$animateObj = $(this);
Helper.animateHeart($animateObj);
});
}
return {
restrict: 'C',
link: linkFunc
}
}])
I got 'assets/feed.icon.heart.png' was not found error from the browser console. I have feed.icon.heart.png located under app/assets/feed.icon.heart.png.
ps: Forget to mention I use assets sync gem to host assets in amazon s3. the image worked well in development but not in production.
Hardcoded asset links only work in development because in production the assets get precompiled. Which means, amongst other things, the filename changes from:
my_image.png
into something like this (it adds and unique md5-hash):
"my_image-231a680f23887d9dd70710ea5efd3c62.png"
Try this:
Change the javascript file extension to: yourjsfile.js.erb
And the link to:
$heartIcon = $("<img id='heart_icon' src='<%= image-url("feed.icon.heart.png") %>' alt='Like' /> ");
For better understanding The Asset Pipeline — Ruby on Rails Guides
You can define the following method somewhere in your helpers, e.g. in app/helpers/application_helper.rb:
def list_image_assets(dir_name)
path = File.expand_path("../../../app/assets/images/#{dir_name}", __FILE__)
full_paths = Dir.glob "#{path}/**.*"
assets_map = {}
full_paths.each do |p|
original_name = File.basename p
asset_path = asset_path p[p.index("#{dir_name}")..-1]
assets_map[original_name] = asset_path
end
assets_map.to_json
end
One can modify the method to work with any assets you wish, not just the ones located in subdirs of app/assets/images as in this example. The method will return a map with all the original asset names as keys and their 'compiled' names as values.
The map returned can be passed to any angular controller via ng-init (not generally recommended, but appropriate in this case):
<div ng-controller="NoController" ng-init="assets='<%=list_image_assets "images_dir_name"%>'"></div>
To make the assets really usable in angular, define a new $scope valiable in the controller:
$scope.$watch('assets', function(value) {
if (value) {
$scope.assets = JSON.parse(value);
}
});
Having this in the $scope, it's possible to use assets names as usual, in e.g. ng-src directives, and this won't brake after the precompile process.
<img ng-src={{::assets['my_image.png']}}/>
Just do the following:
app.run(function($rootScope,$location){
$rootScope.auth_url = "http://localhost:3000"
$rootScope.image_url = $rootScope.auth_url + "/uploads/user/image/"
});
In controller inject dependency for $rootScope
and in views
<img ng-src="{{user.image.url}}" width="100px" height="100px">
Note: It's working great in Rails API and it assumes that you've user object available so that it could specify the correct image in the /uploads/image/ directory
UPDATE: I have fixed this problem after lots of painstaking work on my own. I am happy to be a resource to anybody needing a hand with this. Here is a gist of my working setup.
I have tried every solution I could find Google and SO. Here are some different things I have tried:
page.execute_script %Q{$('#{selector}').val('#{value}').trigger('keydown')}
and
fill_in field, with: options[:with]
page.execute_script %Q{ $('##{field}').trigger('focus') }
page.execute_script %Q{ $('##{field}').trigger('keydown') }
This is what fails:
page.should have_selector('ul.ui-autocomplete li.ui-menu-item a')
But it's definitely there when I look at it in Firebug and test it in the browser.
Here are all of the details, including a restatement of those above. Remember, the autocomplete field works fine in the browser.
listing_integration_spec.rb
require "spec_helper"
describe "Listing Integration" do
let!(:user) { login_user }
it "lets a user add information listing", js: true do
listing = create(:listing, user: user)
click_link('Additional Information')
click_link('Create')
fill_autocomplete('listings_search', with: listing.item_id)
end
end
spec/support/feature_helper.rb
def fill_autocomplete(field, options = {})
fill_in field, with: options[:with]
page.execute_script %Q{ $('##{field}').trigger('focus') }
page.execute_script %Q{ $('##{field}').trigger('keydown') }
selector = %Q{ul.ui-autocomplete li.ui-menu-item a:contains('#{options[:with]}')}
page.should have_selector('ul.ui-autocomplete li.ui-menu-item a')
page.execute_script %Q{ $("##{selector}").trigger('mouseenter').click() }
end
ERB from view template
<%= simple_fields_for :listings do |f| %>
<%= f.input :search, label: "Search by Listing", required: true %>
<% end %>
and the Coffeescript:
$("#listings_search").autocomplete
source: (request, response) ->
options =
term: request.term
$.get "/search_listings", options, (data) ->
if data.length == 0
alert "No listings found."
response data
minLength: 2
select: (event, ui) ->
add_listing_hash =
type: "GET"
url: "/add_listing"
data: { id: ui.item.id }
success: () ->
$.ajax(add_listing_hash)
JS drivers are generally meh, they're slow and not single one of them covers 100% of function, and they're often quirky and hard to debug, but I'm sure you've got that figured out by now.
I've got similar piece of code working on rails 3.2, minitest and poltergeist 1.3.0 (an ajaxed dropdown) but it kind of breaks periodically for no good reason (one might say it has a poltergeist? I have already resorted switching that test between selenium and poltergeist a couple times so far), not sure why autocompleter wouldn't work for you but it feels like a bug,
submit issue to https://github.com/jonleighton/poltergeist (you already have? https://github.com/jonleighton/poltergeist/issues/439), try changing to selenium or webkit, see if it works, you can use a different driver in this one test if it gets you out of the woods (it beats losing days of work over a widget that works).
I've found several solutions online, none of which work with current versions of Poltergeist, Capybara, and Autocomplete. But I learned enough from them to make a working helper method, with no sleep calls.
def fill_autocomplete(css_id, page, options = {})
find("#{css_id}").native.send_keys options[:with]
page.execute_script %{ $('#{css_id}').trigger('focus') }
page.execute_script %{ $('#{css_id}').trigger('keydown') }
selector = %{ul.ui-autocomplete li.ui-menu-item:contains("#{options[:select]}")}
expect(page).to have_selector('ul.ui-autocomplete li.ui-menu-item')
page.execute_script %{ $('#{selector}').trigger('mouseenter').click() }
end
Example usage:
fill_autocomplete(
'#contact_filter_company',
listing_page,
with: 'acm',
select: 'Acme'
)
I have a page argument because I'm using SitePrism - if you're not, you can strip it out.
I'm using this with:
jQuery UI Autocomplete 1.11.2
poltergeist 1.5.1
capybara 2.4.4
rspec 3.1.0
I was able to test my autocompleting text field with Poltergeist without much trouble. The main thing to know about is Poltergeist's .native.send_keys method.
Hacking together a summary out of the Cucumber steps where these lines of code actually live in my project:
find('#username').native.send_keys "the" # this field autocompletes usernames
wait_until { all('a', text: "the_username").any? }
find('a', text: "the_username").click
Then I submit the form and assert the expected results on the following page in the usual way.
wait_until (a reimplementation of a method which was removed from Capybara 2) takes a block which returns true when we should stop waiting. It's faster than waiting for 5 seconds or whatever every time.
def wait_until(delay = 1)
seconds_waited = 0
while ! yield && seconds_waited < Capybara.default_wait_time
sleep delay
seconds_waited += 1
end
raise "Waited for #{Capybara.default_wait_time} seconds but condition did not become true" unless yield
end
I think perhaps you need a mixture of triggering KEYDOWN, but also setting the keycode to DOWN.
e.g.
var keyEvent = $.Event("keydown");
keyEvent.keyCode = $.ui.keyCode.DOWN;
$("#autocomplete").val("j");
$("#autocomplete").trigger(keyEvent);
Here is a working jsfiddle example showing an item selected by the autocomplete: http://jsfiddle.net/alexkey/74BST/ I'm not sure why you need to trigger keydown twice, but that's a problem to solve separately (if a problem at all).
However I'm not familiar with the unit testing framework you are using, but I hope the above helps.
Credit goes to JQuery AutoComplete, manually select first searched item and bind click
I'm using the example code form: http://api.jqueryui.com/autocomplete/#entry-examples
The autocomplete unit tests that the jquery-ui team uses may come in useful for inspiration: https://github.com/jquery/jquery-ui/tree/master/tests/unit/autocomplete
Also a reference to the keycode: http://api.jqueryui.com/jQuery.ui.keyCode/
I had this problem and no proposed solution could solve it. My tests always failed when trying to find the ul.ui-autocomplete element. I finally noticed, that jQuery autocomplete appends the ul to the end of the html page and NOT to the input field in question. In my spec, I follow the practice of targeting my forms explicitly by within my_form do and doing all the fill_instuff inside this block:
within my_form do
fill_autocomplete …
end
Of course this could never find the ul attached OUTSIDE this form element. My solution was simple: Use jQuery autocomplete's attribute appendTo: '#id_of_input_field' when initializing autocomplete. Now it can find my uland everything works fine.