mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
refactor: add markdown renderer tests and remove dead IPC code
- Add 18 unit tests for marked-options-factory covering: - Image renderer (sizing syntax, lazy loading, dimensions) - Paragraph renderer (headings h1./h2. syntax) - parseImageDimensionsFromTitle helper - preprocessMarkdown hook - Export parseImageDimensionsFromTitle and preprocessMarkdown for testability - Remove unused IPC.TOGGLE_FULLSCREEN event and handler (F11 fullscreen is handled directly in main-window.ts)
This commit is contained in:
parent
a1af9d5d59
commit
6ca7223dc6
4 changed files with 196 additions and 12 deletions
|
|
@ -78,11 +78,4 @@ export const initAppControlIpc = (): void => {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on(IPC.TOGGLE_FULLSCREEN, () => {
|
||||
const mainWin = getWin();
|
||||
if (mainWin) {
|
||||
mainWin.setFullScreen(!mainWin.isFullScreen());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -72,8 +72,6 @@ export enum IPC {
|
|||
UPDATE_SETTINGS = 'UPDATE_SETTINGS',
|
||||
UPDATE_TITLE_BAR_DARK_MODE = 'UPDATE_TITLE_BAR_DARK_MODE',
|
||||
|
||||
TOGGLE_FULLSCREEN = 'TOGGLE_FULLSCREEN',
|
||||
|
||||
// maybe_UPDATE_CURRENT_TASK = 'UPDATE_CURRENT_TASK',
|
||||
// maybe_IS_IDLE = 'IS_IDLE',
|
||||
// maybe_IS_BUSY = 'IS_BUSY',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { markedOptionsFactory } from './marked-options-factory';
|
||||
import {
|
||||
markedOptionsFactory,
|
||||
parseImageDimensionsFromTitle,
|
||||
preprocessMarkdown,
|
||||
} from './marked-options-factory';
|
||||
|
||||
describe('markedOptionsFactory', () => {
|
||||
let options: ReturnType<typeof markedOptionsFactory>;
|
||||
|
|
@ -125,7 +129,196 @@ describe('markedOptionsFactory', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('image renderer', () => {
|
||||
it('should render basic image with alt text', () => {
|
||||
const result = options.renderer!.image({
|
||||
href: 'http://example.com/image.png',
|
||||
title: null,
|
||||
text: 'Alt text',
|
||||
} as any);
|
||||
expect(result).toContain('src="http://example.com/image.png"');
|
||||
expect(result).toContain('alt="Alt text"');
|
||||
expect(result).toContain('loading="lazy"');
|
||||
});
|
||||
|
||||
it('should render image with width and height from title', () => {
|
||||
const result = options.renderer!.image({
|
||||
href: 'http://example.com/image.png',
|
||||
title: '200|150',
|
||||
text: 'Sized image',
|
||||
} as any);
|
||||
expect(result).toContain('width="200"');
|
||||
expect(result).toContain('height="150"');
|
||||
expect(result).not.toContain('title=');
|
||||
});
|
||||
|
||||
it('should render image with width only', () => {
|
||||
const result = options.renderer!.image({
|
||||
href: 'http://example.com/image.png',
|
||||
title: '300|',
|
||||
text: 'Width only',
|
||||
} as any);
|
||||
expect(result).toContain('width="300"');
|
||||
expect(result).not.toContain('height=');
|
||||
});
|
||||
|
||||
it('should render image with height only', () => {
|
||||
const result = options.renderer!.image({
|
||||
href: 'http://example.com/image.png',
|
||||
title: '|250',
|
||||
text: 'Height only',
|
||||
} as any);
|
||||
expect(result).toContain('height="250"');
|
||||
expect(result).not.toContain('width=');
|
||||
});
|
||||
|
||||
it('should render regular title when not in dimension format', () => {
|
||||
const result = options.renderer!.image({
|
||||
href: 'http://example.com/image.png',
|
||||
title: 'A descriptive title',
|
||||
text: 'Image',
|
||||
} as any);
|
||||
expect(result).toContain('title="A descriptive title"');
|
||||
expect(result).not.toContain('width=');
|
||||
expect(result).not.toContain('height=');
|
||||
});
|
||||
|
||||
it('should handle null title', () => {
|
||||
const result = options.renderer!.image({
|
||||
href: 'http://example.com/image.png',
|
||||
title: null,
|
||||
text: 'No title',
|
||||
} as any);
|
||||
expect(result).not.toContain('title=');
|
||||
expect(result).toContain('alt="No title"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('paragraph renderer', () => {
|
||||
it('should render simple paragraph', () => {
|
||||
const mockParser = {
|
||||
parseInline: (tokens: any[]) =>
|
||||
tokens.map((t: any) => t.raw || t.text || '').join(''),
|
||||
};
|
||||
const paragraphRenderer = options.renderer!.paragraph.bind({ parser: mockParser });
|
||||
|
||||
const result = paragraphRenderer({
|
||||
tokens: [{ type: 'text', raw: 'Simple paragraph', text: 'Simple paragraph' }],
|
||||
} as any);
|
||||
expect(result).toBe('<p>Simple paragraph</p>');
|
||||
});
|
||||
|
||||
it('should convert h1. syntax to heading', () => {
|
||||
const mockParser = {
|
||||
parseInline: (tokens: any[]) =>
|
||||
tokens.map((t: any) => t.raw || t.text || '').join(''),
|
||||
};
|
||||
const paragraphRenderer = options.renderer!.paragraph.bind({ parser: mockParser });
|
||||
|
||||
const result = paragraphRenderer({
|
||||
tokens: [{ type: 'text', raw: 'h1. My Heading', text: 'h1. My Heading' }],
|
||||
} as any);
|
||||
expect(result).toBe('<h1> My Heading</h1>');
|
||||
});
|
||||
|
||||
it('should convert h2. syntax to heading', () => {
|
||||
const mockParser = {
|
||||
parseInline: (tokens: any[]) =>
|
||||
tokens.map((t: any) => t.raw || t.text || '').join(''),
|
||||
};
|
||||
const paragraphRenderer = options.renderer!.paragraph.bind({ parser: mockParser });
|
||||
|
||||
const result = paragraphRenderer({
|
||||
tokens: [{ type: 'text', raw: 'h2. Subheading', text: 'h2. Subheading' }],
|
||||
} as any);
|
||||
expect(result).toBe('<h2> Subheading</h2>');
|
||||
});
|
||||
});
|
||||
|
||||
// Note: URL auto-linking is handled automatically by marked v17 with gfm: true.
|
||||
// We intentionally do NOT override renderer.text for this - the lexer converts
|
||||
// URLs to link tokens before they reach the text renderer.
|
||||
});
|
||||
|
||||
describe('parseImageDimensionsFromTitle', () => {
|
||||
it('should parse width and height from title', () => {
|
||||
const result = parseImageDimensionsFromTitle('200|150');
|
||||
expect(result).toEqual({ width: '200', height: '150' });
|
||||
});
|
||||
|
||||
it('should parse width only', () => {
|
||||
const result = parseImageDimensionsFromTitle('300|');
|
||||
expect(result).toEqual({ width: '300', height: undefined });
|
||||
});
|
||||
|
||||
it('should parse height only', () => {
|
||||
const result = parseImageDimensionsFromTitle('|250');
|
||||
expect(result).toEqual({ width: undefined, height: '250' });
|
||||
});
|
||||
|
||||
it('should return empty object for null title', () => {
|
||||
const result = parseImageDimensionsFromTitle(null);
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should return empty object for non-dimension format', () => {
|
||||
const result = parseImageDimensionsFromTitle('A regular title');
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should return empty object for empty string', () => {
|
||||
const result = parseImageDimensionsFromTitle('');
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle pipe only', () => {
|
||||
const result = parseImageDimensionsFromTitle('|');
|
||||
expect(result).toEqual({ width: undefined, height: undefined });
|
||||
});
|
||||
});
|
||||
|
||||
describe('preprocessMarkdown', () => {
|
||||
it('should convert image sizing syntax to title format', () => {
|
||||
const input = '';
|
||||
const result = preprocessMarkdown(input);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle width only', () => {
|
||||
const input = '';
|
||||
const result = preprocessMarkdown(input);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle height only', () => {
|
||||
const input = '';
|
||||
const result = preprocessMarkdown(input);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle multiple images in text', () => {
|
||||
const input = 'Some text  more  end';
|
||||
const result = preprocessMarkdown(input);
|
||||
expect(result).toBe(
|
||||
'Some text  more  end',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not modify images without sizing syntax', () => {
|
||||
const input = '';
|
||||
const result = preprocessMarkdown(input);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should not modify images with regular title', () => {
|
||||
const input = '';
|
||||
const result = preprocessMarkdown(input);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should preserve other markdown content', () => {
|
||||
const input = '# Header\n\n\n\nParagraph';
|
||||
const result = preprocessMarkdown(input);
|
||||
expect(result).toBe('# Header\n\n\n\nParagraph');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { Hooks } from 'marked';
|
|||
* The title format is expected to be "width|height" (e.g., "200|150", "200|", "|150")
|
||||
* This is generated by preprocessing.
|
||||
*/
|
||||
const parseImageDimensionsFromTitle = (
|
||||
export const parseImageDimensionsFromTitle = (
|
||||
title: string | null,
|
||||
): { width?: string; height?: string } => {
|
||||
if (!title) {
|
||||
|
|
@ -30,7 +30,7 @@ const parseImageDimensionsFromTitle = (
|
|||
* Converts:  -> 
|
||||
* This allows marked.js to parse it as a valid image with a title.
|
||||
*/
|
||||
const preprocessMarkdown = (markdown: string): string => {
|
||||
export const preprocessMarkdown = (markdown: string): string => {
|
||||
// Match:  or  or 
|
||||
// Capture groups: 1=alt, 2=url, 3=width, 4=height
|
||||
return markdown.replace(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue