refactor: encapsulate instrument chart data
This commit is contained in:
@@ -321,6 +321,7 @@ describe("bond page", () => {
|
||||
fireEvent.click(screen.getByRole("button", { name: /Цены в таблице/ }));
|
||||
await waitFor(() => expect(screen.getByText("99,25 %")).toBeVisible());
|
||||
expect(screen.queryByText("99,25 ₽")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Текущая средняя покупка/)).not.toBeInTheDocument();
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Прошедшие и недатированные купоны" }),
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "@/features/instrument-details";
|
||||
import { useScreenSync } from "@/features/tbank-connection";
|
||||
import { apiErrorCode } from "@/shared/api";
|
||||
import { formatDate, instrumentLogoUrl } from "@/shared/lib";
|
||||
import { instrumentLogoUrl } from "@/shared/lib";
|
||||
|
||||
const periods: BondPeriod[] = ["month", "halfYear", "year", "all"];
|
||||
const filters = ["all", "trades", "coupons", "other"] as const;
|
||||
@@ -80,18 +80,9 @@ function useBondPageVm() {
|
||||
setParameters(next, { replace: true });
|
||||
};
|
||||
|
||||
const chartData
|
||||
= candles.data?.candles.map(candle => ({
|
||||
...candle,
|
||||
date: formatDate(candle.time),
|
||||
price: Number(candle.close),
|
||||
})) ?? [];
|
||||
|
||||
const search = parameters.size > 0 ? `?${parameters.toString()}` : "";
|
||||
|
||||
return {
|
||||
averageLine:
|
||||
null,
|
||||
bond: bond.data,
|
||||
bondState: section(bond, uid ? sync.retry : () => bond.refetch()),
|
||||
candles: candles.data,
|
||||
@@ -100,7 +91,6 @@ function useBondPageVm() {
|
||||
bond.data?.slug && bond.data.slug !== bondId
|
||||
? `/bonds/${encodeURIComponent(bond.data.slug)}${search}`
|
||||
: null,
|
||||
chartData,
|
||||
code: apiErrorCode(bond.error),
|
||||
coupons: coupons.data,
|
||||
couponsState: section(coupons, sync.retry),
|
||||
@@ -119,8 +109,6 @@ function useBondPageVm() {
|
||||
?? null,
|
||||
})) ?? [],
|
||||
filter,
|
||||
hasIncompleteCandle:
|
||||
candles.data?.candles.some(candle => !candle.isComplete) ?? false,
|
||||
hasPrevious: history.hasPrevious,
|
||||
logoSrc: instrumentLogoUrl(bond.data?.logoName),
|
||||
metrics: metrics.data,
|
||||
@@ -143,7 +131,6 @@ function useBondPageVm() {
|
||||
period,
|
||||
position: position.data,
|
||||
positionState: section(position, sync.retry),
|
||||
priceRows: chartData.toReversed(),
|
||||
quote: quote.data,
|
||||
quoteState: section(quote, sync.retry),
|
||||
refresh: sync.refresh,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { MemoryRouter, Route, Routes, useLocation } from "react-router";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
@@ -78,6 +78,7 @@ const quote = {
|
||||
function setup(
|
||||
options: {
|
||||
ambiguousTicker?: boolean;
|
||||
candles?: NonNullable<StockPageVm["candles"]>["candles"];
|
||||
cursorError?: string;
|
||||
failCompany?: boolean;
|
||||
failStock?: boolean;
|
||||
@@ -124,7 +125,7 @@ function setup(
|
||||
|
||||
resource(
|
||||
{
|
||||
candles: [],
|
||||
candles: options.candles ?? [],
|
||||
currency: "RUB",
|
||||
historyFrom: null,
|
||||
observedAt: quote.observedAt,
|
||||
@@ -352,7 +353,10 @@ describe("stock page", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
it("opens by UID, displays server calculations and distinguishes company and personal dividends", async () => {
|
||||
setup();
|
||||
setup({ candles: [
|
||||
{ close: "300.125", isComplete: true, time: "2026-09-09T00:00:00Z" },
|
||||
{ close: "301.123456789", isComplete: false, time: "2026-09-10T00:00:00Z" },
|
||||
] });
|
||||
expect(
|
||||
await screen.findByRole("heading", { name: "Тестовая акция" }),
|
||||
).toBeVisible();
|
||||
@@ -376,6 +380,14 @@ describe("stock page", () => {
|
||||
screen.getByRole("button", { name: "История дивидендов компании" }),
|
||||
);
|
||||
await waitFor(() => expect(screen.getByText("13.10.2025")).toBeVisible());
|
||||
expect(screen.getByText("Текущая средняя покупка: 200 ₽")).toBeVisible();
|
||||
fireEvent.click(screen.getByRole("button", { name: /Цены в таблице/ }));
|
||||
const rows = within(await screen.findByRole("table")).getAllByRole("row");
|
||||
expect(rows[1]).toHaveTextContent("10.09.2026");
|
||||
expect(rows[1]).toHaveTextContent("301,123456789 ₽");
|
||||
expect(rows[1]).toHaveTextContent("Формируется");
|
||||
expect(rows[2]).toHaveTextContent("09.09.2026");
|
||||
await waitFor(() => expect(screen.getByText("300,125 ₽")).toBeVisible());
|
||||
});
|
||||
it("keeps sold-position history and market content when company data fails", async () => {
|
||||
setup({ failCompany: true, noPosition: true });
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "@/features/instrument-details";
|
||||
import { useScreenSync } from "@/features/tbank-connection";
|
||||
import { apiErrorCode } from "@/shared/api";
|
||||
import { formatDate, instrumentLogoUrl } from "@/shared/lib";
|
||||
import { instrumentLogoUrl } from "@/shared/lib";
|
||||
|
||||
const periods: StockPeriod[] = ["month", "halfYear", "year", "all"];
|
||||
const filters = ["all", "trades", "dividends", "other"] as const;
|
||||
@@ -80,35 +80,16 @@ function useStockPageVm() {
|
||||
setParameters(next, { replace: true });
|
||||
};
|
||||
|
||||
const chartData
|
||||
= candles.data?.candles.map(candle => ({
|
||||
...candle,
|
||||
date: formatDate(candle.time),
|
||||
price: Number(candle.close),
|
||||
})) ?? [];
|
||||
|
||||
const average = position.data?.averagePurchasePrice;
|
||||
|
||||
const averageLine
|
||||
= average && average.currency === candles.data?.currency
|
||||
? Number(average.value)
|
||||
: null;
|
||||
|
||||
const companyData = company.data;
|
||||
const search = parameters.size > 0 ? `?${parameters.toString()}` : "";
|
||||
|
||||
return {
|
||||
averageLine:
|
||||
averageLine !== null && Number.isFinite(averageLine)
|
||||
? averageLine
|
||||
: null,
|
||||
candles: candles.data,
|
||||
candlesState: section(candles, sync.retry),
|
||||
canonicalTo:
|
||||
stock.data?.slug && stock.data.slug !== stockId
|
||||
? `/stocks/${encodeURIComponent(stock.data.slug)}${search}`
|
||||
: null,
|
||||
chartData,
|
||||
code: apiErrorCode(stock.error),
|
||||
company: companyData,
|
||||
companyState: section(company, sync.retry),
|
||||
@@ -129,8 +110,6 @@ function useStockPageVm() {
|
||||
?? null,
|
||||
})) ?? [],
|
||||
filter,
|
||||
hasIncompleteCandle:
|
||||
candles.data?.candles.some(candle => !candle.isComplete) ?? false,
|
||||
hasPrevious: history.hasPrevious,
|
||||
logoSrc: instrumentLogoUrl(stock.data?.logoName),
|
||||
onNext: history.onNext,
|
||||
@@ -151,7 +130,6 @@ function useStockPageVm() {
|
||||
period,
|
||||
position: position.data,
|
||||
positionState: section(position, sync.retry),
|
||||
priceRows: chartData.toReversed(),
|
||||
quote: quote.data,
|
||||
quoteState: section(quote, sync.retry),
|
||||
refresh: sync.refresh,
|
||||
|
||||
Reference in New Issue
Block a user