feat: add quiet reading with rare confirmations
Part of the quiet-user-feedback change; docs and specs land in their own repositories.
This commit is contained in:
@@ -352,11 +352,7 @@ describe("bond page", () => {
|
||||
it("keeps market content and sold-position history when coupons fail", async () => {
|
||||
setup({ failCoupons: true, noPosition: true });
|
||||
await screen.findByText("98,125 %");
|
||||
const message = await screen.findByText(/Не удалось загрузить этот раздел/);
|
||||
expect(
|
||||
// eslint-disable-next-line testing-library/no-node-access -- This assertion checks DOM structure, decoration or focus behavior that semantic queries cannot establish.
|
||||
message.closest("[role=\"status\"]"),
|
||||
).toHaveAttribute("aria-live", "polite");
|
||||
expect(screen.queryByRole("button", { name: "Повторить" })).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("heading", { name: "Моя позиция" }),
|
||||
).not.toBeInTheDocument();
|
||||
@@ -364,38 +360,12 @@ describe("bond page", () => {
|
||||
screen.getByRole("heading", { name: "Моя история событий" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
it("preserves cached quote on refresh failure", async () => {
|
||||
const scopes: unknown[] = [];
|
||||
server.use(
|
||||
http.post("*/api/v1/tbank-sync", async ({ request }) => {
|
||||
scopes.push(await request.json());
|
||||
|
||||
return HttpResponse.json(
|
||||
{ code: "TBANK_UNAVAILABLE" },
|
||||
{ status: 503 },
|
||||
);
|
||||
}),
|
||||
);
|
||||
it("preserves cached quote without manual refresh", async () => {
|
||||
const controls = setup({ path: `/bonds/${uid}?period=year&events=trades` });
|
||||
await screen.findByText("98,125 %");
|
||||
controls.failQuote();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Обновить" }));
|
||||
const message = await screen.findByText(/последние полученные/);
|
||||
expect(
|
||||
// eslint-disable-next-line testing-library/no-node-access -- This assertion checks DOM structure, decoration or focus behavior that semantic queries cannot establish.
|
||||
message.closest("[role=\"status\"]"),
|
||||
).toHaveAttribute("aria-live", "polite");
|
||||
expect(screen.getByText("98,125 %")).toBeVisible();
|
||||
expect(scopes).toEqual([
|
||||
{
|
||||
accountId: "all",
|
||||
candlePeriod: "year",
|
||||
categories: ["trade"],
|
||||
instrumentUid: uid,
|
||||
limit: 20,
|
||||
screen: "bond",
|
||||
},
|
||||
]);
|
||||
expect(screen.queryByRole("button", { name: "Обновить" })).not.toBeInTheDocument();
|
||||
});
|
||||
it("offers search for an ambiguous or missing instrument without loading personal data", async () => {
|
||||
const { requests } = setup({ ambiguousTicker: true, path: "/bonds/SAME" });
|
||||
|
||||
@@ -15,7 +15,6 @@ const BondPage: FC = () => {
|
||||
code={vm.code}
|
||||
kind="bond"
|
||||
loading={vm.bondState.loading}
|
||||
retry={vm.bondState.retry}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { FC } from "react";
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Group,
|
||||
Skeleton,
|
||||
@@ -137,8 +136,6 @@ const BondPageView: FC<BondPageViewProps> = (props) => {
|
||||
<InstrumentHeader
|
||||
logoSrc={vm.logoSrc}
|
||||
name={bond.name}
|
||||
onRefresh={vm.refresh}
|
||||
refreshing={vm.refreshing}
|
||||
subtitle={subtitle}
|
||||
>
|
||||
|
||||
@@ -156,20 +153,8 @@ const BondPageView: FC<BondPageViewProps> = (props) => {
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{!!(vm.refreshError || vm.bondState.error || vm.quoteState.error) && (
|
||||
<Alert aria-live="polite" color="orange" mt="md" role="status">
|
||||
{t(vm.quote ? "accounts.refreshError" : "stock.sectionError")}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{quoteContent}
|
||||
|
||||
{!!(vm.quoteState.refreshing || vm.bondState.refreshing) && (
|
||||
<Text c="dimmed" role="status" size="xs">
|
||||
{t("accounts.refreshing")}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<InstrumentTerms title={t("bond.terms")}>
|
||||
|
||||
<InstrumentMetrics>
|
||||
|
||||
@@ -42,7 +42,7 @@ function useBondPageVm() {
|
||||
|
||||
const { operations } = history;
|
||||
|
||||
const sync = useScreenSync(
|
||||
useScreenSync(
|
||||
{
|
||||
accountId: "all",
|
||||
candlePeriod: period,
|
||||
@@ -56,29 +56,26 @@ function useBondPageVm() {
|
||||
|
||||
return {
|
||||
bond: bond.data,
|
||||
bondState: section(bond, uid ? sync.retry : () => bond.refetch()),
|
||||
bondState: section(bond),
|
||||
candles: candles.data,
|
||||
candlesState: section(candles, sync.retry),
|
||||
candlesState: section(candles),
|
||||
canonicalTo:
|
||||
bond.data?.slug && bond.data.slug !== bondId
|
||||
? `/bonds/${encodeURIComponent(bond.data.slug)}${search}`
|
||||
: null,
|
||||
code: apiErrorCode(bond.error),
|
||||
coupons: coupons.data,
|
||||
couponsState: section(coupons, sync.retry),
|
||||
couponsState: section(coupons),
|
||||
eventRows: toInstrumentEventRows(operations.data?.items, bond.data),
|
||||
filter,
|
||||
hasPrevious: history.hasPrevious,
|
||||
logoSrc: instrumentLogoUrl(bond.data?.logoName),
|
||||
metrics: metrics.data,
|
||||
metricsState: section(metrics, sync.retry),
|
||||
metricsState: section(metrics),
|
||||
onNext: history.onNext,
|
||||
onPrevious: history.onPrevious,
|
||||
operations: operations.data,
|
||||
operationsState: {
|
||||
...section(operations, sync.retry),
|
||||
retry: () => history.retry(sync.retry),
|
||||
},
|
||||
operationsState: section(operations),
|
||||
pastCoupons:
|
||||
coupons.data?.coupons
|
||||
.filter(event => event.timing !== "upcoming")
|
||||
@@ -87,12 +84,9 @@ function useBondPageVm() {
|
||||
) ?? [],
|
||||
period,
|
||||
position: position.data,
|
||||
positionState: section(position, sync.retry),
|
||||
positionState: section(position),
|
||||
quote: quote.data,
|
||||
quoteState: section(quote, sync.retry),
|
||||
refresh: sync.refresh,
|
||||
refreshError: sync.transportError || sync.sourceError,
|
||||
refreshing: sync.refreshing,
|
||||
quoteState: section(quote),
|
||||
setFilter,
|
||||
setPeriod,
|
||||
upcoming:
|
||||
|
||||
Reference in New Issue
Block a user