diff --git a/tests/engine/BlogGenerationEngine.test.ts b/tests/engine/BlogGenerationEngine.test.ts
index 4d72dfe..39d509a 100644
--- a/tests/engine/BlogGenerationEngine.test.ts
+++ b/tests/engine/BlogGenerationEngine.test.ts
@@ -264,6 +264,34 @@ describe('BlogGenerationEngine', () => {
expect(singleContentIndex).toBeGreaterThan(singleMenuIndex);
});
+ it('renders menu on generated category and tag archive pages', async () => {
+ const posts = [
+ makePost({
+ id: '1',
+ slug: 'news-post',
+ title: 'News Post',
+ categories: ['news'],
+ tags: ['dev'],
+ createdAt: new Date('2025-03-15T10:00:00Z'),
+ }),
+ ];
+
+ await generate(posts, {
+ menu: {
+ items: [
+ { id: 'home', title: 'Home', kind: 'home', pageSlug: 'home', children: [] },
+ { id: 'news', title: 'News', kind: 'category-archive', categoryName: 'news', children: [] },
+ ],
+ },
+ });
+
+ const categoryHtml = await readFile(path.join(tempDir, 'html', 'category', 'news', 'index.html'), 'utf-8');
+ const tagHtml = await readFile(path.join(tempDir, 'html', 'tag', 'dev', 'index.html'), 'utf-8');
+
+ expect(categoryHtml).toContain('class="blog-menu"');
+ expect(tagHtml).toContain('class="blog-menu"');
+ });
+
it('copies all required asset files to html/assets/ and html/images/', async () => {
const result = await generate([]);
diff --git a/tests/engine/PreviewServer.test.ts b/tests/engine/PreviewServer.test.ts
index d93a78e..d2b7b00 100644
--- a/tests/engine/PreviewServer.test.ts
+++ b/tests/engine/PreviewServer.test.ts
@@ -228,6 +228,32 @@ describe('PreviewServer', () => {
expect(singleTextIndex).toBeGreaterThan(singleMenuIndex);
});
+ it('renders menu on category and tag archive pages', async () => {
+ const posts = [
+ makePost({ id: '1', slug: 'news-post', title: 'News Post', categories: ['news'], tags: ['dev'], createdAt: new Date('2025-01-03T10: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: 'dev', title: 'Dev', kind: 'category-archive', categoryName: 'news', children: [] },
+ ],
+ }),
+ getActiveProjectContext: async () => ({ projectId: 'default' }),
+ });
+
+ await server.start(0);
+
+ const categoryHtml = await (await fetch(`${server.getBaseUrl()}/category/news`)).text();
+ expect(categoryHtml).toContain('class="blog-menu"');
+
+ const tagHtml = await (await fetch(`${server.getBaseUrl()}/tag/dev`)).text();
+ expect(tagHtml).toContain('class="blog-menu"');
+ });
+
it('uses local CSS/JS assets and serves them from the preview server', async () => {
server = new PreviewServer({
postEngine: makeEngine([makePost()]),
@@ -591,6 +617,14 @@ describe('PreviewServer', () => {
expect(response.status).toBe(200);
const html = await response.text();
expect(html).toContain('
Explicit Single Post Title
');
+
+ const mainIndex = html.indexOf('
');
+ const h1Index = html.indexOf('Explicit Single Post Title
');
+ const articleIndex = html.indexOf('');
+ expect(mainIndex).toBeGreaterThan(-1);
+ expect(h1Index).toBeGreaterThan(mainIndex);
+ expect(articleIndex).toBeGreaterThan(mainIndex);
+ expect(h1Index).toBeLessThan(articleIndex);
});
it('uses blog description as h1 on first date archive page and date range h1 on later pages', async () => {
diff --git a/tests/renderer/components/GitSidebar.styles.test.ts b/tests/renderer/components/GitSidebar.styles.test.ts
new file mode 100644
index 0000000..3cebdfa
--- /dev/null
+++ b/tests/renderer/components/GitSidebar.styles.test.ts
@@ -0,0 +1,22 @@
+import { describe, expect, it } from 'vitest';
+import * as fs from 'node:fs';
+import * as path from 'node:path';
+
+describe('GitSidebar styles', () => {
+ const cssPath = path.resolve(
+ __dirname,
+ '../../../src/renderer/components/GitSidebar/GitSidebar.css'
+ );
+
+ it('limits version history section to 50% of available sidebar room', () => {
+ const css = fs.readFileSync(cssPath, 'utf8');
+
+ expect(css).toMatch(/\.git-sidebar-history\s*\{[^}]*flex:\s*0\s+0\s+50%;[^}]*max-height:\s*50%;[^}]*\}/s);
+ });
+
+ it('keeps commit list scrollable within the bounded history section', () => {
+ const css = fs.readFileSync(cssPath, 'utf8');
+
+ expect(css).toMatch(/\.git-sidebar-history-list\s*\{[^}]*overflow:\s*auto;[^}]*\}/s);
+ });
+});
diff --git a/tests/renderer/components/GitSidebar.test.tsx b/tests/renderer/components/GitSidebar.test.tsx
index 7f1156a..dcabb6a 100644
--- a/tests/renderer/components/GitSidebar.test.tsx
+++ b/tests/renderer/components/GitSidebar.test.tsx
@@ -73,6 +73,49 @@ describe('GitSidebar', () => {
expect(screen.getByRole('button', { name: /initialize git/i })).toBeInTheDocument();
});
+ it('ignores stale load results when active project becomes available during async load', async () => {
+ useAppStore.setState({ activeProject: null });
+
+ (window as any).electronAPI.git.getRepoState = vi.fn().mockResolvedValue({
+ isRepo: true,
+ rootPath: '/repo/path',
+ currentBranch: 'main',
+ hasRemote: false,
+ });
+ (window as any).electronAPI.git.getStatus = vi.fn().mockResolvedValue({
+ files: [{ path: 'posts/first.md', status: 'modified' }],
+ counts: { untracked: 0, modified: 1, deleted: 0, renamed: 0, staged: 0, total: 1 },
+ });
+ (window as any).electronAPI.git.getHistory = vi.fn().mockResolvedValue([]);
+
+ let checkAvailabilityCall = 0;
+ (window as any).electronAPI.git.checkAvailability = vi.fn().mockImplementation(async () => {
+ checkAvailabilityCall += 1;
+ if (checkAvailabilityCall === 1) {
+ await new Promise((resolve) => setTimeout(resolve, 25));
+ }
+ return { gitFound: true, version: '2.49.0' };
+ });
+
+ render();
+
+ await act(async () => {
+ useAppStore.setState({
+ activeProject: {
+ id: 'project-1',
+ name: 'Test Project',
+ slug: 'test-project',
+ isActive: true,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ },
+ });
+ });
+
+ expect(await screen.findByText(/open changes/i)).toBeInTheDocument();
+ expect(screen.queryByText(/no active project selected/i)).not.toBeInTheDocument();
+ });
+
it('renders open changes list when repository exists', async () => {
(window as any).electronAPI.git.getRepoState = vi.fn().mockResolvedValue({
isRepo: true,