Skip to content

Commit

Permalink
chore: merge hotfixes into main (#385)
Browse files Browse the repository at this point in the history
  • Loading branch information
ItsANameToo committed Apr 17, 2023
2 parents e7b0d7a + 0be9feb commit 0598540
Show file tree
Hide file tree
Showing 44 changed files with 4,463 additions and 2,579 deletions.
41 changes: 41 additions & 0 deletions src/app/App.blocks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ vi.mock("@/domains/profile/routing", async () => {

describe("App Router", () => {
beforeEach(() => {
process.env.REACT_APP_IS_UNIT = undefined;
history.push("/");
});

Expand Down Expand Up @@ -208,6 +209,46 @@ describe("App Main", () => {
dismissToastSpy.mockRestore();
});

it("should show warning toast when profile has ledger wallets in an incompatible browser", async () => {
const profile = env.profiles().findById(getDefaultProfileId());

const wallet = await profile.walletFactory().fromAddressWithDerivationPath({
address: "FwW39QnQvQRQJF2MCfAoKvsX4DJ28jq",
coin: "ARK",
network: "ark.devnet",
path: "m/44'/1'/0'/0/3",
});

profile.wallets().push(wallet);

process.env.TEST_PROFILES_RESTORE_STATUS = undefined;

const restoredMock = vi.spyOn(profile.status(), "isRestored").mockReturnValue(false);
const warningToastSpy = vi.spyOn(toasts, "warning").mockImplementation(vi.fn());

const profileUrl = `/profiles/${getDefaultProfileId()}/exchange`;
history.push(profileUrl);

render(
<Route path="/profiles/:profileId/exchange">
<Main />
</Route>,
{
history,
route: profileUrl,
withProviders: true,
},
);

await waitFor(() => expect(history.location.pathname).toBe(profileUrl));
await waitFor(() => expect(warningToastSpy).toHaveBeenCalled());

profile.wallets().forget(wallet.id());

restoredMock.mockRestore();
warningToastSpy.mockRestore();
});

it("should redirect to login page if profile changes", async () => {
let onProfileUpdated: () => void;
const { useProfileSynchronizer } = useProfileSynchronizerHook;
Expand Down
10 changes: 7 additions & 3 deletions src/app/App.blocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const Main: React.VFC = () => {
const { env, persist, isEnvironmentBooted, setIsEnvironmentBooted } = useEnvironmentContext();
const isOnline = useNetworkStatus();
const history = useHistory();
const syncingMessageToastId = useRef<number | string>();

const { resetAccentColor } = useAccentColor();
const { resetTheme } = useTheme();
Expand All @@ -89,6 +90,9 @@ const Main: React.VFC = () => {
const { t } = useTranslation();

useProfileSynchronizer({
onLedgerCompatibilityError: () => {
toasts.warning(t("COMMON.LEDGER_COMPATIBILITY_ERROR_LONG"), { autoClose: false });
},
onProfileRestoreError: () =>
history.push({
pathname: "/",
Expand All @@ -106,15 +110,15 @@ const Main: React.VFC = () => {
setShowMobileNavigation(false);
},
onProfileSyncComplete: async () => {
await toasts.dismiss();
await toasts.dismiss(syncingMessageToastId.current);
toasts.success(t("COMMON.PROFILE_SYNC_COMPLETED"));
},
onProfileSyncError: async (failedNetworkNames, retryProfileSync) => {
await toasts.dismiss();
await toasts.dismiss(syncingMessageToastId.current);
toasts.warning(<SyncErrorMessage failedNetworkNames={failedNetworkNames} onRetry={retryProfileSync} />);
},
onProfileSyncStart: () => {
toasts.warning(t("COMMON.PROFILE_SYNC_STARTED"), { autoClose: false });
syncingMessageToastId.current = toasts.warning(t("COMMON.PROFILE_SYNC_STARTED"), { autoClose: false });
},
onProfileUpdated: () => {
history.replace("/");
Expand Down
36 changes: 20 additions & 16 deletions src/app/components/WalletListItem/WalletListItem.blocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { useWalletOptions } from "@/domains/wallet/pages/WalletDetails/hooks/use
import { Skeleton } from "@/app/components/Skeleton";
import { useWalletActions } from "@/domains/wallet/hooks";
import { useWalletTransactions } from "@/domains/wallet/pages/WalletDetails/hooks/use-wallet-transactions";
import { isLedgerWalletCompatible } from "@/utils/wallet-utils";

const starIconDimensions: [number, number] = [18, 18];
const excludedIcons = ["isStarred"];
Expand Down Expand Up @@ -388,7 +389,8 @@ export const ButtonsCell: React.VFC<ButtonsCellProperties> = ({ wallet, isCompac
const { primaryOptions, secondaryOptions } = useWalletOptions(wallet);

const isRestoring = !wallet.hasBeenFullyRestored();
const isButtonDisabled = wallet.balance() === 0 || isRestoring || !wallet.hasSyncedWithNetwork();
const isButtonDisabled =
wallet.balance() === 0 || isRestoring || !wallet.hasSyncedWithNetwork() || !isLedgerWalletCompatible(wallet);

const handleStopPropagation = useCallback((event: React.MouseEvent) => {
event.preventDefault();
Expand All @@ -397,21 +399,23 @@ export const ButtonsCell: React.VFC<ButtonsCellProperties> = ({ wallet, isCompac

return (
<TableCell variant="end" size="sm" innerClassName="justify-end text-theme-secondary-text" isCompact={isCompact}>
<div onClick={handleStopPropagation}>
<Button
data-testid="WalletListItem__send-button"
size={isCompact ? "icon" : undefined}
disabled={isButtonDisabled}
variant={isCompact ? "transparent" : "secondary"}
className={cn({
"my-auto": !isCompact,
"text-theme-primary-600 hover:text-theme-primary-700": isCompact,
})}
onClick={onSend}
>
{t("COMMON.SEND")}
</Button>
</div>
<Tooltip content={isLedgerWalletCompatible(wallet) ? "" : t("COMMON.LEDGER_COMPATIBILITY_ERROR")}>
<div onClick={handleStopPropagation}>
<Button
data-testid="WalletListItem__send-button"
size={isCompact ? "icon" : undefined}
disabled={isButtonDisabled}
variant={isCompact ? "transparent" : "secondary"}
className={cn({
"my-auto": !isCompact,
"text-theme-primary-600 hover:text-theme-primary-700": isCompact,
})}
onClick={onSend}
>
{t("COMMON.SEND")}
</Button>
</div>
</Tooltip>
<div data-testid="WalletListItem__more-button" className={cn({ "ml-3": !isCompact })}>
<Dropdown
toggleContent={
Expand Down
33 changes: 33 additions & 0 deletions src/app/contexts/Ledger/hooks/connection.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -546,5 +546,38 @@ describe("Use Ledger Connection", () => {
coinSpy.mockRestore();
listenSpy.mockRestore();
});

it("should fail to connect and throw a browser compatibility error", async () => {
process.env.REACT_APP_IS_UNIT = undefined;

const listenSpy = mockNanoXTransport();

const connectSpy = vi.spyOn(wallet.coin().ledger(), "connect").mockImplementation(() => {
throw new Error("COMPATIBILITY_ERROR");
});

render(
<Component
retryOptions={{
factor: 1,
minTimeout: 10,
randomize: false,
retries: 2,
}}
/>,
);

userEvent.click(screen.getByText("Connect"));
await waitFor(() => expect(screen.queryByText(LedgerWaitingDevice)).not.toBeInTheDocument(), {
timeout: 4000,
});

await expect(
screen.findByText(walletTranslations.MODAL_LEDGER_WALLET.COMPATIBILITY_ERROR),
).resolves.toBeVisible();

connectSpy.mockReset();
listenSpy.mockReset();
});
});
});
15 changes: 12 additions & 3 deletions src/app/contexts/Ledger/hooks/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "r
import { useTranslation } from "react-i18next";

import { connectionReducer, defaultConnectionState } from "./connection.state";
import { openTransport, closeDevices } from "@/app/contexts/Ledger/transport";
import { openTransport, closeDevices, isLedgerTransportSupported } from "@/app/contexts/Ledger/transport";
import { useEnvironmentContext } from "@/app/contexts/Environment";
import { toasts } from "@/app/services";
import { useLedgerImport } from "@/app/contexts/Ledger/hooks/import";
Expand Down Expand Up @@ -62,6 +62,10 @@ export const useLedgerConnection = () => {
//
}

if (error.message === "COMPATIBILITY_ERROR") {
return dispatch({ message: t("WALLETS.MODAL_LEDGER_WALLET.COMPATIBILITY_ERROR"), type: "failed" });
}

if (error.statusText === "UNKNOWN_ERROR") {
return dispatch({ message: t("WALLETS.MODAL_LEDGER_WALLET.GENERIC_CONNECTION_ERROR"), type: "failed" });
}
Expand All @@ -87,14 +91,19 @@ export const useLedgerConnection = () => {

const connect = useCallback(
async (profile: Contracts.IProfile, coin: string, network: string, retryOptions?: Options) => {
const coinInstance = profile.coins().set(coin, network);

if (!isLedgerTransportSupported()) {
handleLedgerConnectionError({ message: "COMPATIBILITY_ERROR" }, coinInstance);
return;
}

const options = retryOptions || { factor: 1, randomize: false, retries: 50 };
await resetConnectionState();

dispatch({ type: "waiting" });
abortRetryReference.current = false;

const coinInstance = profile.coins().set(coin, network);

try {
await persistLedgerConnection({
coin: coinInstance,
Expand Down
40 changes: 39 additions & 1 deletion src/app/contexts/Ledger/transport.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
/* eslint-disable @typescript-eslint/require-await */
import { Contracts } from "@ardenthq/sdk-profiles";

import { supportedTransport, connectedTransport, openTransport, closeDevices } from "./transport";
import {
supportedTransport,
connectedTransport,
openTransport,
closeDevices,
isLedgerTransportSupported,
} from "./transport";
import {
env,
getDefaultProfileId,
Expand Down Expand Up @@ -107,4 +113,36 @@ describe("Ledger transport", () => {

mockDevices.mockRestore();
});

it("#isLedgerTransportSupported", async () => {
ledgerListenSpy.mockRestore();

process.env.REACT_APP_IS_UNIT = undefined;
window.navigator.usb = "1";
window.navigator.hid = "1";

const mockDevices = mockLedgerDevicesList(
vi.fn().mockImplementation(() => [
{
close: vi.fn(),
open: vi.fn(),
},
{
close: () => {
throw new Error("error");
},
open: vi.fn(),
},
]),
);

expect(isLedgerTransportSupported()).toBe(true);

window.navigator.usb = undefined;
window.navigator.hid = undefined;

expect(isLedgerTransportSupported()).toBe(false);

mockDevices.mockRestore();
});
});
10 changes: 10 additions & 0 deletions src/app/contexts/Ledger/transport.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { LedgerTransportFactory } from "@ardenthq/sdk-ledger";
import { Contracts } from "@ardenthq/sdk";
import { isUnit } from "@/utils/test-helpers";

export const supportedTransport = async () => new LedgerTransportFactory().supportedTransport();

Expand All @@ -16,6 +17,15 @@ export const closeDevices = async () => {
}
};

export const isLedgerTransportSupported = () => {
if (isUnit()) {
return true;
}

//@ts-ignore
return !!navigator.usb || !!navigator.hid;
};

// Assumes user has already granted permission.
// Won't trigger the native permission dialog.
export const connectedTransport = async () => {
Expand Down
16 changes: 16 additions & 0 deletions src/app/hooks/use-document-title.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,20 @@ describe("use-document-title", () => {
documentTitleGetSpy.mockRestore();
documentTitleSetSpy.mockRestore();
});

it("should set arkvault title if custom title is not provided", () => {
const originalTitle = "Original title";
const ArkVaultTitle = "ARKVault";

let documentTitle = originalTitle;
const documentTitleSetSpy = vi.spyOn(document, "title", "set").mockImplementation((value) => {
documentTitle = value;
});

renderHook(() => useDocumentTitle());

expect(documentTitle).toBe(ArkVaultTitle);

documentTitleSetSpy.mockRestore();
});
});
47 changes: 47 additions & 0 deletions src/app/hooks/use-profile-synchronizer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1069,4 +1069,51 @@ describe("useProfileStatusWatcher", () => {
expect(onProfileSyncComplete).not.toHaveBeenCalled();
expect(onProfileSyncError).not.toHaveBeenCalled();
});

it("should emit error for incompatible ledger wallets", async () => {
const profile = env.profiles().findById(getDefaultProfileId());

vi.spyOn(profile.status(), "isRestored").mockReturnValue(false);
process.env.TEST_PROFILES_RESTORE_STATUS = undefined;
process.env.REACT_APP_IS_UNIT = undefined;

const ledgerWallet = await profile.walletFactory().fromAddressWithDerivationPath({
address: "FwW39QnQvQRQJF2MCfAoKvsX4DJ28jq",
coin: "ARK",
network: "ark.devnet",
path: "m/44'/1'/0'/0/3",
});

profile.wallets().push(ledgerWallet);

history.push(dashboardURL);

const onLedgerCompatibilityError = vi.fn();

const Component = () => {
useProfileSynchronizer({
onLedgerCompatibilityError,
});

return <div data-testid="ProfileSynced">test</div>;
};

render(
<Route path="/profiles/:profileId/dashboard">
<Component />
</Route>,
{
history,
route: dashboardURL,
},
);

await expect(screen.findByTestId("ProfileSynced")).resolves.toBeVisible();

await waitFor(() => {
expect(onLedgerCompatibilityError).toHaveBeenCalledTimes(1);
});

vi.restoreAllMocks();
});
});
Loading

1 comment on commit 0598540

@vercel
Copy link

@vercel vercel bot commented on 0598540 Apr 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.