Skip to content
This repository has been archived by the owner on Jun 7, 2023. It is now read-only.

Commit

Permalink
Assign account index to accounts in state (#715)
Browse files Browse the repository at this point in the history
* feat: add read-only (account) index to account info for each account

* fix: order account names by account index

* feat: add ability to auto assign account index to existing accounts in account state

* Reorder account indexes on account deletion
  • Loading branch information
laumair authored and cvarley100 committed Dec 11, 2018
1 parent ced0b49 commit 80fe0ae
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 160 deletions.
4 changes: 4 additions & 0 deletions src/desktop/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Provider as Redux } from 'react-redux';
import { MemoryRouter as Router } from 'react-router';
import i18next from 'libs/i18next';
import store, { persistStore } from 'store';
import { assignAccountIndexIfNecessary } from 'actions/accounts';
import persistElectronStorage from 'libs/storage';
import { changeIotaNode } from 'libs/iota';
import createPlugin from 'bugsnag-react';
Expand Down Expand Up @@ -46,6 +47,9 @@ const persistor = persistStore(store, persistConfig, (err, restoredState) => {
if (node) {
changeIotaNode(node);
}

// Assign accountIndex to every account in accountInfo if it is not assigned already
store.dispatch(assignAccountIndexIfNecessary(get(restoredState, 'accounts.accountInfo')));
});

if (Electron.mode === 'tray') {
Expand Down
4 changes: 4 additions & 0 deletions src/mobile/src/ui/routes/entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Provider } from 'react-redux';
import { changeIotaNode, SwitchingConfig } from 'shared-modules/libs/iota';
import sharedStore from 'shared-modules/store';
import iotaNativeBindings, { overrideAsyncTransactionObject } from 'shared-modules/libs/iota/nativeBindings';
import { assignAccountIndexIfNecessary } from 'shared-modules/actions/accounts';
import { fetchNodeList as fetchNodes } from 'shared-modules/actions/polling';
import { setCompletedForcedPasswordUpdate } from 'shared-modules/actions/settings';
import { ActionTypes } from 'shared-modules/actions/wallet';
Expand Down Expand Up @@ -38,6 +39,9 @@ const launch = (store) => {
store.dispatch(setCompletedForcedPasswordUpdate());
}

// Assign accountIndex to every account in accountInfo if it is not assigned already
store.dispatch(assignAccountIndexIfNecessary(get(state, 'accounts.accountInfo')));

// Set default language
i18next.changeLanguage(getLocaleFromLabel(state.settings.language));

Expand Down
64 changes: 64 additions & 0 deletions src/shared/__tests__/actions/accounts.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { expect } from 'chai';
import * as actions from '../../actions/accounts';

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

describe('actions: accounts', () => {
describe('#assignAccountIndexIfNecessary', () => {
describe('provided param is empty', () => {
it('should not create an action of type IOTA/ACCOUNTS/ASSIGN_ACCOUNT_INDEX', () => {
const store = mockStore({});

// Dispatch action
store.dispatch(actions.assignAccountIndexIfNecessary({}));

expect(store.getActions()).to.eql([]);
});
});

describe('provided param is not empty', () => {
describe('when some accounts in accountInfo object have an index property with a non-numeric value', () => {
it('should not create an action of type IOTA/ACCOUNTS/ASSIGN_ACCOUNT_INDEX', () => {
const store = mockStore({});

let accountInfo = { foo: {}, baz: { index: 2 } };

const expectedActions = [
{
type: 'IOTA/ACCOUNTS/ASSIGN_ACCOUNT_INDEX',
},
];

// Dispatch action
store.dispatch(actions.assignAccountIndexIfNecessary(accountInfo));
expect(store.getActions()).to.eql(expectedActions);

// Clear actions
store.clearActions();

// Reassign account info and check for undefined values
accountInfo = { foo: { index: undefined }, baz: { index: 1 } };

// Dispatch action
store.dispatch(actions.assignAccountIndexIfNecessary(accountInfo));
expect(store.getActions()).to.eql(expectedActions);
});
});

describe('when every account in accountInfo object has an index property with a numeric value', () => {
it('should not create an action of type IOTA/ACCOUNTS/ASSIGN_ACCOUNT_INDEX', () => {
const store = mockStore({});

const accountInfo = { foo: { index: 0 }, baz: { index: 1 } };

// Dispatch action
store.dispatch(actions.assignAccountIndexIfNecessary(accountInfo));
expect(store.getActions()).to.eql([]);
});
});
});
});
});
218 changes: 66 additions & 152 deletions src/shared/__tests__/reducers/accounts.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai';
import reducer, { mergeAddressData } from '../../reducers/accounts';
import reducer, { mergeAddressData, removeAccountAndReorderIndexes } from '../../reducers/accounts';
import * as actions from '../../actions/accounts';

describe('Reducer: accounts', () => {
Expand Down Expand Up @@ -456,157 +456,6 @@ describe('Reducer: accounts', () => {
});
});

describe('FULL_ACCOUNT_INFO_FETCH_SUCCESS', () => {
it('should merge unconfirmedBundleTails in payload to unconfirmedBundleTails in state', () => {
const initialState = {
unconfirmedBundleTails: { foo: {} },
};

const action = actions.fullAccountInfoFetchSuccess({ unconfirmedBundleTails: { baz: {} } });

const newState = reducer(initialState, action);
const expectedState = {
unconfirmedBundleTails: { baz: {}, foo: {} },
};

expect(newState.unconfirmedBundleTails).to.eql(expectedState.unconfirmedBundleTails);
});

it('should merge addresses in payload to accountName in accountInfo', () => {
const initialState = {
accountInfo: {
foo: {
addresses: { foo: {} },
transfers: {},
balance: 0,
},
},
};

const action = actions.fullAccountInfoFetchSuccess({
accountName: 'foo',
accountMeta: { type: 'bar' },
addresses: { foo: {}, baz: {} },
transfers: {},
balance: 100,
hashes: [],
});

const newState = reducer(initialState, action);
const expectedState = {
accountInfo: {
foo: {
meta: { type: 'bar' },
addresses: { foo: {}, baz: {} },
transfers: {},
balance: 100,
hashes: [],
},
},
};

expect(newState.accountInfo).to.eql(expectedState.accountInfo);
});

it('should assign transfers and set balance in payload to accountName in accountInfo', () => {
const initialState = {
accountInfo: {
foo: {
meta: { type: 'bar' },
addresses: { foo: {} },
transfers: { foo: { value: 20 }, baz: { value: 0 } },
balance: 0,
},
},
};

const action = actions.fullAccountInfoFetchSuccess({
accountName: 'foo',
accountMeta: { type: 'bar' },
addresses: { foo: {}, baz: {} },
transfers: { foo: { value: 0 } },
balance: 100,
hashes: [],
});

const newState = reducer(initialState, action);
const expectedState = {
accountInfo: {
foo: {
meta: { type: 'bar' },
addresses: { foo: {}, baz: {} },
transfers: { foo: { value: 0 }, baz: { value: 0 } },
balance: 100,
hashes: [],
},
},
};

expect(newState.accountInfo).to.eql(expectedState.accountInfo);
});

it('should set hashes in payload to accountName in accountInfo', () => {
const initialState = {
accountInfo: {
foo: {
meta: { type: 'bar' },
balance: 0,
transfers: {},
addresses: {},
hashes: ['baz'],
},
},
};

const action = actions.fullAccountInfoFetchSuccess({
accountName: 'foo',
accountMeta: { type: 'bar' },
hashes: ['baz', 'bar'],
transfers: {},
balance: 0,
addresses: {},
});

const newState = reducer(initialState, action);
const expectedState = {
accountInfo: {
foo: {
meta: { type: 'bar' },
balance: 0,
transfers: {},
addresses: {},
hashes: ['baz', 'bar'],
},
},
};

expect(newState.accountInfo).to.eql(expectedState.accountInfo);
});

it('should reset accountInfoDuringSetup to default state', () => {
const initialState = {
accountInfoDuringSetup: {
name: 'foo',
meta: { bar: {} },
usedExistingSeed: true,
},
};

const action = actions.fullAccountInfoFetchSuccess({});

const newState = reducer(initialState, action);
const expectedState = {
accountInfoDuringSetup: {
name: '',
meta: {},
usedExistingSeed: false,
},
};

expect(newState.accountInfoDuringSetup).to.eql(expectedState.accountInfoDuringSetup);
});
});

describe('FULL_ACCOUNT_INFO_FETCH_SUCCESS', () => {
it('should merge addresses in payload to accountName in accountInfo', () => {
const initialState = {
Expand All @@ -622,6 +471,7 @@ describe('Reducer: accounts', () => {

const action = actions.fullAccountInfoFetchSuccess({
accountName: 'foo',
accountIndex: 0,
accountMeta: { type: 'bar' },
addresses: { foo: {}, baz: {} },
transfers: {},
Expand All @@ -634,6 +484,7 @@ describe('Reducer: accounts', () => {
accountInfo: {
foo: {
meta: { type: 'bar' },
index: 0,
addresses: { foo: {}, baz: {} },
transfers: {},
balance: 100,
Expand All @@ -659,6 +510,7 @@ describe('Reducer: accounts', () => {

const action = actions.fullAccountInfoFetchSuccess({
accountName: 'foo',
accountIndex: 0,
accountMeta: { type: 'bar' },
addresses: { foo: {}, baz: {} },
transfers: { foo: { value: 0 } },
Expand All @@ -671,6 +523,7 @@ describe('Reducer: accounts', () => {
accountInfo: {
foo: {
meta: { type: 'bar' },
index: 0,
hashes: [],
addresses: { foo: {}, baz: {} },
transfers: { foo: { value: 0 }, baz: { value: 0 } },
Expand All @@ -696,6 +549,7 @@ describe('Reducer: accounts', () => {

const action = actions.fullAccountInfoFetchSuccess({
accountName: 'foo',
accountIndex: 0,
accountMeta: { type: 'bar' },
hashes: ['baz', 'bar'],
transfers: {},
Expand All @@ -708,6 +562,7 @@ describe('Reducer: accounts', () => {
accountInfo: {
foo: {
meta: { type: 'bar' },
index: 0,
balance: 0,
transfers: {},
addresses: {},
Expand Down Expand Up @@ -1070,6 +925,31 @@ describe('Reducer: accounts', () => {
});
});

describe('IOTA/ACCOUNTS/ASSIGN_ACCOUNT_INDEX', () => {
it('should assign "index" to each account in accountInfo state prop', () => {
const initialState = {
accountInfo: {
foo: { addresses: { ['U'.repeat(81)]: {} }, transfers: {} },
baz: { addresses: {}, transfers: {} },
},
};

const action = {
type: 'IOTA/ACCOUNTS/ASSIGN_ACCOUNT_INDEX',
};

const newState = reducer(initialState, action);
const expectedState = {
accountInfo: {
foo: { index: 0, addresses: { ['U'.repeat(81)]: {} }, transfers: {} },
baz: { index: 1, addresses: {}, transfers: {} },
},
};

expect(newState).to.eql(expectedState);
});
});

[
'IOTA/ACCOUNTS/UPDATE_ACCOUNT_INFO_AFTER_SPENDING',
'IOTA/ACCOUNTS/SYNC_ACCOUNT_BEFORE_MANUAL_PROMOTION',
Expand All @@ -1083,6 +963,7 @@ describe('Reducer: accounts', () => {
const initialState = {
accountInfo: {
dummy: {
index: 1,
meta: { type: 'bar' },
balance: 0,
addresses: { foo: {} },
Expand All @@ -1106,6 +987,7 @@ describe('Reducer: accounts', () => {
const expectedState = {
accountInfo: {
dummy: {
index: 1,
meta: { type: 'bar' },
balance: 0,
addresses: { foo: {}, baz: {} },
Expand Down Expand Up @@ -1259,4 +1141,36 @@ describe('Reducer: accounts', () => {
});
});
});

describe('#removeAccountAndReorderIndexes', () => {
describe('when accountName does not exist in accountInfo', () => {
it('should return existing accountInfo', () => {
const accountInfo = { foo: { index: 0 } };

expect(removeAccountAndReorderIndexes(accountInfo, 'baz')).to.eql(accountInfo);
});
});

describe('when accountName exists in accountInfo', () => {
it('should remove account from accountInfo', () => {
const accountInfo = { foo: { index: 0 }, baz: { index: 1 } };

expect(removeAccountAndReorderIndexes(accountInfo, 'baz')).to.eql({ foo: { index: 0 } });
});

it('should reorder account indexes (Fill missing indexes)', () => {
expect(
removeAccountAndReorderIndexes({ foo: { index: 0 }, baz: { index: 1 }, bar: { index: 2 } }, 'baz'),
).to.eql({ foo: { index: 0 }, bar: { index: 1 } });

expect(
removeAccountAndReorderIndexes({ foo: { index: 0 }, baz: { index: 1 }, bar: { index: 2 } }, 'bar'),
).to.eql({ foo: { index: 0 }, baz: { index: 1 } });

expect(
removeAccountAndReorderIndexes({ foo: { index: 0 }, baz: { index: 1 }, bar: { index: 2 } }, 'foo'),
).to.eql({ baz: { index: 0 }, bar: { index: 1 } });
});
});
});
});
Loading

0 comments on commit 80fe0ae

Please sign in to comment.