feat(FN-3871): add tracking issue title/body formatters
Added GitHub tracking issue title and body formatters to the dashboard (`packages/dashboard/src/github-tracking.ts`), with corresponding tests and documentation in the architecture docs. A changeset was included for this user-facing feature. Fusion-Task-Id: FN-3871
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { Task } from "@fusion/core";
|
||||
import { maybeCreateTrackingIssue } from "../github-tracking.js";
|
||||
import {
|
||||
formatTrackingIssueBody,
|
||||
formatTrackingIssueTitle,
|
||||
maybeCreateTrackingIssue,
|
||||
} from "../github-tracking.js";
|
||||
|
||||
function buildTask(overrides: Partial<Task> = {}): Task {
|
||||
return {
|
||||
@@ -17,6 +21,82 @@ function buildTask(overrides: Partial<Task> = {}): Task {
|
||||
} as Task;
|
||||
}
|
||||
|
||||
describe("formatTrackingIssueTitle", () => {
|
||||
it("formats a normal title", () => {
|
||||
expect(formatTrackingIssueTitle({ id: "FN-1", title: "Hello" })).toBe("[FN-1] Hello");
|
||||
});
|
||||
|
||||
it("falls back for blank title", () => {
|
||||
expect(formatTrackingIssueTitle({ id: "FN-1", title: " \n\t " })).toBe("[FN-1] Untitled task");
|
||||
});
|
||||
|
||||
it("collapses multiline whitespace", () => {
|
||||
expect(formatTrackingIssueTitle({ id: "FN-1", title: "Hello\n\tWorld" })).toBe("[FN-1] Hello World");
|
||||
});
|
||||
|
||||
it("truncates very long titles while preserving id prefix", () => {
|
||||
const longTitle = "x".repeat(400);
|
||||
const formatted = formatTrackingIssueTitle({ id: "FN-123", title: longTitle });
|
||||
expect(formatted.startsWith("[FN-123] ")).toBe(true);
|
||||
expect(formatted.length).toBeLessThanOrEqual(240);
|
||||
expect(formatted.endsWith("…")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatTrackingIssueBody", () => {
|
||||
it("prefers first description paragraph", () => {
|
||||
expect(formatTrackingIssueBody({
|
||||
id: "FN-X",
|
||||
description: "Primary paragraph\n\nSecond paragraph",
|
||||
prompt: "Prompt paragraph",
|
||||
summary: "Summary paragraph",
|
||||
})).toBe("Fusion task: FN-X\n\nPrimary paragraph");
|
||||
});
|
||||
|
||||
it("uses prompt when description is empty", () => {
|
||||
expect(formatTrackingIssueBody({ id: "FN-X", description: "", prompt: "Prompt paragraph", summary: "Summary" }))
|
||||
.toBe("Fusion task: FN-X\n\nPrompt paragraph");
|
||||
});
|
||||
|
||||
it("uses summary when description and prompt are unavailable", () => {
|
||||
expect(formatTrackingIssueBody({ id: "FN-X", summary: "Summary paragraph" }))
|
||||
.toBe("Fusion task: FN-X\n\nSummary paragraph");
|
||||
});
|
||||
|
||||
it("falls back when prompt is undefined and sources are empty", () => {
|
||||
expect(formatTrackingIssueBody({ id: "FN-X", description: " ", summary: " " }))
|
||||
.toBe("Fusion task: FN-X\n\nNo summary available.");
|
||||
});
|
||||
|
||||
it("strips markdown noise including headings, bullets, and code fences", () => {
|
||||
const body = formatTrackingIssueBody({
|
||||
id: "FN-X",
|
||||
description: "# Heading\n- bullet\n1. numbered\n```ts\nconst x = 1;\n```\nfinal",
|
||||
});
|
||||
expect(body).toBe("Fusion task: FN-X\n\nHeading bullet numbered const x = 1; final");
|
||||
});
|
||||
|
||||
it("truncates summary to 500 characters with ellipsis", () => {
|
||||
const body = formatTrackingIssueBody({ id: "FN-X", description: "a".repeat(600) });
|
||||
const summary = body.replace("Fusion task: FN-X\n\n", "");
|
||||
expect(summary.length).toBe(500);
|
||||
expect(summary.endsWith("…")).toBe(true);
|
||||
});
|
||||
|
||||
it("removes fusion-style localhost task urls", () => {
|
||||
const body = formatTrackingIssueBody({
|
||||
id: "FN-1",
|
||||
description: "See http://localhost:4040/tasks/FN-1 and continue",
|
||||
});
|
||||
expect(body).not.toContain("localhost");
|
||||
expect(body).not.toMatch(/https?:\/\/[^\s]*\/tasks\/FN-/);
|
||||
});
|
||||
|
||||
it("always starts with fusion task reference", () => {
|
||||
expect(formatTrackingIssueBody({ id: "FN-99", description: "hello" }).startsWith("Fusion task: FN-99\n\n")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("maybeCreateTrackingIssue", () => {
|
||||
it("returns tracking_disabled when not enabled", async () => {
|
||||
const result = await maybeCreateTrackingIssue(buildTask({ githubTracking: { enabled: false } }), {
|
||||
@@ -89,7 +169,7 @@ describe("maybeCreateTrackingIssue", () => {
|
||||
const linkGithubIssue = vi.fn();
|
||||
const recordActivity = vi.fn();
|
||||
|
||||
const result = await maybeCreateTrackingIssue(buildTask({ title: "Test", githubTracking: { enabled: true } }), {
|
||||
const result = await maybeCreateTrackingIssue(buildTask({ title: "Test", description: "Short body", githubTracking: { enabled: true } }), {
|
||||
taskStore: { linkGithubIssue, recordActivity } as any,
|
||||
githubClient: { createIssue } as any,
|
||||
projectSettings: {},
|
||||
@@ -99,6 +179,12 @@ describe("maybeCreateTrackingIssue", () => {
|
||||
|
||||
expect(result.created).toBe(true);
|
||||
expect(createIssue).toHaveBeenCalledTimes(1);
|
||||
expect(createIssue).toHaveBeenCalledWith(expect.objectContaining({
|
||||
title: "[FN-1] Test",
|
||||
body: expect.stringMatching(/^Fusion task: FN-1\n\n/),
|
||||
}));
|
||||
const calledBody = createIssue.mock.calls[0][0]?.body as string;
|
||||
expect(calledBody.length).toBeLessThanOrEqual("Fusion task: FN-1\n\n".length + 500);
|
||||
expect(linkGithubIssue).toHaveBeenCalledWith("FN-1", expect.objectContaining({ owner: "o", repo: "r", number: 12 }));
|
||||
expect(recordActivity).toHaveBeenCalledWith(expect.objectContaining({
|
||||
metadata: expect.objectContaining({ type: "github-issue-created", repo: "o/r", number: 12 }),
|
||||
|
||||
Reference in New Issue
Block a user