fix: importer bugs and editor bugs
This commit is contained in:
@@ -823,6 +823,73 @@ describe('ImportExecutionEngine', () => {
|
||||
expect(insertedPosts[0].content).toContain('[[video src="test.mp4"]]');
|
||||
});
|
||||
|
||||
it('should not escape underscores inside macro names during markdown conversion', async () => {
|
||||
const wxrPost = createMockWxrPost({
|
||||
content: '<p>Here is a photo archive: [photo_archive]</p>',
|
||||
});
|
||||
const analyzed = createMockAnalyzedPost(wxrPost, 'conflict', 'overwrite');
|
||||
analyzed.existingPost = {
|
||||
id: 'existing-post-id',
|
||||
title: 'Existing Post',
|
||||
slug: 'test-post',
|
||||
checksum: 'old-checksum',
|
||||
pubDate: null,
|
||||
excerpt: null,
|
||||
author: null,
|
||||
tags: [],
|
||||
categories: [],
|
||||
};
|
||||
const report = createMockAnalysisReport({
|
||||
posts: {
|
||||
total: 1,
|
||||
new: 0,
|
||||
updates: 0,
|
||||
conflicts: 1,
|
||||
contentDuplicates: 0,
|
||||
items: [analyzed],
|
||||
},
|
||||
});
|
||||
|
||||
await engine.executeImport(report, {});
|
||||
|
||||
expect(insertedPosts.length).toBe(1);
|
||||
expect(insertedPosts[0].content).toContain('[[photo_archive]]');
|
||||
expect(insertedPosts[0].content).not.toContain('photo\\_archive');
|
||||
});
|
||||
|
||||
it('should not escape underscores in macro attributes during markdown conversion', async () => {
|
||||
const wxrPost = createMockWxrPost({
|
||||
content: '<p>Show: [my_gallery type="grid_view" size="large_thumb"]</p>',
|
||||
});
|
||||
const analyzed = createMockAnalyzedPost(wxrPost, 'conflict', 'overwrite');
|
||||
analyzed.existingPost = {
|
||||
id: 'existing-post-id',
|
||||
title: 'Existing Post',
|
||||
slug: 'test-post',
|
||||
checksum: 'old-checksum',
|
||||
pubDate: null,
|
||||
excerpt: null,
|
||||
author: null,
|
||||
tags: [],
|
||||
categories: [],
|
||||
};
|
||||
const report = createMockAnalysisReport({
|
||||
posts: {
|
||||
total: 1,
|
||||
new: 0,
|
||||
updates: 0,
|
||||
conflicts: 1,
|
||||
contentDuplicates: 0,
|
||||
items: [analyzed],
|
||||
},
|
||||
});
|
||||
|
||||
await engine.executeImport(report, {});
|
||||
|
||||
expect(insertedPosts.length).toBe(1);
|
||||
expect(insertedPosts[0].content).toContain('[[my_gallery type="grid_view" size="large_thumb"]]');
|
||||
});
|
||||
|
||||
it('should map tags based on analysis mappings', async () => {
|
||||
const wxrPost = createMockWxrPost({
|
||||
tags: ['OldTagName', 'NewTag'],
|
||||
|
||||
@@ -463,6 +463,47 @@ describe('MediaEngine', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRelativePath', () => {
|
||||
it('should return relative path from dataDir for a media item', async () => {
|
||||
mediaEngine.setProjectContext('test-project', '/projects/my-blog');
|
||||
|
||||
const selectChain = createSelectChain();
|
||||
selectChain.get.mockResolvedValue({
|
||||
id: 'abc-123',
|
||||
filePath: '/projects/my-blog/media/2025/01/abc-123.jpg',
|
||||
});
|
||||
vi.mocked(mockLocalDb.select).mockReturnValue(selectChain as any);
|
||||
|
||||
const result = await mediaEngine.getRelativePath('abc-123');
|
||||
|
||||
expect(result).toBe('media/2025/01/abc-123.jpg');
|
||||
});
|
||||
|
||||
it('should return null when media item is not found', async () => {
|
||||
mediaEngine.setProjectContext('test-project', '/projects/my-blog');
|
||||
|
||||
const selectChain = createSelectChain();
|
||||
selectChain.get.mockResolvedValue(undefined);
|
||||
vi.mocked(mockLocalDb.select).mockReturnValue(selectChain as any);
|
||||
|
||||
const result = await mediaEngine.getRelativePath('nonexistent');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null when filePath is empty', async () => {
|
||||
mediaEngine.setProjectContext('test-project', '/projects/my-blog');
|
||||
|
||||
const selectChain = createSelectChain();
|
||||
selectChain.get.mockResolvedValue({ id: 'abc-123', filePath: '' });
|
||||
vi.mocked(mockLocalDb.select).mockReturnValue(selectChain as any);
|
||||
|
||||
const result = await mediaEngine.getRelativePath('abc-123');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple Media Import', () => {
|
||||
beforeEach(() => {
|
||||
mockFiles.set('/source/image1.jpg', Buffer.from('image1-data'));
|
||||
|
||||
@@ -2001,17 +2001,24 @@ Published content`);
|
||||
it('should return all posts for current project', async () => {
|
||||
postEngine.setProjectContext('test-project');
|
||||
|
||||
// getAllPosts now makes 3 queries: count, drafts, non-drafts
|
||||
let selectCallCount = 0;
|
||||
vi.mocked(mockLocalDb.select).mockImplementation(() => {
|
||||
selectCallCount++;
|
||||
const chain = createSelectChain();
|
||||
const callNum = selectCallCount;
|
||||
chain.where = vi.fn().mockReturnValue({
|
||||
...chain,
|
||||
orderBy: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
offset: vi.fn().mockReturnThis(),
|
||||
all: vi.fn().mockResolvedValue([
|
||||
{ id: '1', title: 'Post 1', projectId: 'test-project', tags: '[]', categories: '[]' },
|
||||
{ id: '2', title: 'Post 2', projectId: 'test-project', tags: '[]', categories: '[]' },
|
||||
]),
|
||||
all: vi.fn().mockResolvedValue(
|
||||
callNum === 1
|
||||
? [{ id: '1' }, { id: '2' }] // count query: 2 total
|
||||
: callNum === 2
|
||||
? [{ id: '1', title: 'Draft Post', projectId: 'test-project', status: 'draft', tags: '[]', categories: '[]' }] // drafts
|
||||
: [{ id: '2', title: 'Published Post', projectId: 'test-project', status: 'published', tags: '[]', categories: '[]' }] // non-drafts
|
||||
),
|
||||
});
|
||||
return chain;
|
||||
});
|
||||
@@ -2021,21 +2028,24 @@ Published content`);
|
||||
});
|
||||
|
||||
it('should parse tags and categories JSON', async () => {
|
||||
// getAllPosts makes 3 queries: count, drafts, non-drafts
|
||||
let selectCallCount = 0;
|
||||
vi.mocked(mockLocalDb.select).mockImplementation(() => {
|
||||
selectCallCount++;
|
||||
const chain = createSelectChain();
|
||||
const callNum = selectCallCount;
|
||||
chain.where = vi.fn().mockReturnValue({
|
||||
...chain,
|
||||
orderBy: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
offset: vi.fn().mockReturnThis(),
|
||||
all: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: '1',
|
||||
title: 'Tagged Post',
|
||||
tags: '["tag1","tag2"]',
|
||||
categories: '["cat1"]'
|
||||
},
|
||||
]),
|
||||
all: vi.fn().mockResolvedValue(
|
||||
callNum === 1
|
||||
? [{ id: '1' }] // count
|
||||
: callNum === 2
|
||||
? [] // no drafts
|
||||
: [{ id: '1', title: 'Tagged Post', tags: '["tag1","tag2"]', categories: '["cat1"]' }] // non-drafts
|
||||
),
|
||||
});
|
||||
return chain;
|
||||
});
|
||||
@@ -2044,6 +2054,42 @@ Published content`);
|
||||
expect(result.items[0].tags).toEqual(['tag1', 'tag2']);
|
||||
expect(result.items[0].categories).toEqual(['cat1']);
|
||||
});
|
||||
|
||||
it('should always include all drafts regardless of pagination limit', async () => {
|
||||
postEngine.setProjectContext('test-project');
|
||||
|
||||
// Simulate: 3 drafts + many published, limit=2
|
||||
let selectCallCount = 0;
|
||||
vi.mocked(mockLocalDb.select).mockImplementation(() => {
|
||||
selectCallCount++;
|
||||
const chain = createSelectChain();
|
||||
const callNum = selectCallCount;
|
||||
chain.where = vi.fn().mockReturnValue({
|
||||
...chain,
|
||||
orderBy: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
offset: vi.fn().mockReturnThis(),
|
||||
all: vi.fn().mockResolvedValue(
|
||||
callNum === 1
|
||||
? [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }, { id: '5' }] // count: 5 total
|
||||
: callNum === 2
|
||||
? [ // 3 drafts (ALL of them, regardless of limit)
|
||||
{ id: '1', title: 'Draft 1', status: 'draft', tags: '[]', categories: '[]' },
|
||||
{ id: '2', title: 'Draft 2', status: 'draft', tags: '[]', categories: '[]' },
|
||||
{ id: '3', title: 'Draft 3', status: 'draft', tags: '[]', categories: '[]' },
|
||||
]
|
||||
: [] // no remaining slots for non-drafts (limit=2, 3 drafts > 2)
|
||||
),
|
||||
});
|
||||
return chain;
|
||||
});
|
||||
|
||||
// Even with limit=2, all 3 drafts must be returned
|
||||
const result = await postEngine.getAllPosts({ limit: 2, offset: 0 });
|
||||
expect(result.items).toHaveLength(3);
|
||||
expect(result.items.every(p => p.status === 'draft')).toBe(true);
|
||||
expect(result.hasMore).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPostsFiltered', () => {
|
||||
|
||||
@@ -76,6 +76,7 @@ const mockMediaEngine = {
|
||||
reindexText: vi.fn(),
|
||||
getThumbnailDataUrl: vi.fn(),
|
||||
regenerateMissingThumbnails: vi.fn(),
|
||||
getRelativePath: vi.fn(),
|
||||
};
|
||||
|
||||
const mockProjectEngine = {
|
||||
@@ -606,10 +607,21 @@ describe('IPC Handlers', () => {
|
||||
});
|
||||
|
||||
describe('media:getUrl', () => {
|
||||
it('should return bds-media protocol URL', async () => {
|
||||
it('should return relative media path', async () => {
|
||||
mockMediaEngine.getRelativePath.mockResolvedValue('media/2025/01/media-123.jpg');
|
||||
|
||||
const result = await invokeHandler('media:getUrl', 'media-123');
|
||||
|
||||
expect(result).toBe('bds-media://media-123');
|
||||
expect(mockMediaEngine.getRelativePath).toHaveBeenCalledWith('media-123');
|
||||
expect(result).toBe('media/2025/01/media-123.jpg');
|
||||
});
|
||||
|
||||
it('should fall back to media/{id} when relative path is not found', async () => {
|
||||
mockMediaEngine.getRelativePath.mockResolvedValue(null);
|
||||
|
||||
const result = await invokeHandler('media:getUrl', 'media-unknown');
|
||||
|
||||
expect(result).toBe('media/media-unknown');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user