sdlc-hub/web/src/issues/wiki/inline.test.ts
Andrey Epifancev 62057606c0 Правки финального ревью: ссылки на вложения, размер, 413 в пакетах, имя копии, документация (#83)
I-1 I-2 M-1 M-2 M-3 M-5 M-6 M-7 T3.4 T4.3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:14:40 +04:00

120 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from "vitest";
import { type Inline, parseInline } from "./inline.ts";
const P = { projectKey: "AURORA" };
const t = (text: string): Inline => ({ t: "text", text });
const wrap = (kind: "strong" | "em" | "del" | "ins" | "sup" | "sub", ...children: Inline[]): Inline => ({ t: kind, children });
const issue = (key: string, ...children: Inline[]): Inline => ({ t: "issue", key, children });
const link = (href: string, ...children: Inline[]): Inline => ({ t: "link", href, children });
describe("parseInline: выделение", () => {
it.each<[string, Inline[]]>([
["*Итог:* ошибка", [wrap("strong", t("Итог:")), t(" ошибка")]],
["_к_", [wrap("em", t("к"))]],
["-з-", [wrap("del", t("з"))]],
["+п+", [wrap("ins", t("п"))]],
["^в^", [wrap("sup", t("в"))]],
["~н~", [wrap("sub", t("н"))]],
["*_x_*", [wrap("strong", wrap("em", t("x")))]],
["(*x*)", [t("("), wrap("strong", t("x")), t(")")]],
["a *b c* d", [t("a "), wrap("strong", t("b c")), t(" d")]],
["{{data_type}}", [{ t: "code", text: "data_type" }]],
["{{*x*}} и", [{ t: "code", text: "*x*" }, t(" и")]],
])("%s", (src, tree) => expect(parseInline(src, P)).toEqual(tree));
});
describe("parseInline: обычный текст не портится", () => {
it.each([
"data_type = null", "2026-10-15", "счёт-фактура", "a*b*c", "18 803 903,65 - 9 351 102,89", "**важно**",
"* одинокая", "*незакрытый", "[без конца", "*x*y", "{{без конца", "UTF-8", "SHA-256", "DOC-221280", "XAURORA-1",
"AURORA-12x", "",
])("%s", (src) => expect(parseInline(src, P)).toEqual(src ? [t(src)] : []));
});
describe("parseInline: экранирование и перенос", () => {
it("\\* — сам символ", () => expect(parseInline("\\*не жирный\\*", P)).toEqual([t("*не жирный*")]));
it("\\\\ — перенос", () => expect(parseInline("a\\\\b", P)).toEqual([t("a"), { t: "br" }, t("b")]));
});
describe("parseInline: ссылки", () => {
it.each<[string, Inline[]]>([
["[Jira|https://x.ru/a?b=1]", [link("https://x.ru/a?b=1", t("Jira"))]],
["[https://x.ru]", [link("https://x.ru", t("https://x.ru"))]],
["[mail|MAILTO:a@b.ru]", [link("MAILTO:a@b.ru", t("mail"))]],
["[*Жирная* ссылка|http://x.ru]", [link("http://x.ru", wrap("strong", t("Жирная")), t(" ссылка"))]],
["см. https://x.ru/p, далее", [t("см. "), link("https://x.ru/p", t("https://x.ru/p")), t(", далее")]],
["(https://x.ru/p).", [t("("), link("https://x.ru/p", t("https://x.ru/p")), t(").")]],
["[~ivanov]", [{ t: "mention", login: "ivanov" }]],
])("%s", (src, tree) => expect(parseInline(src, P)).toEqual(tree));
it("в подписи ссылки ключи и адреса — текст (I-2: без вложенных ссылок)", () => {
expect(parseInline("[AURORA-1445|https://j.x/browse/AURORA-1445]", P)).toEqual([link("https://j.x/browse/AURORA-1445", t("AURORA-1445"))]);
expect(parseInline("[см. https://a.ru|https://b.ru]", P)).toEqual([link("https://b.ru", t("см. https://a.ru"))]);
expect(parseInline("[*AURORA-1*|AURORA-2]", P)).toEqual([issue("AURORA-2", wrap("strong", t("AURORA-1")))]);
});
it.each(["[нажми|javascript:alert(1)]", "[x|ftp://h]", "[javascript:alert(1)]", "[просто текст]"])(
"опасная или неизвестная цель — текст как есть: %s",
(src) => expect(parseInline(src, P)).toEqual([t(src)]),
);
});
describe("parseInline: ключи задач", () => {
it("ключи проекта в тексте", () =>
expect(parseInline("ведётся в AURORA-1445; AURORA-1645 снята", P)).toEqual([
t("ведётся в "), issue("AURORA-1445"), t("; "), issue("AURORA-1645"), t(" снята"),
]));
it("[ключ] и [текст|ключ]", () => {
expect(parseInline("[AURORA-1]", P)).toEqual([issue("AURORA-1")]);
expect(parseInline("[см.|AURORA-2]", P)).toEqual([issue("AURORA-2", t("см."))]);
});
it("не внутри {{…}} и адреса", () => {
expect(parseInline("{{AURORA-1}}", P)).toEqual([{ t: "code", text: "AURORA-1" }]);
expect(parseInline("https://j/browse/AURORA-1", P)).toEqual([link("https://j/browse/AURORA-1", t("https://j/browse/AURORA-1"))]);
});
it("без projectKey — текст", () => expect(parseInline("AURORA-1445")).toEqual([t("AURORA-1445")]));
});
describe("parseInline: ссылки на вложения", () => {
const atts = [
{ id: "31337", filename: "скрин 1.png", created: "2026-10-15T12:00:00.000+0300" },
{ id: "31338", filename: "a.pdf", created: null },
{ id: "10", filename: "дубль.png", created: "2026-10-14T12:00:00.000+0300" },
{ id: "20", filename: "дубль.png", created: "2026-10-20T12:00:00.000+0300" },
];
const A = { attachments: atts };
const att = (id: string, ...children: Inline[]): Inline => ({ t: "attachment", id, children });
it("!имя|параметры! и !имя!", () => {
expect(parseInline("см. !скрин 1.png|thumbnail!", A)).toEqual([t("см. "), att("31337", t("скрин 1.png"))]);
expect(parseInline("!скрин 1.png!", A)).toEqual([att("31337", t("скрин 1.png"))]);
});
it("[^имя] и [текст|^имя]", () => {
expect(parseInline("[^a.pdf]", A)).toEqual([att("31338", t("a.pdf"))]);
expect(parseInline("[отчёт|^a.pdf]", A)).toEqual([att("31338", t("отчёт"))]);
});
it("имя точно (с регистром); несколько с одним именем — самое новое", () => {
expect(parseInline("!Скрин 1.png!", A)).toEqual([t("!Скрин 1.png!")]);
expect(parseInline("!дубль.png!", A)).toEqual([att("20", t("дубль.png"))]);
});
it("имени нет среди вложений — исходник как есть", () => {
expect(parseInline("!нет.png!", A)).toEqual([t("!нет.png!")]);
expect(parseInline("[^нет.pdf] и [x|^нет.pdf]", A)).toEqual([t("[^нет.pdf] и [x|^нет.pdf]")]);
});
it("одиночный «!» перед ссылкой не мешает ей (I-1)", () => {
expect(parseInline("Внимание! см. !a.pdf!", A)).toContainEqual(att("31338", t("a.pdf")));
const r = parseInline("Важно! [^a.pdf] и !a.pdf!", A);
expect(r.filter((n) => n.t === "attachment")).toEqual([att("31338", t("a.pdf")), att("31338", t("a.pdf"))]);
});
it("внешняя картинка не вложение", () => {
expect(JSON.stringify(parseInline("!http://x/скрин 1.png!", { attachments: [{ id: "1", filename: "http://x/скрин 1.png", created: null }] }))).not.toContain("attachment");
});
it("без опции attachments — как раньше", () => {
expect(parseInline("!скрин 1.png!")).toEqual([t("!скрин 1.png!")]);
expect(parseInline("[^a.pdf]")).toEqual([t("[^a.pdf]")]);
});
it("внутри подписи ссылки вложения нет (кнопка в ссылке недопустима)", () => {
expect(parseInline("[!a.pdf!|https://x.y]", A)).toEqual([link("https://x.y", t("!a.pdf!"))]);
});
});