In my angularjs 1.3 app, i disabled debug info to increase performance :
$compileProvider.debugInfoEnabled(false);
But when i launch my Jasmine tests with Karma, i got error with isolateScope :
var isoScope = element.isolateScope();
I know that it is completely normal,
but i'm looking for a way to reactivate the debug just for testing.
Can i do it programmatically ?
Can i define that in the karma-unit.conf.js ?
My approach to this was to create a set of config files, the load specific files via grunt or gulp, depending on the task or environment variables.
example config:
// configFileLocal.js
angular.module('myapp.config')
.constant('apiUrl', 'https://myapi.com/api')
.constant('debugInfoState', true);
an excerpt from Gruntfile:
copy: {
test: {
src: (function () {
var filename;
if (process.env.REMOTE_TESTS) {
filename = './config-files/configFileTestsRemote.js';
} else {
filename = './config-files/configFileTestsLocal.js';
}
return filename;
})(),
dest: '<%= yeoman.app %>/scripts/root/config.js',
},
dev: {
src: './config-files/configFileLocal.js',
dest: '<%= yeoman.app %>/scripts/root/config.js',
},
...
then of course you need to load it in your app like this:
angular.module('myapp')
.config(function ($compileProvider, debugInfoState) {
$compileProvider.debugInfoEnabled(debugInfoState);
});
so /app/scripts/root/config.js is overwritten every time.
in index.html, the only file that is loaded is /app/scripts/root/config.js
this task should be run before any concatenation tasks.
Answer from wiherek is good but it isn't necessary to use your grun/gulp to manage this. Just transform snippet from Angular documentation to something like this
beforeEach(function() {
module('myApp', function (_$compileProvider_) {
_$compileProvider_.debugInfoEnabled(true);
});
});
Here's how I do it:
$compileProvider.debugInfoEnabled(!!window.__karma__);
To debug an application after disabling debugInfo, you should open your console and run this code directly from your console:
angular.reloadWithDebugInfo();
This will reload your page with all debugging info.
Related
I have a large AngularJS/Express app where I'd like to begin sharing code between client and server, mainly small utility libraries, e.g:
// Name: utilities.js
module.exports.testUtilities = function () {
console.log('testUtilities: Hello world!');
};
I'm now trying to set up Browserify using grunt-browserify:
// Name: Gruntfile.js
browserify: {
client: {
src: ['crossplatform/**/*.js'],
dest: 'app/scripts/crossplatformBrowserify.js'
}
},
I successfully build a crossplatformBundle.js which I include in my index.html.
But I fail at accessing my Browserified code from the AngularJS client:
// Name: MyAngularController.js
var utilities = require('./utilities');
utilities.testUtilities();
Error message: require is undefined.
I see many Browserify questions where the answer is to bundle up ALL the client-side scripts, but I'd rather avoid that if I can, since I want separate JS files in development mode.
Thankful for any tips I can get!
The solution was to define a “standalone” bundle for Browserify:
browserify: {
client: {
src: ['crossplatform/**/*.js'],
dest: 'app/scripts/crossplatform.js',
options: {
browserifyOptions: {
standalone: 'crossplatform'
},
}
}
},
...which allows me to call the method with window.crossplatform.testUtilities() in the browser.
Update: see this thread for more examples of how to bundle multiple libraries/modules.
Can someone please suggest how I can set this up. Here is the scenerio.
I am developing an angular app with an Azure Mobile Services back end.
In angular I have resources that point to the Mobile Service API path. I have a config factory that returns the base service path. I want the dev/staging slot to point to the -dev api and the production slot to point to the production API path.
I can create two config factories, but I'm not sure how to tell it to use the dev one in the staging slot and the production one in the production slot. For a .net app with server code I would use the config stuff, or maybe environmental variables, but this is all client side code.
I thought to just have two web sites, once deployed from a dev branch and one deployed from the master and have a different config... but once I merge dev to master then the config change merges too.
I'm not sure if it matters, but I am deploying from Visual Studio Online as part of the build.
How can I accomplish this?
I was able to solve this very same problem. I have development, testing and production environments and they each need to connect to different API end points. I was able to accomplish this using a grunt plugin called grunt-ng-constant
Basically once you install the plugin, modify your Gruntfile and inside the grunt.initConfig add something like this:
ngconstant: {
// Options for all targets
options: {
space: ' ',
wrap: '"use strict";\n\n {%= __ngModule %}',
name: 'config',
},
// Environment targets
development: {
options: {
dest: '<%= yeoman.app %>/scripts/config.js'
},
constants: {
ENV: {
name: 'development',
apiEndpoint: 'http://your-development.api.endpoint:3000'
}
}
},
production: {
options: {
dest: '<%= yeoman.dist %>/scripts/config.js'
},
constants: {
ENV: {
name: 'production',
apiEndpoint: 'http://api.livesite.com'
}
}
}
},
Register the Grunt task like so:
grunt.registerTask('serve', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'connect:dist:keepalive']);
}
grunt.task.run([
'clean:server',
'ngconstant:development', // ADD THIS
'bower-install',
'concurrent:server',
'autoprefixer',
'connect:livereload',
'watch'
]);
});
Now every time you run grunt serve it will generate a config.js file that holds your development constants. You can configure different tasks such as grunt testing or grunt production to generate the testing or production constants instead.
Finally you add the config.js to your index.html like so:
<script src="/scripts/config.js" />
And register the config module in your app:
var app = angular.module('myApp', [ 'config' ]);
In your controller you can get your "environment" variables like this:
angular.module('myApp')
.controller('MainCtrl', function ($scope, $http, ENV) { // ENV is injected
$scope.login = function() {
$http.post(
ENV.apiEndPoint, // Our environmental var :)
$scope.yourData
).success(function() {
console.log('Cows');
});
};
});
With this approach you can easily automate the entire deployment pipeline. You can have a CI server push your changes to the appropriate servers and build the correct version of your app.
Here's a very helpful resource for you to read and from which I took the code samples: http://mindthecode.com/how-to-use-environment-variables-in-your-angular-application
Hope this helps you!
If you can key off the client hostname of the production versus the development servers ( or not the production server), then you can inject an $http interceptor to rewrite request URLs. In my app.js, I have something like:
var DEV_SRVR = 'http://my-machine.example.com:8000';
var PRODUCTION_FRONT_END_CLIENT_SUFFIX = 'DEPLOYMENT';
app.factory('REST_Interceptor',[
function() {
var request = function(config) {
if (RegExp(PRODUCTION_FRONT_END_CLIENT_SUFFIX,'i').test(window.location.host)) {
return config;
}
var rest_request_regex = new RegExp('^.*?/rest/(.*)$');
config.url = config.url.replace(rest_request_regex, DEV_SRVR+'/$1');
return config;
}
return {
request: request
}
}])
app.config([
'$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('REST_Interceptor');
}])
Thus, requests from AngularJS like:
$http.get('/rest/foo/')
Go to http://my-machine.example.com:8000/foo/ if the client is not the production client interface, otherwise it goes to /rest/foo/ of the production host.
You might be interested in looking how David Ebbo and I setup this scenario. In David's talk at Build he discusses how to configure two websites to communication with each other. The source code for his talk is available on GitHub called ToDoApp. If you prefer to read over watch, there was also an article written about David's talk which is available in the Azure Documentation called Deploy a Complex Application predictably in Azure
You can set App Settings for you site and slot from the Azure Portal, and set separate settings for the dev and production slots.
See these docs for more details: https://azure.microsoft.com/en-us/documentation/articles/web-sites-staged-publishing/#configuration-for-deployment-slots
In my latest project I'm making some API requests.
As far as I need to setup different endpoints (local development, remote production), I set the value of the used endpoint when defining the app. Everything works just fine.
var app = angular.module('app', ['lelylan.dashboards.device', 'ngMockE2E'])
app.value('client.config', { endpoint: 'http://localhost:8000' });
The only problem is that I want to set the endpoint http://localhost:8000 from a service that defines some constants [1]. I tried using the run and config block, but the value is not set. I was doing something like this, without any result.
var app = angular.module('app', ['lelylan.dashboards.device', 'ngMockE2E'])
.config(function(ENV) {
app.value('lelylan.client.config', { endpoint: ENV.endpoint });
})
This looks quite terrible to me, but I've no idea on how to solve this issue.
Thanks a lot.
[1] grunt-ng-constant
Please see here : http://jsbin.com/foqar/1/
var app = angular.module('app', []);
var client = {
endpoint : 'http://localhost:8000'
}
app.value('config', client);
app.controller('firstCtrl', function($scope, config){
$scope.endpoint = config.endpoint;
});
or: http://jsbin.com/foqar/2/edit
var app = angular.module('app', []);
app.value('config',{client :{endpoint : 'http://localhost:8000'}});
app.controller('firstCtrl', function($scope, config){
$scope.endpoint = config.client.endpoint;
});
It looks like the grunt-ng-constant creates a new module file to define the constants. You do not have to bother about it on your app JS file, other than declare the module containing those constants as a dependency.
The below is taken as is on the example config of the documentation of grunt-ng-constant
grunt.initConfig({
ngconstant: {
options: {
name: 'config',
dest: 'config.js',
constants: {
title: 'grunt-ng-constant',
debug: true
}
},
dev: {
constants: {
title: 'grunt-ng-constant-beta'
}
},
prod: {
constants: {
debug: false
}
},
}
});
In the options section, you specify the name of the module, file for the module to be written to and a general set of constants. It works like this,
options: {
name: 'config',
dest: 'config.js',
constants: {
title: 'grunt-ng-constant',
debug: true
}
}
The above code will become,
/*File: config.js*/
angular.module('config', [])
.constant('title', 'grunt-ng-constant')
.constant('debug', true);
To change the constants based on your scenario (development / production) you would be using different task sets. Here is where the dev and prod section comes into play
Considering you are using ng-boilerplate, in the gruntfile.js you have tasks build and compile. The build task is used during development, and the compile gets your app ready to be pushed to production.
In the build task you would add ngconstant:dev and in the compile task you would add ngconstant:prod.
grunt.registerTask('build', ['clean', 'html2js', 'otherTasksComeHere', 'ngconstant:dev']);
grunt.registerTask('compile', ['clean', 'html2js', 'otherTasksComeHere', 'ngconstant:prod']);
For your scenario the code would be as below:
/*gruntfile.js*/
grunt.initConfig({
ngconstant: {
options: {
name: 'lelylan.client.config',
dest: 'config.js',
values: {
endpoint : 'http://localhost:8000'
}
},
dev: {
debug: true
},
prod: {
endpoint: 'http://www.production-server.com/',
debug: false
}
},
});
grunt.registerTask('build', ["ngconstant:dev"]);
grunt.registerTask('compile', ["ngconstant:prod"]);
grunt.registerTask.('default', ["build", "compile"]);
/*app.js*/
var app = angular.module('app', ['lelylan.dashboards.device', 'leylan.client.config', 'ngMockE2E']);
app.controller("appCtrl", ["$scope", "$http", "endpoint",
function($scope, $http, endpoint) {
$scope.submit = function(formData) {
$http.post(endpoint+'/processform.php', formData);
}
}]);
Now it all depends on whether you run grunt build or grunt compile. The default task is run when you use the grunt command.
The solution was to use providers. As the docs states:
during application bootstrap, before Angular goes off creating all
services, it configures and instantiates all providers. We call this
the configuration phase of the application life-cycle. During this
phase services aren't accessible because they haven't been created
yet.
For this reason I created a provider to set the needed configurations.
<script>
angular.module('app', ['lelylan.dashboards.device'])
angular.module('app').config(function(lelylanClientConfigProvider, ENV) {
lelylanClientConfigProvider.configure({ endpoint: ENV.endpoint });
});
</script>
In this way all services will then be able to use the new endpoint. Using .value this was not possible.
Thanks everyone for your help.
After spending the last day trying to make this work, I've found that I've circled back around to the same error I was having at the beginning:
Error: Unexpected request: GET test-directive.html
I'm using Karma and Jasmine to test directives in Angular. I've looked through similar questions on StackOverflow, but find that everything that has been tried in the other examples is to no avail.
Code structure
Test-App
-src
--bower
--lib
--js
--modules
---testDir
----test.js
----test-directive.html
----test
-----test.spec.js
-test
--config
---karma.conf.js
--e2e
Karma Config
'use strict';
module.exports = function(config){
config.set({
basePath: '../../',
frameworks: ['jasmine'],
files: [
// Angular
'src/bower/angular/angular.js',
// Mocks
'src/bower/angular-mocks/angular-mocks.js',
// Libraries
'src/lib/**/*.js',
// App
'src/js/*.js',
'src/modules/*/*.js',
// Tests
'src/modules/**/test/*spec.js',
// Templates
'src/modules/**/*.html'
],
autoWatch: false,
singleRun: true,
reporters: ['progress'],
browsers: ['PhantomJS'],
preprocessors: {
'src/modules/**/*.html': 'ng-html2js'
},
ngHtml2JsPreprocessor: {
moduleName: 'dir-templates'
},
plugins: [
'karma-jasmine',
'karma-ng-html2js-preprocessor',
'karma-phantomjs-launcher',
'karma-chrome-launcher',
'karma-junit-reporter'
]
});
};
test.js
'use strict';
angular.module('modules.test', []).
directive('testDirective', [function() {
return {
restrict: 'E',
templateUrl: 'test-directive.html',
link: function($scope, $elem, $attrs) {
$scope.someFn = function() {
angular.noop();
};
}
};
}]);
test-direct.html
<span>Hello World</span>
test.spec.js
'use strict';
describe('test module', function() {
beforeEach(module('modules.test'));
/* -- DIRECTIVES------------------ */
describe('directives', function() {
var $compile, $scope, elm;
beforeEach(module('dir-templates');
beforeEach(inject(function($compile, $rootScope) {
$scope = $rootScope.$new();
elm = angular.element('<test-directive></test-directive>');
$compile(elm)($scope);
$scope.$digest();
}));
it('should have one span tag', function(){
//Jasmine test here to check for one span tag.
});
});
});
Have shortened a couple of files to stick to just where the issue is. In calling beforeEach(module('dir-templates')), it should be loading all of the matched .html files into the $templateCache and preventing the GET request that is throwing the error.
Any help would be appreciated as it's really been driving me nuts. Please comment if you have any additional questions.
So, a painstaking headache for what seems to be a two line fix. After opening Karma in Chrome (instead of PhantomJS) and looking at the source files, I noticed that when ng-html2js attaches the directive to the $templateCache it uses the entire path, not the one provided in the directive definition.
In short, 'src/modules/test/test-directive.html.js' !== 'test-directive.html.js'.
To achieve this, modify the karma.conf.js file ngHtml2JsProcessor to read like:
ngHtml2JsPreprocessor: {
stripPrefix: 'src/',
moduleName: 'dir-templates'
},
And the directive declaration's templateUrl to look like:
templateUrl: 'modules/test/test-directive.html'
To add to the comments about making sure the template name being matched on matches the prefix (has no leading slash, etc.), one other thing to check for is case.
The template cache key is case sensitive, so make sure that your directives are referencing the html files using proper casing. The template cache keys that the ngHtml2JsPreprocessor generates use the casing of the actual file name and directory names on the file system.
So if your file is named Test-Directive.html or your folder is named "Modules" but your directive is referencing "modules/test-directive.html", it won't resolve from the cache.
Case sensitivity isn't an issue with real (non-test) usage of your directive's templateurl (the GET request obviously is case insensitive and the template cache key will be generated based on whatever the initial GET request was, ie. whatever was specified in your directive).
I'm using Yeoman and generator-angular to manage AngularJS apps, but I'm having trouble with automated testing.
Running grunt test will run unit tests once. I can get E2E tests to run after unit tests by altering the karma config block in Gruntfile.js, adding e2e:
karma: {
//...
e2e: {
configFile: 'karma-e2e.conf.js',
singleRun: true
}
},
Great: now when I type grunt test all tests are run. But they're only run one time, and there's a big overhead (starting compass, running the server, launching the Chrome processes, etc.). Instead, the server and Chrome processes should remain running and, when I save a test, tests should be re-run.
I can achieve this by modifying both karma.conf.js and karma-e2e.conf.js and setting singleRun = true, then running karma start in one terminal pane, and karma start karma-e2e.conf.js in another. Provided none of the ports in the karma configs conflict (which they do by default), this works. Now I'm bypassing Grunt and just doing my own thing (which seems a little silly, as Grunt is supposed to make things easier).
Anyway, after a few more changes (fixes?) — not detailed for brevity — this works but doesn't cut it: I now have to run two different commands and keep an eye on two different terminal panes. Surely there's a better way.
How can I run a single command to watch my test files and re-run tests appropriately?
Bonus question: why on Earth is this functionality not provided as is? Is it just a question of the developer(s) of generator-angular not having enough time to implement this stuff? I ask because I'm only just getting into Angular/Yeoman/Karma (as you probably noticed), and feel that automated testing of both E2E and unit tests are crucial to workflow.
As I mentioned in a comment to your question - PhantomJS saves a lot of hassle. That aside, I believe you can handle everything from within your Gruntfile and just continue to run grunt test to start the whole thing.
grunt-karma allows full customization of your karma options with some handy add-ons.
From the docs:
....
You can override any of the config file's settings directly:
karma: {
unit: {
configFile: 'karma.conf.js',
runnerPort: 9999,
singleRun: true,
browsers: ['PhantomJS']
}
}
Sharing Configs
If you have multiple targets, it may be helpful to share common
configuration settings between them. Grunt-karma supports this by
using the options property:
karma: {
options: {
configFile: 'karma.conf.js',
runnerPort: 9999,
browsers: ['Chrome', 'Firefox']
},
continuous: {
singleRun: true
browsers: ['PhantomJS']
},
dev: {
reporters: 'dots'
}
}
Additionally you may want to snoop around in Yeoman's generator-angular Gruntfile code to see what else may be available or at least mockable.
You can try this to run only e2e tests
grunt karma:e2e
Within the karma.conf.js file (approx line:38) find autoWatch = false; and change it to true.
Now if you run grunt karma:unit you will find that it leaves the test server running and any changes to project files immediately run the tests again.
//
// test/midway/appSpec.js
//
describe("Midway: Testing Modules", function() {
describe("App Module:", function() {
var module;
before(function() {
module = angular.module("App");
});
it("should be registered", function() {
expect(module).not.to.equal(null);
});
describe("Dependencies:", function() {
var deps;
var hasModule = function(m) {
return deps.indexOf(m) >= 0;
};
before(function() {
deps = module.value('appName').requires;
});
//you can also test the module's dependencies
it("should have App.Controllers as a dependency", function() {
expect(hasModule('App.Controllers')).to.equal(true);
});
it("should have App.Directives as a dependency", function() {
expect(hasModule('App.Directives')).to.equal(true);
});
it("should have App.Filters as a dependency", function() {
expect(hasModule('App.Filters')).to.equal(true);
});
it("should have App.Routes as a dependency", function() {
expect(hasModule('App.Routes')).to.equal(true);
});
it("should have App.Services as a dependency", function() {
expect(hasModule('App.Services')).to.equal(true);
});
});
});
});