import type { App, TFile } from 'obsidian'; import { replaceImageEmbedsWithHtml } from '@/utils/imageEmbed'; type LinkResolver = ( linkPath: string, sourcePath: string, files: Map ) => TFile | null | undefined; // Mock App and vault for testing function createMockApp(files: Map = new Map(), resolveLink?: LinkResolver): App { const mockFiles = new Map(); files.forEach((resourcePath, filePath) => { mockFiles.set(filePath, { path: filePath, basename: filePath.split('/').pop()?.replace(/\.[^.]+$/, '') || filePath, } as TFile); }); return { vault: { getAbstractFileByPath: (path: string) => mockFiles.get(path) || null, getResourcePath: (file: TFile) => files.get(file.path) || `app://local/${file.path}`, }, metadataCache: { getFirstLinkpathDest: (linkPath: string, sourcePath: string) => { const resolved = resolveLink?.(linkPath, sourcePath, mockFiles); if (resolved !== undefined) { return resolved; } // Try to find by basename for (const [path, file] of mockFiles) { if (path.endsWith(linkPath) || path === linkPath) { return file; } } return null; }, }, } as unknown as App; } describe('replaceImageEmbedsWithHtml', () => { describe('basic image embeds', () => { it('replaces simple image embed with img tag', () => { const app = createMockApp(new Map([['image.png', 'app://local/image.png']])); const result = replaceImageEmbedsWithHtml('![[image.png]]', app); expect(result).toContain(' { const app = createMockApp(new Map([['assets/photo.jpg', 'app://local/assets/photo.jpg']])); const result = replaceImageEmbedsWithHtml('![[assets/photo.jpg]]', app); expect(result).toContain('src="app://local/assets/photo.jpg"'); }); it('handles image in surrounding text', () => { const app = createMockApp(new Map([['test.png', 'app://local/test.png']])); const result = replaceImageEmbedsWithHtml('Check this ![[test.png]] image', app); expect(result).toContain('Check this'); expect(result).toContain('image'); expect(result).toContain(' { it('uses alt text from wikilink', () => { const app = createMockApp(new Map([['image.png', 'app://local/image.png']])); const result = replaceImageEmbedsWithHtml('![[image.png|My Alt Text]]', app); expect(result).toContain('alt="My Alt Text"'); }); it('applies width dimension from alt text', () => { const app = createMockApp(new Map([['image.png', 'app://local/image.png']])); const result = replaceImageEmbedsWithHtml('![[image.png|300]]', app); expect(result).toContain('style="width: 300px;"'); }); it('applies width and height dimensions', () => { const app = createMockApp(new Map([['image.png', 'app://local/image.png']])); const result = replaceImageEmbedsWithHtml('![[image.png|200x150]]', app); expect(result).toContain('style="width: 200px; height: 150px;"'); }); it('uses basename as alt when no alt text provided', () => { const app = createMockApp(new Map([['folder/my-image.png', 'app://local/folder/my-image.png']])); const result = replaceImageEmbedsWithHtml('![[folder/my-image.png]]', app); expect(result).toContain('alt="my-image"'); }); }); describe('supported image extensions', () => { const extensions = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico']; extensions.forEach((ext) => { it(`replaces .${ext} image embed`, () => { const app = createMockApp(new Map([[`image.${ext}`, `app://local/image.${ext}`]])); const result = replaceImageEmbedsWithHtml(`![[image.${ext}]]`, app); expect(result).toContain(' { const app = createMockApp(new Map([['photo.PNG', 'app://local/photo.PNG']])); const result = replaceImageEmbedsWithHtml('![[photo.PNG]]', app); expect(result).toContain(' { const app = createMockApp(new Map([['image.JpG', 'app://local/image.JpG']])); const result = replaceImageEmbedsWithHtml('![[image.JpG]]', app); expect(result).toContain(' { it('leaves markdown file embed unchanged', () => { const app = createMockApp(); const result = replaceImageEmbedsWithHtml('![[note.md]]', app); expect(result).toBe('![[note.md]]'); }); it('leaves pdf embed unchanged', () => { const app = createMockApp(); const result = replaceImageEmbedsWithHtml('![[document.pdf]]', app); expect(result).toBe('![[document.pdf]]'); }); it('leaves audio embed unchanged', () => { const app = createMockApp(); const result = replaceImageEmbedsWithHtml('![[audio.mp3]]', app); expect(result).toBe('![[audio.mp3]]'); }); it('processes only image embeds in mixed content', () => { const app = createMockApp(new Map([['image.png', 'app://local/image.png']])); const result = replaceImageEmbedsWithHtml('![[note.md]] and ![[image.png]]', app); expect(result).toContain('![[note.md]]'); expect(result).toContain(' { it('shows fallback when image file not found', () => { const app = createMockApp(); // Empty vault const result = replaceImageEmbedsWithHtml('![[missing.png]]', app); expect(result).toContain('class="claudian-embedded-image-fallback"'); expect(result).toContain('![[missing.png]]'); }); it('escapes HTML in fallback wikilink', () => { const app = createMockApp(); const result = replaceImageEmbedsWithHtml('![[.png]]', app); expect(result).not.toContain('']])); const result = replaceImageEmbedsWithHtml('![[test.png]]', app); expect(result).not.toContain('