diff --git a/CodePush.h b/CodePush.h index b69ff7d..ee2e0fc 100644 --- a/CodePush.h +++ b/CodePush.h @@ -22,6 +22,11 @@ + (NSString *)getApplicationSupportDirectory; +// The below methods are only used during tests. ++ (BOOL)isUsingTestConfiguration; ++ (void)setUsingTestConfiguration:(BOOL)shouldUseTestConfiguration; ++ (void)clearTestUpdates; + @end @interface CodePushConfig : NSObject @@ -77,6 +82,10 @@ failCallback:(void (^)(NSError *err))failCallback; + (void)rollbackPackage; +// The below methods are only used during tests. ++ (void)downloadAndReplaceCurrentBundle:(NSString *)remoteBundleUrl; ++ (void)clearTestUpdates; + @end typedef NS_ENUM(NSInteger, CodePushInstallMode) { diff --git a/CodePush.js b/CodePush.js index 9b6272f..183ff65 100644 --- a/CodePush.js +++ b/CodePush.js @@ -20,75 +20,75 @@ function checkForUpdate(deploymentKey = null) { * different from the CodePush update they have already installed. */ return getConfiguration() - .then((configResult) => { - /* - * If a deployment key was explicitly provided, - * then let's override the one we retrieved - * from the native-side of the app. This allows - * dynamically "redirecting" end-users at different - * deployments (e.g. an early access deployment for insiders). - */ - if (deploymentKey) { - config = Object.assign({}, configResult, { deploymentKey }); - } else { - config = configResult; - } - - sdk = getSDK(config); - - // Allow dynamic overwrite of function. This is only to be used for tests. - return module.exports.getCurrentPackage(); - }) - .then((localPackage) => { - var queryPackage = { appVersion: config.appVersion }; - - /* - * If the app has a previously installed update, and that update - * was targetted at the same app version that is currently running, - * then we want to use its package hash to determine whether a new - * release has been made on the server. Otherwise, we only need - * to send the app version to the server, since we are interested - * in any updates for current app store version, regardless of hash. - */ - if (localPackage && localPackage.appVersion && semver.compare(localPackage.appVersion, config.appVersion) === 0) { - queryPackage = localPackage; - } - - return new Promise((resolve, reject) => { - sdk.queryUpdateWithCurrentPackage(queryPackage, (err, update) => { - if (err) { - return reject(err); - } - - /* - * There are three cases where checkForUpdate will resolve to null: - * ---------------------------------------------------------------- - * 1) The server said there isn't an update. This is the most common case. - * 2) The server said there is an update but it requires a newer binary version. - * This would occur when end-users are running an older app store version than - * is available, and CodePush is making sure they don't get an update that - * potentially wouldn't be compatible with what they are running. - * 3) The server said there is an update, but the update's hash is the same as - * the currently running update. This should _never_ happen, unless there is a - * bug in the server, but we're adding this check just to double-check that the - * client app is resilient to a potential issue with the update check. - */ - if (!update || update.updateAppVersion || (update.packageHash === localPackage.packageHash)) { - return resolve(null); - } + .then((configResult) => { + /* + * If a deployment key was explicitly provided, + * then let's override the one we retrieved + * from the native-side of the app. This allows + * dynamically "redirecting" end-users at different + * deployments (e.g. an early access deployment for insiders). + */ + if (deploymentKey) { + config = Object.assign({}, configResult, { deploymentKey }); + } else { + config = configResult; + } + + sdk = new module.exports.AcquisitionSdk(requestFetchAdapter, config); + + // Allow dynamic overwrite of function. This is only to be used for tests. + return module.exports.getCurrentPackage(); + }) + .then((localPackage) => { + var queryPackage = { appVersion: config.appVersion }; + + /* + * If the app has a previously installed update, and that update + * was targetted at the same app version that is currently running, + * then we want to use its package hash to determine whether a new + * release has been made on the server. Otherwise, we only need + * to send the app version to the server, since we are interested + * in any updates for current app store version, regardless of hash. + */ + if (localPackage && localPackage.appVersion && semver.compare(localPackage.appVersion, config.appVersion) === 0) { + queryPackage = localPackage; + } + + return new Promise((resolve, reject) => { + sdk.queryUpdateWithCurrentPackage(queryPackage, (err, update) => { + if (err) { + return reject(err); + } + + /* + * There are three cases where checkForUpdate will resolve to null: + * ---------------------------------------------------------------- + * 1) The server said there isn't an update. This is the most common case. + * 2) The server said there is an update but it requires a newer binary version. + * This would occur when end-users are running an older app store version than + * is available, and CodePush is making sure they don't get an update that + * potentially wouldn't be compatible with what they are running. + * 3) The server said there is an update, but the update's hash is the same as + * the currently running update. This should _never_ happen, unless there is a + * bug in the server, but we're adding this check just to double-check that the + * client app is resilient to a potential issue with the update check. + */ + if (!update || update.updateAppVersion || (update.packageHash === localPackage.packageHash)) { + return resolve(null); + } - update = Object.assign(update, PackageMixins.remote); - - NativeCodePush.isFailedUpdate(update.packageHash) - .then((isFailedHash) => { - update.failedInstall = isFailedHash; - resolve(update); - }) - .catch(reject) - .done(); - }) - }); - }); + update = Object.assign(update, PackageMixins.remote); + + NativeCodePush.isFailedUpdate(update.packageHash) + .then((isFailedHash) => { + update.failedInstall = isFailedHash; + resolve(update); + }) + .catch(reject) + .done(); + }) + }); + }); } var getConfiguration = (() => { @@ -129,30 +129,21 @@ function getCurrentPackage() { }); } -function getSDK(config) { - if (testSdk) { - return testSdk; - } else { - return new Sdk(requestFetchAdapter, config); - } -} - /* Logs messages to console with the [CodePush] prefix */ function log(message) { console.log(`[CodePush] ${message}`) } var testConfig; -var testSdk; // This function is only used for tests. Replaces the default SDK, configuration and native bridge -function setUpTestDependencies(providedTestSdk, providedTestConfig, testNativeBridge) { - if (providedTestSdk) testSdk = providedTestSdk; +function setUpTestDependencies(testSdk, providedTestConfig, testNativeBridge) { + if (testSdk) module.exports.AcquisitionSdk = testSdk; if (providedTestConfig) testConfig = providedTestConfig; if (testNativeBridge) NativeCodePush = testNativeBridge; } -/** +/* * The sync method provides a simple, one-line experience for * incorporating the check, download and application of an update. * @@ -303,6 +294,7 @@ function sync(options = {}, syncStatusChangeCallback, downloadProgressCallback) }; var CodePush = { + AcquisitionSdk: Sdk, checkForUpdate: checkForUpdate, getConfiguration: getConfiguration, getCurrentPackage: getCurrentPackage, diff --git a/CodePush.m b/CodePush.m index 2d5e15d..6820f26 100644 --- a/CodePush.m +++ b/CodePush.m @@ -13,7 +13,7 @@ RCT_EXPORT_MODULE() -static BOOL usingTestFolder = NO; +static BOOL testConfigurationFlag = NO; // These keys represent the names we use to store data in NSUserDefaults static NSString *const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES"; @@ -50,7 +50,10 @@ static NSString *const PackageIsPendingKey = @"isPending"; NSString *packageFile = [CodePushPackage getCurrentPackageBundlePath:&error]; NSURL *binaryJsBundleUrl = [[NSBundle mainBundle] URLForResource:resourceName withExtension:resourceExtension]; + NSString *logMessageFormat = @"Loading JS bundle from %@"; + if (error || !packageFile) { + NSLog(logMessageFormat, binaryJsBundleUrl); return binaryJsBundleUrl; } @@ -61,8 +64,11 @@ static NSString *const PackageIsPendingKey = @"isPending"; if ([binaryDate compare:packageDate] == NSOrderedAscending) { // Return package file because it is newer than the app store binary's JS bundle - return [[NSURL alloc] initFileURLWithPath:packageFile]; + NSURL *packageUrl = [[NSURL alloc] initFileURLWithPath:packageFile]; + NSLog(logMessageFormat, packageUrl); + return packageUrl; } else { + NSLog(logMessageFormat, binaryJsBundleUrl); return binaryJsBundleUrl; } } @@ -73,6 +79,40 @@ static NSString *const PackageIsPendingKey = @"isPending"; return applicationSupportDirectory; } +/* + * This returns a boolean value indicating whether CodePush has + * been set to run under a test configuration. + */ ++ (BOOL)isUsingTestConfiguration +{ + return testConfigurationFlag; +} + +/* + * This is used to enable an environment in which tests can be run. + * Specifically, it flips a boolean flag that causes bundles to be + * saved to a test folder and enables the ability to modify + * installed bundles on the fly from JavaScript. + */ ++ (void)setUsingTestConfiguration:(BOOL)shouldUseTestConfiguration +{ + testConfigurationFlag = shouldUseTestConfiguration; +} + +/* + * This is used to clean up all test updates. It can only be used + * when the testConfigurationFlag is set to YES, otherwise it will + * simply no-op. + */ ++ (void)clearTestUpdates +{ + if ([CodePush isUsingTestConfiguration]) { + [CodePushPackage clearTestUpdates]; + [self removePendingUpdate]; + } +} + + // Private API methods /* @@ -125,6 +165,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; if (updateIsLoading) { // Pending update was initialized, but notifyApplicationReady was not called. // Therefore, deduce that it is a broken update and rollback. + NSLog(@"Update did not finish loading the last time, rolling back to a previous version."); [self rollbackPackage]; } else { // Mark that we tried to initialize the new update, so that if it crashes, @@ -179,7 +220,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; // is debugging and therefore, shouldn't be redirected to a local // file (since Chrome wouldn't support it). Otherwise, update // the current bundle URL to point at the latest update - if (![_bridge.bundleURL.scheme hasPrefix:@"http"]) { + if ([CodePush isUsingTestConfiguration] || ![_bridge.bundleURL.scheme hasPrefix:@"http"]) { _bridge.bundleURL = [CodePush bundleURL]; } @@ -204,7 +245,7 @@ static NSString *const PackageIsPendingKey = @"isPending"; // Rollback to the previous version and de-register the new update [CodePushPackage rollbackPackage]; - [self removePendingUpdate]; + [CodePush removePendingUpdate]; [self loadBundle]; } @@ -231,10 +272,10 @@ static NSString *const PackageIsPendingKey = @"isPending"; } /* - * This method is used to register the fact that a pending + * This method is used to register the fact that a pending * update succeeded and therefore can be removed. */ -- (void)removePendingUpdate ++ (void)removePendingUpdate { NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; [preferences removeObjectForKey:PendingUpdateKey]; @@ -351,19 +392,15 @@ RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage [self savePendingUpdate:updatePackage[PackageHashKey] isLoading:NO]; - if (installMode == CodePushInstallModeImmediate) { - [self loadBundle]; - } else if (installMode == CodePushInstallModeOnNextResume) { + if (installMode == CodePushInstallModeOnNextResume && !_hasResumeListener) { // Ensure we do not add the listener twice. - if (!_hasResumeListener) { - // Register for app resume notifications so that we - // can check for pending updates which support "restart on resume" - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(loadBundle) - name:UIApplicationWillEnterForegroundNotification - object:[UIApplication sharedApplication]]; - _hasResumeListener = YES; - } + // Register for app resume notifications so that we + // can check for pending updates which support "restart on resume" + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(loadBundle) + name:UIApplicationWillEnterForegroundNotification + object:[UIApplication sharedApplication]]; + _hasResumeListener = YES; } // Signal to JS that the update has been applied. resolve(nil); @@ -406,7 +443,7 @@ RCT_EXPORT_METHOD(isFirstRun:(NSString *)packageHash RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - [self removePendingUpdate]; + [CodePush removePendingUpdate]; resolve([NSNull null]); } @@ -418,9 +455,17 @@ RCT_EXPORT_METHOD(restartApp) [self loadBundle]; } -RCT_EXPORT_METHOD(setUsingTestFolder:(BOOL)shouldUseTestFolder) +/* + * This method is the native side of the CodePush.downloadAndReplaceCurrentBundle() + * method, which replaces the current bundle with the one downloaded from + * removeBundleUrl. It is only to be used during tests and no-ops if the test + * configuration flag is not set. + */ +RCT_EXPORT_METHOD(downloadAndReplaceCurrentBundle:(NSString *)remoteBundleUrl) { - usingTestFolder = shouldUseTestFolder; + if ([CodePush isUsingTestConfiguration]) { + [CodePushPackage downloadAndReplaceCurrentBundle:remoteBundleUrl]; + } } @end \ No newline at end of file diff --git a/CodePushPackage.m b/CodePushPackage.m index fba060c..4cb98dc 100644 --- a/CodePushPackage.m +++ b/CodePushPackage.m @@ -14,7 +14,12 @@ NSString * const UnzippedFolderName = @"unzipped"; + (NSString *)getCodePushPath { - return [[CodePush getApplicationSupportDirectory] stringByAppendingPathComponent:@"CodePush"]; + NSString* codePushPath = [[CodePush getApplicationSupportDirectory] stringByAppendingPathComponent:@"CodePush"]; + if ([CodePush isUsingTestConfiguration]) { + codePushPath = [codePushPath stringByAppendingPathComponent:@"TestPackages"]; + } + + return codePushPath; } + (NSString *)getDownloadFilePath @@ -481,4 +486,31 @@ NSString * const UnzippedFolderName = @"unzipped"; [self updateCurrentPackageInfo:info error:&error]; } ++ (void)downloadAndReplaceCurrentBundle:(NSString *)remoteBundleUrl +{ + NSURL *urlRequest = [NSURL URLWithString:remoteBundleUrl]; + NSError *error = nil; + NSString *downloadedBundle = [NSString stringWithContentsOfURL:urlRequest + encoding:NSUTF8StringEncoding + error:&error]; + + if (error) { + NSLog(@"Error downloading from URL %@", remoteBundleUrl); + } else { + NSString *currentPackageBundlePath = [self getCurrentPackageBundlePath:&error]; + [downloadedBundle writeToFile:currentPackageBundlePath + atomically:YES + encoding:NSUTF8StringEncoding + error:&error]; + } +} + ++ (void)clearTestUpdates +{ + if ([CodePush isUsingTestConfiguration]) { + [[NSFileManager defaultManager] removeItemAtPath:[self getCodePushPath] error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:[self getStatusFilePath] error:nil]; + } +} + @end \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests.m b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests.m new file mode 100644 index 0000000..6b74f3a --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests.m @@ -0,0 +1,70 @@ +#import +#import +#import + +#import "RCTAssert.h" +#import "CodePush.h" + +#define FB_REFERENCE_IMAGE_DIR "\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\"" + +@interface CheckForUpdateTests : XCTestCase + +@end + +@implementation CheckForUpdateTests +{ + RCTTestRunner *_runner; +} + +- (void)setUp +{ +#if __LP64__ + RCTAssert(false, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)"); +#endif + + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; + RCTAssert(version.majorVersion == 8 || version.minorVersion == 3, @"Tests should be run on iOS 8.3, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion); + [CodePush setUsingTestConfiguration:YES]; + [CodePush clearTestUpdates]; + _runner = RCTInitRunnerForApp(@"CodePushDemoAppTests/CheckForUpdateTests/CheckForUpdateTestApp", nil); +} + +#pragma mark Logic Tests + +- (void)testFirstUpdate +{ + [_runner runTest:_cmd + module:@"FirstUpdateTest"]; +} + +- (void)testNewUpdate +{ + [_runner runTest:_cmd + module:@"NewUpdateTest"]; +} + +- (void)testNoRemotePackage +{ + + [_runner runTest:_cmd module:@"NoRemotePackageTest"]; +} + +- (void)testRemotePackageAppVersionNewer +{ + [_runner runTest:_cmd + module:@"RemotePackageAppVersionNewerTest"]; +} + +- (void)testSamePackage +{ + [_runner runTest:_cmd + module:@"SamePackageTest"]; +} + +- (void)testSwitchDeploymentKey +{ + [_runner runTest:_cmd + module:@"SwitchDeploymentKeyTest"]; +} + +@end diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/QueryUpdateTestApp.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/CheckForUpdateTestApp.js similarity index 56% rename from Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/QueryUpdateTestApp.js rename to Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/CheckForUpdateTestApp.js index e9f838f..62393c2 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/QueryUpdateTestApp.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/CheckForUpdateTestApp.js @@ -1,8 +1,8 @@ -'use strict'; +"use strict"; -var React = require('react-native'); +import React from "react-native"; -var { +let { AppRegistry, ScrollView, StyleSheet, @@ -11,25 +11,26 @@ var { View, } = React; -var TESTS = [ - require('./NoRemotePackageTest'), - require('./NoRemotePackageWithSameAppVersionTest'), - require('./FirstUpdateTest'), - require('./NewUpdateTest'), - require('./SamePackageTest') +let TESTS = [ + require("./testcases/FirstUpdateTest"), + require("./testcases/NewUpdateTest"), + require("./testcases/NoRemotePackageTest"), + require("./testcases/RemotePackageAppVersionNewerTest"), + require("./testcases/SamePackageTest"), + require("./testcases/SwitchDeploymentKeyTest") ]; TESTS.forEach( (test) => AppRegistry.registerComponent(test.displayName, () => test) ); -var QueryUpdateTestApp = React.createClass({ - getInitialState: function() { +let CheckForUpdateTestApp = React.createClass({ + getInitialState() { return { test: null, }; }, - render: function() { + render() { if (this.state.test) { return ( @@ -40,9 +41,7 @@ var QueryUpdateTestApp = React.createClass({ return ( - Click on a test to run it in this shell for easier debugging and - development. Run all tests in the testing environment with cmd+U in - Xcode. + CheckForUpdate Tests @@ -53,6 +52,9 @@ var QueryUpdateTestApp = React.createClass({ {test.displayName} + + {test.description} + , ])} @@ -62,9 +64,9 @@ var QueryUpdateTestApp = React.createClass({ } }); -var styles = StyleSheet.create({ +let styles = StyleSheet.create({ container: { - backgroundColor: 'white', + backgroundColor: "white", marginTop: 40, margin: 15, }, @@ -72,12 +74,15 @@ var styles = StyleSheet.create({ padding: 10, }, testName: { - fontWeight: '500', + fontWeight: "500", + }, + testDescription: { + fontSize: 10 }, separator: { height: 1, - backgroundColor: '#bbbbbb', + backgroundColor: "#bbbbbb", } }); -AppRegistry.registerComponent('QueryUpdateTestApp', () => QueryUpdateTestApp); \ No newline at end of file +AppRegistry.registerComponent("CheckForUpdateTestApp", () => CheckForUpdateTestApp); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/resources/testPackages.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/resources/testPackages.js new file mode 100644 index 0000000..0f142c1 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/resources/testPackages.js @@ -0,0 +1,32 @@ +export let serverPackage = { + appVersion: "1.5.0", + description: "Angry flappy birds", + downloadUrl: "http://www.windowsazure.com/blobs/awperoiuqpweru", + isAvailable: true, + isMandatory: false, + packageHash: "hash240", + packageSize: 1024, + updateAppVersion: false +}; + +export let localPackage = { + downloadURL: "http://www.windowsazure.com/blobs/awperoiuqpweru", + description: "Angry flappy birds", + appVersion: "1.5.0", + label: "2.4.0", + isMandatory: false, + isAvailable: true, + updateAppVersion: false, + packageHash: "hash123", + packageSize: 1024 +}; + +export let updateAppVersionPackage = { + appVersion: "1.5.0", + description: "", + downloadUrl: "", + isAvailable: false, + isMandatory: false, + packageHash: "", + updateAppVersion: true +}; diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/FirstUpdateTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/FirstUpdateTest.js new file mode 100644 index 0000000..33b286b --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/FirstUpdateTest.js @@ -0,0 +1,38 @@ +"use strict"; + +import React from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; +import createMockAcquisitionSdk from "../../utils/mockAcquisitionSdk"; + +import { serverPackage } from "../resources/testPackages"; +const localPackage = {}; + +let FirstUpdateTest = createTestCaseComponent( + "FirstUpdateTest", + "should return an update when called from freshly installed binary if the server has one", + () => { + let mockAcquisitionSdk = createMockAcquisitionSdk(serverPackage, localPackage); + let mockConfiguration = { appVersion : "1.5.0" }; + CodePush.setUpTestDependencies(mockAcquisitionSdk, mockConfiguration, NativeCodePush); + CodePush.getCurrentPackage = () => { + return Promise.resolve(localPackage); + } + return Promise.resolve(); + }, + () => { + return CodePush.checkForUpdate() + .then((update) => { + if (update) { + assert.deepEqual(update, Object.assign(serverPackage, PackageMixins.remote)); + } else { + throw new Error("checkForUpdate did not return the update from the server"); + } + }); + } +); + +module.exports = FirstUpdateTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/NewUpdateTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/NewUpdateTest.js new file mode 100644 index 0000000..f020fce --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/NewUpdateTest.js @@ -0,0 +1,37 @@ +"use strict"; + +import React from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; +import createMockAcquisitionSdk from "../../utils/mockAcquisitionSdk"; + +import { serverPackage, localPackage } from "../resources/testPackages"; + +let NewUpdateTest = createTestCaseComponent( + "NewUpdateTest", + "should return an update when server has a package that is newer than the current one", + () => { + let mockAcquisitionSdk = createMockAcquisitionSdk(serverPackage, localPackage); + let mockConfiguration = { appVersion : "1.5.0" }; + CodePush.setUpTestDependencies(mockAcquisitionSdk, mockConfiguration, NativeCodePush); + CodePush.getCurrentPackage = () => { + return Promise.resolve(localPackage); + } + return Promise.resolve(); + }, + () => { + return CodePush.checkForUpdate() + .then((update) => { + if (update) { + assert.deepEqual(update, Object.assign(serverPackage, PackageMixins.remote)); + } else { + throw new Error("checkForUpdate did not return the update from the server"); + } + }); + } +); + +module.exports = NewUpdateTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/NoRemotePackageTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/NoRemotePackageTest.js new file mode 100644 index 0000000..d3fb582 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/NoRemotePackageTest.js @@ -0,0 +1,36 @@ +"use strict"; + +import React from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; +import createMockAcquisitionSdk from "../../utils/mockAcquisitionSdk"; + +let serverPackage = null; +let localPackage = {}; + +let NoRemotePackageTest = createTestCaseComponent( + "NoRemotePackageTest", + "should not return an update when the server has none", + () => { + let mockAcquisitionSdk = createMockAcquisitionSdk(serverPackage, localPackage); + let mockConfiguration = { appVersion : "1.5.0" }; + CodePush.setUpTestDependencies(mockAcquisitionSdk, mockConfiguration, NativeCodePush); + CodePush.getCurrentPackage = () => { + return Promise.resolve(localPackage); + } + return Promise.resolve(); + }, + () => { + return CodePush.checkForUpdate() + .then((update) => { + if (update) { + throw new Error("checkForUpdate should not return an update if there is none on the server"); + } + }); + } +); + +module.exports = NoRemotePackageTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/RemotePackageAppVersionNewerTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/RemotePackageAppVersionNewerTest.js new file mode 100644 index 0000000..4b4af51 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/RemotePackageAppVersionNewerTest.js @@ -0,0 +1,38 @@ +"use strict"; + +import React from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; +import createMockAcquisitionSdk from "../../utils/mockAcquisitionSdk"; + +import { updateAppVersionPackage as serverPackage } from "../resources/testPackages"; +let localPackage = {}; + +let RemotePackageAppVersionNewerTest = createTestCaseComponent( + "RemotePackageAppVersionNewerTest", + "should drop the update when the server reports one with a newer binary version", + () => { + return new Promise((resolve, reject) => { + let mockAcquisitionSdk = createMockAcquisitionSdk(serverPackage, localPackage); + let mockConfiguration = { appVersion : "1.0.0" }; + CodePush.setUpTestDependencies(mockAcquisitionSdk, mockConfiguration, NativeCodePush); + CodePush.getCurrentPackage = () => { + return Promise.resolve(localPackage); + } + resolve(); + }); + }, + () => { + return CodePush.checkForUpdate() + .then((update) => { + if (update) { + throw new Error("checkForUpdate should not return an update if remote package is of a different binary version"); + } + }); + } +); + +module.exports = RemotePackageAppVersionNewerTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SamePackageTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SamePackageTest.js new file mode 100644 index 0000000..88288c8 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SamePackageTest.js @@ -0,0 +1,36 @@ +"use strict"; + +import React from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; +import createMockAcquisitionSdk from "../../utils/mockAcquisitionSdk"; + +import { serverPackage } from "../resources/testPackages"; +const localPackage = serverPackage; + +let SamePackageTest = createTestCaseComponent( + "SamePackageTest", + "should not return an update when the server's version is the same as the local version", + () => { + let mockAcquisitionSdk = createMockAcquisitionSdk(serverPackage, localPackage); + let mockConfiguration = { appVersion : "1.5.0" }; + CodePush.setUpTestDependencies(mockAcquisitionSdk, mockConfiguration, NativeCodePush); + CodePush.getCurrentPackage = () => { + return Promise.resolve(localPackage); + } + return Promise.resolve(); + }, + () => { + return CodePush.checkForUpdate() + .then((update) => { + if (update) { + throw new Error("checkForUpdate should not return a package when local package is identical"); + } + }); + } +); + +module.exports = SamePackageTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SwitchDeploymentKeyTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SwitchDeploymentKeyTest.js new file mode 100644 index 0000000..4205559 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/CheckForUpdateTests/testcases/SwitchDeploymentKeyTest.js @@ -0,0 +1,40 @@ +"use strict"; + +import React from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; +import createMockAcquisitionSdk from "../../utils/mockAcquisitionSdk"; + +import { serverPackage } from "../resources/testPackages"; +const localPackage = {}; + +let deploymentKey = "myKey123"; + +let SwitchDeploymentKeyTest = createTestCaseComponent( + "SwitchDeploymentKeyTest", + "should check for an update under the specified deployment key", + () => { + let mockAcquisitionSdk = createMockAcquisitionSdk(serverPackage, localPackage, deploymentKey); + let mockConfiguration = { appVersion : "1.5.0" }; + CodePush.setUpTestDependencies(mockAcquisitionSdk, mockConfiguration, NativeCodePush); + CodePush.getCurrentPackage = () => { + return Promise.resolve(localPackage); + } + return Promise.resolve(); + }, + () => { + return CodePush.checkForUpdate(deploymentKey) + .then((update) => { + if (update) { + assert.deepEqual(update, Object.assign(serverPackage, PackageMixins.remote)); + } else { + throw new Error("checkForUpdate did not return the update from the server"); + } + }); + } +); + +module.exports = SwitchDeploymentKeyTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests.m b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests.m index 47050b0..9ed83b0 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests.m +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests.m @@ -3,6 +3,7 @@ #import #import "RCTAssert.h" +#import "CodePush.h" #define FB_REFERENCE_IMAGE_DIR "\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\"" @@ -12,25 +13,27 @@ @implementation DownloadProgressTests { - RCTTestRunner *_runner; + RCTTestRunner *_runner; } - (void)setUp { #if __LP64__ - RCTAssert(false, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)"); + RCTAssert(false, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)"); #endif - NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; - RCTAssert(version.majorVersion == 8 || version.minorVersion == 3, @"Tests should be run on iOS 8.3, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion); - _runner = RCTInitRunnerForApp(@"CodePushDemoAppTests/DownloadProgressTests/DownloadProgressTestApp", nil); + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; + RCTAssert(version.majorVersion == 8 || version.minorVersion == 3, @"Tests should be run on iOS 8.3, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion); + [CodePush setUsingTestConfiguration:YES]; + [CodePush clearTestUpdates]; + _runner = RCTInitRunnerForApp(@"CodePushDemoAppTests/DownloadProgressTests/DownloadProgressTestApp", nil); } #pragma mark Logic Tests - (void)testDownloadProgress { - [_runner runTest:_cmd module:@"DownloadProgressTest"]; + [_runner runTest:_cmd module:@"DownloadProgressTest"]; } @end diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/DownloadProgressTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/DownloadProgressTest.js deleted file mode 100644 index 75c61ed..0000000 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/DownloadProgressTest.js +++ /dev/null @@ -1,101 +0,0 @@ -"use strict"; - -var React = require("react-native"); -var { Platform, DeviceEventEmitter } = require("react-native"); -var CodePushSdk = require("react-native-code-push"); -var NativeCodePush = require("react-native").NativeModules.CodePush; -var RCTTestModule = require('NativeModules').TestModule || {}; - -var { - Text, - View, -} = React; - -var DownloadProgressTest = React.createClass({ - propTypes: { - shouldThrow: React.PropTypes.bool, - waitOneFrame: React.PropTypes.bool, - }, - - getInitialState() { - return { - done: false, - }; - }, - - componentDidMount() { - if (this.props.waitOneFrame) { - requestAnimationFrame(this.runTest); - } else { - this.runTest(); - } - }, - - checkReceivedAndExpectedBytesEqual() { - if (this.state.progress.receivedBytes !== this.state.progress.totalBytes) { - throw new Error("Bytes do not tally: Received bytes=" + this.state.progress.receivedBytes + " Total bytes=" + this.state.progress.totalBytes); - } - }, - - runTest() { - var downloadProgressSubscription = DeviceEventEmitter.addListener( - "CodePushDownloadProgress", - (progress) => { - this.setState({ - progress:progress, - done: false, - }); - } - ); - - var updates = require("./TestPackages"); - NativeCodePush.downloadUpdate(updates.smallPackage) - .then((smallPackage) => { - if (smallPackage) { - this.checkReceivedAndExpectedBytesEqual(); - return NativeCodePush.downloadUpdate(updates.mediumPackage); - } else { - throw new Error("Small package download failed."); - } - }) - .then((mediumPackage) => { - if (mediumPackage) { - this.checkReceivedAndExpectedBytesEqual(); - return NativeCodePush.downloadUpdate(updates.largePackage); - } else { - throw new Error("Medium package download failed."); - } - }) - .done((largePackage) => { - if (largePackage) { - this.checkReceivedAndExpectedBytesEqual(); - this.setState({done: true}, RCTTestModule.markTestCompleted); - } else { - throw new Error("Large package download failed."); - } - }); - }, - - render() { - var progressView; - if (this.state.progress) { - progressView = ( - {this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received - ); - } - - return ( - - - {this.constructor.displayName + ": "} - {this.state.done ? "Done" : "Testing..."} - - {progressView} - - ); - } -}); - -DownloadProgressTest.displayName = "DownloadProgressTest"; - -module.exports = DownloadProgressTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/DownloadProgressTestApp.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/DownloadProgressTestApp.js index c157ee7..65cf112 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/DownloadProgressTestApp.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/DownloadProgressTestApp.js @@ -1,8 +1,8 @@ "use strict"; -var React = require("react-native"); +import React from "react-native"; -var { +let { AppRegistry, ScrollView, StyleSheet, @@ -11,21 +11,21 @@ var { View, } = React; -var TESTS = [ - require("./DownloadProgressTest") +let TESTS = [ + require("./testcases/DownloadProgressTest") ]; TESTS.forEach( (test) => AppRegistry.registerComponent(test.displayName, () => test) ); -var DownloadProgressTestApp = React.createClass({ - getInitialState: function() { +let DownloadProgressTestApp = React.createClass({ + getInitialState() { return { test: null, }; }, - render: function() { + render() { if (this.state.test) { return ( @@ -36,9 +36,7 @@ var DownloadProgressTestApp = React.createClass({ return ( - Click on a test to run it in this shell for easier debugging and - development. Run all tests in the testing environment with cmd+U in - Xcode. + DownloadProgress Tests @@ -49,6 +47,9 @@ var DownloadProgressTestApp = React.createClass({ {test.displayName} + + {test.description} + , ])} @@ -58,7 +59,7 @@ var DownloadProgressTestApp = React.createClass({ } }); -var styles = StyleSheet.create({ +let styles = StyleSheet.create({ container: { backgroundColor: "white", marginTop: 40, diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/TestPackages.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/resources/TestPackages.js similarity index 60% rename from Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/TestPackages.js rename to Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/resources/TestPackages.js index 960ade2..ae3254c 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/TestPackages.js +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/resources/TestPackages.js @@ -1,7 +1,7 @@ -var { Platform } = require("react-native"); +let { Platform } = require("react-native"); -var packages = { - smallPackage: { +let packages = [ + { downloadUrl: "smallFile", description: "Angry flappy birds", appVersion: "1.5.0", @@ -12,7 +12,7 @@ var packages = { packageHash: "hash240", packageSize: 1024 }, - mediumPackage: { + { downloadUrl: "mediumFile", description: "Angry flappy birds", appVersion: "1.5.0", @@ -23,7 +23,7 @@ var packages = { packageHash: "hash240", packageSize: 1024 }, - largePackage: { + { downloadUrl: "largeFile", description: "Angry flappy birds", appVersion: "1.5.0", @@ -34,15 +34,14 @@ var packages = { packageHash: "hash240", packageSize: 1024 } -}; +]; -for (var aPackage in packages) { +packages.forEach((aPackage) => { if (Platform.OS === "android") { - // Genymotion forwards 10.0.3.2 to host machine's localhost - packages[aPackage].downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/DownloadProgressTests/" + packages[aPackage].downloadUrl; + aPackage.downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/DownloadProgressTests/resources/" + aPackage.downloadUrl; } else if (Platform.OS === "ios") { - packages[aPackage].downloadUrl = "http://localhost:8081/CodePushDemoAppTests/DownloadProgressTests/" + packages[aPackage].downloadUrl; + aPackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/DownloadProgressTests/resources/" + aPackage.downloadUrl; } -} +}); -module.exports = packages; +export default packages; diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/largeFile b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/resources/largeFile similarity index 100% rename from Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/largeFile rename to Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/resources/largeFile diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/mediumFile b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/resources/mediumFile similarity index 100% rename from Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/mediumFile rename to Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/resources/mediumFile diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/smallFile b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/resources/smallFile similarity index 100% rename from Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/smallFile rename to Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/resources/smallFile diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/testcases/DownloadProgressTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/testcases/DownloadProgressTest.js new file mode 100644 index 0000000..b54f3e5 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/DownloadProgressTests/testcases/DownloadProgressTest.js @@ -0,0 +1,52 @@ +"use strict"; + +import React from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; + +import testPackages from "../resources/TestPackages"; +let localPackage = {}; +let saveProgress; + +function checkReceivedAndExpectedBytesEqual() { + assert(saveProgress, "Download progress was not reported."); + assert.equal( + saveProgress.receivedBytes, + saveProgress.totalBytes, + `Bytes do not tally: Received bytes=${saveProgress.receivedBytes} Total bytes=${saveProgress.totalBytes}` + ); + console.log("Downloaded one package."); + saveProgress = null; +} + +let DownloadProgressTest = createTestCaseComponent( + "DownloadProgressTest", + "should successfully download all the bytes contained in the test packages", + () => { + testPackages.forEach((aPackage, index) => { + testPackages[index] = Object.assign(aPackage, PackageMixins.remote); + }); + return Promise.resolve(); + }, + () => { + let downloadProgressCallback = (downloadProgress) => { + console.log(`Expecting ${downloadProgress.totalBytes} bytes, received ${downloadProgress.receivedBytes} bytes.`); + saveProgress = downloadProgress; + }; + + // Chains promises together. + return testPackages.reduce((aPackageDownloaded, nextPackage, index) => { + return aPackageDownloaded + .then(() => { + // Skip the first time. + index && checkReceivedAndExpectedBytesEqual(); + return nextPackage.download(downloadProgressCallback); + }) + }, Promise.resolve()); + } +); + +module.exports = DownloadProgressTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests.m b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests.m index 356f81c..db365a8 100644 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests.m +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests.m @@ -3,6 +3,9 @@ #import #import "RCTAssert.h" +#import "RCTRootView.h" +#import "RCTText.h" +#import "CodePush.h" #define FB_REFERENCE_IMAGE_DIR "\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\"" @@ -12,25 +15,97 @@ @implementation InstallUpdateTests { - RCTTestRunner *_runner; + RCTTestRunner *_runner; } - (void)setUp { #if __LP64__ - RCTAssert(false, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)"); + RCTAssert(false, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)"); #endif - NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; - RCTAssert(version.majorVersion == 8 || version.minorVersion == 3, @"Tests should be run on iOS 8.3, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion); - _runner = RCTInitRunnerForApp(@"CodePushDemoAppTests/InstallUpdateTests/InstallUpdateTestApp", nil); + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; + RCTAssert(version.majorVersion == 8 || version.minorVersion == 3, @"Tests should be run on iOS 8.3, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion); + [CodePush setUsingTestConfiguration:YES]; } #pragma mark Logic Tests -- (void)testDownloadAndInstallUpdate +- (void)testInstallModeImmediate { + [self runTest:@"InstallModeImmediateTest"]; +} + +- (void)testInstallModeOnNextResume +{ + [self runTest:@"InstallModeOnNextResumeTest"]; +} + +- (void)testInstallModeOnNextRestart +{ + [self runTest:@"InstallModeOnNextRestartTest"]; +} + +- (void)testIsFirstRun +{ + [self runTest:@"IsFirstRunTest"]; +} + +- (void)testNotifyApplicationReady +{ + [self runTest:@"NotifyApplicationReadyTest"]; +} + +- (void)testRollback +{ + [self runTest:@"RollbackTest"]; +} + +- (void)testIsFailedUpdate +{ + [self runTest:@"IsFailedUpdateTest"]; +} + +- (void)testIsPending +{ + [self runTest:@"IsPendingTest"]; +} + +- (void)runTest:(NSString *)testName +{ + [CodePush clearTestUpdates]; + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/testcases/%@.bundle?platform=ios&dev=true", testName]] + moduleName:testName + initialProperties:nil + launchOptions:nil]; + rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices + UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController; + vc.view = [UIView new]; + [vc.view addSubview:rootView]; + while (![self foundTestPassedText:vc.view]) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } +} + +- (BOOL)foundTestPassedText:(UIView *)view { + BOOL foundText = NO; + NSArray *subviews = [view subviews]; + if ([subviews count] == 0) { + if ([view isKindOfClass:[RCTText class]] && [[((RCTText *)view) textStorage].string isEqualToString:@"Test Passed!"]) { + return YES; + } + + return NO; + } - [_runner runTest:_cmd module:@"DownloadAndInstallUpdateTest"]; + for (UIView *subview in subviews) { + foundText = [self foundTestPassedText:subview]; + if (foundText) { + break; + } + } + + return foundText; } @end diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/CodePushDemoApp.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/CodePushDemoApp.js deleted file mode 100644 index ed4933a..0000000 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/CodePushDemoApp.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Sample React Native App - * https://github.com/facebook/react-native - */ -'use strict'; - -var React = require('react-native'); -var { - AppRegistry, - StyleSheet, - Text, - TouchableOpacity, - View, -} = React; - -var RCTTestModule = require('NativeModules').TestModule; -var NativeCodePush = require('react-native').NativeModules.CodePush; - -var CodePushDemoApp = React.createClass({ - componentDidMount: function() { - NativeCodePush.setUsingTestFolder(true); - NativeCodePush.getCurrentPackage().then( - (savedPackage) => { - if (savedPackage) { - var testPackage = require("./TestPackage"); - for (var key in testPackage) { - if (savedPackage[key] !== testPackage[key]) { - throw new Error("The local package is still different from the updated package after installation"); - } - } - } else { - throw new Error("The updated package was not saved"); - } - }, - (err) => { - throw new Error("The updated package was not saved"); - } - ); - }, - render: function() { - return ( - - - If you see this, you have successfully installed an update! - - - ); - } -}); - -var styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#F5FCFF', - }, - welcome: { - fontSize: 20, - textAlign: 'center', - margin: 10, - } -}); - -CodePushDemoApp.displayName = 'CodePushDemoApp'; -AppRegistry.registerComponent('CodePushDemoApp', () => CodePushDemoApp); diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/DownloadAndInstallUpdateTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/DownloadAndInstallUpdateTest.js deleted file mode 100644 index a861114..0000000 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/DownloadAndInstallUpdateTest.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -var React = require('react-native'); -var CodePushSdk = require('react-native-code-push'); -var NativeCodePush = require("react-native").NativeModules.CodePush; -var RCTTestModule = require('NativeModules').TestModule || {}; - -var { - AppRegistry, - Text, - View, -} = React; - -var DownloadAndInstallUpdateTest = React.createClass({ - propTypes: { - shouldThrow: React.PropTypes.bool, - waitOneFrame: React.PropTypes.bool, - }, - - getInitialState() { - return { - done: false, - }; - }, - - componentDidMount() { - if (this.props.waitOneFrame) { - requestAnimationFrame(this.runTest); - } else { - this.setUp(); - this.runTest(); - } - }, - - setUp(callWhenDone) { - var mockConfiguration = { appVersion : "1.5.0" }; - NativeCodePush.setUsingTestFolder(true); - CodePushSdk.setUpTestDependencies(null, mockConfiguration, NativeCodePush); - }, - - runTest() { - var update = require("./TestPackage"); - NativeCodePush.downloadUpdate(update).done((downloadedPackage) => { - NativeCodePush.installUpdate(downloadedPackage, /*rollbackTimeout*/ 1000, CodePushSdk.InstallMode.IMMEDIATE) - .then(() => { - CodePushSdk.getCurrentPackage().then((localPackage) => { - if (localPackage.packageHash == update.packageHash) { - this.setState({done: true}, RCTTestModule.markTestCompleted); - } else { - throw new Error("Update was not installed"); - } - }); - }); - }); - }, - - render() { - return ( - - - {this.constructor.displayName + ': '} - {this.state.done ? 'Done' : 'Testing...'} - - - ); - } -}); - -DownloadAndInstallUpdateTest.displayName = 'DownloadAndInstallUpdateTest'; -AppRegistry.registerComponent('CodePushDemoApp', () => DownloadAndInstallUpdateTest); - -module.exports = DownloadAndInstallUpdateTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/InstallUpdateTestApp.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/InstallUpdateTestApp.js deleted file mode 100644 index 6ed099a..0000000 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/InstallUpdateTestApp.js +++ /dev/null @@ -1,79 +0,0 @@ -'use strict'; - -var React = require('react-native'); - -var { - AppRegistry, - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - View, -} = React; - -var TESTS = [ - require('./DownloadAndInstallUpdateTest') -]; - -TESTS.forEach( - (test) => AppRegistry.registerComponent(test.displayName, () => test) -); - -var InstallUpdateTestApp = React.createClass({ - getInitialState: function() { - return { - test: null, - }; - }, - render: function() { - if (this.state.test) { - return ( - - - - ); - } - return ( - - - Click on a test to run it in this shell for easier debugging and - development. Run all tests in the testing environment with cmd+U in - Xcode. - - - - {TESTS.map((test) => [ - this.setState({test})} - style={styles.row}> - - {test.displayName} - - , - - ])} - - - ); - } -}); - -var styles = StyleSheet.create({ - container: { - backgroundColor: 'white', - marginTop: 40, - margin: 15, - }, - row: { - padding: 10, - }, - testName: { - fontWeight: '500', - }, - separator: { - height: 1, - backgroundColor: '#bbbbbb', - } -}); - -AppRegistry.registerComponent('InstallUpdateTestApp', () => InstallUpdateTestApp); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/TestPackage.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/TestPackage.js deleted file mode 100644 index b80776c..0000000 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/TestPackage.js +++ /dev/null @@ -1,21 +0,0 @@ -var { Platform } = require("react-native"); - -var testPackage = { - description: "Angry flappy birds", - appVersion: "1.5.0", - label: "2.4.0", - isMandatory: false, - isAvailable: true, - updateAppVersion: false, - packageHash: "hash240", - packageSize: 1024 -}; - -if (Platform.OS === "android") { - // Genymotion forwards 10.0.3.2 to host machine's localhost - testPackage.downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/CodePushDemoApp.includeRequire.runModule.bundle?platform=android&dev=true" -} else if (Platform.OS === "ios") { - testPackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/CodePushDemoApp.includeRequire.runModule.bundle?platform=ios&dev=true" -} - -module.exports = testPackage; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/CheckIsFirstRunAndPassTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/CheckIsFirstRunAndPassTest.js new file mode 100644 index 0000000..52ddf35 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/CheckIsFirstRunAndPassTest.js @@ -0,0 +1,42 @@ +"use strict"; + +import React from "react-native"; +import CodePush from "react-native-code-push"; + +let { + AppRegistry, + Text, + View, +} = React; + +let IsFirstRunTest = React.createClass({ + getInitialState() { + return {}; + }, + componentDidMount() { + CodePush.getCurrentPackage() + .then((localPackage) => { + if (localPackage.isFirstRun) { + this.setState({ passed: true }); + } else { + this.setState({ passed: false }); + } + }); + }, + render() { + let text = "Testing..."; + if (this.state.passed !== undefined) { + text = this.state.passed ? "Test Passed!" : "Test Failed!"; + } + + return ( + + + {text} + + + ); + } +}); + +AppRegistry.registerComponent("IsFirstRunTest", () => IsFirstRunTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/IsFailedUpdateTestBundleV1.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/IsFailedUpdateTestBundleV1.js new file mode 100644 index 0000000..d7f2eb2 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/IsFailedUpdateTestBundleV1.js @@ -0,0 +1,73 @@ +"use strict"; + +import React from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import createMockAcquisitionSdk from "../../utils/mockAcquisitionSdk"; + +let { + AppRegistry, + Platform, + Text, + View, +} = React; + +let IsFailedUpdateTest = React.createClass({ + getInitialState() { + return {}; + }, + componentDidMount() { + let serverPackage = { + description: "Angry flappy birds", + appVersion: "1.5.0", + label: "2.4.0", + isMandatory: false, + isAvailable: true, + updateAppVersion: false, + packageHash: "hash241", + packageSize: 1024 + }; + + if (Platform.OS === "android") { + serverPackage.downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/resources/IsFailedUpdateTestBundleV2.includeRequire.runModule.bundle?platform=android&dev=true" + } else if (Platform.OS === "ios") { + serverPackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/IsFailedUpdateTestBundleV2.includeRequire.runModule.bundle?platform=ios&dev=true" + } + + let mockAcquisitionSdk = createMockAcquisitionSdk(serverPackage); + let mockConfiguration = { appVersion : "1.5.0" }; + CodePush.setUpTestDependencies(mockAcquisitionSdk, mockConfiguration, NativeCodePush); + + CodePush.notifyApplicationReady() + .then(() => { + return CodePush.checkForUpdate(); + }) + .then((remotePackage) => { + if (remotePackage.failedInstall) { + this.setState({ passed: true }); + } else { + return remotePackage.download(); + } + }) + .then((localPackage) => { + return localPackage && localPackage.install(NativeCodePush.codePushInstallModeImmediate); + }); + }, + render() { + let text = "Testing..."; + if (this.state.passed !== undefined) { + text = this.state.passed ? "Test Passed!" : "Test Failed!"; + } + + return ( + + + {text} + + + ); + } +}); + +AppRegistry.registerComponent("IsFailedUpdateTest", () => IsFailedUpdateTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/IsFailedUpdateTestBundleV2.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/IsFailedUpdateTestBundleV2.js new file mode 100644 index 0000000..bd8fc8a --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/IsFailedUpdateTestBundleV2.js @@ -0,0 +1,27 @@ +"use strict"; + +import React from "react-native"; +import CodePush from "react-native-code-push"; + +let { + AppRegistry, + Text, + View, +} = React; + +let IsFailedUpdateTest = React.createClass({ + componentDidMount() { + CodePush.restartApp(); + }, + render() { + return ( + + + Testing... + + + ); + } +}); + +AppRegistry.registerComponent("IsFailedUpdateTest", () => IsFailedUpdateTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/NotifyApplicationReadyAndRestart.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/NotifyApplicationReadyAndRestart.js new file mode 100644 index 0000000..fcd9f2d --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/NotifyApplicationReadyAndRestart.js @@ -0,0 +1,37 @@ +"use strict"; + +import React from "react-native"; +import { Platform, AppRegistry, Text, View } from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +let RCTTestModule = React.NativeModules.TestModule; + +let NotifyApplicationReadyTest = React.createClass({ + getInitialState() { + return {}; + }, + componentDidMount() { + CodePush.notifyApplicationReady() + .then(() => { + if (Platform.OS === "android") { + return NativeCodePush.downloadAndReplaceCurrentBundle("http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/resources/PassNotifyApplicationReadyTest.includeRequire.runModule.bundle?platform=android&dev=true"); + } else if (Platform.OS === "ios") { + return NativeCodePush.downloadAndReplaceCurrentBundle("http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/PassNotifyApplicationReadyTest.includeRequire.runModule.bundle?platform=ios&dev=true"); + } + }) + .then(() => { + CodePush.restartApp(); + }); + }, + render() { + return ( + + + Testing... + + + ); + } +}); + +AppRegistry.registerComponent("NotifyApplicationReadyTest", () => NotifyApplicationReadyTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeImmediateTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeImmediateTest.js new file mode 100644 index 0000000..d93d8dc --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeImmediateTest.js @@ -0,0 +1,23 @@ +"use strict"; + +import React from "react-native"; + +let { + AppRegistry, + Text, + View, +} = React; + +let InstallModeImmediateTest = React.createClass({ + render() { + return ( + + + Test Passed! + + + ); + } +}); + +AppRegistry.registerComponent("InstallModeImmediateTest", () => InstallModeImmediateTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeOnNextRestartTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeOnNextRestartTest.js new file mode 100644 index 0000000..7743f49 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeOnNextRestartTest.js @@ -0,0 +1,23 @@ +"use strict"; + +import React from "react-native"; + +let { + AppRegistry, + Text, + View, +} = React; + +let InstallModeOnNextRestartTest = React.createClass({ + render() { + return ( + + + Test Passed! + + + ); + } +}); + +AppRegistry.registerComponent("InstallModeOnNextRestartTest", () => InstallModeOnNextRestartTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeOnNextResumeTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeOnNextResumeTest.js new file mode 100644 index 0000000..cfd0a98 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeOnNextResumeTest.js @@ -0,0 +1,23 @@ +"use strict"; + +import React from "react-native"; + +let { + AppRegistry, + Text, + View, +} = React; + +let InstallModeOnNextResumeTest = React.createClass({ + render() { + return ( + + + Test Passed! + + + ); + } +}); + +AppRegistry.registerComponent("InstallModeOnNextResumeTest", () => InstallModeOnNextResumeTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/PassNotifyApplicationReadyTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/PassNotifyApplicationReadyTest.js new file mode 100644 index 0000000..5f426c7 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/PassNotifyApplicationReadyTest.js @@ -0,0 +1,23 @@ +"use strict"; + +import React from "react-native"; + +let { + AppRegistry, + Text, + View, +} = React; + +let NotifyApplicationReadyTest = React.createClass({ + render() { + return ( + + + Test Passed! + + + ); + } +}); + +AppRegistry.registerComponent("NotifyApplicationReadyTest", () => NotifyApplicationReadyTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1.js new file mode 100644 index 0000000..12eb9c5 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1.js @@ -0,0 +1,61 @@ +"use strict"; + +import React from "react-native"; +import CodePush from "react-native-code-push"; +let RCTTestModule = React.NativeModules.TestModule; +let NativeCodePush = React.NativeModules.CodePush; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); + +let { + AppRegistry, + Platform, + Text, + View, +} = React; + +let RollbackTest = React.createClass({ + componentDidMount() { + let remotePackage = { + description: "Angry flappy birds", + appVersion: "1.5.0", + label: "2.4.0", + isMandatory: false, + isAvailable: true, + updateAppVersion: false, + packageHash: "hash241", + packageSize: 1024 + }; + + if (Platform.OS === "android") { + remotePackage.downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV2.includeRequire.runModule.bundle?platform=android&dev=true" + CodePush.notifyApplicationReady() + .then(() => { + NativeCodePush.downloadAndReplaceCurrentBundle("http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1Pass.includeRequire.runModule.bundle?platform=android&dev=true"); + }); + } else if (Platform.OS === "ios") { + remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV2.includeRequire.runModule.bundle?platform=ios&dev=true" + CodePush.notifyApplicationReady() + .then(() => { + NativeCodePush.downloadAndReplaceCurrentBundle("http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1Pass.includeRequire.runModule.bundle?platform=ios&dev=true"); + }); + } + + remotePackage = Object.assign(remotePackage, PackageMixins.remote); + + remotePackage.download() + .then((localPackage) => { + return localPackage.install(NativeCodePush.codePushInstallModeImmediate); + }); + }, + render() { + return ( + + + Testing... + + + ); + } +}); + +AppRegistry.registerComponent("RollbackTest", () => RollbackTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1Pass.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1Pass.js new file mode 100644 index 0000000..2f8b87f --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1Pass.js @@ -0,0 +1,23 @@ +"use strict"; + +import React from "react-native"; + +let { + AppRegistry, + Text, + View, +} = React; + +let RollbackTest = React.createClass({ + render() { + return ( + + + Test Passed! + + + ); + } +}); + +AppRegistry.registerComponent("RollbackTest", () => RollbackTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV2.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV2.js new file mode 100644 index 0000000..12c9ce7 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV2.js @@ -0,0 +1,27 @@ +"use strict"; + +import React from "react-native"; +import CodePush from "react-native-code-push"; + +let { + AppRegistry, + Text, + View, +} = React; + +let RollbackTest = React.createClass({ + componentDidMount() { + CodePush.restartApp(); + }, + render() { + return ( + + + Testing... + + + ); + } +}); + +AppRegistry.registerComponent("RollbackTest", () => RollbackTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/remotePackage.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/remotePackage.js new file mode 100644 index 0000000..5d3e57a --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/resources/remotePackage.js @@ -0,0 +1,10 @@ +export default { + description: "Angry flappy birds", + appVersion: "1.5.0", + label: "2.4.0", + isMandatory: false, + isAvailable: true, + updateAppVersion: false, + packageHash: "hash240", + packageSize: 1024 +}; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeImmediateTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeImmediateTest.js new file mode 100644 index 0000000..aa83023 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeImmediateTest.js @@ -0,0 +1,35 @@ +"use strict"; + +import React from "react-native"; +import { DeviceEventEmitter, Platform, AppRegistry } from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; + +let remotePackage = require("../resources/remotePackage"); + +let InstallModeImmediateTest = createTestCaseComponent( + "InstallModeImmediateTest", + "App should restart immediately to the new version after it is installed", + () => { + if (Platform.OS === "android") { + remotePackage.downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeImmediateTest.includeRequire.runModule.bundle?platform=android&dev=true" + } else if (Platform.OS === "ios") { + remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeImmediateTest.includeRequire.runModule.bundle?platform=ios&dev=true" + } + + remotePackage = Object.assign(remotePackage, PackageMixins.remote); + return Promise.resolve(); + }, + () => { + remotePackage.download() + .then((localPackage) => { + return localPackage.install(NativeCodePush.codePushInstallModeImmediate); + }); + }, + /*passAfterRun*/ false +); + +AppRegistry.registerComponent("InstallModeImmediateTest", () => InstallModeImmediateTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextRestartTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextRestartTest.js new file mode 100644 index 0000000..6429de8 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextRestartTest.js @@ -0,0 +1,39 @@ +"use strict"; + +import React from "react-native"; +import { DeviceEventEmitter, Platform, AppRegistry } from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; + +let remotePackage = require("../resources/remotePackage"); + +let InstallModeOnNextRestartTest = createTestCaseComponent( + "InstallModeOnNextRestartTest", + "App should boot up the new version after it is installed and restarted", + () => { + if (Platform.OS === "android") { + // Genymotion forwards 10.0.3.2 to host machine's localhost + remotePackage.downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeOnNextRestartTest.includeRequire.runModule.bundle?platform=android&dev=true" + } else if (Platform.OS === "ios") { + remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeOnNextRestartTest.includeRequire.runModule.bundle?platform=ios&dev=true" + } + + remotePackage = Object.assign(remotePackage, PackageMixins.remote); + return Promise.resolve(); + }, + () => { + remotePackage.download() + .then((localPackage) => { + return localPackage.install(NativeCodePush.codePushInstallModeOnNextRestart); + }) + .then(() => { + CodePush.restartApp(); + }); + }, + /*passAfterRun*/ false +); + +AppRegistry.registerComponent("InstallModeOnNextRestartTest", () => InstallModeOnNextRestartTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextResumeTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextResumeTest.js new file mode 100644 index 0000000..942d17e --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/InstallModeOnNextResumeTest.js @@ -0,0 +1,38 @@ +"use strict"; + +import React from "react-native"; +import { DeviceEventEmitter, Platform, AppRegistry } from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; + +let remotePackage = require("../resources/remotePackage"); + +let InstallModeOnNextResumeTest = createTestCaseComponent( + "InstallModeOnNextResumeTest", + "App should boot up the new version after it is installed and resumed", + () => { + if (Platform.OS === "android") { + remotePackage.downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeOnNextResumeTest.includeRequire.runModule.bundle?platform=android&dev=true" + } else if (Platform.OS === "ios") { + remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/PassInstallModeOnNextResumeTest.includeRequire.runModule.bundle?platform=ios&dev=true" + } + + remotePackage = Object.assign(remotePackage, PackageMixins.remote); + return Promise.resolve(); + }, + () => { + remotePackage.download() + .then((localPackage) => { + return localPackage.install(NativeCodePush.codePushInstallModeOnNextResume); + }) + .then(() => { + CodePush.restartApp(); + }); + }, + /*passAfterRun*/ false +); + +AppRegistry.registerComponent("InstallModeOnNextResumeTest", () => InstallModeOnNextResumeTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFailedUpdateTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFailedUpdateTest.js new file mode 100644 index 0000000..78c4c6a --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFailedUpdateTest.js @@ -0,0 +1,35 @@ +"use strict"; + +import React from "react-native"; +import { DeviceEventEmitter, Platform, AppRegistry } from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; + +let remotePackage = require("../resources/remotePackage"); + +let IsFailedUpdateTest = createTestCaseComponent( + "IsFailedUpdateTest", + "After an installed update is rolled back, checkForUpdate should return a package with the failedInstall property set to \"true\"", + () => { + if (Platform.OS === "android") { + remotePackage.downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/resources/IsFailedUpdateTestBundleV1.includeRequire.runModule.bundle?platform=android&dev=true" + } else if (Platform.OS === "ios") { + remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/IsFailedUpdateTestBundleV1.includeRequire.runModule.bundle?platform=ios&dev=true" + } + + remotePackage = Object.assign(remotePackage, PackageMixins.remote); + return Promise.resolve(); + }, + () => { + remotePackage.download() + .then((localPackage) => { + return localPackage.install(NativeCodePush.codePushInstallModeImmediate); + }); + }, + /*passAfterRun*/ false +); + +AppRegistry.registerComponent("IsFailedUpdateTest", () => IsFailedUpdateTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFirstRunTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFirstRunTest.js new file mode 100644 index 0000000..0c0866b --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsFirstRunTest.js @@ -0,0 +1,35 @@ +"use strict"; + +import React from "react-native"; +import { DeviceEventEmitter, Platform, AppRegistry } from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; + +let remotePackage = require("../resources/remotePackage"); + +let IsFirstRunTest = createTestCaseComponent( + "IsFirstRunTest", + "After the app is installed, the isFirstRun property on the current package should be set to \"true\"", + () => { + if (Platform.OS === "android") { + remotePackage.downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/resources/CheckIsFirstRunAndPassTest.includeRequire.runModule.bundle?platform=android&dev=true" + } else if (Platform.OS === "ios") { + remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/CheckIsFirstRunAndPassTest.includeRequire.runModule.bundle?platform=ios&dev=true" + } + + remotePackage = Object.assign(remotePackage, PackageMixins.remote); + return Promise.resolve(); + }, + () => { + remotePackage.download() + .then((localPackage) => { + return localPackage.install(NativeCodePush.codePushInstallModeImmediate); + }); + }, + /*passAfterRun*/ false +); + +AppRegistry.registerComponent("IsFirstRunTest", () => IsFirstRunTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsPendingTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsPendingTest.js new file mode 100644 index 0000000..901ef12 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/IsPendingTest.js @@ -0,0 +1,41 @@ +"use strict"; + +import React from "react-native"; +import { DeviceEventEmitter, Platform, AppRegistry } from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; + +let remotePackage = require("../resources/remotePackage"); + +let IsPendingTest = createTestCaseComponent( + "IsPendingTest", + "After the app is installed, the isPending property on the installed package should be set to \"true\"", + () => { + if (Platform.OS === "android") { + remotePackage.downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/resources/CheckIsFirstRunAndPassTest.includeRequire.runModule.bundle?platform=android&dev=true" + } else if (Platform.OS === "ios") { + remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/CheckIsFirstRunAndPassTest.includeRequire.runModule.bundle?platform=ios&dev=true" + } + + remotePackage = Object.assign(remotePackage, PackageMixins.remote); + return Promise.resolve(); + }, + () => { + remotePackage.download() + .then((localPackage) => { + return localPackage.install(NativeCodePush.codePushInstallModeOnNextRestart); + }) + .then((localPackage) => { + assert(localPackage.isPending, "isPending should be set to \"true\" after an install"); + return CodePush.getCurrentPackage(); + }) + .then((localPackage) => { + assert(localPackage.isPending, "isPending should be set to \"true\" after an install"); + }); + } +); + +AppRegistry.registerComponent("IsPendingTest", () => IsPendingTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/NotifyApplicationReadyTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/NotifyApplicationReadyTest.js new file mode 100644 index 0000000..a38fcf2 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/NotifyApplicationReadyTest.js @@ -0,0 +1,35 @@ +"use strict"; + +import React from "react-native"; +import { DeviceEventEmitter, Platform, AppRegistry } from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; + +let remotePackage = require("../resources/remotePackage"); + +let NotifyApplicationReadyTest = createTestCaseComponent( + "NotifyApplicationReadyTest", + "After an update, the app should remain using the installed version after multiple restarts if \"notifyApplicationReady\" is called.", + () => { + if (Platform.OS === "android") { + remotePackage.downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/resources/NotifyApplicationReadyAndRestart.includeRequire.runModule.bundle?platform=android&dev=true" + } else if (Platform.OS === "ios") { + remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/NotifyApplicationReadyAndRestart.includeRequire.runModule.bundle?platform=ios&dev=true" + } + + remotePackage = Object.assign(remotePackage, PackageMixins.remote); + return Promise.resolve(); + }, + () => { + remotePackage.download() + .then((localPackage) => { + return localPackage.install(NativeCodePush.codePushInstallModeImmediate); + }); + }, + /*passAfterRun*/ false +); + +AppRegistry.registerComponent("NotifyApplicationReadyTest", () => NotifyApplicationReadyTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/RollbackTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/RollbackTest.js new file mode 100644 index 0000000..3c098ba --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/InstallUpdateTests/testcases/RollbackTest.js @@ -0,0 +1,35 @@ +"use strict"; + +import React from "react-native"; +import { DeviceEventEmitter, Platform, AppRegistry } from "react-native"; +import CodePush from "react-native-code-push"; +let NativeCodePush = React.NativeModules.CodePush; +import createTestCaseComponent from "../../utils/createTestCaseComponent"; +let PackageMixins = require("react-native-code-push/package-mixins.js")(NativeCodePush); +import assert from "assert"; + +let remotePackage = require("../resources/remotePackage"); + +let RollbackTest = createTestCaseComponent( + "RollbackTest", + "should successfully rollback if \"notifyApplicationReady\" is not called in the installed package.", + () => { + if (Platform.OS === "android") { + remotePackage.downloadUrl = "http://10.0.3.2:8081/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1.includeRequire.runModule.bundle?platform=android&dev=true" + } else if (Platform.OS === "ios") { + remotePackage.downloadUrl = "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/resources/RollbackTestBundleV1.includeRequire.runModule.bundle?platform=ios&dev=true" + } + + remotePackage = Object.assign(remotePackage, PackageMixins.remote); + return Promise.resolve(); + }, + () => { + remotePackage.download() + .then((localPackage) => { + return localPackage.install(NativeCodePush.codePushInstallModeImmediate); + }); + }, + /*passAfterRun*/ false +); + +AppRegistry.registerComponent("RollbackTest", () => RollbackTest); \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests.m b/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests.m deleted file mode 100644 index 1e99ea0..0000000 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests.m +++ /dev/null @@ -1,61 +0,0 @@ -#import -#import -#import - -#import "RCTAssert.h" - -#define FB_REFERENCE_IMAGE_DIR "\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\"" - -@interface QueryUpdateTests : XCTestCase - -@end - -@implementation QueryUpdateTests -{ - RCTTestRunner *_runner; -} - -- (void)setUp -{ -#if __LP64__ - RCTAssert(false, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)"); -#endif - - NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; - RCTAssert(version.majorVersion == 8 || version.minorVersion == 3, @"Tests should be run on iOS 8.3, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion); - _runner = RCTInitRunnerForApp(@"CodePushDemoAppTests/QueryUpdateTests/QueryUpdateTestApp", nil); -} - -#pragma mark Logic Tests -- (void)testNoRemotePackage -{ - - [_runner runTest:_cmd module:@"NoRemotePackageTest"]; -} - -- (void)testNoRemotePackageWithSameAppVersion -{ - [_runner runTest:_cmd - module:@"NoRemotePackageWithSameAppVersionTest"]; -} - -- (void)testFirstUpdate -{ - [_runner runTest:_cmd - module:@"FirstUpdateTest"]; -} - -- (void)testNewUpdate -{ - [_runner runTest:_cmd - module:@"NewUpdateTest"]; -} - -- (void)testSamePackage -{ - [_runner runTest:_cmd - module:@"SamePackageTest"]; -} - - -@end diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/FirstUpdateTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/FirstUpdateTest.js deleted file mode 100644 index 0c7b2fc..0000000 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/FirstUpdateTest.js +++ /dev/null @@ -1,96 +0,0 @@ -'use strict'; - -var RCTTestModule = require('NativeModules').TestModule; -var React = require('react-native'); -var CodePushSdk = require('react-native-code-push'); -var NativeCodePush = require("react-native").NativeModules.CodePush; -var RCTTestModule = require('NativeModules').TestModule || {}; - -var { - Text, - View, -} = React; - -var FirstUpdateTest = React.createClass({ - propTypes: { - shouldThrow: React.PropTypes.bool, - waitOneFrame: React.PropTypes.bool, - }, - - getInitialState() { - return { - done: false, - }; - }, - - componentDidMount() { - if (this.props.waitOneFrame) { - requestAnimationFrame(this.runTest); - } else { - this.setUp(this.runTest); - } - }, - - setUp(callWhenDone) { - var mockAcquisitionSdk = { - latestPackage: { - downloadUrl: "http://www.windowsazure.com/blobs/awperoiuqpweru", - description: "Angry flappy birds", - appVersion: "1.5.0", - label: "2.4.0", - isMandatory: false, - isAvailable: true, - updateAppVersion: false, - packageHash: "hash240", - packageSize: 1024 - }, - queryUpdateWithCurrentPackage: function(queryPackage, callback){ - if (!this.latestPackage || queryPackage.appVersion !== this.latestPackage.appVersion || - queryPackage.packageHash == this.latestPackage.packageHash) { - callback(/*err:*/ null, false); - } else { - callback(/*err:*/ null, this.latestPackage); - } - } - }; - - var mockConfiguration = { appVersion : "1.5.0" }; - NativeCodePush.setUsingTestFolder(true); - CodePushSdk.setUpTestDependencies(mockAcquisitionSdk, mockConfiguration, NativeCodePush); - - CodePushSdk.getCurrentPackage = function () { - return Promise.resolve(null); - } - callWhenDone(); - }, - - runTest() { - CodePushSdk.checkForUpdate().then( - (update) => { - if (update) { - this.setState({done: true}, RCTTestModule.markTestCompleted); - } else { - throw new Error('SDK should return a package when there is an update'); - } - }, - (err) => { - throw new Error(err.message); - }, - ); - }, - - render() { - return ( - - - {this.constructor.displayName + ': '} - {this.state.done ? 'Done' : 'Testing...'} - - - ); - } -}); - -FirstUpdateTest.displayName = 'FirstUpdateTest'; - -module.exports = FirstUpdateTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/NewUpdateTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/NewUpdateTest.js deleted file mode 100644 index 20bef6b..0000000 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/NewUpdateTest.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict'; - -var RCTTestModule = require('NativeModules').TestModule; -var React = require('react-native'); -var CodePushSdk = require('react-native-code-push'); -var NativeCodePush = require("react-native").NativeModules.CodePush; -var RCTTestModule = require('NativeModules').TestModule || {}; - -var { - Text, - View, -} = React; - -var NewUpdateTest = React.createClass({ - propTypes: { - shouldThrow: React.PropTypes.bool, - waitOneFrame: React.PropTypes.bool, - }, - - getInitialState() { - return { - done: false, - }; - }, - - componentDidMount() { - if (this.props.waitOneFrame) { - requestAnimationFrame(this.runTest); - } else { - this.setUp(this.runTest); - } - }, - - setUp(callWhenDone) { - var mockAcquisitionSdk = { - latestPackage: { - downloadUrl: "http://www.windowsazure.com/blobs/awperoiuqpweru", - description: "Angry flappy birds", - appVersion: "1.5.0", - label: "2.4.0", - isMandatory: false, - isAvailable: true, - updateAppVersion: false, - packageHash: "hash240", - packageSize: 1024 - }, - queryUpdateWithCurrentPackage: function(queryPackage, callback){ - if (!this.latestPackage || queryPackage.appVersion !== this.latestPackage.appVersion || - queryPackage.packageHash == this.latestPackage.packageHash) { - callback(/*err:*/ null, false); - } else { - callback(/*err:*/ null, this.latestPackage); - } - } - }; - - var localPackage = { - downloadURL: "http://www.windowsazure.com/blobs/awperoiuqpweru", - description: "Angry flappy birds", - appVersion: "1.5.0", - label: "2.4.0", - isMandatory: false, - isAvailable: true, - updateAppVersion: false, - packageHash: "hash123", - packageSize: 1024 - }; - - var mockConfiguration = { appVersion : "1.5.0" }; - NativeCodePush.setUsingTestFolder(true); - CodePushSdk.setUpTestDependencies(mockAcquisitionSdk, mockConfiguration, NativeCodePush); - - CodePushSdk.getCurrentPackage = function () { - return Promise.resolve(localPackage); - } - callWhenDone(); - }, - - runTest() { - CodePushSdk.checkForUpdate().then( - (update) => { - if (update) { - this.setState({done: true}, RCTTestModule.markTestCompleted); - } else { - throw new Error('SDK should return a package when there is a new update'); - } - }, - (err) => { - throw new Error(err.message); - }, - ); - }, - - render() { - return ( - - - {this.constructor.displayName + ': '} - {this.state.done ? 'Done' : 'Testing...'} - - - ); - } -}); - -NewUpdateTest.displayName = 'NewUpdateTest'; - -module.exports = NewUpdateTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/NoRemotePackageTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/NoRemotePackageTest.js deleted file mode 100644 index 75c55c3..0000000 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/NoRemotePackageTest.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict'; - -var React = require('react-native'); -var CodePushSdk = require('react-native-code-push'); -var NativeCodePush = require("react-native").NativeModules.CodePush; -var RCTTestModule = require('NativeModules').TestModule || {}; - -var { - Text, - View, -} = React; - -var NoRemotePackageTest = React.createClass({ - propTypes: { - shouldThrow: React.PropTypes.bool, - waitOneFrame: React.PropTypes.bool, - }, - - getInitialState() { - return { - done: false, - }; - }, - - componentDidMount() { - if (this.props.waitOneFrame) { - requestAnimationFrame(this.runTest); - } else { - this.setUp(); - this.runTest(); - } - }, - - setUp() { - var mockAcquisitionSdk = { - latestPackage: null, - queryUpdateWithCurrentPackage: function(queryPackage, callback){ - if (!this.latestPackage || queryPackage.appVersion !== this.latestPackage.appVersion || - queryPackage.packageHash == this.latestPackage.packageHash) { - callback(/*err:*/ null, false); - } else { - callback(/*err:*/ null, latestPackage); - } - } - }; - - var mockConfiguration = { appVersion : "1.5.0" }; - NativeCodePush.setUsingTestFolder(true); - CodePushSdk.setUpTestDependencies(mockAcquisitionSdk, mockConfiguration, NativeCodePush); - }, - - runTest() { - CodePushSdk.checkForUpdate().then( - (update) => { - if (update) { - throw new Error('SDK should not return a package if remote does not contain a package'); - } else { - this.setState({done: true}, RCTTestModule.markTestCompleted); - } - }, - (err) => { - throw new Error(err.message); - }, - ); - }, - - render() { - return ( - - - {this.constructor.displayName + ': '} - {this.state.done ? 'Done' : 'Testing...'} - - - ); - } -}); - -NoRemotePackageTest.displayName = 'NoRemotePackageTest'; - -module.exports = NoRemotePackageTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/NoRemotePackageWithSameAppVersionTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/NoRemotePackageWithSameAppVersionTest.js deleted file mode 100644 index 0fa0c43..0000000 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/NoRemotePackageWithSameAppVersionTest.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict'; - -var React = require('react-native'); -var RCTTestModule = React.NativeModules.TestModule; -var CodePushSdk = require('react-native-code-push'); -var NativeCodePush = require("react-native").NativeModules.CodePush; -var RCTTestModule = require('NativeModules').TestModule || {}; - -var { - Text, - View, -} = React; - -var NoRemotePackageWithSameAppVersionTest = React.createClass({ - propTypes: { - shouldThrow: React.PropTypes.bool, - waitOneFrame: React.PropTypes.bool, - }, - - getInitialState() { - return { - done: false, - }; - }, - - componentDidMount() { - if (this.props.waitOneFrame) { - requestAnimationFrame(this.runTest); - } else { - this.setUp(this.runTest); - } - }, - - setUp(callWhenDone) { - var mockAcquisitionSdk = { - latestPackage: { - downloadUrl: "http://www.windowsazure.com/blobs/awperoiuqpweru", - description: "Angry flappy birds", - appVersion: "1.5.0", - label: "2.4.0", - isMandatory: false, - isAvailable: true, - updateAppVersion: false, - packageHash: "hash240", - packageSize: 1024 - }, - queryUpdateWithCurrentPackage: function(queryPackage, callback){ - if (!this.latestPackage || queryPackage.appVersion !== this.latestPackage.appVersion || - queryPackage.packageHash == this.latestPackage.packageHash) { - callback(/*err:*/ null, false); - } else { - callback(/*err:*/ null, latestPackage); - } - } - }; - - NativeCodePush.setUsingTestFolder(true); - var localPackage = { - downloadURL: "http://www.windowsazure.com/blobs/awperoiuqpweru", - description: "Angry flappy birds", - appVersion: "1.0.0", - label: "2.4.0", - isMandatory: false, - isAvailable: true, - updateAppVersion: false, - packageHash: "hash123", - packageSize: 1024 - }; - - var mockConfiguration = { appVersion : "1.0.0" }; - CodePushSdk.setUpTestDependencies(mockAcquisitionSdk, mockConfiguration, NativeCodePush); - - CodePushSdk.getCurrentPackage = function () { - return Promise.resolve(localPackage); - } - callWhenDone(); - }, - - runTest() { - CodePushSdk.checkForUpdate().then( - (update) => { - if (update) { - throw new Error('SDK should not return a package if remote package is of a different version'); - } else { - this.setState({done: true}, RCTTestModule.markTestCompleted); - } - }, - (err) => { - throw new Error(err.message); - }, - ); - }, - - render() { - return ( - - - {this.constructor.displayName + ': '} - {this.state.done ? 'Done' : 'Testing...'} - - - ); - } -}); - -NoRemotePackageWithSameAppVersionTest.displayName = 'NoRemotePackageWithSameAppVersionTest'; - -module.exports = NoRemotePackageWithSameAppVersionTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/SamePackageTest.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/SamePackageTest.js deleted file mode 100644 index 8808e77..0000000 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/QueryUpdateTests/SamePackageTest.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict'; - -var RCTTestModule = require('NativeModules').TestModule; -var React = require('react-native'); -var CodePushSdk = require('react-native-code-push'); -var NativeCodePush = require("react-native").NativeModules.CodePush; -var RCTTestModule = require('NativeModules').TestModule || {}; - -var { - Text, - View, -} = React; - -var SamePackageTest = React.createClass({ - propTypes: { - shouldThrow: React.PropTypes.bool, - waitOneFrame: React.PropTypes.bool, - }, - - getInitialState() { - return { - done: false, - }; - }, - - componentDidMount() { - if (this.props.waitOneFrame) { - requestAnimationFrame(this.runTest); - } else { - this.setUp(this.runTest); - } - }, - - setUp(callWhenDone) { - var mockAcquisitionSdk = { - latestPackage: { - downloadUrl: "http://www.windowsazure.com/blobs/awperoiuqpweru", - description: "Angry flappy birds", - appVersion: "1.5.0", - label: "2.4.0", - isMandatory: false, - isAvailable: true, - updateAppVersion: false, - packageHash: "hash240", - packageSize: 1024 - }, - queryUpdateWithCurrentPackage: function(queryPackage, callback){ - if (!this.latestPackage || queryPackage.appVersion !== this.latestPackage.appVersion || - queryPackage.packageHash == this.latestPackage.packageHash) { - callback(/*err:*/ null, false); - } else { - callback(/*err:*/ null, this.latestPackage); - } - } - }; - - var localPackage = { - downloadURL: "http://www.windowsazure.com/blobs/awperoiuqpweru", - description: "Angry flappy birds", - appVersion: "1.5.0", - label: "2.4.0", - isMandatory: false, - isAvailable: true, - updateAppVersion: false, - packageHash: "hash240", - packageSize: 1024 - }; - - var mockConfiguration = { appVersion : "1.5.0" }; - NativeCodePush.setUsingTestFolder(true); - CodePushSdk.setUpTestDependencies(mockAcquisitionSdk, mockConfiguration, NativeCodePush); - - CodePushSdk.getCurrentPackage = function () { - return Promise.resolve(localPackage); - } - callWhenDone(); - }, - - runTest() { - CodePushSdk.checkForUpdate().then( - (update) => { - if (update) { - throw new Error('SDK should not return a package when local package is identical'); - } else { - this.setState({done: true}, RCTTestModule.markTestCompleted); - } - }, - (err) => { - throw new Error(err.message); - } - ); - }, - - render() { - return ( - - - {this.constructor.displayName + ': '} - {this.state.done ? 'Done' : 'Testing...'} - - - ); - } -}); - -SamePackageTest.displayName = 'SamePackageTest'; - -module.exports = SamePackageTest; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/README.md b/Examples/CodePushDemoApp/CodePushDemoAppTests/README.md deleted file mode 100644 index 9ed5375..0000000 --- a/Examples/CodePushDemoApp/CodePushDemoAppTests/README.md +++ /dev/null @@ -1,15 +0,0 @@ - -Test Cases ---- -* QueryUpdateTests - Tests the functionality of querying for new app updates via the SDK - * testNoRemotePackage - Checks that when the remote server has no update packages available, CodePushSdk.queryUpdate does not return a new package nor throw an error. - * testNoRemotePackageWithSameAppVersion - Checks that when the remote server has an update with a different appVersion, the CodePushSdk.queryUpdate does not return a new package nor throw an error. - * testFirstUpdate - Checks that when there is no current package (for example, the current build is a fresh install from the app store) and the remote server has a new package, CodePushSdk.queryUpdate returns that new package without throwing an error. - * testNewUpdate - Checks that when the remote server has a new package with a different package hash and same version as the current package, CodePushSdk.queryUpdate returns that new package without throwing an error. - * testSamePackage - Checks that when the remote server has a package that is identical to the current package, CodePushSdk.queryUpdate does not return a new package nor throw an error. - -* InstallUpdateTests - Tests the functionality of installing new app updates downloaded from the server via the SDK - * testDownloadAndInstallUpdate - Queries for a new update, downloads it and then verifies that from the UI that the new update has been installed. - -* DownloadProgressTests - Tests the functionality of downloading app updates and tracking the download progress via the SDK - * testDownloadProgress - Downloads three files of different sizes and verifies that the reported number of bytes tally. \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/createTestCaseComponent.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/createTestCaseComponent.js new file mode 100644 index 0000000..22d300c --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/createTestCaseComponent.js @@ -0,0 +1,51 @@ +"use strict"; + +import React from "react-native"; +import { DeviceEventEmitter, Text, View } from "react-native"; +let NativeCodePush = React.NativeModules.CodePush; + +// RCTTestModule is not implemented yet for RN Android. +let RCTTestModule = React.NativeModules.TestModule || {}; + +function createTestCaseComponent(displayName, description, setUp, runTest, passAfterRun = true) { + let TestCaseComponent = React.createClass({ + propTypes: { + shouldThrow: React.PropTypes.bool, + waitOneFrame: React.PropTypes.bool, + }, + getInitialState() { + return { + done: false, + }; + }, + componentDidMount() { + setUp() + .then(runTest) + .then(() => { + if (passAfterRun) { + this.setState({done: true}, RCTTestModule.markTestCompleted); + } + }) + .catch((err) => { + console.error(err); + throw err; + }); + }, + render() { + return ( + + + {this.state.done ? "Test Passed!" : "Testing..."} + + + ); + } + }); + + TestCaseComponent.displayName = displayName; + TestCaseComponent.description = description; + + return TestCaseComponent; +} + +export default createTestCaseComponent; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js b/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js new file mode 100644 index 0000000..253d497 --- /dev/null +++ b/Examples/CodePushDemoApp/CodePushDemoAppTests/utils/mockAcquisitionSdk.js @@ -0,0 +1,19 @@ +import assert from "assert"; + +function createMockAcquisitionSdk(serverPackage, localPackage, expectedDeploymentKey) { + let AcquisitionManager = (httpRequester, configuration) => { + expectedDeploymentKey && assert.equal(expectedDeploymentKey, configuration.deploymentKey, "checkForUpdate did not initialize Acquisition SDK with the expected deployment key"); + }; + + AcquisitionManager.prototype.queryUpdateWithCurrentPackage = (queryPackage, callback) => { + if (localPackage) { + localPackage.appVersion = queryPackage.appVersion; + assert.deepEqual(queryPackage, localPackage, "checkForUpdate did not attach current package info to the acquisition request"); + } + callback(/*err:*/ null, serverPackage); + }; + + return AcquisitionManager; +} + +export default createMockAcquisitionSdk; \ No newline at end of file diff --git a/Examples/CodePushDemoApp/android/app/build.gradle b/Examples/CodePushDemoApp/android/app/build.gradle index bb7e1b2..630bf38 100644 --- a/Examples/CodePushDemoApp/android/app/build.gradle +++ b/Examples/CodePushDemoApp/android/app/build.gradle @@ -17,9 +17,6 @@ android { } } buildTypes { - debug { - buildConfigField "String", "RUN_TEST", "\"\"" - } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' diff --git a/Examples/CodePushDemoApp/android/app/src/main/java/com/microsoft/codepushdemoapp/MainActivity.java b/Examples/CodePushDemoApp/android/app/src/main/java/com/microsoft/codepushdemoapp/MainActivity.java index a62e296..ff818b4 100644 --- a/Examples/CodePushDemoApp/android/app/src/main/java/com/microsoft/codepushdemoapp/MainActivity.java +++ b/Examples/CodePushDemoApp/android/app/src/main/java/com/microsoft/codepushdemoapp/MainActivity.java @@ -29,29 +29,10 @@ public class MainActivity extends FragmentActivity implements DefaultHardwareBac ReactInstanceManager.Builder builder = ReactInstanceManager.builder() .setApplication(getApplication()) - .setJSBundleFile(codePush.getBundleUrl("index.android.bundle")); + .setJSBundleFile(codePush.getBundleUrl("index.android.bundle")) + .setJSMainModuleName("index.android"); - String mainComponentName = null; - - switch (BuildConfig.RUN_TEST) { - case "DOWNLOAD_PROGRESS": - builder = builder.setJSMainModuleName(TEST_FOLDER_PREFIX + "DownloadProgressTests/DownloadProgressTestApp"); - mainComponentName = "DownloadProgressTestApp"; - break; - case "INSTALL_UPDATE": - builder = builder.setJSMainModuleName(TEST_FOLDER_PREFIX + "InstallUpdateTests/InstallUpdateTestApp"); - mainComponentName = "InstallUpdateTestApp"; - break; - case "QUERY_UPDATE": - builder = builder.setJSMainModuleName(TEST_FOLDER_PREFIX + "QueryUpdateTests/QueryUpdateTestApp"); - mainComponentName = "QueryUpdateTestApp"; - break; - default: - // Run the standard demo app. - builder = builder.setJSMainModuleName("index.android"); - mainComponentName = "CodePushDemoApp"; - break; - } + String mainComponentName = "CodePushDemoApp"; mReactInstanceManager = builder.addPackage(new MainReactPackage()) .addPackage(codePush.getReactPackage()) diff --git a/Examples/CodePushDemoApp/crossplatformdemo.js b/Examples/CodePushDemoApp/crossplatformdemo.js index 2bd9d24..b05a128 100644 --- a/Examples/CodePushDemoApp/crossplatformdemo.js +++ b/Examples/CodePushDemoApp/crossplatformdemo.js @@ -16,10 +16,7 @@ var Button = require("react-native-button"); var CodePush = require('react-native-code-push'); var CodePushDemoApp = React.createClass({ - - componentDidMount: function() { - }, - sync: function() { + sync() { var self = this; CodePush.sync( { @@ -83,10 +80,10 @@ var CodePushDemoApp = React.createClass({ CodePush.log(error); }); }, - getInitialState: function() { + getInitialState() { return { }; }, - render: function() { + render() { var syncView; var syncButton; var progressView; diff --git a/Examples/CodePushDemoApp/iOS/CodePushDemoApp.xcodeproj/project.pbxproj b/Examples/CodePushDemoApp/iOS/CodePushDemoApp.xcodeproj/project.pbxproj index b351bb3..3eac84e 100644 --- a/Examples/CodePushDemoApp/iOS/CodePushDemoApp.xcodeproj/project.pbxproj +++ b/Examples/CodePushDemoApp/iOS/CodePushDemoApp.xcodeproj/project.pbxproj @@ -21,8 +21,9 @@ 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 */; }; + 542D39D41C22CED700D4B648 /* libCodePush.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 81551E0F1B3B427200F5B9F1 /* libCodePush.a */; }; 544161591B8BCA81000D9E25 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; - 5451ACBA1B86A5B600E2A7DF /* QueryUpdateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5451ACB81B86A5B600E2A7DF /* QueryUpdateTests.m */; }; + 5451ACBA1B86A5B600E2A7DF /* CheckForUpdateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5451ACB81B86A5B600E2A7DF /* CheckForUpdateTests.m */; }; 5451ACEC1B86E40A00E2A7DF /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5451ACEB1B86E34300E2A7DF /* libRCTTest.a */; }; 54D774BA1B87DAF800F2ABF8 /* InstallUpdateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D774B91B87DAF800F2ABF8 /* InstallUpdateTests.m */; }; 54F5F2B41BF6B45D007C3CEA /* DownloadProgressTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */; }; @@ -149,7 +150,7 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = CodePushDemoApp/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = CodePushDemoApp/main.m; sourceTree = ""; }; 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; - 5451ACB81B86A5B600E2A7DF /* QueryUpdateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QueryUpdateTests.m; sourceTree = ""; }; + 5451ACB81B86A5B600E2A7DF /* CheckForUpdateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CheckForUpdateTests.m; sourceTree = ""; }; 5451ACE61B86E34300E2A7DF /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = "../node_modules/react-native/Libraries/RCTTest/RCTTest.xcodeproj"; sourceTree = ""; }; 54D774B91B87DAF800F2ABF8 /* InstallUpdateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstallUpdateTests.m; sourceTree = ""; }; 54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DownloadProgressTests.m; sourceTree = ""; }; @@ -163,6 +164,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 542D39D41C22CED700D4B648 /* libCodePush.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -241,7 +243,7 @@ isa = PBXGroup; children = ( 54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */, - 5451ACB81B86A5B600E2A7DF /* QueryUpdateTests.m */, + 5451ACB81B86A5B600E2A7DF /* CheckForUpdateTests.m */, 54D774B91B87DAF800F2ABF8 /* InstallUpdateTests.m */, 00E356F01AD99517003FC87E /* Supporting Files */, ); @@ -628,7 +630,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5451ACBA1B86A5B600E2A7DF /* QueryUpdateTests.m in Sources */, + 5451ACBA1B86A5B600E2A7DF /* CheckForUpdateTests.m in Sources */, 54F5F2B41BF6B45D007C3CEA /* DownloadProgressTests.m in Sources */, 54D774BA1B87DAF800F2ABF8 /* InstallUpdateTests.m in Sources */, ); @@ -678,6 +680,13 @@ "DEBUG=1", "$(inherited)", ); + 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/Libraries/Text/", + ); INFOPLIST_FILE = ../CodePushDemoAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -695,6 +704,13 @@ "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", ); + 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/Libraries/Text/", + ); INFOPLIST_FILE = ../CodePushDemoAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; diff --git a/Examples/CodePushDemoApp/package.json b/Examples/CodePushDemoApp/package.json index b0b2e4e..0ea0780 100644 --- a/Examples/CodePushDemoApp/package.json +++ b/Examples/CodePushDemoApp/package.json @@ -9,5 +9,8 @@ "react-native": "0.15.0", "react-native-button": "^1.2.0", "react-native-code-push": "file:../../" + }, + "devDependencies": { + "assert": "^1.3.0" } } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index a0ac474..4ccb030 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -23,7 +23,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.AsyncTask; -import android.util.Log; import org.json.JSONException; import org.json.JSONObject; @@ -38,8 +37,9 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class CodePush { + + private static boolean testConfigurationFlag = false; private boolean didUpdate = false; - private boolean usingTestFolder = false; private String assetsBundleFileName; @@ -290,6 +290,22 @@ public class CodePush { } } + /* The below 3 methods are used for running tests.*/ + public static boolean isUsingTestConfiguration() { + return testConfigurationFlag; + } + + public static void setUsingTestConfiguration(boolean shouldUseTestConfiguration) { + testConfigurationFlag = shouldUseTestConfiguration; + } + + public void clearTestUpdates() { + if (isUsingTestConfiguration()) { + codePushPackage.clearTestUpdates(); + removePendingUpdate(); + } + } + private class CodePushNativeModule extends ReactContextBaseJavaModule { private LifecycleEventListener lifecycleEventListener = null; @@ -385,27 +401,24 @@ public class CodePush { savePendingUpdate(pendingHash, /* isLoading */false); } - if (installMode == CodePushInstallMode.IMMEDIATE.getValue()) { - loadBundle(); - } else if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue()) { + if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue() && + lifecycleEventListener == null) { // Ensure we do not add the listener twice. - if (lifecycleEventListener == null) { - lifecycleEventListener = new LifecycleEventListener() { - @Override - public void onHostResume() { - loadBundle(); - } + lifecycleEventListener = new LifecycleEventListener() { + @Override + public void onHostResume() { + loadBundle(); + } - @Override - public void onHostPause() { - } + @Override + public void onHostPause() { + } - @Override - public void onHostDestroy() { - } - }; - getReactApplicationContext().addLifecycleEventListener(lifecycleEventListener); - } + @Override + public void onHostDestroy() { + } + }; + getReactApplicationContext().addLifecycleEventListener(lifecycleEventListener); } promise.resolve(""); @@ -452,8 +465,16 @@ public class CodePush { } @ReactMethod - public void setUsingTestFolder(boolean shouldUseTestFolder) { - usingTestFolder = shouldUseTestFolder; + // Replaces the current bundle with the one downloaded from removeBundleUrl. + // It is only to be used during tests. No-ops if the test configuration flag is not set. + public void downloadAndReplaceCurrentBundle(String remoteBundleUrl) { + if (isUsingTestConfiguration()) { + try { + codePushPackage.downloadAndReplaceCurrentBundle(remoteBundleUrl); + } catch (IOException e) { + throw new CodePushUnknownException("Unable to replace current bundle", e); + } + } } @Override diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java index 04c51e9..bb828b5 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java @@ -38,7 +38,12 @@ public class CodePushPackage { } public String getCodePushPath() { - return CodePushUtils.appendPathComponent(getDocumentsDirectory(), CODE_PUSH_FOLDER_PREFIX); + String codePushPath = CodePushUtils.appendPathComponent(getDocumentsDirectory(), CODE_PUSH_FOLDER_PREFIX); + if (CodePush.isUsingTestConfiguration()) { + codePushPath = CodePushUtils.appendPathComponent(codePushPath, "TestPackages"); + } + + return codePushPath; } public String getStatusFilePath() { @@ -186,4 +191,45 @@ public class CodePushPackage { info.putNull(PREVIOUS_PACKAGE_KEY); updateCurrentPackageInfo(info); } + + public void downloadAndReplaceCurrentBundle(String remoteBundleUrl) throws IOException { + URL downloadUrl; + HttpURLConnection connection = null; + BufferedInputStream bin = null; + FileOutputStream fos = null; + BufferedOutputStream bout = null; + try { + downloadUrl = new URL(remoteBundleUrl); + connection = (HttpURLConnection) (downloadUrl.openConnection()); + bin = new BufferedInputStream(connection.getInputStream()); + File downloadFile = new File(getCurrentPackageBundlePath()); + downloadFile.delete(); + fos = new FileOutputStream(downloadFile); + bout = new BufferedOutputStream(fos, DOWNLOAD_BUFFER_SIZE); + byte[] data = new byte[DOWNLOAD_BUFFER_SIZE]; + int numBytesRead = 0; + while ((numBytesRead = bin.read(data, 0, DOWNLOAD_BUFFER_SIZE)) >= 0) { + bout.write(data, 0, numBytesRead); + } + } catch (MalformedURLException e) { + throw new CodePushMalformedDataException(remoteBundleUrl, e); + } finally { + try { + if (bout != null) bout.close(); + if (fos != null) fos.close(); + if (bin != null) bin.close(); + if (connection != null) connection.disconnect(); + } catch (IOException e) { + throw new CodePushUnknownException("Error closing IO resources.", e); + } + } + } + + public void clearTestUpdates() { + if (CodePush.isUsingTestConfiguration()) { + File statusFile = new File(getStatusFilePath()); + statusFile.delete(); + CodePushUtils.deleteDirectoryAtPath(getCodePushPath()); + } + } }