diff --git a/tradein-mvp/frontend/src/app/mera-public/__tests__/marketing-placeholders-gate.test.ts b/tradein-mvp/frontend/src/app/mera-public/__tests__/marketing-placeholders-gate.test.ts new file mode 100644 index 00000000..8f6ed5d1 --- /dev/null +++ b/tradein-mvp/frontend/src/app/mera-public/__tests__/marketing-placeholders-gate.test.ts @@ -0,0 +1,105 @@ +/** + * Гейт #2904: снять `noindex` можно только вместе с плейсхолдерными числами. + * + * В `marketing-v3.ts` лежат величины из дизайн-макета, НЕ подтверждённые замером: + * + * | здесь | факт по замеру | + * | ошибка 1,8% | 13,2% медиана |%| (бэктест n=277), held-out 16,1% | + * | 83% в диапазон | 81,95%, но коридор шириной 74% от точки | + * | ±6 дн по сроку | сверки срока с фактом НЕ СУЩЕСТВУЕТ | + * | 42 700 проверок за год | 1 084 оценки всего (база живёт 2,5 месяца) | + * | 42 города РФ | 6 городов области, полное покрытие — 1 (ЕКБ) | + * + * Пока страница закрыта `robots: { index: false }`, эти строки никому не видны, и + * держать их допустимо. Публикация же недостоверных характеристик ПЛАТНОЙ услуги + * подпадает под ст. 5 ФЗ «О рекламе» и ЗоЗПП. + * + * До сих пор это правило жило ТОЛЬКО в комментарии модуля. Правило, записанное + * словами, не мешает снять одну строку в layout.tsx и выкатить страницу — поэтому + * гейт здесь механический: он краснеет ровно в тот момент, когда noindex исчезает, + * а плейсхолдеры остаются. + * + * Тест НЕ запрещает открывать страницу. Он требует, чтобы открытие шло вместе с + * заменой чисел на вычисляемые (или их снятием) — то есть чтобы решение было + * осознанным, а не побочным следствием правки метаданных. + */ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +import { describe, expect, it } from "vitest"; + +const ROOT = join(__dirname, ".."); + +function read(rel: string): string { + return readFileSync(join(ROOT, rel), "utf8"); +} + +/** Компоненты лэндинга v3, которые могут ссылаться на плейсхолдеры. */ +const V3_SOURCES = [ + "v3/page.tsx", + "_components/v3/HeroV3.tsx", + "_components/v3/AccuracyV3.tsx", + "_components/v3/FreeCheckCard.tsx", + "_components/v3/FreeResultV3.tsx", +]; + +const PLACEHOLDER_EXPORTS = [ + "ACCURACY_PLACEHOLDER", + "COST_OF_ERROR_PLACEHOLDER", + "FREE_SAMPLE_PLACEHOLDER", + "PROOF_ROWS_PLACEHOLDER", +]; + +function placeholdersInUse(): string[] { + const used: string[] = []; + for (const rel of V3_SOURCES) { + let src: string; + try { + src = read(rel); + } catch { + continue; // файл переименован/удалён — не повод падать + } + for (const name of PLACEHOLDER_EXPORTS) { + if (src.includes(name) && !used.includes(name)) used.push(name); + } + } + return used; +} + +function noindexIsOn(): boolean { + const layout = read("layout.tsx").replace(/\s+/g, " "); + return layout.includes("robots: { index: false"); +} + +describe("гейт noindex ↔ плейсхолдерные числа (#2904)", () => { + it("пока плейсхолдеры на странице — noindex обязан стоять", () => { + const used = placeholdersInUse(); + if (used.length === 0) { + // Числа заменены вычисляемыми — гейт больше не нужен, открывать можно. + return; + } + expect(noindexIsOn(), [ + `лэндинг v3 всё ещё использует непроверенные величины: ${used.join(", ")},`, + "но `robots: { index: false }` в layout.tsx снят.", + "Перед открытием страницы наружу каждая цифра обязана быть заменена", + "вычисляемой либо снята — см. шапку marketing-v3.ts и issue #2904.", + ].join(" ")).toBe(true); + }); + + it("сам гейт видит и плейсхолдеры, и noindex — иначе он проходил бы вхолостую", () => { + // Контроль на инструмент: если оба зонда перестанут находить своё + // (переименование файлов, смена формы записи), проверка выше станет + // зелёной по построению и перестанет что-либо охранять. + expect(placeholdersInUse().length, "ни один плейсхолдер не найден в компонентах v3 — зонд сломан").toBeGreaterThan(0); + expect(read("layout.tsx"), "layout.tsx не содержит блока robots — зонд сломан").toContain("robots"); + }); + + it("расхождение «заявлено против замера» остаётся в шапке модуля", () => { + // Таблица — единственное место, где зафиксировано, НАСКОЛЬКО числа расходятся + // с фактом. Потеряется она — и следующий читатель решит, что цифры проверены. + const mod = read("marketing-v3.ts"); + expect(mod).toContain("13,2%"); + expect(mod).toContain("НЕ СУЩЕСТВУЕТ"); + expect(mod).toContain("1 084"); + }); +});