diff --git a/.circleci/config.yml b/.circleci/config.yml index 506f4ab8bb..3d85ad2c73 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,15 +15,15 @@ commands: steps: - restore_cache: keys: - - v5-dependencies-root-{{ checksum "yarn.lock" }} - - v5-dependencies-root- + - v6-dependencies-root-{{ checksum "yarn.lock" }} + - v6-dependencies-root- - run: yarn install - save_cache: paths: - yarn.lock - node_modules - key: v5-dependencies-root-{{ checksum "yarn.lock" }} + key: v6-dependencies-root-{{ checksum "yarn.lock" }} # use npm as the extension install happens in the mashine image which doesn't have yarn npm-install-extension: diff --git a/changes/ana_emoney-sendmodal-multiple-denoms b/changes/ana_emoney-sendmodal-multiple-denoms new file mode 100644 index 0000000000..a851e5f0cc --- /dev/null +++ b/changes/ana_emoney-sendmodal-multiple-denoms @@ -0,0 +1 @@ +[Added] [#3370](https://github.com/cosmos/lunie/pull/3370) Now it is possible to select different tokens to send in the SendModal for networks with multiple tokens @Bitcoinera \ No newline at end of file diff --git a/package.json b/package.json index 5f9e208775..3bef70e9ae 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "stylelint": "12.0.1", "stylelint-config-standard": "19.0.0", "stylelint-webpack-plugin": "1.1.2", + "vue-jest": "3.0.5", "vue-template-compiler": "^2.6.10", "webpack": "4.41.5" }, @@ -140,4 +141,4 @@ "type": "git", "url": "git+https://github.com/luniehq/lunie.git" } -} \ No newline at end of file +} diff --git a/src/ActionModal/components/SendModal.vue b/src/ActionModal/components/SendModal.vue index d66e73f304..773c3fd3b1 100644 --- a/src/ActionModal/components/SendModal.vue +++ b/src/ActionModal/components/SendModal.vue @@ -38,12 +38,36 @@ /> + + + + - {{ denom }} + {{ + selectedToken + }} @@ -149,8 +173,8 @@ export default { TmBtn }, props: { - denom: { - type: String, + denoms: { + type: Array, required: true } }, @@ -160,22 +184,29 @@ export default { memo: defaultMemo, max_memo_characters: 256, editMemo: false, - balance: { - amount: null, - denom: `` - } + isFirstLoad: true, + selectedToken: ``, + selectedBalance: ``, + balances: [ + { + amount: null, + denom: `` + } + ] }), computed: { ...mapGetters([`network`]), ...mapGetters({ userAddress: `address` }), transactionData() { + // This is the best place I have found so far to call this function + this.setTokenAndBalance() return { type: transaction.SEND, toAddress: this.address, amounts: [ { amount: uatoms(+this.amount), - denom: toMicroDenom(this.denom) + denom: toMicroDenom(this.selectedToken) } ], memo: this.memo @@ -184,10 +215,24 @@ export default { notifyMessage() { return { title: `Successful Send`, - body: `Successfully sent ${+this.amount} ${this.denom}s to ${ + body: `Successfully sent ${+this.amount} ${this.selectedToken}s to ${ this.address }` } + }, + getDenoms() { + return this.denoms.map(denom => (denom = { key: denom, value: denom })) + } + }, + watch: { + // we set the amount in the input to zero every time the user selects another token so they + // realize they are dealing with a different balance each time + selectedToken: function() { + if (!this.isFirstLoad) { + this.amount = 0 + } else { + this.isFirstLoad = false + } } }, mounted() { @@ -215,15 +260,36 @@ export default { this.sending = false }, setMaxAmount() { - this.amount = this.balance.amount + this.amount = this.selectedBalance.amount }, isMaxAmount() { - if (this.balance.amount === 0) { + if (this.selectedBalance.amount === 0) { return false } else { - return parseFloat(this.amount) === parseFloat(this.balance.amount) + return ( + parseFloat(this.amount) === parseFloat(this.selectedBalance.amount) + ) } }, + setTokenAndBalance() { + // if it is single-token network, then we take the first and only value from the + // balances array + if (this.balances.length === 1) { + this.selectedToken = this.getDenoms[0].value + this.selectedBalance = this.balances[0] + // if it is a multiple-tokens network and we already have a selectedToken by the user + // then we search for the corresponding balance from the array + } else if (this.selectedToken) { + this.selectedBalance = this.balances.filter( + balance => balance.denom === this.selectedToken + )[0] + } + }, + token() { + if (!this.selectedToken) return `` + + return this.selectedToken + }, bech32Validate(param) { try { b32.decode(param) @@ -252,23 +318,20 @@ export default { amount: { required: x => !!x && x !== `0`, decimal, - between: between(SMALLEST, this.balance.amount) + between: between(SMALLEST, this.selectedBalance.amount) }, - denom: { required }, + denoms: { required }, + selectedToken: { required }, memo: { maxLength: maxLength(this.max_memo_characters) } } }, apollo: { - balance: { + balances: { query: gql` - query BalanceSendModal( - $networkId: String! - $address: String! - $denom: String! - ) { - balance(networkId: $networkId, address: $address, denom: $denom) { + query BalancesSendModal($networkId: String!, $address: String!) { + balances(networkId: $networkId, address: $address) { amount denom } @@ -280,8 +343,7 @@ export default { variables() { return { networkId: this.network, - address: this.userAddress, - denom: this.denom + address: this.userAddress } } }, @@ -314,4 +376,8 @@ export default { font-size: 12px; cursor: pointer; } + +#form-group-amount { + margin-bottom: 30px; +} diff --git a/src/components/common/TmBalance.vue b/src/components/common/TmBalance.vue index 57000d20e3..9ccaf6a2da 100644 --- a/src/components/common/TmBalance.vue +++ b/src/components/common/TmBalance.vue @@ -46,7 +46,7 @@ /> - + @@ -82,6 +82,14 @@ export default { // the validator rewards are needed to filter the top 5 validators to withdraw from readyToWithdraw() { return this.overview.totalRewards > 0 + }, + getAllDenoms() { + if (this.overview.balances) { + const balances = this.overview.balances + return balances.map(({ denom }) => denom) + } else { + return [this.stakingDenom] + } } }, methods: { @@ -99,6 +107,10 @@ export default { overview(networkId: $networkId, address: $address) { totalRewards liquidStake + balances { + denom + amount + } totalStake } } diff --git a/tests/unit/specs/components/ActionModal/components/SendModal.spec.js b/tests/unit/specs/components/ActionModal/components/SendModal.spec.js index c7b85d8368..d270b8b03d 100644 --- a/tests/unit/specs/components/ActionModal/components/SendModal.spec.js +++ b/tests/unit/specs/components/ActionModal/components/SendModal.spec.js @@ -37,16 +37,18 @@ describe(`SendModal`, () => { } }, propsData: { - denom: "STAKE" + denoms: ["STAKE"] }, sync: false }) wrapper.setData({ - balance: { - denom: `STAKE`, - amount: 10000 - } + balances: [ + { + denom: `STAKE`, + amount: 10000 + } + ] }) wrapper.vm.$refs.actionModal = { @@ -211,10 +213,12 @@ describe(`SendModal`, () => { }) it(`should not show warning message if balance = 0`, async () => { wrapper.setData({ - balance: { - amount: 0, - denom: "STAKE" - } + balances: [ + { + amount: 0, + denom: "STAKE" + } + ] }) wrapper.vm.setMaxAmount() await wrapper.vm.$nextTick() @@ -224,14 +228,49 @@ describe(`SendModal`, () => { }) it(`isMaxAmount() should return false if balance = 0`, async () => { wrapper.setData({ - balance: { - amount: 0, - denom: "STAKE" - } + balances: [ + { + amount: 0, + denom: "STAKE" + } + ] }) wrapper.vm.setMaxAmount() await wrapper.vm.$nextTick() expect(wrapper.vm.isMaxAmount()).toBe(false) }) }) + + describe(`Set token and balance`, () => { + it(`it takes the corresponding balance from the balances array when + selectedToken has been chosen and balances has a length over 1`, async () => { + wrapper.setData({ + selectedToken: `TOKEN1`, + balances: [ + { + amount: 1, + denom: "TOKEN1" + }, + { + amount: 2, + denom: "TOKEN2" + } + ] + }) + wrapper.vm.setTokenAndBalance() + expect(wrapper.vm.selectedBalance.amount).toBe(1) + }) + // This one creates a lot of ugly errors + // it(`returns empty string if selectedToken hasn't been chosen yet`, () => { + // wrapper.setData({ + // balances: [] + // }) + // const res = wrapper.vm.token() + // expect(res).toBe("") + // }) + it(`returns selectedToken if selectedToken has been chosen`, () => { + const res = wrapper.vm.token() + expect(res).toBe("STAKE") + }) + }) }) diff --git a/tests/unit/specs/components/ActionModal/components/__snapshots__/SendModal.spec.js.snap b/tests/unit/specs/components/ActionModal/components/__snapshots__/SendModal.spec.js.snap index 93aa332f8e..38a2899be1 100644 --- a/tests/unit/specs/components/ActionModal/components/__snapshots__/SendModal.spec.js.snap +++ b/tests/unit/specs/components/ActionModal/components/__snapshots__/SendModal.spec.js.snap @@ -26,10 +26,13 @@ exports[`SendModal should display send modal form 1`] = ` + + + + + + + + + + @@ -129,7 +129,7 @@ exports[`TmBalance show the balance header when signed in 1`] = ` diff --git a/yarn.lock b/yarn.lock index 150fadc59e..43d36d2876 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12632,7 +12632,7 @@ vue-infinite-scroll@^2.0.2: resolved "https://registry.yarnpkg.com/vue-infinite-scroll/-/vue-infinite-scroll-2.0.2.tgz#ca37a91fe92ee0ad3b74acf8682c00917144b711" integrity sha512-n+YghR059YmciANGJh9SsNWRi1YZEBVlODtmnb/12zI+4R72QZSWd+EuZ5mW6auEo/yaJXgxzwsuhvALVnm73A== -vue-jest@^3.0.5: +vue-jest@3.0.5, vue-jest@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-3.0.5.tgz#d6f124b542dcbff207bf9296c19413f4c40b70c9" integrity sha512-xWDxde91pDqYBGDlODENZ3ezPgw+IQFoVDtf+5Awlg466w3KvMSqWzs8PxcTeTr+wmAHi0j+a+Lm3R7aUJa1jA==