Закрыты задачи бэклога по неочевидному поведению UI при ошибках отправки сообщений и блокировках со стороны Message-safety + добалено ограничение на размер сообщения

This commit is contained in:
mi
2026-07-29 16:45:19 +03:00
parent 41e19005fb
commit bda3ff39d7
36 changed files with 486 additions and 141 deletions
@@ -23,6 +23,7 @@ test.beforeEach(async ({ page }) => {
json: {
auth: { phone_enabled: true, password_enabled: false },
operator: { call_phone: "+74950000000" },
messages: { max_text_length: 4000 },
ux: { idle_timeout_minutes: 15 },
attachments: { max_size_mb: 5, allowed_extensions: ["png", "pdf"], allowed_mime_types: ["image/png", "application/pdf"] },
consents: {
@@ -86,11 +87,11 @@ test("Enter отправляет введённое сообщение", async (
await expect(page.getByRole("heading", { name: "Перед началом работы" })).toBeVisible();
});
test("сообщение ограничено 4000 символами", async ({ page }) => {
test("счётчик показывает и ограничивает 4000 символов", async ({ page }) => {
await page.goto("/");
await page.getByLabel("Сообщение").fill("а".repeat(4001));
await page.getByRole("button", { name: "Отправить" }).click();
await expect(page.getByRole("alert")).toContainText("Максимум — 4000 символов");
await expect(page.getByText("4001/4000")).toBeVisible();
await expect(page.getByRole("button", { name: "Отправить" })).toBeDisabled();
await expect(page.getByRole("heading", { name: "Перед началом работы" })).toHaveCount(0);
});
@@ -24,14 +24,21 @@ vi.mock("react-native", () => ({
}));
import { buildOidcDeviceMetadata } from "../../src/oidc-device";
import { ApiError, isMessageBlockedError } from "../../src/api";
import { messageFitsLimit, normalizeMessageText } from "../../src/message-text";
import { reconcileMessages } from "../../src/reconcile";
import { sessionMemory } from "../../src/session";
import { SingleFlight } from "../../src/single-flight";
import { websocketJwtProtocol } from "../../src/realtime";
import {
clearPendingTextIntent,
bindPendingFileIntent,
bindPendingTextIntent,
clearPendingFileIntent,
createPendingTextIntent,
loadPendingFileIntent,
loadPendingTextIntent,
savePendingFileIntent,
savePendingTextIntent,
} from "../../src/pending-intent";
import type { Message } from "../../src/types";
@@ -94,6 +101,33 @@ describe("pending message intent", () => {
expect(loadPendingTextIntent()).toBeNull();
clearPendingTextIntent();
});
it("привязывает отложенный текст и файл к созданному диалогу", () => {
const textIntent = createPendingTextIntent("После входа");
bindPendingTextIntent(textIntent, "dialog-1");
const file = new File(["test"], "test.txt", { type: "text/plain" });
savePendingFileIntent({ file, dialogKey: "file-dialog-key", messageKey: "file-key" });
bindPendingFileIntent("dialog-1");
expect(loadPendingTextIntent()?.dialogId).toBe("dialog-1");
expect(loadPendingFileIntent("dialog-1")?.file).toBe(file);
clearPendingTextIntent();
clearPendingFileIntent();
});
});
describe("message UX rules", () => {
it("нормализует текст и проверяет динамический лимит", () => {
expect(normalizeMessageText(" ")).toBe("A");
expect(messageFitsLimit("1234", 4)).toBe(true);
expect(messageFitsLimit("12345", 4)).toBe(false);
});
it("отличает блокировку message-safety от технической ошибки", () => {
expect(isMessageBlockedError(new ApiError(422, "message_blocked", "blocked"))).toBe(true);
expect(isMessageBlockedError(new ApiError(503, "dependency_unavailable", "failed"))).toBe(false);
});
});
describe("SingleFlight", () => {