Add tests for remarkWikiLinks functionality

This commit is contained in:
2025-05-26 20:58:26 +02:00
parent 49cac03db8
commit e9abe14364

View File

@@ -0,0 +1,337 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
import { remarkWikiLinks } from './remarkWikiLinks';
import * as fileApi from '@/api/file';
// Mock the file API
vi.mock('@/api/file');
// Mock window.API_BASE_URL
const mockApiBaseUrl = 'http://localhost:8080/api/v1';
describe('remarkWikiLinks', () => {
beforeEach(() => {
window.API_BASE_URL = mockApiBaseUrl;
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
const createProcessor = (workspaceName: string) => {
return unified()
.use(remarkParse)
.use(remarkWikiLinks, workspaceName)
.use(remarkStringify);
};
describe('basic wiki link processing', () => {
it('converts existing file links correctly', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue(['docs/test.md']);
const processor = createProcessor('test-workspace');
const markdown = 'Check out [[test]] for more info.';
const result = await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith(
'test-workspace',
'test.md'
);
expect(result.toString()).toContain('test');
});
it('handles non-existent files with not found links', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue([]);
const processor = createProcessor('test-workspace');
const markdown = 'This [[nonexistent]] file does not exist.';
const result = await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith(
'test-workspace',
'nonexistent.md'
);
expect(result.toString()).toContain('nonexistent');
});
it('handles API errors gracefully', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockRejectedValue(new Error('API Error'));
const processor = createProcessor('test-workspace');
const markdown = 'This [[error-file]] causes an error.';
// Should not throw
const result = await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith(
'test-workspace',
'error-file.md'
);
expect(result.toString()).toContain('error-file');
});
});
describe('wiki link syntax variations', () => {
it('handles basic wiki links', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue(['basic.md']);
const processor = createProcessor('workspace');
const markdown = '[[basic]]';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith(
'workspace',
'basic.md'
);
});
it('handles wiki links with display text', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue(['file.md']);
const processor = createProcessor('workspace');
const markdown = '[[file|Display Text]]';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith('workspace', 'file.md');
});
it('handles wiki links with headings', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue(['file.md']);
const processor = createProcessor('workspace');
const markdown = '[[file#section]]';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith('workspace', 'file.md');
});
it('handles wiki links with both headings and display text', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue(['file.md']);
const processor = createProcessor('workspace');
const markdown = '[[file#section|Custom Display]]';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith('workspace', 'file.md');
});
it('handles image wiki links', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue(['image.png']);
const processor = createProcessor('workspace');
const markdown = '![[image.png]]';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith(
'workspace',
'image.png'
);
});
it('handles image wiki links with alt text', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue(['photo.jpg']);
const processor = createProcessor('workspace');
const markdown = '![[photo.jpg|Alt text for photo]]';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith(
'workspace',
'photo.jpg'
);
});
});
describe('file extension handling', () => {
it('adds .md extension to files without extensions', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue(['notes.md']);
const processor = createProcessor('workspace');
const markdown = '[[notes]]';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith(
'workspace',
'notes.md'
);
});
it('preserves existing file extensions', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue(['document.txt']);
const processor = createProcessor('workspace');
const markdown = '[[document.txt]]';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith(
'workspace',
'document.txt'
);
});
it('handles image files without adding .md extension', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue(['screenshot.png']);
const processor = createProcessor('workspace');
const markdown = '![[screenshot.png]]';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith(
'workspace',
'screenshot.png'
);
});
});
describe('multiple wiki links', () => {
it('processes multiple wiki links in the same text', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName
.mockResolvedValueOnce(['first.md'])
.mockResolvedValueOnce(['second.md']);
const processor = createProcessor('workspace');
const markdown = 'See [[first]] and [[second]] for details.';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledTimes(2);
expect(mockLookupFileByName).toHaveBeenNthCalledWith(
1,
'workspace',
'first.md'
);
expect(mockLookupFileByName).toHaveBeenNthCalledWith(
2,
'workspace',
'second.md'
);
});
it('handles mix of existing and non-existing files', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName
.mockResolvedValueOnce(['exists.md'])
.mockResolvedValueOnce([]);
const processor = createProcessor('workspace');
const markdown = 'Check [[exists]] but not [[missing]].';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledTimes(2);
expect(mockLookupFileByName).toHaveBeenNthCalledWith(
1,
'workspace',
'exists.md'
);
expect(mockLookupFileByName).toHaveBeenNthCalledWith(
2,
'workspace',
'missing.md'
);
});
});
describe('edge cases', () => {
it('handles text without wiki links', async () => {
const processor = createProcessor('workspace');
const markdown = 'Just regular text with no wiki links.';
const result = await processor.process(markdown);
expect(result.toString()).toBe('Just regular text with no wiki links.\n');
expect(fileApi.lookupFileByName).not.toHaveBeenCalled();
});
it('handles wiki links with only spaces', async () => {
const processor = createProcessor('workspace');
const markdown = 'Spaces [[ ]] link.';
const result = await processor.process(markdown);
expect(result.toString()).toContain('Spaces');
// Should not call API for empty/whitespace-only links
});
it('handles nested brackets', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue(['test.md']);
const processor = createProcessor('workspace');
const markdown = '[[test]] and some [regular](link) text.';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith('workspace', 'test.md');
});
it('handles special characters in file names', async () => {
const mockLookupFileByName = vi.mocked(fileApi.lookupFileByName);
mockLookupFileByName.mockResolvedValue(['file with spaces & symbols.md']);
const processor = createProcessor('workspace');
const markdown = '[[file with spaces & symbols]]';
await processor.process(markdown);
expect(mockLookupFileByName).toHaveBeenCalledWith(
'workspace',
'file with spaces & symbols.md'
);
});
});
describe('workspace handling', () => {
it('handles empty workspace name gracefully', async () => {
const processor = createProcessor('');
const markdown = '[[test]]';
const result = await processor.process(markdown);
expect(result.toString()).toContain('test');
// Should not call API when workspace is empty
expect(fileApi.lookupFileByName).not.toHaveBeenCalled();
});
it('does not process links when workspace is not provided', async () => {
const processor = unified()
.use(remarkParse)
.use(remarkWikiLinks, '')
.use(remarkStringify);
const markdown = '[[test]]';
const result = await processor.process(markdown);
expect(result.toString()).toContain('test');
expect(fileApi.lookupFileByName).not.toHaveBeenCalled();
});
});
});