294 lines · 9.6 KB · TypeScript
RawDownload
import type { PickedElementPayload } from "@t3tools/contracts";import { describe, expect, it } from "vite-plus/test"; import {  appendElementContextsToPrompt,  buildElementContextBlock,  type ElementContextSelection,  elementContextDedupKey,  extractTrailingElementContexts,  formatElementContextLabel,  formatElementContextSourceLabel,  newElementContextId,  normalizeElementContextSelection,} from "./elementContext"; function makePayload(overrides?: Partial<PickedElementPayload>): PickedElementPayload {  return {    pageUrl: "https://example.com/dashboard",    pageTitle: "Dashboard",    tagName: "BUTTON",    selector: "button.submit",    htmlPreview: '<button class="submit">Save</button>',    componentName: "SubmitButton",    source: {      functionName: "SubmitButton",      fileName: "/repo/src/Button.tsx",      lineNumber: 12,      columnNumber: 5,    },    stack: [      {        functionName: "SubmitButton",        fileName: "/repo/src/Button.tsx",        lineNumber: 12,        columnNumber: 5,      },    ],    styles: ".submit { color: white; }",    pickedAt: "2026-05-03T18:00:00.000Z",    ...overrides,  };} function makeSelection(overrides?: Partial<ElementContextSelection>): ElementContextSelection {  return {    pageUrl: "https://example.com/dashboard",    pageTitle: "Dashboard",    tagName: "button",    selector: "button.submit",    htmlPreview: '<button class="submit">Save</button>',    componentName: "SubmitButton",    source: {      functionName: "SubmitButton",      fileName: "/repo/src/Button.tsx",      lineNumber: 12,      columnNumber: 5,    },    styles: ".submit { color: white; }",    ...overrides,  };} describe("normalizeElementContextSelection", () => {  it("lowercases the tag, trims strings, and prefers `source` over `stack[0]`", () => {    const result = normalizeElementContextSelection(      makePayload({        tagName: "  Button  ",        pageUrl: "  https://example.com  ",        pageTitle: "  Dashboard  ",        selector: "   ",        componentName: "   ",        source: {          functionName: " Outer ",          fileName: " /repo/Outer.tsx ",          lineNumber: 7,          columnNumber: 0,        },        stack: [          {            functionName: "Inner",            fileName: "/repo/Inner.tsx",            lineNumber: 99,            columnNumber: 9,          },        ],      }),    );    expect(result).not.toBeNull();    expect(result?.tagName).toBe("button");    expect(result?.pageUrl).toBe("https://example.com");    expect(result?.pageTitle).toBe("Dashboard");    expect(result?.selector).toBeNull();    expect(result?.componentName).toBeNull();    expect(result?.source).toEqual({      functionName: "Outer",      fileName: "/repo/Outer.tsx",      lineNumber: 7,      columnNumber: 0,    });  });   it("returns null when pageUrl or tagName is empty", () => {    expect(normalizeElementContextSelection(makePayload({ pageUrl: "" }))).toBeNull();    expect(normalizeElementContextSelection(makePayload({ tagName: "   " }))).toBeNull();  });   it("clamps oversized htmlPreview / styles so we don't blow localStorage", () => {    const huge = "x".repeat(10_000);    const result = normalizeElementContextSelection(      makePayload({ htmlPreview: huge, styles: huge }),    );    expect(result).not.toBeNull();    expect(result!.htmlPreview.length).toBeLessThanOrEqual(4000);    expect(result!.styles.length).toBeLessThanOrEqual(4000);    // Truncated values should end with the ellipsis sentinel    expect(result!.htmlPreview.endsWith("…")).toBe(true);    expect(result!.styles.endsWith("…")).toBe(true);  });   it("normalizes Windows line endings inside html/styles", () => {    const result = normalizeElementContextSelection(      makePayload({ htmlPreview: "<a>\r\nhi\r\n</a>", styles: ".a {\r\n  color: red;\r\n}" }),    );    expect(result?.htmlPreview).toBe("<a>\nhi\n</a>");    expect(result?.styles).toBe(".a {\n  color: red;\n}");  });   it("falls back to stack[0] when payload.source is null", () => {    const result = normalizeElementContextSelection(      makePayload({        source: null,        stack: [          {            functionName: "FromStack",            fileName: "/repo/FromStack.tsx",            lineNumber: 3,            columnNumber: null,          },        ],      }),    );    expect(result?.source).toEqual({      functionName: "FromStack",      fileName: "/repo/FromStack.tsx",      lineNumber: 3,      columnNumber: null,    });  });}); describe("formatElementContextLabel", () => {  it("prefers component name over tag name", () => {    expect(formatElementContextLabel(makeSelection())).toBe("<SubmitButton>");  });   it("falls back to tag name when no component name is present", () => {    expect(formatElementContextLabel(makeSelection({ componentName: null }))).toBe("<button>");  });   it("truncates very long tag names", () => {    const tagName = "custom-".concat("x".repeat(60));    const result = formatElementContextLabel(makeSelection({ componentName: null, tagName }));    expect(result.length).toBeLessThan(tagName.length);    expect(result.endsWith("…>")).toBe(true);  });}); describe("formatElementContextSourceLabel", () => {  it("renders basename + line number", () => {    expect(formatElementContextSourceLabel(makeSelection())).toBe("Button.tsx:12");  });   it("returns null when source has no fileName", () => {    expect(formatElementContextSourceLabel(makeSelection({ source: null }))).toBeNull();  });   it("omits line number when missing", () => {    expect(      formatElementContextSourceLabel(        makeSelection({          source: {            functionName: null,            fileName: "/a/b/Foo.tsx",            lineNumber: null,            columnNumber: null,          },        }),      ),    ).toBe("Foo.tsx");  });}); describe("elementContextDedupKey", () => {  it("treats picks of the same element on the same page as duplicates", () => {    const first = makeSelection();    const second = makeSelection({ htmlPreview: "different preview", styles: "different styles" });    expect(elementContextDedupKey(first)).toBe(elementContextDedupKey(second));  });   it("differentiates picks with different selectors", () => {    expect(elementContextDedupKey(makeSelection())).not.toBe(      elementContextDedupKey(makeSelection({ selector: "button.cancel" })),    );  });   it("differentiates picks with different page urls", () => {    expect(elementContextDedupKey(makeSelection())).not.toBe(      elementContextDedupKey(makeSelection({ pageUrl: "https://example.com/other" })),    );  });}); describe("buildElementContextBlock + appendElementContextsToPrompt", () => {  it("returns empty string for empty contexts", () => {    expect(buildElementContextBlock([])).toBe("");    expect(appendElementContextsToPrompt("Hello", [])).toBe("Hello");  });   it("serializes a context with header / url / selector / source / html / styles", () => {    const block = buildElementContextBlock([makeSelection()]);    expect(block.startsWith("<element_context>")).toBe(true);    expect(block.endsWith("</element_context>")).toBe(true);    expect(block).toContain("- <SubmitButton> (Button.tsx:12):");    expect(block).toContain("  url: https://example.com/dashboard");    expect(block).toContain("  selector: button.submit");    expect(block).toContain("  source: /repo/src/Button.tsx:12:5");    expect(block).toContain("  html:");    expect(block).toContain("  styles:");  });   it("appends with a blank line separator when prompt has text", () => {    expect(appendElementContextsToPrompt("Investigate this", [makeSelection()])).toBe(      [        "Investigate this",        "",        "<element_context>",        "- <SubmitButton> (Button.tsx:12):",        "  url: https://example.com/dashboard",        "  selector: button.submit",        "  source: /repo/src/Button.tsx:12:5",        "  html:",        '  <button class="submit">Save</button>',        "  styles:",        "  .submit { color: white; }",        "</element_context>",      ].join("\n"),    );  });   it("emits no leading blank when prompt is empty", () => {    expect(      appendElementContextsToPrompt("", [makeSelection()]).startsWith("<element_context>"),    ).toBe(true);  });}); describe("extractTrailingElementContexts", () => {  it("round-trips appendElementContextsToPrompt and recovers prompt + entries", () => {    const prompt = appendElementContextsToPrompt("Investigate this", [      makeSelection(),      makeSelection({ selector: "button.cancel", componentName: "CancelButton" }),    ]);    const result = extractTrailingElementContexts(prompt);    expect(result.promptText).toBe("Investigate this");    expect(result.contextCount).toBe(2);    expect(result.contexts.map((c) => c.header)).toEqual([      "<SubmitButton> (Button.tsx:12)",      "<CancelButton> (Button.tsx:12)",    ]);    expect(result.contexts[0]?.body).toContain("url: https://example.com/dashboard");    expect(result.contexts[0]?.body).toContain("selector: button.submit");  });   it("returns the original prompt unchanged when no trailing block exists", () => {    expect(extractTrailingElementContexts("hi")).toEqual({      promptText: "hi",      contextCount: 0,      contexts: [],    });  });}); describe("newElementContextId", () => {  it("returns a non-empty string with the element prefix", () => {    const id = newElementContextId();    expect(id.startsWith("el_")).toBe(true);    expect(id.length).toBeGreaterThan(3);  });   it("returns unique ids on repeated calls", () => {    const ids = new Set(Array.from({ length: 10 }, () => newElementContextId()));    expect(ids.size).toBe(10);  });});