feat: render the menu
This commit is contained in:
@@ -4,6 +4,7 @@ import path from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import type { PostData } from '../../src/main/engine/PostEngine';
|
||||
import { resolveUiLanguageFromSystemLocale } from '../../src/main/shared/i18n';
|
||||
import type { MenuDocument } from '../../src/main/engine/MenuEngine';
|
||||
|
||||
const generatedFileHashes = new Map<string, string>();
|
||||
const getGeneratedFileHashMock = vi.fn(async (projectId: string, relativePath: string) => {
|
||||
@@ -183,6 +184,7 @@ describe('BlogGenerationEngine', () => {
|
||||
language: string;
|
||||
pageTitle: string;
|
||||
categorySettings: Record<string, { renderInLists: boolean; showTitle: boolean }>;
|
||||
menu: MenuDocument;
|
||||
}>,
|
||||
) {
|
||||
setupPosts(posts);
|
||||
@@ -198,9 +200,70 @@ describe('BlogGenerationEngine', () => {
|
||||
language: options?.language,
|
||||
pageTitle: options?.pageTitle,
|
||||
categorySettings: options?.categorySettings,
|
||||
menu: options?.menu,
|
||||
}, onProgress);
|
||||
}
|
||||
|
||||
it('renders configured menu below h1 with nested submenu links on list and single pages', async () => {
|
||||
const posts = [
|
||||
makePost({
|
||||
id: '1',
|
||||
slug: 'hello-world',
|
||||
title: 'Hello World',
|
||||
categories: ['news'],
|
||||
createdAt: new Date('2025-03-15T10:00:00Z'),
|
||||
}),
|
||||
makePost({
|
||||
id: '2',
|
||||
slug: 'about',
|
||||
title: 'About',
|
||||
categories: ['page'],
|
||||
createdAt: new Date('2025-03-14T10:00:00Z'),
|
||||
}),
|
||||
];
|
||||
|
||||
await generate(posts, {
|
||||
menu: {
|
||||
items: [
|
||||
{ id: 'home', title: 'Home', kind: 'home', pageSlug: 'home', children: [] },
|
||||
{ id: 'about', title: 'About', kind: 'page', pageSlug: 'about', children: [] },
|
||||
{
|
||||
id: 'sections',
|
||||
title: 'Sections',
|
||||
kind: 'submenu',
|
||||
children: [
|
||||
{ id: 'news', title: 'News', kind: 'category-archive', categoryName: 'news', children: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const indexHtml = await readFile(path.join(tempDir, 'html', 'index.html'), 'utf-8');
|
||||
const singleHtml = await readFile(path.join(tempDir, 'html', '2025', '03', '15', 'hello-world', 'index.html'), 'utf-8');
|
||||
|
||||
expect(indexHtml).toContain('class="blog-menu"');
|
||||
expect(indexHtml).toContain('href="/"');
|
||||
expect(indexHtml).toContain('href="/about/"');
|
||||
expect(indexHtml).toContain('href="/category/news/"');
|
||||
expect(indexHtml).toContain('class="blog-menu-submenu"');
|
||||
|
||||
const listH1Index = indexHtml.indexOf('<h1 class="archive-heading"');
|
||||
const listMenuIndex = indexHtml.indexOf('class="blog-menu"');
|
||||
const listContentIndex = indexHtml.indexOf('<section class="post-list"');
|
||||
expect(listH1Index).toBeGreaterThan(-1);
|
||||
expect(listMenuIndex).toBeGreaterThan(listH1Index);
|
||||
expect(listContentIndex).toBeGreaterThan(listMenuIndex);
|
||||
|
||||
expect(singleHtml).toContain('class="blog-menu"');
|
||||
const singleH1Index = singleHtml.indexOf('<h1>Hello World</h1>');
|
||||
const singleMenuIndex = singleHtml.indexOf('class="blog-menu"');
|
||||
const singleContentIndex = singleHtml.indexOf('<div class="post">');
|
||||
expect(singleH1Index).toBeGreaterThan(-1);
|
||||
expect(singleMenuIndex).toBeGreaterThan(singleH1Index);
|
||||
expect(singleContentIndex).toBeGreaterThan(singleMenuIndex);
|
||||
});
|
||||
|
||||
it('copies all required asset files to html/assets/ and html/images/', async () => {
|
||||
const result = await generate([]);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { tmpdir } from 'node:os';
|
||||
import type { PostData, PostFilter } from '../../src/main/engine/PostEngine';
|
||||
import { PreviewServer } from '../../src/main/engine/PreviewServer';
|
||||
import { resolveUiLanguageFromSystemLocale } from '../../src/main/shared/i18n';
|
||||
import type { MenuDocument } from '../../src/main/engine/MenuEngine';
|
||||
|
||||
type PostEngineLike = {
|
||||
getPostsFiltered: (filter: PostFilter) => Promise<PostData[]>;
|
||||
@@ -19,6 +20,11 @@ type SettingsEngineLike = {
|
||||
setProjectContext: (projectId: string, dataDir?: string) => void;
|
||||
};
|
||||
|
||||
type MenuEngineLike = {
|
||||
getMenu: () => Promise<MenuDocument>;
|
||||
setProjectContext: (projectId: string, dataDir?: string) => void;
|
||||
};
|
||||
|
||||
function makePost(overrides: Partial<PostData> = {}): PostData {
|
||||
const createdAt = overrides.createdAt ?? new Date('2025-01-02T10:00:00.000Z');
|
||||
const updatedAt = overrides.updatedAt ?? createdAt;
|
||||
@@ -117,6 +123,15 @@ function makePostMediaEngine(linksByPostId: Record<string, Array<{ media: { id:
|
||||
};
|
||||
}
|
||||
|
||||
function makeMenuEngine(menu: MenuDocument): MenuEngineLike {
|
||||
return {
|
||||
setProjectContext: vi.fn(),
|
||||
async getMenu(): Promise<MenuDocument> {
|
||||
return menu;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('PreviewServer', () => {
|
||||
let server: PreviewServer;
|
||||
let tempDir: string | null = null;
|
||||
@@ -162,6 +177,57 @@ describe('PreviewServer', () => {
|
||||
expect(html).toContain('02.01.2025');
|
||||
});
|
||||
|
||||
it('renders menu below h1 with nested submenu links', async () => {
|
||||
const posts = [
|
||||
makePost({ id: '1', slug: 'hello', title: 'Hello', categories: ['news'], createdAt: new Date('2025-01-03T10:00:00.000Z') }),
|
||||
makePost({ id: '2', slug: 'about', title: 'About', categories: ['page'], createdAt: new Date('2025-01-02T10:00:00.000Z') }),
|
||||
];
|
||||
|
||||
server = new PreviewServer({
|
||||
postEngine: makeEngine(posts),
|
||||
settingsEngine: makeSettings(50),
|
||||
menuEngine: makeMenuEngine({
|
||||
items: [
|
||||
{ id: 'home', title: 'Home', kind: 'home', pageSlug: 'home', children: [] },
|
||||
{ id: 'about', title: 'About', kind: 'page', pageSlug: 'about', children: [] },
|
||||
{
|
||||
id: 'sections',
|
||||
title: 'Sections',
|
||||
kind: 'submenu',
|
||||
children: [
|
||||
{ id: 'news', title: 'News', kind: 'category-archive', categoryName: 'news', children: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
getActiveProjectContext: async () => ({ projectId: 'default' }),
|
||||
});
|
||||
|
||||
await server.start(0);
|
||||
|
||||
const rootHtml = await (await fetch(`${server.getBaseUrl()}/`)).text();
|
||||
expect(rootHtml).toContain('class="blog-menu"');
|
||||
expect(rootHtml).toContain('href="/"');
|
||||
expect(rootHtml).toContain('href="/about/"');
|
||||
expect(rootHtml).toContain('href="/category/news/"');
|
||||
expect(rootHtml).toContain('class="blog-menu-submenu"');
|
||||
|
||||
const rootH1Index = rootHtml.indexOf('<h1 class="archive-heading"');
|
||||
const rootMenuIndex = rootHtml.indexOf('class="blog-menu"');
|
||||
const rootPostListIndex = rootHtml.indexOf('<section class="post-list"');
|
||||
expect(rootH1Index).toBeGreaterThan(-1);
|
||||
expect(rootMenuIndex).toBeGreaterThan(rootH1Index);
|
||||
expect(rootPostListIndex).toBeGreaterThan(rootMenuIndex);
|
||||
|
||||
const singleHtml = await (await fetch(`${server.getBaseUrl()}/posts/hello`)).text();
|
||||
const singleH1Index = singleHtml.indexOf('<h1>Hello</h1>');
|
||||
const singleMenuIndex = singleHtml.indexOf('class="blog-menu"');
|
||||
const singleTextIndex = singleHtml.indexOf('<div class="post">');
|
||||
expect(singleH1Index).toBeGreaterThan(-1);
|
||||
expect(singleMenuIndex).toBeGreaterThan(singleH1Index);
|
||||
expect(singleTextIndex).toBeGreaterThan(singleMenuIndex);
|
||||
});
|
||||
|
||||
it('uses local CSS/JS assets and serves them from the preview server', async () => {
|
||||
server = new PreviewServer({
|
||||
postEngine: makeEngine([makePost()]),
|
||||
|
||||
Reference in New Issue
Block a user