passing grunt parameters from one task to another - asynchronous

I'm trying to pass the configuration values returned from the server(zookeeper) into compass (cdnHost, environment, etc) and seem to be having a hard time using the right approach.
I looked at ways to pass around args from one task to another on this page as a starting point
http://gruntjs.com/frequently-asked-questions#how-can-i-share-parameters-across-multiple-tasks
module.exports = function(grunt) {
grunt.initConfig({
compass: {
dist: {
//options: grunt.option('foo')
//options: global.bar
options: grunt.config.get('baz')
}
},
...
grunt.registerTask('compassWithConfig', 'Run compass with external async config loaded first', function () {
var done = this.async();
someZookeeperConfig( function () {
// some global.CONFIG object from zookeeper
var config = CONFIG;
// try grunt.option
grunt.option('foo', config);
// try config setting
grunt.config.set('bar', config);
// try global
global['baz'] = config;
done(true);
});
});
...
grunt.registerTask('default', ['clean', 'compassWithConfig', 'compass']);
I also tried calling the compass task directly, and it made no difference.
grunt.task.run('compass');
Any insights would be greatly appreciated. (e.g. way to use initConfig and have the value be available).
Thanks

When you write:
grunt.initConfig({
compass: {
dist: {
options: grunt.config.get('baz')
}
}
... grunt.config is called right away, and returns the value of baz as it is right now. Altering it (later) in another task simply won't get picked-up.
How to solve that?
#1: update compass.dist.options instead of updating baz
grunt.registerTask('compassWithConfig', 'Run compass with external async config loaded first', function () {
var done = this.async();
someZookeeperConfig( function () {
// some global.CONFIG object from zookeeper
var config = CONFIG;
grunt.config.set('compass.dist.options', config);
done();
});
});
Now, running task compassWithConfig first, then task compass will get the result you expect.
#2: wrap-up compass task execution in order to abstract away config mapping
grunt.registerTask('wrappedCompass', '', function () {
grunt.config.set('compass.dist.options', grunt.config.get('baz'));
grunt.task.run('compass');
});
// Then, you can manipulate 'baz' without knowing how it needs to be mapped for compass
grunt.registerTask('globalConfigurator', '', function () {
var done = this.async();
someZookeeperConfig( function () {
// some global.CONFIG object from zookeeper
var config = CONFIG;
grunt.config.set('baz', config);
done();
});
});
Finally, running task globalConfigurator then wrappedCompass will get you to the result.

Related

Asynchronous tasks in grunt.registerTask

I need to call two functions within grunt.registerTask, but the second function has to be called after the first function is done.
So I was wondering if we can use callbacks or promises or other asynchronous mechanisms within grunt.registerTask.
(More specifically, I need to launch karma in my first function call, and run karma in the second function call (to execute the initial unit tests). But in order to run karma, I need to launch it first. And that's what I'm missing.)
I had this:
grunt.registerTask("name_of_task", ["task_a", "task_b", "task_c"]);
And "task_b" had to be executed after "task_a" was done. The problem is that "task_a" is asynchronous and returns right away, so I needed a way to give "task_a" a few seconds to execute.
The solution:
grunt.registerTask("name_of_task", ["task_a", "task_b:proxy", "task_c"]);
grunt.registerTask("task_b:proxy", "task_b description", function () {
var done = this.async();
setTimeout(function () {
grunt.task.run("task_b");
done();
}, 2000);
});
};
From http://gruntjs.com/creating-tasks:
Tasks can be asynchronous.
grunt.registerTask('asyncfoo', 'My "asyncfoo" task.', function() {
// Force task into async mode and grab a handle to the "done" function.
var done = this.async();
// Run some sync stuff.
grunt.log.writeln('Processing task...');
// And some async stuff.
setTimeout(function() {
grunt.log.writeln('All done!');
done();
}, 1000);
});
For the sake of simplicity... having this Grunfile.js
grunt.registerTask('a', function () {
let done = this.async();
setTimeout(function () {
console.log("a");
done();
}, 3000);
});
grunt.registerTask('b', function () {
let done = this.async();
console.log("b1");
setTimeout(function () {
console.log("b2");
done();
}, 3000);
console.log("b3");
});
grunt.registerTask('c', function () {
console.log("c");
});
grunt.registerTask("run", ["a", "b", "c"]);
and then running run task, will produce the following output
Running "a" task
a
Running "b" task
b1
b3
b2 <-- take a look here
Running "c" task
c
Done.
The command is executed in this order
wait 3000 ms
console.log("a")
console.log("b1")
console.log("b3")
wait 3000 ms
console.log("b2")
console.log("c")

Run a command after a grunt task finishes?

I want to run a command but after a task finishes in grunt.
uglify: {
compile: {
options: {...},
files: {...}
}
?onFinish?: {
cmd: 'echo done!',
// or even just a console.log
run: function(){
console.log('done!');
}
}
},
Either run a command in shell, or even just be able to console.log. Is this possible?
Grunt does not support before and after callbacks, but next version could implement events that would work in the same way, as discussed in issue #542.
For now, you should go the task composition way, this is, create tasks for those before and after actions, and group them with a new name:
grunt.registerTask('newuglify', ['before:uglify', 'uglify', 'after:uglify']);
Then remember to run newuglify instead of uglify.
Another option is not to group them but remember to add the before and after tasks individually to a queue containing uglify:
grunt.registerTask('default', ['randomtask1', 'before:uglify', 'uglify', 'after:uglify', 'randomtask2']);
For running commands you can use plugins like grunt-exec or grunt-shell.
If you only want to print something, try grunt.log.
The grunt has one of the horrible code that I've ever seen. I don't know why it is popular. I would never use it even as a joke. This is not related to "legacy code" problem. It is defected by design from the beginning.
var old_runTaskFn = grunt.task.runTaskFn;
grunt.task.runTaskFn = function(context, fn, done, asyncDone) {
var callback;
var promise = new Promise(function(resolve, reject) {
callback = function (err, success) {
if (success) {
resolve();
} else {
reject(err);
}
return done.apply(this, arguments);
};
});
something.trigger("new task", context.name, context.nameArgs, promise);
return old_runTaskFn.call(this, context, fn, callback, asyncDone);
}
You can use callback + function instead of promise + trigger. This function will request the new callback wrapper for new task.

Running tests with localStorage

i have followed this tutorial from Codelab and yeoman. When implemented right you are using local storage to store the TodoList. I have problems with setting up with my tests, to test if this works. This is what i've got so far:
'use strict';
describe('Controller: MainCtrl', function () {
// load the controller's module
beforeEach(module('yeoTodoApp'), module('LocalStorageModule'));
var MainCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, $httpBackend) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope: scope
});
}));
it('should add items to the list', function () {
var beforeLength = scope.todos.length;
scope.todo = 'Test 1';
scope.addTodo();
var afterLength = scope.todos.length;
expect(afterLength-beforeLength).toBe(1);
});
it('should add items to the list then remove', function () {
var beforeLength = scope.todos.length;
scope.todo = 'Test 1';
scope.addTodo();
scope.removeTodo(0);
var afterLength = scope.todos.length;
expect(afterLength-beforeLength).toBe(0);
});
});
The error i get is
line 12 col 68 '$httpBackend' is defined but never used.
});
How would i write my unit tests to sit the local storage?
I think at the moment the idea is kind of mocking your local storage:
Write unit tests
For an extra challenge, revisit unit testing in Step 8 and consider
how you might update your tests now that the code is using local
storage.
Tip: It's not a straight forward answer and involves knowing about
mock services. Check out Unit Testing Best Practices in AngularJS,
specifically the Mocking Services and Modules in AngularJS section.
Things may have changed since this question was asked. Anyhow, here is my solution:
'use strict';
describe('Controller: MainCtrl', function () {
// load the controller's module
beforeEach(module('mytodoyoappApp'));
var MainCtrl,
scope,
localStorage, store;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope:scope
// place here mocked dependencies
});
/*mock the localStorageService*/
store={};
localStorage = {
set: function(key, value) {
store[key] = value;
},
get: function(key) {
return store[key];
}
};
}));
it('should check the list length', function () {
expect(MainCtrl.todos.length).toBe(0);
});
it('should add items to the list', function () {
MainCtrl.todoadded = 'Test 1';
MainCtrl.addTodo();
expect(MainCtrl.todos.length).toBe(1);
});
it('should add then remove an item from the list', function () {
MainCtrl.todoadded = 'Test 2';
MainCtrl.addTodo();
MainCtrl.removeTodo(0);
expect(MainCtrl.todos.length).toBe(0);
});
it('should check that the localstorage is undefined before being set', function() {
var a=localStorage.get('todos');
expect(a).toBeUndefined();
});
it('should set and get the localstorage', function() {
localStorage.set('todos', ['Test 3']);
var a=localStorage.get('todos');
expect(a).toEqual(['Test 3']);
localStorage.set('todos', ['Test 4']);
var b=localStorage.get('todos');
expect(b).toEqual(['Test 4']);
});
});
your setup is correct now (after you removed $httpBackend from the arguments list)
Controller: MainCtrl should add items to the list then remove FAILED
this error is a simple test error, which means that your code somewhere doesnt work as expected (your second test fails)
i for myself would check todos length, and not the result of a mathematical operation.
i would write your tests the test like this:
it('should add items to the list then remove', function () {
scope.todo = 'Test 1';
expect(scope.todos.length).toBe(0);
scope.addTodo();
expect(scope.todos.length).toBe(1);
scope.removeTodo(0);
expect(scope.todos.length).toBe(0);
});
you use jasmine as a test-tool. jasmine logs on errors exactly which expectation fails, so you should get something like
expect '1' to be '0'
go from there!

How can i watch console.log in terminal. Dalekjs

I have javascript:
$('#link').on('click', function ()
{
console.log('Click link');
});
And i write dalekjs-test:
module.exports = {
'Clicked link': function (test)
{
test.open('http://localhost/')
.click('#link')
.done();
}
};
And after running $ dalek tests/test.js i wanna see that Click link.
How can i get it?
This is currently not possible & I do not know if it will be possible in the future.
The thing is, you have to overwrite the console object in the browser. That would mean, you have to actively change something in the environment of your user. And I don't think that this is a good idea.
Although, there might be a workaround for your Problem ;)
Lets say, you write a debugging function (as a client side javascript file) like this:
window.myDebugLog = [];
window.myDebug = function () {
var list = Array.prototype.slice.call(arguments, 0);
window.myDebugLog.push(list);
}
Now, lets modify your logging function a bit:
$('#link').on('click', function () {
var msg = 'Click link';
console.log(msg);
window.myDebug(msg);
});
With this you can access & output your logging. Even better, with some ClojureCompiler or Esprima fun, you could parse out that debugging stuff when building your production code, so that you do not need to ship it.
In your Dalek test, just do this:
module.exports = {
'Clicked link': function (test) {
test.open('http://localhost/')
.click('#link')
.execute(function () {
this.data('logs', window.myDebugLog);
})
.log.message(function () {
return JSON.stringify(test.data('logs').pop());
})
.done();
}
};
That will give you your log stuff in the Command Line Output.
If you´re not willing to add this extra function call, you could also overwrite the console.log on your own. But that could cause some trouble, so take this with a grain of salt:
window.myDebugLog = [];
window.oldLog = console.log;
console.log = function () {
var list = Array.prototype.slice.call(arguments, 0);
window.myDebugLog.push(list);
window.oldLog.apply(console, list);
};
Hope that helps you with your problem.

How to run grunt-init from a Grunt task?

I am either blanking out or it is more complex that it should have been.
I am trying to run grunt-init from a Grunt task, something like this:
grunt.registerTask('init', 'Scaffold various artifacts', function(param) {
// analyze `param` and pass execution to `grunt-init`
// run `grunt-init path/to/some/template/based/on/param/value`
});
The part of analysis of the param is, of course, not the issue. It's running the grunt-init that is.
Running grunt-init directly in the same folder as the below attempts works fine.
I've tried the following methods (path to template is inlined for shortness of the code), all to no avail:
grunt-shell
shell: {
init: {
options: {
stdout: true,
callback: function(err, stdout, stderr, cb) {
...
}
},
command: 'grunt-init path/to/some/template/based/on/param/value'
}
}
and then:
grunt.registerTask('init', 'Scaffold various artifacts', function(param) {
grunt.task.run(['shell:init']);
});
and in command line:
grunt init
or from command line directly:
grunt shell:init
grunt-exec
exec: {
init: {
cmd: 'grunt-init path/to/some/template/based/on/param/value',
callback: function() {
...
}
}
}
and then:
grunt.registerTask('init', 'Scaffold various artifacts', function(param) {
grunt.task.run(['exec:init']);
});
and in command line:
grunt init
or from command line directly:
grunt exec:init
Node's exec
grunt.registerTask('init', 'Scaffold various artifacts', function(param) {
var exec = require('child_process').exec;
exec('grunt-init path/to/some/template/based/on/param/value', function(err, stdout, stderr) {
...
});
});
and in command line:
grunt init
Nothing.
There were various attempts, best of which would print the first line of grunt-init prompt:
Running "init" task
And that's it.
What am I missing? Should I have connected the stdout somehow?
Create a child process with grunt.util.spawn. You can make it asynchronous and set stdio to 'inherit' so that any template prompts can be answered. Also, you should set a cwd or else it will try to overwrite your existing Gruntfile.js!
grunt.registerTask('init', 'Scaffold various artifacts', function(grunt_init_template) {
var done = this.async();
grunt.util.spawn({
cmd: 'grunt-init',
args: [grunt_init_template],
opts: {
stdio: 'inherit',
cwd: 'new_project_dir',
}
}, function (err, result, code) {
done();
});
});
I think I found a way, but it feels hack-ish to me. I am going to answer this, but, please give yours too.
It can be done using grunt-parallel
grunt-parallel
where task is defined using:
parallel: {
init: {
options: {
stream: true
},
tasks: [
{cmd: 'grunt-init'}
]
}
}
and init task is:
grunt.registerTask('init', 'Scaffold various artifacts', function(param) {
// calculate path based on `param`
...
grunt.config.set('parallel.init.tasks.0.args', ['path/to/some/template/based/on/param/value']);
grunt.task.run(['parallel:init']);
});
then, running the following in command line:
grunt init:<some param indicating template type or similar>
properly runs grunt-init.

Resources