Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/wise-rivers-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ledger-live-desktop": minor
---

improve market coverage on mvvm
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import React from "react";
import { render, screen } from "tests/testSetup";
import { RowItemView } from "../RowItemView";
import { MOCK_MARKET_CURRENCY_DATA } from "@ledgerhq/live-common/market/utils/fixtures";
import { MarketAction, RowItemViewProps } from "../types";

const mockCurrency = MOCK_MARKET_CURRENCY_DATA[0];

const mockOnBuy = jest.fn();
const mockOnSwap = jest.fn();
const mockOnStake = jest.fn();

const buyAction: MarketAction = { type: "buy", label: "Buy", onClick: mockOnBuy };
const swapAction: MarketAction = { type: "swap", label: "Swap", onClick: mockOnSwap };
const stakeAction: MarketAction = { type: "stake", label: "Earn", onClick: mockOnStake };

function createDefaultProps(overrides: Partial<RowItemViewProps> = {}): RowItemViewProps {
return {
style: {},
currency: mockCurrency,
counterCurrency: "usd",
locale: "en",
isStarred: false,
hasActions: false,
actions: [],
currentPriceChangePercentage: 2.5,
onCurrencyClick: jest.fn(),
onStarClick: jest.fn(),
...overrides,
};
}

describe("RowItemView", () => {
beforeEach(() => {
jest.clearAllMocks();
});

it("should render currency name and ticker", () => {
render(<RowItemView {...createDefaultProps()} />);

expect(screen.getByText("Bitcoin")).toBeVisible();
expect(screen.getByText("BTC")).toBeVisible();
});

it("should render marketcap rank", () => {
render(<RowItemView {...createDefaultProps()} />);

expect(screen.getByText("1")).toBeVisible();
});

it("should render row when marketcapRank is present", () => {
render(<RowItemView {...createDefaultProps()} />);

const row = screen.getByTestId("market-BTC-row");
expect(row).toBeVisible();
});

it("should render CryptoIcon when ledgerIds has entries", () => {
render(<RowItemView {...createDefaultProps()} />);

expect(screen.queryByAltText("currency logo")).toBeNull();
});

it("should render img fallback when ledgerIds is empty", () => {
const currency = { ...mockCurrency, ledgerIds: [] };
render(<RowItemView {...createDefaultProps({ currency })} />);

expect(screen.getByAltText("currency logo")).toBeVisible();
});

it("should show Buy/Swap/Stake buttons when hasActions and all available", () => {
render(
<RowItemView
{...createDefaultProps({
hasActions: true,
actions: [buyAction, swapAction, stakeAction],
})}
/>,
);

expect(screen.getByTestId("market-BTC-buy-button")).toBeVisible();
expect(screen.getByTestId("market-BTC-swap-button")).toBeVisible();
expect(screen.getByTestId("market-BTC-stake-button")).toBeVisible();
});

it("should hide action buttons when hasActions is false", () => {
render(<RowItemView {...createDefaultProps({ hasActions: false, actions: [] })} />);

expect(screen.queryByTestId("market-BTC-buy-button")).toBeNull();
expect(screen.queryByTestId("market-BTC-swap-button")).toBeNull();
expect(screen.queryByTestId("market-BTC-stake-button")).toBeNull();
});

it("should show only Buy button when only buy action is provided", () => {
render(
<RowItemView
{...createDefaultProps({
hasActions: true,
actions: [buyAction],
})}
/>,
);

expect(screen.getByTestId("market-BTC-buy-button")).toBeVisible();
expect(screen.queryByTestId("market-BTC-swap-button")).toBeNull();
expect(screen.queryByTestId("market-BTC-stake-button")).toBeNull();
});

it("should render '-' when currentPriceChangePercentage is undefined", () => {
render(<RowItemView {...createDefaultProps({ currentPriceChangePercentage: undefined })} />);

const priceChangeCell = screen.getByTestId("market-price-change");
expect(priceChangeCell).toHaveTextContent("-");
});

it("should render FormattedVal when currentPriceChangePercentage is defined", () => {
render(<RowItemView {...createDefaultProps({ currentPriceChangePercentage: 5.5 })} />);

const priceChangeCell = screen.getByTestId("market-price-change");
expect(priceChangeCell).not.toHaveTextContent("-");
});

it("should render star button when isStarred is true", () => {
render(<RowItemView {...createDefaultProps({ isStarred: true })} />);

expect(screen.getByTestId("market-BTC-star-button")).toBeVisible();
});

it("should render star button when isStarred is false", () => {
render(<RowItemView {...createDefaultProps({ isStarred: false })} />);

expect(screen.getByTestId("market-BTC-star-button")).toBeVisible();
});

it("should call onCurrencyClick on row click", async () => {
const onCurrencyClick = jest.fn();
const { user } = render(<RowItemView {...createDefaultProps({ onCurrencyClick })} />);

await user.click(screen.getByTestId("market-BTC-row"));
expect(onCurrencyClick).toHaveBeenCalledTimes(1);
});

it("should call onStarClick on star button click", async () => {
const onStarClick = jest.fn();
const { user } = render(<RowItemView {...createDefaultProps({ onStarClick })} />);

await user.click(screen.getByTestId("market-BTC-star-button"));
expect(onStarClick).toHaveBeenCalledTimes(1);
});

it("should call action onClick when Buy button is clicked", async () => {
const { user } = render(
<RowItemView
{...createDefaultProps({
hasActions: true,
actions: [buyAction],
})}
/>,
);

await user.click(screen.getByTestId("market-BTC-buy-button"));
expect(mockOnBuy).toHaveBeenCalledTimes(1);
});

it("should call action onClick when Swap button is clicked", async () => {
const { user } = render(
<RowItemView
{...createDefaultProps({
hasActions: true,
actions: [swapAction],
})}
/>,
);

await user.click(screen.getByTestId("market-BTC-swap-button"));
expect(mockOnSwap).toHaveBeenCalledTimes(1);
});

it("should call action onClick when Stake button is clicked", async () => {
const { user } = render(
<RowItemView
{...createDefaultProps({
hasActions: true,
actions: [stakeAction],
})}
/>,
);

await user.click(screen.getByTestId("market-BTC-stake-button"));
expect(mockOnStake).toHaveBeenCalledTimes(1);
});

it("should render sparkline chart when sparklineIn7d exists", async () => {
const currency = {
...mockCurrency,
sparklineIn7d: { path: "M0 0L1 1", viewBox: "0 0 100 50", isPositive: true },
};
const { user } = render(<RowItemView {...createDefaultProps({ currency })} />);

await user.hover(screen.getByTestId("market-small-graph"));

const graphCell = screen.getByTestId("market-small-graph");
expect(graphCell.querySelector("svg")).toBeVisible();
});

it("should not render sparkline chart when sparklineIn7d is undefined", () => {
const currency = { ...mockCurrency, sparklineIn7d: undefined };
render(<RowItemView {...createDefaultProps({ currency })} />);

const graphCell = screen.getByTestId("market-small-graph");
expect(graphCell.querySelector("svg")).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ jest.mock("react-router", () => ({
}));

jest.mock("LLD/features/Market/hooks/useMarketActions", () => ({
Page: { Market: "Page Market" },
useMarketActions: jest.fn(),
Page: { Market: "Page Market", MarketCoin: "Page Market Coin" },
useMarketActions: jest.fn(() => ({
onBuy: mockOnBuy,
onSwap: mockOnSwap,
onStake: mockOnStake,
availableOnBuy: false,
availableOnSwap: false,
availableOnStake: false,
})),
}));

jest.mock("~/renderer/hooks/useGetStakeLabelLocaleBased", () => ({
Expand Down Expand Up @@ -70,6 +77,28 @@ describe("useRowItemViewModel", () => {
expect(result.current.hasActions).toBe(false);
});

it("returns hasActions=false when all availableOn flags are false", () => {
mockedUseMarketActions.mockReturnValue({
onBuy: mockOnBuy,
onSwap: mockOnSwap,
onStake: mockOnStake,
availableOnBuy: false,
availableOnSwap: false,
availableOnStake: false,
});

const { result } = renderHook(() =>
useRowItemViewModel({
currency: bitcoinCurrency,
toggleStar: jest.fn(),
range: "24h",
}),
);

expect(result.current.actions).toEqual([]);
expect(result.current.hasActions).toBe(false);
});

it("returns only available actions when currency has ledgerIds", () => {
mockedUseMarketActions.mockReturnValue({
...allActionsAvailable,
Expand Down Expand Up @@ -138,6 +167,22 @@ describe("useRowItemViewModel", () => {
});
});

it("does not navigate when currency is null", () => {
const { result } = renderHook(() =>
useRowItemViewModel({
currency: null,
toggleStar: jest.fn(),
range: "24h",
}),
);

act(() => {
result.current.onCurrencyClick();
});

expect(mockNavigate).not.toHaveBeenCalled();
});

it("onStarClick calls toggleStar and prevents propagation", () => {
const mockToggleStar = jest.fn();

Expand Down
Loading
Loading