Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 67 additions & 12 deletions extension/e2e-tests/loadAccount.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,9 @@ test("Loads wallets data and token prices on Mainnet in batches", async ({
await page.getByTestId("account-view-account-name").click();
await expect(page.getByText("Wallets")).toBeVisible();

await page.getByText("Add a wallet").click();
// By testid: the footer button and the sheet it opens share the "Add
// wallet" label, so matching on text is ambiguous once the sheet is open.
await page.getByTestId("add-wallet").click();
await page.getByText("Create a new wallet").click();
await page.locator("#password-input").fill(PASSWORD);
await page.getByRole("button", { name: "Create New Address" }).click();
Expand All @@ -643,13 +645,30 @@ test("Loads wallets data and token prices on Mainnet in batches", async ({
await page.getByTestId("account-view-account-name").click();
await expect(page.getByText("Wallets")).toBeVisible();

await expect(page.getByText("GDF3…ZEFY - $6.52")).toBeVisible();
await expect(page.getByText("GCKU…67J2 - $5.71")).toBeVisible();
await expect(page.getByText("GDPX…YGJC - $6.11")).toBeVisible();
await expect(page.getByText("GC32…GTQA - $6.52")).toBeVisible();
await expect(page.getByText("GDY4…BQ7C - $6.93")).toBeVisible();
await expect(page.getByText("GBW2…OU3T - $7.34")).toBeVisible();
await expect(page.getByText("GARH…H6NU - $7.75")).toBeVisible();
// Address and balance now live in separate cells (no more concatenated
// "address - $balance" text), so scope each assertion to the row that
// contains the address and check its balance cell within that row.
await expect(
page.getByTestId("wallet-row-select").filter({ hasText: "GDF3…ZEFY" }),
).toContainText("$6.52");
await expect(
page.getByTestId("wallet-row-select").filter({ hasText: "GCKU…67J2" }),
).toContainText("$5.71");
await expect(
page.getByTestId("wallet-row-select").filter({ hasText: "GDPX…YGJC" }),
).toContainText("$6.11");
await expect(
page.getByTestId("wallet-row-select").filter({ hasText: "GC32…GTQA" }),
).toContainText("$6.52");
await expect(
page.getByTestId("wallet-row-select").filter({ hasText: "GDY4…BQ7C" }),
).toContainText("$6.93");
await expect(
page.getByTestId("wallet-row-select").filter({ hasText: "GBW2…OU3T" }),
).toContainText("$7.34");
await expect(
page.getByTestId("wallet-row-select").filter({ hasText: "GARH…H6NU" }),
).toContainText("$7.75");

expect(tokenPricesCallCount).toBe(7);
});
Expand All @@ -659,12 +678,48 @@ test("Renames wallets", async ({ page, extensionId, context }) => {
await page.getByTestId("account-view-account-name").click();
await expect(page.getByText("Wallets")).toBeVisible();

const walletRowOptions = await page.getByTestId("wallet-row-options").all();
await walletRowOptions[0].click();
await page.getByText("Rename wallet").click();
// Rename now acts on the active account via the header pencil button
// instead of the (removed) per-row ellipsis menu. This seed phrase has
// several accounts, but `loginToTestAccount` always leaves one of them
// active by default, so the header already targets an account without
// needing to select a row first.
await page.getByTestId("wallets-header-edit-name").click();
await page.getByTestId("rename-wallet-input").fill("New Wallet");
await page.getByText("Save").click();
await expect(page.getByText("New Wallet")).toBeVisible();

// The new name now renders in two places at once (the active-account
// header, plus that account's own row among several in this seed's
// account list), so scope each check instead of asserting on the bare
// text or the full row list, either of which would be an ambiguous match.
await expect(page.getByTestId("wallets-header")).toContainText("New Wallet");
await expect(
page.getByTestId("wallet-row-select").filter({ hasText: "New Wallet" }),
).toHaveCount(1);
});

test("Copies the active wallet address", async ({
page,
extensionId,
context,
}) => {
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
await loginToTestAccount({ page, extensionId, context });
await page.getByTestId("account-view-account-name").click();
await expect(page.getByText("Wallets")).toBeVisible();

await page.getByTestId("wallets-header-copy").click();

// The clipboard write is what the button exists for, so assert on the
// clipboard itself rather than only on the confirmation.
const copied = await page.evaluate(() => navigator.clipboard.readText());
expect(copied).toMatch(/^G[A-Z2-7]{55}$/);

// Confirmation is a toast, matching the QR screen, not an inline label on
// the button. It quotes the truncated address, which the header also
// shows, so scope the assertion to the toast region.
await expect(
page.getByText(/copied!$/).filter({ hasText: "Address" }),
).toBeVisible();
});

test("Loads collectibles data with successful metadata", async ({
Expand Down
19 changes: 7 additions & 12 deletions extension/src/helpers/stellarIdenticon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export function HSVtoRGB(h, s, v) {

var r, g, b, i, f, p, q, t;
if (arguments.length === 1) {
(s = h.s), (v = h.v), (h = h.h);
((s = h.s), (v = h.v), (h = h.h));
}
i = Math.floor(h * 6);
f = h * 6 - i;
Expand All @@ -127,22 +127,22 @@ export function HSVtoRGB(h, s, v) {
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0:
(r = v), (g = t), (b = p);
((r = v), (g = t), (b = p));
break;
case 1:
(r = q), (g = v), (b = p);
((r = q), (g = v), (b = p));
break;
case 2:
(r = p), (g = v), (b = t);
((r = p), (g = v), (b = t));
break;
case 3:
(r = p), (g = q), (b = v);
((r = p), (g = q), (b = v));
break;
case 4:
(r = t), (g = p), (b = v);
((r = t), (g = p), (b = v));
break;
case 5:
(r = v), (g = p), (b = q);
((r = v), (g = p), (b = q));
break;
}
return {
Expand Down Expand Up @@ -185,9 +185,4 @@ function createStellarIdenticon(stellarAddress, options = {}) {
return canvas;
}

export const getColorPubKey = (publicKey: string) => {
var bytes = publicKeyToBytes(publicKey);
return HSVtoRGB(bytes[0] / 255, 0.7, 0.8);
};

export default createStellarIdenticon;
81 changes: 37 additions & 44 deletions extension/src/popup/components/account/WalletRow/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Icon } from "@stellar/design-system";
import classNames from "classnames";

import { IdenticonImg } from "popup/components/identicons/IdenticonImg";
import { truncatedPublicKey } from "helpers/stellar";
import { getColorPubKey } from "helpers/stellarIdenticon";
Comment thread
CassioMG marked this conversation as resolved.
import { WalletType } from "@shared/constants/hardwareWallet";
import IconEllipsis from "popup/assets/icon-ellipsis.svg";

import "./styles.scss";

Expand All @@ -20,7 +17,6 @@ interface WalletRowProps {
isSelected: boolean;
publicKey: string;
onClick: (publicKey: string) => unknown;
setOptionsOpen: (publicKey: string) => unknown;
}

export const WalletRow = ({
Expand All @@ -32,58 +28,55 @@ export const WalletRow = ({
isSelected,
publicKey,
onClick,
setOptionsOpen,
}: WalletRowProps) => {
const { t } = useTranslation();
const shortPublicKey = truncatedPublicKey(publicKey);
const identiconWrapperStyles = classNames("identicon-wrapper", {
"is-selected": isSelected,
});
const selectedBorderColorRgb = getColorPubKey(publicKey);
const isSelectedColor = `rgb(${selectedBorderColorRgb.r} ${selectedBorderColorRgb.g} ${selectedBorderColorRgb.b} / 100%`;
const borderColor = isSelected ? isSelectedColor : "#232323";

let subTitle = accountValue
? `${shortPublicKey} - ${accountValue}`
: shortPublicKey;
if (isFetchingTokenPrices && !accountValue) {
subTitle = `${shortPublicKey} - ...`;
}
const { t } = useTranslation();
const walletIdentifier =
hardwareWalletType || isImported ? t("Imported") : "";
const isImportedWallet = !!hardwareWalletType || isImported;

// Balance is its own cell now. While prices are still loading we show an
// ellipsis rather than an empty gap, matching the previous subtitle behavior.
const balanceLabel = accountValue || (isFetchingTokenPrices ? "..." : "");

return (
<div className="WalletRow">
<div
className="WalletRow__identicon"
onClick={() => onClick(publicKey)}
data-testid="wallet-row-select"
>
<div
className={identiconWrapperStyles}
style={{ borderColor: borderColor }}
>
// role/aria-current match BalanceRow, the sibling list row. The active
// account is otherwise conveyed only by the badge on its avatar, which
// says nothing to a screen reader.
<div
className="WalletRow"
onClick={() => onClick(publicKey)}
role="button"
aria-current={isSelected ? true : undefined}
data-testid="wallet-row-select"
Comment thread
CassioMG marked this conversation as resolved.
>
<div className="WalletRow__identicon">
<div className="identicon-wrapper">
<IdenticonImg publicKey={publicKey} />
</div>
{isSelected ? (
<div
className="WalletRow__identicon__selected-check"
style={{ backgroundColor: isSelectedColor }}
>
<Icon.Check width="14px" height="14px" />
<div className="WalletRow__identicon__selected-check">
<Icon.Check />
</div>
) : null}
</div>
<div className="WalletRow__details" onClick={() => onClick(publicKey)}>
<div className="WalletRow__details">
<p className="detail-name">{accountName}</p>
<p className="detail-short-key">{subTitle}</p>
<p className="detail-short-key">{walletIdentifier}</p>
<p className="detail-address">
{shortPublicKey}
{isImportedWallet ? (
<>
{/* Decorative separator; the address and label are already
distinct to a screen reader without it. */}
<span className="detail-separator" aria-hidden="true">
•
</span>
<span className="detail-imported">{t("Imported")}</span>
</>
) : null}
</p>
</div>
<div
className="WalletRow__options"
data-testid="wallet-row-options"
onClick={() => setOptionsOpen(publicKey)}
>
<img src={IconEllipsis} alt={t("wallet action options")} />
<div className="WalletRow__balance" data-testid="wallet-row-balance">
{balanceLabel}
</div>
</div>
);
Expand Down
Loading
Loading