diff --git a/tradein-mvp/frontend/src/app/mera-public/__tests__/estimate-draft-survives-estimate.test.tsx b/tradein-mvp/frontend/src/app/mera-public/__tests__/estimate-draft-survives-estimate.test.tsx index 0539e722..146f70ca 100644 --- a/tradein-mvp/frontend/src/app/mera-public/__tests__/estimate-draft-survives-estimate.test.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/__tests__/estimate-draft-survives-estimate.test.tsx @@ -45,6 +45,9 @@ function submitLandingCard(): string { // не из своих догадок о наборе вариантов. const conditions = screen.getByRole("group", { name: /состояние/i }); const chosen = conditions.querySelectorAll("button")[0]; + // Без этой проверки пустая группа роняет тест TypeError'ом на textContent — + // отказом, по которому не видно, что именно разъехалось с формой. + expect(chosen, "в группе «Состояние» не нашлось кнопок").toBeTruthy(); const label = chosen.textContent ?? ""; fireEvent.click(chosen); @@ -56,17 +59,16 @@ function submitLandingCard(): string { describe("Черновик: обязательные поля переживают переход на /estimate", () => { it("этаж и состояние доступны после монтирования целевой страницы", () => { const condition = submitLandingCard(); - expect(condition, "в группе «Состояние» не нашлось кнопок").not.toBe(""); // Тот самый шаг, который раньше уничтожал оба поля. render(); - const extras = readDraftExtras(); + // Читаем так же, как будет читать платный шаг: под тот адрес, о котором + // спрашивают. Чужому адресу хвост не отдаётся — проверено отдельно в + // estimate-draft.test.ts. + const extras = readDraftExtras("Екатеринбург, Ленина 5"); expect(extras, "после /estimate от черновика не осталось ничего").not.toBeNull(); expect(extras?.floor).toBe("7/16"); expect(extras?.condition).toBe(condition); - // Хвост без адреса нельзя предъявлять платному шагу: он не отличит этаж - // этой квартиры от этажа предыдущей в той же вкладке. - expect(extras?.address).toBe("Екатеринбург, Ленина 5"); }); }); diff --git a/tradein-mvp/frontend/src/app/mera-public/__tests__/estimate-draft.test.ts b/tradein-mvp/frontend/src/app/mera-public/__tests__/estimate-draft.test.ts index 8449c935..5e5044a2 100644 --- a/tradein-mvp/frontend/src/app/mera-public/__tests__/estimate-draft.test.ts +++ b/tradein-mvp/frontend/src/app/mera-public/__tests__/estimate-draft.test.ts @@ -1,6 +1,11 @@ import { beforeEach, describe, expect, it } from "vitest"; -import { normalizeDraftRooms, saveDraft, takeDraft } from "../estimate-draft"; +import { + normalizeDraftRooms, + readDraftExtras, + saveDraft, + takeDraft, +} from "../estimate-draft"; /** * Черновик, который лэндинг передаёт на экран проверки. @@ -107,6 +112,24 @@ describe("этаж переживает переход на целевую ст expect(d?.floor).toBeUndefined(); }); + it("черновик без этажа и состояния убирает прошлый хвост", () => { + // Иначе этаж от предыдущей квартиры пережил бы свой черновик и лежал бы, + // дожидаясь потребителя. + saveDraft({ address: "Ленина 5", floor: "7/16", condition: "Хорошее" }); + takeDraft(); + saveDraft({ address: "Малышева 51", rooms: "2" }); + takeDraft(); + expect(readDraftExtras("Ленина 5")).toBeNull(); + expect(readDraftExtras("Малышева 51")).toBeNull(); + }); + + it("хвост не отдаётся другому адресу", () => { + saveDraft({ address: "Ленина 5", floor: "7/16" }); + takeDraft(); + expect(readDraftExtras("Малышева 51")).toBeNull(); + expect(readDraftExtras("Ленина 5")?.floor).toBe("7/16"); + }); + it("этаж не строка — поле выпадает, остальной черновик цел", () => { window.sessionStorage.setItem( "mera:estimate-draft", diff --git a/tradein-mvp/frontend/src/app/mera-public/estimate-draft.ts b/tradein-mvp/frontend/src/app/mera-public/estimate-draft.ts index d36314ef..8063187d 100644 --- a/tradein-mvp/frontend/src/app/mera-public/estimate-draft.ts +++ b/tradein-mvp/frontend/src/app/mera-public/estimate-draft.ts @@ -54,11 +54,7 @@ export interface EstimateDraft { /** * Хвост черновика: то, что собрали на лэндинге, но на `/estimate` не читают. - * - * Адрес лежит здесь ВМЕСТЕ с полями, а не подразумевается: иначе этаж «7/16» - * от квартиры на Ленина переживёт свой черновик и на платном шаге приклеится к - * другому дому в той же вкладке. Потребитель обязан сверить адрес, а не верить - * тому, что нашёл. + * Адрес лежит здесь вместе с полями — сверку делает `readDraftExtras`. */ export interface EstimateDraftExtras { address: string; @@ -137,18 +133,25 @@ function saveDraftExtras(draft: EstimateDraft): void { * Читает, но НЕ забирает: платный шаг может открыться не с первого раза, а * второе чтение того же хвоста ничего не портит. Вкладка закроется — исчезнет * само. + * + * `expectedAddress` обязателен, а не «желателен»: `takeDraft` выходит раньше + * записи хвоста на пустом/битом черновике, поэтому хвост от ПРЕДЫДУЩЕЙ + * квартиры доживает до следующего захода в той же вкладке. Сверку нельзя + * оставлять на совесть потребителя — не тот адрес, значит ничего нет. */ -export function readDraftExtras(): EstimateDraftExtras | null { +export function readDraftExtras( + expectedAddress: string, +): EstimateDraftExtras | null { try { const raw = window.sessionStorage.getItem(EXTRAS_KEY); if (!raw) return null; const parsed: unknown = JSON.parse(raw); if (typeof parsed !== "object" || parsed === null) return null; const value = parsed as Record; - if (typeof value.address !== "string") return null; + if (value.address !== expectedAddress) return null; return { - address: value.address, + address: expectedAddress, condition: typeof value.condition === "string" ? value.condition : undefined, floor: typeof value.floor === "string" ? value.floor : undefined,