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.
Related
I'm looking for a little guidance and suggestions here. My attempts and theories will be at the bottom.
I have a NextJS project from which I want to export the top level component (essentially the entry file) so that I can use it as a preview in my dashboard.
The nextjs project is very simple. For the sake of simplicity, let's imagine that all it renders is a colored <h1>Hello world</h1>. Then in my dashboard, I want to render a cellphone with my NextJS component embedded and then from the dashboard change the color of the text, as a way to preview how it would look like. I hope this makes sense.
I'm lost at how I could export this component from NextJS and import it into my dashboard. The dashboard is rendered in Ruby on Rails. It would be simple enough to just import the repo from git and access the file directly form node_modules, but I'm looking for a solution that doesn't require installing npm on our Rails project.
Paths I have thought about:
1 - Install npm on Rails and just import the source code from NextJS repo and access the file and render with react (Simple, but we're looking for a non-npm solution)
2 - Bundle the component with webpack and load it directly into rails (does this even work?) - I exported the js and all it did was freeze everything :P Still trying this path for now
3 - Using an iframe and just accessing the page (then I can't pass any callbacks into the iframe to change the color directly from the dashboard)
4 - I cannot separate this component from NextJS to use as a library in both repos. The component we are exporting is the "ENTIRE" NextJS app jsx and it wouldn't make sense to separate in a different repo
Does anyone have a suggestion on how I could achieve this?
I think you could use an iframe with the nextjs app url. Then if you want to change the color, simply add the color in query parameter of the iframe and handle it on nextjs app.
Simple example
Rails view (erb)
<iframe src="#{#nextjs_url}?color=#{#color}" />
NextJS
# do something to get the query param of the page and and set to prop of the component
const YourComponent = ({color}) => {
return <h1 style={{color}}>Lorem</h1>;
}
While trying Hoang's solution, I decided to dive deeper into how to communicate with an iframe and the solution actually feels quite good.
You can set up listeners on either side and post messages in between the projects.
So in my dashboard:
function handleEvent(e) {
const data = JSON.parse(e.data)
if (data.type === "card_click") {
//if type is what we want from this event, handle it
}
}
// Setup a listener with a handler
// This will run every time a message is posted from my app
window.addEventListener("message", handleEvent, false)
const postMessage = (color) => {
const event = JSON.stringify({
type: "color_update",
color,
})
// Find the iframe and post a message to it
// This will be picked up by the listener on the other side
document.getElementById("my-iframe-id").contentWindow.postMessage(event, "*")
}
And on my app:
function handleEvent(e) {
const data = JSON.parse(e.data)
if (data.type === "color_update") {
// Do whatever is necessary with the data
}
}
// Setup listener
// This will fire with every message posted from my dashboard
window.addEventListener("message", handleEvent, false)
const handleCardClick = (cardIndex) => {
const event = JSON.stringify({
type: "card_click",
cardIndex,
})
// post message to parent, that will be picked up by listener
// on the other side
window.parent.postMessage(event, "*")
}
It feels pretty straight forward to communicate with an iframe with this solution.
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...
Overview
I am creating a yeoman generator that will generate a customized angular app. It will be generating custom angular controllers and services.
While I could simply take a current angular template, as generated by the angular generator, add my code, and then save this as a template in my generator, I prefer to have the generator call the "official" angular generator (previously installed by the user) as a subgenerator at runtime, so I don't have to synchronize my templates every time the angular generator template changes.
Problem
I have this working, but the only problem is when, from my generator, I add my code to the file generated by the subgenerator (e.g a controller), I get the message:
conflict app/scripts/controllers/mycontroller.js
? Overwrite app/scripts/controllers/mycontroller.js? overwrite
force app/scripts/controllers/mycontroller.js
Is there a way I can get the file generated by the subgenerator before it's written to disk, so I can edit it, without generating the overwrite prompt?
I think it's confusing to be prompted about overwriting a file that is being created for the first time.
Data
In reading the yeoman doc, it makes it sound like generators and subgenerators share the same "soft" in-memory filesystem:
"As asynchronous APIs are harder to use, Yeoman provide a synchronous file-system API where every file gets written to an in-memory file system and are only written to disk once when Yeoman is done running. This memory file system is shared between all composed generators."
But it appears that the subgenerator writes the files to disk and not the in-memory file system. Unfortunately, I do not have control over the subgenerator, as it's the standard angular yeoman generator.
Here is my generator (scroll down to portion marked "Relevant Portion"):
'use strict';
var yeoman = require('yeoman-generator');
var chalk = require('chalk');
var yosay = require('yosay');
module.exports = yeoman.generators.Base.extend({
prompting: function () {
var done = this.async();
// Have Yeoman greet the user.
this.log(yosay(
'Welcome to the epic ' + chalk.red('AngularVr') + ' generator!'
));
var prompts = [{
type: 'confirm',
name: 'someOption',
message: 'Would you like to enable this option?',
default: true
}];
this.prompt(prompts, function (props) {
this.props = props;
done();
}.bind(this));
},
writing: {
app: function () {
this.fs.copy(
this.templatePath('_vt_marker.json'),
this.destinationPath('vt_marker.json')
);
},
====================== Relevant Portion ===============
subgenerators: function () {
this.log("now executing subgenerators");
this.composeWith('angular:controller', {args: ['mycontroller']} );
this.log("now done with subgeneration");
},
subgenerators_read: function () {
// add a new line to generated file..this generates overwrite prompt
var fp = this.destinationPath('app/scripts/controllers/mycontroller.js');
var fc = this.fs.read(fp);
fc += '\nhello there\n';
this.fs.write(fp, fc);
},
});
This is my first attempt at writing a yeoman generator.
Many Thanks.
The issue is probably just that either your generator or the generator-angular is running on an old version of yeoman-generator.
Latest version at this time is 0.20.3 https://www.npmjs.com/package/yeoman-generator
I have an Angular app (running from an ASP.NET MVC project.)
In this app, I have a directive with the following link function:
link: function (scope, element, attrs, formCtrl) {
element.bind('click', function (event) {
formCtrl.$setPristine();
});
}
I wrote this test:
var $compile,
$rootScope,
form;
beforeEach(module('points'));
beforeEach(inject(function(_$compile_, _$rootScope_, testHelper){
$compile = _$compile_;
$rootScope = _$rootScope_;
testHelper.expectBaseRoute();
}));
it('Should set form to pristine when clicked', function () {
var scope = $rootScope;
form = $compile('<form name="testForm" unsaved-warning-clear><input name="test" ng-model="value"/></form>')(scope);
scope.value = "abc";
scope.$digest();
scope.testForm.test.$setViewValue('def');
expect(scope.testForm.$pristine).toBe(false);
$(form).trigger('click');
expect(scope.testForm.$pristine).toBe(true);
});
If I run that in the browser using Jasmine, the test passes.
If I run it with chutzpah inside the ide, it fails. It never fires the click event.
Is there something I'm missing?
For my case, it was an incident of stupid user error.
My Jasmine test runner file being run from the browser had jquery, then angular referenced.
My chutzpah.json file had it the inverse order.
I moved the jquery reference up and it started working.
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!