├── .gitignore ├── LICENSE ├── README.md ├── bin └── velocity ├── index.js ├── lib ├── getPlatform.js ├── hasArgument.js ├── isCommand.js ├── isWindows.js ├── replaceArgument.js ├── replaceCommand.js ├── run.js ├── spawnMeteor.js └── spawnTestPackagesMeteor.js ├── package.json └── spec ├── getPlatform.js ├── hasArgumentSpec.js ├── isCommandSpec.js ├── isWindowsSpec.js ├── replaceArgumentSpec.js ├── runSpec.js ├── spawnMeteorSpec.js ├── spawnTestPackagesMeteorSpec.js └── support └── jasmine.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Velocity 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # velocity-cli 2 | 3 | CLI tool for using Meteor Velocity to test your Meteor apps. 4 | 5 | ## Install 6 | 7 | ```sh 8 | npm install velocity-cli -g 9 | ``` 10 | 11 | ## Testing an app 12 | 13 | ### Watch mode 14 | 15 | This command runs the tests each time you change something. 16 | 17 | ```sh 18 | velocity test-app 19 | ``` 20 | 21 | Note: This command also starts your app right now. 22 | 23 | ### Continuous Integration mode 24 | 25 | This command runs the tests only once and then exits with the status code. 26 | 27 | ```sh 28 | velocity test-app --ci 29 | ``` 30 | 31 | ### More options 32 | 33 | The command supports all options that `meteor run` supports. You can get a full list with `meteor help run`. 34 | 35 | 36 | ## Testing a package 37 | 38 | ### Watch mode 39 | 40 | This command runs the tests each time you change something. 41 | 42 | ```sh 43 | velocity test-package my-package 44 | ``` 45 | 46 | ### Continuous Integration mode 47 | 48 | This command runs the tests only once and then exits with the status code. 49 | 50 | ```sh 51 | velocity test-package my-package --ci 52 | ``` 53 | 54 | ### More options 55 | 56 | The command supports a lot more options. You can find a list [here](https://github.com/meteor/meteor/blob/release/METEOR%401.2.0.2/tools/cli/commands.js#L1425-L1473). 57 | 58 | ## Testing all packages in an app 59 | 60 | ```sh 61 | velocity test-packages 62 | ``` 63 | 64 | You can pass the same additional options as for `velocity test-package`. 65 | -------------------------------------------------------------------------------- /bin/velocity: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../index'); 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var run = require('./lib/run'); 3 | 4 | run({ 5 | args: process.argv.slice(2), 6 | env: _.clone(process.env) 7 | }); 8 | -------------------------------------------------------------------------------- /lib/getPlatform.js: -------------------------------------------------------------------------------- 1 | module.exports = function getPlatform() { 2 | return process.platform 3 | }; 4 | -------------------------------------------------------------------------------- /lib/hasArgument.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | module.exports = function hasArgument(args, argument) { 4 | return _.some(args, function (arg) { 5 | return _.startsWith(arg, argument); 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/isCommand.js: -------------------------------------------------------------------------------- 1 | module.exports = function isCommand(args, command) { 2 | return args[0] === command; 3 | }; 4 | -------------------------------------------------------------------------------- /lib/isWindows.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var getPlatform = require('./getPlatform'); 3 | 4 | module.exports = function isWindows() { 5 | return _.startsWith(getPlatform(), 'win'); 6 | }; 7 | -------------------------------------------------------------------------------- /lib/replaceArgument.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | module.exports = function replaceArgument(args, oldArgument, newArgument) { 4 | return _.map(args, function (arg) { 5 | return arg === oldArgument ? newArgument : arg; 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /lib/replaceCommand.js: -------------------------------------------------------------------------------- 1 | module.exports = function replaceCommand(args, oldCommand, newCommand) { 2 | if (args[0] === oldCommand) { 3 | return [newCommand].concat(args.slice(1)); 4 | } else { 5 | return args; 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /lib/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var hasArgument = require('./hasArgument'); 4 | var isCommand = require('./isCommand'); 5 | var replaceArgument = require('./replaceArgument'); 6 | var replaceCommand = require('./replaceCommand'); 7 | var spawnMeteor = require('./spawnMeteor'); 8 | var spawnTestPackagesMeteor = require('./spawnTestPackagesMeteor'); 9 | 10 | module.exports = function run(options) { 11 | var meteorArguments = options.args; 12 | var meteorEnvironment = options.env; 13 | 14 | if (meteorArguments.indexOf('--ci') !== -1) { 15 | meteorEnvironment.VELOCITY_CI = '1'; 16 | } 17 | 18 | if (isCommand(meteorArguments, 'test-app')) { 19 | meteorArguments = replaceCommand(meteorArguments, 'test-app', 'run'); 20 | meteorArguments = replaceArgument(meteorArguments, '--ci', '--test'); 21 | 22 | spawnMeteor({ 23 | args: meteorArguments, 24 | env: meteorEnvironment 25 | }); 26 | } else if (isCommand(meteorArguments, 'test-package') || 27 | isCommand(meteorArguments, 'test-packages') 28 | ) { 29 | meteorArguments = replaceCommand(meteorArguments, 'test-package', 'test-packages'); 30 | 31 | if (!meteorEnvironment.VELOCITY_USE_CHECKED_OUT_METEOR && !hasArgument(meteorArguments, '--release')) { 32 | meteorArguments.push('--release', 'velocity:METEOR@1.2.1_2'); 33 | } 34 | 35 | if (!hasArgument(meteorArguments, '--driver-package')) { 36 | meteorArguments.push('--driver-package', 'velocity:html-reporter'); 37 | } 38 | 39 | meteorArguments = replaceArgument(meteorArguments, '--ci', '--velocity'); 40 | 41 | spawnTestPackagesMeteor({ 42 | args: meteorArguments, 43 | env: meteorEnvironment 44 | }); 45 | } else { 46 | console.error('Velocity does not support this command.'); 47 | console.log('Supported commands:'); 48 | console.log(' * velocity test-app'); 49 | console.log(' * velocity test-package my-package'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/spawnMeteor.js: -------------------------------------------------------------------------------- 1 | var spawn = require('child_process').spawn; 2 | var isWindows = require('./isWindows'); 3 | 4 | module.exports = function spawnMeteor(options) { 5 | var meteorCommand = isWindows() ? 'meteor.bat' : 'meteor'; 6 | var meteorArguments = options.args || []; 7 | var meteorProcess = spawn(meteorCommand, meteorArguments, { 8 | cwd: process.cwd(), 9 | env: options.env || process.env, 10 | stdio: 'pipe' 11 | }); 12 | 13 | process.stdin.pipe(meteorProcess.stdin); 14 | meteorProcess.stdout.pipe(process.stdout); 15 | meteorProcess.stderr.pipe(process.stderr); 16 | 17 | meteorProcess.on('exit', function (code) { 18 | process.exit(code); 19 | }) 20 | 21 | return meteorProcess; 22 | }; 23 | -------------------------------------------------------------------------------- /lib/spawnTestPackagesMeteor.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var spawnMeteor = require('./spawnMeteor'); 3 | 4 | module.exports = function spawnTestPackagesMeteor(options) { 5 | _.extend(options.env, { 6 | VELOCITY_TEST_PACKAGES: '1' 7 | }); 8 | 9 | return spawnMeteor(options); 10 | }; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "velocity-cli", 3 | "version": "0.4.3", 4 | "description": "CLI tool for using Meteor Velocity to test your Meteor apps.", 5 | "main": "index.js", 6 | "bin": { 7 | "velocity": "bin/velocity" 8 | }, 9 | "scripts": { 10 | "test": "jasmine" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/meteor-velocity/velocity-cli.git" 15 | }, 16 | "keywords": [ 17 | "meteor", 18 | "velocity", 19 | "testing" 20 | ], 21 | "author": "Sanjo", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/meteor-velocity/velocity-cli/issues" 25 | }, 26 | "homepage": "https://github.com/meteor-velocity/velocity-cli", 27 | "dependencies": { 28 | "lodash": "^3.10.0" 29 | }, 30 | "devDependencies": { 31 | "jasmine": "^2.3.1", 32 | "proxyquire": "^1.6.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spec/getPlatform.js: -------------------------------------------------------------------------------- 1 | describe('getPlatform', function () { 2 | var getPlatform = require('../lib/getPlatform'); 3 | 4 | it('return process.platform', function () { 5 | expect(getPlatform()).toBe(process.platform); 6 | }); 7 | }) 8 | -------------------------------------------------------------------------------- /spec/hasArgumentSpec.js: -------------------------------------------------------------------------------- 1 | describe('hasArgument', function () { 2 | var hasArgument = require('../lib/hasArgument.js'); 3 | 4 | it('matches "--argument value"', function () { 5 | var args = ['--argument', 'value']; 6 | expect(hasArgument(args, '--argument')).toBe(true); 7 | }) 8 | 9 | it('matches "--argument=value"', function () { 10 | var args = ['--argument=value']; 11 | expect(hasArgument(args, '--argument')).toBe(true); 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /spec/isCommandSpec.js: -------------------------------------------------------------------------------- 1 | describe('isCommand', function () { 2 | var isCommand = require('../lib/isCommand'); 3 | 4 | beforeEach(function () { 5 | this.arguments = ['test-packages', '--arg', 'value']; 6 | }) 7 | 8 | describe('when the first argument is the command', function () { 9 | it('returns true', function () { 10 | expect(isCommand(this.arguments, 'test-packages')).toBe(true); 11 | }) 12 | }) 13 | 14 | describe('when the first argument is not the command', function () { 15 | it('returns false', function () { 16 | expect(isCommand(this.arguments, 'something')).toBe(false); 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /spec/isWindowsSpec.js: -------------------------------------------------------------------------------- 1 | describe('isWindows', function () { 2 | var proxyquire = require('proxyquire'); 3 | var isWindows; 4 | 5 | beforeEach(function () { 6 | this.getPlatformSpy = jasmine.createSpy('getPlatform'); 7 | 8 | isWindows = proxyquire('../lib/isWindows', { 9 | './getPlatform': this.getPlatformSpy 10 | }); 11 | }); 12 | 13 | describe('when the platform name starts with "win"', function () { 14 | beforeEach(function () { 15 | this.getPlatformSpy.and.returnValue('win32'); 16 | }) 17 | 18 | it('returns true', function () { 19 | expect(isWindows()).toBe(true); 20 | }) 21 | }) 22 | 23 | describe('when the platform name does not start with "win"', function () { 24 | beforeEach(function () { 25 | this.getPlatformSpy.and.returnValue('linux'); 26 | }) 27 | 28 | it('returns false', function () { 29 | expect(isWindows()).toBe(false); 30 | }) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /spec/replaceArgumentSpec.js: -------------------------------------------------------------------------------- 1 | describe('replaceArgument', function () { 2 | var replaceArgument = require('../lib/replaceArgument'); 3 | 4 | it('replaces the argument occurrences', function () { 5 | var args = ['--old', '--other', '--old']; 6 | expect(replaceArgument(args, '--old', '--new')).toEqual(['--new', '--other', '--new']); 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /spec/runSpec.js: -------------------------------------------------------------------------------- 1 | describe('index', function () { 2 | var proxyquire = require('proxyquire'); 3 | var _ = require('lodash'); 4 | var spawnMeteor; 5 | var spawnTestPackagesMeteor; 6 | var run; 7 | 8 | beforeEach(function () { 9 | spawnMeteor = jasmine.createSpy('spawnMeteor'); 10 | spawnTestPackagesMeteor = jasmine.createSpy('spawnTestPackagesMeteor'); 11 | run = proxyquire('../lib/run', { 12 | './spawnMeteor': spawnMeteor, 13 | './spawnTestPackagesMeteor': spawnTestPackagesMeteor 14 | }); 15 | }) 16 | 17 | describe('when no --release is specified', function () { 18 | it('adds the latest Velocity Meteor release as argument', function () { 19 | var args = ['test-package']; 20 | var env = {}; 21 | 22 | run({ 23 | args: args, 24 | env: env 25 | }); 26 | 27 | expect(spawnTestPackagesMeteor).toHaveBeenCalled(); 28 | var spawnOptions = spawnTestPackagesMeteor.calls.argsFor(0)[0]; 29 | var expectedArguments = ['--release', 'velocity:METEOR@1.2.1_2']; 30 | expect(_.intersection(spawnOptions.args.slice(1), expectedArguments)).toEqual(expectedArguments); 31 | }) 32 | }) 33 | 34 | describe('when the environment VELOCITY_USE_CHECKED_OUT_METEOR variable is set to truthy value', function () { 35 | it('does not add the latest Velocity Meteor release as argument', function () { 36 | var args = ['test-package']; 37 | var env = {VELOCITY_USE_CHECKED_OUT_METEOR: '1'}; 38 | 39 | run({ 40 | args: args, 41 | env: env 42 | }); 43 | 44 | expect(spawnTestPackagesMeteor).toHaveBeenCalled(); 45 | var spawnOptions = spawnTestPackagesMeteor.calls.argsFor(0)[0]; 46 | var expectedArguments = ['--release', 'velocity:METEOR@1.2.1_2']; 47 | expect(_.intersection(spawnOptions.args.slice(1), expectedArguments)).toEqual([]); 48 | }) 49 | }) 50 | 51 | describe('when no --driver-package is specified', function () { 52 | it('adds the velocity:html-reporter as driver-package', function () { 53 | var args = ['test-package']; 54 | var env = {}; 55 | 56 | run({ 57 | args: args, 58 | env: env 59 | }); 60 | 61 | expect(spawnTestPackagesMeteor).toHaveBeenCalled(); 62 | var spawnOptions = spawnTestPackagesMeteor.calls.argsFor(0)[0]; 63 | var expectedArguments = ['--driver-package', 'velocity:html-reporter']; 64 | expect(_.intersection(spawnOptions.args.slice(1), expectedArguments)).toEqual(expectedArguments); 65 | }) 66 | }) 67 | 68 | describe('test-package command', function () { 69 | describe('when --ci is passed as argument', function () { 70 | it('it calls meteor with --velocity (CI mode)', function () { 71 | var args = ['test-package', 'foo', '--ci']; 72 | var env = {}; 73 | 74 | run({ 75 | args: args, 76 | env: env 77 | }); 78 | 79 | expect(spawnTestPackagesMeteor).toHaveBeenCalled(); 80 | var spawnOptions = spawnTestPackagesMeteor.calls.argsFor(0)[0]; 81 | var meteorArgs = spawnOptions.args.slice(1); 82 | expect(_.contains(meteorArgs, '--velocity')).toBe(true); 83 | expect(_.contains(meteorArgs, '--ci')).toBe(false); 84 | }) 85 | 86 | it('sets the environment variable VELOCITY_CI=1', function () { 87 | var args = ['test-package', 'foo', '--ci']; 88 | var env = {}; 89 | 90 | run({ 91 | args: args, 92 | env: env 93 | }); 94 | 95 | expect(spawnTestPackagesMeteor).toHaveBeenCalled(); 96 | var spawnOptions = spawnTestPackagesMeteor.calls.argsFor(0)[0]; 97 | expect(spawnOptions.env.VELOCITY_CI).toBe('1'); 98 | }) 99 | }) 100 | 101 | describe('when --ci is not passed as argument', function () { 102 | it('does not set the environment variable VELOCITY_CI=1', function () { 103 | var args = ['test-package', 'foo']; 104 | var env = {}; 105 | 106 | run({ 107 | args: args, 108 | env: env 109 | }); 110 | 111 | expect(spawnTestPackagesMeteor).toHaveBeenCalled(); 112 | var spawnOptions = spawnTestPackagesMeteor.calls.argsFor(0)[0]; 113 | expect(spawnOptions.env.VELOCITY_CI).toBeUndefined(); 114 | }) 115 | }) 116 | }) 117 | 118 | describe('test-app command', function () { 119 | it('executes `meteor run --test`', function () { 120 | var args = ['test-app']; 121 | var env = {}; 122 | 123 | run({ 124 | args: args, 125 | env: env 126 | }); 127 | 128 | expect(spawnMeteor).toHaveBeenCalled(); 129 | var spawnOptions = spawnMeteor.calls.argsFor(0)[0]; 130 | var command = spawnOptions.args[0]; 131 | var meteorArgs = spawnOptions.args.slice(1); 132 | expect(command).toBe('run'); 133 | expect(meteorArgs).toEqual([]); 134 | }) 135 | 136 | describe('when --ci is passed as argument', function () { 137 | it('executes `meteor run --test`', function () { 138 | var args = ['test-app', '--ci']; 139 | var env = {}; 140 | 141 | run({ 142 | args: args, 143 | env: env 144 | }); 145 | 146 | expect(spawnMeteor).toHaveBeenCalled(); 147 | var spawnOptions = spawnMeteor.calls.argsFor(0)[0]; 148 | var command = spawnOptions.args[0]; 149 | var meteorArgs = spawnOptions.args.slice(1); 150 | expect(command).toBe('run'); 151 | expect(meteorArgs).toEqual(['--test']); 152 | }) 153 | 154 | it('sets the environment variable VELOCITY_CI=1', function () { 155 | var args = ['test-app', '--ci']; 156 | var env = {}; 157 | 158 | run({ 159 | args: args, 160 | env: env 161 | }); 162 | 163 | expect(spawnMeteor).toHaveBeenCalled(); 164 | var spawnOptions = spawnMeteor.calls.argsFor(0)[0]; 165 | expect(spawnOptions.env.VELOCITY_CI).toBe('1'); 166 | }) 167 | }) 168 | 169 | describe('when --ci is not passed as argument', function () { 170 | it('does not set the environment variable VELOCITY_CI=1', function () { 171 | var args = ['test-app']; 172 | var env = {}; 173 | 174 | run({ 175 | args: args, 176 | env: env 177 | }); 178 | 179 | expect(spawnMeteor).toHaveBeenCalled(); 180 | var spawnOptions = spawnMeteor.calls.argsFor(0)[0]; 181 | expect(spawnOptions.env.VELOCITY_CI).toBeUndefined(); 182 | }) 183 | }) 184 | }) 185 | }) 186 | -------------------------------------------------------------------------------- /spec/spawnMeteorSpec.js: -------------------------------------------------------------------------------- 1 | describe('spawnMeteor', function () { 2 | var proxyquire = require('proxyquire'); 3 | var spawnMeteor; 4 | var childProcessStub; 5 | 6 | beforeEach(function () { 7 | spyOn(process.stdin, 'pipe'); 8 | childProcessStub = { 9 | spawn: jasmine.createSpy('spawn').and.returnValue({ 10 | stdout: { 11 | pipe: jasmine.createSpy('child.stdout.pipe') 12 | }, 13 | stderr: { 14 | pipe: jasmine.createSpy('child.stderr.pipe') 15 | }, 16 | on: jasmine.createSpy('child.on') 17 | }), 18 | '@noCallThru': true 19 | }; 20 | spawnMeteor = proxyquire('../lib/spawnMeteor', { 21 | 'child_process': childProcessStub, 22 | './isWindows': function () { 23 | return false; 24 | } 25 | }); 26 | }) 27 | 28 | it('spawns meteor', function () { 29 | var args = []; 30 | var env = {}; 31 | 32 | spawnMeteor({ 33 | args: args, 34 | env: env 35 | }); 36 | 37 | expect(childProcessStub.spawn).toHaveBeenCalledWith('meteor', args, { 38 | cwd: process.cwd(), 39 | env: env, 40 | stdio: 'pipe' 41 | }); 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /spec/spawnTestPackagesMeteorSpec.js: -------------------------------------------------------------------------------- 1 | describe('spawnTestPackagesMeteor', function () { 2 | var proxyquire = require('proxyquire'); 3 | var spawnMeteor; 4 | var spawnTestPackagesMeteor; 5 | 6 | beforeEach(function () { 7 | spawnMeteor = jasmine.createSpy('spawnMeteor'); 8 | spawnTestPackagesMeteor = proxyquire('../lib/spawnTestPackagesMeteor', { 9 | './spawnMeteor': spawnMeteor 10 | }); 11 | }) 12 | 13 | it('spawns meteor with the passed options', function () { 14 | var options = {}; 15 | spawnTestPackagesMeteor(options); 16 | expect(spawnMeteor).toHaveBeenCalledWith(options); 17 | }) 18 | 19 | it('adds the environment variable VELOCITY_TEST_PACKAGES=1', function () { 20 | var env = {}; 21 | spawnTestPackagesMeteor({env: env}); 22 | expect(env.VELOCITY_TEST_PACKAGES).toBe('1'); 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ] 9 | } 10 | --------------------------------------------------------------------------------