Skip to content
This repository has been archived by the owner on Apr 15, 2019. It is now read-only.

Allow to save and manage multiple accounts in local storage - Closes #573 #966

Merged
merged 22 commits into from
Nov 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f1fc075
Create pending e2e tests for multi account management
slaweet Nov 7, 2017
405996d
Add network options to peers state
slaweet Nov 8, 2017
b03e510
Fix account removed reducer to check also network
slaweet Nov 8, 2017
4093148
Add accountSwitched action
slaweet Nov 8, 2017
c438b92
Implement multiple account management
slaweet Nov 8, 2017
097fcd8
Remove saveAccountButton and use route directly
slaweet Nov 8, 2017
0732679
Fix and enable saved accounts e2e tests
slaweet Nov 8, 2017
04921d1
Rename saveAccount to savedAccounts
slaweet Nov 8, 2017
83d92c8
Implement the "Remember last account" feature
slaweet Nov 9, 2017
aab4ee4
Add unit tests for savedAccounts middleware
slaweet Nov 9, 2017
c48244b
Add missing unit test for savedAccounts actions
slaweet Nov 9, 2017
0dbd61c
Fix unit test coverage of SavedAccountsHOC
slaweet Nov 9, 2017
f686b47
Move savedAccounts css from js file
slaweet Nov 9, 2017
3461c07
Improve unit test coverage of SavedAccounts component
slaweet Nov 9, 2017
696d816
Fix save second account e2e test
slaweet Nov 9, 2017
348f38e
Refactor savedAccounts util
slaweet Nov 9, 2017
4170b7b
Remove forgotten console.log
slaweet Nov 10, 2017
d30aa0c
Handle invalid accounts in localStorage
slaweet Nov 10, 2017
0f4ce7b
Add unit tests for savedAccounts utils
slaweet Nov 10, 2017
93e3be3
Unify parameter names of savedAccounts actions
slaweet Nov 10, 2017
72ca0b9
Merge branch '1.3.0' into 573-multi-account-management
slaweet Nov 10, 2017
1f7128b
Make "switch accounts" e2e scenario temporarily pending
slaweet Nov 13, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 0 additions & 49 deletions features/accountManagement.feature

This file was deleted.

82 changes: 82 additions & 0 deletions features/savedAccounts.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
Feature: Saved Accounts
Scenario: should allow to save account locally, after page reload it should require passphrase to do the first transaction, and remember the passphrase for next transactions
Given I'm logged in as "genesis"
When I click "saved accounts" in main menu
And I click "add active account button"
And I click "x button"
And I wait 1 seconds
And I should see text "Account saved" in "toast" element
And I refresh the page
And I wait 2 seconds
Then I should be logged in
And I click "send button"
And I should see empty "passphrase" field
And I fill in "1" to "amount" field
And I fill in "537318935439898807L" to "recipient" field
And I fill in passphrase of "genesis" to "passphrase" field
And I click "submit button"
And I click "ok button"
And I wait 1 seconds
And I click "send button"
And I fill in "2" to "amount" field
And I fill in "537318935439898807L" to "recipient" field
And I click "submit button"
And I should see alert dialog with title "Success" and text "Your transaction of 2 LSK to 537318935439898807L was accepted and will be processed in a few seconds."

Scenario: should allow to save second account
Given I'm logged in as "genesis"
When I click "saved accounts" in main menu
And I click "add active account button"
And I click "x button"
And I wait 1 seconds
And I click "logout button"
And I'm logged in as "empty account"
And I click "saved accounts" in main menu
And I click "add active account button"
Then I should see "saved accounts table" table with 2 lines
And I refresh the page
And I wait 2 seconds
And I should be logged in as "empty account" account

Scenario: should allow to forget second account
Given I'm logged in as "genesis"
When I click "saved accounts" in main menu
And I click "add active account button"
And I click "x button"
And I wait 1 seconds
And I click "logout button"
And I'm logged in as "delegate"
And I click "saved accounts" in main menu
And I click "add active account button"
And I should see "saved accounts table" table with 2 lines
And I click "forget button"
Then I should see "saved accounts table" table with 1 lines

@pending
Scenario: should allow to switch account
Given I'm logged in as "genesis"
When I click "saved accounts" in main menu
And I click "add active account button"
And I click "x button"
And I wait 1 seconds
And I click "logout button"
And I'm logged in as "delegate"
And I click "saved accounts" in main menu
And I click "add active account button"
And I click "switch button"
And I wait 1 seconds
And I should be logged in as "genesis" account

Scenario: should login to last active saved account
Given I'm logged in as "genesis"
When I click "saved accounts" in main menu
And I click "add active account button"
And I click "x button"
And I wait 1 seconds
And I click "logout button"
And I'm logged in as "empty account"
And I click "saved accounts" in main menu
And I click "add active account button"
And I click "x button"
And I refresh the page
And I should be logged in as "empty account" account
16 changes: 15 additions & 1 deletion features/step_definitions/generic.step.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ defineSupportCode(({ Given, When, Then, setDefaultTimeout }) => {
.and.notify(callback);
});

Then('I should see "{elementName}" table with {lineCount} lines', (elementName, lineCount, callback) => {
browser.sleep(500);
expect(element.all(by.css(`table.${elementName.replace(/ /g, '-')} tbody tr`)).count()).to.eventually.equal(parseInt(lineCount, 10))
.and.notify(callback);
});


Then('I should see no "{elementName}"', (elementName, callback) => {
const selector = `.${elementName.replace(/ /g, '-')}`;
waitForElemRemoved(selector).then(() => {
Expand Down Expand Up @@ -139,7 +146,6 @@ defineSupportCode(({ Given, When, Then, setDefaultTimeout }) => {
});

Given('I\'m logged in as "{accountName}"', { timeout: 2 * defaultTimeout }, (accountName, callback) => {
browser.get(browser.params.baseURL);
const passphrase = browser.params.useTestnetPassphrase
? browser.params.testnetPassphrase
: accounts[accountName].passphrase;
Expand Down Expand Up @@ -200,6 +206,14 @@ defineSupportCode(({ Given, When, Then, setDefaultTimeout }) => {
browser.executeScript('window.scrollBy(0, 10000);');
});

Then('I should be logged in', (callback) => {
waitForElemAndCheckItsText('.logout-button', 'LOGOUT', callback);
});

Then('I should be logged in as "{accountName}" account', (accountName, callback) => {
waitForElemAndCheckItsText('.full.address', accounts[accountName].address, callback);
});

When('I click "{itemSelector}" in main menu', (itemSelector, callback) => {
waitForElemAndClickIt('.main-menu-icon-button');
browser.sleep(1000);
Expand Down
1 change: 1 addition & 0 deletions features/step_definitions/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ defineSupportCode(({ Before, After }) => {
localStorage.clear();
localStorage.setItem('address', browser.params.liskCoreURL);
localStorage.setItem('network', networks[browser.params.network].code);
browser.get(browser.params.baseURL);
callback();
});

Expand Down
7 changes: 1 addition & 6 deletions features/step_definitions/login.step.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
/* eslint-disable import/no-extraneous-dependencies */
const { defineSupportCode } = require('cucumber');
const { waitForElemAndCheckItsText } = require('../support/util.js');

defineSupportCode(({ Given, Then }) => {
defineSupportCode(({ Given }) => {
Given('I\'m on login page', (callback) => {
browser.get(browser.params.baseURL).then(callback);
});

Then('I should be logged in', (callback) => {
waitForElemAndCheckItsText('.logout-button', 'LOGOUT', callback);
});
});

9 changes: 6 additions & 3 deletions i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"About": "About",
"Account saved": "Account saved",
"Account was successfully forgotten.": "Account was successfully forgotten.",
"Add active account": "Add active account",
"Add vote to": "Add vote to",
"Address": "Address",
"Address copied to clipboard": "Address copied to clipboard",
Expand All @@ -19,6 +20,7 @@
"Blockchain Application Registration": "Blockchain Application Registration",
"Cancel": "Cancel",
"Click to send all funds": "Click to send all funds",
"Close": "Close",
"Confirm": "Confirm",
"Connection re-established": "Connection re-established",
"Copy": "Copy",
Expand All @@ -42,7 +44,7 @@
"Fee": "Fee",
"Fee: {{amount}} LSK": "Fee: {{amount}} LSK",
"Fee: {{fee}} LSK": "Fee: {{fee}} LSK",
"Forget this account": "Forget this account",
"Forget": "Forget",
"Forging": "Forging",
"From / To": "From / To",
"Help": "Help",
Expand Down Expand Up @@ -73,6 +75,7 @@
"Move your mouse to generate random bytes": "Move your mouse to generate random bytes",
"Multisignature Creation": "Multisignature Creation",
"Name": "Name",
"Network": "Network",
"New Account": "New Account",
"Next": "Next",
"No delegates found": "No delegates found",
Expand Down Expand Up @@ -112,16 +115,15 @@
"Register as delegate": "Register as delegate",
"Register second passphrase": "Register second passphrase",
"Reload": "Reload",
"Remember this account": "Remember this account",
"Remove vote from": "Remove vote from",
"Repeat the transaction": "Repeat the transaction",
"Report Issue...": "Report Issue...",
"Required": "Required",
"Result": "Result",
"Result copied to clipboard": "Result copied to clipboard",
"Reward": "Reward",
"Save account": "Save account",
"Save your passphrase in a safe place": "Save your passphrase in a safe place",
"Saved accounts": "Saved accounts",
"Search": "Search",
"Search by username": "Search by username",
"Second Passphrase": "Second Passphrase",
Expand All @@ -143,6 +145,7 @@
"Signing a message with this tool indicates ownership of a privateKey (secret) and provides a level of proof that you are the owner of the key. Its important to bear in mind that this is not a 100% proof as computer systems can be compromised, but is still an effective tool for proving ownership of a particular publicKey/address pair.": "Signing a message with this tool indicates ownership of a privateKey (secret) and provides a level of proof that you are the owner of the key. Its important to bear in mind that this is not a 100% proof as computer systems can be compromised, but is still an effective tool for proving ownership of a particular publicKey/address pair.",
"Submit": "Submit",
"Success": "Success",
"Switch": "Switch",
"Testnet": "Testnet",
"The URL was invalid": "The URL was invalid",
"There are no transactions, yet.": "There are no transactions, yet.",
Expand Down
47 changes: 32 additions & 15 deletions src/actions/savedAccounts.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,58 @@
import actionTypes from '../constants/actions';
import { getSavedAccount, setSavedAccount, removeSavedAccount } from '../utils/saveAccount';
import {
getSavedAccounts,
setSavedAccount,
removeSavedAccount,
setLastActiveAccount,
getLastActiveAccount,
} from '../utils/savedAccounts';

/**
* An action to dispatch accountSaved
*
*/
export const accountSaved = (data) => {
setSavedAccount(data);
export const accountSaved = (account) => {
setSavedAccount(account);
setLastActiveAccount(account);
return {
data,
data: account,
type: actionTypes.accountSaved,
};
};

/**
* An action to dispatch accountRemoved
*/
export const accountRemoved = (publicKey) => {
removeSavedAccount(publicKey);
export const accountRemoved = (account) => {
removeSavedAccount(account);
return {
data: publicKey,
data: account,
type: actionTypes.accountRemoved,
};
};

/**
* An action to dispatch accountSwitched
*/
export const accountSwitched = (account) => {
setLastActiveAccount(account);
return {
data: account,
type: actionTypes.accountSwitched,
};
};

/**
* The action to initiate savedAccounts store with the retrieved accounts
*
* @todo since the utility returns only one item, this action puts it in the array
* eventually it should receive an array and return that to reducer
*
*/
export const accountsRetrieved = () => {
const accounts = getSavedAccount();
const data = accounts !== undefined ? accounts : [];
return {
data,
type: actionTypes.accountsRetrieved,
};
};
export const accountsRetrieved = () => ({
data: {
accounts: getSavedAccounts(),
lastActive: getLastActiveAccount(),
},
type: actionTypes.accountsRetrieved,
});
24 changes: 21 additions & 3 deletions src/actions/savedAccounts.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { expect } from 'chai';
import sinon from 'sinon';
import actionTypes from '../constants/actions';
import * as saveAccountUtils from '../utils/saveAccount';
import * as savedAccountsUtils from '../utils/savedAccounts';
import {
accountSaved,
accountSwitched,
accountRemoved,
accountsRetrieved,
} from './savedAccounts';
Expand All @@ -18,12 +19,19 @@ describe('actions: savedAccount', () => {

describe('accountsRetrieved', () => {
it('should create an action to retrieved the saved accounts list', () => {
sinon.stub(saveAccountUtils, 'getSavedAccount').returns([data]);
const getSavedAccountsStub = sinon.stub(savedAccountsUtils, 'getSavedAccounts').returns([data]);
const getLastActiveAccountStub = sinon.stub(savedAccountsUtils, 'getLastActiveAccount').returns(data);
const expectedAction = {
data: [data],
data: {
accounts: [data],
lastActive: data,
},
type: actionTypes.accountsRetrieved,
};
expect(accountsRetrieved()).to.be.deep.equal(expectedAction);

getSavedAccountsStub.restore();
getLastActiveAccountStub.restore();
});
});

Expand All @@ -37,6 +45,16 @@ describe('actions: savedAccount', () => {
});
});

describe('accountSwitched', () => {
it('should create an action to save account', () => {
const expectedAction = {
data,
type: actionTypes.accountSwitched,
};
expect(accountSwitched(data)).to.be.deep.equal(expectedAction);
});
});

describe('accountRemoved', () => {
it('should create an action to remove account', () => {
const expectedAction = {
Expand Down
8 changes: 4 additions & 4 deletions src/components/dialog/dialogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import i18next from 'i18next';
import ReceiveDialog from '../receiveDialog';
import Register from '../register';
import RegisterDelegate from '../registerDelegate';
import SaveAccount from '../saveAccount';
import SavedAccounts from '../savedAccounts';
import SecondPassphrase from '../secondPassphrase';
import Send from '../send';
import Settings from '../settings';
Expand Down Expand Up @@ -45,9 +45,9 @@ export default () => ({
title: i18next.t('New Account'),
component: Register,
},
'save-account': {
title: i18next.t('Remember this account'),
component: SaveAccount,
'saved-accounts': {
title: i18next.t('Saved accounts'),
component: SavedAccounts,
},
settings: {
title: i18next.t('Settings'),
Expand Down
Loading