test(tradein/frontend): колонка ссылки в «05 РЫНОК» — прочерк, а не кнопка-обманка (#2755)
All checks were successful
CI / changes (pull_request) Successful in 15s
CI Trade-In / changes (pull_request) Successful in 15s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m53s
CI / frontend-tests (pull_request) Successful in 2m4s
CI / openapi-codegen-check (pull_request) Successful in 3m19s

Проверка на удалённую в предыдущем коммите ветку. Она была недостижима, но
выглядела как рабочий элемент интерфейса — некликабельный <span>, стилизованный
под кнопку-ссылку «Росреестр ↗». Без проверки ничто не мешает ей вернуться:
достаточно снова сделать поле опциональным.

Три утверждения через настоящий mapSources: есть URL — ссылка именно на него;
URL нет — прочерк и НИ ОДНОЙ ссылки; javascript:-схема отбрасывается safeUrl,
и строка тоже получает прочерк, а не кнопку.

Проверено, что тест краснеет: возврат «Росреестр ↗» на место прочерка роняет
2 утверждения из 3.

Refs #2755
This commit is contained in:
bot-backend 2026-08-07 14:48:01 +05:00
parent 3f0751f0b6
commit fd1affca8b

View file

@ -0,0 +1,94 @@
// #2755 — колонка ссылки в таблице аналогов «05 РЫНОК».
//
// Здесь до этого жила третья ветка: когда `url` приходил `undefined`, рисовался
// НЕкликабельный <span>, стилизованный под кнопку-ссылку «Росреестр ↗». Ветка
// была недостижима (mapSources всегда ставит `string | null`), но выглядела как
// рабочий элемент интерфейса. Ветку убрали, поле сделали обязательным — этот
// тест держит обе половины: ссылка есть только там, где есть URL, а строка без
// URL получает прочерк, а не кнопку-обманку.
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import SourcesView from "../SourcesView";
import { mapSources } from "../mappers";
import { FIXTURE_ESTIMATE } from "@/app/ui-preview/estimate/fixture";
import type { AggregatedEstimate, AnalogLot } from "@/types/trade-in";
function lot(over: Partial<AnalogLot>): AnalogLot {
return {
address: "ул. Репина, 73",
area_m2: 54,
rooms: 2,
floor: 7,
total_floors: 16,
price_rub: 9_700_000,
price_per_m2: 179_629,
listing_date: "2026-05-12",
days_on_market: 18,
photo_url: null,
source: "avito",
source_url: null,
distance_m: 120,
tier: null,
lat: 56.8401,
lon: 60.5702,
...over,
};
}
// Оффлайн-фикстура оценки репозитория с подменёнными аналогами: остальные поля
// (sources_used, коридоры, KPI) настоящие, меняем ровно то, что проверяем.
function estimateWith(analogs: AnalogLot[]): AggregatedEstimate {
return {
...FIXTURE_ESTIMATE,
analogs,
n_analogs: analogs.length,
actual_deals: [],
};
}
describe("SourcesView — колонка ссылки на оригинал", () => {
it("есть URL — кликабельная ссылка на него", () => {
const estimate = estimateWith([
lot({
address: "ул. Репина, 73",
source_url: "https://www.avito.ru/ekaterinburg/kvartiry/1",
}),
]);
render(<SourcesView data={mapSources(estimate, null)} />);
const link = screen.getByRole("link", { name: /Источник/ });
expect(link).toHaveAttribute(
"href",
"https://www.avito.ru/ekaterinburg/kvartiry/1",
);
});
it("URL нет — прочерк, а не кнопка-обманка «Росреестр ↗»", () => {
const estimate = estimateWith([
lot({ address: "ул. Викулова, 33", source_url: null }),
]);
render(<SourcesView data={mapSources(estimate, null)} />);
expect(screen.queryByText(/Росреестр ↗/)).toBeNull();
expect(screen.queryByRole("link", { name: /Источник/ })).toBeNull();
// Прочерк — единственное содержимое ячейки ссылки в строке аналога.
const row = screen.getByText("ул. Викулова, 33").closest("div");
expect(row?.textContent).toContain("—");
});
it("javascript:-схема отбрасывается safeUrl — прочерк, а не ссылка", () => {
const estimate = estimateWith([
lot({ address: "ул. Кирова, 28", source_url: "javascript:alert(1)" }),
]);
render(<SourcesView data={mapSources(estimate, null)} />);
expect(screen.queryByRole("link", { name: /Источник/ })).toBeNull();
const row = screen.getByText("ул. Кирова, 28").closest("div");
expect(row?.textContent).toContain("—");
});
});