diff --git a/app/actions/customEmojis.js b/app/actions/customEmojis.js deleted file mode 100644 index 740a936a..00000000 --- a/app/actions/customEmojis.js +++ /dev/null @@ -1,8 +0,0 @@ -import * as types from './actionsTypes'; - -export function setCustomEmojis(emojis) { - return { - type: types.SET_CUSTOM_EMOJIS, - emojis - }; -} diff --git a/app/actions/customEmojis.ts b/app/actions/customEmojis.ts new file mode 100644 index 00000000..261fbd24 --- /dev/null +++ b/app/actions/customEmojis.ts @@ -0,0 +1,17 @@ +import { Action } from 'redux'; + +import { ICustomEmojis } from '../reducers/customEmojis'; +import { SET_CUSTOM_EMOJIS } from './actionsTypes'; + +export interface ISetCustomEmojis extends Action { + emojis: ICustomEmojis; +} + +export type TActionCustomEmojis = ISetCustomEmojis; + +export function setCustomEmojis(emojis: ICustomEmojis): ISetCustomEmojis { + return { + type: SET_CUSTOM_EMOJIS, + emojis + }; +} diff --git a/app/actions/encryption.js b/app/actions/encryption.js deleted file mode 100644 index 390dfe90..00000000 --- a/app/actions/encryption.js +++ /dev/null @@ -1,35 +0,0 @@ -import * as types from './actionsTypes'; - -export function encryptionInit() { - return { - type: types.ENCRYPTION.INIT - }; -} - -export function encryptionStop() { - return { - type: types.ENCRYPTION.STOP - }; -} - -export function encryptionSet(enabled = false, banner = null) { - return { - type: types.ENCRYPTION.SET, - enabled, - banner - }; -} - -export function encryptionSetBanner(banner) { - return { - type: types.ENCRYPTION.SET_BANNER, - banner - }; -} - -export function encryptionDecodeKey(password) { - return { - type: types.ENCRYPTION.DECODE_KEY, - password - }; -} diff --git a/app/actions/encryption.ts b/app/actions/encryption.ts new file mode 100644 index 00000000..be3fb43f --- /dev/null +++ b/app/actions/encryption.ts @@ -0,0 +1,52 @@ +import { Action } from 'redux'; + +import { IBanner } from '../reducers/encryption'; +import { ENCRYPTION } from './actionsTypes'; + +export interface IEncryptionSet extends Action { + enabled: boolean; + banner: IBanner; +} + +export interface IEncryptionSetBanner extends Action { + banner: IBanner; +} +export interface IEncryptionDecodeKey extends Action { + password: string; +} + +export type TActionEncryption = IEncryptionSet & IEncryptionSetBanner & IEncryptionDecodeKey; + +export function encryptionInit(): Action { + return { + type: ENCRYPTION.INIT + }; +} + +export function encryptionStop(): Action { + return { + type: ENCRYPTION.STOP + }; +} + +export function encryptionSet(enabled = false, banner: IBanner = ''): IEncryptionSet { + return { + type: ENCRYPTION.SET, + enabled, + banner + }; +} + +export function encryptionSetBanner(banner: IBanner = ''): IEncryptionSetBanner { + return { + type: ENCRYPTION.SET_BANNER, + banner + }; +} + +export function encryptionDecodeKey(password: string): IEncryptionDecodeKey { + return { + type: ENCRYPTION.DECODE_KEY, + password + }; +} diff --git a/app/definitions/redux/index.ts b/app/definitions/redux/index.ts index 998e028b..87753bb1 100644 --- a/app/definitions/redux/index.ts +++ b/app/definitions/redux/index.ts @@ -1,12 +1,15 @@ // ACTIONS import { TActionServer } from '../../actions/server'; import { TActionActiveUsers } from '../../actions/activeUsers'; +import { TActionCustomEmojis } from '../../actions/customEmojis'; +import { TActionEncryption } from '../../actions/encryption'; import { TActionInviteLinks } from '../../actions/inviteLinks'; import { IActionRoles } from '../../actions/roles'; import { TActionSelectedUsers } from '../../actions/selectedUsers'; import { IActionSettings } from '../../actions/settings'; // REDUCERS import { IActiveUsers } from '../../reducers/activeUsers'; +import { IEncryption } from '../../reducers/encryption'; import { IInviteLinks } from '../../reducers/inviteLinks'; import { IRoles } from '../../reducers/roles'; import { ISelectedUsers } from '../../reducers/selectedUsers'; @@ -32,14 +35,16 @@ export interface IApplicationState { createDiscussion: any; inquiry: any; enterpriseModules: any; - encryption: any; + encryption: IEncryption; permissions: any; roles: IRoles; } export type TApplicationActions = TActionActiveUsers & TActionSelectedUsers & + TActionCustomEmojis & TActionInviteLinks & IActionRoles & IActionSettings & + TActionEncryption & TActionServer; diff --git a/app/reducers/customEmojis.js b/app/reducers/customEmojis.js deleted file mode 100644 index fbdaeab8..00000000 --- a/app/reducers/customEmojis.js +++ /dev/null @@ -1,14 +0,0 @@ -import { SET_CUSTOM_EMOJIS } from '../actions/actionsTypes'; - -const initialState = { - customEmojis: {} -}; - -export default function customEmojis(state = initialState, action) { - switch (action.type) { - case SET_CUSTOM_EMOJIS: - return action.emojis; - default: - return state; - } -} diff --git a/app/reducers/customEmojis.test.ts b/app/reducers/customEmojis.test.ts new file mode 100644 index 00000000..3f507f04 --- /dev/null +++ b/app/reducers/customEmojis.test.ts @@ -0,0 +1,16 @@ +import { setCustomEmojis } from '../actions/customEmojis'; +import { ICustomEmojis, initialState } from './customEmojis'; +import { mockedStore } from './mockedStore'; + +describe('test reducer', () => { + it('should return initial state', () => { + const state = mockedStore.getState().customEmojis; + expect(state).toEqual(initialState); + }); + it('should return modified store after action', () => { + const emojis: ICustomEmojis = { dog: { name: 'dog', extension: 'jpg' }, cat: { name: 'cat', extension: 'jpg' } }; + mockedStore.dispatch(setCustomEmojis(emojis)); + const state = mockedStore.getState().customEmojis; + expect(state).toEqual(emojis); + }); +}); diff --git a/app/reducers/customEmojis.ts b/app/reducers/customEmojis.ts new file mode 100644 index 00000000..859d634d --- /dev/null +++ b/app/reducers/customEmojis.ts @@ -0,0 +1,23 @@ +import { SET_CUSTOM_EMOJIS } from '../actions/actionsTypes'; +import { TApplicationActions } from '../definitions'; + +// There are at least three interfaces for emoji, but none of them includes only this data. +interface IEmoji { + name: string; + extension: string; +} + +export interface ICustomEmojis { + [key: string]: IEmoji; +} + +export const initialState: ICustomEmojis = {}; + +export default function customEmojis(state = initialState, action: TApplicationActions): ICustomEmojis { + switch (action.type) { + case SET_CUSTOM_EMOJIS: + return action.emojis; + default: + return state; + } +} diff --git a/app/reducers/encryption.test.ts b/app/reducers/encryption.test.ts new file mode 100644 index 00000000..96abe2a7 --- /dev/null +++ b/app/reducers/encryption.test.ts @@ -0,0 +1,28 @@ +import { encryptionSet, encryptionInit, encryptionSetBanner } from '../actions/encryption'; +import { mockedStore } from './mockedStore'; +import { initialState } from './encryption'; + +describe('test encryption reducer', () => { + it('should return initial state', () => { + const state = mockedStore.getState().encryption; + expect(state).toEqual(initialState); + }); + + it('should return modified store after encryptionSet', () => { + mockedStore.dispatch(encryptionSet(true, 'BANNER')); + const state = mockedStore.getState().encryption; + expect(state).toEqual({ banner: 'BANNER', enabled: true }); + }); + + it('should return empty store after encryptionInit', () => { + mockedStore.dispatch(encryptionInit()); + const state = mockedStore.getState().encryption; + expect(state).toEqual({ banner: '', enabled: false }); + }); + + it('should return initial state after encryptionSetBanner', () => { + mockedStore.dispatch(encryptionSetBanner('BANNER_NEW')); + const state = mockedStore.getState().encryption; + expect(state).toEqual({ banner: 'BANNER_NEW', enabled: false }); + }); +}); diff --git a/app/reducers/encryption.js b/app/reducers/encryption.ts similarity index 54% rename from app/reducers/encryption.js rename to app/reducers/encryption.ts index 0145ae2d..061dc7c9 100644 --- a/app/reducers/encryption.js +++ b/app/reducers/encryption.ts @@ -1,11 +1,18 @@ import { ENCRYPTION } from '../actions/actionsTypes'; +import { TApplicationActions } from '../definitions'; -const initialState = { +export type IBanner = string; +export interface IEncryption { + enabled: boolean; + banner: IBanner; +} + +export const initialState: IEncryption = { enabled: false, - banner: null + banner: '' }; -export default function encryption(state = initialState, action) { +export default function encryption(state = initialState, action: TApplicationActions): IEncryption { switch (action.type) { case ENCRYPTION.SET: return { diff --git a/app/views/E2EEnterYourPasswordView.tsx b/app/views/E2EEnterYourPasswordView.tsx index 6d63f90d..6e08b07b 100644 --- a/app/views/E2EEnterYourPasswordView.tsx +++ b/app/views/E2EEnterYourPasswordView.tsx @@ -1,23 +1,23 @@ +import { StackNavigationOptions } from '@react-navigation/stack'; import React from 'react'; import { ScrollView, StyleSheet, Text } from 'react-native'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; -import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; -import I18n from '../i18n'; -import { withTheme } from '../theme'; -import Button from '../containers/Button'; +import { encryptionDecodeKey } from '../actions/encryption'; import { themes } from '../constants/colors'; -import TextInput from '../containers/TextInput'; -import SafeAreaView from '../containers/SafeAreaView'; +import Button from '../containers/Button'; import * as HeaderButton from '../containers/HeaderButton'; -import { encryptionDecodeKey as encryptionDecodeKeyAction } from '../actions/encryption'; -import scrollPersistTaps from '../utils/scrollPersistTaps'; -import KeyboardView from '../presentation/KeyboardView'; +import SafeAreaView from '../containers/SafeAreaView'; import StatusBar from '../containers/StatusBar'; -import { events, logEvent } from '../utils/log'; -import sharedStyles from './Styles'; +import TextInput from '../containers/TextInput'; +import { IBaseScreen } from '../definitions'; +import I18n from '../i18n'; +import KeyboardView from '../presentation/KeyboardView'; import { E2EEnterYourPasswordStackParamList } from '../stacks/types'; +import { withTheme } from '../theme'; +import { events, logEvent } from '../utils/log'; +import scrollPersistTaps from '../utils/scrollPersistTaps'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ container: { @@ -34,21 +34,17 @@ interface IE2EEnterYourPasswordViewState { password: string; } -interface IE2EEnterYourPasswordViewProps { - encryptionDecodeKey: (password: string) => void; - theme: string; - navigation: StackNavigationProp; -} +type TE2EEnterYourPasswordViewProps = IBaseScreen; -class E2EEnterYourPasswordView extends React.Component { +class E2EEnterYourPasswordView extends React.Component { private passwordInput?: TextInput; - static navigationOptions = ({ navigation }: Pick): StackNavigationOptions => ({ + static navigationOptions = ({ navigation }: Pick): StackNavigationOptions => ({ headerLeft: () => , title: I18n.t('Enter_Your_E2E_Password') }); - constructor(props: IE2EEnterYourPasswordViewProps) { + constructor(props: TE2EEnterYourPasswordViewProps) { super(props); this.state = { password: '' @@ -58,8 +54,8 @@ class E2EEnterYourPasswordView extends React.Component { logEvent(events.E2E_ENTER_PW_SUBMIT); const { password } = this.state; - const { encryptionDecodeKey } = this.props; - encryptionDecodeKey(password); + const { dispatch } = this.props; + dispatch(encryptionDecodeKey(password)); }; render() { @@ -109,7 +105,4 @@ class E2EEnterYourPasswordView extends React.Component ({ - encryptionDecodeKey: (password: string) => dispatch(encryptionDecodeKeyAction(password)) -}); -export default connect(null, mapDispatchToProps)(withTheme(E2EEnterYourPasswordView)); +export default connect(null)(withTheme(E2EEnterYourPasswordView)); diff --git a/app/views/E2ESaveYourPasswordView.tsx b/app/views/E2ESaveYourPasswordView.tsx index 3d9a32ee..cc7bac83 100644 --- a/app/views/E2ESaveYourPasswordView.tsx +++ b/app/views/E2ESaveYourPasswordView.tsx @@ -1,25 +1,24 @@ import React from 'react'; -import { StackNavigationProp } from '@react-navigation/stack'; -import { Dispatch } from 'redux'; -import { connect } from 'react-redux'; import { Clipboard, ScrollView, StyleSheet, Text, View } from 'react-native'; +import { connect } from 'react-redux'; -import { encryptionSetBanner as encryptionSetBannerAction } from '../actions/encryption'; -import { E2E_RANDOM_PASSWORD_KEY } from '../lib/encryption/constants'; +import { encryptionSetBanner } from '../actions/encryption'; +import { themes } from '../constants/colors'; +import Button from '../containers/Button'; import * as HeaderButton from '../containers/HeaderButton'; -import scrollPersistTaps from '../utils/scrollPersistTaps'; import SafeAreaView from '../containers/SafeAreaView'; -import UserPreferences from '../lib/userPreferences'; -import { events, logEvent } from '../utils/log'; import StatusBar from '../containers/StatusBar'; import { LISTENER } from '../containers/Toast'; -import { themes } from '../constants/colors'; -import EventEmitter from '../utils/events'; -import Button from '../containers/Button'; -import { withTheme } from '../theme'; +import { IApplicationState, IBaseScreen } from '../definitions'; import I18n from '../i18n'; -import sharedStyles from './Styles'; +import { E2E_RANDOM_PASSWORD_KEY } from '../lib/encryption/constants'; +import UserPreferences from '../lib/userPreferences'; import { E2ESaveYourPasswordStackParamList } from '../stacks/types'; +import { withTheme } from '../theme'; +import EventEmitter from '../utils/events'; +import { events, logEvent } from '../utils/log'; +import scrollPersistTaps from '../utils/scrollPersistTaps'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ container: { @@ -59,11 +58,8 @@ interface IE2ESaveYourPasswordViewState { password: string; } -interface IE2ESaveYourPasswordViewProps { +interface IE2ESaveYourPasswordViewProps extends IBaseScreen { server: string; - navigation: StackNavigationProp; - encryptionSetBanner(): void; - theme: string; } class E2ESaveYourPasswordView extends React.Component { @@ -103,11 +99,11 @@ class E2ESaveYourPasswordView extends React.Component { logEvent(events.E2E_SAVE_PW_SAVED); - const { navigation, server, encryptionSetBanner } = this.props; + const { navigation, server, dispatch } = this.props; // Remove stored password await UserPreferences.removeItem(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); // Hide encryption banner - encryptionSetBanner(); + dispatch(encryptionSetBanner()); navigation.pop(); }; @@ -173,10 +169,8 @@ class E2ESaveYourPasswordView extends React.Component ({ +const mapStateToProps = (state: IApplicationState) => ({ server: state.server.server }); -const mapDispatchToProps = (dispatch: Dispatch) => ({ - encryptionSetBanner: () => dispatch(encryptionSetBannerAction()) -}); -export default connect(mapStateToProps, mapDispatchToProps)(withTheme(E2ESaveYourPasswordView)); + +export default connect(mapStateToProps)(withTheme(E2ESaveYourPasswordView));