From 812c1e61d60be029c0c309f1288443afdcaefce3 Mon Sep 17 00:00:00 2001 From: SonMooSans Date: Wed, 27 Dec 2023 18:57:42 +0800 Subject: [PATCH] Headless: Rewrite tests --- packages/headless/src/source/create.ts | 10 +- packages/headless/src/source/file-graph.ts | 4 +- .../headless/src/source/page-tree-builder.ts | 11 +- packages/headless/src/source/path.ts | 19 +- packages/headless/src/source/types.ts | 2 +- packages/headless/test/graph.test.ts | 236 +++++++++--------- packages/headless/test/page-builder.test.ts | 223 ++++++++--------- packages/headless/test/path.test.ts | 75 ++++++ 8 files changed, 319 insertions(+), 261 deletions(-) create mode 100644 packages/headless/test/path.test.ts diff --git a/packages/headless/src/source/create.ts b/packages/headless/src/source/create.ts index 11d484875..785231262 100644 --- a/packages/headless/src/source/create.ts +++ b/packages/headless/src/source/create.ts @@ -107,8 +107,8 @@ function createGetUrl( baseUrl: string, ): (slugs: string[], locale?: string) => string { return (slugs, locale) => { - const paths = [baseUrl, ...slugs]; - if (locale) paths.push(locale); + let paths = [baseUrl, ...slugs]; + if (locale) paths = [baseUrl, locale, ...slugs]; return joinPaths(paths, 'leading'); }; @@ -134,8 +134,10 @@ function createOutput({ rootDir = '', transformers, baseUrl = '/', - slugs = (info) => - info.flattenedPath.split('/').filter((s) => !['index'].includes(s)), + slugs = (info) => { + const result = [...info.dirname.split('/'), info.name].filter(Boolean); + return result[result.length - 1] === 'index' ? result.slice(0, -1) : result; + }, url = createGetUrl(baseUrl), }: LoaderOptions): LoaderOutput { const result = load({ diff --git a/packages/headless/src/source/file-graph.ts b/packages/headless/src/source/file-graph.ts index 670178840..ba0de270f 100644 --- a/packages/headless/src/source/file-graph.ts +++ b/packages/headless/src/source/file-graph.ts @@ -47,11 +47,11 @@ export function makeGraph(): Storage { const folders = new Map(); const root: Folder = { type: 'folder', - file: parseFolderPath('.'), + file: parseFolderPath(''), children: [], }; - folders.set('.', root); + folders.set('', root); return { root() { diff --git a/packages/headless/src/source/page-tree-builder.ts b/packages/headless/src/source/page-tree-builder.ts index 4e2678d50..0dacdbe0f 100644 --- a/packages/headless/src/source/page-tree-builder.ts +++ b/packages/headless/src/source/page-tree-builder.ts @@ -1,6 +1,7 @@ import type { ReactElement } from 'react'; import type * as PageTree from '../server/page-tree'; import type * as FileGraph from './file-graph'; +import { joinPaths } from './path'; interface PageTreeBuilderContext { storage: FileGraph.Storage; @@ -64,10 +65,12 @@ function getFolderMeta( folder: FileGraph.Folder, ctx: PageTreeBuilderContext, ): FileGraph.Meta | undefined { - let meta = ctx.storage.read(`${folder.file.path}/meta`); + let meta = ctx.storage.read(joinPaths([folder.file.path, 'meta'])); if (ctx.lang) { - meta = ctx.storage.read(`${folder.file.path}/meta.${ctx.lang}`) ?? meta; + meta = + ctx.storage.read(joinPaths([folder.file.path, `meta.${ctx.lang}`])) ?? + meta; } if (meta?.type === 'meta') return meta; @@ -114,8 +117,8 @@ function buildFolderNode( const extractName = extractResult?.groups?.name ?? item; const itemNode = - ctx.storage.readDir(`${folder.file.path}/${extractName}`) ?? - ctx.storage.read(`${folder.file.path}/${extractName}`); + ctx.storage.readDir(joinPaths([folder.file.path, extractName])) ?? + ctx.storage.read(joinPaths([folder.file.path, extractName])); if (!itemNode) return []; diff --git a/packages/headless/src/source/path.ts b/packages/headless/src/source/path.ts index 26cef965e..2c1083ed2 100644 --- a/packages/headless/src/source/path.ts +++ b/packages/headless/src/source/path.ts @@ -1,4 +1,4 @@ -import { basename, dirname, parse } from 'node:path'; +import { parse } from 'node:path'; import type { FileInfo } from './types'; export function parseFilePath(path: string, root = ''): FileInfo { @@ -6,11 +6,11 @@ export function parseFilePath(path: string, root = ''): FileInfo { const parsed = parse(relativePath); const dir = parsed.dir.replace('\\', '/'); const flattenedPath = joinPaths([dir, parsed.name]); - const locale = parsed.name.split('.')[1]; + const [name, locale] = parsed.name.split('.'); return { dirname: dir, - name: parsed.name, + name, flattenedPath, locale, path: relativePath, @@ -26,16 +26,17 @@ function getRelativePath(path: string, root: string): string { return splitPath(path.substring(root.length)).join('/'); } -export function parseFolderPath(p: string): FileInfo { - const name = basename(p); - const dir = dirname(p).replace('\\', '/'); +export function parseFolderPath(path: string): FileInfo { + const parsed = parse(path); + const dir = parsed.dir.replace('\\', '/'); + const [name, locale] = parsed.base.split('.'); return { dirname: dir, name, - flattenedPath: p, - locale: name.split('.')[1], - path: p, + flattenedPath: path, + locale, + path, }; } diff --git a/packages/headless/src/source/types.ts b/packages/headless/src/source/types.ts index 8d2a90a2b..d28e15882 100644 --- a/packages/headless/src/source/types.ts +++ b/packages/headless/src/source/types.ts @@ -15,7 +15,7 @@ export interface FileInfo { flattenedPath: string; /** - * File name without extension + * File name without locale and extension */ name: string; diff --git a/packages/headless/test/graph.test.ts b/packages/headless/test/graph.test.ts index 6b48ae8f6..5ce46b3ce 100644 --- a/packages/headless/test/graph.test.ts +++ b/packages/headless/test/graph.test.ts @@ -1,161 +1,155 @@ -import { load } from '@/source/load'; +import { loader } from '@/source'; import { parseFilePath, parseFolderPath } from '@/source/path'; import { describe, expect, test } from 'vitest'; describe('Building File Graph', () => { - test('Simple', async () => { - const result = load({ - files: [ - { - type: 'page', - path: 'test.mdx', - data: { - title: 'Hello', - url: '/hello', + test('Simple', () => { + loader({ + source: { + files: [ + { + type: 'page', + path: 'test.mdx', + data: { + title: 'Hello', + }, }, - }, - { - type: 'meta', - path: 'meta.json', - data: { - pages: ['test'], + { + type: 'meta', + path: 'meta.json', + data: { + pages: ['test'], + }, }, + ], + }, + transformers: [ + ({ storage }) => { + expect(storage.root().children).toEqual([ + expect.objectContaining({ + type: 'page', + file: parseFilePath('test.mdx'), + }), + expect.objectContaining({ + type: 'meta', + file: parseFilePath('meta.json'), + }), + ]); }, ], }); - - expect(result.storage).toEqual({ - type: 'root', - children: [ - expect.objectContaining({ - type: 'page', - file: parseFilePath('test.mdx'), - }), - expect.objectContaining({ - type: 'meta', - file: parseFilePath('meta.json'), - }), - ], - }); }); test('Nested Directories', async () => { - const result = await load({ - getUrl(slugs, locale) { - return ''; - }, - getSlugs(info) { - return []; - }, - files: [ - { - type: 'page', - path: 'test.mdx', - data: { - title: 'Hello', - url: '/hello', + loader({ + source: { + files: [ + { + type: 'page', + path: 'test.mdx', + data: { + title: 'Hello', + }, }, - }, - { - type: 'meta', - path: 'meta.json', - data: { - pages: ['test', 'nested'], + { + type: 'meta', + path: 'meta.json', + data: { + pages: ['test', 'nested'], + }, }, - }, - { - type: 'page', - path: '/nested/test.mdx', - data: { - title: 'Nested Page', - url: '/nested/test', + { + type: 'page', + path: '/nested/test.mdx', + data: { + title: 'Nested Page', + }, }, - }, - ], - }); - - expect(result.storage).toEqual({ - type: 'root', - children: [ - expect.objectContaining({ - type: 'page', - file: parseFilePath('test.mdx'), - }), - expect.objectContaining({ - type: 'meta', - file: parseFilePath('meta.json'), - }), - expect.objectContaining({ - type: 'folder', - file: parseFolderPath('nested'), - children: [ + ], + }, + transformers: [ + ({ storage }) => { + expect(storage.root().children).toEqual([ expect.objectContaining({ type: 'page', - file: parseFilePath('nested/test.mdx'), + file: parseFilePath('test.mdx'), + }), + expect.objectContaining({ + type: 'meta', + file: parseFilePath('meta.json'), + }), + expect.objectContaining({ + type: 'folder', + file: parseFolderPath('nested'), + children: [ + expect.objectContaining({ + type: 'page', + file: parseFilePath('nested/test.mdx'), + }), + ], }), - ], - }), + ]); + }, ], }); }); test('Complicated Directories', async () => { - const result = load({ - files: [ - { - type: 'page', - path: 'test.mdx', - data: { - title: 'Hello', - url: '/hello', + loader({ + source: { + files: [ + { + type: 'page', + path: 'test.mdx', + data: { + title: 'Hello', + }, }, - }, - { - type: 'page', - path: '/nested/test.mdx', - data: { - title: 'Nested Page', - url: '/nested/test', + { + type: 'page', + path: '/nested/test.mdx', + data: { + title: 'Nested Page', + }, }, - }, - { - type: 'page', - path: '/nested/nested/test.mdx', - data: { - title: 'Nested Nested Page', - url: '/nested/nested/test', + { + type: 'page', + path: '/nested/nested/test.mdx', + data: { + title: 'Nested Nested Page', + }, }, - }, - ], - }); - - expect(result.storage).toEqual({ - type: 'root', - children: [ - expect.objectContaining({ - type: 'page', - file: parseFilePath('test.mdx'), - }), - expect.objectContaining({ - type: 'folder', - file: parseFolderPath('nested'), - children: [ + ], + }, + transformers: [ + ({ storage }) => { + expect(storage.root().children).toEqual([ expect.objectContaining({ type: 'page', - file: parseFilePath('nested/test.mdx'), + file: parseFilePath('test.mdx'), }), expect.objectContaining({ type: 'folder', - file: parseFolderPath('nested/nested'), + file: parseFolderPath('nested'), children: [ expect.objectContaining({ type: 'page', - file: parseFilePath('nested/nested/test.mdx'), + file: parseFilePath('nested/test.mdx'), + }), + expect.objectContaining({ + type: 'folder', + file: parseFolderPath('nested/nested'), + children: [ + expect.objectContaining({ + type: 'page', + file: parseFilePath('nested/nested/test.mdx'), + }), + ], }), ], }), - ], - }), + ]); + }, ], }); }); diff --git a/packages/headless/test/page-builder.test.ts b/packages/headless/test/page-builder.test.ts index 5a199a4cf..3d24b4c8d 100644 --- a/packages/headless/test/page-builder.test.ts +++ b/packages/headless/test/page-builder.test.ts @@ -1,77 +1,69 @@ import { describe, expect, test } from 'vitest'; -import { type Transformer, createPageTreeBuilder } from '@/source'; -import type { PageTree } from '@/server/types'; -import { load } from '@/source/load'; - -const pageTreeTransformer: Transformer = (ctx) => { - ctx.data.pageTree = createPageTreeBuilder({ - storage: ctx.storage, - }).build(); -}; +import { loader } from '@/source'; +import type * as PageTree from '@/server/page-tree'; describe('Page Tree Builder', () => { test('Simple', () => { - const result = load({ - files: [ - { - type: 'page', - path: 'test.mdx', - data: { - title: 'Hello', - url: '/hello', + const result = loader({ + source: { + files: [ + { + type: 'page', + path: 'test.mdx', + data: { + title: 'Hello', + }, }, - }, - { - type: 'meta', - path: 'meta.json', - data: { - pages: ['test'], + { + type: 'meta', + path: 'meta.json', + data: { + pages: ['test'], + }, }, - }, - ], - transformers: [pageTreeTransformer], + ], + }, }); - expect(result.data.pageTree).toEqual({ - name: 'Docs', - children: [{ type: 'page', name: 'Hello', url: '/hello' }], - } satisfies PageTree); + expect(result.pageTree).toEqual({ + name: '', + children: [{ type: 'page', name: 'Hello', url: '/test' }], + } satisfies PageTree.Root); }); test('Nested Directories', async () => { - const result = load({ - files: [ - { - type: 'page', - path: 'test.mdx', - data: { - title: 'Hello', - url: '/hello', + const result = loader({ + source: { + files: [ + { + type: 'page', + path: 'test.mdx', + data: { + title: 'Hello', + }, }, - }, - { - type: 'meta', - path: 'meta.json', - data: { - pages: ['test', 'nested'], + { + type: 'meta', + path: 'meta.json', + data: { + pages: ['test', 'nested'], + }, }, - }, - { - type: 'page', - path: '/nested/test.mdx', - data: { - title: 'Nested Page', - url: '/nested/test', + { + type: 'page', + path: '/nested/test.mdx', + data: { + title: 'Nested Page', + }, }, - }, - ], - transformers: [pageTreeTransformer], + ], + }, }); - expect(result.data.pageTree).toEqual({ - name: 'Docs', + expect(result.pageTree).toEqual({ + name: '', children: [ - { type: 'page', name: 'Hello', url: '/hello' }, + { type: 'page', name: 'Hello', url: '/test' }, { type: 'folder', name: 'Nested', @@ -84,81 +76,72 @@ describe('Page Tree Builder', () => { ], }, ], - } satisfies PageTree); + } satisfies PageTree.Root); }); test('Internationalized Routing', () => { - const result = load({ - files: [ - { - type: 'page', - path: 'test.mdx', - data: { - title: 'Hello', - url: '/hello', + const result = loader({ + languages: ['cn'], + source: { + files: [ + { + type: 'page', + path: 'test.mdx', + data: { + title: 'Hello', + }, }, - }, - { - type: 'page', - path: 'test.cn.mdx', - data: { - title: 'Hello Chinese', - url: '/hello', + { + type: 'page', + path: 'test.cn.mdx', + data: { + title: 'Hello Chinese', + }, }, - }, - { - type: 'meta', - path: 'meta.json', - data: { - pages: ['test', 'nested'], + { + type: 'meta', + path: 'meta.json', + data: { + pages: ['test', 'nested'], + }, }, - }, - { - type: 'meta', - path: 'meta.cn.json', - data: { - title: 'Docs Chinese', - pages: ['test', 'nested'], + { + type: 'meta', + path: 'meta.cn.json', + data: { + title: 'Docs Chinese', + pages: ['test', 'nested'], + }, }, - }, - { - type: 'page', - path: '/nested/test.mdx', - data: { - title: 'Nested Page', - url: '/nested/test', + { + type: 'page', + path: '/nested/test.mdx', + data: { + title: 'Nested Page', + }, }, - }, - { - type: 'page', - path: '/nested/test.cn.mdx', - data: { - title: 'Nested Page Chinese', - url: '/nested/test', + { + type: 'page', + path: '/nested/test.cn.mdx', + data: { + title: 'Nested Page Chinese', + }, }, - }, - { - type: 'meta', - path: '/nested/meta.cn.json', - data: { - title: 'Nested Chinese', - pages: ['...'], + { + type: 'meta', + path: '/nested/meta.cn.json', + data: { + title: 'Nested Chinese', + }, }, - }, - ], - transformers: [ - (ctx) => { - ctx.data.pageTree = createPageTreeBuilder({ - storage: ctx.storage, - }).buildI18n({ languages: ['cn'] })['cn']; - }, - ], + ], + }, }); - expect(result.data.pageTree).toEqual({ + expect(result.pageTree['cn']).toEqual({ name: 'Docs Chinese', children: [ - { type: 'page', name: 'Hello Chinese', url: '/hello' }, + { type: 'page', name: 'Hello Chinese', url: '/cn/test' }, { type: 'folder', name: 'Nested Chinese', @@ -166,11 +149,11 @@ describe('Page Tree Builder', () => { { type: 'page', name: 'Nested Page Chinese', - url: '/nested/test', + url: '/cn/nested/test', }, ], }, ], - } satisfies PageTree); + } satisfies PageTree.Root); }); }); diff --git a/packages/headless/test/path.test.ts b/packages/headless/test/path.test.ts new file mode 100644 index 000000000..92827a798 --- /dev/null +++ b/packages/headless/test/path.test.ts @@ -0,0 +1,75 @@ +import { + isRelative, + joinPaths, + parseFilePath, + parseFolderPath, + splitPath, +} from '@/source/path'; +import { describe, expect, test } from 'vitest'; + +describe('Path utilities', () => { + test('parse file path', () => { + expect(parseFilePath('test.mdx')).toEqual({ + dirname: '', + name: 'test', + flattenedPath: 'test', + locale: undefined, + path: 'test.mdx', + }); + + expect(parseFilePath('nested/test.mdx')).toEqual({ + dirname: 'nested', + name: 'test', + flattenedPath: 'nested/test', + locale: undefined, + path: 'nested/test.mdx', + }); + + expect(parseFilePath('nested/test.cn.mdx')).toEqual({ + dirname: 'nested', + name: 'test', + flattenedPath: 'nested/test.cn', + locale: 'cn', + path: 'nested/test.cn.mdx', + }); + }); + + test('parse folder path', () => { + expect(parseFolderPath('nested')).toEqual({ + dirname: '', + name: 'nested', + flattenedPath: 'nested', + locale: undefined, + path: 'nested', + }); + + expect(parseFolderPath('nested/nested')).toEqual({ + dirname: 'nested', + name: 'nested', + flattenedPath: 'nested/nested', + locale: undefined, + path: 'nested/nested', + }); + }); + + test('join paths', () => { + expect(joinPaths(['a', 'b', 'c'])).toBe('a/b/c'); + expect(joinPaths(['/a'])).toBe('a'); + expect(joinPaths(['a/', '/b'])).toBe('a/b'); + expect(joinPaths(['a/', 'b/c'])).toBe('a/b/c'); + + expect(joinPaths(['a', 'b'], 'leading')).toBe('/a/b'); + expect(joinPaths(['a', 'b'], 'trailing')).toBe('a/b/'); + }); + + test('split paths', () => { + expect(splitPath('a/b/c')).toEqual(['a', 'b', 'c']); + expect(splitPath('a//c')).toEqual(['a', 'c']); + expect(splitPath('/a/c')).toEqual(['a', 'c']); + }); + + test('is relative', () => { + expect(isRelative('nested/file.mdx', 'nested')).toBe(true); + expect(isRelative('file.mdx', 'nested')).toBe(false); + }); +});