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:
@@ -396,37 +396,15 @@ describe("stock page", () => {
|
||||
screen.queryByRole("heading", { name: "Моя позиция" }),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "График цены" })).toBeVisible();
|
||||
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();
|
||||
});
|
||||
it("recovers an initially missing company section without reloading successful sections", async () => {
|
||||
it("keeps successful sections visible when company data is initially missing", async () => {
|
||||
const { requests } = setup({ failCompany: true });
|
||||
const retry = await screen.findByRole("button", { name: "Повторить" });
|
||||
await screen.findByText("Зачисление дивидендов");
|
||||
const modes: (null | string)[] = [];
|
||||
server.use(
|
||||
http.get("*/api/v1/stocks/:uid/company", ({ request }) => {
|
||||
modes.push(new URL(request.url).searchParams.get("sync"));
|
||||
|
||||
return HttpResponse.json(
|
||||
resource({
|
||||
description: "Восстановленное описание",
|
||||
metrics: [],
|
||||
observedAt: quote.observedAt,
|
||||
unavailable: [],
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
const previousRequests = requests.length;
|
||||
fireEvent.click(retry);
|
||||
expect(await screen.findByText("Восстановленное описание")).toBeVisible();
|
||||
expect(screen.getByText("Зачисление дивидендов")).toBeVisible();
|
||||
expect(requests).toHaveLength(previousRequests);
|
||||
expect(modes).toEqual(["observe"]);
|
||||
expect(screen.queryByRole("button", { name: "Повторить" })).not.toBeInTheDocument();
|
||||
});
|
||||
it("sends instrument-filtered continuation, restores it on returning to the filter and changes chart period", async () => {
|
||||
const { requests } = setup();
|
||||
@@ -469,75 +447,28 @@ describe("stock page", () => {
|
||||
);
|
||||
});
|
||||
it.each([
|
||||
[
|
||||
"INVALID_OPERATIONS_CURSOR",
|
||||
null,
|
||||
null,
|
||||
],
|
||||
["TBANK_UNAVAILABLE", "next", "observe"],
|
||||
"INVALID_OPERATIONS_CURSOR",
|
||||
"TBANK_UNAVAILABLE",
|
||||
])(
|
||||
"recovers history after %s with fresh data and the correct cursor",
|
||||
async (code, cursor, sync) => {
|
||||
"keeps history quiet after %s without technical retry",
|
||||
async (code) => {
|
||||
const controls = setup({ cursorError: code });
|
||||
await screen.findByText("Зачисление дивидендов");
|
||||
fireEvent.click(screen.getByRole("button", { name: "Далее" }));
|
||||
const retry = await screen.findByRole("button", { name: "Повторить" });
|
||||
controls.recoverOperations();
|
||||
const previousRequests = controls.requests.length;
|
||||
fireEvent.click(retry);
|
||||
expect(await screen.findByText("+201 ₽")).toBeVisible();
|
||||
const recovered = controls.requests.slice(previousRequests);
|
||||
expect(recovered).toHaveLength(1);
|
||||
expect(recovered[0]?.pathname).toBe(
|
||||
"/api/v1/brokerage-accounts/all/operations",
|
||||
);
|
||||
expect(recovered[0]?.searchParams.get("instrumentUid")).toBe(uid);
|
||||
expect(recovered[0]?.searchParams.get("limit")).toBe("20");
|
||||
expect(recovered[0]?.searchParams.get("cursor")).toBe(cursor);
|
||||
expect(recovered[0]?.searchParams.get("sync")).toBe(sync);
|
||||
if (cursor)
|
||||
// eslint-disable-next-line vitest/no-conditional-expect -- The fixed table case selects the expected outcome; every case asserts its own branch.
|
||||
expect(screen.getByRole("button", { name: "Назад" })).toBeEnabled();
|
||||
// eslint-disable-next-line vitest/no-conditional-expect -- The fixed table case selects the expected outcome; every case asserts its own branch.
|
||||
else expect(screen.getByRole("button", { name: "Назад" })).toBeDisabled();
|
||||
expect(screen.queryByRole("button", { name: "Повторить" })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Моя история событий" })).toBeVisible();
|
||||
},
|
||||
);
|
||||
it("keeps the last quote on a 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("keeps the last quote without manual refresh", async () => {
|
||||
const controls = setup({
|
||||
path: `/stocks/${uid}?period=year&events=trades`,
|
||||
});
|
||||
|
||||
await screen.findByText("300 ₽");
|
||||
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("300 ₽")).toBeVisible();
|
||||
expect(scopes).toEqual([
|
||||
{
|
||||
accountId: "all",
|
||||
candlePeriod: "year",
|
||||
categories: ["trade"],
|
||||
instrumentUid: uid,
|
||||
limit: 20,
|
||||
screen: "stock",
|
||||
},
|
||||
]);
|
||||
expect(screen.queryByRole("button", { name: "Обновить" })).not.toBeInTheDocument();
|
||||
});
|
||||
it("offers recovery for an unavailable direct link", async () => {
|
||||
setup({ failStock: true });
|
||||
|
||||
Reference in New Issue
Block a user