How to pass arguments/parameters to mocha tests invoked via Grunt - javascript

I have a Gruntfile.js through which i'm invoking mochaTest using grunt-mocha-test module. I can pass an argument/parameter to the gruntTask from command line but i'm struggling to pass the same parameter into the spec file running via the above module. The code looks like below,
mochaTest: {
test: {
options: {
reporter: 'spec',
quiet: false,
clearRequireCache: false,
clearCacheFilter: (key) => true,
noFail: false
},
src: [
'test/createSpec.js'
]
}
}
Task is registered like below,
grunt.registerTask('e2etest', function(scope) {
console.log(scope); // logs user/session based on the parameter passed
grunt.task.run('mochaTest');
});
// Above task is invoked like,
grunt e2etest:user
(or)
grunt e2etest:session
I need to pass this value (user/session) into mochaTest so it can be accessed inside the spec file. Fundamentally the aim is to run the createSpec.js file both for user and session, this values is parametrized inside the spec file and based on the value passed the suite would run.
Is there a possibility to do this? Please advise.

Please refer to this issue for detail, and I think the solution you need here is:
node <node flags here> node_modules/mocha/bin/_mocha <mocha arguments here>

You can utilize nodes process.argv to read the argument (i.e. user or session) from within the file named createSpec.js.
To better understand how, follow these steps:
At the top of createSpec.js add the following line of code:
console.log(process.argv);
Then run grunt e2etest:user via your CLI and you should see the following logged to your console:
[ 'node', '/usr/local/bin/grunt', 'e2etest:user' ]
Note: the information you want is positioned at index two of the array.
Now, delete the line we just added which reads console.log(process.argv); from createSpec.js.
createSpec.js
So, the steps above (1-3) illustrated that the arguments (user or session) can be accessed in createSpec.js utilizing process.argv. In which case you could do something like the following inside createSpec.js.
const argument = process.argv[2].split(':').pop();
if (argument === 'user') {
// Run `user` specific tests here...
} else if (argument === 'session') {
// Run `session` specific tests here...
}
Note, we're using process.argv[2].split(/:/).pop(); to extract either user or session from the array item positioned at index two, whose initial value will be either e2etest:user or e2etest:session respectively.
Gruntfile
Your createSpec.js file is now somewhat dependent on the grunt task named e2etest being invoked correctly. For example, if a user were to run grunt e2etest without providing the arguments then createSpec.js is not going to do much.
To enforce the correct usage of the e2etest task (i.e. it must be run using either grunt e2etest:user or grunt e2etest:session), you could change your task in your Gruntfile as follows:
grunt.registerTask('e2etest', function(scope) {
if (!scope || !(scope === 'user' || scope === 'session')) {
grunt.warn(`Must be invoked with: ${this.name}:user or ${this.name}:session`);
}
grunt.task.run('mochaTest');
});
The gist above initially checks that an argument has been provided and is either user or session. If the argument is incorrect or missing then grunt.warn is utilized to warn the user.
If your version of nodejs does not support ES6 Template literals then use grunt.warn as follows instead:
grunt.warn('Must be invoked with: ' + this.name + ':user or ' + this.name + ':session');
Additional comment
The code/gist shown in the createSpec.js section above will work if your use-case is exactly as you mention in your question. I.e. you invoke via the commandline using grunt e2etest:user or grunt e2etest:session. However, if that changes and you cannot guarantee that e2etest:user or e2etest:session will be exactly positioned at index two of the process.argv array, then you may need to do the following at the top of createSpec.js instead:
// Return the value in the array which starts with
// `e2etest` then extract the string after the colon `:`
const argument = process.argv.filter(arg => {
return arg.match(/^e2etest:.*/);
}).toString().split(':').pop();
if (argument === 'user') {
// Run `user` specific tests here...
} else if (argument === 'session') {
// Run `session` specific tests here...
}

Related

Run a specific task when the task required by user is not present in the gulpfile

Is there any way to run a specific task when the task supplied by the user is not present in the gulpfile.
For example, if the user runs gulp build and the build task is not there in the gulpfile then a specific task (or default task, doesn't matter to me) should run.
As an analogy, consider the specified task as the 404 page for gulp.
gulp inherits from orchestrator, which contains a tasks instance variable that is a plain object with keys that are names of tasks added to the instance. If you replaced tasks with a proxy, you could have it return a default task, like default by using the get trap handler:
const gulp = require('gulp')
gulp.tasks = new Proxy(gulp.tasks, {
get (target, property) {
if (target.hasOwnProperty(property)) {
return target[property]
}
return target.default
}
})
That code can be run at anytime before the sequence of tasks is started, even after some tasks have already been added to the gulp instance.
Patrick's answer was really helpful. I had to modify a little since I was getting an ESLint warning and the following error -
TypeError: this.tasks.hasOwnProperty is not a function
So I thought to post my changes too. Here's my final code -
gulp.tasks = new Proxy(gulp.tasks, {
get: function(target, property) {
if (undefined !== target[property]) {
return target[property];
}
return target.default; //or target["<Custom task name>"]
}
});

Write custom webpack resolver

I'm planning on using a set of a little bit more sophisticated conventions to import assets in my webpack project. So I'm trying to write a plugin that should rewrite parts of requested module locators and then pass that down the resolver waterfall.
Let's assume we just want to
check if a requested module starts with the # character and
if so, replace that with ./lib/. The new module locator should now be looked up by the default resolver.
This means when a file /var/www/source.js does require("#example"), it should then actually get /var/www/lib/example.js.
So far I've figured out I'm apparently supposed to use the module event hook for this purpose. That's also the way chosen by other answers which unfortunately did not help me too much.
So this is my take on the custom resolve plugin, it's pretty straightforward:
function MyResolver () {}
MyResolver.prototype.apply = function (compiler) {
compiler.plugin('module', function (init, callback) {
// Check if rewrite is necessary
if (init.request.startsWith('#')) {
// Create a new payload
const modified = Object.assign({}, init, {
request: './lib/' + init.request.slice(1)
})
// Continue the waterfall with modified payload
callback(null, modified)
} else {
// Continue the waterfall with original payload
callback(null, init)
}
})
}
However, using this (in resolve.plugins) doesn't work. Running webpack, I get the following error:
ERROR in .
Module build failed: Error: EISDIR: illegal operation on a directory, read
# ./source.js 1:0-30
Apparently, this is not the way to do things. But since I couldn't find much example material out there on the matter, I'm a little bit out of ideas.
To make this easier to reproduce, I've put this exact configuration into a GitHub repo. So if you're interested in helping, you may just fetch it:
git clone https://github.com/Loilo/webpack-custom-resolver.git
Then just run npm install and npm run webpack to see the error.
Update: Note that the plugin architecture changed significantly in webpack 4. The code below will no longer work on current webpack versions.
If you're interested in a webpack 4 compliant version, leave a comment and I'll add it to this answer.
I've found the solution, it was mainly triggered by reading the small doResolve() line in the docs.
The solution was a multiple-step process:
1. Running callback() is not sufficient to continue the waterfall.
To pass the resolving task back to webpack, I needed to replace
callback(null, modified)
with
this.doResolve(
'resolve',
modified,
`Looking up ${modified.request}`,
callback
)
(2. Fix the webpack documentation)
The docs were missing the third parameter (message) of the doResolve() method, resulting in an error when using the code as shown there. That's why I had given up on the doResolve() method when I found it before putting the question up on SO.
I've made a pull request, the docs should be fixed shortly.
3. Don't use Object.assign()
It seems that the original request object (named init in the question) must not be duplicated via Object.assign() to be passed on to the resolver.
Apparently it contains internal information that trick the resolver into looking up the wrong paths.
So this line
const modified = Object.assign({}, init, {
request: './lib/' + init.request.slice(1)
})
needs to be replaced by this:
const modified = {
path: init.path,
request: './lib/' + init.request.slice(1),
query: init.query,
directory: init.directory
}
That's it. To see it a bit clearer, here's the whole MyResolver plugin from above now working with the mentioned modifications:
function MyResolver () {}
MyResolver.prototype.apply = function (compiler) {
compiler.plugin('module', function (init, callback) {
// Check if rewrite is necessary
if (init.request.startsWith('#')) {
// Create a new payload
const modified = {
path: init.path,
request: './lib/' + init.request.slice(1),
query: init.query,
directory: init.directory
}
// Continue the waterfall with modified payload
this.doResolve(
// "resolve" just re-runs the whole resolving of this module,
// but this time with our modified request.
'resolve',
modified,
`Looking up ${modified.request}`,
callback
)
} else {
this.doResolve(
// Using "resolve" here would cause an infinite recursion,
// use an array of the possibilities instead.
[ 'module', 'file', 'directory' ],
modified,
`Looking up ${init.request}`,
callback
)
}
})
}

NodeJS - Can I detect when called from command line (ES6 + Babel)..?

In my module I need to detect when it's being called from either the command line, or from another module.
const isFromCLI = '????'
I'm using Babel/ES6, so when called from a command line, index.js is called (with the babel code), which hands off to script.js (with ES6 code). So from the script file, module.parent returns module (the index.js file). So I can't use module.parent!
Also, module.main is undefined (in script.js) when called from either the command line or from another module. So I can't use module.main!
Those are the two solutions that others are suggesting, but they don't work for me.
Is there a simple to detect this when using Babel/ES6..?
Update
require.main returns module when called from either the command line or from another module.
You could use Process in Node.
https://nodejs.org/api/process.html#process_process_argv
Check the value of the second parameter (do a contains type match). Not sure if this is the only way but it's a straight forward way to achieve what you need
A code snippet might be:
const isFromCLI = (process.argv[1].indexOf('my-script.js') !== -1);
You can use Node Environment Variables.
You can set an environment variable like this:
CLI=true node app.js
and then get the environment variable like this:
const isFromCLI = process.env.CLI === 'true'
Note: process.env.CLI will be a string.
Update:
If you want to do something like node app.js --cli, you can do the following:
let isFromCLI
process.argv.forEach(function (val, index, array) {
if (array[index] === '--cli') {
isFromCLI = true
}
})
console.log(isFromCLI)

Understanding gruntjs registerTask colon

I'm currently trying to learn gruntjs for dev and production build.
I want to assign a global config variable to determine stuff.
I have a simple initConfig :
grunt.initConfig({
foo: {
bar: {GLOBAL: true},
baz: {GLOBAL: false}
}
});
grunt.registerTask('one', ['foo:bar']);
grunt.registerTask('two', ['foo:baz']);
My question is:
What exactly is the colon in my tasks doing? (foo:bar or foo:baz)
And what is the difference between a colon and a simple dot?
My Goal is to have a global variable set either to true or false for further processing:
grunt.initConfig({
foo: {
bar: {GLOBAL: true},
baz: {GLOBAL: false}
},
awesomestuff: {
smth: GLOBAL ? 'yes' : 'no',
another: !Global ? 'DoDebug' : 'MakeRelease'
}
});
grunt.registerTask('one', ['foo:bar', 'awesomestuff']);
grunt.registerTask('two', ['foo:baz', 'awesomestuff']);
How would I achieve this?
Update
I got the global variable working somehow. By registering a new new task called init with an argument I can call it in an other task.
grunt.registerTask('init', 'Init', function(param) {
grunt.config('GLOBAL', param)
});
grunt.registerTask('one', ['init:true', 'foo:bar', 'awesomestuff']);
In this case the init task will be called with the vairable param set to true.
But the question is still:
Why would I use a colon insted of a dot to reference an object?
Why would I use a colon instead of a dot to reference an object?
To understand why, you firstly need to understand grunt task configurations and targets.
Single Target
To help you further understand this concept and terminology, take a look at this example configuration for a grunt plugin called grunt-contrib-copy. It's a plugin that copies files. Below is a snippet of that code:
grunt.initConfig({
copy: { // <-- Task
main: { // <-- Target
// ... <-- other configurations go here.
}
}
});
In this example above the Task is named copy and it includes a single Target named main.
To register this Task you would do so as follows:
grunt.registerTask('copyFiles', ['copy:main']);
and you would enter the following via your command line to run it:
$ grunt copyFiles
Multiple Targets
Grunt Tasks can also include more than one Target. Consider this example code below:
grunt.initConfig({
copy: {
js: {
// ... <-- Additional configurations for this Target go here.
},
css: {
// ... <-- Additional configurations for this Target go here.
}
}
});
You could register the example above as follows:
grunt.registerTask('copyJavaScriptFiles', ['copy:js']);
grunt.registerTask('copyCssFiles', ['copy:css']);
So, via the command line:
Running $ grunt copyJavaScriptFiles will copy all the JS files according to the configurations specified.
Running $ grunt copyCssFiles will copy all the CSS files according to the configurations specified.
If you wanted to copy both the JS and CSS files you could register a task as follows:
grunt.registerTask('copyAll', ['copy']);
And you would run it by entering $ grunt copyAll in your command line.
Notice in the last example it does not include any colon :. Grunt this time will run all the Targets in the copy Task, namely the js one and the css one.
And what is the difference between a colon and a simple dot?
Colon
Hopefully by now you can see what the colon : does. It is used to reference a particular Target within a Task and is typically only used when a Task has multiple Targets and you want to specifically reference one of them.
Simple dot
The simple dot is JavaScript's standard notation to access properties of an object. Google "JavaScript notation" to find out more about Dot Notation and Square Bracket Notation.
Within the context of your Gruntfile.js the dot notation is typically used to call the functions/methods/properties of the grunt object. For example:
grunt.initConfig({...});
grunt.loadNpmTasks(...);
grunt.registerTask(...);
EDIT 1 Updated the answer after the original post/question was updated.

GruntJS Configurable First Time Run

I am working on a Angular Demo Application and I want to automatize a lot of things.
It's some sort of a boilerplate, albeit a more complex one, and I want to make a config file in which we'll put API Keys and other stuff, and I want that file to be populated by Grunt with user interaction when the project is started for the first time.
Something like:
grunt build - it should ask the user directly in the console for the API keys, that will be inserted in the config file where I am defining some global constants for the entire App.
Is there such an example of functionality with Grunt ?
You can handle the questioning by using:
https://github.com/dylang/grunt-prompt
It is a nice little plugin that do one job and do it well. It put whatever value you have entered in the command line into variables: (example)
prompt: {
target: {
options: {
questions: [
{
config: 'key', // arbitrary name or config for any other grunt task
type: 'input', // list, checkbox, confirm, input, password
message: 'What is your API key?',
default: '', // default value if nothing is entered
when: function(answers) { return !grunt.file.exists('config.yml'); } // only ask this question when this function returns true
}
]
}
}
}
Then you can use the Grunt.file functions to write those values into files:
http://gruntjs.com/api/grunt.file#grunt.file.write
To orchestrate it, you will need to create a custom task: (example)
grunt.registerTask("my_config_task", function (arg) {
var key = arg || grunt.config('key');
grunt.file.write("config.yml", key);
});
grunt.registerTask('build', ['prompt', 'my_config_task']);
The writing will likely need refinement as you will, I guess, need to replace values and organise as a yml file or json object, etc...
Found one of the possible solutions while looking at the sources of grunt-bump. What are they doing is parsing the config file as a JSON object:
https://github.com/darsain/grunt-bumpup/blob/master/tasks/bumpup.js#L128
Replacing whatever values they need (as JSON) and overwrite the file with the object stringified:
https://github.com/darsain/grunt-bumpup/blob/master/tasks/bumpup.js#153
Seems to work well.

Categories

Resources