UNPKG

ember-cli-ajh

Version:

Command line tool for developing ambitious ember.js apps

1,649 lines (1,322 loc) 62 kB
/*jshint multistr: true */ 'use strict'; var fs = require('fs-extra'); var Task = require('../../../lib/models/task'); var MockProject = require('../../helpers/mock-project'); var MockUI = require('../../helpers/mock-ui'); var processHelpString = require('../../helpers/process-help-string'); var expect = require('chai').expect; var path = require('path'); var glob = require('glob'); var walkSync = require('walk-sync'); var Promise = require('../../../lib/ext/promise'); var remove = Promise.denodeify(fs.remove); var EOL = require('os').EOL; var root = process.cwd(); var tmp = require('tmp-sync'); var tmproot = path.join(root, 'tmp'); var SilentError = require('silent-error'); var stub = require('../../helpers/stub').stub; var proxyquire = require('proxyquire'); var existsSync = require('exists-sync'); var MarkdownColor = require('../../../lib/utilities/markdown-color'); var assign = require('lodash/object/assign'); var existsSyncStub; var readdirSyncStub; var readFileSyncStub; var renderFileStub; var Blueprint = proxyquire('../../../lib/models/blueprint', { 'exists-sync': function() { return existsSyncStub.apply(this, arguments); }, 'fs-extra': { readdirSync: function() { return readdirSyncStub.apply(this, arguments); }, readFileSync: function() { return readFileSyncStub.apply(this, arguments); } }, '../utilities/markdown-color': function() { return { renderFile: function() { return renderFileStub.apply(this, arguments); } }; } }); var defaultBlueprints = path.resolve(__dirname, '..', '..', '..', 'blueprints'); var fixtureBlueprints = path.resolve(__dirname, '..', '..', 'fixtures', 'blueprints'); var basicBlueprint = path.join(fixtureBlueprints, 'basic'); var basicNewBlueprint = path.join(fixtureBlueprints, 'basic_2'); var defaultIgnoredFiles = Blueprint.ignoredFiles; var basicBlueprintFiles = [ '.ember-cli', '.gitignore', 'bar', 'foo.txt', 'test.txt' ]; describe('Blueprint', function() { beforeEach(function() { Blueprint.ignoredFiles = defaultIgnoredFiles; existsSyncStub = existsSync; readdirSyncStub = fs.readdirSync; readFileSyncStub = fs.readFileSync; renderFileStub = MarkdownColor.prototype.renderFile; }); describe('.mapFile', function() { it('replaces all occurences of __name__ with module name',function(){ var path = Blueprint.prototype.mapFile('__name__/__name__-controller.js',{dasherizedModuleName: 'my-blueprint'}); expect(path).to.equal('my-blueprint/my-blueprint-controller.js'); path = Blueprint.prototype.mapFile('__name__/controller.js',{dasherizedModuleName: 'my-blueprint'}); expect(path).to.equal('my-blueprint/controller.js'); path = Blueprint.prototype.mapFile('__name__/__name__.js',{dasherizedModuleName: 'my-blueprint'}); expect(path).to.equal('my-blueprint/my-blueprint.js'); }); it('accepts locals.fileMap with multiple mappings',function(){ var locals = {}; locals.fileMap= { __name__: 'user', __type__: 'controller', __path__: 'pods/users', __plural__: '' }; var path = Blueprint.prototype.mapFile('__name__/__type____plural__.js',locals); expect(path).to.equal('user/controller.js'); path = Blueprint.prototype.mapFile('__path__/__name__/__type__.js',locals); expect(path).to.equal('pods/users/user/controller.js'); }); }); describe('.fileMapTokens', function() { it('adds additional tokens from fileMapTokens hook', function() { var blueprint = Blueprint.lookup(basicBlueprint); blueprint.fileMapTokens = function() { return { __foo__: function(){ return 'foo'; } }; }; var tokens = blueprint._fileMapTokens(); expect(tokens.__foo__()).to.equal('foo'); }); }); describe('.generateFileMap', function() { it('should not have locals in the fileMap', function() { var blueprint = Blueprint.lookup(basicBlueprint); var fileMapVariables = { pod: true, podPath: 'pods', isAddon: false, blueprintName: 'test', dasherizedModuleName: 'foo-baz', locals: { SOME_LOCAL_ARG: 'ARGH' } }; var fileMap = blueprint.generateFileMap(fileMapVariables); var expected = { __name__: 'foo-baz', __path__: 'tests', __root__: 'app', __test__: 'foo-baz-test' }; expect(fileMap).to.deep.equal(expected); }); }); describe('.lookup', function() { it('uses an explicit path if one is given', function() { var expectedClass = require(basicBlueprint); var blueprint = Blueprint.lookup(basicBlueprint); expect(blueprint.name).to.equal('basic'); expect(blueprint.path).to.equal(basicBlueprint); expect(blueprint instanceof expectedClass).to.equal(true); }); it('finds blueprints within given lookup paths', function() { var expectedClass = require(basicBlueprint); var blueprint = Blueprint.lookup('basic', { paths: [fixtureBlueprints] }); expect(blueprint.name).to.equal('basic'); expect(blueprint.path).to.equal(basicBlueprint); expect(blueprint instanceof expectedClass).to.equal(true); }); it('finds blueprints in the ember-cli package', function() { var expectedPath = path.resolve(defaultBlueprints, 'app'); var expectedClass = Blueprint; var blueprint = Blueprint.lookup('app'); expect(blueprint.name).to.equal('app'); expect(blueprint.path).to.equal(expectedPath); expect(blueprint instanceof expectedClass).to.equal(true); }); it('can instantiate a blueprint that exports an object instead of a constructor', function() { var blueprint = Blueprint.lookup('exporting-object', { paths: [fixtureBlueprints] }); expect(blueprint.woot).to.equal('someValueHere'); expect(blueprint instanceof Blueprint).to.equal(true); }); it('throws an error if no blueprint is found', function() { expect(function() { Blueprint.lookup('foo'); }).to.throw('Unknown blueprint: foo'); }); it('returns undefined if no blueprint is found and ignoredMissing is passed', function() { var blueprint = Blueprint.lookup('foo', { ignoreMissing: true }); expect(blueprint).to.equal(undefined); }); }); describe('.list', function() { beforeEach(function() { existsSyncStub = function(path) { return path.indexOf('package.json') === -1; }; stub(Blueprint, 'defaultLookupPaths', []); stub(Blueprint, 'load', function(blueprintPath) { return { name: path.basename(blueprintPath) }; }, true); }); afterEach(function() { if (Blueprint.defaultLookupPaths.restore) { Blueprint.defaultLookupPaths.restore(); } if (Blueprint.load.restore) { Blueprint.load.restore(); } }); it('returns a list of blueprints grouped by lookup path', function() { readdirSyncStub = function() { return ['test1', 'test2']; }; var list = Blueprint.list({ paths: ['test0/blueprints'] }); expect(list[0]).to.deep.equal({ source: 'test0', blueprints: [ { name: 'test1', overridden: false }, { name: 'test2', overridden: false } ] }); }); it('overrides a blueprint of the same name from another package', function() { readdirSyncStub = function() { return ['test2']; }; var list = Blueprint.list({ paths: [ 'test0/blueprints', 'test1/blueprints' ] }); expect(list[0]).to.deep.equal({ source: 'test0', blueprints: [ { name: 'test2', overridden: false } ] }); expect(list[1]).to.deep.equal({ source: 'test1', blueprints: [ { name: 'test2', overridden: true } ] }); }); }); describe('help', function() { it('handles overridden', function() { var blueprint = new Blueprint('path/to/my-blueprint'); assign(blueprint, { overridden: true }); var output = blueprint.printBasicHelp(); var testString = processHelpString('\ \u001b[90m(overridden) my-blueprint\u001b[39m'); expect(output).to.equal(testString); }); it('handles all possible options', function() { var blueprint = new Blueprint('path/to/my-blueprint'); var availableOptions = [ { name: 'test-option', values: ['x', 'y'], default: 'my-def-val', required: true, aliases: ['a', { b: 'c' }], description: 'option desc' }, { name: 'test-type', type: Boolean, aliases: ['a'] } ]; assign(blueprint, { description: 'a paragraph', availableOptions: availableOptions, anonymousOptions: ['anon-test'], printDetailedHelp: function() { expect(arguments[0]).to.equal(availableOptions); return 'some details'; }, dontShowThis: 'test' }); var output = blueprint.printBasicHelp(true); var testString = processHelpString('\ my-blueprint \u001b[33m<anon-test>\u001b[39m \u001b[36m<options...>\u001b[39m' + EOL + '\ \u001b[90ma paragraph\u001b[39m' + EOL + '\ \u001b[36m--test-option\u001b[39m\u001b[36m=x|y\u001b[39m \u001b[36m(Default: my-def-val)\u001b[39m \u001b[36m(Required)\u001b[39m' + EOL + '\ \u001b[90maliases: -a <value>, -b (--test-option=c)\u001b[39m option desc' + EOL + '\ \u001b[36m--test-type\u001b[39m' + EOL + '\ \u001b[90maliases: -a\u001b[39m' + EOL + '\ some details'); expect(output).to.equal(testString); }); it('handles all possible options json', function() { var blueprint = new Blueprint('path/to/my-blueprint'); var availableOptions = [ { type: 'my-string-type', showAnything: true }, { type: function myFunctionType() {} } ]; assign(blueprint, { description: 'a paragraph', overridden: false, availableOptions: availableOptions, anonymousOptions: ['anon-test'], printDetailedHelp: function() { expect(arguments[0]).to.equal(availableOptions); return 'some details'; }, dontShowThis: true }); var json = blueprint.getJson(true); expect(json).to.deep.equal({ name: 'my-blueprint', description: 'a paragraph', overridden: false, availableOptions: [ { type: 'my-string-type', showAnything: true }, { type: 'myFunctionType' } ], anonymousOptions: ['anon-test'], detailedHelp: 'some details' }); }); it('handles the simplest blueprint, to test else skipping', function() { var blueprint = new Blueprint('path/to/my-blueprint'); var output = blueprint.printBasicHelp(); var testString = processHelpString('\ my-blueprint \u001b[33m<name>\u001b[39m'); expect(output).to.equal(testString); }); it('handles the simplest option, to test else skipping', function() { var blueprint = new Blueprint('path/to/my-blueprint'); assign(blueprint, { availableOptions: [{}] }); var output = blueprint.printBasicHelp(); var testString = processHelpString('\ my-blueprint \u001b[33m<name>\u001b[39m \u001b[36m<options...>\u001b[39m' + EOL + '\ \u001b[36m--undefined\u001b[39m'); expect(output).to.equal(testString); }); it('don\'t print prefix if option aliases is empty', function() { var blueprint = new Blueprint('path/to/my-blueprint'); assign(blueprint, { availableOptions: [ { aliases: [] } ] }); var output = blueprint.printBasicHelp(); var testString = processHelpString('\ my-blueprint \u001b[33m<name>\u001b[39m \u001b[36m<options...>\u001b[39m' + EOL + '\ \u001b[36m--undefined\u001b[39m'); expect(output).to.equal(testString); }); it('if blueprint nulls printDetailedHelp, don\'t call it, we should deprecate this', function() { var blueprint = new Blueprint('path/to/my-blueprint'); assign(blueprint, { printDetailedHelp: null }); var output = blueprint.printBasicHelp(true); var testString = processHelpString('\ my-blueprint \u001b[33m<name>\u001b[39m'); expect(output).to.equal(testString); }); it('if blueprint nulls printDetailedHelp, don\'t call it json, we should deprecate this', function() { var blueprint = new Blueprint('path/to/my-blueprint'); assign(blueprint, { printDetailedHelp: null }); var json = blueprint.getJson(true); expect(json).to.deep.equal({ name: 'my-blueprint', availableOptions: [], anonymousOptions: ['name'] }); }); it('if printDetailedHelp returns falsy, don\'t attach property detailedHelp', function() { var blueprint = new Blueprint('path/to/my-blueprint'); stub(blueprint, 'printDetailedHelp', ''); var json = blueprint.getJson(true); expect(blueprint.printDetailedHelp.called).to.equal(1); expect(json).to.not.have.property('detailedHelp'); }); it('handles extra help', function() { existsSyncStub = function() { return true; }; renderFileStub = function() { expect(arguments[1].indent).to.equal(' '); return 'test-file'; }; var blueprint = new Blueprint('path/to/my-blueprint'); var help = blueprint.printDetailedHelp(); expect(help).to.equal('test-file'); }); it('handles no extra help', function() { existsSyncStub = function() { return false; }; renderFileStub = function() { expect.fail(0, 1, 'should not call MarkdownColor.renderFile'); }; var blueprint = new Blueprint('path/to/my-blueprint'); var help = blueprint.printDetailedHelp(); expect(help).to.equal(''); }); }); it('exists', function() { var blueprint = new Blueprint(basicBlueprint); expect(!!blueprint).to.equal(true); }); it('derives name from path', function() { var blueprint = new Blueprint(basicBlueprint); expect(blueprint.name).to.equal('basic'); }); describe('filesPath', function() { it('returns the blueprints default files path', function() { var blueprint = new Blueprint(basicBlueprint); expect(blueprint.filesPath()).to.equal(path.join(basicBlueprint, 'files')); }); }); describe('basic blueprint installation', function() { var BasicBlueprintClass = require(basicBlueprint); var blueprint; var ui; var project; var options; var tmpdir; beforeEach(function() { tmpdir = tmp.in(tmproot); blueprint = new BasicBlueprintClass(basicBlueprint); ui = new MockUI(); project = new MockProject(); options = { ui: ui, project: project, target: tmpdir }; }); afterEach(function() { return remove(tmproot); }); it('installs basic files', function() { expect(!!blueprint).to.equal(true); return blueprint.install(options) .then(function() { var actualFiles = walkSync(tmpdir).sort(); var output = ui.output.trim().split(EOL); expect(output.shift()).to.match(/^installing/); expect(output.shift()).to.match(/create.* .ember-cli/); expect(output.shift()).to.match(/create.* .gitignore/); expect(output.shift()).to.match(/create.* bar/); expect(output.shift()).to.match(/create.* foo.txt/); expect(output.shift()).to.match(/create.* test.txt/); expect(output.length).to.equal(0); expect(actualFiles).to.deep.equal(basicBlueprintFiles); expect( function(){ fs.readFile(path.join(tmpdir , 'test.txt'), 'utf-8', function(err, content){ if(err){ throw 'error'; } expect(content).to.match(/I AM TESTY/); }); } ).not.to.throw(); }); }); it('re-installing identical files', function() { return blueprint.install(options) .then(function() { var output = ui.output.trim().split(EOL); ui.output = ''; expect(output.shift()).to.match(/^installing/); expect(output.shift()).to.match(/create.* .ember-cli/); expect(output.shift()).to.match(/create.* .gitignore/); expect(output.shift()).to.match(/create.* bar/); expect(output.shift()).to.match(/create.* foo.txt/); expect(output.shift()).to.match(/create.* test.txt/); expect(output.length).to.equal(0); return blueprint.install(options); }) .then(function() { var actualFiles = walkSync(tmpdir).sort(); var output = ui.output.trim().split(EOL); expect(output.shift()).to.match(/^installing/); expect(output.shift()).to.match(/identical.* .ember-cli/); expect(output.shift()).to.match(/identical.* .gitignore/); expect(output.shift()).to.match(/identical.* bar/); expect(output.shift()).to.match(/identical.* foo.txt/); expect(output.shift()).to.match(/identical.* test.txt/); expect(output.length).to.equal(0); expect(actualFiles).to.deep.equal(basicBlueprintFiles); }); }); it('re-installing conflicting files', function() { return blueprint.install(options) .then(function() { var output = ui.output.trim().split(EOL); ui.output = ''; expect(output.shift()).to.match(/^installing/); expect(output.shift()).to.match(/create.* .ember-cli/); expect(output.shift()).to.match(/create.* .gitignore/); expect(output.shift()).to.match(/create.* bar/); expect(output.shift()).to.match(/create.* foo.txt/); expect(output.shift()).to.match(/create.* test.txt/); expect(output.length).to.equal(0); var blueprintNew = new Blueprint(basicNewBlueprint); ui.waitForPrompt().then(function(){ ui.inputStream.write('n' + EOL); return ui.waitForPrompt(); }).then(function(){ ui.inputStream.write('y' + EOL); }); return blueprintNew.install(options); }) .then(function() { var actualFiles = walkSync(tmpdir).sort(); // Prompts contain \n EOL // Split output on \n since it will have the same affect as spliting on OS specific EOL var output = ui.output.trim().split('\n'); expect(output.shift()).to.match(/^installing/); expect(output.shift()).to.match(/Overwrite.*foo.*\?/); // Prompt expect(output.shift()).to.match(/Overwrite.*foo.*No, skip/); expect(output.shift()).to.match(/Overwrite.*test.*\?/); // Prompt expect(output.shift()).to.match(/Overwrite.*test.*Yes, overwrite/); expect(output.shift()).to.match(/identical.* \.ember-cli/); expect(output.shift()).to.match(/identical.* \.gitignore/); expect(output.shift()).to.match(/skip.* foo.txt/); expect(output.shift()).to.match(/overwrite.* test.txt/); expect(output.length).to.equal(0); expect(actualFiles).to.deep.equal(basicBlueprintFiles); }); }); it('installs path globPattern file', function() { options.targetFiles = ['foo.txt']; return blueprint.install(options) .then(function() { var actualFiles = walkSync(tmpdir).sort(); var globFiles = glob.sync(path.join('**', 'foo.txt'), { cwd: tmpdir, dot: true, mark: true, strict: true }).sort(); var output = ui.output.trim().split(EOL); expect(output.shift()).to.match(/^installing/); expect(output.shift()).to.match(/create.* foo.txt/); expect(output.length).to.equal(0); expect(actualFiles).to.deep.equal(globFiles); }); }); it('installs multiple globPattern files', function() { options.targetFiles = ['foo.txt','test.txt']; return blueprint.install(options) .then(function() { var actualFiles = walkSync(tmpdir).sort(); var globFiles = glob.sync(path.join('**', '*.txt'), { cwd: tmpdir, dot: true, mark: true, strict: true }).sort(); var output = ui.output.trim().split(EOL); expect(output.shift()).to.match(/^installing/); expect(output.shift()).to.match(/create.* foo.txt/); expect(output.shift()).to.match(/create.* test.txt/); expect(output.length).to.equal(0); expect(actualFiles).to.deep.equal(globFiles); }); }); describe('called on an existing project', function() { beforeEach(function() { Blueprint.ignoredUpdateFiles.push('foo.txt'); }); it('ignores files in ignoredUpdateFiles', function() { return blueprint.install(options) .then(function() { var output = ui.output.trim().split(EOL); ui.output = ''; expect(output.shift()).to.match(/^installing/); expect(output.shift()).to.match(/create.* .ember-cli/); expect(output.shift()).to.match(/create.* .gitignore/); expect(output.shift()).to.match(/create.* bar/); expect(output.shift()).to.match(/create.* foo.txt/); expect(output.shift()).to.match(/create.* test.txt/); expect(output.length).to.equal(0); var blueprintNew = new Blueprint(basicNewBlueprint); ui.waitForPrompt().then(function(){ ui.inputStream.write('n' + EOL); return ui.waitForPrompt(); }).then(function(){ ui.inputStream.write('n' + EOL); }); options.project.isEmberCLIProject = function() { return true; }; return blueprintNew.install(options); }) .then(function() { var actualFiles = walkSync(tmpdir).sort(); // Prompts contain \n EOL // Split output on \n since it will have the same affect as spliting on OS specific EOL var output = ui.output.trim().split('\n'); expect(output.shift()).to.match(/^installing/); expect(output.shift()).to.match(/Overwrite.*test.*\?/); // Prompt expect(output.shift()).to.match(/Overwrite.*test.*No, skip/); expect(output.shift()).to.match(/identical.* \.ember-cli/); expect(output.shift()).to.match(/identical.* \.gitignore/); expect(output.shift()).to.match(/skip.* test.txt/); expect(output.length).to.equal(0); expect(actualFiles).to.deep.equal(basicBlueprintFiles); }); }); }); it('throws error when there is a trailing forward slash in entityName', function(){ options.entity = { name: 'foo/' }; expect(function() { blueprint.install(options); }).to.throw(/You specified "foo\/", but you can't use a trailing slash as an entity name with generators. Please re-run the command with "foo"./); options.entity = { name: 'foo\\' }; expect(function() { blueprint.install(options); }).to.throw(/You specified "foo\\", but you can't use a trailing slash as an entity name with generators. Please re-run the command with "foo"./); options.entity = { name: 'foo' }; expect(function() { blueprint.install(options); }).not.to.throw(); }); it('throws error when an entityName is not provided', function(){ options.entity = { }; expect(function() { blueprint.install(options); }).to.throw(SilentError, /The `ember generate <entity-name>` command requires an entity name to be specified./); }); it('throws error when an action does not exist', function() { blueprint._actions = {}; return blueprint.install(options) .catch(function(err) { expect(err.message).to.equal('Tried to call action "write" but it does not exist'); }); }); it('calls normalizeEntityName hook during install', function(done){ blueprint.normalizeEntityName = function(){ done(); }; options.entity = { name: 'foo' }; blueprint.install(options); }); it('normalizeEntityName hook can modify the entity name', function(){ blueprint.normalizeEntityName = function(){ return 'foo'; }; options.entity = { name: 'bar' }; return blueprint.install(options) .then(function() { var actualFiles = walkSync(tmpdir).sort(); expect(actualFiles).to.deep.equal(basicBlueprintFiles); }); }); it('calls normalizeEntityName before locals hook is called', function(done) { blueprint.normalizeEntityName = function(){ return 'foo'; }; blueprint.locals = function(options) { expect(options.entity.name).to.equal('foo'); done(); }; options.entity = { name: 'bar' }; blueprint.install(options); }); }); describe('basic blueprint uninstallation', function() { var BasicBlueprintClass = require(basicBlueprint); var blueprint; var ui; var project; var options; var tmpdir; function refreshUI() { ui = new MockUI(); options.ui = ui; } beforeEach(function() { tmpdir = tmp.in(tmproot); blueprint = new BasicBlueprintClass(basicBlueprint); project = new MockProject(); options = { project: project, target: tmpdir }; refreshUI(); return blueprint.install(options) .then(refreshUI); }); afterEach(function() { return remove(tmproot); }); it('uninstalls basic files', function() { expect(!!blueprint).to.equal(true); return blueprint.uninstall(options) .then(function() { var actualFiles = walkSync(tmpdir); var output = ui.output.trim().split(EOL); expect(output.shift()).to.match(/^uninstalling/); expect(output.shift()).to.match(/remove.* .ember-cli/); expect(output.shift()).to.match(/remove.* .gitignore/); expect(output.shift()).to.match(/remove.* bar/); expect(output.shift()).to.match(/remove.* foo.txt/); expect(output.shift()).to.match(/remove.* test.txt/); expect(output.length).to.equal(0); expect(actualFiles.length).to.equal(0); fs.exists(path.join(tmpdir, 'test.txt'), function(exists) { expect(exists).to.be.false; }); }); }); }); describe('addPackageToProject', function() { var blueprint; var ui; var tmpdir; beforeEach(function() { tmpdir = tmp.in(tmproot); blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); }); afterEach(function() { return remove(tmproot); }); it('passes a packages array for addPackagesToProject', function() { blueprint.addPackagesToProject = function(packages) { expect(packages).to.deep.equal([{name: 'foo-bar'}]); }; blueprint.addPackageToProject('foo-bar'); }); it('passes a packages array with target for addPackagesToProject', function() { blueprint.addPackagesToProject = function(packages) { expect(packages).to.deep.equal([{name: 'foo-bar', target: '^123.1.12'}]); }; blueprint.addPackageToProject('foo-bar', '^123.1.12'); }); }); describe('addPackagesToProject', function() { var blueprint; var ui; var tmpdir; var NpmInstallTask; var taskNameLookedUp; beforeEach(function() { tmpdir = tmp.in(tmproot); blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); blueprint.taskFor = function(name) { taskNameLookedUp = name; return new NpmInstallTask(); }; }); afterEach(function() { return remove(tmproot); }); it('looks up the `npm-install` task', function() { NpmInstallTask = Task.extend({ run: function() {} }); blueprint.addPackagesToProject([{name: 'foo-bar'}]); expect(taskNameLookedUp).to.equal('npm-install'); }); it('calls the task with package names', function() { var packages; NpmInstallTask = Task.extend({ run: function(options) { packages = options.packages; } }); blueprint.addPackagesToProject([ {name: 'foo-bar'}, {name: 'bar-foo'} ]); expect(packages).to.deep.equal(['foo-bar', 'bar-foo']); }); it('calls the task with package names and versions', function() { var packages; NpmInstallTask = Task.extend({ run: function(options) { packages = options.packages; } }); blueprint.addPackagesToProject([ {name: 'foo-bar', target: '^123.1.12'}, {name: 'bar-foo', target: '0.0.7'} ]); expect(packages).to.deep.equal(['foo-bar@^123.1.12', 'bar-foo@0.0.7']); }); it('writes information to the ui log for a single package', function() { blueprint.ui = ui; blueprint.addPackagesToProject([ {name: 'foo-bar', target: '^123.1.12'} ]); var output = ui.output.trim(); expect(output).to.match(/install package.*foo-bar/); }); it('writes information to the ui log for multiple packages', function() { blueprint.ui = ui; blueprint.addPackagesToProject([ {name: 'foo-bar', target: '^123.1.12'}, {name: 'bar-foo', target: '0.0.7'} ]); var output = ui.output.trim(); expect(output).to.match(/install packages.*foo-bar, bar-foo/); }); it('does not error if ui is not present', function() { delete blueprint.ui; blueprint.addPackagesToProject([ {name: 'foo-bar', target: '^123.1.12'} ]); var output = ui.output.trim(); expect(output).to.not.match(/install package.*foo-bar/); }); it('runs task with --save-dev', function() { var saveDev; NpmInstallTask = Task.extend({ run: function(options) { saveDev = options['save-dev']; } }); blueprint.addPackagesToProject([ {name: 'foo-bar', target: '^123.1.12'}, {name: 'bar-foo', target: '0.0.7'} ]); expect(!!saveDev).to.equal(true); }); it('does not use verbose mode with the task', function() { var verbose; NpmInstallTask = Task.extend({ run: function(options) { verbose = options.verbose; } }); blueprint.addPackagesToProject([ {name: 'foo-bar', target: '^123.1.12'}, {name: 'bar-foo', target: '0.0.7'} ]); expect(verbose).to.equal(false); }); }); describe('removePackageFromProject', function() { var blueprint; var ui; var tmpdir; var NpmUninstallTask; var taskNameLookedUp; beforeEach(function() { tmpdir = tmp.in(tmproot); blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); blueprint.taskFor = function(name) { taskNameLookedUp = name; return new NpmUninstallTask(); }; }); afterEach(function() { return remove(tmproot); }); it('looks up the `npm-uninstall` task', function() { NpmUninstallTask = Task.extend({ run: function() {} }); blueprint.removePackageFromProject({name: 'foo-bar'}); expect(taskNameLookedUp).to.equal('npm-uninstall'); }); }); describe('removePackagesFromProject', function() { var blueprint; var ui; var tmpdir; var NpmUninstallTask; var taskNameLookedUp; beforeEach(function() { tmpdir = tmp.in(tmproot); blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); blueprint.taskFor = function(name) { taskNameLookedUp = name; return new NpmUninstallTask(); }; }); afterEach(function() { return remove(tmproot); }); it('looks up the `npm-uninstall` task', function() { NpmUninstallTask = Task.extend({ run: function() {} }); blueprint.removePackagesFromProject([{name: 'foo-bar'}]); expect(taskNameLookedUp).to.equal('npm-uninstall'); }); it('calls the task with package names', function() { var packages; NpmUninstallTask = Task.extend({ run: function(options) { packages = options.packages; } }); blueprint.removePackagesFromProject([ {name: 'foo-bar'}, {name: 'bar-foo'} ]); expect(packages).to.deep.equal(['foo-bar', 'bar-foo']); }); it('writes information to the ui log for a single package', function() { blueprint.ui = ui; blueprint.removePackagesFromProject([ {name: 'foo-bar'} ]); var output = ui.output.trim(); expect(output).to.match(/uninstall package.*foo-bar/); }); it('writes information to the ui log for multiple packages', function() { blueprint.ui = ui; blueprint.removePackagesFromProject([ {name: 'foo-bar'}, {name: 'bar-foo'} ]); var output = ui.output.trim(); expect(output).to.match(/uninstall packages.*foo-bar, bar-foo/); }); it('does not error if ui is not present', function() { delete blueprint.ui; blueprint.removePackagesFromProject([ {name: 'foo-bar'} ]); var output = ui.output.trim(); expect(output).to.not.match(/uninstall package.*foo-bar/); }); it('runs task with --save-dev', function() { var saveDev; NpmUninstallTask = Task.extend({ run: function(options) { saveDev = options['save-dev']; } }); blueprint.removePackagesFromProject([ {name: 'foo-bar'}, {name: 'bar-foo'} ]); expect(!!saveDev).to.equal(true); }); it('does not use verbose mode with the task', function() { var verbose; NpmUninstallTask = Task.extend({ run: function(options) { verbose = options.verbose; } }); blueprint.removePackagesFromProject([ {name: 'foo-bar'}, {name: 'bar-foo'} ]); expect(verbose).to.equal(false); }); }); describe('addBowerPackageToProject', function() { var blueprint; var ui; var tmpdir; var BowerInstallTask; var taskNameLookedUp; beforeEach(function() { tmpdir = tmp.in(tmproot); blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); blueprint.ui = ui; blueprint.taskFor = function(name) { taskNameLookedUp = name; return new BowerInstallTask(); }; }); afterEach(function() { return remove(tmproot); }); it('passes a packages array for addBowerPackagesToProject', function() { blueprint.addBowerPackagesToProject = function(packages) { expect(packages).to.deep.equal([{name: 'foo-bar', source: 'foo-bar', target: '*'}]); }; blueprint.addBowerPackageToProject('foo-bar'); }); it('passes a packages array with target for addBowerPackagesToProject', function() { blueprint.addBowerPackagesToProject = function(packages) { expect(packages).to.deep.equal([{name: 'foo-bar', source: 'foo-bar', target: '1.0.0'}]); }; blueprint.addBowerPackageToProject('foo-bar', '1.0.0'); }); it('correctly handles local package naming, with a numbered pkg version', function() { blueprint.addBowerPackagesToProject = function(packages) { expect(packages).to.deep.equal([{name: 'foo-bar-local', target: '1.0.0', source: 'foo-bar'}]); }; blueprint.addBowerPackageToProject('foo-bar-local', 'foo-bar#1.0.0'); }); it('correctly handles local package naming, with a non-versioned package', function() { blueprint.addBowerPackagesToProject = function(packages) { expect(packages).to.deep.equal([{name: 'foo-bar-local', target: '*', source: 'http://twitter.github.io/bootstrap/assets/bootstrap'}]); }; blueprint.addBowerPackageToProject('foo-bar-local', 'http://twitter.github.io/bootstrap/assets/bootstrap'); }); it('correctly handles a single versioned package descriptor as argument (1) (DEPRECATED)', function() { blueprint.ui = ui; blueprint.addBowerPackagesToProject = function(packages) { expect(packages).to.deep.equal([{name: 'foo-bar', target: '1.11.1', source: 'foo-bar'}]); }; blueprint.addBowerPackageToProject('foo-bar#1.11.1'); }); }); describe('addBowerPackagesToProject', function() { var blueprint; var ui; var tmpdir; var BowerInstallTask; var taskNameLookedUp; beforeEach(function() { tmpdir = tmp.in(tmproot); blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); blueprint.taskFor = function(name) { taskNameLookedUp = name; return new BowerInstallTask(); }; }); afterEach(function() { return remove(tmproot); }); it('looks up the `bower-install` task', function() { BowerInstallTask = Task.extend({ run: function() {} }); blueprint.addBowerPackagesToProject([{name: 'foo-bar'}]); expect(taskNameLookedUp).to.equal('bower-install'); }); it('calls the task with the package names', function() { var packages; BowerInstallTask = Task.extend({ run: function(options) { packages = options.packages; } }); blueprint.addBowerPackagesToProject([ {name: 'foo-bar'}, {name: 'bar-foo'} ]); expect(packages).to.deep.equal(['foo-bar=foo-bar', 'bar-foo=bar-foo']); }); it('uses the provided target (version, range, sha, etc)', function() { var packages; BowerInstallTask = Task.extend({ run: function(options) { packages = options.packages; } }); blueprint.addBowerPackagesToProject([ {name: 'foo-bar', target: '~1.0.0'}, {name: 'bar-foo', target: '0.7.0'} ]); expect(packages).to.deep.equal(['foo-bar=foo-bar#~1.0.0', 'bar-foo=bar-foo#0.7.0']); }); it('properly parses a variety of bower package endpoints', function() { var packages; BowerInstallTask = Task.extend({ run: function(options) { packages = options.packages; } }); blueprint.addBowerPackagesToProject([ {name: '', source: 'jquery', target: '~2.0.0'}, {name: 'backbone', source: 'backbone-amd', target: '~1.0.0'}, {name: 'bootstrap', source: 'http://twitter.github.io/bootstrap/assets/bootstrap', target: '*'} ]); expect(packages).to.deep.equal([ // standard local name, versioned bower pkg 'jquery#~2.0.0', // custom local name, versioned bower pkg 'backbone=backbone-amd#~1.0.0', // no numbered version, custom local name 'bootstrap=http://twitter.github.io/bootstrap/assets/bootstrap' ]); }); it('uses uses verbose mode with the task', function() { var verbose; BowerInstallTask = Task.extend({ run: function(options) { verbose = options.verbose; } }); blueprint.addBowerPackagesToProject([ {name: 'foo-bar', target: '~1.0.0'}, {name: 'bar-foo', target: '0.7.0'} ]); expect(verbose).to.equal(true); }); }); describe('addAddonToProject', function() { var blueprint; var ui; var tmpdir; beforeEach(function() { tmpdir = tmp.in(tmproot); blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); }); afterEach(function() { return remove(tmproot); }); it('passes a packages array for addAddonsToProject', function() { blueprint.addAddonsToProject = function(options) { expect(options.packages).to.deep.equal(['foo-bar']); }; blueprint.addAddonToProject('foo-bar'); }); it('passes a packages array with target for addAddonsToProject', function() { blueprint.addAddonsToProject = function(options) { expect(options.packages).to.deep.equal([{name: 'foo-bar', target: '^123.1.12'}]); }; blueprint.addAddonToProject({name: 'foo-bar', target: '^123.1.12'}); }); }); describe('addAddonsToProject', function() { var blueprint; var ui; var tmpdir; var AddonInstallTask; var taskNameLookedUp; beforeEach(function() { tmpdir = tmp.in(tmproot); blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); blueprint.taskFor = function(name) { taskNameLookedUp = name; return new AddonInstallTask(); }; }); afterEach(function() { return remove(tmproot); }); it('looks up the `addon-install` task', function() { AddonInstallTask = Task.extend({ run: function() {} }); blueprint.addAddonsToProject({ packages: ['foo-bar'] }); expect(taskNameLookedUp).to.equal('addon-install'); }); it('calls the task with package name', function() { var pkg; AddonInstallTask = Task.extend({ run: function(options) { pkg = options['packages']; } }); blueprint.addAddonsToProject({ packages: ['foo-bar', 'baz-bat'] }); expect(pkg).to.deep.equal(['foo-bar', 'baz-bat']); }); it('calls the task with correctly parsed options', function() { var pkg, args, bluOpts; AddonInstallTask = Task.extend({ run: function(options) { pkg = options['packages']; args = options['extraArgs']; bluOpts = options['blueprintOptions']; } }); blueprint.addAddonsToProject({ packages: [ { name: 'foo-bar', target: '1.0.0' }, 'stuff-things', 'baz-bat@0.0.1' ], extraArgs: ['baz'], blueprintOptions: '-foo' }); expect(pkg).to.deep.equal(['foo-bar@1.0.0', 'stuff-things', 'baz-bat@0.0.1']); expect(args).to.deep.equal(['baz']); expect(bluOpts).to.equal('-foo'); }); it('writes information to the ui log for a single package', function() { blueprint.ui = ui; blueprint.addAddonsToProject({ packages: [{ name: 'foo-bar', target: '^123.1.12' }] }); var output = ui.output.trim(); expect(output).to.match(/install addon.*foo-bar/); }); it('writes information to the ui log for multiple packages', function() { blueprint.ui = ui; blueprint.addAddonsToProject({ packages: [ { name: 'foo-bar', target: '1.0.0' }, 'stuff-things', 'baz-bat@0.0.1' ] }); var output = ui.output.trim(); expect(output).to.match(/install addons.*foo-bar@1.0.0,.*stuff-things,.*baz-bat@0.0.1/); }); it('does not error if ui is not present', function() { delete blueprint.ui; blueprint.addAddonsToProject({ packages: [{ name: 'foo-bar', target: '^123.1.12' }] }); var output = ui.output.trim(); expect(output).to.not.match(/install addon.*foo-bar/); }); }); describe('load', function(){ var blueprint; it('loads and returns a blueprint object', function() { blueprint = Blueprint.load(basicBlueprint); expect(blueprint).to.be.an('object'); expect(blueprint.name).to.equal('basic'); }); it('loads only blueprints with an index.js', function() { expect(Blueprint.load(path.join(fixtureBlueprints, '.notablueprint'))).to.be.empty; }); }); describe('insertIntoFile', function() { var blueprint; var ui; var tmpdir; var project; var filename; beforeEach(function() { tmpdir = tmp.in(tmproot); blueprint = new Blueprint(basicBlueprint); ui = new MockUI(); project = new MockProject(); // normally provided by `install`, but mocked here for testing project.root = tmpdir; blueprint.project = project; filename = 'foo-bar-baz.txt'; }); afterEach(function() { return remove(tmproot); }); it('will create the file if not already existing', function() { var toInsert = 'blahzorz blammo'; return blueprint.insertIntoFile(filename, toInsert) .then(function(result) { var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' }); expect(contents.indexOf(toInsert) > -1).to.equal(true, 'contents were inserted'); expect(result.originalContents).to.equal('', 'returned object should contain original contents'); expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified'); expect(contents).to.equal(result.contents, 'returned object should contain contents'); }); }); it('will insert into the file if it already exists', function() { var toInsert = 'blahzorz blammo'; var originalContent = 'some original content\n'; var filePath = path.join(project.root, filename); fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' }); return blueprint.insertIntoFile(filename, toInsert) .then(function(result) { var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' }); expect(contents).to.equal(originalContent + toInsert, 'inserted contents should be appended to original'); expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents'); expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified'); }); }); it('will not insert into the file if it already contains the content', function() { var toInsert = 'blahzorz blammo'; var filePath = path.join(project.root, filename); fs.writeFileSync(filePath, toInsert, { encoding: 'utf8' }); return blueprint.insertIntoFile(filename, toInsert) .then(function(result) { var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' }); expect(contents).to.equal(toInsert, 'contents should be unchanged'); expect(result.inserted).to.equal(false, 'inserted should indicate that the file was not modified'); }); }); it('will insert into the file if it already contains the content if force option is passed', function() { var toInsert = 'blahzorz blammo'; var filePath = path.join(project.root, filename); fs.writeFileSync(filePath, toInsert, { encoding: 'utf8' }); return blueprint.insertIntoFile(filename, toInsert, { force: true }) .then(function(result) { var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' }); expect(contents).to.equal(toInsert + toInsert, 'contents should be unchanged'); expect(result.inserted).to.equal(true, 'inserted should indicate that the file was not modified'); }); }); it('will insert into the file after a specified string if options.after is specified', function(){ var toInsert = 'blahzorz blammo'; var line1 = 'line1 is here'; var line2 = 'line2 here'; var line3 = 'line3'; var originalContent = [line1, line2, line3].join(EOL); var filePath = path.join(project.root, filename); fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' }); return blueprint.insertIntoFile(filename, toInsert, {after: line2 + EOL}) .then(function(result) { var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' }); expect(contents).to.equal([line1, line2, toInsert, line3].join(EOL), 'inserted contents should be inserted after the `after` value'); expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents'); expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified'); }); }); it('will insert into the file after the first instance of options.after only', function(){ var toInsert = 'blahzorz blammo'; var line1 = 'line1 is here'; var line2 = 'line2 here'; var line3 = 'line3'; var originalContent = [line1, line2, line2, line3].join(EOL); var filePath = path.join(project.root, filename); fs.writeFile