Core: Support handling duplicated slugs and conflicts such as dir/index.mdx vs dir.mdx

This commit is contained in:
Fuma Nama
2025-06-19 19:27:45 +08:00
parent 5b6cd5f857
commit 35c3c0bd86
14 changed files with 713 additions and 659 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-core': patch
---
Support handling duplicated slugs and conflicts such as `dir/index.mdx` vs `dir.mdx`
+16 -25
View File
@@ -5,14 +5,7 @@ import { VirtualFile } from '@/source/loader';
export interface LoadOptions {
transformers?: Transformer[];
}
export interface I18nLoadOptions extends LoadOptions {
i18n: {
parser: 'dot' | 'dir';
languages: string[];
defaultLanguage: string;
};
buildFiles: (files: VirtualFile[]) => (MetaFile | PageFile)[];
}
export type ContentStorage = FileSystem<MetaFile | PageFile>;
@@ -42,22 +35,17 @@ export type Transformer = (context: {
// Virtual files -> Virtual Storage -> Transformers -> Result
export function loadFiles(
files: VirtualFile[],
buildFile: (file: VirtualFile) => MetaFile | PageFile,
options: LoadOptions,
): ContentStorage {
const { transformers = [] } = options;
const storage: ContentStorage = new FileSystem();
const normalized: VirtualFile[] = files.map((file) => ({
...file,
path: normalizePath(file.path),
}));
for (const file of files) {
const parsedPath = normalizePath(file.path);
storage.write(
parsedPath,
buildFile({
...file,
path: parsedPath,
}),
);
for (const item of options.buildFiles(normalized)) {
storage.write(item.path, item);
}
for (const transformer of transformers) {
@@ -72,18 +60,22 @@ export function loadFiles(
export function loadFilesI18n(
files: VirtualFile[],
buildFile: (file: VirtualFile) => MetaFile | PageFile,
options: I18nLoadOptions,
options: LoadOptions,
i18n: {
parser: 'dot' | 'dir';
languages: string[];
defaultLanguage: string;
},
): Record<string, ContentStorage> {
const parser = options.i18n.parser === 'dir' ? dirParser : dotParser;
const parser = i18n.parser === 'dir' ? dirParser : dotParser;
const storages: Record<string, ContentStorage> = {};
for (const lang of options.i18n.languages) {
for (const lang of i18n.languages) {
storages[lang] = loadFiles(
files.flatMap((file) => {
const [path, locale] = parser(normalizePath(file.path));
if ((locale ?? options.i18n.defaultLanguage) === lang) {
if ((locale ?? i18n.defaultLanguage) === lang) {
return {
...file,
path,
@@ -92,7 +84,6 @@ export function loadFilesI18n(
return [];
}),
buildFile,
options,
);
}
+111 -52
View File
@@ -13,7 +13,13 @@ import {
type BuildPageTreeOptions,
createPageTreeBuilder,
} from './page-tree-builder';
import { type FileInfo, parseFilePath } from './path';
import {
basename,
dirname,
extname,
type FileInfo,
parseFilePath,
} from './path';
import { joinPath } from '@/utils/path';
export interface LoaderConfig {
@@ -269,24 +275,6 @@ export function createGetUrl(baseUrl: string, i18n?: I18nConfig): UrlFn {
};
}
/**
* Convert file path into slugs, also encode non-ASCII characters, so they can work in pathname
*/
export function getSlugs(info: FileInfo): string[] {
const slugs: string[] = [];
for (const seg of info.dirname.split('/')) {
// filter empty names and file groups like (group_name)
if (seg.length > 0 && !/^\(.+\)$/.test(seg)) slugs.push(encodeURI(seg));
}
if (info.name !== 'index') {
slugs.push(encodeURI(info.name));
}
return slugs;
}
export function loader<
Config extends SourceConfig,
I18n extends I18nConfig | undefined = undefined,
@@ -305,43 +293,83 @@ function createOutput(options: LoaderOptions): LoaderOutput<LoaderConfig> {
console.warn('`loader()` now requires a `baseUrl` option to be defined.');
}
const { source, slugs: slugsFn = getSlugs, i18n } = options;
const {
source,
baseUrl = '/',
i18n,
slugs: slugsFn,
url: getUrl = createGetUrl(baseUrl ?? '/', i18n),
transformers,
} = options;
const defaultLanguage = i18n?.defaultLanguage ?? '';
const getUrl =
options.url ?? createGetUrl(options.baseUrl ?? '/', options.i18n);
const files =
typeof source.files === 'function' ? source.files() : source.files;
function buildFile(file: VirtualFile): MetaFile | PageFile {
if (file.type === 'page') {
return {
format: 'page',
path: file.path,
slugs: file.slugs ?? slugsFn(parseFilePath(file.path)),
data: file.data as PageData,
absolutePath: file.absolutePath ?? '',
};
function buildFiles(files: VirtualFile[]) {
const indexFiles: VirtualFile[] = [];
const taken = new Set<string>();
for (const file of files) {
if (file.type !== 'page' || file.slugs) continue;
// for custom slugs function, don't handle conflicting cases like `dir/index.mdx` vs `dir.mdx`
if (isIndex(file.path) && !slugsFn) {
indexFiles.push(file);
continue;
}
file.slugs = slugsFn
? slugsFn(parseFilePath(file.path))
: getSlugs(file.path);
const key = file.slugs.join('/');
if (taken.has(key)) throw new Error('Duplicated slugs');
taken.add(key);
}
return {
format: 'meta',
path: file.path,
absolutePath: file.absolutePath ?? '',
data: file.data as MetaData,
};
for (const file of indexFiles) {
file.slugs = getSlugs(file.path);
if (taken.has(file.slugs.join('/'))) file.slugs.push('index');
}
return files.map((file) => {
if (file.type === 'page') {
return {
format: 'page',
path: file.path,
slugs: file.slugs!,
data: file.data,
absolutePath: file.absolutePath ?? '',
} as PageFile;
}
return {
format: 'meta',
path: file.path,
absolutePath: file.absolutePath ?? '',
data: file.data,
} as MetaFile;
});
}
const storages = i18n
? loadFilesI18n(files, buildFile, {
...options,
i18n: {
? loadFilesI18n(
files,
{
buildFiles,
transformers,
},
{
...i18n,
parser: i18n.parser ?? 'dot',
},
})
)
: {
'': loadFiles(files, buildFile, options),
'': loadFiles(files, {
transformers,
buildFiles,
}),
};
const walker = indexPages(storages, getUrl, i18n);
@@ -372,7 +400,6 @@ function createOutput(options: LoaderOptions): LoaderOutput<LoaderConfig> {
pageTree = v;
},
getPageByHref(href, { dir = '', language } = {}) {
const pages = this.getPages(language);
const [value, hash] = href.split('#', 2);
let target;
@@ -380,17 +407,18 @@ function createOutput(options: LoaderOptions): LoaderOutput<LoaderConfig> {
value.startsWith('.') &&
(value.endsWith('.md') || value.endsWith('.mdx'))
) {
const hrefPath = joinPath(dir, value);
target = pages.find((item) => item.file.path === hrefPath);
const path = joinPath(dir, value);
target = walker.pathToPage.get(`${language}.${path}`);
} else {
target = pages.find((item) => item.url === value);
target = this.getPages(language).find((item) => item.url === value);
}
if (!target) return;
return {
page: target,
hash,
};
if (target)
return {
page: target,
hash,
};
},
getPages(language = defaultLanguage) {
const pages: Page[] = [];
@@ -484,3 +512,34 @@ function fileToPage<Data = PageData>(
locale,
};
}
const GroupRegex = /^\(.+\)$/;
function isIndex(file: string) {
return basename(file, extname(file)) === 'index';
}
/**
* Convert file path into slugs, also encode non-ASCII characters, so they can work in pathname
*/
export function getSlugs(file: string | FileInfo): string[] {
if (typeof file !== 'string') return getSlugs(file.path);
const dir = dirname(file);
const name = basename(file, extname(file));
const slugs: string[] = [];
for (const seg of dir.split('/')) {
// filter empty names and file groups like (group_name)
if (seg.length > 0 && !GroupRegex.test(seg)) slugs.push(encodeURI(seg));
}
if (GroupRegex.test(name))
throw new Error(`Cannot use folder group in file names: ${file}`);
if (name !== 'index') {
slugs.push(encodeURI(name));
}
return slugs;
}
+12
View File
@@ -0,0 +1,12 @@
{
"$id": "root",
"children": [
{
"$id": "test.mdx",
"name": "Hello",
"type": "page",
"url": "/test",
},
],
"name": "",
}
+20
View File
@@ -0,0 +1,20 @@
import { Source, SourceConfig } from '@/source';
export const source: Source<SourceConfig> = {
files: [
{
type: 'page',
path: 'test.mdx',
data: {
title: 'Hello',
},
},
{
type: 'meta',
path: 'meta.json',
data: {
pages: ['test'],
},
},
],
};
@@ -0,0 +1,64 @@
{
"cn": {
"$id": "cn",
"children": [
{
"$id": "test.mdx",
"$ref": {
"file": "test.mdx",
},
"name": "Hello Chinese",
"type": "page",
"url": "/cn/test",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"$ref": {
"file": "nested/test.mdx",
},
"name": "Nested Page Chinese",
"type": "page",
"url": "/cn/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
],
"name": "Docs Chinese",
},
"en": {
"$id": "en",
"children": [
{
"$id": "test.mdx",
"$ref": {
"file": "test.mdx",
},
"name": "Hello",
"type": "page",
"url": "/en/test",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"$ref": {
"file": "nested/test.mdx",
},
"name": "Nested Page",
"type": "page",
"url": "/en/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
],
"name": "Docs English",
},
}
+57
View File
@@ -0,0 +1,57 @@
import type { Source, SourceConfig } from '@/source';
export const source: Source<SourceConfig> = {
files: [
{
type: 'page',
path: 'en/test.mdx',
data: {
title: 'Hello',
},
},
{
type: 'page',
path: 'cn/test.mdx',
data: {
title: 'Hello Chinese',
},
},
{
type: 'meta',
path: 'en/meta.json',
data: {
title: 'Docs English',
pages: ['test', 'nested'],
},
},
{
type: 'meta',
path: 'cn/meta.json',
data: {
title: 'Docs Chinese',
pages: ['test', 'nested'],
},
},
{
type: 'page',
path: 'en/nested/test.mdx',
data: {
title: 'Nested Page',
},
},
{
type: 'page',
path: 'cn/nested/test.mdx',
data: {
title: 'Nested Page Chinese',
},
},
{
type: 'meta',
path: 'cn/nested/meta.json',
data: {
title: 'Nested Chinese',
},
},
],
};
@@ -0,0 +1,52 @@
{
"cn": {
"$id": "cn",
"children": [
{
"$id": "test.mdx",
"name": "Hello Chinese",
"type": "page",
"url": "/cn/test",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"name": "Nested Page Chinese",
"type": "page",
"url": "/cn/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
],
"name": "Docs Chinese",
},
"en": {
"$id": "en",
"children": [
{
"$id": "test.mdx",
"name": "Hello",
"type": "page",
"url": "/test",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"name": "Nested Page",
"type": "page",
"url": "/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
],
"name": "Docs English",
},
}
@@ -0,0 +1,90 @@
[
{
"language": "cn",
"pages": [
{
"absolutePath": "",
"data": {
"title": "Hello Chinese",
},
"file": {
"dirname": "",
"ext": ".mdx",
"flattenedPath": "test",
"name": "test",
"path": "test.mdx",
},
"locale": "cn",
"path": "test.mdx",
"slugs": [
"test",
],
"url": "/cn/test",
},
{
"absolutePath": "",
"data": {
"title": "Nested Page Chinese",
},
"file": {
"dirname": "nested",
"ext": ".mdx",
"flattenedPath": "nested/test",
"name": "test",
"path": "nested/test.mdx",
},
"locale": "cn",
"path": "nested/test.mdx",
"slugs": [
"nested",
"test",
],
"url": "/cn/nested/test",
},
],
},
{
"language": "en",
"pages": [
{
"absolutePath": "",
"data": {
"title": "Hello",
},
"file": {
"dirname": "",
"ext": ".mdx",
"flattenedPath": "test",
"name": "test",
"path": "test.mdx",
},
"locale": "en",
"path": "test.mdx",
"slugs": [
"test",
],
"url": "/en/test",
},
{
"absolutePath": "",
"data": {
"title": "Nested Page",
},
"file": {
"dirname": "nested",
"ext": ".mdx",
"flattenedPath": "nested/test",
"name": "test",
"path": "nested/test.mdx",
},
"locale": "en",
"path": "nested/test.mdx",
"slugs": [
"nested",
"test",
],
"url": "/en/nested/test",
},
],
},
]
+64
View File
@@ -0,0 +1,64 @@
{
"cn": {
"$id": "cn",
"children": [
{
"$id": "test.mdx",
"$ref": {
"file": "test.mdx",
},
"name": "Hello Chinese",
"type": "page",
"url": "/cn/test",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"$ref": {
"file": "nested/test.mdx",
},
"name": "Nested Page Chinese",
"type": "page",
"url": "/cn/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
],
"name": "Docs Chinese",
},
"en": {
"$id": "en",
"children": [
{
"$id": "test.mdx",
"$ref": {
"file": "test.mdx",
},
"name": "Hello",
"type": "page",
"url": "/en/test",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"$ref": {
"file": "nested/test.mdx",
},
"name": "Nested Page",
"type": "page",
"url": "/en/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
],
"name": "Docs English",
},
}
+57
View File
@@ -0,0 +1,57 @@
import type { Source, SourceConfig } from '@/source';
export const source: Source<SourceConfig> = {
files: [
{
type: 'page',
path: 'test.mdx',
data: {
title: 'Hello',
},
},
{
type: 'page',
path: 'test.cn.mdx',
data: {
title: 'Hello Chinese',
},
},
{
type: 'meta',
path: 'meta.en.json',
data: {
title: 'Docs English',
pages: ['test', 'nested'],
},
},
{
type: 'meta',
path: 'meta.cn.json',
data: {
title: 'Docs Chinese',
pages: ['test', 'nested'],
},
},
{
type: 'page',
path: '/nested/test.en.mdx',
data: {
title: 'Nested Page',
},
},
{
type: 'page',
path: '/nested/test.cn.mdx',
data: {
title: 'Nested Page Chinese',
},
},
{
type: 'meta',
path: '/nested/meta.cn.json',
data: {
title: 'Nested Chinese',
},
},
],
};
+63
View File
@@ -0,0 +1,63 @@
{
"$id": "root",
"children": [
{
"$id": "test.mdx",
"name": "Hello",
"type": "page",
"url": "/test",
},
{
"$id": "(nested)",
"children": [
{
"$id": "(nested)/hello.mdx",
"name": "Route Group Page",
"type": "page",
"url": "/hello",
},
],
"name": "Nested",
"type": "folder",
},
{
"$id": "hello",
"children": [],
"index": {
"$id": "hello/index.mdx",
"name": "Another hello page",
"type": "page",
"url": "/hello/index",
},
"name": "Another hello page",
"type": "folder",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"name": "Nested Page",
"type": "page",
"url": "/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
{
"external": true,
"name": "Text",
"type": "page",
"url": "https://google.com",
},
{
"external": true,
"icon": "Icon",
"name": "Text",
"type": "page",
"url": "https://google.com",
},
],
"name": "",
}
+54
View File
@@ -0,0 +1,54 @@
import type { Source, SourceConfig } from '@/source';
export const source: Source<SourceConfig> = {
files: [
{
type: 'page',
path: 'test.mdx',
data: {
title: 'Hello',
},
},
{
type: 'page',
path: 'hidden.mdx',
data: {
title: 'Hidden',
},
},
{
type: 'meta',
path: 'meta.json',
data: {
pages: [
'...',
'!hidden',
'nested',
'[Text](https://google.com)',
'[Icon][Text](https://google.com)',
],
},
},
{
type: 'page',
path: '/hello/index.mdx',
data: {
title: 'Another hello page',
},
},
{
type: 'page',
path: '/nested/test.mdx',
data: {
title: 'Nested Page',
},
},
{
type: 'page',
path: '/(nested)/hello.mdx',
data: {
title: 'Route Group Page',
},
},
],
};
+48 -582
View File
@@ -1,13 +1,6 @@
import { expect, test } from 'vitest';
import {
createGetUrl,
getSlugs,
loader,
type Source,
type SourceConfig,
} from '@/source/loader';
import { createGetUrl, getSlugs, loader } from '@/source/loader';
import { parseFilePath } from '@/source';
import { PageTree } from '../dist/server';
import type { ReactElement } from 'react';
import { removeUndefined } from '@/utils/remove-undefined';
@@ -29,10 +22,7 @@ test('get slugs: folder groups', () => {
'page',
]);
expect(getSlugs(parseFilePath('nested/(page).mdx'))).toStrictEqual([
'nested',
'(page)',
]);
expect(() => getSlugs(parseFilePath('nested/(page).mdx'))).toThrowError();
});
test('Get URL: Empty', () => {
@@ -48,47 +38,18 @@ test('Get URL: Base', () => {
expect(getUrl([''])).toBe('/docs');
});
test('Loader: Simple', () => {
test('Loader: Simple', async () => {
const result = loader({
baseUrl: '/',
pageTree: {
noRef: true,
},
source: {
files: [
{
type: 'page',
path: 'test.mdx',
data: {
title: 'Hello',
},
},
{
type: 'meta',
path: 'meta.json',
data: {
pages: ['test'],
},
},
],
},
source: (await import('./fixtures/page-trees/basic')).source,
});
expect(removeUndefined(result.pageTree, true), 'Page Tree')
.toMatchInlineSnapshot(`
{
"$id": "root",
"children": [
{
"$id": "test.mdx",
"name": "Hello",
"type": "page",
"url": "/test",
},
],
"name": "",
}
`);
await expect(removeUndefined(result.pageTree, true)).toMatchFileSnapshot(
'./fixtures/page-trees/basic.tree.json',
);
expect(result.getPages().length).toBe(1);
expect(result.getPage(['test'])).toBeDefined();
@@ -101,108 +62,23 @@ test('Nested Directories', async () => {
pageTree: {
noRef: true,
},
source: {
files: [
{
type: 'page',
path: 'test.mdx',
data: {
title: 'Hello',
},
},
{
type: 'page',
path: 'hidden.mdx',
data: {
title: 'Hidden',
},
},
{
type: 'meta',
path: 'meta.json',
data: {
pages: [
'...',
'!hidden',
'nested',
'[Text](https://google.com)',
'[Icon][Text](https://google.com)',
],
},
},
{
type: 'page',
path: '/nested/test.mdx',
data: {
title: 'Nested Page',
},
},
{
type: 'page',
path: '/(nested)/hello.mdx',
data: {
title: 'Route Group Page',
},
},
],
},
source: (await import('./fixtures/page-trees/nested')).source,
});
expect(removeUndefined(result.pageTree, true), 'Page Tree')
await expect(removeUndefined(result.pageTree, true)).toMatchFileSnapshot(
'./fixtures/page-trees/nested.tree.json',
);
expect(result.getPages().map((page) => page.slugs.join('/')))
.toMatchInlineSnapshot(`
{
"$id": "root",
"children": [
{
"$id": "test.mdx",
"name": "Hello",
"type": "page",
"url": "/test",
},
{
"$id": "(nested)",
"children": [
{
"$id": "(nested)/hello.mdx",
"name": "Route Group Page",
"type": "page",
"url": "/hello",
},
],
"name": "Nested",
"type": "folder",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"name": "Nested Page",
"type": "page",
"url": "/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
{
"external": true,
"name": "Text",
"type": "page",
"url": "https://google.com",
},
{
"external": true,
"icon": "Icon",
"name": "Text",
"type": "page",
"url": "https://google.com",
},
],
"name": "",
}
[
"test",
"hidden",
"hello/index",
"nested/test",
"hello",
]
`);
expect(result.getPages().length).toBe(4);
// page in folder
expect(result.getPage(['nested', 'test'])).toBeDefined();
@@ -210,259 +86,25 @@ test('Nested Directories', async () => {
expect(result.getPage(['hello'])).toBeDefined();
});
const i18nSource: Source<SourceConfig> = {
files: [
{
type: 'page',
path: 'test.mdx',
data: {
title: 'Hello',
},
},
{
type: 'page',
path: 'test.cn.mdx',
data: {
title: 'Hello Chinese',
},
},
{
type: 'meta',
path: 'meta.en.json',
data: {
title: 'Docs English',
pages: ['test', 'nested'],
},
},
{
type: 'meta',
path: 'meta.cn.json',
data: {
title: 'Docs Chinese',
pages: ['test', 'nested'],
},
},
{
type: 'page',
path: '/nested/test.en.mdx',
data: {
title: 'Nested Page',
},
},
{
type: 'page',
path: '/nested/test.cn.mdx',
data: {
title: 'Nested Page Chinese',
},
},
{
type: 'meta',
path: '/nested/meta.cn.json',
data: {
title: 'Nested Chinese',
},
},
],
};
test('Internationalized Routing', () => {
test('Internationalized Routing', async () => {
const result = loader({
baseUrl: '/',
i18n: {
languages: ['cn', 'en'],
defaultLanguage: 'en',
},
source: i18nSource,
source: (await import('./fixtures/page-trees/i18n')).source,
});
expect(
result.getNodePage(result.pageTree['en'].children[0] as PageTree.Item),
).toEqual(
expect.objectContaining({
data: {
title: 'Hello',
},
}),
await expect(removeUndefined(result.pageTree, true)).toMatchFileSnapshot(
'./fixtures/page-trees/i18n.tree.json',
);
expect(
result.getNodePage(
result.pageTree['en'].children[0] as PageTree.Item,
'cn',
),
).toEqual(
expect.objectContaining({
data: {
title: 'Hello Chinese',
},
}),
);
expect(removeUndefined(result.pageTree['en'], true), 'Page Tree')
.toMatchInlineSnapshot(`
{
"$id": "en",
"children": [
{
"$id": "test.mdx",
"$ref": {
"file": "test.mdx",
},
"name": "Hello",
"type": "page",
"url": "/en/test",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"$ref": {
"file": "nested/test.mdx",
},
"name": "Nested Page",
"type": "page",
"url": "/en/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
],
"name": "Docs English",
}
`);
expect(removeUndefined(result.pageTree['cn'], true), 'Page Tree')
.toMatchInlineSnapshot(`
{
"$id": "cn",
"children": [
{
"$id": "test.mdx",
"$ref": {
"file": "test.mdx",
},
"name": "Hello Chinese",
"type": "page",
"url": "/cn/test",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"$ref": {
"file": "nested/test.mdx",
},
"name": "Nested Page Chinese",
"type": "page",
"url": "/cn/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
],
"name": "Docs Chinese",
}
`);
expect(result.getLanguages()).toMatchInlineSnapshot(`
[
{
"language": "cn",
"pages": [
{
"absolutePath": "",
"data": {
"title": "Hello Chinese",
},
"file": {
"dirname": "",
"ext": ".mdx",
"flattenedPath": "test",
"name": "test",
"path": "test.mdx",
},
"locale": "cn",
"path": "test.mdx",
"slugs": [
"test",
],
"url": "/cn/test",
},
{
"absolutePath": "",
"data": {
"title": "Nested Page Chinese",
},
"file": {
"dirname": "nested",
"ext": ".mdx",
"flattenedPath": "nested/test",
"name": "test",
"path": "nested/test.mdx",
},
"locale": "cn",
"path": "nested/test.mdx",
"slugs": [
"nested",
"test",
],
"url": "/cn/nested/test",
},
],
},
{
"language": "en",
"pages": [
{
"absolutePath": "",
"data": {
"title": "Hello",
},
"file": {
"dirname": "",
"ext": ".mdx",
"flattenedPath": "test",
"name": "test",
"path": "test.mdx",
},
"locale": "en",
"path": "test.mdx",
"slugs": [
"test",
],
"url": "/en/test",
},
{
"absolutePath": "",
"data": {
"title": "Nested Page",
},
"file": {
"dirname": "nested",
"ext": ".mdx",
"flattenedPath": "nested/test",
"name": "test",
"path": "nested/test.mdx",
},
"locale": "en",
"path": "nested/test.mdx",
"slugs": [
"nested",
"test",
],
"url": "/en/nested/test",
},
],
},
]
`);
await expect(
removeUndefined(result.getLanguages(), true),
).toMatchFileSnapshot('./fixtures/page-trees/i18n.entries.json');
});
test('Internationalized Routing: Hide Prefix', () => {
test('Internationalized Routing: Hide Prefix', async () => {
const result = loader({
baseUrl: '/',
i18n: {
@@ -473,72 +115,33 @@ test('Internationalized Routing: Hide Prefix', () => {
pageTree: {
noRef: true,
},
source: i18nSource,
source: (await import('./fixtures/page-trees/i18n')).source,
});
expect(removeUndefined(result.pageTree['en'], true), 'Page Tree')
.toMatchInlineSnapshot(`
{
"$id": "en",
"children": [
{
"$id": "test.mdx",
"name": "Hello",
"type": "page",
"url": "/test",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"name": "Nested Page",
"type": "page",
"url": "/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
],
"name": "Docs English",
}
`);
expect(removeUndefined(result.pageTree['cn'], true), 'Page Tree')
.toMatchInlineSnapshot(`
{
"$id": "cn",
"children": [
{
"$id": "test.mdx",
"name": "Hello Chinese",
"type": "page",
"url": "/cn/test",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"name": "Nested Page Chinese",
"type": "page",
"url": "/cn/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
],
"name": "Docs Chinese",
}
`);
await expect(removeUndefined(result.pageTree, true)).toMatchFileSnapshot(
'./fixtures/page-trees/i18n-no-prefix.tree.json',
);
expect(result.getPages().length).toBe(2);
expect(result.getPage(['test'])?.url).toBe('/test');
expect(result.getPage(['test'], 'cn')?.url).toBe('/cn/test');
});
test('Internationalized Routing: dir', async () => {
const result = loader({
baseUrl: '/',
i18n: {
parser: 'dir',
languages: ['cn', 'en'],
defaultLanguage: 'en',
},
source: (await import('./fixtures/page-trees/i18n-dir')).source,
});
await expect(removeUndefined(result.pageTree, true)).toMatchFileSnapshot(
'./fixtures/page-trees/i18n-dir.tree.json',
);
});
test('Loader: Without meta.json', () => {
const result = loader({
baseUrl: '/',
@@ -649,140 +252,3 @@ test('Loader: Rest operator', () => {
}
`);
});
const i18nSource2: Source<SourceConfig> = {
files: [
{
type: 'page',
path: 'en/test.mdx',
data: {
title: 'Hello',
},
},
{
type: 'page',
path: 'cn/test.mdx',
data: {
title: 'Hello Chinese',
},
},
{
type: 'meta',
path: 'en/meta.json',
data: {
title: 'Docs English',
pages: ['test', 'nested'],
},
},
{
type: 'meta',
path: 'cn/meta.json',
data: {
title: 'Docs Chinese',
pages: ['test', 'nested'],
},
},
{
type: 'page',
path: 'en/nested/test.mdx',
data: {
title: 'Nested Page',
},
},
{
type: 'page',
path: 'cn/nested/test.mdx',
data: {
title: 'Nested Page Chinese',
},
},
{
type: 'meta',
path: 'cn/nested/meta.json',
data: {
title: 'Nested Chinese',
},
},
],
};
test('Internationalized Routing: dir', () => {
const result = loader({
baseUrl: '/',
i18n: {
parser: 'dir',
languages: ['cn', 'en'],
defaultLanguage: 'en',
},
source: i18nSource2,
});
expect(removeUndefined(result.pageTree['en'], true), 'Page Tree')
.toMatchInlineSnapshot(`
{
"$id": "en",
"children": [
{
"$id": "test.mdx",
"$ref": {
"file": "test.mdx",
},
"name": "Hello",
"type": "page",
"url": "/en/test",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"$ref": {
"file": "nested/test.mdx",
},
"name": "Nested Page",
"type": "page",
"url": "/en/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
],
"name": "Docs English",
}
`);
expect(removeUndefined(result.pageTree['cn'], true), 'Page Tree')
.toMatchInlineSnapshot(`
{
"$id": "cn",
"children": [
{
"$id": "test.mdx",
"$ref": {
"file": "test.mdx",
},
"name": "Hello Chinese",
"type": "page",
"url": "/cn/test",
},
{
"$id": "nested",
"children": [
{
"$id": "nested/test.mdx",
"$ref": {
"file": "nested/test.mdx",
},
"name": "Nested Page Chinese",
"type": "page",
"url": "/cn/nested/test",
},
],
"name": "Nested",
"type": "folder",
},
],
"name": "Docs Chinese",
}
`);
});