Compare commits

..

4 Commits

Author SHA1 Message Date
Dean Whillier
aa5ecaf417 Bump app build number to 234 (#3327)
# Conflicts:
#	android/app/build.gradle
#	ios/Mattermost.xcodeproj/project.pbxproj
#	ios/Mattermost/Info.plist
#	ios/MattermostShare/Info.plist
#	ios/MattermostTests/Info.plist
#	ios/NotificationService/Info.plist
2019-09-25 16:15:06 -04:00
Dean Whillier
65cad4c562 Bump app version number to 1.23.1 (#3326) 2019-09-25 16:05:58 -04:00
Miguel Alatzar
290e65ac57 Various fixes (#3287)
* Ensure onAppStateChange runs only after GlobalEventHandler is configured (#3268)

* Updated Info.plist with new bluetooth usage description key (#3275)

* Call scrollToIndex only when ref is set and index is in range (#3285)

* Update ios/Mattermost/Info.plist

Co-Authored-By: Elias Nahum <nahumhbl@gmail.com>

* Null check on current (#3309)

* Null check on current

* Null check on current

* [MM-18779] Reset moment local on logout (#3310)

* Reset moment local on logout

* Update app/selectors/i18n.js

Co-Authored-By: Elias Nahum <nahumhbl@gmail.com>
2019-09-25 11:52:07 -07:00
Mattermost Build
20a2078377 add circleci (#3315) 2019-09-24 17:28:38 -07:00
14 changed files with 152 additions and 44 deletions

23
.circleci/config.yml Normal file
View File

@@ -0,0 +1,23 @@
version: 2.1
jobs:
test:
working_directory: ~/mattermost-mobile
docker:
- image: circleci/node:10
steps:
- checkout
- run: |
echo assets/base/config.json
cat assets/base/config.json
# Avoid installing pods
touch .podinstall
# Run tests
make test || exit 1
workflows:
version: 2
pr-test:
jobs:
- test

View File

@@ -123,8 +123,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5"
versionCode 230
versionName "1.23.0"
versionName "1.23.1"
versionCode 234
multiDexEnabled = true
ndk {
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'

View File

@@ -299,30 +299,33 @@ export default class PostList extends PureComponent {
scrollToBottom = () => {
setTimeout(() => {
if (this.flatListRef && this.flatListRef.current) {
if (this.flatListRef.current) {
this.flatListRef.current.scrollToOffset({offset: 0, animated: true});
}
}, 250);
};
flatListScrollToIndex = (index) => {
this.flatListRef.current.scrollToIndex({
animated: false,
index,
viewOffset: 0,
viewPosition: 1, // 0 is at bottom
});
}
scrollToIndex = (index) => {
if (this.flatListRef?.current) {
this.animationFrameInitialIndex = requestAnimationFrame(() => {
this.flatListRef.current.scrollToIndex({
animated: false,
index,
viewOffset: 0,
viewPosition: 1, // 0 is at bottom
});
});
}
this.animationFrameInitialIndex = requestAnimationFrame(() => {
if (this.flatListRef.current && index > 0 && index <= this.getItemCount()) {
this.flatListScrollToIndex(index);
}
});
};
scrollToInitialIndexIfNeeded = (index, count = 0) => {
if (!this.hasDoneInitialScroll && this.flatListRef?.current) {
this.hasDoneInitialScroll = true;
if (!this.hasDoneInitialScroll) {
if (index > 0 && index <= this.getItemCount()) {
this.hasDoneInitialScroll = true;
this.scrollToIndex(index);
} else if (count < 3) {
setTimeout(() => {

View File

@@ -55,4 +55,32 @@ describe('PostList', () => {
expect(baseProps.actions.handleSelectChannelByName).toHaveBeenCalled();
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should call flatListScrollToIndex only when ref is set and index is in range', () => {
jest.spyOn(global, 'requestAnimationFrame').mockImplementation((cb) => cb());
const instance = wrapper.instance();
const flatListScrollToIndex = jest.spyOn(instance, 'flatListScrollToIndex');
const indexInRange = baseProps.postIds.length;
const indexOutOfRange = [-1, indexInRange + 1];
instance.flatListRef = {
current: null,
};
instance.scrollToIndex(indexInRange);
expect(flatListScrollToIndex).not.toHaveBeenCalled();
instance.flatListRef = {
current: {
scrollToIndex: jest.fn(),
},
};
for (const index of indexOutOfRange) {
instance.scrollToIndex(index);
expect(flatListScrollToIndex).not.toHaveBeenCalled();
}
instance.scrollToIndex(indexInRange);
expect(flatListScrollToIndex).toHaveBeenCalled();
});
});

View File

@@ -108,12 +108,8 @@ function loadTranslation(locale) {
}
}
let momentLocale = DEFAULT_LOCALE;
function setMomentLocale(locale) {
if (momentLocale !== locale) {
momentLocale = moment.locale(locale);
}
export function resetMomentLocale() {
moment.locale(DEFAULT_LOCALE);
}
export function getTranslations(locale) {
@@ -121,8 +117,6 @@ export function getTranslations(locale) {
loadTranslation(locale);
}
setMomentLocale(locale.toLowerCase());
return TRANSLATIONS[locale] || TRANSLATIONS[DEFAULT_LOCALE];
}

View File

@@ -18,7 +18,7 @@ import {selectDefaultChannel} from 'app/actions/views/channel';
import {showOverlay} from 'app/actions/navigation';
import {loadConfigAndLicense, setDeepLinkURL, startDataCleanup} from 'app/actions/views/root';
import {NavigationTypes, ViewTypes} from 'app/constants';
import {getTranslations} from 'app/i18n';
import {getTranslations, resetMomentLocale} from 'app/i18n';
import mattermostManaged from 'app/mattermost_managed';
import PushNotifications from 'app/push_notifications';
import {getCurrentLocale} from 'app/selectors/i18n';
@@ -40,7 +40,6 @@ class GlobalEventHandler {
EventEmitter.on(General.SERVER_VERSION_CHANGED, this.onServerVersionChanged);
EventEmitter.on(General.CONFIG_CHANGED, this.onServerConfigChanged);
EventEmitter.on(General.SWITCH_TO_DEFAULT_CHANNEL, this.onSwitchToDefaultChannel);
this.turnOnInAppNotificationHandling();
Dimensions.addEventListener('change', this.onOrientationChange);
AppState.addEventListener('change', this.onAppStateChange);
Linking.addEventListener('url', this.onDeepLink);
@@ -77,6 +76,10 @@ class GlobalEventHandler {
this.store = opts.store;
this.launchApp = opts.launchApp;
// onAppStateChange may be called by the AppState listener before we
// configure the global event handler so we manually call it here
this.onAppStateChange('active');
const window = Dimensions.get('window');
this.onOrientationChange({window});
@@ -113,12 +116,12 @@ class GlobalEventHandler {
if (this.store) {
this.store.dispatch(setAppState(isActive));
}
if (isActive && emmProvider.previousAppState === 'background') {
this.appActive();
} else if (isBackground) {
this.appInactive();
if (isActive && (!emmProvider.enabled || emmProvider.previousAppState === 'background')) {
this.appActive();
} else if (isBackground) {
this.appInactive();
}
}
emmProvider.previousAppState = appState;
@@ -142,6 +145,7 @@ class GlobalEventHandler {
this.store.dispatch(setServerVersion(''));
deleteFileCache();
removeAppCredentials();
resetMomentLocale();
PushNotifications.clearNotifications();

View File

@@ -6,6 +6,7 @@ import thunk from 'redux-thunk';
import intitialState from 'app/initial_state';
import PushNotification from 'app/push_notifications';
import * as I18n from 'app/i18n';
import GlobalEventHandler from './global_event_handler';
@@ -15,6 +16,12 @@ jest.mock('app/init/credentials', () => ({
removeAppCredentials: jest.fn(),
}));
jest.mock('app/utils/error_handling', () => ({
default: {
initializeErrorHandling: jest.fn(),
},
}));
jest.mock('react-native-notifications', () => ({
addEventListener: jest.fn(),
cancelAllLocalNotifications: jest.fn(),
@@ -29,10 +36,54 @@ GlobalEventHandler.store = store;
// TODO: Add Android test as part of https://mattermost.atlassian.net/browse/MM-17110
describe('GlobalEventHandler', () => {
it('should clear notifications on logout', async () => {
it('should clear notifications and reset moment locale on logout', async () => {
const clearNotifications = jest.spyOn(PushNotification, 'clearNotifications');
const resetMomentLocale = jest.spyOn(I18n, 'resetMomentLocale');
await GlobalEventHandler.onLogout();
expect(clearNotifications).toHaveBeenCalled();
expect(resetMomentLocale).toHaveBeenCalledWith();
});
it('should call onAppStateChange after configuration', () => {
const onAppStateChange = jest.spyOn(GlobalEventHandler, 'onAppStateChange');
GlobalEventHandler.configure({store});
expect(GlobalEventHandler.store).not.toBeNull();
expect(onAppStateChange).toHaveBeenCalledWith('active');
});
it('should handle onAppStateChange to active if the store set', () => {
const appActive = jest.spyOn(GlobalEventHandler, 'appActive');
const appInactive = jest.spyOn(GlobalEventHandler, 'appInactive');
expect(GlobalEventHandler.store).not.toBeNull();
GlobalEventHandler.onAppStateChange('active');
expect(appActive).toHaveBeenCalled();
expect(appInactive).not.toHaveBeenCalled();
});
it('should handle onAppStateChange to background if the store set', () => {
const appActive = jest.spyOn(GlobalEventHandler, 'appActive');
const appInactive = jest.spyOn(GlobalEventHandler, 'appInactive');
expect(GlobalEventHandler.store).not.toBeNull();
GlobalEventHandler.onAppStateChange('background');
expect(appActive).not.toHaveBeenCalled();
expect(appInactive).toHaveBeenCalled();
});
it('should not handle onAppStateChange if the store is not set', () => {
const appActive = jest.spyOn(GlobalEventHandler, 'appActive');
const appInactive = jest.spyOn(GlobalEventHandler, 'appInactive');
GlobalEventHandler.store = null;
GlobalEventHandler.onAppStateChange('active');
expect(appActive).not.toHaveBeenCalled();
expect(appInactive).not.toHaveBeenCalled();
GlobalEventHandler.onAppStateChange('background');
expect(appActive).not.toHaveBeenCalled();
expect(appInactive).not.toHaveBeenCalled();
});
});

View File

@@ -2785,7 +2785,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 230;
CURRENT_PROJECT_VERSION = 234;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@@ -2845,7 +2845,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 230;
CURRENT_PROJECT_VERSION = 234;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;

View File

@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.23.0</string>
<string>1.23.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -34,7 +34,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>230</string>
<string>234</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
@@ -56,6 +56,8 @@
</dict>
<key>NSAppleMusicUsageDescription</key>
<string>Let Mattermost access your Media files</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Share post data <span class="x x-first x-last">across</span> devices with Mattermost</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Share post data accross devices with Mattermost</string>
<key>NSCalendarsUsageDescription</key>

View File

@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.23.0</string>
<string>1.23.1</string>
<key>CFBundleVersion</key>
<string>230</string>
<string>234</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>

View File

@@ -15,10 +15,10 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.23.0</string>
<string>1.23.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>230</string>
<string>234</string>
</dict>
</plist>

View File

@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.23.0</string>
<string>1.23.1</string>
<key>CFBundleVersion</key>
<string>230</string>
<string>234</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>

View File

@@ -1,6 +1,6 @@
{
"name": "mattermost-mobile",
"version": "1.23.0",
"version": "1.23.1",
"description": "Mattermost Mobile with React Native",
"repository": "git@github.com:mattermost/mattermost-mobile.git",
"author": "Mattermost, Inc.",
@@ -147,4 +147,4 @@
"node_modules/(?!react-native|jail-monkey)"
]
}
}
}

View File

@@ -43,6 +43,9 @@ jest.mock('NativeModules', () => {
addEventListener: jest.fn(),
getCurrentState: jest.fn().mockResolvedValue({isConnected: true}),
},
StatusBarManager: {
getHeight: jest.fn(),
},
};
});
jest.mock('NativeEventEmitter');