Skip to content

Commit a8b2125

Browse files
authored
(chore): always respect frontmatter title (#2666)
1 parent 0881554 commit a8b2125

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { serializeMdx } from "../bundler/serialize";
2+
3+
vi.mock("server-only", () => ({}));
4+
5+
describe("remarkExtractTitle", () => {
6+
it("extracts title from first h1 heading and adds to frontmatter", async () => {
7+
const result = await serializeMdx("# My Title");
8+
expect(result?.frontmatter?.title).toBe("My Title");
9+
});
10+
11+
it("preserves existing frontmatter title and does not extract from h1", async () => {
12+
const result = await serializeMdx(
13+
"---\ntitle: Existing Title\n---\n\n# My Title"
14+
);
15+
expect(result?.frontmatter?.title).toBe("Existing Title");
16+
});
17+
18+
it("removes the extracted h1 heading from the content", async () => {
19+
const result = await serializeMdx("# My Title\n\nSome content");
20+
expect(result?.frontmatter?.title).toBe("My Title");
21+
});
22+
23+
it("extracts title from first h1 when multiple h1s exist", async () => {
24+
const result = await serializeMdx("# First Title\n\n# Second Title");
25+
expect(result?.frontmatter?.title).toBe("First Title");
26+
});
27+
28+
it("trims whitespace from extracted title", async () => {
29+
const result = await serializeMdx("# My Title ");
30+
expect(result?.frontmatter?.title).toBe("My Title");
31+
});
32+
33+
it("updates existing frontmatter while preserving other fields", async () => {
34+
const result = await serializeMdx(
35+
"---\ndescription: Some description\n---\n\n# My Title"
36+
);
37+
expect(result?.frontmatter?.title).toBe("My Title");
38+
expect(result?.frontmatter?.description).toBe("Some description");
39+
});
40+
41+
it("preserves frontmatter title when no h1 exists", async () => {
42+
const result = await serializeMdx(
43+
"---\ntitle: Frontmatter Title\n---\n\nSome content without heading"
44+
);
45+
expect(result?.frontmatter?.title).toBe("Frontmatter Title");
46+
});
47+
48+
it("preserves frontmatter title when h1 exists but is not the first content", async () => {
49+
const result = await serializeMdx(
50+
"---\ntitle: Frontmatter Title\n---\n\nSome intro text\n\n# Page Heading\n\nMore content"
51+
);
52+
expect(result?.frontmatter?.title).toBe("Frontmatter Title");
53+
});
54+
55+
it("preserves frontmatter title when multiple h1s exist", async () => {
56+
const result = await serializeMdx(
57+
"---\ntitle: Frontmatter Title\n---\n\n# First Heading\n\n# Second Heading"
58+
);
59+
expect(result?.frontmatter?.title).toBe("Frontmatter Title");
60+
});
61+
62+
it("preserves frontmatter title when h1 contains markdown", async () => {
63+
const result = await serializeMdx(
64+
"---\ntitle: Frontmatter Title\n---\n\n# Page **Heading** with `code`"
65+
);
66+
expect(result?.frontmatter?.title).toBe("Frontmatter Title");
67+
});
68+
});

packages/fern-docs/bundle/src/mdx/plugins/remark-extract-title.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ import { type Mdast, Unified, mdastToString } from "@fern-docs/mdx";
1414
*/
1515
export const remarkExtractTitle: Unified.Plugin<[], Mdast.Root> = () => {
1616
return (tree: Mdast.Root) => {
17+
const yaml = tree.children.find((child) => child.type === "yaml");
18+
19+
if (yaml && parseYaml(yaml.value).title) {
20+
return;
21+
}
22+
1723
const firstHeadingIndex = tree.children.findIndex(
1824
(child) => child.type !== "mdxjsEsm" && child.type !== "yaml"
1925
);
@@ -30,7 +36,6 @@ export const remarkExtractTitle: Unified.Plugin<[], Mdast.Root> = () => {
3036
}
3137
tree.children.splice(firstHeadingIndex, 1);
3238

33-
const yaml = tree.children.find((child) => child.type === "yaml");
3439
if (yaml == null) {
3540
tree.children.unshift({
3641
type: "yaml",

0 commit comments

Comments
 (0)