diff --git a/.flowconfig b/.flowconfig index 92991af156f8bb..dd29ffc7434af9 100644 --- a/.flowconfig +++ b/.flowconfig @@ -46,6 +46,9 @@ # Ignore BUCK generated folders .*\.buckd/ +# Ignore RNPM +.*/local-cli/rnpm/.* + .*/node_modules/is-my-json-valid/test/.*\.json .*/node_modules/iconv-lite/encodings/tables/.*\.json .*/node_modules/y18n/test/.*\.json diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000000000..65a8df8d40829e --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +# rnpm +/local-cli/rnpm diff --git a/local-cli/rnpm/core/package.json b/local-cli/rnpm/core/package.json new file mode 100644 index 00000000000000..2ff57ffd09f8ac --- /dev/null +++ b/local-cli/rnpm/core/package.json @@ -0,0 +1,50 @@ +{ + "name": "rnpm", + "version": "1.7.0", + "description": "React Native Package Manager", + "main": "./src/getCommands.js", + "scripts": { + "test": "jest" + }, + "jest": { + "testDirectoryName": "test", + "collectCoverage": true, + "testRunner": "/node_modules/jest-cli/src/testRunners/jasmine/jasmine2.js" + }, + "author": "Amazing React Native Community (https://github.com/facebook/react-native)", + "contributors": [ + "Alexey Kureev (https://github.com/Kureev)", + "Mike Grabowski (https://github.com/grabbou)" + ], + "engines": { + "node": ">= 4.0.0" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/rnpm/rnpm/issues" + }, + "keywords": [ + "react-native", + "native-modules", + "packager", + "rnpm" + ], + "homepage": "https://github.com/rnpm/rnpm#readme", + "dependencies": { + "commander": "^2.9.0", + "glob": "^7.0.1", + "lodash": "^3.10.1", + "rnpm-plugin-install": "^1.1.0", + "rnpm-plugin-link": "^1.7.4", + "update-notifier": "^0.6.0", + "xmldoc": "^0.4.0" + }, + "devDependencies": { + "babel-eslint": "^4.1.5", + "eslint": "^1.9.0", + "mock-fs": "^3.5.0", + "mock-require": "^1.2.1", + "rewire": "^2.5.1", + "jest-cli": "^0.9.0-fb2" + } +} diff --git a/local-cli/rnpm/core/src/config/android/findAndroidAppFolder.js b/local-cli/rnpm/core/src/config/android/findAndroidAppFolder.js new file mode 100644 index 00000000000000..2f1ddb355fc1a1 --- /dev/null +++ b/local-cli/rnpm/core/src/config/android/findAndroidAppFolder.js @@ -0,0 +1,21 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * @param {String} folder Folder to seek in + * @return {String} + */ +module.exports = function findAndroidAppFolder(folder) { + const flat = 'android'; + const nested = path.join('android', 'app'); + + if (fs.existsSync(path.join(folder, nested))) { + return nested; + } + + if (fs.existsSync(path.join(folder, flat))) { + return flat; + } + + return null; +}; diff --git a/local-cli/rnpm/core/src/config/android/findManifest.js b/local-cli/rnpm/core/src/config/android/findManifest.js new file mode 100644 index 00000000000000..3827b4276e7ee5 --- /dev/null +++ b/local-cli/rnpm/core/src/config/android/findManifest.js @@ -0,0 +1,17 @@ +const glob = require('glob'); +const path = require('path'); + +/** + * Find an android application path in the folder + * + * @param {String} folder Name of the folder where to seek + * @return {String} + */ +module.exports = function findManifest(folder) { + const manifestPath = glob.sync(path.join('**', 'AndroidManifest.xml'), { + cwd: folder, + ignore: ['node_modules/**', '**/build/**', 'Examples/**', 'examples/**'], + })[0]; + + return manifestPath ? path.join(folder, manifestPath) : null; +}; diff --git a/local-cli/rnpm/core/src/config/android/findPackageClassName.js b/local-cli/rnpm/core/src/config/android/findPackageClassName.js new file mode 100644 index 00000000000000..30a33b1337096d --- /dev/null +++ b/local-cli/rnpm/core/src/config/android/findPackageClassName.js @@ -0,0 +1,20 @@ +const fs = require('fs'); +const path = require('path'); +const glob = require('glob'); + +/** + * Gets package's class name (class that implements ReactPackage) + * by searching for its declaration in all Java files present in the folder + * + * @param {String} folder Folder to find java files + */ +module.exports = function getPackageClassName(folder) { + const files = glob.sync('**/*.java', { cwd: folder }); + + const packages = files + .map(filePath => fs.readFileSync(path.join(folder, filePath), 'utf8')) + .map(file => file.match(/class (.*) implements ReactPackage/)) + .filter(match => match); + + return packages.length ? packages[0][1] : null; +}; diff --git a/local-cli/rnpm/core/src/config/android/index.js b/local-cli/rnpm/core/src/config/android/index.js new file mode 100644 index 00000000000000..f8277307f1844f --- /dev/null +++ b/local-cli/rnpm/core/src/config/android/index.js @@ -0,0 +1,111 @@ +const path = require('path'); +const fs = require('fs'); +const glob = require('glob'); +const findAndroidAppFolder = require('./findAndroidAppFolder'); +const findManifest = require('./findManifest'); +const readManifest = require('./readManifest'); +const findPackageClassName = require('./findPackageClassName'); + +const getPackageName = (manifest) => manifest.attr.package; + +/** + * Gets android project config by analyzing given folder and taking some + * defaults specified by user into consideration + */ +exports.projectConfig = function projectConfigAndroid(folder, userConfig) { + const src = userConfig.sourceDir || findAndroidAppFolder(folder); + + if (!src) { + return null; + } + + const sourceDir = path.join(folder, src); + const isFlat = sourceDir.indexOf('app') === -1; + const manifestPath = findManifest(sourceDir); + + if (!manifestPath) { + return null; + } + + const manifest = readManifest(manifestPath); + + const packageName = userConfig.packageName || getPackageName(manifest); + const packageFolder = userConfig.packageFolder || + packageName.replace(/\./g, path.sep); + + const mainActivityPath = path.join( + sourceDir, + userConfig.mainActivityPath || `src/main/java/${packageFolder}/MainActivity.java` + ); + + const stringsPath = path.join( + sourceDir, + userConfig.stringsPath || 'src/main/res/values/strings.xml' + ); + + const settingsGradlePath = path.join( + folder, + 'android', + userConfig.settingsGradlePath || 'settings.gradle' + ); + + const assetsPath = path.join( + sourceDir, + userConfig.assetsPath || 'src/main/assets' + ); + + const buildGradlePath = path.join( + sourceDir, + userConfig.buildGradlePath || 'build.gradle' + ); + + return { + sourceDir, + isFlat, + folder, + stringsPath, + manifestPath, + buildGradlePath, + settingsGradlePath, + assetsPath, + mainActivityPath, + }; +}; + +/** + * Same as projectConfigAndroid except it returns + * different config that applies to packages only + */ +exports.dependencyConfig = function dependencyConfigAndroid(folder, userConfig) { + const src = userConfig.sourceDir || findAndroidAppFolder(folder); + + if (!src) { + return null; + } + + const sourceDir = path.join(folder, src); + const manifestPath = findManifest(sourceDir); + + if (!manifestPath) { + return null; + } + + const manifest = readManifest(manifestPath); + const packageName = userConfig.packageName || getPackageName(manifest); + const packageClassName = findPackageClassName(sourceDir); + + /** + * This module has no package to export + */ + if (!packageClassName) { + return null; + } + + const packageImportPath = userConfig.packageImportPath || + `import ${packageName}.${packageClassName};`; + + const packageInstance = userConfig.packageInstance || + `new ${packageClassName}()`; + + return { sourceDir, folder, manifest, packageImportPath, packageInstance }; +}; diff --git a/local-cli/rnpm/core/src/config/android/readManifest.js b/local-cli/rnpm/core/src/config/android/readManifest.js new file mode 100644 index 00000000000000..056447695c19d6 --- /dev/null +++ b/local-cli/rnpm/core/src/config/android/readManifest.js @@ -0,0 +1,10 @@ +const fs = require('fs'); +const xml = require('xmldoc'); + +/** + * @param {String} manifestPath + * @return {XMLDocument} Parsed manifest's content + */ +module.exports = function readManifest(manifestPath) { + return new xml.XmlDocument(fs.readFileSync(manifestPath, 'utf8')); +}; diff --git a/local-cli/rnpm/core/src/config/findAssets.js b/local-cli/rnpm/core/src/config/findAssets.js new file mode 100644 index 00000000000000..0cd2f0dd3b1dfa --- /dev/null +++ b/local-cli/rnpm/core/src/config/findAssets.js @@ -0,0 +1,20 @@ +const glob = require('glob'); +const path = require('path'); + +const findAssetsInFolder = (folder) => + glob.sync(path.join(folder, '**'), { nodir: true }); + +/** + * Given an array of assets folders, e.g. ['Fonts', 'Images'], + * it globs in them to find all files that can be copied. + * + * It returns an array of absolute paths to files found. + */ +module.exports = function findAssets(folder, assets) { + return (assets || []) + .map(assetsFolder => path.join(folder, assetsFolder)) + .reduce((assets, assetsFolder) => + assets.concat(findAssetsInFolder(assetsFolder)), + [] + ); +}; diff --git a/local-cli/rnpm/core/src/config/index.js b/local-cli/rnpm/core/src/config/index.js new file mode 100644 index 00000000000000..ac75735108204d --- /dev/null +++ b/local-cli/rnpm/core/src/config/index.js @@ -0,0 +1,44 @@ +const path = require('path'); + +const android = require('./android'); +const ios = require('./ios'); +const findAssets = require('./findAssets'); +const wrapCommands = require('./wrapCommands'); + +const getRNPMConfig = (folder) => + require(path.join(folder, './package.json')).rnpm || {}; + +/** + * Returns project config from the current working directory + * @return {Object} + */ +exports.getProjectConfig = function getProjectConfig() { + const folder = process.cwd(); + const rnpm = getRNPMConfig(folder); + + return Object.assign({}, rnpm, { + ios: ios.projectConfig(folder, rnpm.ios || {}), + android: android.projectConfig(folder, rnpm.android || {}), + assets: findAssets(folder, rnpm.assets), + }); +}; + +/** + * Returns a dependency config from node_modules/ + * @param {String} packageName Dependency name + * @return {Object} + */ +exports.getDependencyConfig = function getDependencyConfig(packageName) { + const folder = path.join(process.cwd(), 'node_modules', packageName); + const rnpm = getRNPMConfig( + path.join(process.cwd(), 'node_modules', packageName.split('/')[0]) + ); + + return Object.assign({}, rnpm, { + ios: ios.dependencyConfig(folder, rnpm.ios || {}), + android: android.dependencyConfig(folder, rnpm.android || {}), + assets: findAssets(folder, rnpm.assets), + commands: wrapCommands(rnpm.commands), + params: rnpm.params || [], + }); +}; diff --git a/local-cli/rnpm/core/src/config/ios/findProject.js b/local-cli/rnpm/core/src/config/ios/findProject.js new file mode 100644 index 00000000000000..17c99cf4880354 --- /dev/null +++ b/local-cli/rnpm/core/src/config/ios/findProject.js @@ -0,0 +1,47 @@ +const glob = require('glob'); +const path = require('path'); + +/** + * Glob pattern to look for xcodeproj + */ +const GLOB_PATTERN = '**/*.xcodeproj'; + +/** + * Regexp matching all test projects + */ +const TEST_PROJECTS = /test|example|sample/i; + +/** + * Base iOS folder + */ +const IOS_BASE = 'ios'; + +/** + * These folders will be excluded from search to speed it up + */ +const GLOB_EXCLUDE_PATTERN = ['**/@(Pods|node_modules)/**']; + +/** + * Finds iOS project by looking for all .xcodeproj files + * in given folder. + * + * Returns first match if files are found or null + * + * Note: `./ios/*.xcodeproj` are returned regardless of the name + */ +module.exports = function findProject(folder) { + const projects = glob + .sync(GLOB_PATTERN, { + cwd: folder, + ignore: GLOB_EXCLUDE_PATTERN, + }) + .filter(project => { + return path.dirname(project) === IOS_BASE || !TEST_PROJECTS.test(project); + }); + + if (projects.length === 0) { + return null; + } + + return projects[0]; +}; diff --git a/local-cli/rnpm/core/src/config/ios/index.js b/local-cli/rnpm/core/src/config/ios/index.js new file mode 100644 index 00000000000000..04ada20bfc84f8 --- /dev/null +++ b/local-cli/rnpm/core/src/config/ios/index.js @@ -0,0 +1,31 @@ +const path = require('path'); +const findProject = require('./findProject'); + +/** + * Returns project config by analyzing given folder and applying some user defaults + * when constructing final object + */ +exports.projectConfig = function projectConfigIOS(folder, userConfig) { + const project = userConfig.project || findProject(folder); + + /** + * No iOS config found here + */ + if (!project) { + return null; + } + + const projectPath = path.join(folder, project); + + return { + sourceDir: path.dirname(projectPath), + folder: folder, + pbxprojPath: path.join(projectPath, 'project.pbxproj'), + projectPath: projectPath, + projectName: path.basename(projectPath), + libraryFolder: userConfig.libraryFolder || 'Libraries', + plist: userConfig.plist || [], + }; +}; + +exports.dependencyConfig = exports.projectConfig; diff --git a/local-cli/rnpm/core/src/config/wrapCommands.js b/local-cli/rnpm/core/src/config/wrapCommands.js new file mode 100644 index 00000000000000..7be7f1cf36a95c --- /dev/null +++ b/local-cli/rnpm/core/src/config/wrapCommands.js @@ -0,0 +1,9 @@ +const makeCommand = require('../makeCommand'); + +module.exports = function wrapCommands(commands) { + const mappedCommands = {}; + Object.keys(commands || []).forEach((k) => + mappedCommands[k] = makeCommand(commands[k]) + ); + return mappedCommands; +}; diff --git a/local-cli/rnpm/core/src/findPlugins.js b/local-cli/rnpm/core/src/findPlugins.js new file mode 100644 index 00000000000000..58d38559ee9e86 --- /dev/null +++ b/local-cli/rnpm/core/src/findPlugins.js @@ -0,0 +1,37 @@ +const path = require('path'); +const fs = require('fs'); +const union = require('lodash').union; +const uniq = require('lodash').uniq; +const flatten = require('lodash').flatten; + +/** + * Filter dependencies by name pattern + * @param {String} dependency Name of the dependency + * @return {Boolean} If dependency is a rnpm plugin + */ +const isPlugin = (dependency) => !!~dependency.indexOf('rnpm-plugin-'); + +const findPluginInFolder = (folder) => { + var pjson; + try { + pjson = require(path.join(folder, 'package.json')); + } catch (e) { + return []; + } + + const deps = union( + Object.keys(pjson.dependencies || {}), + Object.keys(pjson.devDependencies || {}) + ); + + return deps.filter(isPlugin); +}; + +/** + * Find plugins in package.json of the given folder + * @param {String} folder Path to the folder to get the package.json from + * @type {Array} Array of plugins or an empty array if no package.json found + */ +module.exports = function findPlugins(folders) { + return uniq(flatten(folders.map(findPluginInFolder))); +}; diff --git a/local-cli/rnpm/core/src/getCommands.js b/local-cli/rnpm/core/src/getCommands.js new file mode 100644 index 00000000000000..0de2cf7feadbaa --- /dev/null +++ b/local-cli/rnpm/core/src/getCommands.js @@ -0,0 +1,20 @@ +const path = require('path'); +const fs = require('fs'); +const uniq = require('lodash').uniq; +const flattenDeep = require('lodash').flattenDeep; +const findPlugins = require('./findPlugins'); + +/** + * @return {Array} Array of commands + */ +module.exports = function getCommands() { + const rnpmRoot = path.join(__dirname, '..'); + const appRoot = process.cwd(); + + return uniq( + flattenDeep([ + findPlugins([rnpmRoot]).map(require), + findPlugins([appRoot]).map(name => require(path.join(appRoot, 'node_modules', name))), + ]) + , 'name'); +}; diff --git a/local-cli/rnpm/core/src/makeCommand.js b/local-cli/rnpm/core/src/makeCommand.js new file mode 100644 index 00000000000000..5175f0e6877b2b --- /dev/null +++ b/local-cli/rnpm/core/src/makeCommand.js @@ -0,0 +1,25 @@ +const spawn = require('child_process').spawn; + +module.exports = function makeCommand(command) { + return (cb) => { + if (!cb) { + throw new Error(`You missed a callback function for the ${command} command`); + } + + const args = command.split(' '); + const cmd = args.shift(); + + const commandProcess = spawn(cmd, args, { + stdio: 'inherit', + stdin: 'inherit', + }); + + commandProcess.on('close', function prelink(code) { + if (code) { + throw new Error(`Error occured during executing "${command}" command`); + } + + cb(); + }); + }; +}; diff --git a/local-cli/rnpm/core/test/android/findAndroidAppFolder.spec.js b/local-cli/rnpm/core/test/android/findAndroidAppFolder.spec.js new file mode 100644 index 00000000000000..fe6223aadda65b --- /dev/null +++ b/local-cli/rnpm/core/test/android/findAndroidAppFolder.spec.js @@ -0,0 +1,30 @@ +jest.autoMockOff(); + +const findAndroidAppFolder = require('../../src/config/android/findAndroidAppFolder'); +const mockFs = require('mock-fs'); +const mocks = require('../fixtures/android'); + +describe('android::findAndroidAppFolder', () => { + beforeAll(() => mockFs({ + empty: {}, + nested: { + android: { + app: mocks.valid, + }, + }, + flat: { + android: mocks.valid, + }, + })); + + it('should return an android app folder if it exists in the given folder', () => { + expect(findAndroidAppFolder('flat')).toBe('android'); + expect(findAndroidAppFolder('nested')).toBe('android/app'); + }); + + it('should return `null` if there\'s no android app folder', () => { + expect(findAndroidAppFolder('empty')).toBe(null); + }); + + afterAll(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/android/findManifest.spec.js b/local-cli/rnpm/core/test/android/findManifest.spec.js new file mode 100644 index 00000000000000..27a81fd6d4d954 --- /dev/null +++ b/local-cli/rnpm/core/test/android/findManifest.spec.js @@ -0,0 +1,25 @@ +jest.autoMockOff(); + +const findManifest = require('../../src/config/android/findManifest'); +const mockFs = require('mock-fs'); +const mocks = require('../fixtures/android'); + +describe('android::findManifest', () => { + + beforeAll(() => mockFs({ + empty: {}, + flat: { + android: mocks.valid, + }, + })); + + it('should return a manifest path if file exists in the folder', () => { + expect(typeof findManifest('flat')).toBe('string'); + }); + + it('should return `null` if there is no manifest in the folder', () => { + expect(findManifest('empty')).toBe(null); + }); + + afterAll(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/android/findPackageClassName.spec.js b/local-cli/rnpm/core/test/android/findPackageClassName.spec.js new file mode 100644 index 00000000000000..fbdd614a16acdb --- /dev/null +++ b/local-cli/rnpm/core/test/android/findPackageClassName.spec.js @@ -0,0 +1,25 @@ +jest.autoMockOff(); + +const findPackageClassName = require('../../src/config/android/findPackageClassName'); +const mockFs = require('mock-fs'); +const mocks = require('../fixtures/android'); + +describe('android::findPackageClassName', () => { + + beforeAll(() => mockFs({ + empty: {}, + flat: { + android: mocks.valid, + }, + })); + + it('should return manifest content if file exists in the folder', () => { + expect(typeof findPackageClassName('flat')).toBe('string'); + }); + + it('should return `null` if there\'s no matches', () => { + expect(findPackageClassName('empty')).toBe(null); + }); + + afterAll(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/android/getDependencyConfig.spec.js b/local-cli/rnpm/core/test/android/getDependencyConfig.spec.js new file mode 100644 index 00000000000000..15126a2de97166 --- /dev/null +++ b/local-cli/rnpm/core/test/android/getDependencyConfig.spec.js @@ -0,0 +1,49 @@ +jest.autoMockOff(); + +const getDependencyConfig = require('../../src/config/android').dependencyConfig; +const mockFs = require('mock-fs'); +const mocks = require('../fixtures/android'); +const userConfig = {}; + +describe('android::getDependencyConfig', () => { + + beforeAll(() => mockFs({ + empty: {}, + nested: { + android: { + app: mocks.valid, + }, + }, + corrupted: { + android: { + app: mocks.corrupted, + }, + }, + noPackage: { + android: {}, + }, + })); + + it('should return an object with android project configuration', () => { + expect(getDependencyConfig('nested', userConfig)).not.toBe(null); + expect(typeof getDependencyConfig('nested', userConfig)).toBe('object'); + }); + + it('should return `null` if manifest file hasn\'t been found', () => { + expect(getDependencyConfig('empty', userConfig)).toBe(null); + }); + + it('should return `null` if android project was not found', () => { + expect(getDependencyConfig('empty', userConfig)).toBe(null); + }); + + it('should return `null` if android project does not contain ReactPackage', () => { + expect(getDependencyConfig('noPackage', userConfig)).toBe(null); + }); + + it('should return `null` if it can\'t find a packageClassName', () => { + expect(getDependencyConfig('corrupted', userConfig)).toBe(null); + }); + + afterAll(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/android/getProjectConfig.spec.js b/local-cli/rnpm/core/test/android/getProjectConfig.spec.js new file mode 100644 index 00000000000000..1e62dbeca0b5bc --- /dev/null +++ b/local-cli/rnpm/core/test/android/getProjectConfig.spec.js @@ -0,0 +1,56 @@ +jest.autoMockOff(); + +const getProjectConfig = require('../../src/config/android').projectConfig; +const mockFs = require('mock-fs'); +const mocks = require('../fixtures/android'); + +describe('android::getProjectConfig', () => { + beforeAll(() => mockFs({ + empty: {}, + nested: { + android: { + app: mocks.valid, + }, + }, + flat: { + android: mocks.valid, + }, + noManifest: { + android: {}, + }, + })); + + it('should return `null` if manifest file hasn\'t been found', () => { + const userConfig = {}; + const folder = 'noManifest'; + + expect(getProjectConfig(folder, userConfig)).toBe(null); + }); + + describe('return an object with android project configuration for', () => { + it('nested structure', () => { + const userConfig = {}; + const folder = 'nested'; + + expect(getProjectConfig(folder, userConfig)).not.toBe(null); + expect(typeof getProjectConfig(folder, userConfig)).toBe('object'); + }); + + it('flat structure', () => { + const userConfig = {}; + const folder = 'flat'; + + expect(getProjectConfig(folder, userConfig)).not.toBe(null); + expect(typeof getProjectConfig(folder, userConfig)).toBe('object'); + }); + }); + + it('should return `null` if android project was not found', () => { + const userConfig = {}; + const folder = 'empty'; + + expect(getProjectConfig(folder, userConfig)).toBe(null); + }); + + afterAll(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/android/readManifest.spec.js b/local-cli/rnpm/core/test/android/readManifest.spec.js new file mode 100644 index 00000000000000..571e183415361e --- /dev/null +++ b/local-cli/rnpm/core/test/android/readManifest.spec.js @@ -0,0 +1,31 @@ +jest.autoMockOff(); + +const findManifest = require('../../src/config/android/findManifest'); +const readManifest = require('../../src/config/android/readManifest'); +const mockFs = require('mock-fs'); +const mocks = require('../fixtures/android'); + +describe('android::readManifest', () => { + + beforeAll(() => mockFs({ + empty: {}, + nested: { + android: { + app: mocks.valid, + }, + }, + })); + + it('should return manifest content if file exists in the folder', () => { + const manifestPath = findManifest('nested'); + expect(readManifest(manifestPath)).not.toBe(null); + expect(typeof readManifest(manifestPath)).toBe('object'); + }); + + it('should throw an error if there is no manifest in the folder', () => { + const fakeManifestPath = findManifest('empty'); + expect(() => readManifest(fakeManifestPath)).toThrow(); + }); + + afterAll(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/findAssets.spec.js b/local-cli/rnpm/core/test/findAssets.spec.js new file mode 100644 index 00000000000000..b31d5fac30cb61 --- /dev/null +++ b/local-cli/rnpm/core/test/findAssets.spec.js @@ -0,0 +1,31 @@ +jest.autoMockOff(); + +const findAssets = require('../src/config/findAssets'); +const mockFs = require('mock-fs'); +const dependencies = require('./fixtures/dependencies'); +const isArray = (arg) => + Object.prototype.toString.call(arg) === '[object Array]'; + +describe('findAssets', () => { + + beforeEach(() => mockFs({ testDir: dependencies.withAssets })); + + it('should return an array of all files in given folders', () => { + const assets = findAssets('testDir', ['fonts', 'images']); + + expect(isArray(assets)).toBeTruthy(); + expect(assets.length).toEqual(3); + }); + + it('should prepend assets paths with the folder path', () => { + const assets = findAssets('testDir', ['fonts', 'images']); + + assets.forEach(assetPath => expect(assetPath).toContain('testDir')); + }); + + it('should return an empty array if given assets are null', () => { + expect(findAssets('testDir', null).length).toEqual(0); + }); + + afterEach(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/findPlugins.js b/local-cli/rnpm/core/test/findPlugins.js new file mode 100644 index 00000000000000..c7285687357351 --- /dev/null +++ b/local-cli/rnpm/core/test/findPlugins.js @@ -0,0 +1,46 @@ +jest.autoMockOff(); + +const path = require('path'); +const findPlugins = require('../src/findPlugins'); + +const pjsonPath = path.join(process.cwd(), 'package.json'); +const isArray = (arg) => + Object.prototype.toString.call(arg) === '[object Array]'; + +describe('findPlugins', () => { + + it('should return an array of dependencies', () => { + jest.setMock(pjsonPath, { + dependencies: { 'rnpm-plugin-test': '*' }, + }); + expect(findPlugins([process.cwd()]).length).toBe(1); + expect(findPlugins([process.cwd()])[0]).toBe('rnpm-plugin-test'); + }); + + it('should return an empty array if there\'re no plugins in this folder', () => { + jest.setMock(pjsonPath, {}); + expect(findPlugins([process.cwd()]).length).toBe(0); + }); + + it('should return an empty array if there\'s no package.json in the supplied folder', () => { + expect(isArray(findPlugins(['fake-path']))).toBeTruthy(); + expect(findPlugins(['fake-path']).length).toBe(0); + }); + + it('should return plugins from both dependencies and dev dependencies', () => { + jest.setMock(pjsonPath, { + dependencies: { 'rnpm-plugin-test': '*' }, + devDependencies: { 'rnpm-plugin-test-2': '*' }, + }); + expect(findPlugins([process.cwd()]).length).toEqual(2); + }); + + it('should return unique list of plugins', () => { + jest.setMock(pjsonPath, { + dependencies: { 'rnpm-plugin-test': '*' }, + devDependencies: { 'rnpm-plugin-test': '*' }, + }); + expect(findPlugins([process.cwd()]).length).toEqual(1); + }); + +}); diff --git a/local-cli/rnpm/core/test/fixtures/android.js b/local-cli/rnpm/core/test/fixtures/android.js new file mode 100644 index 00000000000000..ebecf9b8c9029f --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/android.js @@ -0,0 +1,49 @@ +const fs = require('fs'); +const path = require('path'); + +const manifest = fs.readFileSync(path.join(__dirname, './files/AndroidManifest.xml')); +const mainJavaClass = fs.readFileSync(path.join(__dirname, './files/Main.java')); + +exports.valid = { + src: { + 'AndroidManifest.xml': manifest, + main: { + com: { + some: { + example: { + 'Main.java': mainJavaClass, + 'ReactPackage.java': fs.readFileSync(path.join(__dirname, './files/ReactPackage.java')), + }, + }, + }, + }, + }, +}; + +exports.corrupted = { + src: { + 'AndroidManifest.xml': manifest, + main: { + com: { + some: { + example: {}, + }, + }, + }, + }, +}; + +exports.noPackage = { + src: { + 'AndroidManifest.xml': manifest, + main: { + com: { + some: { + example: { + 'Main.java': mainJavaClass, + }, + }, + }, + }, + }, +}; diff --git a/local-cli/rnpm/core/test/fixtures/commands.js b/local-cli/rnpm/core/test/fixtures/commands.js new file mode 100644 index 00000000000000..cbe4193be29e78 --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/commands.js @@ -0,0 +1,15 @@ +exports.single = { + func: () => {}, + description: 'Test action', + name: 'test', +}; + +exports.multiple = [{ + func: () => {}, + description: 'Test action #1', + name: 'test1', +}, { + func: () => {}, + description: 'Test action #2', + name: 'test2', +}]; diff --git a/local-cli/rnpm/core/test/fixtures/dependencies.js b/local-cli/rnpm/core/test/fixtures/dependencies.js new file mode 100644 index 00000000000000..ad0ce62529d81a --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/dependencies.js @@ -0,0 +1,27 @@ +const fs = require('fs'); +const path = require('path'); +const android = require('./android'); + +const pjson = fs.readFileSync(path.join(__dirname, 'files', 'package.json')); + +module.exports = { + valid: { + 'package.json': pjson, + android: android.valid, + }, + withAssets: { + 'package.json': pjson, + android: android.valid, + fonts: { + 'A.ttf': '', + 'B.ttf': '', + }, + images: { + 'C.jpg': '', + }, + }, + noPackage: { + 'package.json': pjson, + android: android.noPackage, + }, +}; diff --git a/local-cli/rnpm/core/test/fixtures/files/AndroidManifest.xml b/local-cli/rnpm/core/test/fixtures/files/AndroidManifest.xml new file mode 100644 index 00000000000000..83cf6c79916298 --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/files/AndroidManifest.xml @@ -0,0 +1,4 @@ + + diff --git a/local-cli/rnpm/core/test/fixtures/files/Main.java b/local-cli/rnpm/core/test/fixtures/files/Main.java new file mode 100644 index 00000000000000..08dc35a58bfabf --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/files/Main.java @@ -0,0 +1,8 @@ +package com.some.example; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class SomeExamplePackage {} diff --git a/local-cli/rnpm/core/test/fixtures/files/ReactPackage.java b/local-cli/rnpm/core/test/fixtures/files/ReactPackage.java new file mode 100644 index 00000000000000..b1ea146323fc0b --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/files/ReactPackage.java @@ -0,0 +1,35 @@ +package com.some.example; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class SomeExamplePackage implements ReactPackage { + + public SomeExamplePackage() {} + + @Override + public List createNativeModules( + ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new SomeExampleModule(reactContext)); + return modules; + } + + @Override + public List> createJSModules() { + return Collections.emptyList(); + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} diff --git a/local-cli/rnpm/core/test/fixtures/files/package.json b/local-cli/rnpm/core/test/fixtures/files/package.json new file mode 100644 index 00000000000000..cce13033d3a405 --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/files/package.json @@ -0,0 +1,62 @@ +{ + "name": "react-native-vector-icons", + "version": "1.0.0", + "description": "Customizable Icons for React Native with support for NavBar/TabBar, image source and full styling. Choose from 3000+ bundled icons or use your own.", + "main": "index.js", + "bin": { + "generate-icon": "./generate-icon.js" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "rm -rf {Fonts,Entypo.js,EvilIcons.js,FontAwesome.js,Foundation.js,Ionicons.js,MaterialIcons.js,Octicons.js,Zocial.js} && mkdir Fonts && npm run build-entypo && npm run build-evilicons && npm run build-fontawesome && npm run build-foundation && npm run build-ionicons && npm run build-materialicons && npm run build-octicons && npm run build-zocial", + "build-entypo": "mkdir -p tmp/svg && curl https://dl.dropboxusercontent.com/u/4339492/entypo.zip > tmp/entypo.zip && unzip -j tmp/entypo.zip *.svg -x __MACOSX/* -d tmp/svg && fontcustom compile tmp/svg -o tmp -n Entypo -t css -h && node generate-icon tmp/Entypo.css --componentName=Entypo --fontFamily=Entypo > Entypo.js && cp tmp/Entypo.ttf Fonts && rm -rf {tmp,.fontcustom-manifest.json}", + "build-evilicons": "fontcustom compile node_modules/evil-icons/assets/icons -o tmp -n EvilIcons -t css -h && node generate-icon tmp/EvilIcons.css --prefix=.icon-ei- --componentName=EvilIcons --fontFamily=EvilIcons > EvilIcons.js && cp tmp/EvilIcons.ttf Fonts && rm -rf {tmp,.fontcustom-manifest.json}", + "build-fontawesome": "node generate-icon node_modules/font-awesome/css/font-awesome.css --prefix=.fa- --componentName=FontAwesome --fontFamily=FontAwesome > FontAwesome.js && cp node_modules/font-awesome/fonts/fontawesome-webfont.ttf Fonts/FontAwesome.ttf", + "build-foundation": "node generate-icon bower_components/foundation-icon-fonts/foundation-icons.css --prefix=.fi- --componentName=Foundation --fontFamily=fontcustom > Foundation.js && cp bower_components/foundation-icon-fonts/foundation-icons.ttf Fonts/Foundation.ttf", + "build-ionicons": "node generate-icon bower_components/ionicons/css/ionicons.css --prefix=.ion- --componentName=Ionicons --fontFamily=Ionicons > Ionicons.js && cp bower_components/ionicons/fonts/ionicons.ttf Fonts/Ionicons.ttf", + "build-materialicons": "mkdir -p tmp/svg && for f in ./node_modules/material-design-icons/*/svg/production/ic_*_48px.svg; do t=${f/*\\/ic_/}; t=${t/_48px/}; cp \"$f\" \"./tmp/svg/${t//_/-}\"; done && fontcustom compile tmp/svg -o tmp -n MaterialIcons -t css -h && node generate-icon tmp/MaterialIcons.css --componentName=MaterialIcons --fontFamily=MaterialIcons > MaterialIcons.js && cp tmp/MaterialIcons.ttf Fonts && rm -rf {tmp,.fontcustom-manifest.json}", + "build-octicons": "node generate-icon bower_components/octicons/octicons/octicons.css --prefix=.octicon- --componentName=Octicons --fontFamily=octicons > Octicons.js && cp bower_components/octicons/octicons/octicons.ttf Fonts/Octicons.ttf", + "build-zocial": "node generate-icon bower_components/css-social-buttons/css/zocial.css --prefix=.zocial. --componentName=Zocial --fontFamily=zocial > Zocial.js && cp bower_components/css-social-buttons/css/zocial.ttf Fonts/Zocial.ttf" + }, + "keywords": [ + "react-native", + "react-component", + "react-native-component", + "react", + "mobile", + "ios", + "android", + "ui", + "icon", + "icons", + "vector", + "retina", + "font" + ], + "author": { + "name": "Joel Arvidsson", + "email": "joel@oblador.se" + }, + "homepage": "https://github.com/oblador/react-native-vector-icons", + "bugs": { + "url": "https://github.com/oblador/react-native-vector-icons/issues" + }, + "repository": { + "type": "git", + "url": "git://github.com/oblador/react-native-vector-icons.git" + }, + "license": "MIT", + "peerDependencies": { + "react-native": ">=0.4.0 || 0.5.0-rc1 || 0.6.0-rc || 0.7.0-rc || 0.7.0-rc.2 || 0.8.0-rc || 0.8.0-rc.2 || 0.9.0-rc || 0.10.0-rc || 0.11.0-rc || 0.12.0-rc || 0.13.0-rc || 0.14.0-rc || 0.15.0-rc || 0.16.0-rc" + }, + "dependencies": { + "lodash": "^3.8.0", + "yargs": "^3.30.0", + "rnpm-plugin-test": "*" + }, + "devDependencies": { + "evil-icons": "^1.7.6", + "font-awesome": "^4.4.0", + "material-design-icons": "^2.1.1" + } +} diff --git a/local-cli/rnpm/core/test/fixtures/files/project.pbxproj b/local-cli/rnpm/core/test/fixtures/files/project.pbxproj new file mode 100644 index 00000000000000..e5ac7a27b54e32 --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/files/project.pbxproj @@ -0,0 +1,804 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { +/* Begin PBXBuildFile section */ + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; }; + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; + 00E356F31AD99517003FC87E /* androidTestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* androidTestTests.m */; }; + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 672DE8B31B124B8088D0D29F /* libBVLinearGradient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B5255B7628A54AC2A9B4B2A0 /* libBVLinearGradient.a */; }; + 68FEB18F24414EF981BD7940 /* libCodePush.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 53C67FE8F7294B7A83790610 /* libCodePush.a */; }; + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; + C6C437D070BA42D6BE39198B /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A7396DFBAFA4CA092E367F5 /* libRCTVideo.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTActionSheet; + }; + 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTGeolocation; + }; + 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5115D1A9E6B3D00147676; + remoteInfo = RCTImage; + }; + 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B511DB1A9E6C8500147676; + remoteInfo = RCTNetwork; + }; + 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; + remoteInfo = RCTVibration; + }; + 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13B07F861A680F5B00A75B9A; + remoteInfo = androidTest; + }; + 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTSettings; + }; + 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3C86DF461ADF2C930047B81A; + remoteInfo = RCTWebSocket; + }; + 146834031AC3E56700842450 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; + remoteInfo = React; + }; + 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTLinking; + }; + 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5119B1A9E6C1200147676; + remoteInfo = RCTText; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = "../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj"; sourceTree = ""; }; + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; + 00E356EE1AD99517003FC87E /* androidTestTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = androidTestTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 00E356F21AD99517003FC87E /* androidTestTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = androidTestTests.m; sourceTree = ""; }; + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; + 13B07F961A680F5B00A75B9A /* androidTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = androidTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = androidTest/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = androidTest/AppDelegate.m; sourceTree = ""; }; + 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = androidTest/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = androidTest/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = androidTest/main.m; sourceTree = ""; }; + 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; + 53C67FE8F7294B7A83790610 /* libCodePush.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libCodePush.a; sourceTree = ""; }; + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; + B5255B7628A54AC2A9B4B2A0 /* libBVLinearGradient.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libBVLinearGradient.a; sourceTree = ""; }; + 467A6CBCB2164E7D9B673D4C /* CodePush.xcodeproj */ = {isa = PBXFileReference; name = "CodePush.xcodeproj"; path = "../node_modules/react-native-code-push/CodePush.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + FD7121847BA447D8B737F22A /* BVLinearGradient.xcodeproj */ = {isa = PBXFileReference; name = "BVLinearGradient.xcodeproj"; path = "../node_modules/react-native-linear-gradient/BVLinearGradient.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + 409DA945815C46DEB4F254DB /* RCTVideo.xcodeproj */ = {isa = PBXFileReference; name = "RCTVideo.xcodeproj"; path = "../node_modules/react-native-video/RCTVideo.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + 3A7396DFBAFA4CA092E367F5 /* libRCTVideo.a */ = {isa = PBXFileReference; name = "libRCTVideo.a"; path = "libRCTVideo.a"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 00E356EB1AD99517003FC87E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 146834051AC3E58100842450 /* libReact.a in Frameworks */, + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */, + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */, + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */, + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, + 672DE8B31B124B8088D0D29F /* libBVLinearGradient.a in Frameworks */, + 68FEB18F24414EF981BD7940 /* libCodePush.a in Frameworks */, + C6C437D070BA42D6BE39198B /* libRCTVideo.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00C302A81ABCB8CE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302B61ABCB90400DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302BC1ABCB91800DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302D41ABCB9D200DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302E01ABCB9EE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */, + ); + name = Products; + sourceTree = ""; + }; + 00E356EF1AD99517003FC87E /* androidTestTests */ = { + isa = PBXGroup; + children = ( + 00E356F21AD99517003FC87E /* androidTestTests.m */, + 00E356F01AD99517003FC87E /* Supporting Files */, + ); + path = androidTestTests; + sourceTree = ""; + }; + 00E356F01AD99517003FC87E /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 00E356F11AD99517003FC87E /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 139105B71AF99BAD00B5F7CC /* Products */ = { + isa = PBXGroup; + children = ( + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */, + ); + name = Products; + sourceTree = ""; + }; + 139FDEE71B06529A00C62182 /* Products */ = { + isa = PBXGroup; + children = ( + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */, + ); + name = Products; + sourceTree = ""; + }; + 13B07FAE1A68108700A75B9A /* androidTest */ = { + isa = PBXGroup; + children = ( + 008F07F21AC5B25A0029DE68 /* main.jsbundle */, + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.m */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, + 13B07FB71A68108700A75B9A /* main.m */, + ); + name = androidTest; + sourceTree = ""; + }; + 146834001AC3E56700842450 /* Products */ = { + isa = PBXGroup; + children = ( + 146834041AC3E56700842450 /* libReact.a */, + ); + name = Products; + sourceTree = ""; + }; + 78C398B11ACF4ADC00677621 /* Products */ = { + isa = PBXGroup; + children = ( + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */, + ); + name = Products; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + 146833FF1AC3E56700842450 /* React.xcodeproj */, + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */, + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */, + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */, + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */, + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */, + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */, + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */, + 467A6CBCB2164E7D9B673D4C /* CodePush.xcodeproj */, + FD7121847BA447D8B737F22A /* BVLinearGradient.xcodeproj */, + 409DA945815C46DEB4F254DB /* RCTVideo.xcodeproj */, + ); + name = Libraries; + sourceTree = ""; + }; + 832341B11AAA6A8300B99B32 /* Products */ = { + isa = PBXGroup; + children = ( + 832341B51AAA6A8300B99B32 /* libRCTText.a */, + ); + name = Products; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* androidTest */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 00E356EF1AD99517003FC87E /* androidTestTests */, + 83CBBA001A601CBA00E9B192 /* Products */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* androidTest.app */, + 00E356EE1AD99517003FC87E /* androidTestTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 00E356ED1AD99517003FC87E /* androidTestTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "androidTestTests" */; + buildPhases = ( + 00E356EA1AD99517003FC87E /* Sources */, + 00E356EB1AD99517003FC87E /* Frameworks */, + 00E356EC1AD99517003FC87E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 00E356F51AD99517003FC87E /* PBXTargetDependency */, + ); + name = androidTestTests; + productName = androidTestTests; + productReference = 00E356EE1AD99517003FC87E /* androidTestTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13B07F861A680F5B00A75B9A /* androidTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "androidTest" */; + buildPhases = ( + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = androidTest; + productName = "Hello World"; + productReference = 13B07F961A680F5B00A75B9A /* androidTest.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 00E356ED1AD99517003FC87E = { + CreatedOnToolsVersion = 6.2; + TestTargetID = 13B07F861A680F5B00A75B9A; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "androidTest" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */; + ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + }, + { + ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */; + ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + }, + { + ProductGroup = 00C302BC1ABCB91800DB3ED1 /* Products */; + ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + }, + { + ProductGroup = 78C398B11ACF4ADC00677621 /* Products */; + ProjectRef = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + }, + { + ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */; + ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + }, + { + ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */; + ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + }, + { + ProductGroup = 832341B11AAA6A8300B99B32 /* Products */; + ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + }, + { + ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */; + ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + }, + { + ProductGroup = 139FDEE71B06529A00C62182 /* Products */; + ProjectRef = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + }, + { + ProductGroup = 146834001AC3E56700842450 /* Products */; + ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* androidTest */, + 00E356ED1AD99517003FC87E /* androidTestTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTActionSheet.a; + remoteRef = 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTGeolocation.a; + remoteRef = 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTImage.a; + remoteRef = 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTNetwork.a; + remoteRef = 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTVibration.a; + remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTSettings.a; + remoteRef = 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTWebSocket.a; + remoteRef = 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 146834041AC3E56700842450 /* libReact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libReact.a; + remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTLinking.a; + remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 832341B51AAA6A8300B99B32 /* libRCTText.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTText.a; + remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 00E356EC1AD99517003FC87E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "../node_modules/react-native/packager/react-native-xcode.sh"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 00E356EA1AD99517003FC87E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 00E356F31AD99517003FC87E /* androidTestTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13B07F861A680F5B00A75B9A /* androidTest */; + targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 13B07FB21A68108700A75B9A /* Base */, + ); + name = LaunchScreen.xib; + path = androidTest; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 00E356F61AD99517003FC87E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = androidTestTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/androidTest.app/androidTest"; + }; + name = Debug; + }; + 00E356F71AD99517003FC87E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = androidTestTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/androidTest.app/androidTest"; + }; + name = Release; + }; + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEAD_CODE_STRIPPING = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)", + "$(SRCROOT)/../node_modules/react-native-code-push", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/**", + "$(SRCROOT)/../node_modules/react-native-video", + ); + INFOPLIST_FILE = androidTest/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = androidTest; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)", + "$(SRCROOT)/../node_modules/react-native-code-push", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/**", + "$(SRCROOT)/../node_modules/react-native-video", + ); + INFOPLIST_FILE = androidTest/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = androidTest; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)", + "$(SRCROOT)/../node_modules/react-native-code-push", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/**", + "$(SRCROOT)/../node_modules/react-native-video", + ); + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)", + "$(SRCROOT)/../node_modules/react-native-code-push", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/**", + "$(SRCROOT)/../node_modules/react-native-video", + ); + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "androidTestTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00E356F61AD99517003FC87E /* Debug */, + 00E356F71AD99517003FC87E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "androidTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "androidTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/local-cli/rnpm/core/test/fixtures/ios.js b/local-cli/rnpm/core/test/fixtures/ios.js new file mode 100644 index 00000000000000..50943559889555 --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/ios.js @@ -0,0 +1,14 @@ +const fs = require('fs'); +const path = require('path'); + +exports.valid = { + 'demoProject.xcodeproj': { + 'project.pbxproj': fs.readFileSync(path.join(__dirname, './files/project.pbxproj')), + }, +}; + +exports.validTestName = { + 'MyTestProject.xcodeproj': { + 'project.pbxproj': fs.readFileSync(path.join(__dirname, './files/project.pbxproj')), + }, +}; diff --git a/local-cli/rnpm/core/test/fixtures/projects.js b/local-cli/rnpm/core/test/fixtures/projects.js new file mode 100644 index 00000000000000..673aec6483f575 --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/projects.js @@ -0,0 +1,24 @@ +const fs = require('fs'); +const path = require('path'); +const android = require('./android'); +const ios = require('./ios'); + +const flat = { + android: android.valid, + ios: ios.valid, +}; + +const nested = { + android: { + app: android.valid, + }, + ios: ios.valid, +}; + +const withExamples = { + Examples: flat, + ios: ios.valid, + android: android.valid, +}; + +module.exports = { flat, nested, withExamples }; diff --git a/local-cli/rnpm/core/test/getCommands.spec.js b/local-cli/rnpm/core/test/getCommands.spec.js new file mode 100644 index 00000000000000..58c25446fb9200 --- /dev/null +++ b/local-cli/rnpm/core/test/getCommands.spec.js @@ -0,0 +1,171 @@ +jest.autoMockOff(); + +const path = require('path'); +const mock = require('mock-require'); +const rewire = require('rewire'); + +const commands = require('./fixtures/commands'); +const isArray = (arg) => + Object.prototype.toString.call(arg) === '[object Array]'; + +/** + * Paths to two possible `node_modules` locations `rnpm` can be installed + */ +const LOCAL_NODE_MODULES = path.join(process.cwd(), 'node_modules'); +const GLOBAL_NODE_MODULES = '/usr/local/lib/node_modules'; + +/** + * Paths to `package.json` of project, and rnpm - in two installation locations + */ +const APP_JSON = path.join(process.cwd(), 'package.json'); +const GLOBAL_RNPM_PJSON = path.join(GLOBAL_NODE_MODULES, '/rnpm/package.json'); +const LOCAL_RNPM_PJSON = path.join(LOCAL_NODE_MODULES, 'rnpm/package.json'); + +/** + * Sample `rnpm` plugin used in test cases + */ +const SAMPLE_RNPM_PLUGIN = 'rnpm-plugin-test'; + +/** + * Sample `package.json` of RNPM that will be used in test cases + */ +const SAMPLE_RNPM_JSON = { + dependencies: { + [SAMPLE_RNPM_PLUGIN]: '*', + }, +}; + +/** + * Project without `rnpm` plugins defined + */ +const NO_PLUGINS_JSON = { + dependencies: {}, +}; + +const getCommands = rewire('../src/getCommands'); +var revert; + +describe('getCommands', () => { + + afterEach(mock.stopAll); + + describe('in all installations', () => { + + beforeEach(() => { + revert = getCommands.__set__({ + __dirname: path.join(LOCAL_NODE_MODULES, 'rnpm/src'), + }); + mock(APP_JSON, NO_PLUGINS_JSON); + }); + + afterEach(() => revert()); + + it('list of the commands should be a non-empty array', () => { + mock(APP_JSON, NO_PLUGINS_JSON); + mock(LOCAL_RNPM_PJSON, SAMPLE_RNPM_JSON); + mock(SAMPLE_RNPM_PLUGIN, commands.single); + + expect(getCommands().length).not.toBe(0); + expect(isArray(getCommands())).toBeTruthy(); + }); + + it('should export one command', () => { + mock(LOCAL_RNPM_PJSON, SAMPLE_RNPM_JSON); + mock(SAMPLE_RNPM_PLUGIN, commands.single); + + expect(getCommands().length).toEqual(1); + }); + + it('should export multiple commands', () => { + mock(LOCAL_RNPM_PJSON, SAMPLE_RNPM_JSON); + mock(SAMPLE_RNPM_PLUGIN, commands.multiple); + + expect(getCommands().length).toEqual(2); + }); + + it('should export unique list of commands by name', () => { + mock(LOCAL_RNPM_PJSON, { + dependencies: { + [SAMPLE_RNPM_PLUGIN]: '*', + [`${SAMPLE_RNPM_PLUGIN}-2`]: '*', + }, + }); + + mock(SAMPLE_RNPM_PLUGIN, commands.single); + mock(`${SAMPLE_RNPM_PLUGIN}-2`, commands.single); + + expect(getCommands().length).toEqual(1); + }); + + }); + + describe('project plugins', () => { + /** + * In this test suite we only test project plugins thus we make sure + * `rnpm` package.json is properly mocked + */ + beforeEach(() => { + mock(LOCAL_RNPM_PJSON, NO_PLUGINS_JSON); + mock(GLOBAL_RNPM_PJSON, NO_PLUGINS_JSON); + }); + + afterEach(() => revert()); + + it('shoud load when installed locally', () => { + revert = getCommands.__set__({ + __dirname: path.join(LOCAL_NODE_MODULES, 'rnpm/src'), + }); + + mock(APP_JSON, SAMPLE_RNPM_JSON); + mock( + path.join(LOCAL_NODE_MODULES, SAMPLE_RNPM_PLUGIN), + commands.single + ); + + expect(getCommands()[0]).toEqual(commands.single); + }); + + it('should load when installed globally', () => { + revert = getCommands.__set__({ + __dirname: path.join(GLOBAL_NODE_MODULES, 'rnpm/src'), + }); + + mock(APP_JSON, SAMPLE_RNPM_JSON); + mock( + path.join(LOCAL_NODE_MODULES, SAMPLE_RNPM_PLUGIN), + commands.single + ); + + expect(getCommands()[0]).toEqual(commands.single); + }); + + }); + + describe('rnpm and project plugins', () => { + + beforeEach(() => { + revert = getCommands.__set__({ + __dirname: path.join(LOCAL_NODE_MODULES, 'rnpm/src'), + }); + }); + + afterEach(() => revert()); + + it('should load concatenated list of plugins', () => { + mock(APP_JSON, SAMPLE_RNPM_JSON); + mock(LOCAL_RNPM_PJSON, { + dependencies: { + [`${SAMPLE_RNPM_PLUGIN}-2`]: '*', + }, + }); + + mock( + path.join(LOCAL_NODE_MODULES, SAMPLE_RNPM_PLUGIN), + commands.multiple + ); + mock(`${SAMPLE_RNPM_PLUGIN}-2`, commands.single); + + expect(getCommands().length).toEqual(3); + }); + }); +}); diff --git a/local-cli/rnpm/core/test/ios/findProject.spec.js b/local-cli/rnpm/core/test/ios/findProject.spec.js new file mode 100644 index 00000000000000..6626921ba14731 --- /dev/null +++ b/local-cli/rnpm/core/test/ios/findProject.spec.js @@ -0,0 +1,84 @@ +jest.autoMockOff(); + +const findProject = require('../../src/config/ios/findProject'); +const mockFs = require('mock-fs'); +const projects = require('../fixtures/projects'); +const ios = require('../fixtures/ios'); +const userConfig = {}; + +describe('ios::findProject', () => { + it('should return path to xcodeproj if found', () => { + mockFs(projects.flat); + expect(findProject('')).not.toBe(null); + }); + + it('should return null if there\'re no projects', () => { + mockFs({ testDir: projects }); + expect(findProject('')).toBe(null); + }); + + it('should return ios project regardless of its name', () => { + mockFs({ ios: ios.validTestName }); + expect(findProject('')).not.toBe(null); + }); + + it('should ignore node_modules', () => { + mockFs({ node_modules: projects.flat }); + expect(findProject('')).toBe(null); + }); + + it('should ignore Pods', () => { + mockFs({ Pods: projects.flat }); + expect(findProject('')).toBe(null); + }); + + it('should ignore Pods inside `ios` folder', () => { + mockFs({ + ios: { + Pods: projects.flat, + DemoApp: projects.flat.ios, + }, + }); + expect(findProject('')).toBe('ios/DemoApp/demoProject.xcodeproj'); + }); + + it('should ignore xcodeproj from example folders', () => { + mockFs({ + examples: projects.flat, + Examples: projects.flat, + example: projects.flat, + KeychainExample: projects.flat, + Zpp: projects.flat, + }); + + expect(findProject('').toLowerCase()).not.toContain('example'); + }); + + it('should ignore xcodeproj from sample folders', () => { + mockFs({ + samples: projects.flat, + Samples: projects.flat, + sample: projects.flat, + KeychainSample: projects.flat, + Zpp: projects.flat, + }); + + expect(findProject('').toLowerCase()).not.toContain('sample'); + }); + + it('should ignore xcodeproj from test folders at any level', () => { + mockFs({ + test: projects.flat, + IntegrationTests: projects.flat, + tests: projects.flat, + Zpp: { + tests: projects.flat, + src: projects.flat, + }, + }); + + expect(findProject('').toLowerCase()).not.toContain('test'); + }); + + afterEach(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/ios/getProjectConfig.spec.js b/local-cli/rnpm/core/test/ios/getProjectConfig.spec.js new file mode 100644 index 00000000000000..bba3bedb13071b --- /dev/null +++ b/local-cli/rnpm/core/test/ios/getProjectConfig.spec.js @@ -0,0 +1,26 @@ +jest.autoMockOff(); + +const getProjectConfig = require('../../src/config/ios').projectConfig; +const mockFs = require('mock-fs'); +const projects = require('../fixtures/projects'); + +describe('ios::getProjectConfig', () => { + const userConfig = {}; + + beforeEach(() => mockFs({ testDir: projects })); + + it('should return an object with ios project configuration', () => { + const folder = 'testDir/nested'; + + expect(getProjectConfig(folder, userConfig)).not.toBe(null); + expect(typeof getProjectConfig(folder, userConfig)).toBe('object'); + }); + + it('should return `null` if ios project was not found', () => { + const folder = 'testDir/empty'; + + expect(getProjectConfig(folder, userConfig)).toBe(null); + }); + + afterEach(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/makeCommand.spec.js b/local-cli/rnpm/core/test/makeCommand.spec.js new file mode 100644 index 00000000000000..1fb50dfe6629a0 --- /dev/null +++ b/local-cli/rnpm/core/test/makeCommand.spec.js @@ -0,0 +1,35 @@ +var spawnError = false; + +jest.setMock('child_process', { + spawn: () => ({ + on: (ev, cb) => cb(spawnError), + }), +}); +jest.dontMock('../src/makeCommand'); + +const makeCommand = require('../src/makeCommand'); + +describe('makeCommand', () => { + const command = makeCommand('echo'); + + it('should generate a function around shell command', () => { + expect(typeof command).toBe('function'); + }); + + it('should throw an error if there\'s no callback provided', () => { + expect(command).toThrow(); + }); + + it('should invoke a callback after command execution', () => { + const spy = jest.genMockFunction(); + command(spy); + + expect(spy.mock.calls.length).toBe(1); + }); + + it('should throw an error if spawn ended up with error', () => { + spawnError = true; + const cb = jest.genMockFunction(); + expect(() => command(cb)).toThrow(); + }); +}); diff --git a/local-cli/rnpm/install/index.js b/local-cli/rnpm/install/index.js new file mode 100644 index 00000000000000..66891919c75adc --- /dev/null +++ b/local-cli/rnpm/install/index.js @@ -0,0 +1,11 @@ +module.exports = [ + { + func: require('./src/install'), + description: 'Install and link native dependencies', + name: 'install [packageName]', + }, { + func: require('./src/uninstall'), + description: 'Uninstall and unlink native dependencies', + name: 'uninstall [packageName]', + }, +]; diff --git a/local-cli/rnpm/install/package.json b/local-cli/rnpm/install/package.json new file mode 100644 index 00000000000000..e0f4987356749b --- /dev/null +++ b/local-cli/rnpm/install/package.json @@ -0,0 +1,26 @@ +{ + "name": "rnpm-plugin-install", + "version": "1.1.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/rnpm/rnpm-plugin-install.git" + }, + "author": "Alexey Kureev (https://github.com/Kureev)", + "contributors": [ + "Alexey Kureev (https://github.com/Kureev)", + "Mike Grabowski (https://github.com/grabbou)" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/rnpm/rnpm-plugin-install/issues" + }, + "homepage": "https://github.com/rnpm/rnpm-plugin-install#readme", + "dependencies": { + "npmlog": "^2.0.2" + } +} diff --git a/local-cli/rnpm/install/src/install.js b/local-cli/rnpm/install/src/install.js new file mode 100644 index 00000000000000..d9eed2db433ac1 --- /dev/null +++ b/local-cli/rnpm/install/src/install.js @@ -0,0 +1,26 @@ +const spawnSync = require('child_process').spawnSync; +const log = require('npmlog'); +const spawnOpts = { + stdio: 'inherit', + stdin: 'inherit', +}; + +log.heading = 'rnpm-install'; + +module.exports = function install(config, args, callback) { + const name = args[0]; + + var res = spawnSync('npm', ['install', name, '--save'], spawnOpts); + + if (res.status) { + process.exit(res.status); + } + + res = spawnSync('rnpm', ['link', name], spawnOpts); + + if (res.status) { + process.exit(res.status); + } + + log.info(`Module ${name} has been successfully installed & linked`); +}; diff --git a/local-cli/rnpm/install/src/uninstall.js b/local-cli/rnpm/install/src/uninstall.js new file mode 100644 index 00000000000000..a1d7d9fc2d6b49 --- /dev/null +++ b/local-cli/rnpm/install/src/uninstall.js @@ -0,0 +1,26 @@ +const spawnSync = require('child_process').spawnSync; +const log = require('npmlog'); +const spawnOpts = { + stdio: 'inherit', + stdin: 'inherit', +}; + +log.heading = 'rnpm-install'; + +module.exports = function install(config, args, callback) { + const name = args[0]; + + var res = spawnSync('rnpm', ['unlink', name], spawnOpts); + + if (res.status) { + process.exit(res.status); + } + + res = spawnSync('npm', ['uninstall', name], spawnOpts); + + if (res.status) { + process.exit(res.status); + } + + log.info(`Module ${name} has been successfully uninstalled & unlinked`); +}; diff --git a/local-cli/rnpm/link/index.js b/local-cli/rnpm/link/index.js new file mode 100644 index 00000000000000..13b39b59956315 --- /dev/null +++ b/local-cli/rnpm/link/index.js @@ -0,0 +1,9 @@ +module.exports = [{ + func: require('./src/link'), + description: 'Links all native dependencies', + name: 'link [packageName]', +}, { + func: require('./src/unlink'), + description: 'Unlink native dependency', + name: 'unlink ', +}]; diff --git a/local-cli/rnpm/link/package.json b/local-cli/rnpm/link/package.json new file mode 100644 index 00000000000000..74f49e1a3aac4e --- /dev/null +++ b/local-cli/rnpm/link/package.json @@ -0,0 +1,52 @@ +{ + "name": "rnpm-plugin-link", + "version": "1.7.4", + "description": "rnpm plugin that links native dependencies to your project", + "main": "index.js", + "scripts": { + "test": "eslint ./ && mocha ./test --recursive" + }, + "keywords": [ + "rnpm", + "react-native", + "react-native link" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/rnpm/rnpm.git" + }, + "engines": { + "node": ">= 4.0.0" + }, + "author": "Amazing React Native Community (https://github.com/facebook/react-native)", + "contributors": [ + "Alexey Kureev (https://github.com/Kureev)", + "Mike Grabowski (https://github.com/grabbou)" + ], + "bugs": { + "url": "https://github.com/rnpm/rnpm/issues" + }, + "homepage": "https://github.com/rnpm/rnpm#readme", + "license": "MIT", + "dependencies": { + "fs-extra": "^0.26.2", + "glob": "^7.0.0", + "inquirer": "^0.12.0", + "lodash": "^3.10.1", + "mime": "^1.3.4", + "npmlog": "^2.0.0", + "plist": "^1.2.0", + "semver": "^5.1.0", + "to-camel-case": "^1.0.0", + "xcode": "^0.8.2" + }, + "devDependencies": { + "babel-eslint": "^4.1.5", + "chai": "^3.4.1", + "eslint": "^1.9.0", + "mocha": "^2.3.4", + "mock-fs": "^3.5.0", + "mock-require": "^1.2.1", + "sinon": "^1.17.2" + } +} diff --git a/local-cli/rnpm/link/src/android/copyAssets.js b/local-cli/rnpm/link/src/android/copyAssets.js new file mode 100644 index 00000000000000..709bf0b95c770b --- /dev/null +++ b/local-cli/rnpm/link/src/android/copyAssets.js @@ -0,0 +1,17 @@ +const fs = require('fs-extra'); +const path = require('path'); +const groupFilesByType = require('../groupFilesByType'); + +/** + * Copies each file from an array of assets provided to targetPath directory + * + * For now, the only types of files that are handled are: + * - Fonts (otf, ttf) - copied to targetPath/fonts under original name + */ +module.exports = function copyAssetsAndroid(files, targetPath) { + const assets = groupFilesByType(files); + + (assets.font || []).forEach(asset => + fs.copySync(asset, path.join(targetPath, 'fonts', path.basename(asset))) + ); +}; diff --git a/local-cli/rnpm/link/src/android/fs.js b/local-cli/rnpm/link/src/android/fs.js new file mode 100644 index 00000000000000..faa31d671ddb12 --- /dev/null +++ b/local-cli/rnpm/link/src/android/fs.js @@ -0,0 +1,8 @@ +const fs = require('fs-extra'); + +exports.readFile = (file) => + () => fs.readFileSync(file, 'utf8'); + +exports.writeFile = (file, content) => content ? + fs.writeFileSync(file, content, 'utf8') : + (c) => fs.writeFileSync(file, c, 'utf8'); diff --git a/local-cli/rnpm/link/src/android/getPrefix.js b/local-cli/rnpm/link/src/android/getPrefix.js new file mode 100644 index 00000000000000..1c40029e9e0c3f --- /dev/null +++ b/local-cli/rnpm/link/src/android/getPrefix.js @@ -0,0 +1,16 @@ +const semver = require('semver'); +const versions = ['0.20', '0.18', '0.17']; + +module.exports = function getPrefix(rnVersion) { + const version = rnVersion.replace(/-.*/, ''); + var prefix = 'patches/0.20'; + + versions.forEach((item, i) => { + const nextVersion = versions[i + 1]; + if (semver.lt(version, item + '.0') && nextVersion) { + prefix = `patches/${nextVersion}`; + } + }); + + return prefix; +}; diff --git a/local-cli/rnpm/link/src/android/isInstalled.js b/local-cli/rnpm/link/src/android/isInstalled.js new file mode 100644 index 00000000000000..3389a383fbbd7c --- /dev/null +++ b/local-cli/rnpm/link/src/android/isInstalled.js @@ -0,0 +1,8 @@ +const fs = require('fs'); +const makeBuildPatch = require(`./patches/makeBuildPatch`); + +module.exports = function isInstalled(config, name) { + return fs + .readFileSync(config.buildGradlePath) + .indexOf(makeBuildPatch(name).patch) > -1; +}; diff --git a/local-cli/rnpm/link/src/android/patches/0.17/makeImportPatch.js b/local-cli/rnpm/link/src/android/patches/0.17/makeImportPatch.js new file mode 100644 index 00000000000000..c7bcf3d794e8b1 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/0.17/makeImportPatch.js @@ -0,0 +1,6 @@ +module.exports = function makeImportPatch(packageImportPath) { + return { + pattern: 'import android.app.Activity;', + patch: '\n' + packageImportPath, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/0.17/makePackagePatch.js b/local-cli/rnpm/link/src/android/patches/0.17/makePackagePatch.js new file mode 100644 index 00000000000000..ea4552c198bbe7 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/0.17/makePackagePatch.js @@ -0,0 +1,10 @@ +const applyParams = require('../applyParams'); + +module.exports = function makePackagePatch(packageInstance, params, prefix) { + const processedInstance = applyParams(packageInstance, params, prefix); + + return { + pattern: '.addPackage(new MainReactPackage())', + patch: `\n .addPackage(${processedInstance})`, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/0.18/makeImportPatch.js b/local-cli/rnpm/link/src/android/patches/0.18/makeImportPatch.js new file mode 100644 index 00000000000000..687d61bd4fd6c4 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/0.18/makeImportPatch.js @@ -0,0 +1,6 @@ +module.exports = function makeImportPatch(packageImportPath) { + return { + pattern: 'import com.facebook.react.ReactActivity;', + patch: '\n' + packageImportPath, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/0.18/makePackagePatch.js b/local-cli/rnpm/link/src/android/patches/0.18/makePackagePatch.js new file mode 100644 index 00000000000000..6609303902de3e --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/0.18/makePackagePatch.js @@ -0,0 +1,10 @@ +const applyParams = require('../applyParams'); + +module.exports = function makePackagePatch(packageInstance, params, prefix) { + const processedInstance = applyParams(packageInstance, params, prefix); + + return { + pattern: 'new MainReactPackage()', + patch: ',\n ' + processedInstance, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js b/local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js new file mode 100644 index 00000000000000..687d61bd4fd6c4 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js @@ -0,0 +1,6 @@ +module.exports = function makeImportPatch(packageImportPath) { + return { + pattern: 'import com.facebook.react.ReactActivity;', + patch: '\n' + packageImportPath, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/0.20/makePackagePatch.js b/local-cli/rnpm/link/src/android/patches/0.20/makePackagePatch.js new file mode 100644 index 00000000000000..17120b14272211 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/0.20/makePackagePatch.js @@ -0,0 +1,10 @@ +const applyParams = require('../applyParams'); + +module.exports = function makePackagePatch(packageInstance, params, prefix) { + const processedInstance = applyParams(packageInstance, params, prefix); + + return { + pattern: 'new MainReactPackage()', + patch: ',\n ' + processedInstance, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/applyParams.js b/local-cli/rnpm/link/src/android/patches/applyParams.js new file mode 100644 index 00000000000000..90973c2f589a91 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/applyParams.js @@ -0,0 +1,14 @@ +const toCamelCase = require('to-camel-case'); + +module.exports = function applyParams(str, params, prefix) { + return str.replace( + /\$\{(\w+)\}/g, + (pattern, param) => { + const name = toCamelCase(prefix) + '_' + param; + + return params[param] + ? `this.getResources().getString(R.strings.${name})` + : null; + } + ); +}; diff --git a/local-cli/rnpm/link/src/android/patches/applyPatch.js b/local-cli/rnpm/link/src/android/patches/applyPatch.js new file mode 100644 index 00000000000000..67337d41ac243f --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/applyPatch.js @@ -0,0 +1,8 @@ +const fs = require('fs'); + +module.exports = function applyPatch(file, patch) { + fs.writeFileSync(file, fs + .readFileSync(file, 'utf8') + .replace(patch.pattern, match => `${match}${patch.patch}`) + ); +}; diff --git a/local-cli/rnpm/link/src/android/patches/makeBuildPatch.js b/local-cli/rnpm/link/src/android/patches/makeBuildPatch.js new file mode 100644 index 00000000000000..879acbd059b2ef --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/makeBuildPatch.js @@ -0,0 +1,6 @@ +module.exports = function makeBuildPatch(name) { + return { + pattern: /[^ \t]dependencies {\n/, + patch: ` compile project(':${name}')\n`, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/makeSettingsPatch.js b/local-cli/rnpm/link/src/android/patches/makeSettingsPatch.js new file mode 100644 index 00000000000000..af93d058703f34 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/makeSettingsPatch.js @@ -0,0 +1,26 @@ +const path = require('path'); +const isWin = process.platform === 'win32'; + +module.exports = function makeSettingsPatch(name, androidConfig, projectConfig) { + var projectDir = path.relative( + path.dirname(projectConfig.settingsGradlePath), + androidConfig.sourceDir + ); + + /* + * Fix for Windows + * Backslashes is the escape character and will result in + * an invalid path in settings.gradle + * https://github.com/rnpm/rnpm/issues/113 + */ + if (isWin) { + projectDir = projectDir.replace(/\\/g, '/'); + } + + return { + pattern: 'include \':app\'\n', + patch: `include ':${name}'\n` + + `project(':${name}').projectDir = ` + + `new File(rootProject.projectDir, '${projectDir}')\n`, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/makeStringsPatch.js b/local-cli/rnpm/link/src/android/patches/makeStringsPatch.js new file mode 100644 index 00000000000000..06300085dad429 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/makeStringsPatch.js @@ -0,0 +1,14 @@ +const toCamelCase = require('to-camel-case'); + +module.exports = function makeStringsPatch(params, prefix) { + const patch = Object.keys(params).map(param => { + const name = toCamelCase(prefix) + '_' + param; + return ' ' + + `${params[param]}`; + }).join('\n') + '\n'; + + return { + pattern: '\n', + patch: patch, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/revokePatch.js b/local-cli/rnpm/link/src/android/patches/revokePatch.js new file mode 100644 index 00000000000000..8905923b1b3413 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/revokePatch.js @@ -0,0 +1,8 @@ +const fs = require('fs'); + +module.exports = function revokePatch(file, patch) { + fs.writeFileSync(file, fs + .readFileSync(file, 'utf8') + .replace(patch.patch, '') + ); +}; diff --git a/local-cli/rnpm/link/src/android/registerNativeModule.js b/local-cli/rnpm/link/src/android/registerNativeModule.js new file mode 100644 index 00000000000000..fa01c2081bd222 --- /dev/null +++ b/local-cli/rnpm/link/src/android/registerNativeModule.js @@ -0,0 +1,38 @@ +const fs = require('fs'); +const getReactVersion = require('../getReactNativeVersion'); +const getPrefix = require('./getPrefix'); + +const applyPatch = require('./patches/applyPatch'); +const makeStringsPatch = require('./patches/makeStringsPatch'); +const makeSettingsPatch = require(`./patches/makeSettingsPatch`); +const makeBuildPatch = require(`./patches/makeBuildPatch`); + +module.exports = function registerNativeAndroidModule( + name, + androidConfig, + params, + projectConfig +) { + const buildPatch = makeBuildPatch(name); + const prefix = getPrefix(getReactVersion(projectConfig.folder)); + const makeImportPatch = require(`./${prefix}/makeImportPatch`); + const makePackagePatch = require(`./${prefix}/makePackagePatch`); + + applyPatch( + projectConfig.settingsGradlePath, + makeSettingsPatch(name, androidConfig, projectConfig) + ); + + applyPatch(projectConfig.buildGradlePath, buildPatch); + applyPatch(projectConfig.stringsPath, makeStringsPatch(params, name)); + + applyPatch( + projectConfig.mainActivityPath, + makePackagePatch(androidConfig.packageInstance, params, name) + ); + + applyPatch( + projectConfig.mainActivityPath, + makeImportPatch(androidConfig.packageImportPath) + ); +}; diff --git a/local-cli/rnpm/link/src/android/unlinkAssets.js b/local-cli/rnpm/link/src/android/unlinkAssets.js new file mode 100644 index 00000000000000..79c51a1864515e --- /dev/null +++ b/local-cli/rnpm/link/src/android/unlinkAssets.js @@ -0,0 +1,20 @@ +const fs = require('fs-extra'); +const path = require('path'); +const groupFilesByType = require('../groupFilesByType'); + +/** + * Copies each file from an array of assets provided to targetPath directory + * + * For now, the only types of files that are handled are: + * - Fonts (otf, ttf) - copied to targetPath/fonts under original name + */ +module.exports = function unlinkAssetsAndroid(files, targetPath) { + const grouped = groupFilesByType(files); + + grouped.font.forEach((file) => { + const filename = path.basename(file); + if (fs.existsSync(filename)) { + fs.unlinkSync(path.join(targetPath, 'fonts', filename)); + } + }); +}; diff --git a/local-cli/rnpm/link/src/android/unregisterNativeModule.js b/local-cli/rnpm/link/src/android/unregisterNativeModule.js new file mode 100644 index 00000000000000..64db4a0fb6ce36 --- /dev/null +++ b/local-cli/rnpm/link/src/android/unregisterNativeModule.js @@ -0,0 +1,47 @@ +const fs = require('fs'); +const getReactVersion = require('../getReactNativeVersion'); +const getPrefix = require('./getPrefix'); +const toCamelCase = require('to-camel-case'); + +const revokePatch = require('./patches/revokePatch'); +const makeSettingsPatch = require('./patches/makeSettingsPatch'); +const makeBuildPatch = require('./patches/makeBuildPatch'); +const makeStringsPatch = require('./patches/makeStringsPatch'); + +module.exports = function unregisterNativeAndroidModule( + name, + androidConfig, + projectConfig +) { + const buildPatch = makeBuildPatch(name); + const prefix = getPrefix(getReactVersion(projectConfig.folder)); + const makeImportPatch = require(`./${prefix}/makeImportPatch`); + const makePackagePatch = require(`./${prefix}/makePackagePatch`); + const strings = fs.readFileSync(projectConfig.stringsPath, 'utf8'); + var params = {}; + + strings.replace( + /moduleConfig="true" name="(\w+)">(.*) { + params[param.slice(toCamelCase(name).length + 1)] = value; + } + ); + + revokePatch( + projectConfig.settingsGradlePath, + makeSettingsPatch(name, androidConfig, projectConfig) + ); + + revokePatch(projectConfig.buildGradlePath, buildPatch); + revokePatch(projectConfig.stringsPath, makeStringsPatch(params, name)); + + revokePatch( + projectConfig.mainActivityPath, + makePackagePatch(androidConfig.packageInstance, params, name) + ); + + revokePatch( + projectConfig.mainActivityPath, + makeImportPatch(androidConfig.packageImportPath) + ); +}; diff --git a/local-cli/rnpm/link/src/getDependencyConfig.js b/local-cli/rnpm/link/src/getDependencyConfig.js new file mode 100644 index 00000000000000..597be6aab5cb30 --- /dev/null +++ b/local-cli/rnpm/link/src/getDependencyConfig.js @@ -0,0 +1,17 @@ +/** + * Given an array of dependencies - it returns their RNPM config + * if they were valid. + */ +module.exports = function getDependencyConfig(config, deps) { + return deps.reduce((acc, name) => { + try { + return acc.concat({ + config: config.getDependencyConfig(name), + name, + }); + } catch (err) { + console.log(err); + return acc; + } + }, []); +}; diff --git a/local-cli/rnpm/link/src/getProjectDependencies.js b/local-cli/rnpm/link/src/getProjectDependencies.js new file mode 100644 index 00000000000000..1897395371f9fe --- /dev/null +++ b/local-cli/rnpm/link/src/getProjectDependencies.js @@ -0,0 +1,9 @@ +const path = require('path'); + +/** + * Returns an array of dependencies that should be linked/checked. + */ +module.exports = function getProjectDependencies() { + const pjson = require(path.join(process.cwd(), './package.json')); + return Object.keys(pjson.dependencies || {}).filter(name => name !== 'react-native'); +}; diff --git a/local-cli/rnpm/link/src/getReactNativeVersion.js b/local-cli/rnpm/link/src/getReactNativeVersion.js new file mode 100644 index 00000000000000..a1756e9e0bb3dc --- /dev/null +++ b/local-cli/rnpm/link/src/getReactNativeVersion.js @@ -0,0 +1,5 @@ +const path = require('path'); + +module.exports = (folder) => require( + path.join(folder, 'node_modules', 'react-native', 'package.json') +).version; diff --git a/local-cli/rnpm/link/src/groupFilesByType.js b/local-cli/rnpm/link/src/groupFilesByType.js new file mode 100644 index 00000000000000..ddf0df6dbe00fa --- /dev/null +++ b/local-cli/rnpm/link/src/groupFilesByType.js @@ -0,0 +1,27 @@ +const groupBy = require('lodash').groupBy; +const mime = require('mime'); + +/** + * Since there are no officialy registered MIME types + * for ttf/otf yet http://www.iana.org/assignments/media-types/media-types.xhtml, + * we define two non-standard ones for the sake of parsing + */ +mime.define({ + 'font/opentype': ['otf'], + 'font/truetype': ['ttf'], +}); + +/** + * Given an array of files, it groups it by it's type. + * Type of the file is inferred from it's mimetype based on the extension + * file ends up with. The returned value is an object with properties that + * correspond to the first part of the mimetype, e.g. images will be grouped + * under `image` key since the mimetype for them is `image/jpg` etc. + * + * Example: + * Given an array ['fonts/a.ttf', 'images/b.jpg'], + * the returned object will be: {font: ['fonts/a.ttf'], image: ['images/b.jpg']} + */ +module.exports = function groupFilesByType(assets) { + return groupBy(assets, type => mime.lookup(type).split('/')[0]); +}; diff --git a/local-cli/rnpm/link/src/ios/addFileToProject.js b/local-cli/rnpm/link/src/ios/addFileToProject.js new file mode 100644 index 00000000000000..eab1d12535ef75 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/addFileToProject.js @@ -0,0 +1,14 @@ +const PbxFile = require('xcode/lib/pbxFile'); + +/** + * Given xcodeproj and filePath, it creates new file + * from path provided, adds it to the project + * and returns newly created instance of a file + */ +module.exports = function addFileToProject(project, filePath) { + const file = new PbxFile(filePath); + file.uuid = project.generateUuid(); + file.fileRef = project.generateUuid(); + project.addToPbxFileReferenceSection(file); + return file; +}; diff --git a/local-cli/rnpm/link/src/ios/addProjectToLibraries.js b/local-cli/rnpm/link/src/ios/addProjectToLibraries.js new file mode 100644 index 00000000000000..4e65ef30f35241 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/addProjectToLibraries.js @@ -0,0 +1,13 @@ +/** + * Given an array of xcodeproj libraries and pbxFile, + * it appends it to that group + * + * Important: That function mutates `libraries` and it's not pure. + * It's mainly due to limitations of `xcode` library. + */ +module.exports = function addProjectToLibraries(libraries, file) { + return libraries.children.push({ + value: file.fileRef, + comment: file.basename, + }); +}; diff --git a/local-cli/rnpm/link/src/ios/addSharedLibraries.js b/local-cli/rnpm/link/src/ios/addSharedLibraries.js new file mode 100644 index 00000000000000..f84603abb30eeb --- /dev/null +++ b/local-cli/rnpm/link/src/ios/addSharedLibraries.js @@ -0,0 +1,3 @@ +module.exports = function addSharedLibraries(project, libraries) { + +}; diff --git a/local-cli/rnpm/link/src/ios/addToHeaderSearchPaths.js b/local-cli/rnpm/link/src/ios/addToHeaderSearchPaths.js new file mode 100644 index 00000000000000..1eff6055e4b6a7 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/addToHeaderSearchPaths.js @@ -0,0 +1,5 @@ +const mapHeaderSearchPaths = require('./mapHeaderSearchPaths'); + +module.exports = function addToHeaderSearchPaths(project, path) { + mapHeaderSearchPaths(project, searchPaths => searchPaths.concat(path)); +}; diff --git a/local-cli/rnpm/link/src/ios/copyAssets.js b/local-cli/rnpm/link/src/ios/copyAssets.js new file mode 100644 index 00000000000000..538381a035f8ca --- /dev/null +++ b/local-cli/rnpm/link/src/ios/copyAssets.js @@ -0,0 +1,57 @@ +const fs = require('fs-extra'); +const path = require('path'); +const xcode = require('xcode'); +const log = require('npmlog'); +const plistParser = require('plist'); +const groupFilesByType = require('../groupFilesByType'); +const createGroup = require('./createGroup'); +const getPlist = require('./getPlist'); +const getPlistPath = require('./getPlistPath'); + +/** + * This function works in a similar manner to its Android version, + * except it does not copy fonts but creates XCode Group references + */ +module.exports = function linkAssetsIOS(files, projectConfig) { + const project = xcode.project(projectConfig.pbxprojPath).parseSync(); + const assets = groupFilesByType(files); + const plist = getPlist(project, projectConfig.sourceDir); + + if (!plist) { + return log.error( + 'ERRPLIST', + `Could not locate Info.plist. Check if your project has 'INFOPLIST_FILE' set properly` + ); + } + + if (!project.pbxGroupByName('Resources')) { + createGroup(project, 'Resources'); + + log.warn( + 'ERRGROUP', + `Group 'Resources' does not exist in your XCode project. We have created it automatically for you.` + ); + } + + const fonts = (assets.font || []) + .map(asset => + project.addResourceFile( + path.relative(projectConfig.sourceDir, asset), + { target: project.getFirstTarget().uuid } + ) + ) + .filter(file => file) // xcode returns false if file is already there + .map(file => file.basename); + + plist.UIAppFonts = (plist.UIAppFonts || []).concat(fonts); + + fs.writeFileSync( + projectConfig.pbxprojPath, + project.writeSync() + ); + + fs.writeFileSync( + getPlistPath(project, projectConfig.sourceDir), + plistParser.build(plist) + ); +}; diff --git a/local-cli/rnpm/link/src/ios/createGroup.js b/local-cli/rnpm/link/src/ios/createGroup.js new file mode 100644 index 00000000000000..dac9ec66c5df40 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/createGroup.js @@ -0,0 +1,27 @@ +const getGroup = require('./getGroup'); + +const hasGroup = (pbxGroup, name) => pbxGroup.children.find(group => group.comment === name); + +/** + * Given project and path of the group, it deeply creates a given group + * making all outer groups if neccessary + * + * Returns newly created group + */ +module.exports = function createGroup(project, path) { + return path.split('/').reduce( + (group, name) => { + if (!hasGroup(group, name)) { + const uuid = project.pbxCreateGroup(name, '""'); + + group.children.push({ + value: uuid, + comment: name, + }); + } + + return project.pbxGroupByName(name); + }, + getGroup(project) + ); +}; diff --git a/local-cli/rnpm/link/src/ios/getBuildProperty.js b/local-cli/rnpm/link/src/ios/getBuildProperty.js new file mode 100644 index 00000000000000..181a74ec9302c3 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getBuildProperty.js @@ -0,0 +1,18 @@ +/** + * Gets build property from the main target build section + * + * It differs from the project.getBuildProperty exposed by xcode in the way that: + * - it only checks for build property in the main target `Debug` section + * - `xcode` library iterates over all build sections and because it misses + * an early return when property is found, it will return undefined/wrong value + * when there's another build section typically after the one you want to access + * without the property defined (e.g. CocoaPods sections appended to project + * miss INFOPLIST_FILE), see: https://github.com/alunny/node-xcode/blob/master/lib/pbxProject.js#L1765 + */ +module.exports = function getBuildProperty(project, prop) { + const target = project.getFirstTarget().firstTarget; + const config = project.pbxXCConfigurationList()[target.buildConfigurationList]; + const buildSection = project.pbxXCBuildConfigurationSection()[config.buildConfigurations[0].value]; + + return buildSection.buildSettings[prop]; +}; diff --git a/local-cli/rnpm/link/src/ios/getGroup.js b/local-cli/rnpm/link/src/ios/getGroup.js new file mode 100644 index 00000000000000..ab24df8d3d511d --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getGroup.js @@ -0,0 +1,34 @@ +const getFirstProject = (project) => project.getFirstProject().firstProject; + +const findGroup = (group, name) => group.children.find(group => group.comment === name); + +/** + * Returns group from .xcodeproj if one exists, null otherwise + * + * Unlike node-xcode `pbxGroupByName` - it does not return `first-matching` + * group if multiple groups with the same name exist + * + * If path is not provided, it returns top-level group + */ +module.exports = function getGroup(project, path) { + const firstProject = getFirstProject(project); + + var group = project.getPBXGroupByKey(firstProject.mainGroup); + + if (!path) { + return group; + } + + for (var name of path.split('/')) { + var foundGroup = findGroup(group, name); + + if (foundGroup) { + group = project.getPBXGroupByKey(foundGroup.value); + } else { + group = null; + break; + } + } + + return group; +}; diff --git a/local-cli/rnpm/link/src/ios/getHeaderSearchPath.js b/local-cli/rnpm/link/src/ios/getHeaderSearchPath.js new file mode 100644 index 00000000000000..750c060914acb9 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getHeaderSearchPath.js @@ -0,0 +1,52 @@ +const path = require('path'); +const union = require('lodash').union; +const last = require('lodash').last; + +/** + * Given an array of directories, it returns the one that contains + * all the other directories in a given array inside it. + * + * Example: + * Given an array of directories: ['/Users/Kureev/a', '/Users/Kureev/b'] + * the returned folder is `/Users/Kureev` + * + * Check `getHeaderSearchPath.spec.js` for more use-cases. + */ +const getOuterDirectory = (directories) => + directories.reduce((topDir, currentDir) => { + const currentFolders = currentDir.split(path.sep); + const topMostFolders = topDir.split(path.sep); + + if (currentFolders.length === topMostFolders.length + && last(currentFolders) !== last(topMostFolders)) { + return currentFolders.slice(0, -1).join(path.sep); + } + + return currentFolders.length < topMostFolders.length + ? currentDir + : topDir; + }); + +/** + * Given an array of headers it returns search path so Xcode can resolve + * headers when referenced like below: + * ``` + * #import "CodePush.h" + * ``` + * If all files are located in one directory (directories.length === 1), + * we simply return a relative path to that location. + * + * Otherwise, we loop through them all to find the outer one that contains + * all the headers inside. That location is then returned with /** appended at + * the end so Xcode marks that location as `recursive` and will look inside + * every folder of it to locate correct headers. + */ +module.exports = function getHeaderSearchPath(sourceDir, headers) { + const directories = union( + headers.map(path.dirname) + ); + + return directories.length === 1 + ? `"$(SRCROOT)${path.sep}${path.relative(sourceDir, directories[0])}"` + : `"$(SRCROOT)${path.sep}${path.relative(sourceDir, getOuterDirectory(directories))}/**"`; +}; diff --git a/local-cli/rnpm/link/src/ios/getHeadersInFolder.js b/local-cli/rnpm/link/src/ios/getHeadersInFolder.js new file mode 100644 index 00000000000000..7dc954db59a6c6 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getHeadersInFolder.js @@ -0,0 +1,18 @@ +const glob = require('glob'); +const path = require('path'); + +const GLOB_EXCLUDE_PATTERN = ['node_modules/**', 'Pods/**', 'Examples/**', 'examples/**']; + +/** + * Given folder, it returns an array of all header files + * inside it, ignoring node_modules and examples + */ +module.exports = function getHeadersInFolder(folder) { + return glob + .sync('**/*.h', { + cwd: folder, + nodir: true, + ignore: GLOB_EXCLUDE_PATTERN, + }) + .map(file => path.join(folder, file)); +}; diff --git a/local-cli/rnpm/link/src/ios/getPlist.js b/local-cli/rnpm/link/src/ios/getPlist.js new file mode 100644 index 00000000000000..9190aa3459d86e --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getPlist.js @@ -0,0 +1,20 @@ +const plistParser = require('plist'); +const getPlistPath = require('./getPlistPath'); +const fs = require('fs'); + +/** + * Returns Info.plist located in the iOS project + * + * Returns `null` if INFOPLIST_FILE is not specified. + */ +module.exports = function getPlist(project, sourceDir) { + const plistPath = getPlistPath(project, sourceDir); + + if (!plistPath || !fs.existsSync(plistPath)) { + return null; + } + + return plistParser.parse( + fs.readFileSync(plistPath, 'utf-8') + ); +}; diff --git a/local-cli/rnpm/link/src/ios/getPlistPath.js b/local-cli/rnpm/link/src/ios/getPlistPath.js new file mode 100644 index 00000000000000..44b810ab1e298b --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getPlistPath.js @@ -0,0 +1,15 @@ +const path = require('path'); +const getBuildProperty = require('./getBuildProperty'); + +module.exports = function getPlistPath(project, sourceDir) { + const plistFile = getBuildProperty(project, 'INFOPLIST_FILE'); + + if (!plistFile) { + return null; + } + + return path.join( + sourceDir, + plistFile.replace(/"/g, '').replace('$(SRCROOT)', '') + ); +}; diff --git a/local-cli/rnpm/link/src/ios/getProducts.js b/local-cli/rnpm/link/src/ios/getProducts.js new file mode 100644 index 00000000000000..b51492734c35b2 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getProducts.js @@ -0,0 +1,12 @@ +/** + * Given xcodeproj it returns list of products ending with + * .a extension, so that we know what elements add to target + * project static library + */ +module.exports = function getProducts(project) { + return project + .pbxGroupByName('Products') + .children + .map(c => c.comment) + .filter(c => c.indexOf('.a') > -1); +}; diff --git a/local-cli/rnpm/link/src/ios/hasLibraryImported.js b/local-cli/rnpm/link/src/ios/hasLibraryImported.js new file mode 100644 index 00000000000000..b380309344e41e --- /dev/null +++ b/local-cli/rnpm/link/src/ios/hasLibraryImported.js @@ -0,0 +1,10 @@ +/** + * Given an array of libraries already imported and packageName that will be + * added, returns true or false depending on whether the library is already linked + * or not + */ +module.exports = function hasLibraryImported(libraries, packageName) { + return libraries.children + .filter(library => library.comment === packageName) + .length > 0; +}; diff --git a/local-cli/rnpm/link/src/ios/isInstalled.js b/local-cli/rnpm/link/src/ios/isInstalled.js new file mode 100644 index 00000000000000..81b1fff01fb9ff --- /dev/null +++ b/local-cli/rnpm/link/src/ios/isInstalled.js @@ -0,0 +1,18 @@ +const xcode = require('xcode'); +const getGroup = require('./getGroup'); +const hasLibraryImported = require('./hasLibraryImported'); + +/** + * Returns true if `xcodeproj` specified by dependencyConfig is present + * in a top level `libraryFolder` + */ +module.exports = function isInstalled(projectConfig, dependencyConfig) { + const project = xcode.project(projectConfig.pbxprojPath).parseSync(); + const libraries = getGroup(project, projectConfig.libraryFolder); + + if (!libraries) { + return false; + } + + return hasLibraryImported(libraries, dependencyConfig.projectName); +}; diff --git a/local-cli/rnpm/link/src/ios/mapHeaderSearchPaths.js b/local-cli/rnpm/link/src/ios/mapHeaderSearchPaths.js new file mode 100644 index 00000000000000..ea76aac832d3bc --- /dev/null +++ b/local-cli/rnpm/link/src/ios/mapHeaderSearchPaths.js @@ -0,0 +1,36 @@ +/** + * Given Xcode project and path, iterate over all build configurations + * and execute func with HEADER_SEARCH_PATHS from current section + * + * We cannot use builtin addToHeaderSearchPaths method since react-native init does not + * use $(TARGET_NAME) for PRODUCT_NAME, but sets it manually so that method will skip + * that target. + * + * To workaround that issue and make it more bullet-proof for different names, + * we iterate over all configurations and look if React is already there. If it is, + * we assume we want to modify that section either + * + * Important: That function mutates `buildSettings` and it's not pure thus you should + * not rely on its return value + */ +module.exports = function headerSearchPathIter(project, func) { + const config = project.pbxXCBuildConfigurationSection(); + + Object + .keys(config) + .filter(ref => ref.indexOf('_comment') === -1) + .forEach(ref => { + const buildSettings = config[ref].buildSettings; + const shouldVisitBuildSettings = ( + Array.isArray(buildSettings.HEADER_SEARCH_PATHS) ? + buildSettings.HEADER_SEARCH_PATHS : + [] + ) + .filter(path => path.indexOf('react-native/React/**')) + .length > 0; + + if (shouldVisitBuildSettings) { + buildSettings.HEADER_SEARCH_PATHS = func(buildSettings.HEADER_SEARCH_PATHS); + } + }); +}; diff --git a/local-cli/rnpm/link/src/ios/registerNativeModule.js b/local-cli/rnpm/link/src/ios/registerNativeModule.js new file mode 100644 index 00000000000000..6d8bce262e761e --- /dev/null +++ b/local-cli/rnpm/link/src/ios/registerNativeModule.js @@ -0,0 +1,67 @@ +const xcode = require('xcode'); +const fs = require('fs'); +const path = require('path'); +const log = require('npmlog'); + +const addToHeaderSearchPaths = require('./addToHeaderSearchPaths'); +const getHeadersInFolder = require('./getHeadersInFolder'); +const getHeaderSearchPath = require('./getHeaderSearchPath'); +const getProducts = require('./getProducts'); +const createGroup = require('./createGroup'); +const hasLibraryImported = require('./hasLibraryImported'); +const addFileToProject = require('./addFileToProject'); +const addProjectToLibraries = require('./addProjectToLibraries'); +const addSharedLibraries = require('./addSharedLibraries'); +const isEmpty = require('lodash').isEmpty; +const getGroup = require('./getGroup'); + +/** + * Register native module IOS adds given dependency to project by adding + * its xcodeproj to project libraries as well as attaching static library + * to the first target (the main one) + * + * If library is already linked, this action is a no-op. + */ +module.exports = function registerNativeModuleIOS(dependencyConfig, projectConfig) { + const project = xcode.project(projectConfig.pbxprojPath).parseSync(); + const dependencyProject = xcode.project(dependencyConfig.pbxprojPath).parseSync(); + + var libraries = getGroup(project, projectConfig.libraryFolder); + + if (!libraries) { + libraries = createGroup(project, projectConfig.libraryFolder); + + log.warn( + 'ERRGROUP', + `Group ${projectConfig.libraryFolder} does not exist in your XCode project. We have created it automatically for you.` + ); + } + + const file = addFileToProject( + project, + path.relative(projectConfig.sourceDir, dependencyConfig.projectPath) + ); + + addProjectToLibraries(libraries, file); + + getProducts(dependencyProject).forEach(product => { + project.addStaticLibrary(product, { + target: project.getFirstTarget().uuid, + }); + }); + + addSharedLibraries(project, dependencyConfig.sharedLibraries); + + const headers = getHeadersInFolder(dependencyConfig.folder); + if (!isEmpty(headers)) { + addToHeaderSearchPaths( + project, + getHeaderSearchPath(projectConfig.sourceDir, headers) + ); + } + + fs.writeFileSync( + projectConfig.pbxprojPath, + project.writeSync() + ); +}; diff --git a/local-cli/rnpm/link/src/ios/removeFromHeaderSearchPaths.js b/local-cli/rnpm/link/src/ios/removeFromHeaderSearchPaths.js new file mode 100644 index 00000000000000..07259f1d223e0b --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeFromHeaderSearchPaths.js @@ -0,0 +1,10 @@ +const mapHeaderSearchPaths = require('./mapHeaderSearchPaths'); + +/** + * Given Xcode project and absolute path, it makes sure there are no headers referring to it + */ +module.exports = function addToHeaderSearchPaths(project, path) { + mapHeaderSearchPaths(project, + searchPaths => searchPaths.filter(searchPath => searchPath !== path) + ); +}; diff --git a/local-cli/rnpm/link/src/ios/removeFromPbxItemContainerProxySection.js b/local-cli/rnpm/link/src/ios/removeFromPbxItemContainerProxySection.js new file mode 100644 index 00000000000000..e0a6a84a7a65dc --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeFromPbxItemContainerProxySection.js @@ -0,0 +1,16 @@ +/** + * For all files that are created and referenced from another `.xcodeproj` - + * a new PBXItemContainerProxy is created that contains `containerPortal` value + * which equals to xcodeproj file.uuid from PBXFileReference section. + */ +module.exports = function removeFromPbxItemContainerProxySection(project, file) { + const section = project.hash.project.objects.PBXContainerItemProxy; + + for (var key of Object.keys(section)) { + if (section[key].containerPortal === file.uuid) { + delete section[key]; + } + } + + return; +}; diff --git a/local-cli/rnpm/link/src/ios/removeFromPbxReferenceProxySection.js b/local-cli/rnpm/link/src/ios/removeFromPbxReferenceProxySection.js new file mode 100644 index 00000000000000..b867964ff6ef0a --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeFromPbxReferenceProxySection.js @@ -0,0 +1,15 @@ +/** + * Every file added to the project from another project is attached to + * `PBXItemContainerProxy` through `PBXReferenceProxy`. + */ +module.exports = function removeFromPbxReferenceProxySection(project, file) { + const section = project.hash.project.objects.PBXReferenceProxy; + + for (var key of Object.keys(section)) { + if (section[key].path === file.basename) { + delete section[key]; + } + } + + return; +}; diff --git a/local-cli/rnpm/link/src/ios/removeFromProjectReferences.js b/local-cli/rnpm/link/src/ios/removeFromProjectReferences.js new file mode 100644 index 00000000000000..652b189295c297 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeFromProjectReferences.js @@ -0,0 +1,26 @@ +/** + * For each file (.xcodeproj), there's an entry in `projectReferences` created + * that has two entries - `ProjectRef` - reference to a file.uuid and + * `ProductGroup` - uuid of a Products group. + * + * When projectReference is found - it's deleted and the removed value is returned + * so that ProductGroup in PBXGroup section can be removed as well. + * + * Otherwise returns null + */ +module.exports = function removeFromProjectReferences(project, file) { + const firstProject = project.getFirstProject().firstProject; + + const projectRef = firstProject.projectReferences.find(item => item.ProjectRef === file.uuid); + + if (!projectRef) { + return null; + } + + firstProject.projectReferences.splice( + firstProject.projectReferences.indexOf(projectRef), + 1 + ); + + return projectRef; +}; diff --git a/local-cli/rnpm/link/src/ios/removeFromStaticLibraries.js b/local-cli/rnpm/link/src/ios/removeFromStaticLibraries.js new file mode 100644 index 00000000000000..2a66fa44aac879 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeFromStaticLibraries.js @@ -0,0 +1,21 @@ +const PbxFile = require('xcode/lib/pbxFile'); +const removeFromPbxReferenceProxySection = require('./removeFromPbxReferenceProxySection'); + +/** + * Removes file from static libraries + * + * Similar to `node-xcode` addStaticLibrary + */ +module.exports = function removeFromStaticLibraries(project, path, opts) { + const file = new PbxFile(path); + + file.target = opts ? opts.target : undefined; + + project.removeFromPbxFileReferenceSection(file); + project.removeFromPbxBuildFileSection(file); + project.removeFromPbxFrameworksBuildPhase(file); + project.removeFromLibrarySearchPaths(file); + removeFromPbxReferenceProxySection(project, file); + + return file; +}; diff --git a/local-cli/rnpm/link/src/ios/removeProductGroup.js b/local-cli/rnpm/link/src/ios/removeProductGroup.js new file mode 100644 index 00000000000000..186aed7f7b17f8 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeProductGroup.js @@ -0,0 +1,11 @@ +module.exports = function removeProductGroup(project, productGroupId) { + const section = project.hash.project.objects.PBXGroup; + + for (var key of Object.keys(section)) { + if (key === productGroupId) { + delete section[key]; + } + } + + return; +}; diff --git a/local-cli/rnpm/link/src/ios/removeProjectFromLibraries.js b/local-cli/rnpm/link/src/ios/removeProjectFromLibraries.js new file mode 100644 index 00000000000000..20965a67e09292 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeProjectFromLibraries.js @@ -0,0 +1,12 @@ +/** + * Given an array of xcodeproj libraries and pbxFile, + * it removes it from that group by comparing basenames + * + * Important: That function mutates `libraries` and it's not pure. + * It's mainly due to limitations of `xcode` library. + */ +module.exports = function removeProjectFromLibraries(libraries, file) { + libraries.children = libraries.children.filter(library => + library.comment !== file.basename + ); +}; diff --git a/local-cli/rnpm/link/src/ios/removeProjectFromProject.js b/local-cli/rnpm/link/src/ios/removeProjectFromProject.js new file mode 100644 index 00000000000000..3a3f173dbe682d --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeProjectFromProject.js @@ -0,0 +1,26 @@ +const PbxFile = require('xcode/lib/pbxFile'); +const removeFromPbxItemContainerProxySection = require('./removeFromPbxItemContainerProxySection'); +const removeFromProjectReferences = require('./removeFromProjectReferences'); +const removeProductGroup = require('./removeProductGroup'); + +/** + * Given xcodeproj and filePath, it creates new file + * from path provided and removes it. That operation is required since + * underlying method requires PbxFile instance to be passed (it does not + * have to have uuid or fileRef defined since it will do equality check + * by path) + * + * Returns removed file (that one will have UUID) + */ +module.exports = function removeProjectFromProject(project, filePath) { + const file = project.removeFromPbxFileReferenceSection(new PbxFile(filePath)); + const projectRef = removeFromProjectReferences(project, file); + + if (projectRef) { + removeProductGroup(project, projectRef.ProductGroup); + } + + removeFromPbxItemContainerProxySection(project, file); + + return file; +}; diff --git a/local-cli/rnpm/link/src/ios/removeSharedLibraries.js b/local-cli/rnpm/link/src/ios/removeSharedLibraries.js new file mode 100644 index 00000000000000..82e611f5fa63b0 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeSharedLibraries.js @@ -0,0 +1,3 @@ +module.exports = function removeSharedLibraries(project, libraries) { + +}; diff --git a/local-cli/rnpm/link/src/ios/unlinkAssets.js b/local-cli/rnpm/link/src/ios/unlinkAssets.js new file mode 100644 index 00000000000000..44422592985cce --- /dev/null +++ b/local-cli/rnpm/link/src/ios/unlinkAssets.js @@ -0,0 +1,54 @@ +const fs = require('fs-extra'); +const path = require('path'); +const xcode = require('xcode'); +const log = require('npmlog'); +const plistParser = require('plist'); +const groupFilesByType = require('../groupFilesByType'); +const getPlist = require('./getPlist'); +const getPlistPath = require('./getPlistPath'); +const difference = require('lodash').difference; + +/** + * Unlinks assets from iOS project. Removes references for fonts from `Info.plist` + * fonts provided by application and from `Resources` group + */ +module.exports = function unlinkAssetsIOS(files, projectConfig) { + const project = xcode.project(projectConfig.pbxprojPath).parseSync(); + const assets = groupFilesByType(files); + const plist = getPlist(project, projectConfig.sourceDir); + + if (!plist) { + return log.error( + 'ERRPLIST', + `Could not locate Info.plist file. Check if your project has 'INFOPLIST_FILE' set properly` + ); + } + + if (!project.pbxGroupByName('Resources')) { + return log.error( + 'ERRGROUP', + `Group 'Resources' does not exist in your XCode project. There is nothing to unlink.` + ); + } + + const fonts = (assets.font || []) + .map(asset => + project.removeResourceFile( + path.relative(projectConfig.sourceDir, asset), + { target: project.getFirstTarget().uuid } + ) + ) + .map(file => file.basename); + + plist.UIAppFonts = difference(plist.UIAppFonts || [], fonts); + + fs.writeFileSync( + projectConfig.pbxprojPath, + project.writeSync() + ); + + fs.writeFileSync( + getPlistPath(project, projectConfig.sourceDir), + plistParser.build(plist) + ); +}; diff --git a/local-cli/rnpm/link/src/ios/unregisterNativeModule.js b/local-cli/rnpm/link/src/ios/unregisterNativeModule.js new file mode 100644 index 00000000000000..509848a5552897 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/unregisterNativeModule.js @@ -0,0 +1,54 @@ +const xcode = require('xcode'); +const path = require('path'); +const fs = require('fs'); + +const getProducts = require('./getProducts'); +const getHeadersInFolder = require('./getHeadersInFolder'); +const isEmpty = require('lodash').isEmpty; +const getHeaderSearchPath = require('./getHeaderSearchPath'); +const removeProjectFromProject = require('./removeProjectFromProject'); +const removeProjectFromLibraries = require('./removeProjectFromLibraries'); +const removeFromStaticLibraries = require('./removeFromStaticLibraries'); +const removeFromHeaderSearchPaths = require('./removeFromHeaderSearchPaths'); +const removeSharedLibraries = require('./addSharedLibraries'); +const getGroup = require('./getGroup'); + +/** + * Unregister native module IOS + * + * If library is already unlinked, this action is a no-op. + */ +module.exports = function unregisterNativeModule(dependencyConfig, projectConfig) { + const project = xcode.project(projectConfig.pbxprojPath).parseSync(); + const dependencyProject = xcode.project(dependencyConfig.pbxprojPath).parseSync(); + + const libraries = getGroup(project, projectConfig.libraryFolder); + + const file = removeProjectFromProject( + project, + path.relative(projectConfig.sourceDir, dependencyConfig.projectPath) + ); + + removeProjectFromLibraries(libraries, file); + + getProducts(dependencyProject).forEach(product => { + removeFromStaticLibraries(project, product, { + target: project.getFirstTarget().uuid, + }); + }); + + removeSharedLibraries(project, dependencyConfig.sharedLibraries); + + const headers = getHeadersInFolder(dependencyConfig.folder); + if (!isEmpty(headers)) { + removeFromHeaderSearchPaths( + project, + getHeaderSearchPath(projectConfig.sourceDir, headers) + ); + } + + fs.writeFileSync( + projectConfig.pbxprojPath, + project.writeSync() + ); +}; diff --git a/local-cli/rnpm/link/src/link.js b/local-cli/rnpm/link/src/link.js new file mode 100644 index 00000000000000..51261aa30f741c --- /dev/null +++ b/local-cli/rnpm/link/src/link.js @@ -0,0 +1,136 @@ +const log = require('npmlog'); +const path = require('path'); +const uniq = require('lodash').uniq; +const flatten = require('lodash').flatten; +const pkg = require('../package.json'); + +const isEmpty = require('lodash').isEmpty; +const promiseWaterfall = require('./promiseWaterfall'); +const registerDependencyAndroid = require('./android/registerNativeModule'); +const registerDependencyIOS = require('./ios/registerNativeModule'); +const isInstalledAndroid = require('./android/isInstalled'); +const isInstalledIOS = require('./ios/isInstalled'); +const copyAssetsAndroid = require('./android/copyAssets'); +const copyAssetsIOS = require('./ios/copyAssets'); +const getProjectDependencies = require('./getProjectDependencies'); +const getDependencyConfig = require('./getDependencyConfig'); +const pollParams = require('./pollParams'); + +log.heading = 'rnpm-link'; + +const commandStub = (cb) => cb(); +const dedupeAssets = (assets) => uniq(assets, asset => path.basename(asset)); + +const promisify = (func) => new Promise((resolve, reject) => + func((err, res) => err ? reject(err) : resolve(res)) +); + +const linkDependencyAndroid = (androidProject, dependency) => { + if (!androidProject || !dependency.config.android) { + return null; + } + + const isInstalled = isInstalledAndroid(androidProject, dependency.name); + + if (isInstalled) { + log.info(`Android module ${dependency.name} is already linked`); + return null; + } + + return pollParams(dependency.config.params).then(params => { + log.info(`Linking ${dependency.name} android dependency`); + + registerDependencyAndroid( + dependency.name, + dependency.config.android, + params, + androidProject + ); + + log.info(`Android module ${dependency.name} has been successfully linked`); + }); +}; + +const linkDependencyIOS = (iOSProject, dependency) => { + if (!iOSProject || !dependency.config.ios) { + return; + } + + const isInstalled = isInstalledIOS(iOSProject, dependency.config.ios); + + if (isInstalled) { + log.info(`iOS module ${dependency.name} is already linked`); + return; + } + + log.info(`Linking ${dependency.name} ios dependency`); + + registerDependencyIOS(dependency.config.ios, iOSProject); + + log.info(`iOS module ${dependency.name} has been successfully linked`); +}; + +const linkAssets = (project, assets) => { + if (isEmpty(assets)) { + return; + } + + if (project.ios) { + log.info('Linking assets to ios project'); + copyAssetsIOS(assets, project.ios); + } + + if (project.android) { + log.info('Linking assets to android project'); + copyAssetsAndroid(assets, project.android.assetsPath); + } + + log.info(`Assets has been successfully linked to your project`); +}; + +/** + * Updates project and linkes all dependencies to it + * + * If optional argument [packageName] is provided, it's the only one that's checked + */ +module.exports = function link(config, args) { + var project; + try { + project = config.getProjectConfig(); + } catch (err) { + log.error( + 'ERRPACKAGEJSON', + 'No package found. Are you sure it\'s a React Native project?' + ); + return Promise.reject(err); + } + + const packageName = args[0]; + + const dependencies = getDependencyConfig( + config, + packageName ? [packageName] : getProjectDependencies() + ); + + const assets = dedupeAssets(dependencies.reduce( + (assets, dependency) => assets.concat(dependency.config.assets), + project.assets + )); + + const tasks = flatten(dependencies.map(dependency => [ + () => promisify(dependency.config.commands.prelink || commandStub), + () => linkDependencyAndroid(project.android, dependency), + () => linkDependencyIOS(project.ios, dependency), + () => promisify(dependency.config.commands.postlink || commandStub), + ])); + + tasks.push(() => linkAssets(project, assets)); + + return promiseWaterfall(tasks).catch(err => { + log.error( + `It seems something went wrong while linking. Error: ${err.message} \n` + + `Please file an issue here: ${pkg.bugs.url}` + ); + throw err; + }); +}; diff --git a/local-cli/rnpm/link/src/pollParams.js b/local-cli/rnpm/link/src/pollParams.js new file mode 100644 index 00000000000000..65540147951aac --- /dev/null +++ b/local-cli/rnpm/link/src/pollParams.js @@ -0,0 +1,9 @@ +var inquirer = require('inquirer'); + +module.exports = (questions) => new Promise((resolve, reject) => { + if (!questions) { + return resolve({}); + } + + inquirer.prompt(questions, resolve); +}); diff --git a/local-cli/rnpm/link/src/promiseWaterfall.js b/local-cli/rnpm/link/src/promiseWaterfall.js new file mode 100644 index 00000000000000..e6df880587403e --- /dev/null +++ b/local-cli/rnpm/link/src/promiseWaterfall.js @@ -0,0 +1,14 @@ +/** + * Given an array of promise creators, executes them in a sequence. + * + * If any of the promises in the chain fails, all subsequent promises + * will be skipped + * + * Returns the value last promise from a sequence resolved + */ +module.exports = function promiseWaterfall(tasks) { + return tasks.reduce( + (prevTaskPromise, task) => prevTaskPromise.then(task), + Promise.resolve() + ); +}; diff --git a/local-cli/rnpm/link/src/unlink.js b/local-cli/rnpm/link/src/unlink.js new file mode 100644 index 00000000000000..e40b091e9737bd --- /dev/null +++ b/local-cli/rnpm/link/src/unlink.js @@ -0,0 +1,117 @@ +const path = require('path'); +const log = require('npmlog'); + +const getProjectDependencies = require('./getProjectDependencies'); +const unregisterDependencyAndroid = require('./android/unregisterNativeModule'); +const unregisterDependencyIOS = require('./ios/unregisterNativeModule'); +const isInstalledAndroid = require('./android/isInstalled'); +const isInstalledIOS = require('./ios/isInstalled'); +const unlinkAssetsAndroid = require('./android/unlinkAssets'); +const unlinkAssetsIOS = require('./ios/unlinkAssets'); +const getDependencyConfig = require('./getDependencyConfig'); +const difference = require('lodash').difference; +const isEmpty = require('lodash').isEmpty; +const flatten = require('lodash').flatten; + +log.heading = 'rnpm-link'; + +const unlinkDependencyAndroid = (androidProject, dependency, packageName) => { + if (!androidProject || !dependency.android) { + return; + } + + const isInstalled = isInstalledAndroid(androidProject, packageName); + + if (!isInstalled) { + log.info(`Android module ${packageName} is not installed`); + return; + } + + log.info(`Unlinking ${packageName} android dependency`); + + unregisterDependencyAndroid(packageName, dependency.android, androidProject); + + log.info(`Android module ${packageName} has been successfully unlinked`); +}; + +const unlinkDependencyIOS = (iOSProject, dependency, packageName) => { + if (!iOSProject || !dependency.ios) { + return; + } + + const isInstalled = isInstalledIOS(iOSProject, dependency.ios); + + if (!isInstalled) { + log.info(`iOS module ${packageName} is not installed`); + return; + } + + log.info(`Unlinking ${packageName} ios dependency`); + + unregisterDependencyIOS(dependency.ios, iOSProject); + + log.info(`iOS module ${packageName} has been successfully unlinked`); +}; + +/** + * Updates project and unlink specific dependency + * + * If optional argument [packageName] is provided, it's the only one + * that's checked + */ +module.exports = function unlink(config, args) { + const packageName = args[0]; + + var project; + var dependency; + + try { + project = config.getProjectConfig(); + } catch (err) { + log.error( + 'ERRPACKAGEJSON', + 'No package found. Are you sure it\'s a React Native project?' + ); + return Promise.reject(err); + } + + try { + dependency = config.getDependencyConfig(packageName); + } catch (err) { + log.warn( + 'ERRINVALIDPROJ', + `Project ${packageName} is not a react-native library` + ); + return Promise.reject(err); + } + + unlinkDependencyAndroid(project.android, dependency, packageName); + unlinkDependencyIOS(project.ios, dependency, packageName); + + const allDependencies = getDependencyConfig(config, getProjectDependencies()); + + const assets = difference( + dependency.assets, + flatten(allDependencies, d => d.assets) + ); + + if (isEmpty(assets)) { + return Promise.resolve(); + } + + if (project.ios) { + log.info('Unlinking assets from ios project'); + unlinkAssetsIOS(assets, project.ios); + } + + if (project.android) { + log.info('Unlinking assets from android project'); + unlinkAssetsAndroid(assets, project.android.assetsPath); + } + + log.info( + `${packageName} assets has been successfully unlinked from your project` + ); + + return Promise.resolve(); +}; diff --git a/local-cli/rnpm/link/test/android/isInstalled.spec.js b/local-cli/rnpm/link/test/android/isInstalled.spec.js new file mode 100644 index 00000000000000..bde18c09e43dd4 --- /dev/null +++ b/local-cli/rnpm/link/test/android/isInstalled.spec.js @@ -0,0 +1,28 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const isInstalled = require('../../src/android/isInstalled'); + +const projectConfig = { + buildGradlePath: 'build.gradle', +}; + +describe('android::isInstalled', () => { + before(() => mock({ + 'build.gradle': fs.readFileSync( + path.join(__dirname, '../fixtures/android/patchedBuild.gradle') + ), + })); + + it('should return true when project is already in build.gradle', () => + expect(isInstalled(projectConfig, 'test')).to.be.true + ); + + it('should return false when project is not in build.gradle', () => + expect(isInstalled(projectConfig, 'test2')).to.be.false + ); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js b/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js new file mode 100644 index 00000000000000..d2a5f43c728ac6 --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js @@ -0,0 +1,31 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const makeImportPatch = require('../../../../src/android/patches/0.17/makeImportPatch'); +const applyPatch = require('../../../../src/android/patches/applyPatch'); + +const projectConfig = { + mainActivityPath: 'MainActivity.java', +}; + +const packageImportPath = 'import some.example.project'; + +describe('makeImportPatch@0.17', () => { + before(() => mock({ + 'MainActivity.java': fs.readFileSync( + path.join(__dirname, '../../../fixtures/android/0.17/MainActivity.java') + ), + })); + + it('MainActivity contains a correct 0.17 import patch', () => { + const importPatch = makeImportPatch(packageImportPath); + + applyPatch('MainActivity.java', importPatch); + expect(fs.readFileSync('MainActivity.java', 'utf8')) + .to.have.string(importPatch.patch); + }); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js b/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js new file mode 100644 index 00000000000000..99857a5713df09 --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js @@ -0,0 +1,36 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const makePackagePatch = require('../../../../src/android/patches/0.17/makePackagePatch'); +const applyPatch = require('../../../../src/android/patches/applyPatch'); + +const projectConfig = { + mainActivityPath: 'MainActivity.java', +}; + +const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; +const name = 'some-library'; +const params = { + foo: 'foo', + bar: 'bar', +}; + +describe('makePackagePatch@0.17', () => { + before(() => mock({ + 'MainActivity.java': fs.readFileSync( + path.join(__dirname, '../../../fixtures/android/0.17/MainActivity.java') + ), + })); + + it('MainActivity contains a correct 0.17 package patch', () => { + const packagePatch = makePackagePatch(packageInstance, params, name); + + applyPatch('MainActivity.java', packagePatch); + expect(fs.readFileSync('MainActivity.java', 'utf8')) + .to.have.string(packagePatch.patch); + }); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js b/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js new file mode 100644 index 00000000000000..ffff9f045f60c3 --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js @@ -0,0 +1,31 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const makeImportPatch = require('../../../../src/android/patches/0.18/makeImportPatch'); +const applyPatch = require('../../../../src/android/patches/applyPatch'); + +const projectConfig = { + mainActivityPath: 'MainActivity.java', +}; + +const packageImportPath = 'import some.example.project'; + +describe('makeImportPatch@0.18', () => { + before(() => mock({ + 'MainActivity.java': fs.readFileSync( + path.join(__dirname, '../../../fixtures/android/0.18/MainActivity.java') + ), + })); + + it('MainActivity contains a correct 0.18 import patch', () => { + const importPatch = makeImportPatch(packageImportPath); + + applyPatch('MainActivity.java', importPatch); + expect(fs.readFileSync('MainActivity.java', 'utf8')) + .to.have.string(importPatch.patch); + }); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js b/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js new file mode 100644 index 00000000000000..a2d4802f4e484b --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js @@ -0,0 +1,36 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const makePackagePatch = require('../../../../src/android/patches/0.18/makePackagePatch'); +const applyPatch = require('../../../../src/android/patches/applyPatch'); + +const projectConfig = { + mainActivityPath: 'MainActivity.java', +}; + +const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; +const name = 'some-library'; +const params = { + foo: 'foo', + bar: 'bar', +}; + +describe('makePackagePatch@0.18', () => { + before(() => mock({ + 'MainActivity.java': fs.readFileSync( + path.join(__dirname, '../../../fixtures/android/0.18/MainActivity.java') + ), + })); + + it('MainActivity contains a correct 0.18 package patch', () => { + const packagePatch = makePackagePatch(packageInstance, params, name); + + applyPatch('MainActivity.java', packagePatch); + expect(fs.readFileSync('MainActivity.java', 'utf8')) + .to.have.string(packagePatch.patch); + }); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js b/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js new file mode 100644 index 00000000000000..b80fc9a577d76e --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js @@ -0,0 +1,31 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const makeImportPatch = require('../../../../src/android/patches/0.20/makeImportPatch'); +const applyPatch = require('../../../../src/android/patches/applyPatch'); + +const projectConfig = { + mainActivityPath: 'MainActivity.java', +}; + +const packageImportPath = 'import some.example.project'; + +describe('makeImportPatch@0.20', () => { + before(() => mock({ + 'MainActivity.java': fs.readFileSync( + path.join(__dirname, '../../../fixtures/android/0.20/MainActivity.java') + ), + })); + + it('MainActivity contains a correct 0.20 import patch', () => { + const importPatch = makeImportPatch(packageImportPath); + + applyPatch('MainActivity.java', importPatch); + expect(fs.readFileSync('MainActivity.java', 'utf8')) + .to.have.string(importPatch.patch); + }); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js b/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js new file mode 100644 index 00000000000000..87f807636e192b --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js @@ -0,0 +1,36 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const makePackagePatch = require('../../../../src/android/patches/0.20/makePackagePatch'); +const applyPatch = require('../../../../src/android/patches/applyPatch'); + +const projectConfig = { + mainActivityPath: 'MainActivity.java', +}; + +const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; +const name = 'some-library'; +const params = { + foo: 'foo', + bar: 'bar', +}; + +describe('makePackagePatch@0.20', () => { + before(() => mock({ + 'MainActivity.java': fs.readFileSync( + path.join(__dirname, '../../../fixtures/android/0.20/MainActivity.java') + ), + })); + + it('MainActivity contains a correct 0.20 package patch', () => { + const packagePatch = makePackagePatch(packageInstance, params, name); + + applyPatch('MainActivity.java', packagePatch); + expect(fs.readFileSync('MainActivity.java', 'utf8')) + .to.have.string(packagePatch.patch); + }); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/applyPatch.js b/local-cli/rnpm/link/test/android/patches/applyPatch.js new file mode 100644 index 00000000000000..cc5ba34f1d7092 --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/applyPatch.js @@ -0,0 +1,17 @@ +const chai = require('chai'); +const expect = chai.expect; +const applyParams = require('../../../src/android/patches/applyParams'); + +describe('applyParams', () => { + it('apply params to the string', () => { + expect( + applyParams('${foo}', {foo: 'foo'}, 'react-native') + ).to.be.equal('this.getResources().getString(R.strings.reactNative_foo)'); + }); + + it('use null if no params provided', () => { + expect( + applyParams('${foo}', {}, 'react-native') + ).to.be.equal('null'); + }); +}); diff --git a/local-cli/rnpm/link/test/android/patches/makeBuildPatch.spec.js b/local-cli/rnpm/link/test/android/patches/makeBuildPatch.spec.js new file mode 100644 index 00000000000000..51136a72ebf241 --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/makeBuildPatch.spec.js @@ -0,0 +1,17 @@ +const chai = require('chai'); +const expect = chai.expect; +const makeBuildPatch = require('../../../src/android/patches/makeBuildPatch'); +const applyPatch = require('../../../src/android/patches/applyPatch'); + +const name = 'test'; + +describe('makeBuildPatch', () => { + it('should build a patch function', () => { + expect(makeBuildPatch(name)).to.be.an('object'); + }); + + it('should make a correct patch', () => { + expect(makeBuildPatch(name).patch) + .to.be.equal(` compile project(':${name}')\n`); + }); +}); diff --git a/local-cli/rnpm/link/test/android/patches/makeSettingsPatch.spec.js b/local-cli/rnpm/link/test/android/patches/makeSettingsPatch.spec.js new file mode 100644 index 00000000000000..2dfe97c6c49f66 --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/makeSettingsPatch.spec.js @@ -0,0 +1,36 @@ +const fs = require('fs'); +const path = require('path'); +const chai = require('chai'); +const expect = chai.expect; +const makeSettingsPatch = require('../../../src/android/patches/makeSettingsPatch'); + +const name = 'test'; +const projectConfig = { + sourceDir: '/home/project/android/app', + settingsGradlePath: '/home/project/android/settings.gradle', +}; +const dependencyConfig = { + sourceDir: `/home/project/node_modules/${name}/android`, +}; + +describe('makeSettingsPatch', () => { + it('should build a patch function', () => { + expect( + makeSettingsPatch(name, dependencyConfig, {}, projectConfig) + ).to.be.an('object'); + }); + + it('should make a correct patch', () => { + const projectDir = path.relative( + path.dirname(projectConfig.settingsGradlePath), + dependencyConfig.sourceDir + ); + + expect(makeSettingsPatch(name, dependencyConfig, projectConfig).patch) + .to.be.equal( + `include ':${name}'\n` + + `project(':${name}').projectDir = ` + + `new File(rootProject.projectDir, '${projectDir}')\n` + ); + }); +}); diff --git a/local-cli/rnpm/link/test/fixtures/android/0.17/MainActivity.java b/local-cli/rnpm/link/test/fixtures/android/0.17/MainActivity.java new file mode 100644 index 00000000000000..0a532a7eddfa85 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/0.17/MainActivity.java @@ -0,0 +1,78 @@ +package com.basic; + +import android.app.Activity; +import android.os.Bundle; +import android.view.KeyEvent; + +import com.facebook.react.LifecycleState; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactRootView; +import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.soloader.SoLoader; + +public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { + + private ReactInstanceManager mReactInstanceManager; + private ReactRootView mReactRootView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mReactRootView = new ReactRootView(this); + + mReactInstanceManager = ReactInstanceManager.builder() + .setApplication(getApplication()) + .setBundleAssetName("index.android.bundle") + .setJSMainModuleName("index.android") + .addPackage(new MainReactPackage()) + .setUseDeveloperSupport(BuildConfig.DEBUG) + .setInitialLifecycleState(LifecycleState.RESUMED) + .build(); + + mReactRootView.startReactApplication(mReactInstanceManager, "Basic", null); + + setContentView(mReactRootView); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { + mReactInstanceManager.showDevOptionsDialog(); + return true; + } + return super.onKeyUp(keyCode, event); + } + + @Override + public void onBackPressed() { + if (mReactInstanceManager != null) { + mReactInstanceManager.onBackPressed(); + } else { + super.onBackPressed(); + } + } + + @Override + public void invokeDefaultOnBackPressed() { + super.onBackPressed(); + } + + @Override + protected void onPause() { + super.onPause(); + + if (mReactInstanceManager != null) { + mReactInstanceManager.onPause(); + } + } + + @Override + protected void onResume() { + super.onResume(); + + if (mReactInstanceManager != null) { + mReactInstanceManager.onResume(this, this); + } + } +} diff --git a/local-cli/rnpm/link/test/fixtures/android/0.17/patchedMainActivity.java b/local-cli/rnpm/link/test/fixtures/android/0.17/patchedMainActivity.java new file mode 100644 index 00000000000000..edd841d76211c3 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/0.17/patchedMainActivity.java @@ -0,0 +1,80 @@ +package com.basic; + +import android.app.Activity; +import com.oblador.vectoricons.VectorIconsPackage; +import android.os.Bundle; +import android.view.KeyEvent; + +import com.facebook.react.LifecycleState; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactRootView; +import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.soloader.SoLoader; + +public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { + + private ReactInstanceManager mReactInstanceManager; + private ReactRootView mReactRootView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mReactRootView = new ReactRootView(this); + + mReactInstanceManager = ReactInstanceManager.builder() + .setApplication(getApplication()) + .setBundleAssetName("index.android.bundle") + .setJSMainModuleName("index.android") + .addPackage(new MainReactPackage()) + .addPackage(new VectorIconsPackage()) + .setUseDeveloperSupport(BuildConfig.DEBUG) + .setInitialLifecycleState(LifecycleState.RESUMED) + .build(); + + mReactRootView.startReactApplication(mReactInstanceManager, "Basic", null); + + setContentView(mReactRootView); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { + mReactInstanceManager.showDevOptionsDialog(); + return true; + } + return super.onKeyUp(keyCode, event); + } + + @Override + public void onBackPressed() { + if (mReactInstanceManager != null) { + mReactInstanceManager.onBackPressed(); + } else { + super.onBackPressed(); + } + } + + @Override + public void invokeDefaultOnBackPressed() { + super.onBackPressed(); + } + + @Override + protected void onPause() { + super.onPause(); + + if (mReactInstanceManager != null) { + mReactInstanceManager.onPause(); + } + } + + @Override + protected void onResume() { + super.onResume(); + + if (mReactInstanceManager != null) { + mReactInstanceManager.onResume(this, this); + } + } +} diff --git a/local-cli/rnpm/link/test/fixtures/android/0.18/MainActivity.java b/local-cli/rnpm/link/test/fixtures/android/0.18/MainActivity.java new file mode 100644 index 00000000000000..c2ccca8905f7b6 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/0.18/MainActivity.java @@ -0,0 +1,39 @@ +package com.testrn; + +import com.facebook.react.ReactActivity; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; + +import java.util.Arrays; +import java.util.List; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "TestRN"; + } + + /** + * Returns whether dev mode should be enabled. + * This enables e.g. the dev menu. + */ + @Override + protected boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + /** + * A list of packages used by the app. If the app uses additional views + * or modules besides the default ones, add more packages here. + */ + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage()); + } +} diff --git a/local-cli/rnpm/link/test/fixtures/android/0.18/patchedMainActivity.java b/local-cli/rnpm/link/test/fixtures/android/0.18/patchedMainActivity.java new file mode 100644 index 00000000000000..3c6331eaf9fb78 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/0.18/patchedMainActivity.java @@ -0,0 +1,41 @@ +package com.testrn; + +import com.facebook.react.ReactActivity; +import com.oblador.vectoricons.VectorIconsPackage; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; + +import java.util.Arrays; +import java.util.List; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "TestRN"; + } + + /** + * Returns whether dev mode should be enabled. + * This enables e.g. the dev menu. + */ + @Override + protected boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + /** + * A list of packages used by the app. If the app uses additional views + * or modules besides the default ones, add more packages here. + */ + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + new VectorIconsPackage()); + } +} diff --git a/local-cli/rnpm/link/test/fixtures/android/0.20/MainActivity.java b/local-cli/rnpm/link/test/fixtures/android/0.20/MainActivity.java new file mode 100644 index 00000000000000..d9df1311598a37 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/0.20/MainActivity.java @@ -0,0 +1,40 @@ +package com.myawesomeproject; + +import com.facebook.react.ReactActivity; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; + +import java.util.Arrays; +import java.util.List; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "TestRN"; + } + + /** + * Returns whether dev mode should be enabled. + * This enables e.g. the dev menu. + */ + @Override + protected boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + /** + * A list of packages used by the app. If the app uses additional views + * or modules besides the default ones, add more packages here. + */ + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage() + ); + } +} diff --git a/local-cli/rnpm/link/test/fixtures/android/build.gradle b/local-cli/rnpm/link/test/fixtures/android/build.gradle new file mode 100644 index 00000000000000..07f260d6d48032 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/build.gradle @@ -0,0 +1,5 @@ +dependencies { + compile fileTree(dir: "libs", include: ["*.jar"]) + compile "com.android.support:appcompat-v7:23.0.1" + compile "com.facebook.react:react-native:0.18.+" +} diff --git a/local-cli/rnpm/link/test/fixtures/android/patchedBuild.gradle b/local-cli/rnpm/link/test/fixtures/android/patchedBuild.gradle new file mode 100644 index 00000000000000..8c75995d38e543 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/patchedBuild.gradle @@ -0,0 +1,6 @@ +dependencies { + compile project(':test') + compile fileTree(dir: "libs", include: ["*.jar"]) + compile "com.android.support:appcompat-v7:23.0.1" + compile "com.facebook.react:react-native:0.18.+" +} diff --git a/local-cli/rnpm/link/test/fixtures/android/patchedSettings.gradle b/local-cli/rnpm/link/test/fixtures/android/patchedSettings.gradle new file mode 100644 index 00000000000000..8854e3d106fae0 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/patchedSettings.gradle @@ -0,0 +1,5 @@ +rootProject.name = 'TestRN' + +include ':app' +include ':test' +project(':test').projectDir = new File(rootProject.projectDir, '../node_modules/test/android') diff --git a/local-cli/rnpm/link/test/fixtures/android/settings.gradle b/local-cli/rnpm/link/test/fixtures/android/settings.gradle new file mode 100644 index 00000000000000..ded35f713e9413 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'TestRN' + +include ':app' diff --git a/local-cli/rnpm/link/test/fixtures/linearGradient.pbxproj b/local-cli/rnpm/link/test/fixtures/linearGradient.pbxproj new file mode 100644 index 00000000000000..89f34dc5a34376 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/linearGradient.pbxproj @@ -0,0 +1,258 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + BBD49E3F1AC8DEF000610F8E /* BVLinearGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = BBD49E3A1AC8DEF000610F8E /* BVLinearGradient.m */; }; + BBD49E401AC8DEF000610F8E /* BVLinearGradientManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BBD49E3C1AC8DEF000610F8E /* BVLinearGradientManager.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 58B511D91A9E6C8500147676 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 134814201AA4EA6300B7C361 /* libBVLinearGradient.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBVLinearGradient.a; sourceTree = BUILT_PRODUCTS_DIR; }; + BBD49E391AC8DEF000610F8E /* BVLinearGradient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVLinearGradient.h; sourceTree = ""; }; + BBD49E3A1AC8DEF000610F8E /* BVLinearGradient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVLinearGradient.m; sourceTree = ""; }; + BBD49E3B1AC8DEF000610F8E /* BVLinearGradientManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVLinearGradientManager.h; sourceTree = ""; }; + BBD49E3C1AC8DEF000610F8E /* BVLinearGradientManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVLinearGradientManager.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 58B511D81A9E6C8500147676 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 134814211AA4EA7D00B7C361 /* Products */ = { + isa = PBXGroup; + children = ( + 134814201AA4EA6300B7C361 /* libBVLinearGradient.a */, + ); + name = Products; + sourceTree = ""; + }; + 58B511D21A9E6C8500147676 = { + isa = PBXGroup; + children = ( + BBD49E391AC8DEF000610F8E /* BVLinearGradient.h */, + BBD49E3A1AC8DEF000610F8E /* BVLinearGradient.m */, + BBD49E3B1AC8DEF000610F8E /* BVLinearGradientManager.h */, + BBD49E3C1AC8DEF000610F8E /* BVLinearGradientManager.m */, + 134814211AA4EA7D00B7C361 /* Products */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 58B511DA1A9E6C8500147676 /* BVLinearGradient */ = { + isa = PBXNativeTarget; + buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "BVLinearGradient" */; + buildPhases = ( + 58B511D71A9E6C8500147676 /* Sources */, + 58B511D81A9E6C8500147676 /* Frameworks */, + 58B511D91A9E6C8500147676 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BVLinearGradient; + productName = RCTDataManager; + productReference = 134814201AA4EA6300B7C361 /* libBVLinearGradient.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 58B511D31A9E6C8500147676 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 58B511DA1A9E6C8500147676 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "BVLinearGradient" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 58B511D21A9E6C8500147676; + productRefGroup = 58B511D21A9E6C8500147676; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 58B511DA1A9E6C8500147676 /* BVLinearGradient */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 58B511D71A9E6C8500147676 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BBD49E3F1AC8DEF000610F8E /* BVLinearGradient.m in Sources */, + BBD49E401AC8DEF000610F8E /* BVLinearGradientManager.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 58B511ED1A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 58B511EE1A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 58B511F01A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../React/**", + "$(SRCROOT)/../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = BVLinearGradient; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 58B511F11A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../React/**", + "$(SRCROOT)/../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = BVLinearGradient; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "BVLinearGradient" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511ED1A9E6C8500147676 /* Debug */, + 58B511EE1A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "BVLinearGradient" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511F01A9E6C8500147676 /* Debug */, + 58B511F11A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 58B511D31A9E6C8500147676 /* Project object */; +} diff --git a/local-cli/rnpm/link/test/fixtures/project.pbxproj b/local-cli/rnpm/link/test/fixtures/project.pbxproj new file mode 100644 index 00000000000000..e812b4ca885f0e --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/project.pbxproj @@ -0,0 +1,778 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; }; + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; + 00E356F31AD99517003FC87E /* BasicTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* BasicTests.m */; }; + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTActionSheet; + }; + 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTGeolocation; + }; + 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5115D1A9E6B3D00147676; + remoteInfo = RCTImage; + }; + 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B511DB1A9E6C8500147676; + remoteInfo = RCTNetwork; + }; + 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; + remoteInfo = RCTVibration; + }; + 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13B07F861A680F5B00A75B9A; + remoteInfo = Basic; + }; + 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTSettings; + }; + 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3C86DF461ADF2C930047B81A; + remoteInfo = RCTWebSocket; + }; + 146834031AC3E56700842450 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; + remoteInfo = React; + }; + 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTLinking; + }; + 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5119B1A9E6C1200147676; + remoteInfo = RCTText; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = "../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj"; sourceTree = ""; }; + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; + 00E356EE1AD99517003FC87E /* BasicTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BasicTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 00E356F21AD99517003FC87E /* BasicTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BasicTests.m; sourceTree = ""; }; + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; + 13B07F961A680F5B00A75B9A /* Basic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Basic.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Basic/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Basic/AppDelegate.m; sourceTree = ""; }; + 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Basic/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Basic/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Basic/main.m; sourceTree = ""; }; + 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 00E356EB1AD99517003FC87E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 146834051AC3E58100842450 /* libReact.a in Frameworks */, + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */, + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */, + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */, + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00C302A81ABCB8CE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302B61ABCB90400DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302BC1ABCB91800DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302D41ABCB9D200DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302E01ABCB9EE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */, + ); + name = Products; + sourceTree = ""; + }; + 00E356EF1AD99517003FC87E /* BasicTests */ = { + isa = PBXGroup; + children = ( + 00E356F21AD99517003FC87E /* BasicTests.m */, + 00E356F01AD99517003FC87E /* Supporting Files */, + ); + path = BasicTests; + sourceTree = ""; + }; + 00E356F01AD99517003FC87E /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 00E356F11AD99517003FC87E /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 139105B71AF99BAD00B5F7CC /* Products */ = { + isa = PBXGroup; + children = ( + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */, + ); + name = Products; + sourceTree = ""; + }; + 139FDEE71B06529A00C62182 /* Products */ = { + isa = PBXGroup; + children = ( + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */, + ); + name = Products; + sourceTree = ""; + }; + 13B07FAE1A68108700A75B9A /* Basic */ = { + isa = PBXGroup; + children = ( + 008F07F21AC5B25A0029DE68 /* main.jsbundle */, + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.m */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, + 13B07FB71A68108700A75B9A /* main.m */, + ); + name = Basic; + sourceTree = ""; + }; + 146834001AC3E56700842450 /* Products */ = { + isa = PBXGroup; + children = ( + 146834041AC3E56700842450 /* libReact.a */, + ); + name = Products; + sourceTree = ""; + }; + 78C398B11ACF4ADC00677621 /* Products */ = { + isa = PBXGroup; + children = ( + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */, + ); + name = Products; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + 146833FF1AC3E56700842450 /* React.xcodeproj */, + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */, + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */, + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */, + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */, + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */, + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */, + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */, + ); + name = Libraries; + sourceTree = ""; + }; + 832341B11AAA6A8300B99B32 /* Products */ = { + isa = PBXGroup; + children = ( + 832341B51AAA6A8300B99B32 /* libRCTText.a */, + ); + name = Products; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + AD9196DA1CABA83E000E8D91 /* NestedGroup */, + 13B07FAE1A68108700A75B9A /* Basic */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 00E356EF1AD99517003FC87E /* BasicTests */, + 83CBBA001A601CBA00E9B192 /* Products */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* Basic.app */, + 00E356EE1AD99517003FC87E /* BasicTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + AD9196DA1CABA83E000E8D91 /* NestedGroup */ = { + isa = PBXGroup; + children = ( + AD9196DB1CABA844000E8D91 /* Libraries */, + ); + name = NestedGroup; + sourceTree = ""; + }; + AD9196DB1CABA844000E8D91 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 00E356ED1AD99517003FC87E /* BasicTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "BasicTests" */; + buildPhases = ( + 00E356EA1AD99517003FC87E /* Sources */, + 00E356EB1AD99517003FC87E /* Frameworks */, + 00E356EC1AD99517003FC87E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 00E356F51AD99517003FC87E /* PBXTargetDependency */, + ); + name = BasicTests; + productName = BasicTests; + productReference = 00E356EE1AD99517003FC87E /* BasicTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13B07F861A680F5B00A75B9A /* Basic */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Basic" */; + buildPhases = ( + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Basic; + productName = "Hello World"; + productReference = 13B07F961A680F5B00A75B9A /* Basic.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 00E356ED1AD99517003FC87E = { + CreatedOnToolsVersion = 6.2; + TestTargetID = 13B07F861A680F5B00A75B9A; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "a" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */; + ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + }, + { + ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */; + ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + }, + { + ProductGroup = 00C302BC1ABCB91800DB3ED1 /* Products */; + ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + }, + { + ProductGroup = 78C398B11ACF4ADC00677621 /* Products */; + ProjectRef = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + }, + { + ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */; + ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + }, + { + ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */; + ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + }, + { + ProductGroup = 832341B11AAA6A8300B99B32 /* Products */; + ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + }, + { + ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */; + ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + }, + { + ProductGroup = 139FDEE71B06529A00C62182 /* Products */; + ProjectRef = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + }, + { + ProductGroup = 146834001AC3E56700842450 /* Products */; + ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* Basic */, + 00E356ED1AD99517003FC87E /* BasicTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTActionSheet.a; + remoteRef = 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTGeolocation.a; + remoteRef = 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTImage.a; + remoteRef = 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTNetwork.a; + remoteRef = 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTVibration.a; + remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTSettings.a; + remoteRef = 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTWebSocket.a; + remoteRef = 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 146834041AC3E56700842450 /* libReact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libReact.a; + remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTLinking.a; + remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 832341B51AAA6A8300B99B32 /* libRCTText.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTText.a; + remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 00E356EC1AD99517003FC87E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "../node_modules/react-native/packager/react-native-xcode.sh"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 00E356EA1AD99517003FC87E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 00E356F31AD99517003FC87E /* BasicTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13B07F861A680F5B00A75B9A /* Basic */; + targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 13B07FB21A68108700A75B9A /* Base */, + ); + name = LaunchScreen.xib; + path = Basic; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 00E356F61AD99517003FC87E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = BasicTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Basic.app/Basic"; + }; + name = Debug; + }; + 00E356F71AD99517003FC87E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = BasicTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Basic.app/Basic"; + }; + name = Release; + }; + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEAD_CODE_STRIPPING = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + INFOPLIST_FILE = "Basic/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = Basic; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + INFOPLIST_FILE = "Basic/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = Basic; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "BasicTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00E356F61AD99517003FC87E /* Debug */, + 00E356F71AD99517003FC87E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Basic" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "a" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/local-cli/rnpm/link/test/getDependencyConfig.spec.js b/local-cli/rnpm/link/test/getDependencyConfig.spec.js new file mode 100644 index 00000000000000..7fba3b65bccd99 --- /dev/null +++ b/local-cli/rnpm/link/test/getDependencyConfig.spec.js @@ -0,0 +1,23 @@ +const chai = require('chai'); +const expect = chai.expect; +const getDependencyConfig = require('../src/getDependencyConfig'); +const sinon = require('sinon'); + +describe('getDependencyConfig', () => { + it('should return an array of dependencies\' rnpm config', () => { + const config = { + getDependencyConfig: sinon.stub(), + }; + + expect(getDependencyConfig(config, ['abcd'])).to.be.an.array; + expect(config.getDependencyConfig.callCount).to.equals(1); + }); + + it('should filter out invalid react-native projects', () => { + const config = { + getDependencyConfig: sinon.stub().throws(new Error('Cannot require')), + }; + + expect(getDependencyConfig(config, ['abcd'])).to.deep.equal([]); + }); +}); diff --git a/local-cli/rnpm/link/test/getPrefix.spec.js b/local-cli/rnpm/link/test/getPrefix.spec.js new file mode 100644 index 00000000000000..1b5e7262732d7e --- /dev/null +++ b/local-cli/rnpm/link/test/getPrefix.spec.js @@ -0,0 +1,20 @@ +const chai = require('chai'); +const expect = chai.expect; +const getMainActivityPatch = require('../src/android/getPrefix'); +const newPrefix = 'patches/0.18'; +const oldPrefix = 'patches/0.17'; + +describe('getPrefix', () => { + it('require a specific patch for react-native < 0.18', () => { + expect(getMainActivityPatch('0.17.0-rc')).to.equals(oldPrefix); + expect(getMainActivityPatch('0.17.1-rc2')).to.equals(oldPrefix); + expect(getMainActivityPatch('0.17.2')).to.equals(oldPrefix); + }); + + it('require a specific patch for react-native > 0.18', () => { + expect(getMainActivityPatch('0.19.0')).to.equals(newPrefix); + expect(getMainActivityPatch('0.19.0-rc')).to.equals(newPrefix); + expect(getMainActivityPatch('0.18.0-rc1')).to.equals(newPrefix); + expect(getMainActivityPatch('0.18.2')).to.equals(newPrefix); + }); +}); diff --git a/local-cli/rnpm/link/test/getProjectDependencies.spec.js b/local-cli/rnpm/link/test/getProjectDependencies.spec.js new file mode 100644 index 00000000000000..21abb764828b07 --- /dev/null +++ b/local-cli/rnpm/link/test/getProjectDependencies.spec.js @@ -0,0 +1,27 @@ +const chai = require('chai'); +const expect = chai.expect; +const getProjectDependencies = require('../src/getProjectDependencies'); +const mock = require('mock-require'); +const path = require('path'); + +describe('getProjectDependencies', () => { + + it('should return an array of project dependencies', () => { + mock( + path.join(process.cwd(), './package.json'), + { dependencies: { lodash: '^6.0.0', 'react-native': '^16.0.0' } } + ); + + expect(getProjectDependencies()).to.deep.equals(['lodash']); + }); + + it('should return an empty array when no dependencies set', () => { + mock(path.join(process.cwd(), './package.json'), {}); + expect(getProjectDependencies()).to.deep.equals([]); + }); + + afterEach(() => { + mock.stopAll(); + }); + +}); diff --git a/local-cli/rnpm/link/test/groupFilesByType.spec.js b/local-cli/rnpm/link/test/groupFilesByType.spec.js new file mode 100644 index 00000000000000..ac5ecfc691a582 --- /dev/null +++ b/local-cli/rnpm/link/test/groupFilesByType.spec.js @@ -0,0 +1,23 @@ +const chai = require('chai'); +const expect = chai.expect; +const groupFilesByType = require('../src/groupFilesByType'); + +describe('groupFilesByType', () => { + + it('should group files by its type', () => { + const fonts = [ + 'fonts/a.ttf', + 'fonts/b.ttf', + ]; + const images = [ + 'images/a.jpg', + 'images/c.jpeg', + ]; + + const groupedFiles = groupFilesByType(fonts.concat(images)); + + expect(groupedFiles.font).to.deep.equal(fonts); + expect(groupedFiles.image).to.deep.equal(images); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/addFileToProject.spec.js b/local-cli/rnpm/link/test/ios/addFileToProject.spec.js new file mode 100644 index 00000000000000..141d2ba7130f45 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/addFileToProject.spec.js @@ -0,0 +1,22 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const addFileToProject = require('../../src/ios/addFileToProject'); + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::addFileToProject', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should add file to a project', () => { + const file = addFileToProject(project, '../fixtures/linearGradient.pbxproj'); + + expect( + project.pbxFileReferenceSection() + ).to.include.keys(file.fileRef); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/addProjectToLibraries.spec.js b/local-cli/rnpm/link/test/ios/addProjectToLibraries.spec.js new file mode 100644 index 00000000000000..696cce114c4218 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/addProjectToLibraries.spec.js @@ -0,0 +1,28 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const PbxFile = require('xcode/lib/pbxFile'); +const addProjectToLibraries = require('../../src/ios/addProjectToLibraries'); +const last = require('lodash').last; + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::addProjectToLibraries', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should append file to Libraries group', () => { + const file = new PbxFile('fakePath'); + const libraries = project.pbxGroupByName('Libraries'); + + addProjectToLibraries(libraries, file); + + const child = last(libraries.children); + + expect(child).to.have.keys(['value', 'comment']); + expect(child.comment).to.equals(file.basename); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/createGroup.spec.js b/local-cli/rnpm/link/test/ios/createGroup.spec.js new file mode 100644 index 00000000000000..f4653ea612c08c --- /dev/null +++ b/local-cli/rnpm/link/test/ios/createGroup.spec.js @@ -0,0 +1,49 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const createGroup = require('../../src/ios/createGroup'); +const getGroup = require('../../src/ios/getGroup'); +const last = require('lodash').last; + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::createGroup', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should create a group with given name', () => { + const createdGroup = createGroup(project, 'Resources'); + expect(createdGroup.name).to.equals('Resources'); + }); + + it('should attach group to main project group', () => { + const createdGroup = createGroup(project, 'Resources'); + const mainGroup = getGroup(project); + + expect( + last(mainGroup.children).comment + ).to.equals(createdGroup.name); + }); + + it('should create a nested group with given path', () => { + const createdGroup = createGroup(project, 'NewGroup/NewNestedGroup'); + const outerGroup = getGroup(project, 'NewGroup'); + + expect( + last(outerGroup.children).comment + ).to.equals(createdGroup.name); + }); + + it('should-not create already created groups', () => { + const createdGroup = createGroup(project, 'Libraries/NewNestedGroup'); + const outerGroup = getGroup(project, 'Libraries'); + const mainGroup = getGroup(project); + + expect( + mainGroup.children.filter(group => group.comment === 'Libraries').length + ).to.equals(1); + expect(last(outerGroup.children).comment).to.equals(createdGroup.name); + }); +}); diff --git a/local-cli/rnpm/link/test/ios/getBuildProperty.spec.js b/local-cli/rnpm/link/test/ios/getBuildProperty.spec.js new file mode 100644 index 00000000000000..1ca3f30a605ed7 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getBuildProperty.spec.js @@ -0,0 +1,19 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const getBuildProperty = require('../../src/ios/getBuildProperty'); + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::getBuildProperty', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should return build property from main target', () => { + const plistPath = getBuildProperty(project, 'INFOPLIST_FILE'); + expect(plistPath).to.equals('"Basic/Info.plist"'); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/getGroup.spec.js b/local-cli/rnpm/link/test/ios/getGroup.spec.js new file mode 100644 index 00000000000000..5df45f7f000f78 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getGroup.spec.js @@ -0,0 +1,36 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const getGroup = require('../../src/ios/getGroup'); + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::getGroup', () => { + beforeEach(() => { + project.parseSync(); + }); + + it('should return a top-level group', () => { + const group = getGroup(project, 'Libraries'); + expect(group.children.length > 0).to.be.true; // our test top-level Libraries has children + expect(group.name).to.equals('Libraries'); + }); + + it('should return nested group when specified', () => { + const group = getGroup(project, 'NestedGroup/Libraries'); + expect(group.children.length).to.equals(0); // our test nested Libraries is empty + expect(group.name).to.equals('Libraries'); + }); + + it('should return null when no group found', () => { + const group = getGroup(project, 'I-Dont-Exist'); + expect(group).to.be.null; + }); + + it('should return top-level group when name not specified', () => { + const mainGroupId = project.getFirstProject().firstProject.mainGroup; + const mainGroup = project.getPBXGroupByKey(mainGroupId); + const group = getGroup(project); + expect(group).to.equals(mainGroup); + }); +}); diff --git a/local-cli/rnpm/link/test/ios/getHeaderSearchPath.spec.js b/local-cli/rnpm/link/test/ios/getHeaderSearchPath.spec.js new file mode 100644 index 00000000000000..216979ca41273c --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getHeaderSearchPath.spec.js @@ -0,0 +1,58 @@ +const chai = require('chai'); +const expect = chai.expect; +const getHeaderSearchPath = require('../../src/ios/getHeaderSearchPath'); +const path = require('path'); + +const SRC_DIR = path.join('react-native-project', 'ios'); + +describe('ios::getHeaderSearchPath', () => { + + /** + * See https://github.com/Microsoft/react-native-code-push + */ + it('should return correct path when all headers are in root folder', () => { + const files = [ + path.join('react-native-project', 'node_modules', 'package', 'Gradient.h'), + path.join('react-native-project', 'node_modules', 'package', 'Manager.h'), + ]; + + const searchPath = getHeaderSearchPath(SRC_DIR, files); + + expect(searchPath).to.equal( + `"${['$(SRCROOT)', '..', 'node_modules', 'package'].join(path.sep)}"` + ); + }); + + /** + * See https://github.com/facebook/react-native/tree/master/React + */ + it('should return correct path when headers are in multiple folders', () => { + const files = [ + path.join('react-native-project', 'node_modules', 'package', 'src', 'folderA', 'Gradient.h'), + path.join('react-native-project', 'node_modules', 'package', 'src', 'folderB', 'Manager.h'), + ]; + + const searchPath = getHeaderSearchPath(SRC_DIR, files); + + expect(searchPath).to.equal( + `"${['$(SRCROOT)', '..', 'node_modules', 'package', 'src'].join(path.sep)}/**"` + ); + }); + + /** + * This is just to make sure the above two does not collide with each other + */ + it('should return correct path when headers are in root and nested folders', () => { + const files = [ + path.join('react-native-project', 'node_modules', 'package', 'src', 'folderA', 'Gradient.h'), + path.join('react-native-project', 'node_modules', 'package', 'src', 'folderB', 'Manager.h'), + path.join('react-native-project', 'node_modules', 'package', 'src', 'Manager.h'), + ]; + + const searchPath = getHeaderSearchPath(SRC_DIR, files); + + expect(searchPath).to.equal( + `"${['$(SRCROOT)', '..', 'node_modules', 'package', 'src'].join(path.sep)}/**"` + ); + }); +}); diff --git a/local-cli/rnpm/link/test/ios/getHeadersInFolder.spec.js b/local-cli/rnpm/link/test/ios/getHeadersInFolder.spec.js new file mode 100644 index 00000000000000..54f4c0f6e914fa --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getHeadersInFolder.spec.js @@ -0,0 +1,45 @@ +const chai = require('chai'); +const expect = chai.expect; +const getHeadersInFolder = require('../../src/ios/getHeadersInFolder'); +const mock = require('mock-fs'); + +describe('ios::getHeadersInFolder', () => { + + it('should return an array of all headers in given folder', () => { + mock({ + 'FileA.h': '', + 'FileB.h': '', + }); + + const foundHeaders = getHeadersInFolder(process.cwd()); + + expect(foundHeaders.length).to.equals(2); + + getHeadersInFolder(process.cwd()).forEach(headerPath => { + expect(headerPath).to.contain(process.cwd()); + }); + }); + + it('should ignore all headers in Pods, Examples & node_modules', () => { + mock({ + 'FileA.h': '', + 'FileB.h': '', + Pods: { + 'FileC.h': '', + }, + Examples: { + 'FileD.h': '', + }, + node_modules: { + 'FileE.h': '', + }, + }); + + expect(getHeadersInFolder(process.cwd()).length).to.equals(2); + }); + + afterEach(() => { + mock.restore(); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/getPlist.spec.js b/local-cli/rnpm/link/test/ios/getPlist.spec.js new file mode 100644 index 00000000000000..76e072755c2661 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getPlist.spec.js @@ -0,0 +1,23 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const getPlist = require('../../src/ios/getPlist'); + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::getPlist', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should return null when `.plist` file missing', () => { + const plistPath = getPlist(project, process.cwd()); + expect(plistPath).to.equals(null); + }); + + it.skip('should return parsed `plist`', () => { + // @todo mock fs here + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/getPlistPath.spec.js b/local-cli/rnpm/link/test/ios/getPlistPath.spec.js new file mode 100644 index 00000000000000..c8c758ac04e3b1 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getPlistPath.spec.js @@ -0,0 +1,19 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const getPlistPath = require('../../src/ios/getPlistPath'); + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::getPlistPath', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should return path without Xcode $(SRCROOT)', () => { + const plistPath = getPlistPath(project, '/'); + expect(plistPath).to.equals('/Basic/Info.plist'); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/getProducts.spec.js b/local-cli/rnpm/link/test/ios/getProducts.spec.js new file mode 100644 index 00000000000000..bf483f1b2e69ad --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getProducts.spec.js @@ -0,0 +1,20 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const getProducts = require('../../src/ios/getProducts'); + +const project = xcode.project('test/fixtures/linearGradient.pbxproj'); + +describe('ios::getProducts', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should return an array of static libraries project exports', () => { + const products = getProducts(project); + expect(products.length).to.equals(1); + expect(products).to.contains('libBVLinearGradient.a'); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/hasLibraryImported.spec.js b/local-cli/rnpm/link/test/ios/hasLibraryImported.spec.js new file mode 100644 index 00000000000000..c321418799eafa --- /dev/null +++ b/local-cli/rnpm/link/test/ios/hasLibraryImported.spec.js @@ -0,0 +1,24 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const hasLibraryImported = require('../../src/ios/hasLibraryImported'); + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::hasLibraryImported', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should return true if project has been already imported', () => { + const libraries = project.pbxGroupByName('Libraries'); + expect(hasLibraryImported(libraries, 'React.xcodeproj')).to.be.true; + }); + + it('should return false if project is not imported', () => { + const libraries = project.pbxGroupByName('Libraries'); + expect(hasLibraryImported(libraries, 'ACME.xcodeproj')).to.be.false; + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/isInstalled.spec.js b/local-cli/rnpm/link/test/ios/isInstalled.spec.js new file mode 100644 index 00000000000000..65983c6f9922fd --- /dev/null +++ b/local-cli/rnpm/link/test/ios/isInstalled.spec.js @@ -0,0 +1,39 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const isInstalled = require('../../src/ios/isInstalled'); + +const baseProjectConfig = { + pbxprojPath: 'project.pbxproj', + libraryFolder: 'Libraries', +}; + +describe('ios::isInstalled', () => { + + before(() => { + mock({ + 'project.pbxproj': fs.readFileSync(path.join(__dirname, '../fixtures/project.pbxproj')), + }); + }); + + it('should return true when .xcodeproj in Libraries', () => { + const dependencyConfig = { projectName: 'React.xcodeproj' }; + expect(isInstalled(baseProjectConfig, dependencyConfig)).to.be.true; + }); + + it('should return false when .xcodeproj not in Libraries', () => { + const dependencyConfig = { projectName: 'Missing.xcodeproj' }; + expect(isInstalled(baseProjectConfig, dependencyConfig)).to.be.false; + }); + + it('should return false when `LibraryFolder` is missing', () => { + const dependencyConfig = { projectName: 'React.xcodeproj' }; + const projectConfig = Object.assign({}, baseProjectConfig, { libraryFolder: 'Missing' }); + expect(isInstalled(projectConfig, dependencyConfig)).to.be.false; + }); + + after(mock.restore); + +}); diff --git a/local-cli/rnpm/link/test/ios/mapHeaderSearchPaths.spec.js b/local-cli/rnpm/link/test/ios/mapHeaderSearchPaths.spec.js new file mode 100644 index 00000000000000..f885fbd8f39164 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/mapHeaderSearchPaths.spec.js @@ -0,0 +1,23 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const mapHeaderSearchPaths = require('../../src/ios/mapHeaderSearchPaths'); + +const project = xcode.project('test/fixtures/project.pbxproj'); +const reactPath = '"$(SRCROOT)/../node_modules/react-native/React/**"'; + +describe('ios::mapHeaderSearchPaths', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should iterate over headers with `react` added only', () => { + const path = '../../node_modules/path-to-module/**'; + + mapHeaderSearchPaths(project, paths => { + expect(paths.find(path => path.indexOf(reactPath))).to.be.not.empty; + }); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/removeProjectFromLibraries.js b/local-cli/rnpm/link/test/ios/removeProjectFromLibraries.js new file mode 100644 index 00000000000000..49a4c42b463061 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/removeProjectFromLibraries.js @@ -0,0 +1,33 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const PbxFile = require('xcode/lib/pbxFile'); +const addProjectToLibraries = require('../../src/ios/addProjectToLibraries'); +const removeProjectFromLibraries = require('../../src/ios/removeProjectFromLibraries'); +const last = require('lodash').last; + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::removeProjectFromLibraries', () => { + + beforeEach(() => { + project.parseSync(); + + addProjectToLibraries( + project.pbxGroupByName('Libraries'), + new PbxFile('fakePath') + ); + }); + + it('should remove file from Libraries group', () => { + const file = new PbxFile('fakePath'); + const libraries = project.pbxGroupByName('Libraries'); + + removeProjectFromLibraries(libraries, file); + + const child = last(libraries.children); + + expect(child.comment).to.not.equals(file.basename); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/removeProjectFromProject.spec.js b/local-cli/rnpm/link/test/ios/removeProjectFromProject.spec.js new file mode 100644 index 00000000000000..8220028aeafbec --- /dev/null +++ b/local-cli/rnpm/link/test/ios/removeProjectFromProject.spec.js @@ -0,0 +1,32 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const pbxFile = require('xcode/lib/pbxFile'); +const addFileToProject = require('../../src/ios/addFileToProject'); +const removeProjectFromProject = require('../../src/ios/removeProjectFromProject'); + +const project = xcode.project('test/fixtures/project.pbxproj'); +const filePath = '../fixtures/linearGradient.pbxproj'; + +describe('ios::addFileToProject', () => { + + beforeEach(() => { + project.parseSync(); + addFileToProject(project, filePath); + }); + + it('should return removed file', () => { + expect(removeProjectFromProject(project, filePath)).to.be.instanceof(pbxFile); + }); + + it('should remove file from a project', () => { + const file = removeProjectFromProject(project, filePath); + expect(project.pbxFileReferenceSection()).to.not.include.keys(file.fileRef); + }); + + it.skip('should remove file from PBXContainerProxy', () => { + // todo(mike): add in .xcodeproj after Xcode modifications so we can test extra + // removals later. + }); + +}); diff --git a/local-cli/rnpm/link/test/link.spec.js b/local-cli/rnpm/link/test/link.spec.js new file mode 100644 index 00000000000000..9cce9988da09df --- /dev/null +++ b/local-cli/rnpm/link/test/link.spec.js @@ -0,0 +1,199 @@ +const chai = require('chai'); +const expect = chai.expect; +const sinon = require('sinon'); +const mock = require('mock-require'); +const log = require('npmlog'); +const path = require('path'); + +const link = require('../src/link'); + +log.level = 'silent'; + +describe('link', () => { + + beforeEach(() => { + delete require.cache[require.resolve('../src/link')]; + }); + + it('should reject when run in a folder without package.json', (done) => { + const config = { + getProjectConfig: () => { + throw new Error('No package.json found'); + }, + }; + + link(config).catch(() => done()); + }); + + it('should accept a name of a dependency to link', (done) => { + const config = { + getProjectConfig: () => ({ assets: [] }), + getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }), + }; + + link(config, ['react-native-gradient']).then(() => { + expect( + config.getDependencyConfig.calledWith('react-native-gradient') + ).to.be.true; + done(); + }); + }); + + it('should read dependencies from package.json when name not provided', (done) => { + const config = { + getProjectConfig: () => ({ assets: [] }), + getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }), + }; + + mock( + path.join(process.cwd(), 'package.json'), + { + dependencies: { + 'react-native-test': '*', + }, + } + ); + + link(config, []).then(() => { + expect( + config.getDependencyConfig.calledWith('react-native-test') + ).to.be.true; + done(); + }); + }); + + it('should register native module when android/ios projects are present', (done) => { + const registerNativeModule = sinon.stub(); + const dependencyConfig = {android: {}, ios: {}, assets: [], commands: {}}; + const config = { + getProjectConfig: () => ({android: {}, ios: {}, assets: []}), + getDependencyConfig: sinon.stub().returns(dependencyConfig), + }; + + mock( + '../src/android/isInstalled.js', + sinon.stub().returns(false) + ); + + mock( + '../src/android/registerNativeModule.js', + registerNativeModule + ); + + mock( + '../src/ios/isInstalled.js', + sinon.stub().returns(false) + ); + + mock( + '../src/ios/registerNativeModule.js', + registerNativeModule + ); + + const link = require('../src/link'); + + link(config, ['react-native-blur']).then(() => { + expect(registerNativeModule.calledTwice).to.be.true; + done(); + }); + }); + + it('should not register modules when they are already installed', (done) => { + const registerNativeModule = sinon.stub(); + const dependencyConfig = {ios: {}, android: {}, assets: [], commands: {}}; + const config = { + getProjectConfig: () => ({ ios: {}, android: {}, assets: [] }), + getDependencyConfig: sinon.stub().returns(dependencyConfig), + }; + + mock( + '../src/ios/isInstalled.js', + sinon.stub().returns(true) + ); + + mock( + '../src/android/isInstalled.js', + sinon.stub().returns(true) + ); + + mock( + '../src/ios/registerNativeModule.js', + registerNativeModule + ); + + mock( + '../src/android/registerNativeModule.js', + registerNativeModule + ); + + const link = require('../src/link'); + + link(config, ['react-native-blur']).then(() => { + expect(registerNativeModule.callCount).to.equal(0); + done(); + }); + }); + + it('should run prelink and postlink commands at the appropriate times', (done) => { + const registerNativeModule = sinon.stub(); + const prelink = sinon.stub().yieldsAsync(); + const postlink = sinon.stub().yieldsAsync(); + + mock( + '../src/ios/registerNativeModule.js', + registerNativeModule + ); + + mock( + '../src/ios/isInstalled.js', + sinon.stub().returns(false) + ); + + const config = { + getProjectConfig: () => ({ ios: {}, assets: [] }), + getDependencyConfig: sinon.stub().returns({ + ios: {}, assets: [], commands: { prelink, postlink }, + }), + }; + + const link = require('../src/link'); + + link(config, ['react-native-blur']).then(() => { + expect(prelink.calledBefore(registerNativeModule)).to.be.true; + expect(postlink.calledAfter(registerNativeModule)).to.be.true; + done(); + }); + }); + + it('should copy assets from both project and dependencies projects', (done) => { + const dependencyAssets = ['Fonts/Font.ttf']; + const dependencyConfig = {assets: dependencyAssets, commands: {}}; + const projectAssets = ['Fonts/FontC.ttf']; + const copyAssets = sinon.stub(); + + mock( + '../src/ios/copyAssets.js', + copyAssets + ); + + const config = { + getProjectConfig: () => ({ ios: {}, assets: projectAssets }), + getDependencyConfig: sinon.stub().returns(dependencyConfig), + }; + + const link = require('../src/link'); + + link(config, ['react-native-blur']).then(() => { + expect(copyAssets.calledOnce).to.be.true; + expect(copyAssets.getCall(0).args[0]).to.deep.equals( + projectAssets.concat(dependencyAssets) + ); + done(); + }); + }); + + afterEach(() => { + mock.stopAll(); + }); + +}); diff --git a/local-cli/rnpm/link/test/promiseWaterfall.spec.js b/local-cli/rnpm/link/test/promiseWaterfall.spec.js new file mode 100644 index 00000000000000..55d215038f03cb --- /dev/null +++ b/local-cli/rnpm/link/test/promiseWaterfall.spec.js @@ -0,0 +1,37 @@ +const chai = require('chai'); +const expect = chai.expect; +const sinon = require('sinon'); +const promiseWaterfall = require('../src/promiseWaterfall'); + +describe('promiseWaterfall', () => { + + it('should run promises in a sequence', (done) => { + const tasks = [sinon.stub(), sinon.stub()]; + + promiseWaterfall(tasks).then(() => { + expect(tasks[0].calledBefore(tasks[1])).to.be.true; + done(); + }); + }); + + it('should resolve with last promise value', (done) => { + const tasks = [sinon.stub().returns(1), sinon.stub().returns(2)]; + + promiseWaterfall(tasks).then(value => { + expect(value).to.equal(2); + done(); + }); + }); + + it('should stop the sequence when one of promises is rejected', (done) => { + const error = new Error(); + const tasks = [sinon.stub().throws(error), sinon.stub().returns(2)]; + + promiseWaterfall(tasks).catch(err => { + expect(err).to.equal(error); + expect(tasks[1].callCount).to.equal(0); + done(); + }); + }); + +}); diff --git a/package.json b/package.json index 6ecdcb28406df2..df2d7d249f1dc5 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "^[./a-zA-Z0-9$_-]+\\.png$": "RelativeImageStub" }, "testPathIgnorePatterns": [ - "/node_modules/" + "/node_modules/", + "/local-cli/rnpm/" ], "haste": { "defaultPlatform": "ios",