Skip to content

Commit afa682c

Browse files
pyxelrdelucis
andauthored
fix: respect global tableOfContents config with zod v4 (withastro#3751)
Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
1 parent 2658adc commit afa682c

5 files changed

Lines changed: 85 additions & 19 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { describe, expect, test, vi } from 'vitest';
2+
import { routes } from '../../utils/routing';
3+
import { getRouteDataTestContext } from '../test-utils';
4+
import { generateRouteData } from '../../utils/routing/data';
5+
6+
vi.mock('astro:content', async () =>
7+
(await import('../test-utils')).mockedAstroContent({
8+
docs: [
9+
['index.mdx', { title: 'Home Page' }],
10+
['showcase.mdx', { title: 'ToC Disabled', tableOfContents: false }],
11+
[
12+
'environmental-impact.md',
13+
{ title: 'Explicit update date', tableOfContents: { minHeadingLevel: 2 } },
14+
],
15+
],
16+
})
17+
);
18+
19+
const headings = [
20+
{ depth: 1, slug: 'heading-1', text: 'Heading 1' },
21+
{ depth: 2, slug: 'heading-2', text: 'Heading 2' },
22+
{ depth: 3, slug: 'heading-3', text: 'Heading 3' },
23+
{ depth: 4, slug: 'heading-4', text: 'Heading 4' },
24+
];
25+
26+
describe('custom table of contents config', () => {
27+
test('table of contents heading levels match configuration', () => {
28+
const route = routes[0]!;
29+
const data = generateRouteData({
30+
props: { ...route, headings },
31+
context: getRouteDataTestContext(),
32+
});
33+
expect(data.toc?.minHeadingLevel).toBe(1);
34+
expect(data.toc?.maxHeadingLevel).toBe(4);
35+
});
36+
37+
test('table of contents can be disabled by frontmatter', () => {
38+
const route = routes[1]!;
39+
const data = generateRouteData({
40+
props: { ...route, headings },
41+
context: getRouteDataTestContext(),
42+
});
43+
expect(data.toc).toBeUndefined();
44+
});
45+
46+
test('table of contents heading levels can be customised by frontmatter', () => {
47+
const route = routes[2]!;
48+
const data = generateRouteData({
49+
props: { ...route, headings },
50+
context: getRouteDataTestContext(),
51+
});
52+
expect(data.toc?.minHeadingLevel).toBe(2);
53+
expect(data.toc?.maxHeadingLevel).toBe(3);
54+
});
55+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { defineVitestConfig } from '../test-config';
2+
3+
export default defineVitestConfig({
4+
title: 'custom ToC config',
5+
tableOfContents: {
6+
minHeadingLevel: 1,
7+
maxHeadingLevel: 4,
8+
},
9+
});

schema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { z } from 'astro/zod';
22
import type { SchemaContext } from 'astro:content';
33
import { HeadConfigSchema } from './schemas/head';
44
import { PrevNextLinkConfigSchema } from './schemas/prevNextLink';
5-
import { TableOfContentsSchema } from './schemas/tableOfContents';
5+
import { FrontmatterTableOfContentsSchema } from './schemas/tableOfContents';
66
import { BadgeConfigSchema } from './schemas/badge';
77
import { HeroSchema } from './schemas/hero';
88
import { SidebarLinkItemHTMLAttributesSchema } from './schemas/sidebar';
@@ -33,7 +33,7 @@ const StarlightFrontmatterSchema = (context: SchemaContext) =>
3333
head: HeadConfigSchema({ source: 'content' }),
3434

3535
/** Override global table of contents configuration for this page. */
36-
tableOfContents: TableOfContentsSchema().optional(),
36+
tableOfContents: FrontmatterTableOfContentsSchema(),
3737

3838
/**
3939
* Set the layout style for this page.

schemas/tableOfContents.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@ import { z } from 'astro/zod';
22

33
const defaults = { minHeadingLevel: 2, maxHeadingLevel: 3 };
44

5-
export const TableOfContentsSchema = () =>
6-
z
7-
.union([
8-
z.object({
9-
/** The level to start including headings at in the table of contents. Default: 2. */
10-
minHeadingLevel: z.int().min(1).max(6).optional().default(2),
11-
/** The level to stop including headings at in the table of contents. Default: 3. */
12-
maxHeadingLevel: z.int().min(1).max(6).optional().default(3),
13-
}),
14-
z.boolean().transform((enabled) => (enabled ? defaults : false)),
15-
])
16-
.default(defaults)
17-
.refine((toc) => (toc ? toc.minHeadingLevel <= toc.maxHeadingLevel : true), {
18-
error: 'minHeadingLevel must be less than or equal to maxHeadingLevel',
19-
});
5+
const TableOfContentsBaseSchema = z
6+
.union([
7+
z.object({
8+
/** The level to start including headings at in the table of contents. Default: 2. */
9+
minHeadingLevel: z.int().min(1).max(6).optional().default(2),
10+
/** The level to stop including headings at in the table of contents. Default: 3. */
11+
maxHeadingLevel: z.int().min(1).max(6).optional().default(3),
12+
}),
13+
z.boolean().transform((enabled) => (enabled ? defaults : false)),
14+
])
15+
.refine((toc) => (toc ? toc.minHeadingLevel <= toc.maxHeadingLevel : true), {
16+
error: 'minHeadingLevel must be less than or equal to maxHeadingLevel',
17+
});
18+
19+
export const UserConfigTableOfContentsSchema = () => TableOfContentsBaseSchema.default(defaults);
20+
21+
export const FrontmatterTableOfContentsSchema = () => TableOfContentsBaseSchema.optional();

utils/user-config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { PagefindConfigDefaults, PagefindConfigSchema } from '../schemas/pagefin
99
import { SidebarItemSchema } from '../schemas/sidebar';
1010
import { TitleConfigSchema, TitleTransformConfigSchema } from '../schemas/site-title';
1111
import { SocialLinksSchema } from '../schemas/social';
12-
import { TableOfContentsSchema } from '../schemas/tableOfContents';
12+
import { UserConfigTableOfContentsSchema } from '../schemas/tableOfContents';
1313
import { BuiltInDefaultLocale } from './i18n';
1414

1515
const LocaleSchema = z.object({
@@ -49,7 +49,7 @@ const UserConfigSchema = z.object({
4949
tagline: z.string().optional(),
5050

5151
/** Configure the defaults for the table of contents on each page. */
52-
tableOfContents: TableOfContentsSchema(),
52+
tableOfContents: UserConfigTableOfContentsSchema(),
5353

5454
/** Enable and configure “Edit this page” links. */
5555
editLink: z

0 commit comments

Comments
 (0)