Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit c835a76

Browse files
committed
Merge pull request #116 from Microsoft/tests
Tests + Bugfix
2 parents d5a7971 + 32dea6b commit c835a76

File tree

62 files changed

+1575
-1135
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1575
-1135
lines changed

CodePush.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222

2323
+ (NSString *)getApplicationSupportDirectory;
2424

25+
// The below methods are only used during tests.
26+
+ (BOOL)isUsingTestConfiguration;
27+
+ (void)setUsingTestConfiguration:(BOOL)shouldUseTestConfiguration;
28+
+ (void)clearTestUpdates;
29+
2530
@end
2631

2732
@interface CodePushConfig : NSObject
@@ -77,6 +82,10 @@ failCallback:(void (^)(NSError *err))failCallback;
7782

7883
+ (void)rollbackPackage;
7984

85+
// The below methods are only used during tests.
86+
+ (void)downloadAndReplaceCurrentBundle:(NSString *)remoteBundleUrl;
87+
+ (void)clearTestUpdates;
88+
8089
@end
8190

8291
typedef NS_ENUM(NSInteger, CodePushInstallMode) {

CodePush.js

Lines changed: 72 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -20,75 +20,75 @@ function checkForUpdate(deploymentKey = null) {
2020
* different from the CodePush update they have already installed.
2121
*/
2222
return getConfiguration()
23-
.then((configResult) => {
24-
/*
25-
* If a deployment key was explicitly provided,
26-
* then let's override the one we retrieved
27-
* from the native-side of the app. This allows
28-
* dynamically "redirecting" end-users at different
29-
* deployments (e.g. an early access deployment for insiders).
30-
*/
31-
if (deploymentKey) {
32-
config = Object.assign({}, configResult, { deploymentKey });
33-
} else {
34-
config = configResult;
35-
}
36-
37-
sdk = getSDK(config);
38-
39-
// Allow dynamic overwrite of function. This is only to be used for tests.
40-
return module.exports.getCurrentPackage();
41-
})
42-
.then((localPackage) => {
43-
var queryPackage = { appVersion: config.appVersion };
44-
45-
/*
46-
* If the app has a previously installed update, and that update
47-
* was targetted at the same app version that is currently running,
48-
* then we want to use its package hash to determine whether a new
49-
* release has been made on the server. Otherwise, we only need
50-
* to send the app version to the server, since we are interested
51-
* in any updates for current app store version, regardless of hash.
52-
*/
53-
if (localPackage && localPackage.appVersion && semver.compare(localPackage.appVersion, config.appVersion) === 0) {
54-
queryPackage = localPackage;
55-
}
56-
57-
return new Promise((resolve, reject) => {
58-
sdk.queryUpdateWithCurrentPackage(queryPackage, (err, update) => {
59-
if (err) {
60-
return reject(err);
61-
}
62-
63-
/*
64-
* There are three cases where checkForUpdate will resolve to null:
65-
* ----------------------------------------------------------------
66-
* 1) The server said there isn't an update. This is the most common case.
67-
* 2) The server said there is an update but it requires a newer binary version.
68-
* This would occur when end-users are running an older app store version than
69-
* is available, and CodePush is making sure they don't get an update that
70-
* potentially wouldn't be compatible with what they are running.
71-
* 3) The server said there is an update, but the update's hash is the same as
72-
* the currently running update. This should _never_ happen, unless there is a
73-
* bug in the server, but we're adding this check just to double-check that the
74-
* client app is resilient to a potential issue with the update check.
75-
*/
76-
if (!update || update.updateAppVersion || (update.packageHash === localPackage.packageHash)) {
77-
return resolve(null);
78-
}
23+
.then((configResult) => {
24+
/*
25+
* If a deployment key was explicitly provided,
26+
* then let's override the one we retrieved
27+
* from the native-side of the app. This allows
28+
* dynamically "redirecting" end-users at different
29+
* deployments (e.g. an early access deployment for insiders).
30+
*/
31+
if (deploymentKey) {
32+
config = Object.assign({}, configResult, { deploymentKey });
33+
} else {
34+
config = configResult;
35+
}
36+
37+
sdk = new module.exports.AcquisitionSdk(requestFetchAdapter, config);
38+
39+
// Allow dynamic overwrite of function. This is only to be used for tests.
40+
return module.exports.getCurrentPackage();
41+
})
42+
.then((localPackage) => {
43+
var queryPackage = { appVersion: config.appVersion };
44+
45+
/*
46+
* If the app has a previously installed update, and that update
47+
* was targetted at the same app version that is currently running,
48+
* then we want to use its package hash to determine whether a new
49+
* release has been made on the server. Otherwise, we only need
50+
* to send the app version to the server, since we are interested
51+
* in any updates for current app store version, regardless of hash.
52+
*/
53+
if (localPackage && localPackage.appVersion && semver.compare(localPackage.appVersion, config.appVersion) === 0) {
54+
queryPackage = localPackage;
55+
}
56+
57+
return new Promise((resolve, reject) => {
58+
sdk.queryUpdateWithCurrentPackage(queryPackage, (err, update) => {
59+
if (err) {
60+
return reject(err);
61+
}
62+
63+
/*
64+
* There are three cases where checkForUpdate will resolve to null:
65+
* ----------------------------------------------------------------
66+
* 1) The server said there isn't an update. This is the most common case.
67+
* 2) The server said there is an update but it requires a newer binary version.
68+
* This would occur when end-users are running an older app store version than
69+
* is available, and CodePush is making sure they don't get an update that
70+
* potentially wouldn't be compatible with what they are running.
71+
* 3) The server said there is an update, but the update's hash is the same as
72+
* the currently running update. This should _never_ happen, unless there is a
73+
* bug in the server, but we're adding this check just to double-check that the
74+
* client app is resilient to a potential issue with the update check.
75+
*/
76+
if (!update || update.updateAppVersion || (update.packageHash === localPackage.packageHash)) {
77+
return resolve(null);
78+
}
7979

80-
update = Object.assign(update, PackageMixins.remote);
81-
82-
NativeCodePush.isFailedUpdate(update.packageHash)
83-
.then((isFailedHash) => {
84-
update.failedInstall = isFailedHash;
85-
resolve(update);
86-
})
87-
.catch(reject)
88-
.done();
89-
})
90-
});
91-
});
80+
update = Object.assign(update, PackageMixins.remote);
81+
82+
NativeCodePush.isFailedUpdate(update.packageHash)
83+
.then((isFailedHash) => {
84+
update.failedInstall = isFailedHash;
85+
resolve(update);
86+
})
87+
.catch(reject)
88+
.done();
89+
})
90+
});
91+
});
9292
}
9393

9494
var getConfiguration = (() => {
@@ -129,30 +129,21 @@ function getCurrentPackage() {
129129
});
130130
}
131131

132-
function getSDK(config) {
133-
if (testSdk) {
134-
return testSdk;
135-
} else {
136-
return new Sdk(requestFetchAdapter, config);
137-
}
138-
}
139-
140132
/* Logs messages to console with the [CodePush] prefix */
141133
function log(message) {
142134
console.log(`[CodePush] ${message}`)
143135
}
144136

145137
var testConfig;
146-
var testSdk;
147138

148139
// This function is only used for tests. Replaces the default SDK, configuration and native bridge
149-
function setUpTestDependencies(providedTestSdk, providedTestConfig, testNativeBridge) {
150-
if (providedTestSdk) testSdk = providedTestSdk;
140+
function setUpTestDependencies(testSdk, providedTestConfig, testNativeBridge) {
141+
if (testSdk) module.exports.AcquisitionSdk = testSdk;
151142
if (providedTestConfig) testConfig = providedTestConfig;
152143
if (testNativeBridge) NativeCodePush = testNativeBridge;
153144
}
154145

155-
/**
146+
/*
156147
* The sync method provides a simple, one-line experience for
157148
* incorporating the check, download and application of an update.
158149
*
@@ -303,6 +294,7 @@ function sync(options = {}, syncStatusChangeCallback, downloadProgressCallback)
303294
};
304295

305296
var CodePush = {
297+
AcquisitionSdk: Sdk,
306298
checkForUpdate: checkForUpdate,
307299
getConfiguration: getConfiguration,
308300
getCurrentPackage: getCurrentPackage,

CodePush.m

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ @implementation CodePush {
1313

1414
RCT_EXPORT_MODULE()
1515

16-
static BOOL usingTestFolder = NO;
16+
static BOOL testConfigurationFlag = NO;
1717

1818
// These keys represent the names we use to store data in NSUserDefaults
1919
static NSString *const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES";
@@ -50,7 +50,10 @@ + (NSURL *)bundleURLForResource:(NSString *)resourceName
5050
NSString *packageFile = [CodePushPackage getCurrentPackageBundlePath:&error];
5151
NSURL *binaryJsBundleUrl = [[NSBundle mainBundle] URLForResource:resourceName withExtension:resourceExtension];
5252

53+
NSString *logMessageFormat = @"Loading JS bundle from %@";
54+
5355
if (error || !packageFile) {
56+
NSLog(logMessageFormat, binaryJsBundleUrl);
5457
return binaryJsBundleUrl;
5558
}
5659

@@ -61,8 +64,11 @@ + (NSURL *)bundleURLForResource:(NSString *)resourceName
6164

6265
if ([binaryDate compare:packageDate] == NSOrderedAscending) {
6366
// Return package file because it is newer than the app store binary's JS bundle
64-
return [[NSURL alloc] initFileURLWithPath:packageFile];
67+
NSURL *packageUrl = [[NSURL alloc] initFileURLWithPath:packageFile];
68+
NSLog(logMessageFormat, packageUrl);
69+
return packageUrl;
6570
} else {
71+
NSLog(logMessageFormat, binaryJsBundleUrl);
6672
return binaryJsBundleUrl;
6773
}
6874
}
@@ -73,6 +79,40 @@ + (NSString *)getApplicationSupportDirectory
7379
return applicationSupportDirectory;
7480
}
7581

82+
/*
83+
* This returns a boolean value indicating whether CodePush has
84+
* been set to run under a test configuration.
85+
*/
86+
+ (BOOL)isUsingTestConfiguration
87+
{
88+
return testConfigurationFlag;
89+
}
90+
91+
/*
92+
* This is used to enable an environment in which tests can be run.
93+
* Specifically, it flips a boolean flag that causes bundles to be
94+
* saved to a test folder and enables the ability to modify
95+
* installed bundles on the fly from JavaScript.
96+
*/
97+
+ (void)setUsingTestConfiguration:(BOOL)shouldUseTestConfiguration
98+
{
99+
testConfigurationFlag = shouldUseTestConfiguration;
100+
}
101+
102+
/*
103+
* This is used to clean up all test updates. It can only be used
104+
* when the testConfigurationFlag is set to YES, otherwise it will
105+
* simply no-op.
106+
*/
107+
+ (void)clearTestUpdates
108+
{
109+
if ([CodePush isUsingTestConfiguration]) {
110+
[CodePushPackage clearTestUpdates];
111+
[self removePendingUpdate];
112+
}
113+
}
114+
115+
76116
// Private API methods
77117

78118
/*
@@ -125,6 +165,7 @@ - (void)initializeUpdateAfterRestart
125165
if (updateIsLoading) {
126166
// Pending update was initialized, but notifyApplicationReady was not called.
127167
// Therefore, deduce that it is a broken update and rollback.
168+
NSLog(@"Update did not finish loading the last time, rolling back to a previous version.");
128169
[self rollbackPackage];
129170
} else {
130171
// Mark that we tried to initialize the new update, so that if it crashes,
@@ -179,7 +220,7 @@ - (void)loadBundle
179220
// is debugging and therefore, shouldn't be redirected to a local
180221
// file (since Chrome wouldn't support it). Otherwise, update
181222
// the current bundle URL to point at the latest update
182-
if (![_bridge.bundleURL.scheme hasPrefix:@"http"]) {
223+
if ([CodePush isUsingTestConfiguration] || ![_bridge.bundleURL.scheme hasPrefix:@"http"]) {
183224
_bridge.bundleURL = [CodePush bundleURL];
184225
}
185226

@@ -204,7 +245,7 @@ - (void)rollbackPackage
204245

205246
// Rollback to the previous version and de-register the new update
206247
[CodePushPackage rollbackPackage];
207-
[self removePendingUpdate];
248+
[CodePush removePendingUpdate];
208249
[self loadBundle];
209250
}
210251

@@ -231,10 +272,10 @@ - (void)saveFailedUpdate:(NSString *)packageHash
231272
}
232273

233274
/*
234-
* This method is used to register the fact that a pending
275+
* This method is used to register the fact that a pending
235276
* update succeeded and therefore can be removed.
236277
*/
237-
- (void)removePendingUpdate
278+
+ (void)removePendingUpdate
238279
{
239280
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
240281
[preferences removeObjectForKey:PendingUpdateKey];
@@ -351,19 +392,15 @@ - (void)savePendingUpdate:(NSString *)packageHash
351392
[self savePendingUpdate:updatePackage[PackageHashKey]
352393
isLoading:NO];
353394

354-
if (installMode == CodePushInstallModeImmediate) {
355-
[self loadBundle];
356-
} else if (installMode == CodePushInstallModeOnNextResume) {
395+
if (installMode == CodePushInstallModeOnNextResume && !_hasResumeListener) {
357396
// Ensure we do not add the listener twice.
358-
if (!_hasResumeListener) {
359-
// Register for app resume notifications so that we
360-
// can check for pending updates which support "restart on resume"
361-
[[NSNotificationCenter defaultCenter] addObserver:self
362-
selector:@selector(loadBundle)
363-
name:UIApplicationWillEnterForegroundNotification
364-
object:[UIApplication sharedApplication]];
365-
_hasResumeListener = YES;
366-
}
397+
// Register for app resume notifications so that we
398+
// can check for pending updates which support "restart on resume"
399+
[[NSNotificationCenter defaultCenter] addObserver:self
400+
selector:@selector(loadBundle)
401+
name:UIApplicationWillEnterForegroundNotification
402+
object:[UIApplication sharedApplication]];
403+
_hasResumeListener = YES;
367404
}
368405
// Signal to JS that the update has been applied.
369406
resolve(nil);
@@ -406,7 +443,7 @@ - (void)savePendingUpdate:(NSString *)packageHash
406443
RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve
407444
rejecter:(RCTPromiseRejectBlock)reject)
408445
{
409-
[self removePendingUpdate];
446+
[CodePush removePendingUpdate];
410447
resolve([NSNull null]);
411448
}
412449

@@ -418,9 +455,17 @@ - (void)savePendingUpdate:(NSString *)packageHash
418455
[self loadBundle];
419456
}
420457

421-
RCT_EXPORT_METHOD(setUsingTestFolder:(BOOL)shouldUseTestFolder)
458+
/*
459+
* This method is the native side of the CodePush.downloadAndReplaceCurrentBundle()
460+
* method, which replaces the current bundle with the one downloaded from
461+
* removeBundleUrl. It is only to be used during tests and no-ops if the test
462+
* configuration flag is not set.
463+
*/
464+
RCT_EXPORT_METHOD(downloadAndReplaceCurrentBundle:(NSString *)remoteBundleUrl)
422465
{
423-
usingTestFolder = shouldUseTestFolder;
466+
if ([CodePush isUsingTestConfiguration]) {
467+
[CodePushPackage downloadAndReplaceCurrentBundle:remoteBundleUrl];
468+
}
424469
}
425470

426471
@end

0 commit comments

Comments
 (0)