cordova-android/spec/unit/builders/ProjectBuilder.spec.js

318 lines
12 KiB
JavaScript

/*
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.
*/
const fs = require('fs-extra');
const path = require('path');
const rewire = require('rewire');
const CordovaError = require('cordova-common').CordovaError;
describe('ProjectBuilder', () => {
const rootDir = '/root';
let builder;
let ProjectBuilder;
let execaSpy;
beforeEach(() => {
execaSpy = jasmine.createSpy('execa').and.returnValue(new Promise(() => {}));
ProjectBuilder = rewire('../../../bin/templates/cordova/lib/builders/ProjectBuilder');
ProjectBuilder.__set__('execa', execaSpy);
builder = new ProjectBuilder(rootDir);
});
describe('constructor', () => {
it('should set the project directory to the passed argument', () => {
expect(builder.root).toBe(rootDir);
});
it('should set the project directory to three folders up', () => {
ProjectBuilder.__set__('__dirname', 'projecttest/platforms/android/app');
builder = new ProjectBuilder();
expect(builder.root).toMatch(/projecttest$/);
});
});
describe('getArgs', () => {
beforeEach(() => {
spyOn(fs, 'existsSync').and.returnValue(true);
});
it('should set release argument', () => {
const args = builder.getArgs('release', {});
expect(args[0]).toBe('cdvBuildRelease');
});
it('should set debug argument', () => {
const args = builder.getArgs('debug', {});
expect(args[0]).toBe('cdvBuildDebug');
});
it('should set apk release', () => {
const args = builder.getArgs('release', {
packageType: 'apk'
});
expect(args[0]).withContext(args).toBe('cdvBuildRelease');
});
it('should set apk debug', () => {
const args = builder.getArgs('debug', {
packageType: 'apk'
});
expect(args[0]).withContext(args).toBe('cdvBuildDebug');
});
it('should set bundle release', () => {
const args = builder.getArgs('release', {
packageType: 'bundle'
});
expect(args[0]).withContext(args).toBe(':app:bundleRelease');
});
it('should set bundle debug', () => {
const args = builder.getArgs('debug', {
packageType: 'bundle'
});
expect(args[0]).withContext(args).toBe(':app:bundleDebug');
});
it('should add architecture if it is passed', () => {
const arch = 'unittest';
const args = builder.getArgs('debug', { arch });
expect(args).toContain(`-PcdvBuildArch=${arch}`);
});
it('should clean apk', () => {
const args = builder.getArgs('clean', {
packageType: 'apk'
});
expect(args[0]).toBe('clean');
});
it('should clean bundle', () => {
const args = builder.getArgs('clean', {
packageType: 'bundle'
});
expect(args[0]).toBe('clean');
});
describe('should accept extra arguments', () => {
it('apk', () => {
const args = builder.getArgs('debug', {
extraArgs: ['-PcdvVersionCode=12344']
});
expect(args).toContain('-PcdvVersionCode=12344');
});
it('bundle', () => {
const args = builder.getArgs('debug', {
packageType: 'bundle',
extraArgs: ['-PcdvVersionCode=12344']
});
expect(args).toContain('-PcdvVersionCode=12344');
});
});
});
describe('runGradleWrapper', () => {
it('should run the provided gradle command if a gradle wrapper does not already exist', () => {
spyOn(fs, 'existsSync').and.returnValue(false);
builder.runGradleWrapper('/my/sweet/gradle');
expect(execaSpy).toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));
});
it('should do nothing if a gradle wrapper exists in the project directory', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
builder.runGradleWrapper('/my/sweet/gradle');
expect(execaSpy).not.toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));
});
});
describe('extractRealProjectNameFromManifest', () => {
it('should get the project name from the Android Manifest', () => {
const projectName = 'unittestproject';
const projectId = `io.cordova.${projectName}`;
const manifest = `<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="${projectId}"></manifest>`;
spyOn(fs, 'readFileSync').and.returnValue(manifest);
expect(builder.extractRealProjectNameFromManifest()).toBe(projectName);
});
it('should throw an error if there is no package in the Android manifest', () => {
const manifest = `<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"></manifest>`;
spyOn(fs, 'readFileSync').and.returnValue(manifest);
expect(() => builder.extractRealProjectNameFromManifest()).toThrow(jasmine.any(CordovaError));
});
});
describe('build', () => {
beforeEach(() => {
spyOn(builder, 'getArgs');
});
it('should set build type to debug', () => {
const opts = { buildType: 'debug' };
builder.build(opts);
expect(builder.getArgs).toHaveBeenCalledWith('debug', opts);
});
it('should set build type to release', () => {
const opts = { buildType: 'release' };
builder.build(opts);
expect(builder.getArgs).toHaveBeenCalledWith('release', opts);
});
it('should default build type to release', () => {
const opts = {};
builder.build(opts);
expect(builder.getArgs).toHaveBeenCalledWith('release', opts);
});
it('should spawn gradle with correct args', () => {
const testArgs = ['test', 'args', '-c'];
builder.getArgs.and.returnValue(testArgs);
builder.build({});
expect(execaSpy).toHaveBeenCalledWith(path.join(rootDir, 'gradlew'), testArgs, jasmine.anything());
});
it('should reject if the spawn fails', () => {
const errorMessage = 'Test error';
execaSpy.and.rejectWith(new Error(errorMessage));
return builder.build({}).then(
() => fail('Unexpectedly resolved'),
error => {
expect(error.message).toBe(errorMessage);
}
);
});
it('should check the Android target if failed to find target', () => {
const checkReqsSpy = jasmine.createSpyObj('check_reqs', ['check_android_target']);
const testError = 'failed to find target with hash string';
ProjectBuilder.__set__('check_reqs', checkReqsSpy);
checkReqsSpy.check_android_target.and.resolveTo();
execaSpy.and.rejectWith(testError);
return builder.build({}).then(
() => fail('Unexpectedly resolved'),
error => {
expect(checkReqsSpy.check_android_target).toHaveBeenCalled();
expect(error).toBe(testError);
}
);
});
});
describe('clean', () => {
beforeEach(() => {
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 = {};
return builder.clean(opts).then(() => {
expect(builder.getArgs).toHaveBeenCalledWith('clean', opts);
});
});
it('should spawn gradle', () => {
const opts = {};
const gradleArgs = ['test', 'args', '-f'];
builder.getArgs.and.returnValue(gradleArgs);
return builder.clean(opts).then(() => {
expect(execaSpy).toHaveBeenCalledWith(path.join(rootDir, 'gradlew'), gradleArgs, jasmine.anything());
});
});
it('should remove "out" folder', () => {
return builder.clean({}).then(() => {
expect(fs.removeSync).toHaveBeenCalledWith(path.join(rootDir, 'out'));
});
});
it('should remove signing files if they are autogenerated', () => {
const debugSigningFile = path.join(rootDir, 'debug-signing.properties');
const releaseSigningFile = path.join(rootDir, 'release-signing.properties');
spyOn(fs, 'existsSync').and.returnValue(true);
return builder.clean({}).then(() => {
expect(fs.removeSync).toHaveBeenCalledWith(debugSigningFile);
expect(fs.removeSync).toHaveBeenCalledWith(releaseSigningFile);
});
});
it('should not remove signing files if they are not autogenerated', () => {
const debugSigningFile = path.join(rootDir, 'debug-signing.properties');
const releaseSigningFile = path.join(rootDir, 'release-signing.properties');
spyOn(fs, 'existsSync').and.returnValue(false);
return builder.clean({}).then(() => {
expect(fs.removeSync).not.toHaveBeenCalledWith(debugSigningFile);
expect(fs.removeSync).not.toHaveBeenCalledWith(releaseSigningFile);
});
});
});
describe('outputFileComparator', () => {
it('should sort APKs from most recent to oldest, deprioritising unsigned arch-specific builds', () => {
const APKs = {
'app-debug.apk': new Date('2018-04-20'),
'app-release.apk': new Date('2018-05-20'),
'app-debug-x86.apk': new Date('2018-06-01'),
'app-release-x86.apk': new Date('2018-06-20'),
'app-debug-arm.apk': new Date('2018-05-24'),
'app-release-arm.apk': new Date('2018-06-24'),
'app-release-unsigned.apk': new Date('2018-06-28')
};
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'];
spyOn(fs, 'statSync').and.callFake(filename => {
return { mtime: APKs[filename] };
});
const apkArray = Object.keys(APKs);
const sortedApks = apkArray.sort(ProjectBuilder.__get__('outputFileComparator'));
expect(sortedApks).toEqual(expectedResult);
});
});
});