@@ -85,9 +51,10 @@ export function PurchasePage({
availableVSPs,
account,
setAccount,
- numTickets,
+ numTicketsToBuy,
+ onIncrementNumTickets,
+ onDecrementNumTickets,
onChangeNumTickets,
- setNumTickets,
handleOnKeyDown,
ticketPrice,
setVSP,
@@ -113,11 +80,7 @@ export function PurchasePage({
{!isVSPListingEnabled &&
}
-
}
- className="flex-row">
-
-
+
} />
{mixedAccount && changeAccount &&
}
{spvMode && blocksNumberToNextTicket === 2 ? (
) : (
-
)}
diff --git a/app/components/views/TicketsPage/PurchaseTab/PurchaseTickets/PurchaseForm.jsx b/app/components/views/TicketsPage/PurchaseTab/PurchaseTickets/PurchaseForm.jsx
deleted file mode 100644
index 2455ecc22a..0000000000
--- a/app/components/views/TicketsPage/PurchaseTab/PurchaseTickets/PurchaseForm.jsx
+++ /dev/null
@@ -1,181 +0,0 @@
-import { FormattedMessage as T } from "react-intl";
-import { classNames, Checkbox, Tooltip } from "pi-ui";
-import { PassphraseModalButton, KeyBlueButton } from "buttons";
-import { AccountsSelect, NumTicketsInput, VSPSelect } from "inputs";
-import { Balance } from "shared";
-import styles from "../PurchaseTab.module.css";
-
-const purchaseLabel = () =>
;
-
-const PurchaseTicketsForm = ({
- isValid,
- handleOnKeyDown,
- numTickets,
- onChangeNumTickets,
- setNumTickets,
- setAccount,
- account,
- ticketPrice,
- isWatchingOnly,
- setVSP,
- vsp,
- vspFee,
- setVspFee,
- onV3PurchaseTicket,
- onRevokeTickets,
- availableVSPs,
- isLoading,
- rememberedVspHost,
- toggleRememberVspHostCheckBox,
- notMixedAccounts,
- getRunningIndicator
-}) => (
- <>
-
-
-
-
-
-
-
-
- {vsp && (
-
}
- className={styles.ticketPoolFee}>
-
- {vspFee} %
-
-
- )}
-
- {vsp && (
-
- }
- id="rememberVspHost"
- checked={!!rememberedVspHost}
- onChange={toggleRememberVspHostCheckBox}
- />
- )}
-
-
-
-
-
- }
- numTickets={numTickets}
- incrementNumTickets={() => onChangeNumTickets(true)}
- decrementNumTickets={() => onChangeNumTickets(false)}
- onChangeNumTickets={setNumTickets}
- onKeyDown={handleOnKeyDown}
- showErrors={true}
- />
- {account.spendable >= numTickets * ticketPrice && (
-
- ,
- remaining: (
-
- )
- }}
- />
-
- )}
-
-
-
-
- {/* ADD VSP INFO HERE */}
-
-
-
- }
- className={styles.revokeButton}
- onSubmit={onRevokeTickets}
- buttonLabel={
}
- />
- {isWatchingOnly ? (
-
- {purchaseLabel()}
-
- ) : isLoading ? (
-
- ) : getRunningIndicator ? (
-
- }>
-
-
- ) : (
-
- }
- disabled={!isValid}
- onSubmit={onV3PurchaseTicket}
- buttonLabel={purchaseLabel()}
- />
- )}
-
- >
-);
-
-export default PurchaseTicketsForm;
diff --git a/app/components/views/TicketsPage/PurchaseTab/PurchaseTickets/PurchaseTickets.jsx b/app/components/views/TicketsPage/PurchaseTab/PurchaseTickets/PurchaseTickets.jsx
index 8fe1e8d5b7..376bfa4043 100644
--- a/app/components/views/TicketsPage/PurchaseTab/PurchaseTickets/PurchaseTickets.jsx
+++ b/app/components/views/TicketsPage/PurchaseTab/PurchaseTickets/PurchaseTickets.jsx
@@ -1,6 +1,7 @@
import { useEffect, useState } from "react";
import { PurchasePage } from "./Page";
import { usePurchaseTab } from "../hooks";
+import { isEqual } from "lodash/fp";
const Tickets = ({ toggleIsLegacy }) => {
const {
@@ -25,49 +26,69 @@ const Tickets = ({ toggleIsLegacy }) => {
notMixedAccounts,
isVSPListingEnabled,
onEnableVSPListing,
- getRunningIndicator
+ getRunningIndicator,
+ visibleAccounts
} = usePurchaseTab();
const [account, setAccount] = useState(defaultSpendingAccount);
+ useEffect(() => {
+ const newAccount = visibleAccounts?.find((a) =>
+ isEqual(a.value, account?.value)
+ );
+ newAccount && setAccount(newAccount);
+ }, [visibleAccounts, account]);
+
// todo use this vsp to buy solo tickets.
const [vsp, setVSP] = useState(
rememberedVspHost ? { host: rememberedVspHost.host } : null
);
- const [numTickets, setNumTickets] = useState(1);
+ const [numTicketsToBuy, setNumTicketsToBuy] = useState(1);
const [isValid, setIsValid] = useState(false);
const toggleRememberVspHostCheckBox = () => {
setRememberedVspHost(!rememberedVspHost ? vsp : null);
};
- // onChangeNumTickets deals with ticket increment or decrement.
- const onChangeNumTickets = (increment) => {
- if (numTickets === 0 && !increment) return;
- increment
- ? setNumTickets(parseInt(numTickets) + 1)
- : setNumTickets(parseInt(numTickets) - 1);
+ const handleOnKeyDown = (e) => {
+ if (e.keyCode == 38) {
+ e.preventDefault();
+ onIncrementNumTickets();
+ } else if (e.keyCode == 40) {
+ e.preventDefault();
+ onDecrementNumTickets();
+ }
+ };
+
+ const onIncrementNumTickets = () => {
+ setNumTicketsToBuy((numTicketsToBuy) =>
+ numTicketsToBuy == "" ? 1 : numTicketsToBuy + 1
+ );
+ };
+
+ const onChangeNumTickets = (numTicketsToBuy) => {
+ if (parseInt(numTicketsToBuy)) {
+ setNumTicketsToBuy(parseInt(numTicketsToBuy));
+ } else if (numTicketsToBuy == "") {
+ setNumTicketsToBuy(numTicketsToBuy);
+ }
+ };
+
+ const onDecrementNumTickets = () => {
+ setNumTicketsToBuy((numTicketsToBuy) =>
+ numTicketsToBuy <= 1 ? 1 : numTicketsToBuy - 1
+ );
};
const onV3PurchaseTicket = (passphrase) => {
- onPurchaseTicketV3(passphrase, account, numTickets, vsp);
+ onPurchaseTicketV3(passphrase, account, numTicketsToBuy, vsp);
};
useEffect(() => {
const { spendable } = account;
- const canAfford = numTickets * ticketPrice <= spendable;
- const hasTickets = numTickets > 0;
+ const canAfford = numTicketsToBuy * ticketPrice <= spendable;
+ const hasTickets = numTicketsToBuy > 0;
setIsValid(canAfford && hasTickets && !!vsp);
- }, [ticketPrice, numTickets, account, vsp]);
-
- const handleOnKeyDown = (e) => {
- if (e.keyCode == 38) {
- e.preventDefault();
- onChangeNumTickets(true);
- } else if (e.keyCode == 40) {
- e.preventDefault();
- onChangeNumTickets(false);
- }
- };
+ }, [ticketPrice, numTicketsToBuy, account, vsp]);
const [vspFee, setVspFee] = useState(
availableVSPs &&
@@ -83,9 +104,10 @@ const Tickets = ({ toggleIsLegacy }) => {
sidebarOnBottom,
isWatchingOnly,
account,
- numTickets,
+ numTicketsToBuy,
+ onIncrementNumTickets,
+ onDecrementNumTickets,
onChangeNumTickets,
- setNumTickets,
handleOnKeyDown,
setAccount,
ticketPrice,
diff --git a/app/components/views/TicketsPage/PurchaseTab/StakeInfo/StakeInfo.module.css b/app/components/views/TicketsPage/PurchaseTab/StakeInfo/StakeInfo.module.css
index 47e9c2afa8..24d89eccfb 100644
--- a/app/components/views/TicketsPage/PurchaseTab/StakeInfo/StakeInfo.module.css
+++ b/app/components/views/TicketsPage/PurchaseTab/StakeInfo/StakeInfo.module.css
@@ -1,39 +1,29 @@
.accordion {
- margin-bottom: 40px;
+ margin-bottom: 30px;
}
.accordionArrow {
top: 15px;
right: 20px;
height: 13px;
+ transform: rotate(90deg);
+}
+.activeAccordionArrow {
+ transform: rotate(-90deg) !important;
}
.area {
background-color: var(--background-back-color);
- padding: 16px 19px 16px 19px;
+ padding: 30px 40px 20px 0;
position: relative;
display: grid;
- grid-template-areas: "purchaseTicketsTitle purchaseTicketsTitle purchaseTicketsTitle purchaseTicketsTitle";
-}
-
-.purchaseTicketTitle {
- box-sizing: content-box;
- grid-area: purchaseTicketsTitle;
- font-size: 15px;
+ grid-template-columns: repeat(4, minmax(max-content, 1fr));
}
.purchaseTicketFoot {
border-bottom: 1px var(--tx-detail-text) solid;
}
-.purchaseTicketLabel {
- font-size: 13px;
-}
-
-.purchaseTicketCount {
- font-size: 17px;
-}
-
.balance {
display: inline;
}
@@ -43,36 +33,36 @@
}
.details {
- height: 70px;
+ height: 100px;
display: grid;
- grid-template-columns: 1.2fr repeat(3, 1fr);
- grid-template-rows: 22px 22px;
+ grid-template-columns: minmax(max-content, 1fr) minmax(max-content, 1fr) auto;
row-gap: 11px;
- box-sizing: content-box;
- padding-top: 12px;
- margin-left: 19px;
- margin-right: 19px;
+ padding: 24px 0 30px 0;
+ margin: 0 40px;
background-color: var(--background-back-color);
- border-top: 1px solid;
- border-color: var(--input-placeholder-color);
+ border-top: 1px solid var(--stakeinfo-border);
}
.stakeInfo {
- display: grid;
- grid-template-rows: 15px 24px 15px;
+ display: flex;
+ text-align: center;
+ flex-direction: column;
}
.label {
vertical-align: middle;
- line-height: 15px;
- font-size: 11px;
+ line-height: 16px;
+ font-size: 13px;
+ color: var(--purchase-label);
+ margin-bottom: 10px;
}
.value {
vertical-align: middle;
- line-height: 24px;
- font-size: 17px;
+ line-height: 22px;
+ font-size: 18px;
font-weight: 600;
+ color: var(--stakeinfo-value);
}
.foot {
@@ -84,22 +74,26 @@
}
.detail {
- margin-bottom: 10px;
- display: inline-grid;
- grid-template-columns: 22px auto 1fr;
- vertical-align: middle;
line-height: 22px;
font-size: 13px;
min-width: 0;
+ display: flex;
+ flex-direction: row;
}
.detailIcon {
background-repeat: no-repeat;
+ width: 20px;
+ height: 20px;
+}
+
+.detail:nth-child(3),
+.detail:nth-child(6) {
+ justify-content: flex-end;
}
.detailLabel {
- display: block;
- padding-left: 6px;
+ padding-left: 11px;
}
.detailValue {
@@ -135,45 +129,30 @@
background-image: var(--ticket-voted);
}
-@media screen and (max-width: 1179px) {
- .details {
- height: 85px;
- row-gap: 25px;
- }
-
- .detail {
- grid-template-columns: 22px auto 1fr;
- }
-}
-
@media screen and (max-width: 768px) {
.accordion {
margin-bottom: 30px;
}
.area {
- grid-template-areas:
- "purchaseTicketsTitle purchaseTicketsTitle"
- "purchaseTicketsTitle purchaseTicketsTitle";
- grid-template-rows: 71px 60px;
+ grid-template-columns: repeat(2, minmax(max-content, 1fr));
+ grid-gap: 20px;
+ padding: 30px 20px;
}
-
.details {
- height: 130px;
- row-gap: 25px;
grid-template-columns: repeat(2, 1fr);
- grid-template-rows: 22px 22px 22px;
+ margin: 0 15px 0 10px;
+ grid-gap: 12px;
+ height: 135px;
}
-
- .detail {
- grid-template-columns: 22px auto 1fr;
+ .detail:nth-child(3),
+ .detail:nth-child(6) {
+ justify-content: flex-start;
}
-
- .details .detail:first-child .detailLabel {
- width: 80px;
+ .detail:nth-child(even) {
+ justify-content: flex-end;
}
-
- .purchaseTicketLabel {
- font-size: 13px;
+ .detailLabel {
+ padding-left: 2px;
}
}
diff --git a/app/components/views/TicketsPage/PurchaseTab/StakeInfo/StakeInfoDetails.jsx b/app/components/views/TicketsPage/PurchaseTab/StakeInfo/StakeInfoDetails.jsx
index 9b6558fa81..f2b20e53b0 100644
--- a/app/components/views/TicketsPage/PurchaseTab/StakeInfo/StakeInfoDetails.jsx
+++ b/app/components/views/TicketsPage/PurchaseTab/StakeInfo/StakeInfoDetails.jsx
@@ -1,11 +1,12 @@
import { FormattedNumber, FormattedMessage as T } from "react-intl";
import styles from "./StakeInfo.module.css";
+import { classNames } from "pi-ui";
const StakeInfoTicketCount = ({ className, label, value }) => (
-
-
{label}
-
{value}
+
+
{label}
+
{value}
);
@@ -24,26 +25,26 @@ const StakeInfoDisplay = ({
label={
}
value={
}
/>
-
}
- value={
}
- />
}
value={
}
/>
-
}
- value={
}
- />
}
value={
}
/>
+
}
+ value={
}
+ />
+
}
+ value={
}
+ />
}
diff --git a/app/components/views/TicketsPage/PurchaseTab/StakeInfo/StakeInfoDisplay.jsx b/app/components/views/TicketsPage/PurchaseTab/StakeInfo/StakeInfoDisplay.jsx
index c193172bfb..cf361168ba 100644
--- a/app/components/views/TicketsPage/PurchaseTab/StakeInfo/StakeInfoDisplay.jsx
+++ b/app/components/views/TicketsPage/PurchaseTab/StakeInfo/StakeInfoDisplay.jsx
@@ -18,20 +18,7 @@ const StakeInfoDisplayItem = ({ label, value, foot }) => (
);
const StakeInfoDisplayTicketCount = ({ value }) => (
-
-
-
-
- ),
- tickets: value
- }}
- />
-
+
);
const StakeInfoDisplay = ({
@@ -147,7 +134,8 @@ const StakeInfoDisplay = ({
show={isShowingDetails}
onToggleAccordion={onToggleStakeinfo}
className={styles.accordion}
- arrowClassName={styles.accordionArrow}>
+ arrowClassName={styles.accordionArrow}
+ activeArrowClassName={styles.activeAccordionArrow}>
(
- <>
- }
- />
-
-
- {isRunning ? (
-
- ) : getRunningIndicator ? (
-
- }>
-
-
- ) : (
-
- }
- modalDescription={
-
-
-
-
-
-
-
-
-
- :
-
-
{vsp && vsp.host}
-
-
-
- }
- onSubmit={onStartAutoBuyer}
- onClick={onClick}
- isValid={isValid}
- />
- )}
-
-
-
-
-
-
-
-
- }
- showErrors
- />
-
-
-
- {clicked && isValid === false && (
-
-
-
- )}
-
- >
-);
-
-export default TicketAutoBuyerForm;
diff --git a/app/components/views/TicketsPage/PurchaseTab/TicketAutoBuyer/TicketAutoBuyer.jsx b/app/components/views/TicketsPage/PurchaseTab/TicketAutoBuyer/TicketAutoBuyer.jsx
index 6c8d194ab1..2ca966c667 100644
--- a/app/components/views/TicketsPage/PurchaseTab/TicketAutoBuyer/TicketAutoBuyer.jsx
+++ b/app/components/views/TicketsPage/PurchaseTab/TicketAutoBuyer/TicketAutoBuyer.jsx
@@ -1,90 +1,60 @@
-import { injectIntl } from "react-intl";
-import TicketAutoBuyerForm from "./Form";
-import { useState, useEffect } from "react";
-import { usePurchaseTab } from "../hooks";
+import { TicketAutoBuyerForm } from "shared";
+import { useTicketAutoBuyer } from "./hooks";
+import { VSPSelect } from "inputs";
-function TicketAutoBuyer({ intl }) {
+function TicketAutoBuyer() {
const {
+ balanceToMaintain,
+ setBalanceToMaintain,
+ account,
+ setAccount,
+ vsp,
+ setVsp,
availableVSPs,
- onEnableTicketAutoBuyer,
- onDisableTicketAutoBuyer,
- ticketAutoBuyerRunning,
- buyerAccount,
- buyerBalanceToMantain,
- buyerVSP,
+ isRunning,
notMixedAccounts,
- getRunningIndicator
- } = usePurchaseTab();
- const [balanceToMaintain, setBalanceToMaintain] = useState(
- buyerBalanceToMantain
- );
- const [account, setAccount] = useState(buyerAccount);
- const [vsp, setVSP] = useState(buyerVSP);
- // we use this bool flag so the error does not show before trying.
- const [clicked, setClicked] = useState(false);
- // isValid check if we can show the modal to start the auto buyer.
- const [isValid, setIsValid] = useState(null);
-
- const onHandleBalanceToMantain = (e) => setBalanceToMaintain(e.atomValue);
-
- const onStartAutoBuyer = (passphrase) => {
- onEnableTicketAutoBuyer(passphrase, account, balanceToMaintain, vsp);
- };
-
- const checkIsValid = (vsp, balanceToMaintain, account) => {
- let isValid = true;
- if (vsp) {
- if (!vsp.pubkey || !vsp.host) {
- isValid = false;
- }
- } else {
- isValid = false;
- }
-
- // balance to mantain can be 0.
- return (
- isValid && (!!balanceToMaintain || balanceToMaintain === 0) && !!account
- );
- };
-
- const onClick = () => {
- setClicked(true);
- setIsValid(checkIsValid(vsp, balanceToMaintain, account));
- };
-
- const onStopAutoBuyer = () => onDisableTicketAutoBuyer();
-
- useEffect(
- // we pass those values as parameter, so we don't need to add checkIsValid
- // into the dependecy array.
- () => {
- setIsValid(checkIsValid(vsp, balanceToMaintain, account));
- },
- [vsp, balanceToMaintain, account]
+ getRunningIndicator,
+ clicked,
+ isValid,
+ isSettingsModalVisible,
+ showSettingsModal,
+ hideSettingsModal,
+ onClick,
+ onStartAutoBuyer,
+ onStopAutoBuyer,
+ onSaveAutoBuyerSettings,
+ vspHost
+ } = useTicketAutoBuyer();
+
+ const VSPSelectControl = (
+
);
return (
);
}
-export default injectIntl(TicketAutoBuyer);
+export default TicketAutoBuyer;
diff --git a/app/components/views/TicketsPage/PurchaseTab/TicketAutoBuyer/hooks.js b/app/components/views/TicketsPage/PurchaseTab/TicketAutoBuyer/hooks.js
new file mode 100644
index 0000000000..9a3fb49ea4
--- /dev/null
+++ b/app/components/views/TicketsPage/PurchaseTab/TicketAutoBuyer/hooks.js
@@ -0,0 +1,135 @@
+import { useSelector, useDispatch } from "react-redux";
+import { useState, useCallback, useEffect } from "react";
+import * as vspa from "actions/VSPActions";
+import * as ca from "actions/ControlActions";
+import * as sel from "selectors";
+
+export const useTicketAutoBuyer = () => {
+ const availableVSPs = useSelector(sel.getAvailableVSPs);
+ const isRunning = useSelector(sel.getTicketAutoBuyerRunning);
+
+ const buyerBalanceToMaintain = useSelector(sel.buyerBalanceToMaintain);
+ const buyerAccount = useSelector(sel.buyerAccount);
+ const buyerVSP = useSelector(sel.buyerVSP);
+
+ const [balanceToMaintain, setBalanceToMaintain] = useState(
+ buyerBalanceToMaintain
+ );
+ const [account, setAccount] = useState(buyerAccount);
+ const [vsp, setVsp] = useState(buyerVSP);
+
+ const notMixedAccounts = useSelector(sel.getNotMixedAccounts);
+ // isValid check if we can show the modal to start the auto buyer.
+ const [isValid, setIsValid] = useState(null);
+
+ const [isSettingsModalVisible, setIsSettingsModalVisible] = useState(false);
+ const showSettingsModal = () => setIsSettingsModalVisible(true);
+ const hideSettingsModal = () => {
+ resetSettingsState();
+ setIsSettingsModalVisible(false);
+ };
+ // we use this bool flag so the error does not show before trying.
+ const [clicked, setClicked] = useState(false);
+ const onStartAutoBuyer = (passphrase) => {
+ onEnableTicketAutoBuyer(
+ passphrase,
+ account,
+ balanceToMaintain?.atomValue,
+ vsp
+ );
+ };
+
+ const checkIsValid = (vsp, balanceToMaintain, account) => {
+ let isValid = true;
+ if (vsp) {
+ if (!vsp.pubkey || !vsp.host) {
+ isValid = false;
+ }
+ } else {
+ isValid = false;
+ }
+ // balance to mantain can be 0.
+ return isValid && balanceToMaintain?.atomValue >= 0 && !!account;
+ };
+
+ const onClick = () => {
+ setIsValid(checkIsValid(vsp, balanceToMaintain, account));
+ if (!isValid) {
+ setIsSettingsModalVisible(true);
+ }
+ };
+
+ useEffect(
+ // we pass those values as parameter, so we don't need to add checkIsValid
+ // into the dependecy array.
+ () => {
+ setIsValid(checkIsValid(vsp, balanceToMaintain, account));
+ },
+ [vsp, balanceToMaintain, account]
+ );
+ const getRunningIndicator = useSelector(sel.getRunningIndicator);
+
+ const resetSettingsState = () => {
+ setBalanceToMaintain(buyerBalanceToMaintain);
+ setAccount(buyerAccount);
+ setVsp(buyerVSP);
+ setClicked(false);
+ };
+ const vspHost = vsp && vsp.host;
+
+ const dispatch = useDispatch();
+ const onEnableTicketAutoBuyer = useCallback(
+ (passphrase, account, balanceToMaintain, vsp) =>
+ dispatch(
+ ca.startTicketBuyerV3Attempt(
+ passphrase,
+ account,
+ balanceToMaintain,
+ vsp
+ )
+ ),
+ [dispatch]
+ );
+
+ const onStopAutoBuyer = useCallback(() => dispatch(ca.ticketBuyerCancel()), [
+ dispatch
+ ]);
+
+ const onSaveAutoBuyerSettings = (balanceToMaintain, account, vsp) => {
+ setIsValid(checkIsValid(vsp, balanceToMaintain, account));
+ if (!isValid) {
+ setClicked(true);
+ } else {
+ dispatch(
+ vspa.saveAutoBuyerSettings({
+ balanceToMaintain,
+ account,
+ vsp
+ })
+ );
+ setIsSettingsModalVisible(false);
+ }
+ };
+ return {
+ balanceToMaintain,
+ setBalanceToMaintain,
+ account,
+ setAccount,
+ vsp,
+ setVsp,
+ availableVSPs,
+ isRunning,
+ notMixedAccounts,
+ getRunningIndicator,
+ clicked,
+ isValid,
+ isSettingsModalVisible,
+ showSettingsModal,
+ hideSettingsModal,
+ onClick,
+ onStartAutoBuyer,
+ onStopAutoBuyer,
+ onSaveAutoBuyerSettings,
+ vspHost
+ };
+};
diff --git a/app/components/views/TicketsPage/PurchaseTab/TicketAutoBuyer/index.js b/app/components/views/TicketsPage/PurchaseTab/TicketAutoBuyer/index.js
new file mode 100644
index 0000000000..d86648f3ab
--- /dev/null
+++ b/app/components/views/TicketsPage/PurchaseTab/TicketAutoBuyer/index.js
@@ -0,0 +1 @@
+export { default } from "./TicketAutoBuyer";
diff --git a/app/components/views/TicketsPage/PurchaseTab/hooks.js b/app/components/views/TicketsPage/PurchaseTab/hooks.js
index 2f1a3eb81d..1f3d6d5a1a 100644
--- a/app/components/views/TicketsPage/PurchaseTab/hooks.js
+++ b/app/components/views/TicketsPage/PurchaseTab/hooks.js
@@ -25,9 +25,10 @@ export const usePurchaseTab = () => {
const notMixedAccounts = useSelector(sel.getNotMixedAccounts);
const buyerVSP = useSelector(sel.buyerVSP);
- const buyerBalanceToMantain = useSelector(sel.buyerBalanceToMantain);
+ const buyerBalanceToMaintain = useSelector(sel.buyerBalanceToMaintain);
const buyerAccount = useSelector(sel.buyerAccount);
const rememberedVspHost = useSelector(sel.getRememberedVspHost);
+ const visibleAccounts = useSelector(sel.visibleAccounts);
// VSP listing checks
const { onAddAllowedRequestType, isVSPListingEnabled } = useSettings();
@@ -120,7 +121,7 @@ export const usePurchaseTab = () => {
ticketAutoBuyerRunning,
buyerVSP,
buyerAccount,
- buyerBalanceToMantain,
+ buyerBalanceToMaintain,
getVSPTicketsByFeeStatus,
isLegacy,
toggleIsLegacy,
@@ -134,6 +135,7 @@ export const usePurchaseTab = () => {
isVSPListingEnabled,
onEnableVSPListing,
onListUnspentOutputs,
- getRunningIndicator
+ getRunningIndicator,
+ visibleAccounts
};
};
diff --git a/app/reducers/control.js b/app/reducers/control.js
index 31fb6ca61b..4a05269be3 100644
--- a/app/reducers/control.js
+++ b/app/reducers/control.js
@@ -71,7 +71,8 @@ import {
GETACCOUNTEXTENDEDKEY_FAILED,
GETACCOUNTEXTENDEDKEY_SUCCESS,
HIDE_CANTCLOSE_MODAL,
- SHOW_CANTCLOSE_MODAL
+ SHOW_CANTCLOSE_MODAL,
+ SAVE_LEGACY_AUTOBUYER_SETTINGS
} from "../actions/ControlActions";
import { WALLET_AUTOBUYER_SETTINGS } from "actions/DaemonActions";
import { CLOSEWALLET_SUCCESS } from "actions/WalletLoaderActions";
@@ -526,6 +527,13 @@ export default function control(state = {}, action) {
};
case CLOSEWALLET_SUCCESS:
return { ...state, changeScriptByAccount: {} };
+ case SAVE_LEGACY_AUTOBUYER_SETTINGS:
+ return {
+ ...state,
+ legacyBalanceToMaintain: action.balanceToMaintain,
+ legacyAccount: action.account,
+ legacyVsp: action.vsp
+ };
default:
return state;
}
diff --git a/app/reducers/vsp.js b/app/reducers/vsp.js
index f2d8aa3779..7a30a238b6 100644
--- a/app/reducers/vsp.js
+++ b/app/reducers/vsp.js
@@ -16,7 +16,8 @@ import {
PROCESSUNMANAGEDTICKETS_FAILED,
SETVSPDVOTECHOICE_ATTEMPT,
SETVSPDVOTECHOICE_FAILED,
- SETVSPDVOTECHOICE_SUCCESS
+ SETVSPDVOTECHOICE_SUCCESS,
+ SAVE_AUTOBUYER_SETTINGS
} from "actions/VSPActions";
import {
STARTTICKETBUYERV3_ATTEMPT,
@@ -138,6 +139,13 @@ export default function vsp(state = {}, action) {
...state,
setVspdVoteChoicesRequestAttempt: false
};
+ case SAVE_AUTOBUYER_SETTINGS:
+ return {
+ ...state,
+ balanceToMaintain: action.balanceToMaintain,
+ account: action.account,
+ vsp: action.vsp
+ };
default:
return state;
}
diff --git a/app/selectors.js b/app/selectors.js
index 42cea9b776..721906593d 100644
--- a/app/selectors.js
+++ b/app/selectors.js
@@ -913,8 +913,14 @@ export const isSyncingTickets = get(["vsp", "syncVSPRequestAttempt"]);
// ticket auto buyer
export const getTicketAutoBuyerRunning = get(["vsp", "ticketAutoBuyerRunning"]);
+export const legacyBuyerVSP = get(["control", "legacyVsp"]);
+export const legacyBuyerBalanceToMaintain = get([
+ "control",
+ "legacyBalanceToMaintain"
+]);
+export const legacyBuyerAccount = get(["control", "legacyAccount"]);
export const buyerVSP = get(["vsp", "vsp"]);
-export const buyerBalanceToMantain = get(["vsp", "balanceToMaintain"]);
+export const buyerBalanceToMaintain = get(["vsp", "balanceToMaintain"]);
export const buyerAccount = get(["vsp", "account"]);
export const getHasVSPTicketsError = get(["vsp", "hasVSPTicketsError"]);
export const getIsLegacy = get(["vsp", "isLegacy"]);
diff --git a/app/style/themes/darkTheme.js b/app/style/themes/darkTheme.js
index 7a5a616013..a8f3d8205d 100644
--- a/app/style/themes/darkTheme.js
+++ b/app/style/themes/darkTheme.js
@@ -145,6 +145,11 @@ const darkTheme = {
"small-button-bg": "#2f4d8c",
"small-button-disabled-bg": "#1f325f",
"send-transaction-border": "#608ace",
+ "secondary-piui-button-bg": "#fff",
+ "purchase-ticket-border": "#608ace",
+ "stakeinfo-value": "#e9f8fe",
+ "stakeinfo-border": "#608ace",
+ "purchase-label": "#b7deee",
// override pi-ui's toggle default dark background
"toggle-bar-color": "var(--background-copy-color)",
diff --git a/app/style/themes/lightTheme.js b/app/style/themes/lightTheme.js
index 643f8c96d5..ca88309f97 100644
--- a/app/style/themes/lightTheme.js
+++ b/app/style/themes/lightTheme.js
@@ -148,6 +148,11 @@ const lightTheme = {
"small-button-bg": "#fff",
"small-button-disabled-bg": "#e6eaed",
"send-transaction-border": "var(--grey-3)",
+ "secondary-piui-button-bg": "#fff",
+ "purchase-ticket-border": "var(--grey-3)",
+ "stakeinfo-value": "var(--main-dark-blue)",
+ "stakeinfo-border": "var(--grey-3)",
+ "purchase-label": "var(--grey-7)",
// override pi-ui's tab colors
"tab-default-color": "transparent", // default border
diff --git a/test/unit/components/TicketsPage/PurchaseTab/LEGACY_PurchasePage/LEGACY_PurchasePage.spec.js b/test/unit/components/TicketsPage/PurchaseTab/LEGACY_PurchasePage/LEGACY_PurchasePage.spec.js
index 65b23f9ab8..717f67beec 100644
--- a/test/unit/components/TicketsPage/PurchaseTab/LEGACY_PurchasePage/LEGACY_PurchasePage.spec.js
+++ b/test/unit/components/TicketsPage/PurchaseTab/LEGACY_PurchasePage/LEGACY_PurchasePage.spec.js
@@ -2,8 +2,7 @@ import Purchase from "../../../../../../app/components/views/TicketsPage/Purchas
import TicketAutoBuyer from "../../../../../../app/components/views/TicketsPage/PurchaseTab/LEGACY_PurchasePage/LEGACY_TicketAutoBuyer";
import { render } from "test-utils.js";
import user from "@testing-library/user-event";
-import { fireEvent } from "@testing-library/react";
-import { screen } from "@testing-library/react";
+import { screen, act, fireEvent } from "@testing-library/react";
import * as sel from "selectors";
import * as ca from "actions/ControlActions";
import * as spa from "actions/VSPActions";
@@ -149,11 +148,15 @@ let mockIsTicketAutoBuyerEnabled;
let mockTicketBuyerV2Cancel;
let mockGetRunningIndicator;
let mockAddCustomStakePool;
+let mockToggleIsLegacy;
beforeEach(() => {
selectors.getIsLegacy = jest.fn(() => true);
selectors.stakePoolListingEnabled = jest.fn(() => true);
selectors.unconfiguredStakePools = jest.fn(() => mockUnconfiguredStakePools);
+ selectors.spendingAccounts = jest.fn(() => [mockMixedAccount]);
+ selectors.visibleAccounts = jest.fn(() => [mockMixedAccount]);
+ selectors.getMixedAccount = jest.fn(() => mockMixedAccountValue);
selectors.configuredStakePools = jest.fn(() => mockConfiguredStakePools);
selectors.defaultSpendingAccount = jest.fn(() => mockMixedAccount);
//stakeInfo
@@ -192,10 +195,19 @@ beforeEach(() => {
mockGetRunningIndicator = selectors.getRunningIndicator = jest.fn(
() => false
);
+ mockToggleIsLegacy = spActions.toggleIsLegacy = jest.fn(() => () => {});
});
test("render LEGACY_PurchasePage", () => {
render(, initialState);
+
+ // check Use Legacy VSP checkbox
+ expect(
+ screen.queryByText(/use a VSP which has not updated to vspd/i)
+ ).not.toBeInTheDocument(); // tooltip
+ user.click(screen.getByLabelText("Use Legacy VSP"));
+ expect(mockToggleIsLegacy).toHaveBeenCalledWith(false);
+
expect(
screen.getByText("Current VSP").nextElementSibling.textContent
).toMatch(mockConfiguredStakePools[0].Host);
@@ -242,6 +254,9 @@ test("render LEGACY_PurchasePage", () => {
`${mockConfiguredStakePools[0].value.PoolFees}`
);
+ expect(
+ screen.queryByLabelText("Always use this VSP")
+ ).not.toBeInTheDocument();
// change stakepool
const stakePoolSelectOption = screen.getAllByRole("option", {
name: mockConfiguredStakePools[0].Host
@@ -307,7 +322,7 @@ test("render LEGACY_PurchasePage", () => {
);
// test amount input
- const inputTag = screen.getByLabelText("Amount:");
+ const inputTag = screen.getByLabelText("Amount");
const moreButton = screen.getByRole("button", { name: "more" });
user.click(moreButton);
@@ -340,7 +355,7 @@ test("render LEGACY_PurchasePage", () => {
expect(mockPurchaseTicketsAttempt).not.toHaveBeenCalled();
//close advanced panel
- user.click(ticketCogsButton);
+ act(() => user.click(ticketCogsButton));
expect(ticketCogsButton.className).toMatch(/opened/i);
// backup redeem script
@@ -383,27 +398,54 @@ test("render LEGACY_PurchasePage", () => {
test("test legacy autobuyer", () => {
render(, initialState);
+ const settingsButton = screen.getByRole("button", {
+ name: "Ticket Autobuyer Settings"
+ });
+ user.click(screen.getByTestId("toggleSwitch"));
+ const saveButton = screen.getByRole("button", { name: "Save" });
+ user.click(saveButton);
+ expect(screen.getByText("Fill all fields.")).toBeInTheDocument();
const mockBalanceToMaintain = 14;
user.type(
- screen.getByLabelText("Balance to Maintain:"),
+ screen.getByLabelText(/Balance to Maintain/i),
`${mockBalanceToMaintain}`
);
- // change stakepool
- const stakePoolSelectOption = screen.getAllByRole("option", {
- name: mockConfiguredStakePools[0].Host
- })[0];
-
- user.click(stakePoolSelectOption);
+ user.click(saveButton);
+ expect(screen.getByText("Fill all fields.")).toBeInTheDocument();
+ // set stakepool
+ user.click(screen.getByText("Select VSP..."));
user.click(
screen.getByRole("option", { name: mockConfiguredStakePools[1].Host })
);
- user.click(screen.getByTestId("toggleSwitch"));
+ user.click(saveButton);
+ expect(screen.getByText("Fill all fields.")).toBeInTheDocument();
+
+ // set account
+ user.click(screen.getByText("Select account"));
+ user.click(screen.getByText(mockMixedAccount.name));
+ user.click(saveButton);
+
+ // check settings
+ user.click(settingsButton);
+ expect(screen.getByLabelText(/Balance to Maintain/i).value).toBe(
+ `${mockBalanceToMaintain}`
+ );
+ expect(
+ screen.getByText(mockConfiguredStakePools[1].Host)
+ ).toBeInTheDocument();
+ expect(screen.getByText(mockMixedAccount.name)).toBeInTheDocument();
+ user.click(screen.getByRole("button", { name: "Cancel" }));
+
+ // clicking again on switch should open the confirmation modal
+ user.click(screen.getByTestId("toggleSwitch"));
expect(
screen.getByText(/start ticket buyer confirmation/i)
).toBeInTheDocument();
- expect(screen.getAllByText(mockConfiguredStakePools[1].Host).length).toBe(2);
+ expect(
+ screen.getByText(mockConfiguredStakePools[1].Host)
+ ).toBeInTheDocument();
expect(screen.getByText(`${mockBalanceToMaintain}.00`)).toBeInTheDocument();
// cancel first
user.click(screen.getByText("Cancel"));
@@ -417,7 +459,6 @@ test("test legacy autobuyer", () => {
mockBalanceToMaintain * 100000000,
mockConfiguredStakePools[1]
);
- //
});
test("test legacy autobuyer (autobuyer is runnning)", () => {
diff --git a/test/unit/components/TicketsPage/PurchaseTab/PurchasePage.spec.js b/test/unit/components/TicketsPage/PurchaseTab/PurchasePage.spec.js
new file mode 100644
index 0000000000..7dd1bc5cd0
--- /dev/null
+++ b/test/unit/components/TicketsPage/PurchaseTab/PurchasePage.spec.js
@@ -0,0 +1,362 @@
+import Purchase from "../../../../../app/components/views/TicketsPage/PurchaseTab/PurchaseTab.jsx";
+import TicketAutoBuyer from "../../../../../app/components/views/TicketsPage/PurchaseTab/TicketAutoBuyer/";
+import { render } from "test-utils.js";
+import user from "@testing-library/user-event";
+import { fireEvent, screen, wait } from "@testing-library/react";
+import * as sel from "selectors";
+import * as ca from "actions/ControlActions";
+import * as vsp from "../../../../../app/wallet/vsp";
+import * as vspa from "actions/VSPActions";
+import * as sa from "actions/SettingsActions";
+import { DCR } from "constants";
+import { en as enLocale } from "i18n/locales";
+import { DEFAULT_LIGHT_THEME_NAME } from "pi-ui";
+import { EXTERNALREQUEST_STAKEPOOL_LISTING } from "main_dev/externalRequests";
+
+const selectors = sel;
+const controlActions = ca;
+const vspActions = vspa;
+const settingsActions = sa;
+const vspFuncs = vsp;
+
+const mockVspInfo = {
+ data: {
+ pubkey: "test-pubkey",
+ feepercentage: 2
+ }
+};
+const mockAvailableVsps = [
+ {
+ host: "https://test-stakepool1.eu",
+ label: "https://test-stakepool1.eu",
+ vspData: {
+ feepercentage: 1
+ }
+ },
+ {
+ host: "https://test-stakepool2.eu",
+ label: "https://test-stakepool2.eu",
+ vspData: {
+ feepercentage: 2
+ }
+ }
+];
+const mockMixedAccountValue = 6;
+const mockChangeAccountValue = 6;
+const mockNumTicketsToBuy = 1;
+const mockMixedAccount = {
+ hidden: false,
+ label: "mixed: 249.79547928 DCR",
+ name: "mixed",
+ spendable: 24979547928,
+ spendableAndUnit: "249.79547928 DCR",
+ total: 24979547928,
+ value: mockMixedAccountValue
+};
+
+const mockPassphrase = "test-passphrase";
+const locale = enLocale;
+const currentSettings = {
+ locale: locale.key,
+ theme: DEFAULT_LIGHT_THEME_NAME,
+ allowedExternalRequests: [EXTERNALREQUEST_STAKEPOOL_LISTING]
+};
+const initialState = {
+ initialState: {
+ control: {
+ numTicketsToBuy: mockNumTicketsToBuy
+ },
+ settings: {
+ currentSettings,
+ tempSettings: currentSettings
+ }
+ }
+};
+
+const mockVotedTicketsCount = 3;
+const mockOwnMempoolTicketsCount = 5;
+const mockRevokedTicketsCount = 7;
+const mockImmatureTicketsCount = 6;
+const mockLiveTicketsCount = 7;
+const mockUnspentTicketsCount = 2;
+const mockTotalSubsidy = 400000000;
+const mockLastVotedTicket = null;
+const mockCurrencyDisplay = DCR;
+const mockTicketPrice = 6816662938;
+
+let mockPurchaseTicketsAttempt;
+let mockRevokeTicketsAttempt;
+let mockStartTicketBuyerV3Attempt;
+let mockGetTicketAutoBuyerRunning;
+let mockTicketBuyerCancel;
+let mockGetRunningIndicator;
+let mockToggleIsLegacy;
+let mockSetRememberedVspHost;
+let mockAddAllowedExternalRequest;
+
+beforeEach(() => {
+ selectors.getIsLegacy = jest.fn(() => false);
+ selectors.stakePoolListingEnabled = jest.fn(() => true);
+ selectors.getAvailableVSPs = jest.fn(() => mockAvailableVsps);
+ selectors.spendingAccounts = jest.fn(() => [mockMixedAccount]);
+ selectors.visibleAccounts = jest.fn(() => [mockMixedAccount]);
+ selectors.getMixedAccount = jest.fn(() => mockMixedAccountValue);
+ selectors.getChangeAccount = jest.fn(() => mockChangeAccountValue);
+ selectors.defaultSpendingAccount = jest.fn(() => mockMixedAccount);
+ selectors.ticketPrice = jest.fn(() => mockTicketPrice);
+ //stakeInfo
+ selectors.votedTicketsCount = jest.fn(() => mockVotedTicketsCount);
+ selectors.ownMempoolTicketsCount = jest.fn(() => mockOwnMempoolTicketsCount);
+ selectors.revokedTicketsCount = jest.fn(() => mockRevokedTicketsCount);
+ selectors.immatureTicketsCount = jest.fn(() => mockImmatureTicketsCount);
+ selectors.liveTicketsCount = jest.fn(() => mockLiveTicketsCount);
+ selectors.unspentTicketsCount = jest.fn(() => mockUnspentTicketsCount);
+ selectors.totalSubsidy = jest.fn(() => mockTotalSubsidy);
+ selectors.isSPV = jest.fn(() => false);
+ selectors.lastVotedTicket = jest.fn(() => mockLastVotedTicket);
+ selectors.currencyDisplay = jest.fn(() => mockCurrencyDisplay);
+ selectors.blocksNumberToNextTicket = jest.fn(() => 13);
+ mockAddAllowedExternalRequest = settingsActions.addAllowedExternalRequest = jest.fn(
+ () => () => {}
+ );
+
+ mockPurchaseTicketsAttempt = controlActions.newPurchaseTicketsAttempt = jest.fn(
+ () => () => {}
+ );
+ mockRevokeTicketsAttempt = controlActions.revokeTicketsAttempt = jest.fn(
+ () => () => {}
+ );
+ mockStartTicketBuyerV3Attempt = controlActions.startTicketBuyerV3Attempt = jest.fn(
+ () => () => {}
+ );
+ mockGetTicketAutoBuyerRunning = selectors.getTicketAutoBuyerRunning = jest.fn(
+ () => false
+ );
+ mockTicketBuyerCancel = controlActions.ticketBuyerCancel = jest.fn(
+ () => () => {}
+ );
+ mockGetRunningIndicator = selectors.getRunningIndicator = jest.fn(
+ () => false
+ );
+ vspFuncs.getVSPInfo = jest.fn(() => {
+ return Promise.resolve(mockVspInfo);
+ });
+ mockToggleIsLegacy = vspActions.toggleIsLegacy = jest.fn(() => () => {});
+ mockSetRememberedVspHost = vspActions.setRememberedVspHost = jest.fn(
+ () => () => {}
+ );
+});
+
+test("render PurchasePage", async () => {
+ render(, initialState);
+
+ // check PrivacyInfo
+ user.click(screen.getByText("You are purchasing mixed tickets"));
+ expect(
+ screen.getByText(/Purchasing mixed tickets can take some time/i)
+ ).toBeInTheDocument();
+
+ // check Use Legacy VSP checkbox
+ expect(
+ screen.getByText(/use a VSP which has not updated to vspd/i)
+ ).toBeInTheDocument(); //tooltip
+ user.click(screen.getByLabelText("Use Legacy VSP"));
+ expect(mockToggleIsLegacy).toHaveBeenCalledWith(true);
+
+ // set stakepool
+ user.click(screen.getByText("Select VSP..."));
+ user.click(screen.getByText(mockAvailableVsps[1].host));
+ await wait(() =>
+ expect(screen.queryByText("Loading")).not.toBeInTheDocument()
+ );
+
+ // check Always use this VSP checkbox
+ user.click(screen.getByLabelText("Always use this VSP"));
+ expect(mockSetRememberedVspHost).toHaveBeenCalledWith({
+ host: mockAvailableVsps[1].host,
+ pubkey: mockVspInfo.data.pubkey
+ });
+
+ // check summary
+ expect(screen.getByText("VSP Fee:").nextElementSibling.textContent).toMatch(
+ `${mockVspInfo.data.feepercentage} %`
+ );
+ expect(screen.getByText(/Total:/).textContent).toMatch(
+ `${mockTicketPrice / 100000000} ${DCR}`
+ );
+ expect(screen.getByText(/Remaining:/).textContent).toMatch(
+ `${(mockMixedAccount.spendable - mockTicketPrice) / 100000000} ${DCR}`
+ );
+
+ const purchaseButton = screen.getByText("Purchase");
+ user.click(purchaseButton);
+ user.type(screen.getByLabelText("Private Passphrase:"), mockPassphrase);
+ user.click(screen.getByText("Continue"));
+ expect(mockPurchaseTicketsAttempt).toHaveBeenCalledWith(
+ mockPassphrase,
+ mockMixedAccount,
+ mockNumTicketsToBuy,
+ {
+ host: mockAvailableVsps[1].host,
+ pubkey: mockVspInfo.data.pubkey
+ }
+ );
+
+ // test amount input
+ const inputTag = screen.getByLabelText("Amount");
+
+ const moreButton = screen.getByRole("button", { name: "more" });
+ user.click(moreButton);
+ expect(inputTag.value).toBe("2");
+
+ const lessButton = screen.getByRole("button", { name: "less" });
+ user.click(lessButton);
+ expect(inputTag.value).toBe("1");
+ // remain 1
+ user.click(lessButton);
+ expect(inputTag.value).toBe("1");
+
+ /* test arrow key */
+ fireEvent.keyDown(inputTag, { keyCode: 38 });
+ expect(inputTag.value).toBe("2");
+ fireEvent.keyDown(inputTag, { keyCode: 40 });
+ expect(inputTag.value).toBe("1");
+
+ user.clear(inputTag);
+ expect(inputTag.value).toBe("");
+
+ // "" => 1
+ user.click(moreButton);
+ expect(inputTag.value).toBe("1");
+
+ // not enough funds
+ mockPurchaseTicketsAttempt.mockReset();
+ user.type(inputTag, "100000000");
+ user.click(purchaseButton);
+ expect(mockPurchaseTicketsAttempt).not.toHaveBeenCalled();
+
+ // revoke
+ user.click(screen.getByText("Revoke"));
+ expect(screen.getByText(/revoke tickets confirmation/i)).toBeInTheDocument();
+ // cancel first
+ user.click(screen.getByText("Cancel"));
+ // try again
+ user.click(screen.getByText("Revoke"));
+ user.type(screen.getByLabelText("Private Passphrase:"), mockPassphrase);
+ user.click(screen.getByText("Continue"));
+ expect(mockRevokeTicketsAttempt).toHaveBeenCalledWith(mockPassphrase);
+});
+
+test("test autobuyer", async () => {
+ render(, initialState);
+ const settingsButton = screen.getByRole("button", {
+ name: "Ticket Autobuyer Settings"
+ });
+ user.click(screen.getByTestId("toggleSwitch"));
+ const saveButton = screen.getByRole("button", { name: "Save" });
+ user.click(saveButton);
+ expect(screen.getByText("Fill all fields.")).toBeInTheDocument();
+ const mockBalanceToMaintain = 14;
+ user.type(
+ screen.getByLabelText(/Balance to Maintain/i),
+ `${mockBalanceToMaintain}`
+ );
+
+ user.click(saveButton);
+ expect(screen.getByText("Fill all fields.")).toBeInTheDocument();
+ // set stakepool
+ user.click(screen.getByText("Select VSP..."));
+ user.click(screen.getByText(mockAvailableVsps[1].host));
+ await wait(() =>
+ expect(screen.queryByText("Loading")).not.toBeInTheDocument()
+ );
+ user.click(saveButton);
+ expect(screen.getByText("Fill all fields.")).toBeInTheDocument();
+
+ // set account
+ user.click(screen.getByText("Select account"));
+ user.click(screen.getByText(mockMixedAccount.name));
+ user.click(saveButton);
+
+ // check settings
+ user.click(settingsButton);
+ expect(screen.getByLabelText(/Balance to Maintain/i).value).toBe(
+ `${mockBalanceToMaintain}`
+ );
+ expect(screen.getByText(mockAvailableVsps[1].host)).toBeInTheDocument();
+ expect(screen.getByText(mockMixedAccount.name)).toBeInTheDocument();
+ user.click(screen.getByRole("button", { name: "Cancel" }));
+
+ user.click(screen.getByTestId("toggleSwitch"));
+ // clicking again on switch should open the confirmation modal
+ user.click(screen.getByTestId("toggleSwitch"));
+ expect(
+ screen.getByText(/start ticket buyer confirmation/i)
+ ).toBeInTheDocument();
+ expect(screen.getByText(mockAvailableVsps[1].host)).toBeInTheDocument();
+ expect(screen.getByText(`${mockBalanceToMaintain}.00`)).toBeInTheDocument();
+ // cancel first
+ user.click(screen.getByText("Cancel"));
+ // try again
+ user.click(screen.getByTestId("toggleSwitch"));
+ user.type(screen.getByLabelText("Private Passphrase:"), mockPassphrase);
+ user.click(screen.getByText("Continue"));
+ expect(mockStartTicketBuyerV3Attempt).toHaveBeenCalledWith(
+ mockPassphrase,
+ mockMixedAccount,
+ mockBalanceToMaintain * 100000000,
+ {
+ host: mockAvailableVsps[1].host,
+ pubkey: mockVspInfo.data.pubkey
+ }
+ );
+});
+
+test("test autobuyer (autobuyer is runnning)", () => {
+ mockGetTicketAutoBuyerRunning = selectors.getTicketAutoBuyerRunning = jest.fn(
+ () => true
+ );
+ render(, initialState);
+ expect(mockGetTicketAutoBuyerRunning).toHaveBeenCalled();
+ expect(screen.getByText(/turn off auto buyer/i)).toBeInTheDocument();
+ user.click(screen.getByTestId("toggleSwitch"));
+ expect(mockTicketBuyerCancel).toHaveBeenCalled();
+});
+
+test("test legacy autobuyer (a process is runnning)", () => {
+ mockGetRunningIndicator = selectors.getRunningIndicator = jest.fn(() => true);
+ render(, initialState);
+ expect(
+ screen.getByText(/privacy mixer or purchase ticket attempt running/i)
+ ).toBeInTheDocument();
+ user.click(screen.getByTestId("toggleSwitch"));
+
+ expect(
+ screen.queryByText(/start ticket buyer confirmation/i)
+ ).not.toBeInTheDocument();
+ expect(mockGetRunningIndicator).toHaveBeenCalled();
+});
+
+test("test when VSP listing is not enabled ", () => {
+ render();
+ expect(
+ screen.getByText(
+ /VSP listing from external API endpoint is currently disabled/i
+ )
+ ).toBeInTheDocument();
+ user.click(screen.getByRole("button", { name: "Enable VSP Listing" }));
+ expect(mockAddAllowedExternalRequest).toHaveBeenCalledWith(
+ EXTERNALREQUEST_STAKEPOOL_LISTING
+ );
+});
+
+test("test `end of a ticket interval` state", () => {
+ selectors.blocksNumberToNextTicket = jest.fn(() => 2);
+ selectors.isSPV = jest.fn(() => true);
+ render(, initialState);
+ expect(
+ screen.getByText(
+ /Purchase Tickets is not available right now, because we are at the end of a ticket interval. After one block it will be available again./i
+ )
+ ).toBeInTheDocument();
+});
diff --git a/test/unit/components/TicketsPage/PurchaseTab/StakeInfo.spec.js b/test/unit/components/TicketsPage/PurchaseTab/StakeInfo.spec.js
index 3b554b44d5..f581f7cd46 100644
--- a/test/unit/components/TicketsPage/PurchaseTab/StakeInfo.spec.js
+++ b/test/unit/components/TicketsPage/PurchaseTab/StakeInfo.spec.js
@@ -35,11 +35,11 @@ test("test StakeInfo (SPV enabled)", () => {
const unspentTickets = screen.getByText(/unspent tickets/i);
expect(unspentTickets.nextElementSibling.textContent).toBe(
- `${mockUnspentTicketsCount} tickets`
+ `${mockUnspentTicketsCount}`
);
expect(screen.getByText(/total voted/i).nextElementSibling.textContent).toBe(
- `${mockVotedTicketsCount} tickets`
+ `${mockVotedTicketsCount}`
);
expect(
screen.getByText(/last ticket voted/i).nextElementSibling.textContent
@@ -75,7 +75,7 @@ test("test StakeInfo (SPV disabled)", () => {
const liveTickets = screen.getByText(/live/i);
expect(liveTickets.nextElementSibling.textContent).toBe(
- `${mockLiveTicketsCount} tickets`
+ `${mockLiveTicketsCount}`
);
expect(liveTickets.nextElementSibling.nextElementSibling.textContent).toMatch(
`Own Mempool: ${mockOwnMempoolTicketsCount}`
diff --git a/test/unit/components/inputs/Inputs.spec.js b/test/unit/components/inputs/Inputs.spec.js
index 7581ba4df2..734f9cabc3 100644
--- a/test/unit/components/inputs/Inputs.spec.js
+++ b/test/unit/components/inputs/Inputs.spec.js
@@ -399,11 +399,11 @@ test("render default NumTicketsInput", () => {
checkDefaultInput(input, inputTag, "text", "", testInitValue);
expect(input.className).toMatch("numericInput");
expect(input.className).toMatch("integerInput");
- expect(screen.getByText("Ticket")).toBeInTheDocument(); // single ticket
+ expect(screen.getByText("ticket")).toBeInTheDocument(); // single ticket
//manual type a value
user.type(inputTag, "2.3");
expect(inputTag.value).toBe("123");
- expect(screen.getByText("Tickets")).toBeInTheDocument(); // multiple tickets
+ expect(screen.getByText("tickets")).toBeInTheDocument(); // multiple tickets
/* test buttons */
const lessButton = screen.getByRole("button", { name: "less" });