Add tests for PostMediaEngine and ImportAnalysisEngine branch coverage
Co-authored-by: rfc1437 <774975+rfc1437@users.noreply.github.com>
This commit is contained in:
@@ -762,6 +762,97 @@ describe('ImportAnalysisEngine', () => {
|
|||||||
expect(youtubeMacro?.usages[0].params.id).toBe('wordpress');
|
expect(youtubeMacro?.usages[0].params.id).toBe('wordpress');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('HTML to Markdown Conversion - Linked Images', () => {
|
||||||
|
it('should convert linked images with image href (WordPress full-size pattern)', async () => {
|
||||||
|
setupDbReturns([], [], []);
|
||||||
|
|
||||||
|
const wxrData = createWxrData({
|
||||||
|
posts: [createWxrPost({
|
||||||
|
content: '<a href="http://example.com/full-image.jpg"><img src="http://example.com/thumb.jpg" alt="My Image" /></a>',
|
||||||
|
})],
|
||||||
|
});
|
||||||
|
|
||||||
|
const report = await engine.analyzeWxr(wxrData, '/test.xml');
|
||||||
|
|
||||||
|
// Should use the href URL (the full-size image) in markdown
|
||||||
|
expect(report.posts.items[0].markdownPreview).toContain('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert linked images with non-image href (use img src)', async () => {
|
||||||
|
setupDbReturns([], [], []);
|
||||||
|
|
||||||
|
const wxrData = createWxrData({
|
||||||
|
posts: [createWxrPost({
|
||||||
|
content: '<a href="http://example.com/article"><img src="http://example.com/image.jpg" alt="Article Image" /></a>',
|
||||||
|
})],
|
||||||
|
});
|
||||||
|
|
||||||
|
const report = await engine.analyzeWxr(wxrData, '/test.xml');
|
||||||
|
|
||||||
|
// Should use the img src since href is not an image
|
||||||
|
expect(report.posts.items[0].markdownPreview).toContain('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use img title as alt text when alt is empty', async () => {
|
||||||
|
setupDbReturns([], [], []);
|
||||||
|
|
||||||
|
const wxrData = createWxrData({
|
||||||
|
posts: [createWxrPost({
|
||||||
|
content: '<a href="http://example.com/full.jpg"><img src="http://example.com/thumb.jpg" alt="" title="My Title" /></a>',
|
||||||
|
})],
|
||||||
|
});
|
||||||
|
|
||||||
|
const report = await engine.analyzeWxr(wxrData, '/test.xml');
|
||||||
|
|
||||||
|
// Should use title as alt text and include title in markdown
|
||||||
|
expect(report.posts.items[0].markdownPreview).toContain('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract filename as alt text when both alt and title are empty', async () => {
|
||||||
|
setupDbReturns([], [], []);
|
||||||
|
|
||||||
|
const wxrData = createWxrData({
|
||||||
|
posts: [createWxrPost({
|
||||||
|
content: '<a href="http://example.com/beautiful-sunset.jpg"><img src="http://example.com/thumb.jpg" alt="" title="" /></a>',
|
||||||
|
})],
|
||||||
|
});
|
||||||
|
|
||||||
|
const report = await engine.analyzeWxr(wxrData, '/test.xml');
|
||||||
|
|
||||||
|
// Should extract filename from URL as alt text
|
||||||
|
expect(report.posts.items[0].markdownPreview).toContain('beautiful-sunset.jpg');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty/whitespace content gracefully', async () => {
|
||||||
|
setupDbReturns([], [], []);
|
||||||
|
|
||||||
|
const wxrData = createWxrData({
|
||||||
|
posts: [createWxrPost({
|
||||||
|
content: ' ',
|
||||||
|
})],
|
||||||
|
});
|
||||||
|
|
||||||
|
const report = await engine.analyzeWxr(wxrData, '/test.xml');
|
||||||
|
|
||||||
|
expect(report.posts.items[0].markdownPreview).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve line breaks in text content', async () => {
|
||||||
|
setupDbReturns([], [], []);
|
||||||
|
|
||||||
|
const wxrData = createWxrData({
|
||||||
|
posts: [createWxrPost({
|
||||||
|
content: '<p>Line one\nLine two\nLine three</p>',
|
||||||
|
})],
|
||||||
|
});
|
||||||
|
|
||||||
|
const report = await engine.analyzeWxr(wxrData, '/test.xml');
|
||||||
|
|
||||||
|
// Line breaks within text should be preserved
|
||||||
|
expect(report.posts.items[0].markdownPreview).toContain('Line one');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -538,4 +538,126 @@ describe('PostMediaEngine', () => {
|
|||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('importMediaForPost', () => {
|
||||||
|
it('should import media and link it to the post', async () => {
|
||||||
|
const postId = 'post-1';
|
||||||
|
const sourcePath = '/path/to/image.jpg';
|
||||||
|
const importedMediaId = 'imported-media-123';
|
||||||
|
|
||||||
|
mockImportMedia.mockResolvedValue({ id: importedMediaId });
|
||||||
|
mockGetMedia.mockResolvedValue(createMockMedia({ id: importedMediaId, linkedPostIds: [] }));
|
||||||
|
|
||||||
|
const result = await engine.importMediaForPost(postId, sourcePath);
|
||||||
|
|
||||||
|
expect(mockImportMedia).toHaveBeenCalledWith(sourcePath);
|
||||||
|
expect(result.postId).toBe(postId);
|
||||||
|
expect(result.mediaId).toBe(importedMediaId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getLinkedMediaDataForPost', () => {
|
||||||
|
it('should return linked media with full media data', async () => {
|
||||||
|
const postId = 'post-1';
|
||||||
|
const media1 = createMockMedia({ id: 'media-1', title: 'Image 1' });
|
||||||
|
const media2 = createMockMedia({ id: 'media-2', title: 'Image 2' });
|
||||||
|
|
||||||
|
selectMockData = [
|
||||||
|
{ id: 'link-1', projectId: 'test-project', postId, mediaId: 'media-1', sortOrder: 0, createdAt: new Date() },
|
||||||
|
{ id: 'link-2', projectId: 'test-project', postId, mediaId: 'media-2', sortOrder: 1, createdAt: new Date() },
|
||||||
|
];
|
||||||
|
|
||||||
|
mockGetMedia.mockImplementation((id: string) => {
|
||||||
|
if (id === 'media-1') return Promise.resolve(media1);
|
||||||
|
if (id === 'media-2') return Promise.resolve(media2);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await engine.getLinkedMediaDataForPost(postId);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(2);
|
||||||
|
expect(result[0].media.title).toBe('Image 1');
|
||||||
|
expect(result[1].media.title).toBe('Image 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip links where media is not found', async () => {
|
||||||
|
const postId = 'post-1';
|
||||||
|
const media1 = createMockMedia({ id: 'media-1', title: 'Image 1' });
|
||||||
|
|
||||||
|
selectMockData = [
|
||||||
|
{ id: 'link-1', projectId: 'test-project', postId, mediaId: 'media-1', sortOrder: 0, createdAt: new Date() },
|
||||||
|
{ id: 'link-2', projectId: 'test-project', postId, mediaId: 'media-deleted', sortOrder: 1, createdAt: new Date() },
|
||||||
|
];
|
||||||
|
|
||||||
|
mockGetMedia.mockImplementation((id: string) => {
|
||||||
|
if (id === 'media-1') return Promise.resolve(media1);
|
||||||
|
return Promise.resolve(null); // media-deleted not found
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await engine.getLinkedMediaDataForPost(postId);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].media.title).toBe('Image 1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array when no links exist', async () => {
|
||||||
|
selectMockData = [];
|
||||||
|
|
||||||
|
const result = await engine.getLinkedMediaDataForPost('post-no-links');
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('edge cases for linkMediaToPost', () => {
|
||||||
|
it('should not add duplicate postId to linkedPostIds', async () => {
|
||||||
|
const postId = 'post-1';
|
||||||
|
const mediaId = 'media-1';
|
||||||
|
|
||||||
|
// Media already has this post linked
|
||||||
|
mockGetMedia.mockResolvedValue(createMockMedia({
|
||||||
|
id: mediaId,
|
||||||
|
linkedPostIds: [postId] // Already linked
|
||||||
|
}));
|
||||||
|
|
||||||
|
await engine.linkMediaToPost(postId, mediaId);
|
||||||
|
|
||||||
|
// updateMedia should not be called since post is already in linkedPostIds
|
||||||
|
expect(mockUpdateMedia).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate correct sortOrder when existing links present', async () => {
|
||||||
|
const postId = 'post-1';
|
||||||
|
const mediaId = 'media-new';
|
||||||
|
|
||||||
|
// Existing links with specific sort orders
|
||||||
|
selectMockData = [
|
||||||
|
{ id: 'link-1', projectId: 'test-project', postId, mediaId: 'media-1', sortOrder: 5, createdAt: new Date() },
|
||||||
|
{ id: 'link-2', projectId: 'test-project', postId, mediaId: 'media-2', sortOrder: 10, createdAt: new Date() },
|
||||||
|
];
|
||||||
|
|
||||||
|
mockGetMedia.mockResolvedValue(createMockMedia({ id: mediaId, linkedPostIds: [] }));
|
||||||
|
|
||||||
|
const result = await engine.linkMediaToPost(postId, mediaId);
|
||||||
|
|
||||||
|
// sortOrder should be max + 1 = 11
|
||||||
|
expect(result.sortOrder).toBe(11);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle null media when linking', async () => {
|
||||||
|
const postId = 'post-1';
|
||||||
|
const mediaId = 'media-nonexistent';
|
||||||
|
|
||||||
|
// Media not found
|
||||||
|
mockGetMedia.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const result = await engine.linkMediaToPost(postId, mediaId);
|
||||||
|
|
||||||
|
// Should still create the link
|
||||||
|
expect(result.postId).toBe(postId);
|
||||||
|
expect(result.mediaId).toBe(mediaId);
|
||||||
|
// But updateMedia shouldn't be called
|
||||||
|
expect(mockUpdateMedia).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user