From d8e112e645aed497bb6be3ed76f503af46a95cf9 Mon Sep 17 00:00:00 2001 From: Vit Stanislav Date: Fri, 6 Oct 2017 11:00:42 +0200 Subject: [PATCH 1/7] Refactor addedTransaction middleware to transactions middleware --- src/store/middlewares/addedTransaction.js | 24 -------------- src/store/middlewares/index.js | 4 +-- src/store/middlewares/transactions.js | 31 +++++++++++++++++++ ...ansaction.test.js => transactions.test.js} | 4 +-- 4 files changed, 35 insertions(+), 28 deletions(-) delete mode 100644 src/store/middlewares/addedTransaction.js create mode 100644 src/store/middlewares/transactions.js rename src/store/middlewares/{addedTransaction.test.js => transactions.test.js} (94%) diff --git a/src/store/middlewares/addedTransaction.js b/src/store/middlewares/addedTransaction.js deleted file mode 100644 index 5667846c8..000000000 --- a/src/store/middlewares/addedTransaction.js +++ /dev/null @@ -1,24 +0,0 @@ -import i18next from 'i18next'; -import actionTypes from '../../constants/actions'; -import { successAlertDialogDisplayed } from '../../actions/dialog'; -import { fromRawLsk } from '../../utils/lsk'; -import transactionTypes from '../../constants/transactionTypes'; - -const addedTransactionMiddleware = store => next => (action) => { - next(action); - if (action.type === actionTypes.transactionAdded) { - const texts = { - [transactionTypes.setSecondPassphrase]: i18next.t('Second passphrase registration was successfully submitted. It can take several seconds before it is processed.'), - [transactionTypes.registerDelegate]: i18next.t('Delegate registration was successfully submitted with username: "{{username}}". It can take several seconds before it is processed.', - { username: action.data.username }), - [transactionTypes.vote]: i18next.t('Your votes were successfully submitted. It can take several seconds before they are processed.'), - [transactionTypes.send]: i18next.t('Your transaction of {{amount}} LSK to {{recipientAddress}} was accepted and will be processed in a few seconds.', - { amount: fromRawLsk(action.data.amount), recipientAddress: action.data.recipientId }), - }; - const text = texts[action.data.type]; - const newAction = successAlertDialogDisplayed({ text }); - store.dispatch(newAction); - } -}; - -export default addedTransactionMiddleware; diff --git a/src/store/middlewares/index.js b/src/store/middlewares/index.js index 41a5f13fe..a732e0146 100644 --- a/src/store/middlewares/index.js +++ b/src/store/middlewares/index.js @@ -2,7 +2,7 @@ import thunk from 'redux-thunk'; import metronomeMiddleware from './metronome'; import accountMiddleware from './account'; import loginMiddleware from './login'; -import addedTransactionMiddleware from './addedTransaction'; +import transactionsMiddleware from './transactions'; import loadingBarMiddleware from './loadingBar'; import offlineMiddleware from './offline'; import notificationMiddleware from './notification'; @@ -11,7 +11,7 @@ import savedAccountsMiddleware from './savedAccounts'; export default [ thunk, - addedTransactionMiddleware, + transactionsMiddleware, loginMiddleware, metronomeMiddleware, accountMiddleware, diff --git a/src/store/middlewares/transactions.js b/src/store/middlewares/transactions.js new file mode 100644 index 000000000..44b007094 --- /dev/null +++ b/src/store/middlewares/transactions.js @@ -0,0 +1,31 @@ +import i18next from 'i18next'; +import actionTypes from '../../constants/actions'; +import { successAlertDialogDisplayed } from '../../actions/dialog'; +import { fromRawLsk } from '../../utils/lsk'; +import transactionTypes from '../../constants/transactionTypes'; + +const transactionAdded = (store, action) => { + const texts = { + [transactionTypes.setSecondPassphrase]: i18next.t('Second passphrase registration was successfully submitted. It can take several seconds before it is processed.'), + [transactionTypes.registerDelegate]: i18next.t('Delegate registration was successfully submitted with username: "{{username}}". It can take several seconds before it is processed.', + { username: action.data.username }), + [transactionTypes.vote]: i18next.t('Your votes were successfully submitted. It can take several seconds before they are processed.'), + [transactionTypes.send]: i18next.t('Your transaction of {{amount}} LSK to {{recipientAddress}} was accepted and will be processed in a few seconds.', + { amount: fromRawLsk(action.data.amount), recipientAddress: action.data.recipientId }), + }; + const text = texts[action.data.type]; + const newAction = successAlertDialogDisplayed({ text }); + store.dispatch(newAction); +}; + +const transactionsMiddleware = store => next => (action) => { + next(action); + switch (action.type) { + case actionTypes.transactionAdded: + transactionAdded(store, action); + break; + default: break; + } +}; + +export default transactionsMiddleware; diff --git a/src/store/middlewares/addedTransaction.test.js b/src/store/middlewares/transactions.test.js similarity index 94% rename from src/store/middlewares/addedTransaction.test.js rename to src/store/middlewares/transactions.test.js index 93150c9a2..f908bf682 100644 --- a/src/store/middlewares/addedTransaction.test.js +++ b/src/store/middlewares/transactions.test.js @@ -2,10 +2,10 @@ import { expect } from 'chai'; import { spy, stub } from 'sinon'; import i18next from 'i18next'; import { successAlertDialogDisplayed } from '../../actions/dialog'; -import middleware from './addedTransaction'; +import middleware from './transactions'; import actionTypes from '../../constants/actions'; -describe('addedTransaction middleware', () => { +describe('transaction middleware', () => { let store; let next; From a9dc67fe625f561a2070c3cfdef35e7aafc71e7c Mon Sep 17 00:00:00 2001 From: Vit Stanislav Date: Mon, 9 Oct 2017 12:14:44 +0200 Subject: [PATCH 2/7] Check for pending transactions to find failed transactions --- src/actions/transactions.js | 9 +++++++++ src/constants/actions.js | 1 + src/store/middlewares/transactions.js | 21 +++++++++++++++++++-- src/utils/api/account.js | 9 +++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/actions/transactions.js b/src/actions/transactions.js index bf88a67c2..04c24bfa9 100644 --- a/src/actions/transactions.js +++ b/src/actions/transactions.js @@ -10,6 +10,15 @@ export const transactionAdded = data => ({ type: actionTypes.transactionAdded, }); +/** + * An action to dispatch transactionsFailed + * + */ +export const transactionsFailed = data => ({ + data, + type: actionTypes.transactionsFailed, +}); + /** * An action to dispatch transactionsUpdated * diff --git a/src/constants/actions.js b/src/constants/actions.js index 6cb29238d..7750db36f 100644 --- a/src/constants/actions.js +++ b/src/constants/actions.js @@ -22,6 +22,7 @@ const actionTypes = { loadingStarted: 'LOADING_STARTED', loadingFinished: 'LOADING_FINISHED', transactionAdded: 'TRANSACTION_ADDED', + transactionsFailed: 'TRANSACTIONS_FAILED', transactionsUpdated: 'TRANSACTIONS_UPDATED', transactionsLoaded: 'TRANSACTIONS_LOADED', transactionsReset: 'TRANSACTIONS_RESET', diff --git a/src/store/middlewares/transactions.js b/src/store/middlewares/transactions.js index 44b007094..1d82ee3be 100644 --- a/src/store/middlewares/transactions.js +++ b/src/store/middlewares/transactions.js @@ -1,7 +1,10 @@ import i18next from 'i18next'; -import actionTypes from '../../constants/actions'; -import { successAlertDialogDisplayed } from '../../actions/dialog'; + import { fromRawLsk } from '../../utils/lsk'; +import { getUnconfirmedTransactions } from '../../utils/api/account'; +import { successAlertDialogDisplayed } from '../../actions/dialog'; +import { transactionsFailed } from '../../actions/transactions'; +import actionTypes from '../../constants/actions'; import transactionTypes from '../../constants/transactionTypes'; const transactionAdded = (store, action) => { @@ -18,12 +21,26 @@ const transactionAdded = (store, action) => { store.dispatch(newAction); }; +const transactionsUpdated = (store) => { + const { transactions, account, peers } = store.getState(); + if (transactions.pending.length) { + getUnconfirmedTransactions(peers.data, account.address) + .then(response => store.dispatch(transactionsFailed({ + failed: transactions.pending.filter(tx => + response.transactions.filter(unconfirmedTx => tx.id === unconfirmedTx.id).length === 0), + }))); + } +}; + const transactionsMiddleware = store => next => (action) => { next(action); switch (action.type) { case actionTypes.transactionAdded: transactionAdded(store, action); break; + case actionTypes.transactionsUpdated: + transactionsUpdated(store, action); + break; default: break; } }; diff --git a/src/utils/api/account.js b/src/utils/api/account.js index 0ffdfdb42..20ed14460 100644 --- a/src/utils/api/account.js +++ b/src/utils/api/account.js @@ -35,6 +35,15 @@ export const transactions = (activePeer, address, limit = 20, offset = 0, orderB orderBy, }); +export const unconfirmedTransactions = (activePeer, address, limit = 20, offset = 0, orderBy = 'timestamp:desc') => + requestToActivePeer(activePeer, 'transactions/unconfirmed', { + senderId: address, + recipientId: address, + limit, + offset, + orderBy, + }); + export const extractPublicKey = passphrase => Lisk.crypto.getKeys(passphrase).publicKey; From 696b4d828ac05692a5da0c879e45df80c1126fa4 Mon Sep 17 00:00:00 2001 From: Vit Stanislav Date: Mon, 9 Oct 2017 12:16:58 +0200 Subject: [PATCH 3/7] Process failed transactions in reducers --- src/components/voting/index.test.js | 1 + src/store/reducers/transactions.js | 12 ++++++++++-- src/store/reducers/transactions.test.js | 26 +++++++++++++------------ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/components/voting/index.test.js b/src/components/voting/index.test.js index 8773b2397..3f143d90d 100644 --- a/src/components/voting/index.test.js +++ b/src/components/voting/index.test.js @@ -17,6 +17,7 @@ describe('VotingHOC', () => { transactions: { pending: [], confirmed: [], + failed: [], }, voting: { delegates: [ diff --git a/src/store/reducers/transactions.js b/src/store/reducers/transactions.js index 84dac0442..66547a4d4 100644 --- a/src/store/reducers/transactions.js +++ b/src/store/reducers/transactions.js @@ -5,12 +5,20 @@ import actionTypes from '../../constants/actions'; * @param {Array} state * @param {Object} action */ -const transactions = (state = { pending: [], confirmed: [], count: null }, action) => { +const transactions = (state = { pending: [], confirmed: [], failed: [], count: null }, action) => { switch (action.type) { case actionTypes.transactionAdded: return Object.assign({}, state, { pending: [action.data, ...state.pending], }); + case actionTypes.transactionsFailed: + return Object.assign({}, state, { + // Filter any failed transaction from pending + pending: state.pending.filter( + pendingTransaction => action.data.failed.filter( + transaction => transaction.id === pendingTransaction.id).length === 0), + failed: [...action.data, ...state.failed.map(tx => ({ ...tx, failed: true }))], + }); case actionTypes.transactionsLoaded: return Object.assign({}, state, { confirmed: [ @@ -35,7 +43,7 @@ const transactions = (state = { pending: [], confirmed: [], count: null }, actio count: action.data.count, }); case actionTypes.accountLoggedOut: - return { pending: [], confirmed: [], count: 0 }; + return { pending: [], confirmed: [], failed: [], count: 0 }; default: return state; } diff --git a/src/store/reducers/transactions.test.js b/src/store/reducers/transactions.test.js index 8aa4e43df..f4c3971ed 100644 --- a/src/store/reducers/transactions.test.js +++ b/src/store/reducers/transactions.test.js @@ -3,6 +3,11 @@ import transactions from './transactions'; import actionTypes from '../../constants/actions'; describe('Reducer: transactions(state, action)', () => { + const defaultState = { + pending: [], + confirmed: [], + failed: [], + }; const mockTransactions = [{ amount: 100000000000, id: '16295820046284152875', @@ -19,8 +24,8 @@ describe('Reducer: transactions(state, action)', () => { it('should prepend action.data to state.pending if action.type = actionTypes.transactionAdded', () => { const state = { + ...defaultState, pending: [mockTransactions[1]], - confirmed: [], }; const action = { type: actionTypes.transactionAdded, @@ -31,10 +36,7 @@ describe('Reducer: transactions(state, action)', () => { }); it('should concat action.data to state.confirmed if action.type = actionTypes.transactionsLoaded', () => { - const state = { - pending: [], - confirmed: [], - }; + const state = { ...defaultState }; const action = { type: actionTypes.transactionsLoaded, data: { @@ -43,7 +45,7 @@ describe('Reducer: transactions(state, action)', () => { }, }; const expectedState = { - pending: [], + ...defaultState, confirmed: action.data.confirmed, count: action.data.count, }; @@ -53,6 +55,7 @@ describe('Reducer: transactions(state, action)', () => { it('should prepend newer transactions from action.data to state.confirmed and remove from state.pending if action.type = actionTypes.transactionsUpdated', () => { const state = { + ...defaultState, pending: [mockTransactions[0]], confirmed: [mockTransactions[1], mockTransactions[2]], count: mockTransactions[1].length + mockTransactions[2].length, @@ -66,7 +69,7 @@ describe('Reducer: transactions(state, action)', () => { }; const changedState = transactions(state, action); expect(changedState).to.deep.equal({ - pending: [], + ...defaultState, confirmed: mockTransactions, count: mockTransactions.length, }); @@ -74,8 +77,7 @@ describe('Reducer: transactions(state, action)', () => { it('should action.data to state.confirmed if state.confirmed is empty and action.type = actionTypes.transactionsUpdated', () => { const state = { - pending: [], - confirmed: [], + ...defaultState, }; const action = { type: actionTypes.transactionsUpdated, @@ -86,7 +88,7 @@ describe('Reducer: transactions(state, action)', () => { }; const changedState = transactions(state, action); expect(changedState).to.deep.equal({ - pending: [], + ...defaultState, confirmed: mockTransactions, count: mockTransactions.length, }); @@ -94,6 +96,7 @@ describe('Reducer: transactions(state, action)', () => { it('should reset all data if action.type = actionTypes.accountLoggedOut', () => { const state = { + ...defaultState, pending: [{ amount: 110000000000, id: '16295820046284152275', @@ -104,8 +107,7 @@ describe('Reducer: transactions(state, action)', () => { const action = { type: actionTypes.accountLoggedOut }; const changedState = transactions(state, action); expect(changedState).to.deep.equal({ - pending: [], - confirmed: [], + ...defaultState, count: 0, }); }); From ccedb700c0ec0c7cfd5e97981d70d78b6fe4fda4 Mon Sep 17 00:00:00 2001 From: Vit Stanislav Date: Mon, 9 Oct 2017 12:17:28 +0200 Subject: [PATCH 4/7] Display failed transactions in transactions tab --- src/components/transactions/index.js | 6 +++++- src/components/transactions/index.test.js | 4 +++- src/components/transactions/transactionRow.js | 6 +++++- src/components/transactions/transactions.css | 4 ++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/transactions/index.js b/src/components/transactions/index.js index fdf7dc517..deb49536e 100644 --- a/src/components/transactions/index.js +++ b/src/components/transactions/index.js @@ -6,7 +6,11 @@ import Transactions from './transactions'; const mapStateToProps = state => ({ address: state.account.address, activePeer: state.peers.data, - transactions: [...state.transactions.pending, ...state.transactions.confirmed], + transactions: [ + ...state.transactions.pending, + ...state.transactions.failed, + ...state.transactions.confirmed, + ], count: state.transactions.count, confirmedCount: state.transactions.confirmed.length, pendingCount: state.transactions.pending.length, diff --git a/src/components/transactions/index.test.js b/src/components/transactions/index.test.js index 55bafa23e..11ecdceb6 100644 --- a/src/components/transactions/index.test.js +++ b/src/components/transactions/index.test.js @@ -14,8 +14,10 @@ describe('TransactionsHOC', () => { let wrapper; const confirmed = []; const pending = []; + const failed = []; const transactions = { pending, + failed, confirmed, count: confirmed.length, }; @@ -46,7 +48,7 @@ describe('TransactionsHOC', () => { const props = wrapper.find('Transactions').props(); expect(props.address).to.be.equal(account.address); expect(props.activePeer).to.be.equal(peers.data); - expect(props.transactions).to.deep.equal([...transactions, ...pending]); + expect(props.transactions).to.deep.equal([...pending, ...failed, ...confirmed]); expect(props.count).to.be.equal(transactions.count); expect(props.confirmedCount).to.be.equal(confirmed.length); expect(props.pendingCount).to.be.equal(pending.length); diff --git a/src/components/transactions/transactionRow.js b/src/components/transactions/transactionRow.js index 26f78338e..ca46138f7 100644 --- a/src/components/transactions/transactionRow.js +++ b/src/components/transactions/transactionRow.js @@ -9,6 +9,10 @@ import Status from './status'; import Amount from './amount'; import Spinner from '../spinner'; +const setRowClass = tx => ( + tx.failed && styles.failedTransaction +); + class TransactionRow extends React.Component { // eslint-disable-next-line class-methods-use-this shouldComponentUpdate(nextProps) { @@ -17,7 +21,7 @@ class TransactionRow extends React.Component { render() { const props = this.props; - return ( + return ( {props.value.confirmations ? : diff --git a/src/components/transactions/transactions.css b/src/components/transactions/transactions.css index e8e38aab4..779fa90f5 100644 --- a/src/components/transactions/transactions.css +++ b/src/components/transactions/transactions.css @@ -24,6 +24,10 @@ background: color(var(--in) alpha(45%)) !important; } +.failedTransaction { + background-color: rgb(255, 228, 220) !important; +} + .empty { color: #777; text-align: center; From c3353176aa99156a345a9aa2a6e5c6d4cb5beca9 Mon Sep 17 00:00:00 2001 From: Vit Stanislav Date: Mon, 9 Oct 2017 12:23:39 +0200 Subject: [PATCH 5/7] Revert "Display failed transactions in transactions tab" because the failed transactions is rarer than I thought and there is no easy way to reproduce it so that we can actually test it. This reverts commit ccedb700c0ec0c7cfd5e97981d70d78b6fe4fda4. --- src/components/transactions/index.js | 6 +----- src/components/transactions/index.test.js | 4 +--- src/components/transactions/transactionRow.js | 6 +----- src/components/transactions/transactions.css | 4 ---- 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/components/transactions/index.js b/src/components/transactions/index.js index deb49536e..fdf7dc517 100644 --- a/src/components/transactions/index.js +++ b/src/components/transactions/index.js @@ -6,11 +6,7 @@ import Transactions from './transactions'; const mapStateToProps = state => ({ address: state.account.address, activePeer: state.peers.data, - transactions: [ - ...state.transactions.pending, - ...state.transactions.failed, - ...state.transactions.confirmed, - ], + transactions: [...state.transactions.pending, ...state.transactions.confirmed], count: state.transactions.count, confirmedCount: state.transactions.confirmed.length, pendingCount: state.transactions.pending.length, diff --git a/src/components/transactions/index.test.js b/src/components/transactions/index.test.js index 11ecdceb6..55bafa23e 100644 --- a/src/components/transactions/index.test.js +++ b/src/components/transactions/index.test.js @@ -14,10 +14,8 @@ describe('TransactionsHOC', () => { let wrapper; const confirmed = []; const pending = []; - const failed = []; const transactions = { pending, - failed, confirmed, count: confirmed.length, }; @@ -48,7 +46,7 @@ describe('TransactionsHOC', () => { const props = wrapper.find('Transactions').props(); expect(props.address).to.be.equal(account.address); expect(props.activePeer).to.be.equal(peers.data); - expect(props.transactions).to.deep.equal([...pending, ...failed, ...confirmed]); + expect(props.transactions).to.deep.equal([...transactions, ...pending]); expect(props.count).to.be.equal(transactions.count); expect(props.confirmedCount).to.be.equal(confirmed.length); expect(props.pendingCount).to.be.equal(pending.length); diff --git a/src/components/transactions/transactionRow.js b/src/components/transactions/transactionRow.js index ca46138f7..26f78338e 100644 --- a/src/components/transactions/transactionRow.js +++ b/src/components/transactions/transactionRow.js @@ -9,10 +9,6 @@ import Status from './status'; import Amount from './amount'; import Spinner from '../spinner'; -const setRowClass = tx => ( - tx.failed && styles.failedTransaction -); - class TransactionRow extends React.Component { // eslint-disable-next-line class-methods-use-this shouldComponentUpdate(nextProps) { @@ -21,7 +17,7 @@ class TransactionRow extends React.Component { render() { const props = this.props; - return ( + return ( {props.value.confirmations ? : diff --git a/src/components/transactions/transactions.css b/src/components/transactions/transactions.css index 779fa90f5..e8e38aab4 100644 --- a/src/components/transactions/transactions.css +++ b/src/components/transactions/transactions.css @@ -24,10 +24,6 @@ background: color(var(--in) alpha(45%)) !important; } -.failedTransaction { - background-color: rgb(255, 228, 220) !important; -} - .empty { color: #777; text-align: center; From f4d995b60cd1a8683a85c811877ec05a13c99c1f Mon Sep 17 00:00:00 2001 From: Vit Stanislav Date: Mon, 9 Oct 2017 12:26:10 +0200 Subject: [PATCH 6/7] Remove failed transactions from redux because the failed transactions is rarer than I thought and there is no easy way to reproduce it so that we can actually test it. --- src/components/voting/index.test.js | 1 - src/store/reducers/transactions.js | 5 ++--- src/store/reducers/transactions.test.js | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/voting/index.test.js b/src/components/voting/index.test.js index 3f143d90d..8773b2397 100644 --- a/src/components/voting/index.test.js +++ b/src/components/voting/index.test.js @@ -17,7 +17,6 @@ describe('VotingHOC', () => { transactions: { pending: [], confirmed: [], - failed: [], }, voting: { delegates: [ diff --git a/src/store/reducers/transactions.js b/src/store/reducers/transactions.js index 66547a4d4..27e2815e0 100644 --- a/src/store/reducers/transactions.js +++ b/src/store/reducers/transactions.js @@ -5,7 +5,7 @@ import actionTypes from '../../constants/actions'; * @param {Array} state * @param {Object} action */ -const transactions = (state = { pending: [], confirmed: [], failed: [], count: null }, action) => { +const transactions = (state = { pending: [], confirmed: [], count: null }, action) => { switch (action.type) { case actionTypes.transactionAdded: return Object.assign({}, state, { @@ -17,7 +17,6 @@ const transactions = (state = { pending: [], confirmed: [], failed: [], count: n pending: state.pending.filter( pendingTransaction => action.data.failed.filter( transaction => transaction.id === pendingTransaction.id).length === 0), - failed: [...action.data, ...state.failed.map(tx => ({ ...tx, failed: true }))], }); case actionTypes.transactionsLoaded: return Object.assign({}, state, { @@ -43,7 +42,7 @@ const transactions = (state = { pending: [], confirmed: [], failed: [], count: n count: action.data.count, }); case actionTypes.accountLoggedOut: - return { pending: [], confirmed: [], failed: [], count: 0 }; + return { pending: [], confirmed: [], count: 0 }; default: return state; } diff --git a/src/store/reducers/transactions.test.js b/src/store/reducers/transactions.test.js index f4c3971ed..cd72e0c5b 100644 --- a/src/store/reducers/transactions.test.js +++ b/src/store/reducers/transactions.test.js @@ -6,7 +6,6 @@ describe('Reducer: transactions(state, action)', () => { const defaultState = { pending: [], confirmed: [], - failed: [], }; const mockTransactions = [{ amount: 100000000000, From cbeb12cec5db3c5689eb3b199b995db2857d0874 Mon Sep 17 00:00:00 2001 From: Vit Stanislav Date: Mon, 9 Oct 2017 14:22:40 +0200 Subject: [PATCH 7/7] Add unit tests for failed transactions logic --- src/actions/transactions.test.js | 16 ++++- src/store/middlewares/transactions.js | 4 +- src/store/middlewares/transactions.test.js | 69 ++++++++++++++++++---- src/utils/api/account.test.js | 9 ++- 4 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/actions/transactions.test.js b/src/actions/transactions.test.js index fcc9f87f5..ab9378407 100644 --- a/src/actions/transactions.test.js +++ b/src/actions/transactions.test.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; import actionTypes from '../constants/actions'; -import { transactionAdded, transactionsUpdated, +import { transactionAdded, transactionsUpdated, transactionsFailed, transactionsLoaded, transactionsRequested } from './transactions'; import * as accountApi from '../utils/api/account'; @@ -20,6 +20,20 @@ describe('actions: transactions', () => { }); }); + describe('transactionsFailed', () => { + it('should create an action to transactionsFailed', () => { + const data = { + id: 'dummy', + }; + const expectedAction = { + data, + type: actionTypes.transactionsFailed, + }; + + expect(transactionsFailed(data)).to.be.deep.equal(expectedAction); + }); + }); + describe('transactionsUpdated', () => { it('should create an action to transactionsUpdated', () => { const data = { diff --git a/src/store/middlewares/transactions.js b/src/store/middlewares/transactions.js index 1d82ee3be..3980afbce 100644 --- a/src/store/middlewares/transactions.js +++ b/src/store/middlewares/transactions.js @@ -1,7 +1,7 @@ import i18next from 'i18next'; import { fromRawLsk } from '../../utils/lsk'; -import { getUnconfirmedTransactions } from '../../utils/api/account'; +import { unconfirmedTransactions } from '../../utils/api/account'; import { successAlertDialogDisplayed } from '../../actions/dialog'; import { transactionsFailed } from '../../actions/transactions'; import actionTypes from '../../constants/actions'; @@ -24,7 +24,7 @@ const transactionAdded = (store, action) => { const transactionsUpdated = (store) => { const { transactions, account, peers } = store.getState(); if (transactions.pending.length) { - getUnconfirmedTransactions(peers.data, account.address) + unconfirmedTransactions(peers.data, account.address) .then(response => store.dispatch(transactionsFailed({ failed: transactions.pending.filter(tx => response.transactions.filter(unconfirmedTx => tx.id === unconfirmedTx.id).length === 0), diff --git a/src/store/middlewares/transactions.test.js b/src/store/middlewares/transactions.test.js index f908bf682..5c464bc3f 100644 --- a/src/store/middlewares/transactions.test.js +++ b/src/store/middlewares/transactions.test.js @@ -1,24 +1,44 @@ import { expect } from 'chai'; -import { spy, stub } from 'sinon'; +import { spy, stub, mock } from 'sinon'; import i18next from 'i18next'; +import * as accountApi from '../../utils/api/account'; import { successAlertDialogDisplayed } from '../../actions/dialog'; +import { transactionsFailed } from '../../actions/transactions'; import middleware from './transactions'; import actionTypes from '../../constants/actions'; describe('transaction middleware', () => { let store; let next; + let state; + let accountApiMock; + const mockTransaction = { + username: 'test', + amount: 1e8, + recipientId: '16313739661670634666L', + }; beforeEach(() => { store = stub(); - store.getState = () => ({ + state = { peers: { data: {}, }, - account: {}, - }); + account: { + address: '8096217735672704724L', + }, + transactions: { + pending: [], + }, + }; + store.getState = () => (state); store.dispatch = spy(); next = spy(); + accountApiMock = mock(accountApi); + }); + + afterEach(() => { + accountApiMock.restore(); }); it('should passes the action to next middleware', () => { @@ -30,14 +50,10 @@ describe('transaction middleware', () => { expect(next).to.have.been.calledWith(givenAction); }); - it('fire success dialog action with appropriate text ', () => { + it('should fire success dialog action with appropriate text if action.type is transactionAdded', () => { const givenAction = { type: actionTypes.transactionAdded, - data: { - username: 'test', - amount: 1e8, - recipientId: '16313739661670634666L', - }, + data: mockTransaction, }; const expectedMessages = [ @@ -54,5 +70,38 @@ describe('transaction middleware', () => { expect(store.dispatch).to.have.been.calledWith(expectedAction); } }); + + it('should do nothing if state.transactions.pending.length === 0 and action.type is transactionsUpdated', () => { + const givenAction = { + type: actionTypes.transactionsUpdated, + data: [mockTransaction], + }; + + middleware(store)(next)(givenAction); + expect(store.dispatch).to.not.have.been.calledWith(); + }); + + it('should call unconfirmedTransactions and then dispatch transactionsFailed if state.transactions.pending.length > 0 and action.type is transactionsUpdated', () => { + const transactions = [ + mockTransaction, + ]; + accountApiMock.expects('unconfirmedTransactions') + .withExactArgs(state.peers.data, state.account.address) + .returnsPromise().resolves({ transactions }); + store.getState = () => ({ + ...state, + transactions: { + pending: transactions, + }, + }); + const givenAction = { + type: actionTypes.transactionsUpdated, + data: [], + }; + + middleware(store)(next)(givenAction); + const expectedAction = transactionsFailed({ failed: [] }); + expect(store.dispatch).to.have.been.calledWith(expectedAction); + }); }); diff --git a/src/utils/api/account.test.js b/src/utils/api/account.test.js index 24de50fb6..46816e5af 100644 --- a/src/utils/api/account.test.js +++ b/src/utils/api/account.test.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { mock } from 'sinon'; -import { getAccount, setSecondPassphrase, send, transactions, +import { getAccount, setSecondPassphrase, send, transactions, unconfirmedTransactions, extractPublicKey, extractAddress } from './account'; import { activePeerSet } from '../../actions/peers'; @@ -82,6 +82,13 @@ describe('Utils: Account', () => { }); }); + describe('unconfirmedTransactions', () => { + it('should return a promise', () => { + const promise = unconfirmedTransactions(); + expect(typeof promise.then).to.be.equal('function'); + }); + }); + describe('extractPublicKey', () => { it('should return a Hex string from any given string', () => { const passphrase = 'field organ country moon fancy glare pencil combine derive fringe security pave';