refactor: remove shelljs dependency (#842)

Co-authored-by: エリス <erisu@users.noreply.github.com>
This commit is contained in:
Norman Breau
2020-01-28 21:12:55 -04:00
committed by GitHub
parent dee1e77d0b
commit 3712619f5c
20 changed files with 702 additions and 511 deletions
+150
View File
@@ -0,0 +1,150 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
let fs = require('fs-extra');
var cp = require('child_process');
var path = require('path');
var util = require('util');
var cordova_bin = path.join(__dirname, '../../../bin');
/**
* Creates a project using platform create script with given parameters
* @param {string} projectname - name of the project
* @param {string} projectid - id of the project
* @param {string} platformpath - path to the platform
* @param {function} callback - function which is called (without arguments) when the project is created or (with error object) when error occurs
*/
module.exports.createProject = function (projectname, projectid, platformpath, callback) {
// platformpath is optional
if (!callback && typeof platformpath === 'function') {
callback = platformpath;
platformpath = null;
}
var projectDirName = getDirName(projectid);
var createScriptPath = platformpath ? path.join(platformpath, 'bin/create') : path.join(cordova_bin, 'create');
// remove existing folder
module.exports.removeProject(projectid);
// create the project
var command = util.format('"%s" %s %s "%s"', createScriptPath, projectDirName, projectid, projectname);
cp.exec(command, function (error, stdout, stderr) {
if (error) {
console.log(stdout);
console.error(stderr);
}
callback(error);
});
};
/**
* Updates a project using platform update script with given parameters
* @param {string} projectid - id of the project
* @param {string} platformpath - path to the platform
* @param {function} callback - function which is called (without arguments) when the project is updated or (with error object) when error occurs
*/
module.exports.updateProject = function (projectid, platformpath, callback) {
// platformpath is optional
if (!callback && typeof platformpath === 'function') {
callback = platformpath;
platformpath = null;
}
var projectDirName = getDirName(projectid);
var updateScriptPath = platformpath ? path.join(platformpath, 'bin/update') : path.join(cordova_bin, 'update');
var command = util.format('"%s" %s', updateScriptPath, projectDirName);
cp.exec(command, function (error, stdout, stderr) {
if (error) {
console.log(stdout);
console.error(stderr);
}
callback(error);
});
};
/**
* Builds a project using platform build script with given parameters
* @param {string} projectid - id of the project
* @param {function} callback - function which is called (without arguments) when the project is built or (with error object) when error occurs
*/
module.exports.buildProject = function (projectid, callback) {
var projectDirName = getDirName(projectid);
var command = path.join(projectDirName, 'cordova/build');
cp.exec(command, function (error, stdout, stderr) {
if (error) {
console.log(stdout);
console.error(stderr);
}
callback(error);
});
};
/**
* Removes a project
* @param {string} projectid - id of the project
*/
module.exports.removeProject = function (projectid) {
var projectDirName = getDirName(projectid);
fs.removeSync(projectDirName);
};
/**
* Add a plugin to a project using platform api
* @param {string} projectid - id of the project
* @param {string} plugindir - path to a plugin
* @param {function} callback - function which is called (without arguments) when the plugin is added or (with error object) when error occurs
*/
module.exports.addPlugin = function (projectid, plugindir, callback) {
var projectDirName = getDirName(projectid);
var pip = new PluginInfoProvider();
var pluginInfo = pip.get(plugindir);
var Api = require(path.join(__dirname, '../../..', projectDirName, 'cordova', 'Api.js'));
var api = new Api('android', projectDirName);
api.addPlugin(pluginInfo).then(function () {
callback(null);
}, function (error) {
console.error(error);
callback(error);
});
};
/**
* Gets a version number from project using platform script
* @param {string} projectid - id of the project
* @param {function} callback - function which is called with platform version as an argument
*/
module.exports.getPlatformVersion = function (projectid, callback) {
var command = path.join(getDirName(projectid), 'cordova/version');
cp.exec(command, function (error, stdout, stderr) {
if (error) {
console.log(stdout);
console.error(stderr);
}
callback(stdout.trim());
});
};
function getDirName (projectid) {
return 'test-' + projectid;
}
+2 -3
View File
@@ -18,9 +18,8 @@
*/
const os = require('os');
const fs = require('fs');
const fs = require('fs-extra');
const path = require('path');
const shell = require('shelljs');
const execa = require('execa');
const { PluginInfoProvider } = require('cordova-common');
@@ -34,7 +33,7 @@ describe('plugin add', function () {
tmpDir = fs.realpathSync(fs.mkdtempSync(tmpDirTemplate));
});
afterEach(() => {
shell.rm('-rf', tmpDir);
fs.removeSync(tmpDir);
});
it('Test#001 : create project and add a plugin with framework', function () {
+17 -47
View File
@@ -17,7 +17,7 @@
under the License.
*/
const fs = require('fs');
const fs = require('fs-extra');
const path = require('path');
const rewire = require('rewire');
@@ -215,20 +215,20 @@ describe('ProjectBuilder', () => {
});
describe('clean', () => {
let shellSpy;
beforeEach(() => {
shellSpy = jasmine.createSpyObj('shell', ['rm']);
ProjectBuilder.__set__('shell', shellSpy);
const marker = ProjectBuilder.__get__('MARKER');
spyOn(fs, 'readFileSync').and.returnValue(`Some Header Here: ${marker}`);
spyOn(fs, 'removeSync');
spyOn(builder, 'getArgs');
execaSpy.and.returnValue(Promise.resolve());
});
it('should get arguments for cleaning', () => {
const opts = {};
builder.clean(opts);
expect(builder.getArgs).toHaveBeenCalledWith('clean', opts);
return builder.clean(opts).then(() => {
expect(builder.getArgs).toHaveBeenCalledWith('clean', opts);
});
});
it('should spawn gradle', () => {
@@ -243,7 +243,7 @@ describe('ProjectBuilder', () => {
it('should remove "out" folder', () => {
return builder.clean({}).then(() => {
expect(shellSpy.rm).toHaveBeenCalledWith('-rf', path.join(rootDir, 'out'));
expect(fs.removeSync).toHaveBeenCalledWith(path.join(rootDir, 'out'));
});
});
@@ -251,13 +251,11 @@ describe('ProjectBuilder', () => {
const debugSigningFile = path.join(rootDir, 'debug-signing.properties');
const releaseSigningFile = path.join(rootDir, 'release-signing.properties');
const isAutoGeneratedSpy = jasmine.createSpy('isAutoGenerated');
ProjectBuilder.__set__('isAutoGenerated', isAutoGeneratedSpy);
isAutoGeneratedSpy.and.returnValue(true);
spyOn(fs, 'existsSync').and.returnValue(true);
return builder.clean({}).then(() => {
expect(shellSpy.rm).toHaveBeenCalledWith(jasmine.any(String), debugSigningFile);
expect(shellSpy.rm).toHaveBeenCalledWith(jasmine.any(String), releaseSigningFile);
expect(fs.removeSync).toHaveBeenCalledWith(debugSigningFile);
expect(fs.removeSync).toHaveBeenCalledWith(releaseSigningFile);
});
});
@@ -265,18 +263,16 @@ describe('ProjectBuilder', () => {
const debugSigningFile = path.join(rootDir, 'debug-signing.properties');
const releaseSigningFile = path.join(rootDir, 'release-signing.properties');
const isAutoGeneratedSpy = jasmine.createSpy('isAutoGenerated');
ProjectBuilder.__set__('isAutoGenerated', isAutoGeneratedSpy);
isAutoGeneratedSpy.and.returnValue(false);
spyOn(fs, 'existsSync').and.returnValue(false);
return builder.clean({}).then(() => {
expect(shellSpy.rm).not.toHaveBeenCalledWith(jasmine.any(String), debugSigningFile);
expect(shellSpy.rm).not.toHaveBeenCalledWith(jasmine.any(String), releaseSigningFile);
expect(fs.removeSync).not.toHaveBeenCalledWith(debugSigningFile);
expect(fs.removeSync).not.toHaveBeenCalledWith(releaseSigningFile);
});
});
});
describe('apkSorter', () => {
describe('fileSorter', () => {
it('should sort APKs from most recent to oldest, deprioritising unsigned arch-specific builds', () => {
const APKs = {
'app-debug.apk': new Date('2018-04-20'),
@@ -291,40 +287,14 @@ describe('ProjectBuilder', () => {
const expectedResult = ['app-release.apk', 'app-debug.apk', 'app-release-unsigned.apk',
'app-release-arm.apk', 'app-release-x86.apk', 'app-debug-x86.apk', 'app-debug-arm.apk'];
const fsSpy = jasmine.createSpyObj('fs', ['statSync']);
fsSpy.statSync.and.callFake(filename => {
spyOn(fs, 'statSync').and.callFake(filename => {
return { mtime: APKs[filename] };
});
ProjectBuilder.__set__('fs', fsSpy);
const apkArray = Object.keys(APKs);
const sortedApks = apkArray.sort(ProjectBuilder.__get__('apkSorter'));
const sortedApks = apkArray.sort(ProjectBuilder.__get__('fileSorter'));
expect(sortedApks).toEqual(expectedResult);
});
});
describe('isAutoGenerated', () => {
let fsSpy;
beforeEach(() => {
fsSpy = jasmine.createSpyObj('fs', ['existsSync', 'readFileSync']);
fsSpy.existsSync.and.returnValue(true);
ProjectBuilder.__set__('fs', fsSpy);
});
it('should return true if the file contains the autogenerated marker', () => {
const fileContents = `# DO NOT MODIFY - YOUR CHANGES WILL BE ERASED!`;
fsSpy.readFileSync.and.returnValue(fileContents);
expect(ProjectBuilder.__get__('isAutoGenerated')()).toBe(true);
});
it('should return false if the file does not contain the autogenerated marker', () => {
const fileContents = `# My modified file`;
fsSpy.readFileSync.and.returnValue(fileContents);
expect(ProjectBuilder.__get__('isAutoGenerated')()).toBe(false);
});
});
});
+10 -10
View File
@@ -20,10 +20,10 @@
var rewire = require('rewire');
var check_reqs = rewire('../../bin/templates/cordova/lib/check_reqs');
var android_sdk = require('../../bin/templates/cordova/lib/android_sdk');
var shelljs = require('shelljs');
var fs = require('fs');
var fs = require('fs-extra');
var path = require('path');
var events = require('cordova-common').events;
var which = require('which');
// This should match /bin/templates/project/build.gradle
const DEFAULT_TARGET_API = 29;
@@ -46,7 +46,7 @@ describe('check_reqs', function () {
});
describe('even if no Android binaries are on the PATH', function () {
beforeEach(function () {
spyOn(shelljs, 'which').and.returnValue(null);
spyOn(which, 'sync').and.returnValue(null);
spyOn(fs, 'existsSync').and.returnValue(true);
});
it('it should set ANDROID_HOME on Windows', () => {
@@ -79,7 +79,7 @@ describe('check_reqs', function () {
});
it('should set ANDROID_HOME based on `android` command if command exists in a SDK-like directory structure', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
spyOn(shelljs, 'which').and.callFake(function (cmd) {
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'android') {
return '/android/sdk/tools/android';
} else {
@@ -91,7 +91,7 @@ describe('check_reqs', function () {
});
});
it('should error out if `android` command exists in a non-SDK-like directory structure', () => {
spyOn(shelljs, 'which').and.callFake(function (cmd) {
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'android') {
return '/just/some/random/path/android';
} else {
@@ -107,7 +107,7 @@ describe('check_reqs', function () {
});
it('should set ANDROID_HOME based on `adb` command if command exists in a SDK-like directory structure', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
spyOn(shelljs, 'which').and.callFake(function (cmd) {
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'adb') {
return '/android/sdk/platform-tools/adb';
} else {
@@ -119,7 +119,7 @@ describe('check_reqs', function () {
});
});
it('should error out if `adb` command exists in a non-SDK-like directory structure', () => {
spyOn(shelljs, 'which').and.callFake(function (cmd) {
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'adb') {
return '/just/some/random/path/adb';
} else {
@@ -135,7 +135,7 @@ describe('check_reqs', function () {
});
it('should set ANDROID_HOME based on `avdmanager` command if command exists in a SDK-like directory structure', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
spyOn(shelljs, 'which').and.callFake(function (cmd) {
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'avdmanager') {
return '/android/sdk/tools/bin/avdmanager';
} else {
@@ -147,7 +147,7 @@ describe('check_reqs', function () {
});
});
it('should error out if `avdmanager` command exists in a non-SDK-like directory structure', () => {
spyOn(shelljs, 'which').and.callFake(function (cmd) {
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'avdmanager') {
return '/just/some/random/path/avdmanager';
} else {
@@ -165,7 +165,7 @@ describe('check_reqs', function () {
});
describe('set PATH for various Android binaries if not available', function () {
beforeEach(function () {
spyOn(shelljs, 'which').and.returnValue(null);
spyOn(which, 'sync').and.returnValue(null);
process.env.ANDROID_HOME = 'let the children play';
spyOn(fs, 'existsSync').and.returnValue(true);
});
+47 -17
View File
@@ -18,11 +18,11 @@
*/
var rewire = require('rewire');
var utils = require('../../bin/lib/utils');
var create = rewire('../../bin/lib/create');
var check_reqs = require('../../bin/templates/cordova/lib/check_reqs');
var fs = require('fs');
var fs = require('fs-extra');
var path = require('path');
var shell = require('shelljs');
describe('create', function () {
describe('validatePackageName helper method', function () {
@@ -53,26 +53,33 @@ describe('create', function () {
it('should reject empty package names', () => {
return expectPackageNameToBeRejected('');
});
it('should reject package names containing "class"', () => {
return expectPackageNameToBeRejected('com.class.is.bad');
});
it('should reject package names that do not start with a latin letter', () => {
return expectPackageNameToBeRejected('_un.der.score');
});
it('should reject package names with terms that do not start with a latin letter', () => {
return expectPackageNameToBeRejected('un._der.score');
});
it('should reject package names containing non-alphanumeric or underscore characters', () => {
return expectPackageNameToBeRejected('th!$.!$.b@d');
});
it('should reject package names that do not contain enough dots', () => {
return expectPackageNameToBeRejected('therearenodotshere');
});
it('should reject package names that end with a dot', () => {
return expectPackageNameToBeRejected('this.is.a.complete.sentence.');
});
});
});
describe('validateProjectName helper method', function () {
describe('happy path (valid project names)', function () {
var valid = [
@@ -90,6 +97,7 @@ describe('create', function () {
});
});
});
describe('failure cases (invalid project names)', function () {
it('should reject empty project names', () => {
return create.validateProjectName('').then(() => {
@@ -101,6 +109,7 @@ describe('create', function () {
});
});
});
describe('main method', function () {
var config_mock;
var events_mock;
@@ -110,6 +119,7 @@ describe('create', function () {
var app_path = path.join(project_path, 'app', 'src', 'main');
var default_templates = path.join(__dirname, '..', '..', 'bin', 'templates', 'project');
var fake_android_target = 'android-1337';
beforeEach(function () {
Manifest_mock.prototype = jasmine.createSpyObj('AndroidManifest instance mock', ['setPackageId', 'getActivity', 'setName', 'write']);
Manifest_mock.prototype.setPackageId.and.returnValue(new Manifest_mock());
@@ -117,7 +127,6 @@ describe('create', function () {
Manifest_mock.prototype.setName.and.returnValue(new Manifest_mock());
spyOn(create, 'validatePackageName').and.resolveTo();
spyOn(create, 'validateProjectName').and.resolveTo();
spyOn(create, 'setShellFatal').and.callFake(function (noop, cb) { cb(); });
spyOn(create, 'copyJsAndLibrary');
spyOn(create, 'copyScripts');
spyOn(create, 'copyBuildRules');
@@ -125,16 +134,18 @@ describe('create', function () {
spyOn(create, 'prepBuildFiles');
revert_manifest_mock = create.__set__('AndroidManifest', Manifest_mock);
spyOn(fs, 'existsSync').and.returnValue(false);
spyOn(shell, 'cp');
spyOn(shell, 'mkdir');
spyOn(shell, 'sed');
spyOn(fs, 'copySync');
spyOn(fs, 'ensureDirSync');
spyOn(utils, 'replaceFileContents');
config_mock = jasmine.createSpyObj('ConfigParser mock instance', ['packageName', 'android_packageName', 'name', 'android_activityName']);
events_mock = jasmine.createSpyObj('EventEmitter mock instance', ['emit']);
spyOn(check_reqs, 'get_target').and.returnValue(fake_android_target);
});
afterEach(function () {
revert_manifest_mock();
});
describe('parameter values and defaults', function () {
it('should have a default package name of my.cordova.project', () => {
config_mock.packageName.and.returnValue(undefined);
@@ -142,42 +153,49 @@ describe('create', function () {
expect(create.validatePackageName).toHaveBeenCalledWith('my.cordova.project');
});
});
it('should use the ConfigParser-provided package name, if exists', () => {
config_mock.packageName.and.returnValue('org.apache.cordova');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.validatePackageName).toHaveBeenCalledWith('org.apache.cordova');
});
});
it('should have a default project name of CordovaExample', () => {
config_mock.name.and.returnValue(undefined);
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.validateProjectName).toHaveBeenCalledWith('CordovaExample');
});
});
it('should use the ConfigParser-provided project name, if exists', () => {
config_mock.name.and.returnValue('MySweetAppName');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.validateProjectName).toHaveBeenCalledWith('MySweetAppName');
});
});
it('should replace any non-word characters (including unicode and spaces) in the ConfigParser-provided project name with underscores', () => {
config_mock.name.and.returnValue('応応応応 hello 用用用用');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.validateProjectName).toHaveBeenCalledWith('_____hello_____');
});
});
it('should have a default activity name of MainActivity', () => {
config_mock.android_activityName.and.returnValue(undefined);
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(Manifest_mock.prototype.setName).toHaveBeenCalledWith('MainActivity');
});
});
it('should use the activityName provided via options parameter, if exists', () => {
config_mock.android_activityName.and.returnValue(undefined);
return create.create(project_path, config_mock, { activityName: 'AwesomeActivity' }, events_mock).then(() => {
expect(Manifest_mock.prototype.setName).toHaveBeenCalledWith('AwesomeActivity');
});
});
it('should use the ConfigParser-provided activity name, if exists', () => {
config_mock.android_activityName.and.returnValue('AmazingActivity');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
@@ -185,6 +203,7 @@ describe('create', function () {
});
});
});
describe('failure', function () {
it('should fail if the target path already exists', () => {
fs.existsSync.and.returnValue(true);
@@ -195,6 +214,7 @@ describe('create', function () {
expect(err.message).toContain('Project already exists!');
});
});
it('should fail if validateProjectName rejects', () => {
const fakeError = new Error();
create.validateProjectName.and.callFake(() => Promise.reject(fakeError));
@@ -207,63 +227,73 @@ describe('create', function () {
});
});
describe('happy path', function () {
it('should copy project templates from a specified custom template', () => {
return create.create(project_path, config_mock, { customTemplate: '/template/path' }, events_mock).then(() => {
expect(shell.cp).toHaveBeenCalledWith('-r', path.join('/template/path', 'assets'), app_path);
expect(shell.cp).toHaveBeenCalledWith('-r', path.join('/template/path', 'res'), app_path);
expect(shell.cp).toHaveBeenCalledWith(path.join('/template/path', 'gitignore'), path.join(project_path, '.gitignore'));
expect(fs.copySync).toHaveBeenCalledWith(path.join('/template/path', 'assets'), path.join(app_path, 'assets'));
expect(fs.copySync).toHaveBeenCalledWith(path.join('/template/path', 'res'), path.join(app_path, 'res'));
expect(fs.copySync).toHaveBeenCalledWith(path.join('/template/path', 'gitignore'), path.join(project_path, '.gitignore'));
});
});
it('should copy project templates from the default templates location if no custom template is provided', () => {
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(shell.cp).toHaveBeenCalledWith('-r', path.join(default_templates, 'assets'), app_path);
expect(shell.cp).toHaveBeenCalledWith('-r', path.join(default_templates, 'res'), app_path);
expect(shell.cp).toHaveBeenCalledWith(path.join(default_templates, 'gitignore'), path.join(project_path, '.gitignore'));
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'assets'), path.join(app_path, 'assets'));
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'res'), path.join(app_path, 'res'));
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'gitignore'), path.join(project_path, '.gitignore'));
});
});
it('should copy JS and library assets', () => {
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.copyJsAndLibrary).toHaveBeenCalled();
});
});
it('should create a java src directory based on the provided project package name', () => {
config_mock.packageName.and.returnValue('org.apache.cordova');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(shell.mkdir).toHaveBeenCalledWith('-p', path.join(app_path, 'java', 'org', 'apache', 'cordova'));
expect(fs.ensureDirSync).toHaveBeenCalledWith(path.join(app_path, 'java', 'org', 'apache', 'cordova'));
});
});
it('should copy, rename and interpolate the template Activity java class with the project-specific activity name and package name', () => {
config_mock.packageName.and.returnValue('org.apache.cordova');
config_mock.android_activityName.and.returnValue('CEEDEEVEE');
var activity_path = path.join(app_path, 'java', 'org', 'apache', 'cordova', 'CEEDEEVEE.java');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(shell.cp).toHaveBeenCalledWith('-f', path.join(default_templates, 'Activity.java'), activity_path);
expect(shell.sed).toHaveBeenCalledWith('-i', /__ACTIVITY__/, 'CEEDEEVEE', activity_path);
expect(shell.sed).toHaveBeenCalledWith('-i', /__ID__/, 'org.apache.cordova', activity_path);
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'Activity.java'), activity_path);
expect(utils.replaceFileContents).toHaveBeenCalledWith(activity_path, /__ACTIVITY__/, 'CEEDEEVEE');
expect(utils.replaceFileContents).toHaveBeenCalledWith(activity_path, /__ID__/, 'org.apache.cordova');
});
});
it('should interpolate the project name into strings.xml', () => {
config_mock.name.and.returnValue('IncredibleApp');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(shell.sed).toHaveBeenCalledWith('-i', /__NAME__/, 'IncredibleApp', path.join(app_path, 'res', 'values', 'strings.xml'));
expect(utils.replaceFileContents).toHaveBeenCalledWith(path.join(app_path, 'res', 'values', 'strings.xml'), /__NAME__/, 'IncredibleApp');
});
});
it('should copy template scripts into generated project', () => {
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.copyScripts).toHaveBeenCalledWith(project_path);
});
});
it('should copy build rules / gradle files into generated project', () => {
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.copyBuildRules).toHaveBeenCalledWith(project_path);
});
});
it('should write project.properties file with project details and target API', () => {
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.writeProjectProperties).toHaveBeenCalledWith(project_path, fake_android_target);
});
});
it('should prepare build files', () => {
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.prepBuildFiles).toHaveBeenCalledWith(project_path);
+10 -10
View File
@@ -17,10 +17,10 @@
under the License.
*/
const fs = require('fs');
const fs = require('fs-extra');
const path = require('path');
const rewire = require('rewire');
const shelljs = require('shelljs');
const which = require('which');
const CordovaError = require('cordova-common').CordovaError;
const check_reqs = require('../../bin/templates/cordova/lib/check_reqs');
@@ -83,7 +83,7 @@ describe('emulator', () => {
});
it('should try to parse AVD information using `avdmanager` first', () => {
spyOn(shelljs, 'which').and.callFake(cmd => cmd === 'avdmanager');
spyOn(which, 'sync').and.callFake(cmd => cmd === 'avdmanager');
const avdmanager_spy = spyOn(emu, 'list_images_using_avdmanager').and.returnValue(Promise.resolve([]));
@@ -93,7 +93,7 @@ describe('emulator', () => {
});
it('should delegate to `android` if `avdmanager` cant be found and `android` can', () => {
spyOn(shelljs, 'which').and.callFake(cmd => cmd !== 'avdmanager');
spyOn(which, 'sync').and.callFake(cmd => cmd !== 'avdmanager');
const android_spy = spyOn(emu, 'list_images_using_android').and.returnValue(Promise.resolve([]));
@@ -103,7 +103,7 @@ describe('emulator', () => {
});
it('should correct api level information and fill in the blanks about api level if exists', () => {
spyOn(shelljs, 'which').and.callFake(cmd => cmd === 'avdmanager');
spyOn(which, 'sync').and.callFake(cmd => cmd === 'avdmanager');
spyOn(emu, 'list_images_using_avdmanager').and.returnValue(Promise.resolve([
{
name: 'Pixel_7.0',
@@ -127,7 +127,7 @@ describe('emulator', () => {
});
it('should throw an error if neither `avdmanager` nor `android` are able to be found', () => {
spyOn(shelljs, 'which').and.returnValue(false);
spyOn(which, 'sync').and.returnValue(false);
return emu.list_images().then(
() => fail('Unexpectedly resolved'),
@@ -255,7 +255,7 @@ describe('emulator', () => {
let AdbSpy;
let checkReqsSpy;
let execaSpy;
let shellJsSpy;
let whichSpy;
beforeEach(() => {
emulator = {
@@ -286,9 +286,9 @@ describe('emulator', () => {
const proc = emu.__get__('process');
spyOn(proc.stdout, 'write').and.stub();
shellJsSpy = jasmine.createSpyObj('shelljs', ['which']);
shellJsSpy.which.and.returnValue('/dev/android-sdk/tools');
emu.__set__('shelljs', shellJsSpy);
whichSpy = jasmine.createSpyObj('which', ['sync']);
whichSpy.sync.and.returnValue('/dev/android-sdk/tools');
emu.__set__('which', whichSpy);
});
it('should find an emulator if an id is not specified', () => {
+23 -24
View File
@@ -19,9 +19,8 @@
var rewire = require('rewire');
var common = rewire('../../../bin/templates/cordova/lib/pluginHandlers');
var path = require('path');
var fs = require('fs');
var fs = require('fs-extra');
var osenv = require('os');
var shell = require('shelljs');
var test_dir = path.join(osenv.tmpdir(), 'test_plugman');
var project_dir = path.join(test_dir, 'project');
var src = path.join(project_dir, 'src');
@@ -39,22 +38,22 @@ describe('common platform handler', function () {
describe('copyFile', function () {
it('Test#001 : should throw if source path not found', function () {
shell.rm('-rf', src);
fs.removeSync(src);
expect(function () { copyFile(test_dir, src, project_dir, dest); })
.toThrow(new Error('"' + src + '" not found!'));
});
it('Test#002 : should throw if src not in plugin directory', function () {
shell.mkdir('-p', project_dir);
fs.ensureDirSync(project_dir);
fs.writeFileSync(non_plugin_file, 'contents', 'utf-8');
var outside_file = '../non_plugin_file';
expect(function () { copyFile(test_dir, outside_file, project_dir, dest); })
.toThrow(new Error('File "' + path.resolve(test_dir, outside_file) + '" is located outside the plugin directory "' + test_dir + '"'));
shell.rm('-rf', test_dir);
fs.removeSync(test_dir);
});
it('Test#003 : should allow symlink src, if inside plugin', function () {
shell.mkdir('-p', java_dir);
fs.ensureDirSync(java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8');
// This will fail on windows if not admin - ignore the error in that case.
@@ -63,11 +62,11 @@ describe('common platform handler', function () {
}
copyFile(test_dir, symlink_file, project_dir, dest);
shell.rm('-rf', project_dir);
fs.removeSync(project_dir);
});
it('Test#004 : should throw if symlink is linked to a file outside the plugin', function () {
shell.mkdir('-p', java_dir);
fs.ensureDirSync(java_dir);
fs.writeFileSync(non_plugin_file, 'contents', 'utf-8');
// This will fail on windows if not admin - ignore the error in that case.
@@ -77,68 +76,68 @@ describe('common platform handler', function () {
expect(function () { copyFile(test_dir, symlink_file, project_dir, dest); })
.toThrow(new Error('File "' + path.resolve(test_dir, symlink_file) + '" is located outside the plugin directory "' + test_dir + '"'));
shell.rm('-rf', project_dir);
fs.removeSync(project_dir);
});
it('Test#005 : should throw if dest is outside the project directory', function () {
shell.mkdir('-p', java_dir);
fs.ensureDirSync(java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8');
expect(function () { copyFile(test_dir, java_file, project_dir, non_plugin_file); })
.toThrow(new Error('Destination "' + path.resolve(project_dir, non_plugin_file) + '" for source file "' + path.resolve(test_dir, java_file) + '" is located outside the project'));
shell.rm('-rf', project_dir);
fs.removeSync(project_dir);
});
it('Test#006 : should call mkdir -p on target path', function () {
shell.mkdir('-p', java_dir);
fs.ensureDirSync(java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8');
var s = spyOn(shell, 'mkdir').and.callThrough();
var s = spyOn(fs, 'ensureDirSync').and.callThrough();
var resolvedDest = path.resolve(project_dir, dest);
copyFile(test_dir, java_file, project_dir, dest);
expect(s).toHaveBeenCalled();
expect(s).toHaveBeenCalledWith('-p', path.dirname(resolvedDest));
shell.rm('-rf', project_dir);
expect(s).toHaveBeenCalledWith(path.dirname(resolvedDest));
fs.removeSync(project_dir);
});
it('Test#007 : should call cp source/dest paths', function () {
shell.mkdir('-p', java_dir);
fs.ensureDirSync(java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8');
var s = spyOn(shell, 'cp').and.callThrough();
var s = spyOn(fs, 'copySync').and.callThrough();
var resolvedDest = path.resolve(project_dir, dest);
copyFile(test_dir, java_file, project_dir, dest);
expect(s).toHaveBeenCalled();
expect(s).toHaveBeenCalledWith('-f', java_file, resolvedDest);
expect(s).toHaveBeenCalledWith(java_file, resolvedDest);
shell.rm('-rf', project_dir);
fs.removeSync(project_dir);
});
});
describe('copyNewFile', function () {
it('Test#008 : should throw if target path exists', function () {
shell.mkdir('-p', dest);
fs.ensureDirSync(dest);
expect(function () { copyNewFile(test_dir, src, project_dir, dest); })
.toThrow(new Error('"' + dest + '" already exists!'));
shell.rm('-rf', dest);
fs.removeSync(dest);
});
});
describe('deleteJava', function () {
beforeEach(function () {
shell.mkdir('-p', java_dir);
fs.ensureDirSync(java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8');
});
afterEach(function () {
shell.rm('-rf', java_dir);
fs.removeSync(java_dir);
});
it('Test#009 : should call fs.unlinkSync on the provided paths', function () {
var s = spyOn(fs, 'unlinkSync').and.callThrough();
var s = spyOn(fs, 'removeSync').and.callThrough();
deleteJava(project_dir, java_file);
expect(s).toHaveBeenCalled();
expect(s).toHaveBeenCalledWith(path.resolve(project_dir, java_file));
+37 -44
View File
@@ -21,14 +21,13 @@ var rewire = require('rewire');
var common = rewire('../../../bin/templates/cordova/lib/pluginHandlers');
var android = common.__get__('handlers');
var path = require('path');
var fs = require('fs');
var shell = require('shelljs');
var fs = require('fs-extra');
var os = require('os');
var temp = path.join(os.tmpdir(), 'plugman');
var plugins_dir = path.join(temp, 'cordova/plugins');
var dummyplugin = path.join(__dirname, '../../fixtures/org.test.plugins.dummyplugin');
var faultyplugin = path.join(__dirname, '../../fixtures/org.test.plugins.faultyplugin');
var android_studio_project = path.join(__dirname, '../../fixtures/android_studio_project/*');
var android_studio_project = path.join(__dirname, '../../fixtures/android_studio_project');
var PluginInfo = require('cordova-common').PluginInfo;
var AndroidProject = require('../../../bin/templates/cordova/lib/AndroidProject');
@@ -48,14 +47,14 @@ describe('android project handler', function () {
var dummyProject;
beforeEach(function () {
shell.mkdir('-p', temp);
fs.ensureDirSync(temp);
dummyProject = AndroidProject.getProjectFile(temp);
copyFileSpy.calls.reset();
common.__set__('copyFile', copyFileSpy);
});
afterEach(function () {
shell.rm('-rf', temp);
fs.removeSync(temp);
common.__set__('copyFile', copyFileOrig);
});
@@ -75,7 +74,7 @@ describe('android project handler', function () {
describe('of <source-file> elements', function () {
beforeEach(function () {
shell.cp('-rf', android_studio_project, temp);
fs.copySync(android_studio_project, temp);
});
it('Test#003 : should copy stuff from one location to another by calling common.copyFile', function () {
@@ -94,7 +93,7 @@ describe('android project handler', function () {
it('Test#006 : should throw if target file already exists', function () {
// write out a file
let target = path.resolve(temp, 'app', 'src', 'main', 'java', 'com', 'phonegap', 'plugins', 'dummyplugin');
shell.mkdir('-p', target);
fs.ensureDirSync(target);
target = path.join(target, 'DummyPlugin.java');
fs.writeFileSync(target, 'some bs', 'utf-8');
@@ -185,7 +184,7 @@ describe('android project handler', function () {
var copyNewFileSpy = jasmine.createSpy('copyNewFile');
beforeEach(function () {
shell.cp('-rf', android_studio_project, temp);
fs.copySync(android_studio_project, temp);
spyOn(dummyProject, 'addSystemLibrary');
spyOn(dummyProject, 'addSubProject');
@@ -215,7 +214,7 @@ describe('android project handler', function () {
it('Test#010 : should not copy anything if "custom" attribute is not set', function () {
var framework = { src: 'plugin-lib' };
var cpSpy = spyOn(shell, 'cp');
var cpSpy = spyOn(fs, 'copySync');
android.framework.install(framework, dummyPluginInfo, dummyProject);
expect(dummyProject.addSystemLibrary).toHaveBeenCalledWith(someString, framework.src);
expect(cpSpy).not.toHaveBeenCalled();
@@ -282,26 +281,24 @@ describe('android project handler', function () {
describe('uninstallation', function () {
var removeFileOrig = common.__get__('removeFile');
var deleteJavaOrig = common.__get__('deleteJava');
var removeFileSpy = jasmine.createSpy('removeFile');
let originalRemoveSync = fs.removeSync;
var deleteJavaSpy = jasmine.createSpy('deleteJava');
var dummyProject;
let removeSyncSpy;
beforeEach(function () {
shell.mkdir('-p', temp);
shell.mkdir('-p', plugins_dir);
shell.cp('-rf', android_studio_project, temp);
fs.ensureDirSync(temp);
fs.ensureDirSync(plugins_dir);
fs.copySync(android_studio_project, temp);
AndroidProject.purgeCache();
dummyProject = AndroidProject.getProjectFile(temp);
common.__set__('removeFile', removeFileSpy);
removeSyncSpy = spyOn(fs, 'removeSync');
common.__set__('deleteJava', deleteJavaSpy);
});
afterEach(function () {
shell.rm('-rf', temp);
common.__set__('removeFile', removeFileOrig);
originalRemoveSync.call(fs, temp);
common.__set__('deleteJava', deleteJavaOrig);
});
@@ -309,7 +306,7 @@ describe('android project handler', function () {
it('Test#017 : should remove jar files for Android Studio projects', function () {
android['lib-file'].install(valid_libs[0], dummyPluginInfo, dummyProject);
android['lib-file'].uninstall(valid_libs[0], dummyPluginInfo, dummyProject);
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/libs/TestLib.jar'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestLib.jar'));
});
});
@@ -317,7 +314,7 @@ describe('android project handler', function () {
it('Test#018 : should remove files for Android Studio projects', function () {
android['resource-file'].install(valid_resources[0], dummyPluginInfo, dummyProject);
android['resource-file'].uninstall(valid_resources[0], dummyPluginInfo, dummyProject);
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app', 'src', 'main', 'res', 'xml', 'dummy.xml'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app', 'src', 'main', 'res', 'xml', 'dummy.xml'));
});
});
@@ -337,55 +334,55 @@ describe('android project handler', function () {
it('Test#019b : should remove stuff by calling common.removeFile for Android Studio projects, of jar with new app target-dir scheme', function () {
android['source-file'].install(valid_source[2], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[2], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/libs/TestLib.jar'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestLib.jar'));
});
it('Test#019c : should remove stuff by calling common.removeFile for Android Studio projects, of aar with new app target-dir scheme', function () {
android['source-file'].install(valid_source[3], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[3], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/libs/TestAar.aar'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestAar.aar'));
});
it('Test#019d : should remove stuff by calling common.removeFile for Android Studio projects, of xml with old target-dir scheme', function () {
android['source-file'].install(valid_source[4], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[4], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/res/xml/mysettings.xml'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/res/xml/mysettings.xml'));
});
it('Test#019e : should remove stuff by calling common.removeFile for Android Studio projects, of file with other extension with old target-dir scheme', function () {
android['source-file'].install(valid_source[5], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[5], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/res/values/other.extension'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/res/values/other.extension'));
});
it('Test#019f : should remove stuff by calling common.removeFile for Android Studio projects, of aidl with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[6], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[6], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/aidl/com/mytest/myapi.aidl'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/aidl/com/mytest/myapi.aidl'));
});
it('Test#019g : should remove stuff by calling common.removeFile for Android Studio projects, of aar with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[7], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[7], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/libs/testaar2.aar'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/testaar2.aar'));
});
it('Test#019h : should remove stuff by calling common.removeFile for Android Studio projects, of jar with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[8], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[8], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/libs/testjar2.jar'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/testjar2.jar'));
});
it('Test#019i : should remove stuff by calling common.removeFile for Android Studio projects, of .so lib file with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[9], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[9], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/jniLibs/x86/libnative.so'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/jniLibs/x86/libnative.so'));
});
it('Test#019j : should remove stuff by calling common.deleteJava for Android Studio projects, with target-dir that includes "app"', function () {
android['source-file'].install(valid_source[10], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[10], dummyPluginInfo, dummyProject, { android_studio: true });
expect(deleteJavaSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/java/com/appco/DummyPlugin2.java'));
expect(deleteJavaSpy).toHaveBeenCalledWith(dummyProject.projectDir, path.join('app/src/main/java/com/appco/DummyPlugin2.java'));
});
});
@@ -394,7 +391,7 @@ describe('android project handler', function () {
var someString = jasmine.any(String);
beforeEach(function () {
shell.mkdir(path.join(dummyProject.projectDir, dummyPluginInfo.id));
fs.ensureDirSync(path.join(dummyProject.projectDir, dummyPluginInfo.id));
spyOn(dummyProject, 'removeSystemLibrary');
spyOn(dummyProject, 'removeSubProject');
@@ -421,13 +418,13 @@ describe('android project handler', function () {
var framework = { src: 'plugin-lib', custom: true };
android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
expect(dummyProject.removeSubProject).toHaveBeenCalledWith(dummyProject.projectDir, someString);
expect(removeFileSpy).toHaveBeenCalledWith(dummyProject.projectDir, someString);
expect(removeSyncSpy).toHaveBeenCalledWith(someString);
});
it('Test#24 : should install gradleReference using project.removeGradleReference', function () {
var framework = { src: 'plugin-lib', custom: true, type: 'gradleReference' };
android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
expect(removeFileSpy).toHaveBeenCalledWith(dummyProject.projectDir, someString);
expect(removeSyncSpy).toHaveBeenCalledWith(someString);
expect(dummyProject.removeGradleReference).toHaveBeenCalledWith(dummyProject.projectDir, someString);
});
});
@@ -441,8 +438,6 @@ describe('android project handler', function () {
wwwDest = path.resolve(dummyProject.www, 'plugins', dummyPluginInfo.id, jsModule.src);
platformWwwDest = path.resolve(dummyProject.platformWww, 'plugins', dummyPluginInfo.id, jsModule.src);
spyOn(shell, 'rm');
var existsSyncOrig = fs.existsSync;
spyOn(fs, 'existsSync').and.callFake(function (file) {
if ([wwwDest, platformWwwDest].indexOf(file) >= 0) return true;
@@ -452,14 +447,14 @@ describe('android project handler', function () {
it('Test#025 : should put module to both www and platform_www when options.usePlatformWww flag is specified', function () {
android['js-module'].uninstall(jsModule, dummyPluginInfo, dummyProject, { usePlatformWww: true });
expect(shell.rm).toHaveBeenCalledWith('-Rf', wwwDest);
expect(shell.rm).toHaveBeenCalledWith('-Rf', platformWwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(platformWwwDest);
});
it('Test#026 : should put module to www only when options.usePlatformWww flag is not specified', function () {
android['js-module'].uninstall(jsModule, dummyPluginInfo, dummyProject);
expect(shell.rm).toHaveBeenCalledWith('-Rf', wwwDest);
expect(shell.rm).not.toHaveBeenCalledWith('-Rf', platformWwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(removeSyncSpy).not.toHaveBeenCalledWith(platformWwwDest);
});
});
@@ -471,8 +466,6 @@ describe('android project handler', function () {
wwwDest = path.resolve(dummyProject.www, asset.target);
platformWwwDest = path.resolve(dummyProject.platformWww, asset.target);
spyOn(shell, 'rm');
var existsSyncOrig = fs.existsSync;
spyOn(fs, 'existsSync').and.callFake(function (file) {
if ([wwwDest, platformWwwDest].indexOf(file) >= 0) return true;
@@ -482,14 +475,14 @@ describe('android project handler', function () {
it('Test#027 : should put module to both www and platform_www when options.usePlatformWww flag is specified', function () {
android.asset.uninstall(asset, dummyPluginInfo, dummyProject, { usePlatformWww: true });
expect(shell.rm).toHaveBeenCalledWith(jasmine.any(String), wwwDest);
expect(shell.rm).toHaveBeenCalledWith(jasmine.any(String), platformWwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(platformWwwDest);
});
it('Test#028 : should put module to www only when options.usePlatformWww flag is not specified', function () {
android.asset.uninstall(asset, dummyPluginInfo, dummyProject);
expect(shell.rm).toHaveBeenCalledWith(jasmine.any(String), wwwDest);
expect(shell.rm).not.toHaveBeenCalledWith(jasmine.any(String), platformWwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(removeSyncSpy).not.toHaveBeenCalledWith(platformWwwDest);
});
});
});