Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions packages/provider-core/src/server/textGenerationUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, it } from "vite-plus/test";

import { sanitizeCommitSubject } from "./textGenerationUtils.ts";

describe("sanitizeCommitSubject", () => {
it.each([
{ name: "an emoji", prefixLength: 71, character: "๐Ÿš€" },
{ name: "an accented character", prefixLength: 71, character: "e\u0301" },
{ name: "an emoji with a skin tone", prefixLength: 70, character: "๐Ÿ‘๐Ÿฝ" },
{ name: "a flag", prefixLength: 69, character: "๐Ÿ‡ฎ๐Ÿ‡น" },
{ name: "a joined emoji", prefixLength: 65, character: "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ" },
])("keeps $name intact at the subject limit", ({ prefixLength, character }) => {
const prefix = "a".repeat(prefixLength);

expect(sanitizeCommitSubject(`${prefix}${character} more`)).toBe(prefix);
});

it("keeps a complete character that ends exactly at the limit", () => {
const expected = `${"a".repeat(70)}๐Ÿš€`;

expect(sanitizeCommitSubject(`${expected} more`)).toBe(expected);
});

it("uses the existing fallback when a single character exceeds the limit", () => {
expect(sanitizeCommitSubject(`a${"\u0301".repeat(72)} more`)).toBe("Update project files");
});

it("keeps the existing ASCII limit and subject normalization", () => {
expect(sanitizeCommitSubject("a".repeat(80))).toBe("a".repeat(72));
expect(sanitizeCommitSubject(" Fix tests...\nignored ")).toBe("Fix tests");
expect(sanitizeCommitSubject("...")).toBe("Update project files");
});
});
10 changes: 9 additions & 1 deletion packages/provider-core/src/server/textGenerationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ export function sanitizeCommitSubject(raw: string): string {
if (withoutTrailingPeriod.length <= 72) {
return withoutTrailingPeriod;
}
return withoutTrailingPeriod.slice(0, 72).trimEnd();
const segments = new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(
withoutTrailingPeriod,
);
let subject = "";
for (const { segment } of segments) {
if (subject.length + segment.length > 72) break;
subject += segment;
}
return subject.trimEnd() || "Update project files";
}

/** Normalise a raw PR title to a single line with a sensible fallback. */
Expand Down
Loading