Веб: токен эпика, проверка определённости var(--…), выделение в строке (#108)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a6de46b3f8
commit
9212c19dcf
@ -144,6 +144,27 @@ describe("«Бэклог»", () => {
|
||||
await waitFor(() => expect(router.state.location.pathname).toBe("/app/jira/backlog"));
|
||||
});
|
||||
|
||||
it("выделение вне строки не мешает открыть карточку", async () => {
|
||||
hub();
|
||||
const router = open("/app/jira/backlog?project=AWPCA");
|
||||
const p = await loaded();
|
||||
window.getSelection()!.selectAllChildren(within(p).getByRole("heading", { name: "AWPCA_Sprint_4" }));
|
||||
await userEvent.click(within(p).getByText("Тема AURORA-1"));
|
||||
await screen.findByRole("dialog");
|
||||
expect(router.state.location.pathname).toBe("/app/jira/issues/AURORA-1");
|
||||
window.getSelection()!.removeAllRanges();
|
||||
});
|
||||
|
||||
it("щелчок по ссылке ключа открывает карточку один раз", async () => {
|
||||
hub();
|
||||
const router = open("/app/jira/backlog?project=AWPCA");
|
||||
const p = await loaded();
|
||||
await userEvent.click(within(p).getByRole("link", { name: "AURORA-1" }));
|
||||
await screen.findByRole("dialog");
|
||||
await userEvent.click(within(screen.getByRole("dialog")).getByRole("button", { name: "Закрыть" }));
|
||||
await waitFor(() => expect(router.state.location.pathname).toBe("/app/jira/backlog")); // одна запись истории, а не две
|
||||
});
|
||||
|
||||
it("нет данных доски — «Нужен агент v15 и обновление из Jira» и «Обновить из Jira»", async () => {
|
||||
hub({ data: { board: null, sprints: [], backlog: null, board_issues: null } });
|
||||
open("/app/jira/backlog?project=AWPCA");
|
||||
|
||||
@ -9,7 +9,8 @@ export function AgileIssueRow({ issue, mark }: { issue: AgileIssue; mark?: strin
|
||||
const open = useOpenIssue();
|
||||
const click = (e: MouseEvent) => {
|
||||
if ((e.target as HTMLElement).closest("a")) return;
|
||||
if (window.getSelection()?.toString()) return;
|
||||
const sel = window.getSelection();
|
||||
if (sel && !sel.isCollapsed && sel.containsNode(e.currentTarget, true)) return; // выделен текст этой строки
|
||||
open(issue.key);
|
||||
};
|
||||
return (
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
/* экраны доски (бэклог, спринт, Kanban): только токены дизайн-системы */
|
||||
.agile{display:flex;flex-direction:column;gap:var(--space-4);padding-top:var(--space-6)}
|
||||
.agile .ui-page-header{margin-bottom:0}
|
||||
.agile__gate{padding-top:var(--space-6)}
|
||||
.agile .agile__gate{padding-top:0}
|
||||
.agile__gate{min-width:0}
|
||||
.agile__link{color:var(--color-action-primary);font-weight:600;text-decoration:none}
|
||||
.agile__link:hover{text-decoration:underline}
|
||||
.agile__empty{margin:0;color:var(--color-text-secondary)}
|
||||
@ -25,7 +24,7 @@
|
||||
.agile-row__sp{flex:0 0 32px;text-align:right;color:var(--color-text-secondary)}
|
||||
.agile-row__mark{flex:0 0 auto;font-size:var(--fs-caption);color:var(--color-status-warning)}
|
||||
.agile-type{display:inline-flex;flex:0 0 auto;color:var(--color-text-secondary)}
|
||||
.agile-type--epic{color:var(--color-cat-1)}
|
||||
.agile-type--epic{color:var(--k-ui)}
|
||||
.agile-type--bug{color:var(--color-status-error)}
|
||||
.agile-type--task{color:var(--color-status-success)}
|
||||
.agile-past{display:flex;flex-direction:column;gap:var(--space-2)}
|
||||
|
||||
@ -71,7 +71,6 @@ export interface IssueDetail extends IssueRow {
|
||||
changes?: ChangeEvent[]; // хаб до журнала изменений (#70) поля не шлёт
|
||||
}
|
||||
|
||||
|
||||
// ответ GET /agile (доска проекта, спринты, бэклог); поля — как отдаёт хаб
|
||||
export interface AgileIssue {
|
||||
key: string; summary: string | null; issue_type: string | null; status: string | null; priority: string | null;
|
||||
|
||||
@ -64,6 +64,25 @@ describe("токены AURA", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("использованные токены определены", () => {
|
||||
const defs = (css: string) => new Set([...stripComments(css).matchAll(/(--[\w-]+)\s*:/g)].map((m) => m[1]));
|
||||
// переменные, которые выставляет код страницы (setProperty), а не css
|
||||
const SET_BY_JS = new Set(["--head-h"]);
|
||||
const tokenDefs = defs(readFileSync(TOKENS, "utf8"));
|
||||
it("каждый var(--…) в css определён в tokens.css или в том же файле", () => {
|
||||
const missing: string[] = [];
|
||||
for (const f of cssFiles(SRC)) {
|
||||
if (f === TOKENS) continue;
|
||||
const css = readFileSync(f, "utf8");
|
||||
const local = defs(css);
|
||||
for (const m of stripComments(css).matchAll(/var\(\s*(--[\w-]+)/g)) {
|
||||
if (!tokenDefs.has(m[1]) && !local.has(m[1]) && !SET_BY_JS.has(m[1])) missing.push(`${relative(SRC, f)}: ${m[1]}`);
|
||||
}
|
||||
}
|
||||
expect([...new Set(missing)]).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("доска без чередования дорожек (#69, design 9)", () => {
|
||||
it("токена --lane-alt нет, правила чётной дорожки нет", () => {
|
||||
expect(readFileSync(TOKENS, "utf8")).not.toContain("--lane-alt");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user