├── .gitignore ├── .jscs.json ├── .jshintrc ├── .travis.yml ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── lib ├── filterTasks.js ├── get_output.js ├── reporters.js └── taskIdentifiers.js ├── package.json ├── screenshot.png ├── tasks └── available_tasks.js └── test └── lib ├── filterTasks.test.js └── get_output.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | -------------------------------------------------------------------------------- /.jscs.json: -------------------------------------------------------------------------------- 1 | { 2 | "disallowKeywordsOnNewLine": ["else"], 3 | "requireAlignedObjectValues": "all", 4 | "requireCurlyBraces": ["if", "else", "for", "while", "do"], 5 | "requireMultipleVarDecl": true, 6 | "requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="], 7 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return"], 8 | "requireSpaceAfterObjectKeys": true, 9 | "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="], 10 | "requireSpacesInFunctionExpression": { 11 | "beforeOpeningCurlyBrace": true 12 | }, 13 | "safeContextKeyword": "self" 14 | } 15 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "expr": true, 3 | "strict": true, 4 | "trailing": true, 5 | "undef": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "latedef": true, 10 | "noarg": true, 11 | "noempty": true, 12 | "unused": true, 13 | "node": true, 14 | "indent": 4 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '5' 4 | - '4' 5 | - '0.12' 6 | - '0.10' 7 | - 'iojs' 8 | before_script: 9 | - npm install -g grunt-cli 10 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-available-tasks 3 | * https://github.com/ben-eb/grunt-available-tasks 4 | * 5 | * Copyright (c) 2013 Ben Briggs 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(grunt) { 12 | require('load-grunt-tasks')(grunt); 13 | grunt.initConfig({ 14 | jscs: { 15 | options: { 16 | config: '.jscs.json' 17 | }, 18 | source: [ 19 | 'lib/**/*.js', 20 | 'tasks/**/*.js', 21 | 'test/**/*.js' 22 | ] 23 | }, 24 | jshint: { 25 | all: [ 26 | 'Gruntfile.js', 27 | 'tasks/**/*.js', 28 | 'lib/**/*.js' 29 | ], 30 | test: { 31 | src: ['test/**/*.js'], 32 | options: { 33 | 'predef' : ['describe', 'it', 'beforeEach'] 34 | } 35 | }, 36 | options: { 37 | jshintrc: true 38 | }, 39 | }, 40 | lintspaces: { 41 | source: { 42 | src: [ 43 | 'tasks/**/*.js', 44 | 'test/**/*.js', 45 | 'lib/**/*.js', 46 | 'Gruntfile.js', 47 | 'README.md' 48 | ], 49 | options: { 50 | newline : true, 51 | trailingspaces : true, 52 | indentation : 'spaces', 53 | spaces : 4, 54 | ignores : ['js-comments'] 55 | } 56 | } 57 | }, 58 | availabletasks: { 59 | options: { 60 | filter: 'include', 61 | tasks: ['tasks', 'default'], 62 | sort: ['tasks'] 63 | }, 64 | defaultreporter: {}, 65 | markdownreporter: { 66 | options: { 67 | reporter: 'markdown' 68 | } 69 | }, 70 | customreporter: { 71 | options: { 72 | reporter: function(options) { 73 | grunt.log.writeln(options.currentTask.name); 74 | } 75 | } 76 | } 77 | }, 78 | mochaTest: { 79 | unit: ['test/lib/**/*.js'], 80 | options: { 81 | reporter: 'spec' 82 | } 83 | } 84 | }); 85 | // Lint all the things 86 | grunt.registerTask('default', 'Run code validation tasks', ['lintspaces', 'jshint', 'jscs', 'mochaTest', 'tasks']); 87 | // Alias availabletasks with tasks for easier typing 88 | grunt.registerTask('tasks', ['availabletasks:defaultreporter']); 89 | // Actually load this plugin's task(s). 90 | grunt.loadTasks('tasks'); 91 | }; 92 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Ben Briggs (http://beneb.info) 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grunt-available-tasks 2 | 3 | ![screenshot](screenshot.png) 4 | 5 | [![Build Status](https://travis-ci.org/ben-eb/grunt-available-tasks.svg?branch=master)][ci] [![NPM version](https://badge.fury.io/js/grunt-available-tasks.svg)][npm] [![Dependency Status](https://gemnasium.com/ben-eb/grunt-available-tasks.svg)][deps] [![Code Climate](https://codeclimate.com/github/ben-eb/grunt-available-tasks/badges/gpa.svg)][cc] 6 | 7 | Want all of your registered tasks in a nice, alphabetized, colour coded list? 8 | Think the task list outputted by `grunt --help` could be more descriptive? 9 | `grunt-available-tasks` to the rescue! 10 | 11 | ## Install 12 | 13 | Install via [npm](https://npmjs.org/package/grunt-available-tasks): 14 | 15 | ``` 16 | npm install grunt-available-tasks --save-dev 17 | ``` 18 | 19 | ## Example 20 | 21 | ```js 22 | module.exports = function(grunt) { 23 | grunt.initConfig({ 24 | availabletasks: { // task 25 | tasks: {} // target 26 | } 27 | }); 28 | 29 | grunt.loadNpmTasks('grunt-available-tasks'); 30 | // Now run the command below on the command line to get your tasks list: 31 | // grunt availabletasks 32 | }; 33 | ``` 34 | 35 | If you want some further customisation, the options are as follows: 36 | 37 | ## Options 38 | 39 | ### options.tasks 40 | Type: `Object` 41 | Default value: `false` 42 | 43 | The list of tasks to either include or exclude with the filter option. 44 | 45 | ### options.filter 46 | Type: `String` 47 | Default value: `false` 48 | 49 | Define either 'include', or 'exclude'. The filter configuration will override 50 | the group, description and sort configurations; so if you have filtered out a 51 | task it will not show up in any groups, it won't receive a custom description 52 | and it won't appear at the top of your task list. An example configuration: 53 | 54 | ```js 55 | availabletasks: { 56 | tasks: { 57 | options: { 58 | filter: 'include', 59 | tasks: ['availabletasks', 'default'] 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | ### options.showTasks 66 | Type: `Array` 67 | Default value: `['single', 'multi', 'user']` 68 | 69 | Use this option if you would like to show only a subset of the task types. For 70 | example if you just want to show the tasks that you have written: 71 | 72 | ```js 73 | availabletasks: { 74 | tasks: { 75 | options: { 76 | showTasks: ['user'] 77 | } 78 | } 79 | } 80 | ``` 81 | 82 | ### options.groups 83 | Type: `Object` 84 | Default value: `{}` (empty) 85 | 86 | You may choose to group similar tasks if you'd like; note that the same task can 87 | appear in multiple groups if you wish. An example configuration: 88 | 89 | ```js 90 | availabletasks: { 91 | tasks: { 92 | options: { 93 | groups: { 94 | 'Run code validation tasks': ['lintspaces', 'jshint', 'jscs'] 95 | } 96 | } 97 | } 98 | } 99 | ``` 100 | 101 | ### options.descriptions 102 | Type: `Object` 103 | Default value: `{}` (empty) 104 | 105 | Override any task name, including aliases, with any description that you like. 106 | An example configuration: 107 | 108 | ```js 109 | availabletasks: { 110 | tasks: { 111 | options: { 112 | descriptions: { 113 | 'availabletasks': 'A powerful task list helper for Grunt enabled projects.' 114 | } 115 | } 116 | } 117 | } 118 | ``` 119 | 120 | ### options.sort 121 | Type: `Boolean|Array` 122 | Default value: `true` 123 | 124 | Setting this to `false` will maintain the original sort order for the tasks. 125 | `true` will sort alphabetically, and specifying an array will allow you to do 126 | your own custom sorting. An example configuration: 127 | 128 | ```js 129 | availabletasks: { 130 | tasks: { 131 | options: { 132 | sort: ['lintspaces', 'availabletasks'] 133 | } 134 | } 135 | } 136 | ``` 137 | 138 | ### options.hideUngrouped 139 | Type: `Boolean` 140 | Default value: `false` 141 | 142 | Setting this to `true` will not output any tasks that haven't 143 | been assigned to a group. `false` will display ungrouped tasks. 144 | An example configuration: 145 | 146 | ```js 147 | availabletasks: { 148 | tasks: { 149 | options: { 150 | hideUngrouped: true 151 | } 152 | } 153 | } 154 | ``` 155 | 156 | ### options.reporter 157 | Type: `String|Function` 158 | Default value: `default` 159 | 160 | Choose either the default reporter (`default`) or the Markdown reporter 161 | (`markdown`). Alternately, you can pass a `function` to this option if you'd 162 | like to specify a custom reporter. A simple reporter could look like this: 163 | 164 | ```js 165 | availabletasks: { 166 | tasks: { 167 | options: { 168 | reporter: function(options) { 169 | grunt.log.writeln(options.currentTask.name); 170 | } 171 | } 172 | } 173 | } 174 | ``` 175 | 176 | In this function you are expected to handle group headings and how you'd like 177 | the multi task targets to be displayed. The options object that is passed will 178 | look something like this: 179 | 180 | ```js 181 | { 182 | currentTask: { 183 | name: 'availabletasks', 184 | type: '=>', 185 | info: 'List available Grunt tasks & targets.', 186 | group: 'Ungrouped' 187 | }, 188 | meta: { 189 | taskCount: 2, 190 | groupCount: 0, 191 | header: 'Ungrouped', // Only passed when the group has changed 192 | longest: 14 // The length of the longest task, useful for column padding. 193 | } 194 | } 195 | ``` 196 | 197 | See the [reporters.js](lib/reporters.js) file for the default reporters, which 198 | you can take and customise to your liking. 199 | 200 | ## Output 201 | 202 | From left to right, this plugin outputs the task name, the type of the task, 203 | then the description and finally a list of multitask targets should you have 204 | configured two or more. The type of the task is registered with arrows: 205 | 206 | * ` > ` denotes a single target task. 207 | * ` -> ` denotes a multi target task. 208 | * ` => ` denotes a user defined task. 209 | 210 | ## Contributing 211 | 212 | Pull requests are welcome. If you add functionality, then please add unit tests 213 | to cover it. 214 | 215 | ## License 216 | 217 | MIT © [Ben Briggs](http://beneb.info) 218 | 219 | [cc]: https://codeclimate.com/github/ben-eb/grunt-available-tasks 220 | [ci]: https://travis-ci.org/ben-eb/grunt-available-tasks 221 | [deps]: https://gemnasium.com/ben-eb/grunt-available-tasks 222 | [npm]: http://badge.fury.io/js/grunt-available-tasks 223 | -------------------------------------------------------------------------------- /lib/filterTasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | // Filtering rules are optional; delete those tasks that don't pass a filter 6 | function filterTasks (type, tasks, alltasks) { 7 | var contains = function (task) { 8 | return _.includes(tasks, task.name); 9 | }; 10 | 11 | if (type === 'include') { 12 | return _.filter(alltasks, contains); 13 | } else if (type === 'exclude') { 14 | return _.reject(alltasks, contains); 15 | } 16 | return alltasks; 17 | } 18 | 19 | module.exports = filterTasks; 20 | -------------------------------------------------------------------------------- /lib/get_output.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | function getOutput (output, groups, taskOptions, hideUngrouped) { 6 | var hasGroup = false; 7 | Object.keys(groups).forEach(function (group) { 8 | // Use contains to make sure that the task name is an exact match: 9 | // i.e. we don't want to match tasks and availabletasks as true 10 | if (_.includes(groups[group], taskOptions.name)) { 11 | hasGroup = true; 12 | var newobj = _.clone(taskOptions); 13 | output.push(_.extend(newobj, { 14 | group : _.capitalize(group) 15 | })); 16 | } 17 | }); 18 | if (!hasGroup) { 19 | if (Object.keys(groups).length) { 20 | if (!hideUngrouped) { 21 | output.push(_.extend(taskOptions, { 22 | group : 'Ungrouped' 23 | })); 24 | } 25 | } else { 26 | output.push(taskOptions); 27 | } 28 | } 29 | } 30 | 31 | module.exports = getOutput; 32 | -------------------------------------------------------------------------------- /lib/reporters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'), 4 | chalk = require('chalk'); 5 | 6 | function markdownReporter (options) { 7 | var meta = options.meta, 8 | task = options.currentTask, 9 | targets = '', 10 | indentlevel = ''; 11 | 12 | if (meta.header && meta.groupCount) { 13 | indentlevel = '#'; 14 | console.log('## ' + task.group + '\n'); 15 | } 16 | 17 | if (task.targets.length > 1) { 18 | targets = indentlevel + '### Targets: `' + task.targets.join('`, `') + '`'; 19 | } 20 | 21 | console.log(indentlevel + '## ' + task.name + '\n' + targets + '\n' + task.info + '\n'); 22 | } 23 | 24 | function defaultReporter (options) { 25 | var meta = options.meta, 26 | task = options.currentTask, 27 | targets = ''; 28 | 29 | if (meta.header && meta.groupCount) { 30 | console.log('\n' + chalk.bold(task.group)); 31 | } 32 | 33 | if (task.targets.length > 1) { 34 | targets = '(' + task.targets.join('|') + ')'; 35 | } 36 | 37 | console.log( 38 | chalk.cyan(_.padEnd(task.name, meta.longest)), 39 | chalk.white(_.pad(task.type, 4)), 40 | task.info, 41 | chalk.green(targets) 42 | ); 43 | } 44 | 45 | module.exports.default = defaultReporter; 46 | module.exports.markdown = markdownReporter; 47 | -------------------------------------------------------------------------------- /lib/taskIdentifiers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | single : '>', 3 | multi : '->', 4 | user : '=>' 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-available-tasks", 3 | "description": "List available Grunt tasks & targets.", 4 | "version": "0.6.3", 5 | "homepage": "https://github.com/ben-eb/grunt-available-tasks", 6 | "author": { 7 | "name": "Ben Briggs", 8 | "email": "beneb.info@gmail.com", 9 | "url": "http://beneb.info" 10 | }, 11 | "repository": "ben-eb/grunt-available-tasks", 12 | "bugs": { 13 | "url": "https://github.com/ben-eb/grunt-available-tasks/issues" 14 | }, 15 | "files": [ 16 | "lib", 17 | "tasks", 18 | "LICENSE-MIT", 19 | "Gruntfile.js" 20 | ], 21 | "license": "MIT", 22 | "main": "Gruntfile.js", 23 | "engines": { 24 | "node": ">= 0.10.0" 25 | }, 26 | "dependencies": { 27 | "chalk": "^1.1.1", 28 | "lodash": "^4.10.0" 29 | }, 30 | "devDependencies": { 31 | "chai": "^3.5.0", 32 | "grunt": "^1.0.0", 33 | "grunt-contrib-jshint": "^1.0.0", 34 | "grunt-jscs": "^2.1.0", 35 | "grunt-lintspaces": "~0.7.3", 36 | "grunt-mocha-test": "0.12.7", 37 | "load-grunt-tasks": "^3.5.0", 38 | "mocha": "^2.4.5" 39 | }, 40 | "peerDependencies": { 41 | "grunt": ">=0.4.0" 42 | }, 43 | "scripts": { 44 | "test": "grunt" 45 | }, 46 | "keywords": [ 47 | "help", 48 | "gruntplugin", 49 | "list", 50 | "tasks" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexgs/grunt-available-tasks/faf0c76196cde8b3ed377fed2c2fae3ec1c33f6b/screenshot.png -------------------------------------------------------------------------------- /tasks/available_tasks.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-available-tasks 3 | * https://github.com/ben-eb/grunt-available-tasks 4 | * 5 | * Copyright (c) 2013-2015 Ben Briggs 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var filterTasks = require('../lib/filterTasks'), 12 | getOutput = require('../lib/get_output'), 13 | reporter = require('../lib/reporters'), 14 | ids = require('../lib/taskIdentifiers'), 15 | _ = require('lodash'); 16 | 17 | module.exports = function (grunt) { 18 | grunt.registerMultiTask('availabletasks', 'List available Grunt tasks & targets.', function () { 19 | var output = [], 20 | header = '', 21 | options = this.options({ 22 | filter : false, 23 | tasks : false, 24 | sort : true, 25 | hideUngrouped : false, 26 | groups : {}, 27 | descriptions : {}, 28 | showTasks : ['single', 'multi', 'user'], 29 | reporter : 'default' 30 | }), 31 | // Delete tasks that don't pass a filter 32 | tasks = filterTasks(options.filter, options.tasks, grunt.task._tasks); 33 | 34 | // Override descriptions with our own values 35 | Object.keys(options.descriptions).forEach(function (description) { 36 | var task = _.find(tasks, { name : description }); 37 | if (task) { 38 | task.info = options.descriptions[description]; 39 | } 40 | }); 41 | // Sort the tasks by name if sorting is enabled 42 | if (options.sort) { 43 | tasks = _.sortBy(tasks, 'name'); 44 | } 45 | // Did we define a custom sort? 46 | if (options.sort instanceof Array) { 47 | tasks = _.sortBy(tasks, function (task) { 48 | var index = options.sort.indexOf(task.name); 49 | return (!~index) ? options.sort.length : index; 50 | }); 51 | } 52 | _.each(tasks, function (task) { 53 | var name = task.name, 54 | config = grunt.config.getRaw(name), 55 | targets = [], 56 | type = ids.user; 57 | // test if the task is a local config or something installed 58 | if (~task.meta.info.indexOf('local Npm module')) { 59 | type = (task.multi) ? ids.multi : ids.single; 60 | } 61 | // Delete global options from the task targets 62 | if (typeof config === 'object' && task.multi) { 63 | delete config.options; 64 | targets = Object.keys(config); 65 | } 66 | // Get the output of the task 67 | var allowedTypes = _.map(Object.keys(ids), function (id) { 68 | if (_.includes(options.showTasks, id)) { 69 | return ids[id]; 70 | } 71 | }); 72 | if (_.includes(allowedTypes, type)) { 73 | getOutput(output, options.groups, { 74 | name : task.name, 75 | type : type, 76 | info : task.info, 77 | targets : targets 78 | }, options.hideUngrouped); 79 | } 80 | }); 81 | _.chain(output) 82 | .sortBy(function (value) { 83 | return (value.group === 'Ungrouped') ? 1 : 0; 84 | }) 85 | .groupBy('group') 86 | .each(function (group) { 87 | header = group.group; 88 | _.each(group, function (o) { 89 | var reportoptions = { 90 | currentTask : o, 91 | meta : { 92 | taskCount : Object.keys(tasks).length, 93 | groupCount : Object.keys(options.groups).length, 94 | header : header !== '', 95 | longest : _.maxBy(tasks, function (task) { 96 | return task.name.length; 97 | }).name.length 98 | } 99 | }; 100 | header = ''; 101 | var reportFn = (typeof options.reporter === 'function') ? options.reporter : reporter[options.reporter]; 102 | reportFn.call(this, reportoptions); 103 | }); 104 | }) 105 | .value(); 106 | }); 107 | }; 108 | -------------------------------------------------------------------------------- /test/lib/filterTasks.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach */ 2 | 3 | 'use strict'; 4 | 5 | var expect = require('chai').expect, 6 | filterTasks = require('../../lib/filterTasks'), 7 | tasks; 8 | 9 | describe('filterTasks', function () { 10 | 11 | beforeEach(function() { 12 | tasks = [{ 13 | name : 'available' 14 | }, { 15 | name : 'tasks' 16 | }, { 17 | name : 'is' 18 | }, { 19 | name : 'being' 20 | }, { 21 | name : 'tested' 22 | }]; 23 | }); 24 | 25 | it('should pass through if no filter was specified', function() { 26 | expect(filterTasks(false, false, tasks)).to.equal(tasks); 27 | }); 28 | 29 | it('should pass through if a filter was specified but no tasks were in the list', function() { 30 | expect(filterTasks(false, [], tasks)).to.equal(tasks); 31 | }); 32 | 33 | it('should exclude tasks from the filter list', function() { 34 | // Check that the task was removed from the array 35 | expect(filterTasks('exclude', ['being'], tasks).length).to.equal(4); 36 | }); 37 | 38 | it('should include tasks from the filter list', function() { 39 | // Check that all tasks but this task was removed from the array 40 | expect(filterTasks('include', ['tested'], tasks).length).to.equal(1); 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /test/lib/get_output.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach */ 2 | 3 | 'use strict'; 4 | 5 | var expect = require('chai').expect, 6 | getOutput = require('../../lib/get_output'); 7 | 8 | describe('getOutput', function () { 9 | 10 | beforeEach(function() { 11 | this.output = []; 12 | this.task = { 13 | name : 'mocha' 14 | }; 15 | }); 16 | 17 | it('handles the case when there are no groups', function () { 18 | getOutput(this.output, {}, this.task); 19 | expect(this.output).to.have.length(1); 20 | expect(this.output[0].name).to.eql('mocha'); 21 | }); 22 | 23 | it('handles the case where there are groups', function () { 24 | var groups = { 25 | 'Lint tools' : ['jshint', 'mocha'] 26 | }; 27 | getOutput(this.output, groups, this.task); 28 | getOutput(this.output, groups, { 29 | name : 'jshint' 30 | }); 31 | 32 | expect(this.output).to.have.length(2); 33 | expect(this.output[1].group).to.eql('Lint tools'); 34 | }); 35 | }); 36 | --------------------------------------------------------------------------------