feat: first take at calendar

This commit is contained in:
2026-02-22 14:15:57 +01:00
parent a29143d6dd
commit 64e1fb3d90
18 changed files with 438 additions and 7 deletions

View File

@@ -336,6 +336,9 @@ describe('BlogGenerationEngine', () => {
expect(await fileExists(path.join(tempDir, 'html', 'assets', 'lightbox.min.css'))).toBe(true);
expect(await fileExists(path.join(tempDir, 'html', 'assets', 'lightbox.min.js'))).toBe(true);
expect(await fileExists(path.join(tempDir, 'html', 'assets', 'tag-cloud.js'))).toBe(true);
expect(await fileExists(path.join(tempDir, 'html', 'assets', 'vanilla-calendar.min.css'))).toBe(true);
expect(await fileExists(path.join(tempDir, 'html', 'assets', 'vanilla-calendar.min.js'))).toBe(true);
expect(await fileExists(path.join(tempDir, 'html', 'assets', 'calendar-runtime.js'))).toBe(true);
expect(await fileExists(path.join(tempDir, 'html', 'images', 'prev.png'))).toBe(true);
expect(await fileExists(path.join(tempDir, 'html', 'images', 'next.png'))).toBe(true);
expect(await fileExists(path.join(tempDir, 'html', 'images', 'close.png'))).toBe(true);
@@ -345,6 +348,60 @@ describe('BlogGenerationEngine', () => {
expect(picoContent.length).toBeGreaterThan(0);
});
it('writes calendar.json and wires calendar UI in generated html', async () => {
const posts = [
makePost({
id: '1',
slug: 'one',
title: 'One',
categories: ['news'],
createdAt: new Date('2025-03-15T10:00:00Z'),
}),
makePost({
id: '2',
slug: 'two',
title: 'Two',
categories: ['news'],
createdAt: new Date('2025-03-15T12:00:00Z'),
}),
makePost({
id: '3',
slug: 'three',
title: 'Three',
categories: ['news'],
createdAt: new Date('2025-04-01T10:00:00Z'),
}),
];
await generate(posts, {
menu: {
items: [
{ id: 'home', title: 'Home', kind: 'home', pageSlug: 'home', children: [] },
],
},
});
const calendarJsonRaw = await readFile(path.join(tempDir, 'html', 'calendar.json'), 'utf-8');
const calendarJson = JSON.parse(calendarJsonRaw) as {
years: Record<string, number>;
months: Record<string, number>;
days: Record<string, number>;
};
expect(calendarJson.years['2025']).toBe(3);
expect(calendarJson.months['2025-03']).toBe(2);
expect(calendarJson.months['2025-04']).toBe(1);
expect(calendarJson.days['2025-03-15']).toBe(2);
expect(calendarJson.days['2025-04-01']).toBe(1);
const indexHtml = await readFile(path.join(tempDir, 'html', 'index.html'), 'utf-8');
expect(indexHtml).toContain('class="blog-menu-calendar-button"');
expect(indexHtml).toContain('id="blog-calendar"');
expect(indexHtml).toContain('href="/assets/vanilla-calendar.min.css"');
expect(indexHtml).toContain('src="/assets/vanilla-calendar.min.js"');
expect(indexHtml).toContain('src="/assets/calendar-runtime.js"');
});
it('generates root index.html for published posts', async () => {
const posts = [
makePost({ id: '1', slug: 'first', title: 'First Post', createdAt: new Date('2025-01-15T10:00:00Z') }),

View File

@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest';
import type { PostData } from '../../src/main/engine/PostEngine';
import {
buildCalendarArchiveData,
buildSitemapAndFeeds,
type GenerationPostIndexLike,
} from '../../src/main/engine/GenerationSitemapFeedService';
@@ -71,6 +72,25 @@ function buildIndex(posts: PostData[]): GenerationPostIndexLike {
}
describe('GenerationSitemapFeedService', () => {
it('builds calendar archive data with year/month/day post counts', () => {
const publishedPosts = [
makePost({ id: '1', slug: 'a', createdAt: new Date('2025-01-15T10:00:00.000Z') }),
makePost({ id: '2', slug: 'b', createdAt: new Date('2025-01-15T12:00:00.000Z') }),
makePost({ id: '3', slug: 'c', createdAt: new Date('2025-02-01T08:00:00.000Z') }),
makePost({ id: '4', slug: 'd', createdAt: new Date('2026-01-01T08:00:00.000Z') }),
];
const result = buildCalendarArchiveData(publishedPosts);
expect(result.years['2025']).toBe(3);
expect(result.years['2026']).toBe(1);
expect(result.months['2025-01']).toBe(2);
expect(result.months['2025-02']).toBe(1);
expect(result.days['2025-01-15']).toBe(2);
expect(result.days['2025-02-01']).toBe(1);
expect(result.days['2026-01-01']).toBe(1);
});
it('builds canonical sitemap urls and paginated archive routes', () => {
const publishedPosts = [
makePost({

View File

@@ -305,11 +305,14 @@ describe('PreviewServer', () => {
expect(rootHtml).toContain('href="/assets/pico.min.css"');
expect(rootHtml).toContain('href="/assets/lightbox.min.css"');
expect(rootHtml).toContain('href="/assets/highlight.min.css"');
expect(rootHtml).toContain('href="/assets/vanilla-calendar.min.css"');
expect(rootHtml).toContain('src="/assets/lightbox.min.js"');
expect(rootHtml).toContain('src="/assets/highlight.min.js"');
expect(rootHtml).toContain('src="/assets/code-enhancements.js"');
expect(rootHtml).toContain('src="/assets/d3.layout.cloud.js"');
expect(rootHtml).toContain('src="/assets/tag-cloud.js"');
expect(rootHtml).toContain('src="/assets/vanilla-calendar.min.js"');
expect(rootHtml).toContain('src="/assets/calendar-runtime.js"');
expect(rootHtml).not.toContain('function parseWords(');
expect(rootHtml).not.toContain('cdn.jsdelivr.net');