diff --git a/app/actions/ClientActions.js b/app/actions/ClientActions.js
index 9267354b00..6ee969c349 100644
--- a/app/actions/ClientActions.js
+++ b/app/actions/ClientActions.js
@@ -81,7 +81,7 @@ const startWalletServicesTrigger = () => (dispatch, getState) =>
if (privacyEnabled) {
dispatch(getAccountMixerServiceAttempt());
}
- dispatch(discoverAvailableVSPs());
+ await dispatch(discoverAvailableVSPs());
await dispatch(getNextAddressAttempt(0));
await dispatch(getPeerInfo());
await dispatch(getTicketPriceAttempt());
diff --git a/app/actions/VSPActions.js b/app/actions/VSPActions.js
index ad9b2054d3..de1e289cb6 100644
--- a/app/actions/VSPActions.js
+++ b/app/actions/VSPActions.js
@@ -294,43 +294,46 @@ export const GETVSPSPUBKEYS_SUCCESS = "GETVSPSPUBKEYS_SUCCESS";
export const GETVSPSPUBKEYS_FAILED = "GETVSPSPUBKEYS_FAILED";
// getVSPsPubkeys gets all available vsps and its pubkeys, so we can store it.
-export const getVSPsPubkeys = () => async (dispatch) => {
- try {
+export const getVSPsPubkeys = () => (dispatch) =>
+ new Promise((resolve, reject) => {
dispatch({ type: GETVSPSPUBKEYS_ATTEMPT });
- const vsps = await dispatch(discoverAvailableVSPs());
- if (!isArray(vsps)) {
- throw new Error("INVALID_VSPS");
- }
- await Promise.all(
- vsps.map((vsp) => {
- return new Promise((resolve) =>
- dispatch(getVSPInfo(vsp.host))
- .then(({ pubkey }) => {
- if (pubkey) {
- vsp.pubkey = pubkey;
- resolve(vsp);
- } else {
- resolve(null);
- }
- })
- .catch(() => {
- resolve(null);
- // Skip to the next vsp.
- })
- );
- })
- ).then((result) => {
- const availableVSPsPubkeys = result.filter((vsp) => !!vsp?.pubkey);
- dispatch({
- type: GETVSPSPUBKEYS_SUCCESS,
- availableVSPsPubkeys
+ dispatch(discoverAvailableVSPs()).then((vsps) => {
+ if (!isArray(vsps)) {
+ dispatch({
+ type: GETVSPSPUBKEYS_FAILED,
+ error: new Error("INVALID_VSPS")
+ });
+ return reject("INVALID_VSPS");
+ }
+
+ Promise.all(
+ vsps.map((vsp) => {
+ return new Promise((resolveVspInfo) =>
+ dispatch(getVSPInfo(vsp.host))
+ .then(({ pubkey }) => {
+ if (pubkey) {
+ vsp.pubkey = pubkey;
+ resolveVspInfo(vsp);
+ } else {
+ resolveVspInfo(null);
+ }
+ })
+ .catch(() => {
+ resolveVspInfo(null);
+ // Skip to the next vsp.
+ })
+ );
+ })
+ ).then((result) => {
+ const availableVSPsPubkeys = result.filter((vsp) => !!vsp?.pubkey);
+ dispatch({
+ type: GETVSPSPUBKEYS_SUCCESS,
+ availableVSPsPubkeys
+ });
+ resolve(availableVSPsPubkeys);
});
- return availableVSPsPubkeys;
});
- } catch (error) {
- dispatch({ type: GETVSPSPUBKEYS_FAILED, error });
- }
-};
+ });
export const PROCESSMANAGEDTICKETS_ATTEMPT = "PROCESSMANAGEDTICKETS_ATTEMPT";
export const PROCESSMANAGEDTICKETS_SUCCESS = "PROCESSMANAGEDTICKETS_SUCCESS";
@@ -340,14 +343,14 @@ export const PROCESSMANAGEDTICKETS_FAILED = "PROCESSMANAGEDTICKETS_FAILED";
// synced, and sync them.
export const processManagedTickets =
(passphrase) => async (dispatch, getState) => {
+ dispatch({ type: PROCESSMANAGEDTICKETS_ATTEMPT });
const walletService = sel.walletService(getState());
let availableVSPsPubkeys = sel.getAvailableVSPsPubkeys(getState());
- if (!availableVSPsPubkeys) {
- availableVSPsPubkeys = await dispatch(getVSPsPubkeys());
- }
try {
- dispatch({ type: PROCESSMANAGEDTICKETS_ATTEMPT });
+ if (!availableVSPsPubkeys) {
+ availableVSPsPubkeys = await dispatch(getVSPsPubkeys());
+ }
let feeAccount, changeAccount;
const mixedAccount = sel.getMixedAccount(getState());
if (mixedAccount) {
@@ -386,13 +389,6 @@ export const processManagedTickets =
dispatch({ type: PROCESSMANAGEDTICKETS_SUCCESS });
} catch (error) {
dispatch({ type: PROCESSMANAGEDTICKETS_FAILED, error });
- if (
- String(error).indexOf(
- "wallet.Unlock: invalid passphrase:: secretkey.DeriveKey"
- ) > 0
- ) {
- throw "Invalid private passphrase, please try again.";
- }
throw error;
}
};
diff --git a/app/components/views/GetStartedPage/SetupWallet/ProcessManagedTickets/ProcessManagedTickets.jsx b/app/components/views/GetStartedPage/SetupWallet/ProcessManagedTickets/ProcessManagedTickets.jsx
index 177a1c1b69..43a4e62a80 100644
--- a/app/components/views/GetStartedPage/SetupWallet/ProcessManagedTickets/ProcessManagedTickets.jsx
+++ b/app/components/views/GetStartedPage/SetupWallet/ProcessManagedTickets/ProcessManagedTickets.jsx
@@ -1,26 +1,15 @@
-import { useState, useEffect } from "react";
import { Subtitle } from "shared";
import { FormattedMessage as T } from "react-intl";
import { PassphraseModalButton, InvisibleButton } from "buttons";
import styles from "./ProcessUnmanagedTickets.module.css";
-import { VSPSelect } from "inputs";
+import { useDaemonStartup } from "hooks";
-export default ({
- cancel,
- send,
- onProcessTickets,
- title,
- description,
- noVspSelection,
- error,
- isProcessingManaged
-}) => {
- const [isValid, setIsValid] = useState(false);
- const [vsp, setVSP] = useState(null);
+export default ({ cancel, send, error }) => {
+ const { onProcessManagedTickets, isProcessingManaged } = useDaemonStartup();
const onSubmitContinue = (passphrase) => {
// send a continue so we can go to the loading state
- onProcessTickets(passphrase)
+ onProcessManagedTickets(passphrase)
.then(() => send({ type: "CONTINUE" }))
.catch((error) => {
send({ type: "ERROR", error });
@@ -28,23 +17,28 @@ export default ({
return;
};
- useEffect(() => {
- if (noVspSelection) {
- setIsValid(true);
- return;
- }
- if (vsp) {
- setIsValid(true);
- }
- }, [vsp, noVspSelection]);
-
return (
-
-
{description}
- {!noVspSelection && (
-
- )}
+
+ }
+ />
+
+ {
+
+ }
+
{error &&
{error}
}
}
- disabled={!isValid || isProcessingManaged}
+ disabled={isProcessingManaged}
loading={isProcessingManaged}
/>
- {!isProcessingManaged && (
-
-
-
- )}
+
+
+
);
diff --git a/app/components/views/GetStartedPage/SetupWallet/ProcessManagedTickets/index.js b/app/components/views/GetStartedPage/SetupWallet/ProcessManagedTickets/index.js
new file mode 100644
index 0000000000..3cf044f124
--- /dev/null
+++ b/app/components/views/GetStartedPage/SetupWallet/ProcessManagedTickets/index.js
@@ -0,0 +1 @@
+export { default } from "./ProcessManagedTickets";
diff --git a/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/ProcessUnmanagedTickets.jsx b/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/ProcessUnmanagedTickets.jsx
index 9706ef6c7d..4cb171e1b8 100644
--- a/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/ProcessUnmanagedTickets.jsx
+++ b/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/ProcessUnmanagedTickets.jsx
@@ -1,52 +1,44 @@
-import { useState, useEffect } from "react";
import { Subtitle } from "shared";
import { FormattedMessage as T } from "react-intl";
import { PassphraseModalButton, InvisibleButton } from "buttons";
import styles from "./ProcessUnmanagedTickets.module.css";
import { VSPSelect } from "inputs";
+import { useProcessUnmanagedTickets } from "./hooks";
-export default ({
- cancel,
- send,
- onProcessTickets,
- title,
- description,
- noVspSelection,
- isProcessingUnmanaged,
- error,
- availableVSPs
-}) => {
- const [isValid, setIsValid] = useState(false);
- const [vsp, setVSP] = useState(null);
-
- const onSubmitContinue = (passphrase) => {
- onProcessTickets(passphrase, vsp.host, vsp.pubkey)
- .then(() => send({ type: "CONTINUE" }))
- .catch((error) => {
- send({ type: "ERROR", error });
- });
- };
-
- useEffect(() => {
- if (noVspSelection) {
- setIsValid(true);
- return;
- }
- if (vsp) {
- setIsValid(true);
- }
- }, [vsp, noVspSelection]);
+export default ({ cancel, send, error }) => {
+ const {
+ isProcessingUnmanaged,
+ availableVSPs,
+ vsp,
+ setVSP,
+ onSubmitContinue
+ } = useProcessUnmanagedTickets({ send });
return (
-
-
{description}
- {!noVspSelection && (
-
- )}
+
+ }
+ />
+
+ {
+
+ }
+
+
{error &&
{error}
}
}
- disabled={!isValid || isProcessingUnmanaged}
+ disabled={!vsp || isProcessingUnmanaged}
loading={isProcessingUnmanaged}
/>
- {!isProcessingUnmanaged && (
-
-
-
- )}
+
+
+
);
diff --git a/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/ProcessUnmanagedTickets.module.css b/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/ProcessUnmanagedTickets.module.css
index 5f49ebe945..c3a6e8cc39 100644
--- a/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/ProcessUnmanagedTickets.module.css
+++ b/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/ProcessUnmanagedTickets.module.css
@@ -16,5 +16,13 @@
}
.buttonWrapper {
- margin-top: 10px;
+ margin-top: 2rem;
+}
+
+.buttonWrapper .skipButton {
+ margin-left: 1rem;
+}
+
+.description {
+ margin-bottom: 1rem;
}
diff --git a/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/hooks.js b/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/hooks.js
new file mode 100644
index 0000000000..9a6a687822
--- /dev/null
+++ b/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/hooks.js
@@ -0,0 +1,31 @@
+import { useState } from "react";
+import { useSettings } from "hooks";
+import { useSelector } from "react-redux";
+import { useDaemonStartup } from "hooks";
+import { getAvailableVSPs } from "selectors";
+
+export const useProcessUnmanagedTickets = ({ send }) => {
+ const { onProcessUnmanagedTickets, isProcessingUnmanaged } =
+ useDaemonStartup();
+ const { isVSPListingEnabled } = useSettings();
+ const availableVSPs = isVSPListingEnabled
+ ? useSelector(getAvailableVSPs)
+ : [];
+ const [vsp, setVSP] = useState(null);
+
+ const onSubmitContinue = (passphrase) => {
+ onProcessUnmanagedTickets(passphrase, vsp.host, vsp.pubkey)
+ .then(() => send({ type: "CONTINUE" }))
+ .catch((error) => {
+ send({ type: "ERROR", error });
+ });
+ };
+
+ return {
+ isProcessingUnmanaged,
+ availableVSPs,
+ vsp,
+ setVSP,
+ onSubmitContinue
+ };
+};
diff --git a/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/index.js b/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/index.js
new file mode 100644
index 0000000000..4ea3490f6b
--- /dev/null
+++ b/app/components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets/index.js
@@ -0,0 +1 @@
+export { default } from "./ProcessUnmanagedTickets";
diff --git a/app/components/views/GetStartedPage/SetupWallet/hooks.js b/app/components/views/GetStartedPage/SetupWallet/hooks.js
index d6195921e7..d5b77e04a9 100644
--- a/app/components/views/GetStartedPage/SetupWallet/hooks.js
+++ b/app/components/views/GetStartedPage/SetupWallet/hooks.js
@@ -3,26 +3,22 @@ import { useService } from "@xstate/react";
import { IMMATURE, LIVE, UNMINED } from "constants/decrediton";
import { FormattedMessage as T } from "react-intl";
import SettingMixedAccount from "./SetMixedAcctPage/SetMixedAcctPage";
-import ProcessUnmanagedTickets from "./ProcessUnmanagedTickets/ProcessUnmanagedTickets";
-import ProcessManagedTickets from "./ProcessManagedTickets/ProcessManagedTickets";
+import ProcessUnmanagedTickets from "./ProcessUnmanagedTickets";
+import ProcessManagedTickets from "./ProcessManagedTickets";
import SettingAccountsPassphrase from "./SetAccountsPassphrase";
import ResendVotesToRecentlyUpdatedVSPs from "./ResendVotesToRecentlyUpdatedVSPs";
import { useDaemonStartup, useAccounts, usePrevious } from "hooks";
-import { useDispatch, useSelector } from "react-redux";
+import { useDispatch } from "react-redux";
import {
checkAllAccountsEncrypted,
setAccountsPass
} from "actions/ControlActions";
import {
- getVSPsPubkeys,
setCanDisableProcessManaged,
getRecentlyUpdatedUsedVSPs,
getNotAbstainVotes
} from "actions/VSPActions";
import { ExternalLink } from "shared";
-import { DecredLoading } from "indicators";
-import * as sel from "selectors";
-import { useSettings } from "hooks";
export const useWalletSetup = (settingUpWalletRef) => {
const dispatch = useDispatch();
@@ -32,17 +28,11 @@ export const useWalletSetup = (settingUpWalletRef) => {
const {
getCoinjoinOutputspByAcct,
stakeTransactions,
- onProcessManagedTickets,
goToHome,
- onProcessUnmanagedTickets,
- isProcessingUnmanaged,
isProcessingManaged,
needsProcessManagedTickets
} = useDaemonStartup();
- const { isVSPListingEnabled } = useSettings();
- const availableVSPs = useSelector(sel.getAvailableVSPs);
-
const { mixedAccount } = useAccounts();
const previousState = usePrevious(current);
@@ -58,11 +48,6 @@ export const useWalletSetup = (settingUpWalletRef) => {
[dispatch]
);
- const onGetVSPsPubkeys = useCallback(
- () => dispatch(getVSPsPubkeys()),
- [dispatch]
- );
-
const onGetRecentlyUpdatedUsedVSPs = useCallback(
() => dispatch(getRecentlyUpdatedUsedVSPs()),
[dispatch]
@@ -204,16 +189,6 @@ export const useWalletSetup = (settingUpWalletRef) => {
}
}
break;
- case "gettingVSPInfo":
- // if no live tickets, we can skip it.
- if (!hasLiveVSPdTickets) {
- sendContinue();
- } else {
- component = h(DecredLoading);
- await onGetVSPsPubkeys();
- sendContinue();
- }
- break;
case "processingManagedTickets":
// if no live tickets, we can skip it.
if (!hasLiveVSPdTickets || !needsProcessManagedTickets) {
@@ -221,28 +196,8 @@ export const useWalletSetup = (settingUpWalletRef) => {
} else {
component = h(ProcessManagedTickets, {
error,
- onSendContinue: sendContinue,
- onSendError,
send,
- cancel: onSkipProcessManaged,
- onProcessTickets: onProcessManagedTickets,
- title: (
-
- ),
- isProcessingManaged: isProcessingManaged,
- noVspSelection: true,
- description: (
-
- )
+ cancel: onSkipProcessManaged
});
}
break;
@@ -276,24 +231,7 @@ export const useWalletSetup = (settingUpWalletRef) => {
send,
onSendContinue: sendContinue,
onSendError,
- onProcessTickets: onProcessUnmanagedTickets,
- isProcessingUnmanaged: isProcessingUnmanaged,
- cancel: onSendBack,
- availableVSPs: isVSPListingEnabled ? availableVSPs : [],
- title: (
-
- ),
- description: (
-
- )
+ cancel: onSendBack
});
}
break;
@@ -328,24 +266,18 @@ export const useWalletSetup = (settingUpWalletRef) => {
getCoinjoinOutputspByAcct,
goToHome,
mixedAccount,
- onProcessManagedTickets,
send,
stakeTransactions,
sendContinue,
isProcessingManaged,
- isProcessingUnmanaged,
needsProcessManagedTickets,
- onProcessUnmanagedTickets,
onSendBack,
onSendError,
previousState,
current,
- availableVSPs,
onCheckAcctsPass,
onProcessAccounts,
- onGetVSPsPubkeys,
onSkipProcessManaged,
- isVSPListingEnabled,
onGetRecentlyUpdatedUsedVSPs,
onGetNotAbstainVotes
]);
diff --git a/app/stateMachines/SetupWalletConfigMachine.js b/app/stateMachines/SetupWalletConfigMachine.js
index e86e867bcc..ca38dffe9d 100644
--- a/app/stateMachines/SetupWalletConfigMachine.js
+++ b/app/stateMachines/SetupWalletConfigMachine.js
@@ -20,12 +20,6 @@ export const SetupWalletConfigMachine = Machine({
},
settingMixedAccount: {
on: {
- CONTINUE: "gettingVSPInfo"
- }
- },
- gettingVSPInfo: {
- on: {
- BACK: "processingManagedTickets",
CONTINUE: "processingManagedTickets"
}
},
diff --git a/test/unit/actions/VSPActions.spec.js b/test/unit/actions/VSPActions.spec.js
index a3acf114cf..30e401be72 100644
--- a/test/unit/actions/VSPActions.spec.js
+++ b/test/unit/actions/VSPActions.spec.js
@@ -348,7 +348,11 @@ test("test getVSPsPubkeys (error)", async () => {
const testErrorMessage = "test-error-message";
wallet.getAllVSPs = jest.fn(() => Promise.reject(testErrorMessage));
const store = createStore({});
- await store.dispatch(vspActions.getVSPsPubkeys());
+ try {
+ await store.dispatch(vspActions.getVSPsPubkeys());
+ } catch (error) {
+ console.log({ error });
+ }
expect(store.getState().vsp.availableVSPsPubkeys).toEqual(undefined);
expect(store.getState().vsp.availableVSPsPubkeysError).toEqual(
diff --git a/test/unit/components/views/GetStaredPage/SetupWallet/ProcessManagedTickets.spec.js b/test/unit/components/views/GetStaredPage/SetupWallet/ProcessManagedTickets.spec.js
new file mode 100644
index 0000000000..852fa3e84e
--- /dev/null
+++ b/test/unit/components/views/GetStaredPage/SetupWallet/ProcessManagedTickets.spec.js
@@ -0,0 +1,374 @@
+import ProcessManagedTickets from "components/views/GetStartedPage/SetupWallet/ProcessManagedTickets";
+import { render } from "test-utils.js";
+import { screen, wait } from "@testing-library/react";
+import user from "@testing-library/user-event";
+import * as sel from "selectors";
+import * as wal from "wallet";
+import * as arrs from "../../../../../../app/helpers/arrays";
+const selectors = sel;
+const wallet = wal;
+const arrays = arrs;
+import {
+ VSP_FEE_PROCESS_ERRORED,
+ VSP_FEE_PROCESS_STARTED,
+ VSP_FEE_PROCESS_PAID,
+ VSP_FEE_PROCESS_CONFIRMED
+} from "constants";
+import {
+ defaultMockAvailableMainnetVsps,
+ defaultMockAvailableTestnetVsps,
+ defaultMockAvailableInvalidVsps,
+ mockPubkeys,
+ fetchTimes
+} from "../../../../actions/vspMocks";
+import {
+ testBalances,
+ changeAccountNumber,
+ mixedAccountNumber,
+ defaultAccountNumber,
+ mockUnlockLockAndGetAccountsAttempt
+} from "../../../../actions/accountMocks";
+import { cloneDeep } from "lodash";
+
+const mockAvailableMainnetVsps = cloneDeep(defaultMockAvailableMainnetVsps);
+const mockAvailableMainnetVspsPubkeys = cloneDeep(
+ defaultMockAvailableMainnetVsps
+).map((v) => ({ ...v, pubkey: `${v.host}-pubkey` }));
+const mockSend = jest.fn(() => {});
+const mockCancel = jest.fn(() => {});
+
+const testPassphrase = "test-passphrase";
+const testWalletService = "test-wallet-service";
+const testError = "test-error";
+
+let mockProcessManagedTickets;
+let mockGetVSPTicketsByFeeStatus;
+let mockGetVSPTrackedTickets;
+let mockGetAllVSPs;
+let mockGetVSPInfo;
+beforeEach(() => {
+ selectors.getAvailableVSPsPubkeys = jest.fn(() => null);
+ selectors.balances = jest.fn(() => cloneDeep(testBalances));
+ selectors.unlockableAccounts = jest.fn(() =>
+ cloneDeep(testBalances).filter(
+ (acct) => acct.accountNumber < Math.pow(2, 31) - 1 && acct.encrypted
+ )
+ );
+ arrays.shuffle = jest.fn((arr) => arr);
+ selectors.getVSPInfoTimeoutTime = jest.fn(() => 100);
+ selectors.isTestNet = jest.fn(() => false);
+ selectors.resendVSPDVoteChoicesAttempt = jest.fn(() => false);
+ selectors.walletService = jest.fn(() => testWalletService);
+ selectors.defaultSpendingAccount = jest.fn(() => ({
+ value: defaultAccountNumber
+ }));
+ selectors.getMixedAccount = jest.fn(() => mixedAccountNumber);
+ selectors.getChangeAccount = jest.fn(() => changeAccountNumber);
+ mockGetAllVSPs = wallet.getAllVSPs = jest.fn(() => [
+ ...cloneDeep(mockAvailableMainnetVsps),
+ ...cloneDeep(defaultMockAvailableTestnetVsps),
+ ...cloneDeep(defaultMockAvailableInvalidVsps)
+ ]);
+ mockGetVSPInfo = wallet.getVSPInfo = jest.fn((host) => {
+ if (!mockPubkeys[host]) {
+ return Promise.reject("invalid host");
+ }
+
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ mockPubkeys[host] !== "invalid"
+ ? resolve({ data: { pubkey: mockPubkeys[host] } })
+ : mockPubkeys[host]
+ ? resolve({ data: {} })
+ : reject("invalid host");
+ }, fetchTimes[host]);
+ });
+ });
+ mockProcessManagedTickets = wallet.processManagedTickets = jest.fn(() => {});
+ mockGetVSPTrackedTickets = wallet.getVSPTrackedTickets = jest.fn(() =>
+ Promise.resolve()
+ );
+ mockGetVSPTicketsByFeeStatus = wallet.getVSPTicketsByFeeStatus = jest.fn(() =>
+ Promise.resolve({
+ ticketHashes: []
+ })
+ );
+});
+
+const getSkipButton = () => screen.getByRole("button", { name: "Skip" });
+const getCancelButton = () => screen.getByRole("button", { name: "Cancel" });
+const getContinueButton = () =>
+ screen.getByRole("button", { name: "Continue" });
+const getModalContinueButton = () =>
+ screen.getAllByRole("button", { name: "Continue" })[1];
+
+const initialState = {
+ grpc: {
+ walletService: testWalletService
+ }
+};
+
+test("skip ProcessManagedTickets and show error", () => {
+ render(
+
+ );
+ user.click(getSkipButton());
+ expect(screen.getByText(testError)).toBeInTheDocument();
+ expect(mockCancel).toHaveBeenCalled();
+});
+
+test("do ProcessManagedTickets - in a private wallet", async () => {
+ mockUnlockLockAndGetAccountsAttempt();
+ render(, {
+ initialState
+ });
+ const continueButton = getContinueButton();
+ user.click(continueButton);
+
+ expect(screen.getByText("Passphrase")).toBeInTheDocument();
+
+ // cancel first
+ user.click(getCancelButton());
+
+ user.click(continueButton);
+ user.type(screen.getByLabelText("Private Passphrase"), testPassphrase);
+ user.click(getModalContinueButton());
+
+ await wait(() => expect(mockSend).toHaveBeenCalled());
+
+ expect(mockProcessManagedTickets).toHaveBeenNthCalledWith(
+ 1,
+ testWalletService,
+ defaultMockAvailableMainnetVsps[0].host,
+ mockPubkeys[`https://${mockAvailableMainnetVsps[0].host}`],
+ mixedAccountNumber,
+ changeAccountNumber
+ );
+
+ expect(mockProcessManagedTickets).toHaveBeenNthCalledWith(
+ 2,
+ testWalletService,
+ defaultMockAvailableMainnetVsps[3].host,
+ mockPubkeys[`https://${mockAvailableMainnetVsps[3].host}`],
+ mixedAccountNumber,
+ changeAccountNumber
+ );
+
+ expect(mockProcessManagedTickets).toHaveBeenNthCalledWith(
+ 3,
+ testWalletService,
+ defaultMockAvailableMainnetVsps[5].host,
+ mockPubkeys[`https://${mockAvailableMainnetVsps[5].host}`],
+ mixedAccountNumber,
+ changeAccountNumber
+ );
+
+ expect(mockGetVSPTrackedTickets).toHaveBeenCalledTimes(1);
+
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 1,
+ testWalletService,
+ VSP_FEE_PROCESS_ERRORED
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 2,
+ testWalletService,
+ VSP_FEE_PROCESS_STARTED
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 3,
+ testWalletService,
+ VSP_FEE_PROCESS_PAID
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 4,
+ testWalletService,
+ VSP_FEE_PROCESS_CONFIRMED
+ );
+
+ expect(mockGetAllVSPs).toHaveBeenCalled();
+ expect(mockGetVSPInfo).toHaveBeenCalled();
+});
+
+test("do ProcessManagedTickets - in a default wallet, available vps pubkeys have been already fetched", async () => {
+ selectors.getAvailableVSPsPubkeys = jest.fn(
+ () => mockAvailableMainnetVspsPubkeys
+ );
+ selectors.getMixedAccount = jest.fn(() => null);
+ selectors.getChangeAccount = jest.fn(() => null);
+ mockUnlockLockAndGetAccountsAttempt();
+ render(, {
+ initialState
+ });
+ const continueButton = getContinueButton();
+ user.click(continueButton);
+
+ expect(screen.getByText("Passphrase")).toBeInTheDocument();
+
+ // cancel first
+ user.click(getCancelButton());
+
+ user.click(continueButton);
+ user.type(screen.getByLabelText("Private Passphrase"), testPassphrase);
+ user.click(getModalContinueButton());
+
+ await wait(() => expect(mockSend).toHaveBeenCalled());
+
+ expect(mockProcessManagedTickets).toHaveBeenNthCalledWith(
+ 1,
+ testWalletService,
+ defaultMockAvailableMainnetVsps[0].host,
+ `${mockAvailableMainnetVsps[0].host}-pubkey`,
+ defaultAccountNumber,
+ defaultAccountNumber
+ );
+
+ expect(mockProcessManagedTickets).toHaveBeenNthCalledWith(
+ 2,
+ testWalletService,
+ defaultMockAvailableMainnetVsps[1].host,
+ `${mockAvailableMainnetVsps[1].host}-pubkey`,
+ defaultAccountNumber,
+ defaultAccountNumber
+ );
+
+ expect(mockProcessManagedTickets).toHaveBeenNthCalledWith(
+ 3,
+ testWalletService,
+ defaultMockAvailableMainnetVsps[2].host,
+ `${mockAvailableMainnetVsps[2].host}-pubkey`,
+ defaultAccountNumber,
+ defaultAccountNumber
+ );
+
+ expect(mockGetVSPTrackedTickets).toHaveBeenCalledTimes(1);
+
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 1,
+ testWalletService,
+ VSP_FEE_PROCESS_ERRORED
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 2,
+ testWalletService,
+ VSP_FEE_PROCESS_STARTED
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 3,
+ testWalletService,
+ VSP_FEE_PROCESS_PAID
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 4,
+ testWalletService,
+ VSP_FEE_PROCESS_CONFIRMED
+ );
+
+ expect(mockGetAllVSPs).not.toHaveBeenCalled();
+ expect(mockGetVSPInfo).not.toHaveBeenCalled();
+});
+
+test("do ProcessManagedTickets - in a default wallet, available vps pubkeys have been already fetched", async () => {
+ selectors.getAvailableVSPsPubkeys = jest.fn(
+ () => mockAvailableMainnetVspsPubkeys
+ );
+ selectors.getMixedAccount = jest.fn(() => null);
+ selectors.getChangeAccount = jest.fn(() => null);
+ mockUnlockLockAndGetAccountsAttempt();
+ render(, {
+ initialState
+ });
+ const continueButton = getContinueButton();
+ user.click(continueButton);
+
+ expect(screen.getByText("Passphrase")).toBeInTheDocument();
+
+ // cancel first
+ user.click(getCancelButton());
+
+ user.click(continueButton);
+ user.type(screen.getByLabelText("Private Passphrase"), testPassphrase);
+ user.click(getModalContinueButton());
+
+ await wait(() => expect(mockSend).toHaveBeenCalled());
+
+ expect(mockProcessManagedTickets).toHaveBeenNthCalledWith(
+ 1,
+ testWalletService,
+ defaultMockAvailableMainnetVsps[0].host,
+ `${mockAvailableMainnetVsps[0].host}-pubkey`,
+ defaultAccountNumber,
+ defaultAccountNumber
+ );
+
+ expect(mockProcessManagedTickets).toHaveBeenNthCalledWith(
+ 2,
+ testWalletService,
+ defaultMockAvailableMainnetVsps[1].host,
+ `${mockAvailableMainnetVsps[1].host}-pubkey`,
+ defaultAccountNumber,
+ defaultAccountNumber
+ );
+
+ expect(mockProcessManagedTickets).toHaveBeenNthCalledWith(
+ 3,
+ testWalletService,
+ defaultMockAvailableMainnetVsps[2].host,
+ `${mockAvailableMainnetVsps[2].host}-pubkey`,
+ defaultAccountNumber,
+ defaultAccountNumber
+ );
+
+ expect(mockGetVSPTrackedTickets).toHaveBeenCalledTimes(1);
+
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 1,
+ testWalletService,
+ VSP_FEE_PROCESS_ERRORED
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 2,
+ testWalletService,
+ VSP_FEE_PROCESS_STARTED
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 3,
+ testWalletService,
+ VSP_FEE_PROCESS_PAID
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 4,
+ testWalletService,
+ VSP_FEE_PROCESS_CONFIRMED
+ );
+
+ expect(mockGetAllVSPs).not.toHaveBeenCalled();
+ expect(mockGetVSPInfo).not.toHaveBeenCalled();
+});
+
+test("do ProcessManagedTickets - failed to fetch vsps", async () => {
+ mockUnlockLockAndGetAccountsAttempt();
+ mockGetAllVSPs = wallet.getAllVSPs = jest.fn(() => {
+ throw testError;
+ });
+ render(, {
+ initialState
+ });
+ const continueButton = getContinueButton();
+ user.click(continueButton);
+
+ user.type(screen.getByLabelText("Private Passphrase"), testPassphrase);
+ user.click(getModalContinueButton());
+
+ await wait(() => expect(mockSend).toHaveBeenCalled());
+
+ expect(mockGetAllVSPs).toHaveBeenCalled();
+ expect(mockProcessManagedTickets).not.toHaveBeenCalled();
+ expect(mockGetVSPTrackedTickets).not.toHaveBeenCalled();
+ expect(mockGetVSPTicketsByFeeStatus).not.toHaveBeenCalled();
+ expect(mockGetVSPInfo).not.toHaveBeenCalled();
+});
diff --git a/test/unit/components/views/GetStaredPage/SetupWallet/ProcessUnmanagedTickets.spec.js b/test/unit/components/views/GetStaredPage/SetupWallet/ProcessUnmanagedTickets.spec.js
new file mode 100644
index 0000000000..791647422a
--- /dev/null
+++ b/test/unit/components/views/GetStaredPage/SetupWallet/ProcessUnmanagedTickets.spec.js
@@ -0,0 +1,375 @@
+import ProcessUnmanagedTickets from "components/views/GetStartedPage/SetupWallet/ProcessUnmanagedTickets";
+import { render } from "test-utils.js";
+import { screen, wait } from "@testing-library/react";
+import user from "@testing-library/user-event";
+import * as sel from "selectors";
+import * as wal from "wallet";
+import * as arrs from "../../../../../../app/helpers/arrays";
+const selectors = sel;
+const wallet = wal;
+const arrays = arrs;
+import { DEFAULT_LIGHT_THEME_NAME } from "pi-ui";
+import {
+ VSP_FEE_PROCESS_ERRORED,
+ VSP_FEE_PROCESS_STARTED,
+ VSP_FEE_PROCESS_PAID,
+ VSP_FEE_PROCESS_CONFIRMED,
+ EXTERNALREQUEST_STAKEPOOL_LISTING
+} from "constants";
+import {
+ defaultMockAvailableMainnetVsps,
+ defaultMockAvailableTestnetVsps,
+ defaultMockAvailableInvalidVsps,
+ mockPubkeys,
+ fetchTimes
+} from "../../../../actions/vspMocks";
+import {
+ testBalances,
+ changeAccountNumber,
+ mixedAccountNumber,
+ defaultAccountNumber,
+ mockUnlockLockAndGetAccountsAttempt
+} from "../../../../actions/accountMocks";
+import { cloneDeep } from "lodash";
+
+const mockAvailableMainnetVsps = cloneDeep(defaultMockAvailableMainnetVsps);
+const mockSend = jest.fn(() => {});
+const mockCancel = jest.fn(() => {});
+
+const testPassphrase = "test-passphrase";
+const testWalletService = "test-wallet-service";
+const testError = "test-error";
+const testCustomVspHost = "custom-vsp-host";
+const testCustomVspHostPubkey = "test-custom-vsp-host-pubkey";
+
+let mockProcessUnmanagedTickets;
+let mockGetVSPTicketsByFeeStatus;
+let mockGetAllVSPs;
+let mockGetVSPInfo;
+beforeEach(() => {
+ selectors.getAvailableVSPsPubkeys = jest.fn(() => null);
+ selectors.balances = jest.fn(() => cloneDeep(testBalances));
+ selectors.unlockableAccounts = jest.fn(() =>
+ cloneDeep(testBalances).filter(
+ (acct) => acct.accountNumber < Math.pow(2, 31) - 1 && acct.encrypted
+ )
+ );
+ arrays.shuffle = jest.fn((arr) => arr);
+ selectors.getVSPInfoTimeoutTime = jest.fn(() => 100);
+ selectors.isTestNet = jest.fn(() => false);
+ selectors.resendVSPDVoteChoicesAttempt = jest.fn(() => false);
+ selectors.walletService = jest.fn(() => testWalletService);
+ selectors.defaultSpendingAccount = jest.fn(() => ({
+ value: defaultAccountNumber
+ }));
+ selectors.getMixedAccount = jest.fn(() => mixedAccountNumber);
+ selectors.getChangeAccount = jest.fn(() => changeAccountNumber);
+ mockGetAllVSPs = wallet.getAllVSPs = jest.fn(() => [
+ ...cloneDeep(mockAvailableMainnetVsps),
+ ...cloneDeep(defaultMockAvailableTestnetVsps),
+ ...cloneDeep(defaultMockAvailableInvalidVsps)
+ ]);
+ mockGetVSPInfo = wallet.getVSPInfo = jest.fn((host) => {
+ if (host === `https://${testCustomVspHost}`) {
+ return Promise.resolve({ data: { pubkey: testCustomVspHostPubkey } });
+ }
+ if (!mockPubkeys[host]) {
+ return Promise.reject("invalid host");
+ }
+
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ mockPubkeys[host] !== "invalid"
+ ? resolve({ data: { pubkey: mockPubkeys[host] } })
+ : mockPubkeys[host]
+ ? resolve({ data: {} })
+ : reject("invalid host");
+ }, fetchTimes[host]);
+ });
+ });
+ mockProcessUnmanagedTickets = wallet.processUnmanagedTicketsStartup = jest.fn(
+ () => {}
+ );
+ wallet.getVSPTrackedTickets = jest.fn(() => Promise.resolve());
+ mockGetVSPTicketsByFeeStatus = wallet.getVSPTicketsByFeeStatus = jest.fn(() =>
+ Promise.resolve({
+ ticketHashes: []
+ })
+ );
+
+ selectors.getAvailableVSPs = jest.fn(() => mockAvailableMainnetVsps);
+});
+
+const getSkipButton = () => screen.getByRole("button", { name: "Skip" });
+const getCancelButton = () => screen.getByRole("button", { name: "Cancel" });
+const getContinueButton = () =>
+ screen.getByRole("button", { name: "Continue" });
+const getModalContinueButton = () =>
+ screen.getAllByRole("button", { name: "Continue" })[1];
+
+const initialState = {
+ grpc: {
+ walletService: testWalletService
+ },
+ settings: {
+ currentSettings: {
+ theme: DEFAULT_LIGHT_THEME_NAME,
+ allowedExternalRequests: [EXTERNALREQUEST_STAKEPOOL_LISTING]
+ },
+ tempSettings: {
+ theme: DEFAULT_LIGHT_THEME_NAME,
+ allowedExternalRequests: [EXTERNALREQUEST_STAKEPOOL_LISTING]
+ }
+ }
+};
+
+test("skip ProcessUnmanagedTickets and show error", () => {
+ render(
+
+ );
+ expect(screen.getByText(testError)).toBeInTheDocument();
+ user.click(getSkipButton());
+ expect(mockCancel).toHaveBeenCalled();
+});
+
+test("do ProcessUnmanagedTickets - in a private wallet", async () => {
+ mockUnlockLockAndGetAccountsAttempt();
+ render(, {
+ initialState
+ });
+ const continueButton = getContinueButton();
+ expect(continueButton.disabled).toBe(true);
+
+ user.click(screen.getByText("Select VSP..."));
+ user.click(screen.getByText(mockAvailableMainnetVsps[0].host));
+ expect(screen.getByText("Loading")).toBeInTheDocument();
+ await wait(() => expect(getContinueButton().disabled).toBeFalsy());
+ expect(screen.queryByText("Loading")).not.toBeInTheDocument();
+ expect(
+ screen.getByText(mockAvailableMainnetVsps[0].host)
+ ).toBeInTheDocument();
+
+ user.click(continueButton);
+ expect(screen.getByText("Passphrase")).toBeInTheDocument();
+
+ // cancel first
+ user.click(getCancelButton());
+
+ user.click(continueButton);
+ user.type(screen.getByLabelText("Private Passphrase"), testPassphrase);
+ user.click(getModalContinueButton());
+
+ await wait(() => expect(mockSend).toHaveBeenCalled());
+
+ expect(mockProcessUnmanagedTickets).toHaveBeenCalledWith(
+ testWalletService,
+ defaultMockAvailableMainnetVsps[0].host,
+ mockPubkeys[`https://${mockAvailableMainnetVsps[0].host}`],
+ mixedAccountNumber,
+ changeAccountNumber
+ );
+
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 1,
+ testWalletService,
+ VSP_FEE_PROCESS_ERRORED
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 2,
+ testWalletService,
+ VSP_FEE_PROCESS_STARTED
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 3,
+ testWalletService,
+ VSP_FEE_PROCESS_PAID
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 4,
+ testWalletService,
+ VSP_FEE_PROCESS_CONFIRMED
+ );
+
+ expect(mockGetAllVSPs).not.toHaveBeenCalled();
+ expect(mockGetVSPInfo).toHaveBeenCalled();
+});
+
+test("do ProcessUnmanagedTickets - in a default wallet", async () => {
+ selectors.getMixedAccount = jest.fn(() => null);
+ selectors.getChangeAccount = jest.fn(() => null);
+ mockUnlockLockAndGetAccountsAttempt();
+ render(, {
+ initialState
+ });
+ const continueButton = getContinueButton();
+ expect(continueButton.disabled).toBe(true);
+
+ user.click(screen.getByText("Select VSP..."));
+ user.click(screen.getByText(mockAvailableMainnetVsps[0].host));
+ expect(screen.getByText("Loading")).toBeInTheDocument();
+ await wait(() => expect(getContinueButton().disabled).toBeFalsy());
+ expect(screen.queryByText("Loading")).not.toBeInTheDocument();
+ expect(
+ screen.getByText(mockAvailableMainnetVsps[0].host)
+ ).toBeInTheDocument();
+
+ user.click(continueButton);
+ expect(screen.getByText("Passphrase")).toBeInTheDocument();
+
+ // cancel first
+ user.click(getCancelButton());
+
+ user.click(continueButton);
+ user.type(screen.getByLabelText("Private Passphrase"), testPassphrase);
+ user.click(getModalContinueButton());
+
+ await wait(() => expect(mockSend).toHaveBeenCalled());
+
+ expect(mockProcessUnmanagedTickets).toHaveBeenCalledWith(
+ testWalletService,
+ defaultMockAvailableMainnetVsps[0].host,
+ mockPubkeys[`https://${mockAvailableMainnetVsps[0].host}`],
+ defaultAccountNumber,
+ defaultAccountNumber
+ );
+
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 1,
+ testWalletService,
+ VSP_FEE_PROCESS_ERRORED
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 2,
+ testWalletService,
+ VSP_FEE_PROCESS_STARTED
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 3,
+ testWalletService,
+ VSP_FEE_PROCESS_PAID
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 4,
+ testWalletService,
+ VSP_FEE_PROCESS_CONFIRMED
+ );
+
+ expect(mockGetAllVSPs).not.toHaveBeenCalled();
+ expect(mockGetVSPInfo).toHaveBeenCalled();
+});
+
+test("do ProcessUnmanagedTickets - vsp listing is not enabled", async () => {
+ mockUnlockLockAndGetAccountsAttempt();
+ render(, {
+ initialState: cloneDeep({
+ ...initialState,
+ settings: {
+ ...initialState.settings,
+ tempSettings: {
+ ...initialState.settings.tempSettings,
+ allowedExternalRequests: []
+ }
+ }
+ })
+ });
+
+ const continueButton = getContinueButton();
+ expect(continueButton.disabled).toBe(true);
+
+ user.type(screen.getByRole("combobox"), testCustomVspHost);
+ user.click(screen.getByText(`Create "${testCustomVspHost}"`));
+ expect(screen.getByText("Loading")).toBeInTheDocument();
+ await wait(() =>
+ expect(screen.getByText(testCustomVspHost)).toBeInTheDocument()
+ );
+
+ user.click(continueButton);
+ expect(screen.getByText("Passphrase")).toBeInTheDocument();
+
+ // cancel first
+ user.click(getCancelButton());
+
+ user.click(continueButton);
+ user.type(screen.getByLabelText("Private Passphrase"), testPassphrase);
+ user.click(getModalContinueButton());
+
+ await wait(() => expect(mockSend).toHaveBeenCalled());
+
+ expect(mockProcessUnmanagedTickets).toHaveBeenCalledWith(
+ testWalletService,
+ testCustomVspHost,
+ testCustomVspHostPubkey,
+ mixedAccountNumber,
+ changeAccountNumber
+ );
+
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 1,
+ testWalletService,
+ VSP_FEE_PROCESS_ERRORED
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 2,
+ testWalletService,
+ VSP_FEE_PROCESS_STARTED
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 3,
+ testWalletService,
+ VSP_FEE_PROCESS_PAID
+ );
+ expect(mockGetVSPTicketsByFeeStatus).toHaveBeenNthCalledWith(
+ 4,
+ testWalletService,
+ VSP_FEE_PROCESS_CONFIRMED
+ );
+
+ expect(mockGetAllVSPs).not.toHaveBeenCalled();
+ expect(mockGetVSPInfo).toHaveBeenCalled();
+});
+
+test("do ProcessUnManagedTickets - failed", async () => {
+ mockProcessUnmanagedTickets = wallet.processUnmanagedTicketsStartup = jest.fn(
+ () => {
+ throw testError;
+ }
+ );
+ mockUnlockLockAndGetAccountsAttempt();
+ render(, {
+ initialState
+ });
+ const continueButton = getContinueButton();
+ expect(continueButton.disabled).toBe(true);
+
+ user.click(screen.getByText("Select VSP..."));
+ user.click(screen.getByText(mockAvailableMainnetVsps[0].host));
+ expect(screen.getByText("Loading")).toBeInTheDocument();
+ await wait(() => expect(getContinueButton().disabled).toBeFalsy());
+ expect(screen.queryByText("Loading")).not.toBeInTheDocument();
+ expect(
+ screen.getByText(mockAvailableMainnetVsps[0].host)
+ ).toBeInTheDocument();
+
+ user.click(continueButton);
+ expect(screen.getByText("Passphrase")).toBeInTheDocument();
+
+ user.click(continueButton);
+ user.type(screen.getByLabelText("Private Passphrase"), testPassphrase);
+ user.click(getModalContinueButton());
+
+ await wait(() => expect(mockSend).toHaveBeenCalled());
+
+ expect(mockProcessUnmanagedTickets).toHaveBeenCalledWith(
+ testWalletService,
+ defaultMockAvailableMainnetVsps[0].host,
+ mockPubkeys[`https://${mockAvailableMainnetVsps[0].host}`],
+ mixedAccountNumber,
+ changeAccountNumber
+ );
+
+ expect(mockGetVSPTicketsByFeeStatus).not.toHaveBeenCalled();
+});