Merge pull request #13 from raphinesse/cleanup
Major code cleanup
diff --git a/index.js b/index.js
index aac2f38..61181ca 100644
--- a/index.js
+++ b/index.js
@@ -17,24 +17,27 @@
under the License.
*/
-var path = require('path');
var fs = require('fs');
+var os = require('os');
+var path = require('path');
+
+var Promise = require('q');
+var isUrl = require('is-url');
var shell = require('shelljs');
+var requireFresh = require('import-fresh');
+var validateIdentifier = require('valid-identifier');
+
+var fetch = require('cordova-fetch');
var events = require('cordova-common').events;
-var Q = require('q');
var CordovaError = require('cordova-common').CordovaError;
var ConfigParser = require('cordova-common').ConfigParser;
-var fetch = require('cordova-fetch');
-var url = require('url');
-var validateIdentifier = require('valid-identifier');
var CordovaLogger = require('cordova-common').CordovaLogger.get();
+const DEFAULT_VERSION = '1.0.0';
+
// Global configuration paths
-var global_config_path = process.env.CORDOVA_HOME;
-if (!global_config_path) {
- var HOME = process.env[(process.platform.slice(0, 3) === 'win') ? 'USERPROFILE' : 'HOME'];
- global_config_path = path.join(HOME, '.cordova');
-}
+var global_config_path = process.env.CORDOVA_HOME || path.join(os.homedir(), '.cordova');
+
/**
* Sets up to forward events to another instance, or log console.
* This will make the create internal events visible outside
@@ -64,7 +67,7 @@
**/
// Returns a promise.
module.exports = function (dir, optionalId, optionalName, cfg, extEvents) {
- return Q.fcall(function () {
+ return Promise.resolve().then(function () {
events = setupEvents(extEvents);
events.emit('verbose', 'Using detached cordova-create');
@@ -129,20 +132,10 @@
cfg.lib.www.url = cfg.lib.www.url || cfg.lib.www.uri;
if (!cfg.lib.www.url) {
- try {
- cfg.lib.www.url = require.resolve('cordova-app-hello-world');
- cfg.lib.www.template = true;
- } catch (e) {
- // Falling back on npm@2 path hierarchy
- // TODO: Remove fallback after cordova-app-hello-world release
- cfg.lib.www.url = path.join(__dirname, '..', '..', 'node_modules', 'cordova-app-hello-world');
- }
+ cfg.lib.www.url = require.resolve('cordova-app-hello-world');
+ cfg.lib.www.template = true;
}
- // TODO (kamrik): extend lazy_load for retrieval without caching to allow net urls for --src.
- cfg.lib.www.version = cfg.lib.www.version || 'not_versioned';
- cfg.lib.www.id = cfg.lib.www.id || 'dummy_id';
-
// Make sure that the source www/ is not a direct ancestor of the
// target www/, or else we will recursively copy forever. To do this,
// we make sure that the shortest relative path from source-to-target
@@ -163,36 +156,16 @@
// Finally, Ready to start!
events.emit('log', 'Creating a new cordova project.');
- // Strip link and url from cfg to avoid them being persisted to disk via .cordova/config.json.
- // TODO: apparently underscore has no deep clone. Replace with lodash or something. For now, abuse JSON.
- var cfgToPersistToDisk = JSON.parse(JSON.stringify(cfg));
-
- delete cfgToPersistToDisk.lib.www;
- if (Object.keys(cfgToPersistToDisk.lib).length === 0) {
- delete cfgToPersistToDisk.lib;
- }
-
- // Update cached version of config.json
- writeToConfigJson(dir, cfgToPersistToDisk, false);
- })
- .then(function () {
- var isGit;
- var isNPM;
- var options;
-
// If symlink, don't fetch
if (cfg.lib.www.link) {
- events.emit('verbose', 'Symlinking assets.');
- return Q(cfg.lib.www.url);
+ return cfg.lib.www.url;
}
- events.emit('verbose', 'Copying assets."');
- isGit = cfg.lib.www.template && isUrl(cfg.lib.www.url);
- isNPM = cfg.lib.www.template && (cfg.lib.www.url.indexOf('@') > -1 || !fs.existsSync(path.resolve(cfg.lib.www.url))) && !isGit;
+ var isGit = cfg.lib.www.template && isUrl(cfg.lib.www.url);
+ var isNPM = cfg.lib.www.template && (cfg.lib.www.url.indexOf('@') > -1 || !fs.existsSync(path.resolve(cfg.lib.www.url))) && !isGit;
// Always use cordova fetch to obtain the npm or git template
if (isGit || isNPM) {
// Saved to .Cordova folder (ToDo: Delete installed template after using)
- // ToDo: @carynbear properly label errors from fetch as such
var tempDest = global_config_path;
var target = cfg.lib.www.url;
// add latest to npm module if no version is specified
@@ -202,30 +175,25 @@
}
events.emit('verbose', 'Using cordova-fetch for ' + target);
return fetch(target, tempDest, {})
- .fail(function (err) {
+ .catch(function (err) {
events.emit('error', '\x1B[1m \x1B[31m Error from Cordova Fetch: ' + err.message);
events.emit('error', 'The template you are trying to use is invalid.' +
' Make sure you follow the template guide found here https://cordova.apache.org/docs/en/latest/guide/cli/template.html.' +
' Templates now require a package.json.');
- if (options.verbose) {
- console.trace();
- }
throw err;
});
// If assets are not online, resolve as a relative path on local computer
} else {
- cfg.lib.www.url = path.resolve(cfg.lib.www.url);
- return Q(cfg.lib.www.url);
+ return path.resolve(cfg.lib.www.url);
}
- }).then(function (input_directory) {
+ })
+ .then(function (input_directory) {
var import_from_path = input_directory;
// handle when input wants to specify sub-directory (specified in index.js as "dirname" export);
var isSubDir = false;
try {
- // Delete cached require incase one exists
- delete require.cache[require.resolve(input_directory)];
- var templatePkg = require(input_directory);
+ var templatePkg = requireFresh(input_directory);
if (templatePkg && templatePkg.dirname) {
import_from_path = templatePkg.dirname;
isSubDir = true;
@@ -240,37 +208,31 @@
import_from_path);
}
- var paths = {};
-
- // get stock config.xml, used if template does not contain config.xml
- paths.configXml = path.join(require('cordova-app-hello-world').dirname, 'config.xml');
-
- // get stock www; used if template does not contain www
- paths.www = path.join(require('cordova-app-hello-world').dirname, 'www');
-
- // get stock hooks; used if template does not contain hooks
- paths.hooks = path.join(require('cordova-app-hello-world').dirname, 'hooks');
-
- // ToDo: get stock package.json if template does not contain package.json;
var dirAlreadyExisted = fs.existsSync(dir);
if (!dirAlreadyExisted) {
fs.mkdirSync(dir);
}
try {
-
// Copy files from template to project
- if (cfg.lib.www.template) { copyTemplateFiles(import_from_path, dir, isSubDir); }
+ if (cfg.lib.www.template) {
+ events.emit('verbose', 'Copying assets.');
+ copyTemplateFiles(import_from_path, dir, isSubDir);
+ }
// If --link, link merges, hooks, www, and config.xml (and/or copy to root)
- if (cfg.lib.www.link) { linkFromTemplate(import_from_path, dir); }
+ if (cfg.lib.www.link) {
+ events.emit('verbose', 'Symlinking assets.');
+ linkFromTemplate(import_from_path, dir);
+ }
// If following were not copied/linked from template, copy from stock app hello world
- copyIfNotExists(paths.www, path.join(dir, 'www'));
- copyIfNotExists(paths.hooks, path.join(dir, 'hooks'));
+ // TODO: get stock package.json if template does not contain package.json;
+ copyIfNotExists(stockAssetPath('www'), path.join(dir, 'www'));
+ copyIfNotExists(stockAssetPath('hooks'), path.join(dir, 'hooks'));
var configXmlExists = projectConfig(dir); // moves config to root if in www
- if (paths.configXml && !configXmlExists) {
- shell.cp(paths.configXml, path.join(dir, 'config.xml'));
+ if (!configXmlExists) {
+ shell.cp(stockAssetPath('config.xml'), path.join(dir, 'config.xml'));
}
} catch (e) {
if (!dirAlreadyExisted) {
@@ -285,8 +247,7 @@
var pkgjsonPath = path.join(dir, 'package.json');
// Update package.json name and version fields
if (fs.existsSync(pkgjsonPath)) {
- delete require.cache[require.resolve(pkgjsonPath)];
- var pkgjson = require(pkgjsonPath);
+ var pkgjson = requireFresh(pkgjsonPath);
// Pkjson.displayName should equal config's name.
if (cfg.name) {
@@ -295,28 +256,27 @@
// Pkjson.name should equal config's id.
if (cfg.id) {
pkgjson.name = cfg.id.toLowerCase();
- } else if (!cfg.id) {
+ } else {
// Use default name.
pkgjson.name = 'helloworld';
}
- pkgjson.version = '1.0.0';
+ pkgjson.version = DEFAULT_VERSION;
fs.writeFileSync(pkgjsonPath, JSON.stringify(pkgjson, null, 4), 'utf8');
}
// Create basic project structure.
- if (!fs.existsSync(path.join(dir, 'platforms'))) { shell.mkdir(path.join(dir, 'platforms')); }
-
- if (!fs.existsSync(path.join(dir, 'plugins'))) { shell.mkdir(path.join(dir, 'plugins')); }
+ shell.mkdir('-p', path.join(dir, 'platforms'));
+ shell.mkdir('-p', path.join(dir, 'plugins'));
var configPath = path.join(dir, 'config.xml');
// only update config.xml if not a symlink
if (!fs.lstatSync(configPath).isSymbolicLink()) {
- // Write out id and name to config.xml; set version to 1.0.0 (to match package.json default version)
+ // Write out id, name and default version to config.xml
var conf = new ConfigParser(configPath);
if (cfg.id) conf.setPackageName(cfg.id);
if (cfg.name) conf.setName(cfg.name);
- conf.setVersion('1.0.0');
+ conf.setVersion(DEFAULT_VERSION);
conf.write();
}
});
@@ -352,8 +312,7 @@
copyPath = path.resolve(templateDir);
shell.cp('-R', copyPath, projectDir);
} else {
- var templateFiles; // Current file
- templateFiles = fs.readdirSync(templateDir);
+ var templateFiles = fs.readdirSync(templateDir);
// Remove directories, and files that are unwanted
if (!isSubDir) {
var excludes = ['package.json', 'RELEASENOTES.md', '.git', 'NOTICE', 'LICENSE', 'COPYRIGHT', '.npmignore'];
@@ -370,15 +329,6 @@
}
/**
- * @param {String} value
- * @return {Boolean} is the input value a url?
- */
-function isUrl (value) {
- var u = value && url.parse(value);
- return !!(u && u.protocol && u.protocol.length > 2); // Account for windows c:/ paths
-}
-
-/**
* Find config file in project directory or www directory
* If file is in www directory, move it outside
* @param {String} project directory to be searched
@@ -414,33 +364,6 @@
}
/**
- * Write opts to .cordova/config.json
- *
- * @param {String} project directory
- * @param {Object} opts containing the additions to config.json
- * @param {Boolean} autopersist option
- * @return {JSON Data}
- */
-function writeToConfigJson (project_root, opts, autoPersist) {
- var json = dotCordovaConfig(project_root);
- for (var p in opts) {
- json[p] = opts[p];
- }
- if (autoPersist) {
- var configPath = path.join(project_root, '.cordova', 'config.json');
- var contents = JSON.stringify(json, null, 4);
- // Don't write the file for an empty config.
- if (contents !== '{}' || fs.existsSync(configPath)) {
- shell.mkdir('-p', path.join(project_root, '.cordova'));
- fs.writeFileSync(configPath, contents, 'utf-8');
- }
- return json;
- } else {
- return json;
- }
-}
-
-/**
* Removes existing files and symlinks them if they exist.
* Symlinks folders: www, merges, hooks
* Symlinks file: config.xml (but only if it exists outside of the www folder)
@@ -483,3 +406,7 @@
shell.cp(copySrc, projectDir);
}
}
+
+function stockAssetPath (p) {
+ return path.join(require('cordova-app-hello-world').dirname, p);
+}
diff --git a/package.json b/package.json
index f89a74b..9ac95d1 100644
--- a/package.json
+++ b/package.json
@@ -28,20 +28,22 @@
"cordova-app-hello-world": "^3.11.0",
"cordova-common": "^2.2.0",
"cordova-fetch": "^1.3.0",
- "q": "1.0.1",
- "shelljs": "0.3.0",
+ "import-fresh": "^2.0.0",
+ "is-url": "^1.2.4",
+ "q": "^1.5.1",
+ "shelljs": "^0.8.2",
"valid-identifier": "0.0.1"
},
"devDependencies": {
"eslint": "^4.2.0",
- "eslint-config-semistandard": "^11.0.0",
- "eslint-config-standard": "^10.2.1",
+ "eslint-config-semistandard": "^12.0.1",
+ "eslint-config-standard": "^11.0.0",
"eslint-plugin-import": "^2.3.0",
- "eslint-plugin-node": "^5.0.0",
+ "eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1",
- "jasmine": "^2.4.1",
- "semver": "^5.3.0"
+ "jasmine": "^3.1.0",
+ "rewire": "^4.0.1"
},
"scripts": {
"test": "npm run eslint && npm run jasmine",
diff --git a/spec/create.spec.js b/spec/create.spec.js
index 1a79a81..68e924a 100644
--- a/spec/create.spec.js
+++ b/spec/create.spec.js
@@ -17,106 +17,46 @@
under the License.
*/
-var helpers = require('./helpers');
-var path = require('path');
-var shell = require('shelljs');
-var events = require('cordova-common').events;
-var ConfigParser = require('cordova-common').ConfigParser;
-var create = require('../index');
var fs = require('fs');
-var semver = require('semver');
-var tmpDir = helpers.tmpDir('create_test');
+var path = require('path');
+
+var shell = require('shelljs');
+var requireFresh = require('import-fresh');
+
+var create = require('..');
+var events = require('cordova-common').events;
+var CordovaError = require('cordova-common').CordovaError;
+var ConfigParser = require('cordova-common').ConfigParser;
+const {tmpDir, createWith, createWithMockFetch, expectRejection} = require('./helpers');
+
var appName = 'TestBase';
var appId = 'org.testing';
var project = path.join(tmpDir, appName);
-// Global configuration paths
-var global_config_path = process.env.CORDOVA_HOME;
-if (!global_config_path) {
- var HOME = process.env[(process.platform.slice(0, 3) === 'win') ? 'USERPROFILE' : 'HOME'];
- global_config_path = path.join(HOME, '.cordova');
-}
-
-var configSubDirPkgJson = {
- lib: {
- www: {
- template: true,
- url: path.join(__dirname, 'templates', 'withsubdirectory_package_json'),
- version: ''
- }
- }
-};
-
-var configConfigInWww = {
- lib: {
- www: {
- template: true,
- url: path.join(__dirname, 'templates', 'config_in_www'),
- version: ''
- }
- }
-};
-
-var configGit = {
- lib: {
- www: {
- url: 'https://github.com/apache/cordova-app-hello-world',
- template: true,
- version: 'not_versioned'
- }
- }
-};
-
-var configNPMold = {
- lib: {
- www: {
- template: true,
- url: 'phonegap-template-vue-f7-tabs@1.0.0',
- version: ''
- }
- }
-};
-
-var configNPM = {
- lib: {
- www: {
- template: true,
- url: 'phonegap-template-vue-f7-tabs',
- version: ''
- }
- }
-};
+// Setup and teardown test dirs
+beforeEach(function () {
+ shell.rm('-rf', project);
+ shell.mkdir('-p', tmpDir);
+});
+afterEach(function () {
+ process.chdir(path.join(__dirname, '..')); // Needed to rm the dir on Windows.
+ shell.rm('-rf', tmpDir);
+});
describe('cordova create checks for valid-identifier', function () {
- it('should reject reserved words from start of id', function (done) {
- create('projectPath', 'int.bob', 'appName', {}, events)
- .fail(function (err) {
- expect(err.message).toBe('App id contains a reserved word, or is not a valid identifier.');
- })
- .fin(done);
- }, 60000);
+ const error = new CordovaError('is not a valid identifier');
- it('should reject reserved words from end of id', function (done) {
- create('projectPath', 'bob.class', 'appName', {}, events)
- .fail(function (err) {
- expect(err.message).toBe('App id contains a reserved word, or is not a valid identifier.');
- })
- .fin(done);
- }, 60000);
+ it('should reject reserved words from start of id', function () {
+ return expectRejection(create(project, 'int.bob', appName, {}, events), error);
+ });
+
+ it('should reject reserved words from end of id', function () {
+ return expectRejection(create(project, 'bob.class', appName, {}, events), error);
+ });
});
describe('create end-to-end', function () {
- beforeEach(function () {
- shell.rm('-rf', project);
- shell.mkdir('-p', tmpDir);
- });
-
- afterEach(function () {
- process.chdir(path.join(__dirname, '..')); // Needed to rm the dir on Windows.
- shell.rm('-rf', tmpDir);
- });
-
function checkProject () {
// Check if top level dirs exist.
var dirs = ['hooks', 'platforms', 'plugins', 'www'];
@@ -184,9 +124,8 @@
var configXml = new ConfigParser(path.join(project, 'config.xml'));
expect(configXml.packageName()).toEqual(appId);
expect(configXml.version()).toEqual('1.0.0');
- delete require.cache[require.resolve(path.join(project, 'package.json'))];
// Check that we got package.json (the correct one)
- var pkjson = require(path.join(project, 'package.json'));
+ var pkjson = requireFresh(path.join(project, 'package.json'));
// Pkjson.displayName should equal config's name.
expect(pkjson.displayName).toEqual(appName);
expect(pkjson.valid).toEqual('true');
@@ -195,173 +134,174 @@
expect(configXml.description()).toEqual('this is the correct config.xml');
}
- it('should successfully run without template and use default hello-world app', function (done) {
+ it('should successfully run without template and use default hello-world app', function () {
// Create a real project with no template
// use default cordova-app-hello-world app
return create(project, appId, appName, {}, events)
.then(checkProject)
.then(function () {
- delete require.cache[require.resolve(path.join(project, 'package.json'))];
- var pkgJson = require(path.join(project, 'package.json'));
+ var pkgJson = requireFresh(path.join(project, 'package.json'));
// confirm default hello world app copies over package.json and it matched appId
expect(pkgJson.name).toEqual(appId);
- }).fail(function (err) {
- console.log(err && err.stack);
- expect(err).toBeUndefined();
- })
- .fin(done);
- }, 60000);
+ });
+ });
- it('should successfully run with Git URL', function (done) {
- // Create a real project with gitURL as template
- return create(project, appId, appName, configGit, events)
- .then(checkProject)
- .fail(function (err) {
- console.log(err && err.stack);
- expect(err).toBeUndefined();
+ it('should successfully run with Git URL', function () {
+ // Create a real project with git URL as template
+ var config = {
+ lib: {
+ www: {
+ url: 'https://github.com/apache/cordova-app-hello-world',
+ template: true
+ }
+ }
+ };
+ return createWithMockFetch(project, appId, appName, config, events)
+ .then(fetchSpy => {
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
+ expect(fetchSpy.calls.argsFor(0)[0]).toBe(config.lib.www.url);
})
- .fin(done);
- }, 60000);
+ .then(checkProject);
+ });
- it('should successfully run with NPM package and not use old cache of template on second create', function (done) {
- var templatePkgJsonPath = path.join(global_config_path, 'node_modules', 'phonegap-template-vue-f7-tabs', 'package.json');
+ it('should successfully run with NPM package', function () {
// Create a real project with npm module as template
- // tests cache clearing of npm template
- // uses phonegap-template-vue-f7-tabs
- return create(project, appId, appName, configNPMold)
- .then(checkProject)
- .then(function () {
- shell.rm('-rf', project);
- delete require.cache[require.resolve(templatePkgJsonPath)];
- var pkgJson = require(templatePkgJsonPath);
- expect(pkgJson.version).toBe('1.0.0');
- return create(project, appId, appName, configNPM);
- }).then(function () {
- delete require.cache[require.resolve(templatePkgJsonPath)];
- var pkgJson = require(templatePkgJsonPath);
- expect(semver.gt(pkgJson.version, '1.0.0')).toBeTruthy();
- }).fail(function (err) {
- console.log(err && err.stack);
- expect(err).toBeUndefined();
- })
- .fin(done);
- }, 60000);
-
- it('should successfully run with template not having a package.json at toplevel', function (done) {
- // Call cordova create with no args, should return help.
var config = {
lib: {
www: {
template: true,
- url: path.join(__dirname, 'templates', 'nopackage_json'),
- version: ''
+ url: 'phonegap-template-vue-f7-tabs@1'
}
}
};
- // Create a real project
+ return createWithMockFetch(project, appId, appName, config, events)
+ .then(fetchSpy => {
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
+ expect(fetchSpy.calls.argsFor(0)[0]).toBe(config.lib.www.url);
+ })
+ .then(checkProject);
+ });
+
+ it('should successfully run with NPM package and explicitly fetch latest if no version is given', function () {
+ // Create a real project with npm module as template
+ // TODO fetch should be responsible for the cache busting part of this test
+ var config = {
+ lib: {
+ www: {
+ template: true,
+ url: 'phonegap-template-vue-f7-tabs'
+ }
+ }
+ };
+ return createWithMockFetch(project, appId, appName, config, events)
+ .then(fetchSpy => {
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
+ expect(fetchSpy.calls.argsFor(0)[0]).toBe(config.lib.www.url + '@latest');
+ })
+ .then(checkProject);
+ });
+
+ it('should successfully run with template not having a package.json at toplevel', function () {
+ var config = {
+ lib: {
+ www: {
+ template: true,
+ url: path.join(__dirname, 'templates', 'nopackage_json')
+ }
+ }
+ };
return create(project, appId, appName, config, events)
.then(checkProject)
.then(function () {
// Check that we got the right config.xml
var configXml = new ConfigParser(path.join(project, 'config.xml'));
expect(configXml.description()).toEqual('this is the very correct config.xml');
- })
- .fail(function (err) {
- console.log(err && err.stack);
- expect(err).toBeUndefined();
- })
- .fin(done);
- }, 60000);
+ });
+ });
- it('should successfully run with template having package.json and no sub directory', function (done) {
- // Call cordova create with no args, should return help.
+ it('should successfully run with template having package.json and no sub directory', function () {
var config = {
lib: {
www: {
template: true,
- url: path.join(__dirname, 'templates', 'withpackage_json'),
- version: ''
+ url: path.join(__dirname, 'templates', 'withpackage_json')
}
}
};
- // Create a real project
return create(project, appId, appName, config, events)
- .then(checkProject)
- .fail(function (err) {
- console.log(err && err.stack);
- expect(err).toBeUndefined();
- })
- .fin(done);
- }, 60000);
+ .then(checkProject);
+ });
- it('should successfully run with template having package.json, and subdirectory, and no package.json in subdirectory', function (done) {
- // Call cordova create with no args, should return help.
+ it('should successfully run with template having package.json, and subdirectory, and no package.json in subdirectory', function () {
var config = {
lib: {
www: {
template: true,
- url: path.join(__dirname, 'templates', 'withsubdirectory'),
- version: ''
+ url: path.join(__dirname, 'templates', 'withsubdirectory')
}
}
};
-
- // Create a real project
return create(project, appId, appName, config, events)
- .then(checkProject)
- .fail(function (err) {
- console.log(err && err.stack);
- expect(err).toBeUndefined();
- })
- .fin(done);
- }, 60000);
+ .then(checkProject);
+ });
- it('should successfully run with template having package.json, and subdirectory, and package.json in subdirectory', function (done) {
- // Call cordova create with no args, should return help.
- var config = configSubDirPkgJson;
- return create(project, appId, appName, config, events)
- .then(checkSubDir)
- .fail(function (err) {
- console.log(err && err.stack);
- expect(err).toBeUndefined();
- })
- .fin(done);
- }, 60000);
-
- it('should successfully run config.xml in the www folder and move it outside', function (done) {
- // Call cordova create with no args, should return help.
- var config = configConfigInWww;
- // Create a real project
- return create(project, appId, appName, config, events)
- .then(checkConfigXml)
- .fail(function (err) {
- console.log(err && err.stack);
- expect(err).toBeUndefined();
- })
- .fin(done);
- }, 60000);
-
- it('should successfully run with www folder as the template', function (done) {
+ it('should successfully run with template having package.json, and subdirectory, and package.json in subdirectory', function () {
var config = {
lib: {
www: {
template: true,
- url: path.join(__dirname, 'templates', 'config_in_www', 'www'),
- version: ''
+ url: path.join(__dirname, 'templates', 'withsubdirectory_package_json')
}
}
};
return create(project, appId, appName, config, events)
- .then(checkConfigXml)
- .fail(function (err) {
- console.log(err && err.stack);
- expect(err).toBeUndefined();
- })
- .fin(done);
- }, 60000);
+ .then(checkSubDir);
+ });
+
+ it('should successfully run config.xml in the www folder and move it outside', function () {
+ var config = {
+ lib: {
+ www: {
+ template: true,
+ url: path.join(__dirname, 'templates', 'config_in_www')
+ }
+ }
+ };
+ return create(project, appId, appName, config, events)
+ .then(checkConfigXml);
+ });
+
+ it('should successfully run with www folder as the template', function () {
+ var config = {
+ lib: {
+ www: {
+ template: true,
+ url: path.join(__dirname, 'templates', 'config_in_www', 'www')
+ }
+ }
+ };
+ return create(project, appId, appName, config, events)
+ .then(checkConfigXml);
+ });
+
+ it('should successfully run with existing, empty destination', function () {
+ shell.mkdir('-p', project);
+ return create(project, appId, appName, {}, events)
+ .then(checkProject);
+ });
describe('when --link-to is provided', function () {
- it('when passed www folder should not move www/config.xml, only copy and update', function (done) {
+ function allowSymlinkErrorOnWindows (err) {
+ const onWindows = process.platform.slice(0, 3) === 'win';
+ const isSymlinkError = err && String(err.message).startsWith('Symlinks on Windows');
+ if (onWindows && isSymlinkError) {
+ pending(err.message);
+ } else {
+ throw err;
+ }
+ }
+
+ it('when passed www folder should not move www/config.xml, only copy and update', function () {
function checkSymWWW () {
// Check if top level dirs exist.
var dirs = ['hooks', 'platforms', 'plugins', 'www'];
@@ -403,28 +343,16 @@
www: {
template: true,
url: path.join(__dirname, 'templates', 'config_in_www', 'www'),
- version: '',
link: true
}
}
};
return create(project, appId, appName, config, events)
.then(checkSymWWW)
- .fail(function (err) {
- if (process.platform.slice(0, 3) === 'win') {
- // Allow symlink error if not in admin mode
- expect(err.message).toBe('Symlinks on Windows require Administrator privileges');
- } else {
- if (err) {
- console.log(err.stack);
- }
- expect(err).toBeUndefined();
- }
- })
- .fin(done);
- }, 60000);
+ .catch(allowSymlinkErrorOnWindows);
+ });
- it('with subdirectory should not update symlinked project/config.xml', function (done) {
+ it('with subdirectory should not update symlinked project/config.xml', function () {
function checkSymSubDir () {
// Check if top level dirs exist.
var dirs = ['hooks', 'platforms', 'plugins', 'www'];
@@ -452,9 +380,8 @@
// Check that we got the right config.xml
expect(configXml.description()).toEqual('this is the correct config.xml');
- delete require.cache[require.resolve(path.join(project, 'package.json'))];
// Check that we got package.json (the correct one) and it was changed
- var pkjson = require(path.join(project, 'package.json'));
+ var pkjson = requireFresh(path.join(project, 'package.json'));
// Pkjson.name should equal config's id.
expect(pkjson.name).toEqual(appId.toLowerCase());
expect(pkjson.valid).toEqual('true');
@@ -464,28 +391,16 @@
www: {
template: true,
url: path.join(__dirname, 'templates', 'withsubdirectory_package_json'),
- version: '',
link: true
}
}
};
return create(project, appId, appName, config, events)
.then(checkSymSubDir)
- .fail(function (err) {
- if (process.platform.slice(0, 3) === 'win') {
- // Allow symlink error if not in admin mode
- expect(err.message).toBe('Symlinks on Windows require Administrator privileges');
- } else {
- if (err) {
- console.log(err.stack);
- }
- expect(err).toBeUndefined();
- }
- })
- .fin(done);
- }, 60000);
+ .catch(allowSymlinkErrorOnWindows);
+ });
- it('with no config should create one and update it', function (done) {
+ it('with no config should create one and update it', function () {
function checkSymNoConfig () {
// Check if top level dirs exist.
var dirs = ['hooks', 'platforms', 'plugins', 'www'];
@@ -514,26 +429,78 @@
www: {
template: true,
url: path.join(__dirname, 'templates', 'noconfig'),
- version: '',
link: true
}
}
};
return create(project, appId, appName, config, events)
.then(checkSymNoConfig)
- .fail(function (err) {
- if (process.platform.slice(0, 3) === 'win') {
- // Allow symlink error if not in admin mode
- expect(err.message).toBe('Symlinks on Windows require Administrator privileges');
- } else {
- if (err) {
- console.log(err.stack);
- }
- expect(err).toBeUndefined();
- }
- })
- .fin(done);
- }, 60000);
+ .catch(allowSymlinkErrorOnWindows);
+ });
});
});
+
+describe('when shit happens', function () {
+ it('should fail when dir is missing', function () {
+ return expectRejection(
+ create(null, appId, appName, {}, events),
+ new CordovaError('Directory not specified')
+ );
+ });
+
+ it('should fail when dir already exists', function () {
+ return expectRejection(
+ create(__dirname, appId, appName, {}, events),
+ new CordovaError('Path already exists and is not empty')
+ );
+ });
+
+ it('should fail when destination is inside template', function () {
+ const config = {
+ lib: {
+ www: {
+ url: path.join(tmpDir, 'template')
+ }
+ }
+ };
+ const destination = path.join(config.lib.www.url, 'destination');
+ return expectRejection(
+ create(destination, appId, appName, config, events),
+ new CordovaError('inside the template')
+ );
+ });
+
+ it('should fail when fetch fails', function () {
+ const config = {
+ lib: {
+ www: {
+ template: true,
+ url: 'http://localhost:123456789/cordova-create'
+ }
+ }
+ };
+ const fetchError = new Error('Fetch fail');
+ const failingFetch = jasmine.createSpy('failingFetch')
+ .and.callFake(() => Promise.reject(fetchError));
+ return expectRejection(
+ createWith({fetch: failingFetch})(project, appId, appName, config),
+ fetchError
+ );
+
+ });
+
+ it('should fail when template does not exist', function () {
+ const config = {
+ lib: {
+ www: {
+ url: path.join(__dirname, 'doesnotexist')
+ }
+ }
+ };
+ return expectRejection(
+ create(project, appId, appName, config, events),
+ new CordovaError('Could not find directory')
+ );
+ });
+});
diff --git a/spec/helpers.js b/spec/helpers.js
index 174a7ae..daf6722 100644
--- a/spec/helpers.js
+++ b/spec/helpers.js
@@ -17,133 +17,61 @@
under the License.
*/
-var path = require('path'),
- fs = require('fs'),
- shell = require('shelljs'),
- os = require('os'),
- ConfigParser = require('cordova-common').ConfigParser;
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
-// Just use Android everywhere; we're mocking out any calls to the `android` binary.
-module.exports.testPlatform = 'android';
+const rewire = require('rewire');
+const shell = require('shelljs');
-function getConfigPath (dir) {
- // if path ends with 'config.xml', return it
- if (dir.indexOf('config.xml') == dir.length - 10) {
- return dir;
- }
- // otherwise, add 'config.xml' to the end of it
- return path.join(dir, 'config.xml');
+// Disable regular console output during tests
+const CordovaLogger = require('cordova-common').CordovaLogger;
+CordovaLogger.get().setLevel(CordovaLogger.ERROR);
+
+// Temporary directory to use for all tests
+const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cordova-create-tests-'));
+
+// Returns a version of create with its local scope rewired
+const create = rewire('..');
+function createWith (rewiring) {
+ return (...args) => create.__with__(rewiring)(() => create(...args));
}
-module.exports.tmpDir = function (subdir) {
- var dir = path.join(os.tmpdir(), 'e2e-test');
- if (subdir) {
- dir = path.join(dir, subdir);
- }
- if (fs.existsSync(dir)) {
- shell.rm('-rf', dir);
- }
- shell.mkdir('-p', dir);
- return dir;
-};
+// Calls create with mocked fetch to not depend on the outside world
+function createWithMockFetch (dir, id, name, cfg, events) {
+ const mockFetchDest = path.join(tmpDir, 'mockFetchDest');
+ const templateDir = path.dirname(require.resolve('cordova-app-hello-world'));
+ const fetchSpy = jasmine.createSpy('fetchSpy')
+ .and.callFake(() => Promise.resolve(mockFetchDest));
-// Returns the platform that should be used for testing on this host platform.
-/*
-var host = os.platform();
-if (host.match(/win/)) {
- module.exports.testPlatform = 'wp8';
-} else if (host.match(/darwin/)) {
- module.exports.testPlatform = 'ios';
-} else {
- module.exports.testPlatform = 'android';
+ shell.cp('-R', templateDir, mockFetchDest);
+ return createWith({fetch: fetchSpy})(dir, id, name, cfg, events)
+ .then(() => fetchSpy);
}
-*/
-module.exports.setEngineSpec = function (appPath, engine, spec) {
- appPath = getConfigPath(appPath);
- var parser = new ConfigParser(appPath);
+// Expect promise to get rejected with a reason matching expectedReason
+function expectRejection (promise, expectedReason) {
+ return promise.then(
+ () => fail('Expected promise to be rejected'),
+ reason => {
+ if (expectedReason instanceof Error) {
+ expect(reason instanceof expectedReason.constructor).toBeTruthy();
+ expect(reason.message).toContain(expectedReason.message);
+ } else if (typeof expectedReason === 'function') {
+ expect(expectedReason(reason)).toBeTruthy();
+ } else if (expectedReason !== undefined) {
+ expect(reason).toBe(expectedReason);
+ } else {
+ expect().nothing();
+ }
+ });
+}
- parser.removeEngine(engine);
- parser.addEngine(engine, spec);
- parser.write();
-};
-
-module.exports.getEngineSpec = function (appPath, engine) {
- appPath = getConfigPath(appPath);
- var parser = new ConfigParser(appPath),
- engines = parser.getEngines();
-
- for (var i = 0; i < engines.length; i++) {
- if (engines[i].name === module.exports.testPlatform) {
- return engines[i].spec;
- }
- }
- return null;
-};
-
-module.exports.removeEngine = function (appPath, engine) {
- appPath = getConfigPath(appPath);
- var parser = new ConfigParser(appPath);
-
- parser.removeEngine(module.exports.testPlatform);
- parser.write();
-};
-
-module.exports.setPluginSpec = function (appPath, plugin, spec) {
- appPath = getConfigPath(appPath);
- var parser = new ConfigParser(appPath),
- p = parser.getPlugin(plugin),
- variables = [];
-
- if (p) {
- parser.removePlugin(p.name);
- if (p.variables.length && p.variables.length > 0) {
- variables = p.variables;
- }
- }
-
- parser.addPlugin({ 'name': plugin, 'spec': spec }, variables);
- parser.write();
-};
-
-module.exports.getPluginSpec = function (appPath, plugin) {
- appPath = getConfigPath(appPath);
- var parser = new ConfigParser(appPath),
- p = parser.getPlugin(plugin);
-
- if (p) {
- return p.spec;
- }
- return null;
-};
-
-module.exports.getPluginVariable = function (appPath, plugin, variable) {
- appPath = getConfigPath(appPath);
- var parser = new ConfigParser(appPath),
- p = parser.getPlugin(plugin);
-
- if (p && p.variables) {
- return p.variables[variable];
- }
- return null;
-};
-
-module.exports.removePlugin = function (appPath, plugin) {
- appPath = getConfigPath(appPath);
- var parser = new ConfigParser(appPath);
-
- parser.removePlugin(plugin);
- parser.write();
-};
-
-module.exports.getConfigContent = function (appPath) {
- var configFile = path.join(appPath, 'config.xml');
- return fs.readFileSync(configFile, 'utf-8');
-};
-
-module.exports.writeConfigContent = function (appPath, configContent) {
- var configFile = path.join(appPath, 'config.xml');
- fs.writeFileSync(configFile, configContent, 'utf-8');
+module.exports = {
+ tmpDir,
+ createWith,
+ createWithMockFetch,
+ expectRejection
};
// Add the toExist matcher.