feat: categories have settings for filtering and titles
This commit is contained in:
@@ -423,10 +423,10 @@ describe('MetaEngine', () => {
|
||||
});
|
||||
|
||||
const metadata = await metaEngine.getProjectMetadata();
|
||||
expect(metadata).toEqual({
|
||||
expect(metadata).toEqual(expect.objectContaining({
|
||||
name: 'My Blog',
|
||||
description: 'A personal blog about technology',
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
it('should update project name only', async () => {
|
||||
@@ -593,6 +593,70 @@ describe('MetaEngine', () => {
|
||||
expect(parsed.picoTheme).toBe('slate');
|
||||
});
|
||||
|
||||
it('should apply default category settings for standard categories', async () => {
|
||||
await metaEngine.setProjectMetadata({
|
||||
name: 'Category Defaults Project',
|
||||
} as any);
|
||||
|
||||
const metadata = await metaEngine.getProjectMetadata() as any;
|
||||
expect(metadata.categorySettings).toEqual(
|
||||
expect.objectContaining({
|
||||
article: { renderInLists: true, showTitle: true },
|
||||
picture: { renderInLists: true, showTitle: true },
|
||||
aside: { renderInLists: true, showTitle: false },
|
||||
page: { renderInLists: false, showTitle: true },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should persist category settings to project.json', async () => {
|
||||
await metaEngine.setProjectMetadata({
|
||||
name: 'Persisted Category Settings',
|
||||
categorySettings: {
|
||||
article: { renderInLists: true, showTitle: true },
|
||||
aside: { renderInLists: true, showTitle: false },
|
||||
page: { renderInLists: false, showTitle: true },
|
||||
custom: { renderInLists: false, showTitle: true },
|
||||
},
|
||||
} as any);
|
||||
|
||||
const metaDir = metaEngine.getMetaDir();
|
||||
const projectPath = normalizePath(`${metaDir}/project.json`);
|
||||
const content = mockFiles.get(projectPath);
|
||||
const parsed = JSON.parse(content!);
|
||||
|
||||
expect(parsed.categorySettings).toEqual(
|
||||
expect.objectContaining({
|
||||
custom: { renderInLists: false, showTitle: true },
|
||||
aside: { renderInLists: true, showTitle: false },
|
||||
page: { renderInLists: false, showTitle: true },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should merge missing category settings with defaults when loading from filesystem', async () => {
|
||||
const metaDir = metaEngine.getMetaDir();
|
||||
const projectPath = normalizePath(`${metaDir}/project.json`);
|
||||
mockFiles.set(projectPath, JSON.stringify({
|
||||
name: 'Loaded Project',
|
||||
categorySettings: {
|
||||
custom: { renderInLists: false, showTitle: false },
|
||||
},
|
||||
}));
|
||||
|
||||
await metaEngine.loadProjectMetadata();
|
||||
|
||||
const metadata = await metaEngine.getProjectMetadata() as any;
|
||||
expect(metadata.categorySettings).toEqual(
|
||||
expect.objectContaining({
|
||||
custom: { renderInLists: false, showTitle: false },
|
||||
article: { renderInLists: true, showTitle: true },
|
||||
aside: { renderInLists: true, showTitle: false },
|
||||
page: { renderInLists: false, showTitle: true },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should load picoTheme from filesystem', async () => {
|
||||
const metaDir = metaEngine.getMetaDir();
|
||||
const projectPath = normalizePath(`${metaDir}/project.json`);
|
||||
@@ -691,10 +755,10 @@ describe('MetaEngine', () => {
|
||||
description: 'Testing events',
|
||||
});
|
||||
|
||||
expect(handler).toHaveBeenCalledWith({
|
||||
expect(handler).toHaveBeenCalledWith(expect.objectContaining({
|
||||
name: 'Event Test',
|
||||
description: 'Testing events',
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
it('should clear project metadata when project context changes', () => {
|
||||
|
||||
@@ -65,6 +65,10 @@ function makeEngine(posts: PostData[]): PostEngineLike {
|
||||
result = result.filter((post) => filter.categories!.some((category) => post.categories.includes(category)));
|
||||
}
|
||||
|
||||
if ((filter as any).excludeCategories && (filter as any).excludeCategories.length > 0) {
|
||||
result = result.filter((post) => !(filter as any).excludeCategories.some((category: string) => post.categories.includes(category)));
|
||||
}
|
||||
|
||||
if (filter.year !== undefined) {
|
||||
result = result.filter((post) => post.createdAt.getUTCFullYear() === filter.year);
|
||||
}
|
||||
@@ -519,6 +523,108 @@ describe('PreviewServer', () => {
|
||||
expect(secondPageHtml).toContain('<h1 class="archive-heading">news - 1.1.2020 - 2.1.2020</h1>');
|
||||
});
|
||||
|
||||
it('filters out categories disabled for list rendering on list routes', async () => {
|
||||
const posts = [
|
||||
makePost({ id: 'list-1', slug: 'list-1', title: 'List Included', categories: ['article'], createdAt: new Date('2025-02-05T10:00:00.000Z') }),
|
||||
makePost({ id: 'list-2', slug: 'list-2', title: 'List Excluded', categories: ['page'], createdAt: new Date('2025-02-04T10:00:00.000Z') }),
|
||||
];
|
||||
|
||||
server = new PreviewServer({
|
||||
postEngine: makeEngine(posts),
|
||||
settingsEngine: {
|
||||
setProjectContext: vi.fn(),
|
||||
async getProjectMetadata() {
|
||||
return {
|
||||
maxPostsPerPage: 50,
|
||||
categorySettings: {
|
||||
article: { renderInLists: true, showTitle: true },
|
||||
page: { renderInLists: false, showTitle: true },
|
||||
},
|
||||
};
|
||||
},
|
||||
} as any,
|
||||
getActiveProjectContext: async () => ({ projectId: 'default' }),
|
||||
});
|
||||
|
||||
await server.start(0);
|
||||
|
||||
const html = await (await fetch(`${server.getBaseUrl()}/`)).text();
|
||||
expect(html).toContain('List Included');
|
||||
expect(html).not.toContain('List Excluded');
|
||||
});
|
||||
|
||||
it('suppresses all list category titles when any assigned category has showTitle disabled', async () => {
|
||||
const posts = [
|
||||
makePost({
|
||||
id: 'ct-1',
|
||||
slug: 'ct-1',
|
||||
title: 'Category Title Test',
|
||||
categories: ['aside', 'article'],
|
||||
content: 'Body without markdown headings',
|
||||
createdAt: new Date('2025-02-05T10:00:00.000Z'),
|
||||
}),
|
||||
];
|
||||
|
||||
server = new PreviewServer({
|
||||
postEngine: makeEngine(posts),
|
||||
settingsEngine: {
|
||||
setProjectContext: vi.fn(),
|
||||
async getProjectMetadata() {
|
||||
return {
|
||||
maxPostsPerPage: 50,
|
||||
categorySettings: {
|
||||
article: { renderInLists: true, showTitle: true },
|
||||
aside: { renderInLists: true, showTitle: false },
|
||||
},
|
||||
};
|
||||
},
|
||||
} as any,
|
||||
getActiveProjectContext: async () => ({ projectId: 'default' }),
|
||||
});
|
||||
|
||||
await server.start(0);
|
||||
|
||||
const html = await (await fetch(`${server.getBaseUrl()}/`)).text();
|
||||
expect(html).not.toContain('<h2 class="post-title">Category Title Test</h2>');
|
||||
});
|
||||
|
||||
it('renders post title in list when category titles are enabled', async () => {
|
||||
const posts = [
|
||||
makePost({
|
||||
id: 'pt-1',
|
||||
slug: 'pt-1',
|
||||
title: 'Article Title',
|
||||
categories: ['article'],
|
||||
content: 'Body without markdown headings',
|
||||
createdAt: new Date('2025-02-06T10:00:00.000Z'),
|
||||
}),
|
||||
];
|
||||
|
||||
server = new PreviewServer({
|
||||
postEngine: makeEngine(posts),
|
||||
settingsEngine: {
|
||||
setProjectContext: vi.fn(),
|
||||
async getProjectMetadata() {
|
||||
return {
|
||||
maxPostsPerPage: 50,
|
||||
categorySettings: {
|
||||
article: { renderInLists: true, showTitle: true },
|
||||
aside: { renderInLists: true, showTitle: false },
|
||||
page: { renderInLists: false, showTitle: true },
|
||||
},
|
||||
};
|
||||
},
|
||||
} as any,
|
||||
getActiveProjectContext: async () => ({ projectId: 'default' }),
|
||||
});
|
||||
|
||||
await server.start(0);
|
||||
|
||||
const html = await (await fetch(`${server.getBaseUrl()}/`)).text();
|
||||
expect(html).toContain('<h2 class="post-title">Article Title</h2>');
|
||||
expect(html).not.toContain('<h2 class="post-category-title">article</h2>');
|
||||
});
|
||||
|
||||
it('supports tag, category, and page-slug routes', async () => {
|
||||
const tagged = makePost({ id: 'tag1', title: 'Tagged', slug: 'tagged', tags: ['dev'] });
|
||||
const categorized = makePost({ id: 'cat1', title: 'Categorized', slug: 'categorized', categories: ['news'] });
|
||||
@@ -982,7 +1088,7 @@ describe('PreviewServer', () => {
|
||||
slug: 'published-slug',
|
||||
content: '# Published content only',
|
||||
tags: ['published-tag'],
|
||||
categories: ['page'],
|
||||
categories: ['article'],
|
||||
createdAt: new Date('2025-02-14T10:00:00.000Z'),
|
||||
});
|
||||
|
||||
|
||||
@@ -43,7 +43,16 @@ describe('SettingsView Diff Preferences', () => {
|
||||
meta: {
|
||||
...(window as any).electronAPI?.meta,
|
||||
getCategories: vi.fn().mockResolvedValue(['article', 'picture', 'aside', 'page']),
|
||||
getProjectMetadata: vi.fn().mockResolvedValue({ maxPostsPerPage: 75, publicUrl: 'https://example.com' }),
|
||||
getProjectMetadata: vi.fn().mockResolvedValue({
|
||||
maxPostsPerPage: 75,
|
||||
publicUrl: 'https://example.com',
|
||||
categorySettings: {
|
||||
article: { renderInLists: true, showTitle: true },
|
||||
picture: { renderInLists: true, showTitle: true },
|
||||
aside: { renderInLists: true, showTitle: false },
|
||||
page: { renderInLists: false, showTitle: true },
|
||||
},
|
||||
}),
|
||||
updateProjectMetadata: vi.fn().mockResolvedValue({ maxPostsPerPage: 12, publicUrl: 'https://example.com' }),
|
||||
},
|
||||
chat: {
|
||||
@@ -107,4 +116,35 @@ describe('SettingsView Diff Preferences', () => {
|
||||
expect.objectContaining({ publicUrl: 'https://example.com' })
|
||||
);
|
||||
});
|
||||
|
||||
it('renders category settings checkboxes with required defaults', async () => {
|
||||
render(<SettingsView />);
|
||||
|
||||
const asideShowTitle = await screen.findByLabelText(/aside show titles/i);
|
||||
const asideRenderInLists = screen.getByLabelText(/aside render in lists/i);
|
||||
const pageRenderInLists = screen.getByLabelText(/page render in lists/i);
|
||||
const articleShowTitle = screen.getByLabelText(/article show titles/i);
|
||||
|
||||
expect((asideShowTitle as HTMLInputElement).checked).toBe(false);
|
||||
expect((asideRenderInLists as HTMLInputElement).checked).toBe(true);
|
||||
expect((pageRenderInLists as HTMLInputElement).checked).toBe(false);
|
||||
expect((articleShowTitle as HTMLInputElement).checked).toBe(true);
|
||||
});
|
||||
|
||||
it('persists category settings changes via project metadata update', async () => {
|
||||
render(<SettingsView />);
|
||||
|
||||
const pageRenderInLists = await screen.findByLabelText(/page render in lists/i);
|
||||
fireEvent.click(pageRenderInLists);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
expect((window as any).electronAPI.meta.updateProjectMetadata).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
categorySettings: expect.objectContaining({
|
||||
page: expect.objectContaining({ renderInLists: true, showTitle: true }),
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user