2025-08-10 01:17:12 -07:00
|
|
|
import { EditorSelection } from '@codemirror/state';
|
|
|
|
|
import createTestEditor from '../../testing/createTestEditor';
|
2025-09-08 04:01:54 -07:00
|
|
|
import renderBlockImages, { resetImageResourceEffect, testing__resetImageRefreshCounterCache } from './renderBlockImages';
|
2025-08-10 01:17:12 -07:00
|
|
|
import { EditorView } from '@codemirror/view';
|
|
|
|
|
|
2025-09-08 04:01:54 -07:00
|
|
|
const allowImageUrlsToBeFetched = async () => {
|
|
|
|
|
// Yield to the event loop. Since image URLs are fetched asynchronously, this is needed to
|
|
|
|
|
// allow the asynchronous promise code to run
|
|
|
|
|
await Promise.resolve();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const createEditor = async (initialMarkdown: string, hasImage: boolean) => {
|
|
|
|
|
const resolveImageSrc = jest.fn((src, counter) => Promise.resolve(`${src}?r=${counter}`));
|
|
|
|
|
const editor = await createTestEditor(
|
2025-08-10 01:17:12 -07:00
|
|
|
initialMarkdown,
|
|
|
|
|
EditorSelection.cursor(0),
|
|
|
|
|
hasImage ? ['Image'] : [],
|
|
|
|
|
[renderBlockImages({ resolveImageSrc })],
|
|
|
|
|
);
|
2025-09-08 04:01:54 -07:00
|
|
|
await allowImageUrlsToBeFetched();
|
|
|
|
|
return editor;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const findImages = (editor: EditorView) => {
|
|
|
|
|
return editor.dom.querySelectorAll<HTMLDivElement>('div.cm-md-image > .image');
|
2025-08-10 01:17:12 -07:00
|
|
|
};
|
|
|
|
|
|
2025-09-08 04:01:54 -07:00
|
|
|
const getImageBackgroundUrls = (editor: EditorView) => {
|
|
|
|
|
return [...findImages(editor)].map(image => image.style.backgroundImage);
|
2025-08-10 01:17:12 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
describe('renderBlockImages', () => {
|
2025-09-08 04:01:54 -07:00
|
|
|
beforeEach(() => {
|
|
|
|
|
testing__resetImageRefreshCounterCache();
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-10 01:17:12 -07:00
|
|
|
test.each([
|
|
|
|
|
{ spaceBefore: '', spaceAfter: '\n\n', alt: 'test' },
|
|
|
|
|
{ spaceBefore: '', spaceAfter: '', alt: 'This is a test!' },
|
|
|
|
|
{ spaceBefore: ' ', spaceAfter: ' ', alt: 'test' },
|
|
|
|
|
{ spaceBefore: '', spaceAfter: '', alt: '!!!!' },
|
|
|
|
|
])('should render images below their Markdown source (case %#)', async ({ spaceBefore, spaceAfter, alt }) => {
|
|
|
|
|
const editor = await createEditor(`${spaceBefore}${spaceAfter}`, true);
|
|
|
|
|
|
2025-09-08 04:01:54 -07:00
|
|
|
const images = findImages(editor);
|
|
|
|
|
expect(images).toHaveLength(1);
|
|
|
|
|
expect(images[0].role).toBe('image');
|
|
|
|
|
expect(images[0].ariaLabel).toBe(alt);
|
2025-08-10 01:17:12 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// For now, only Joplin resources are rendered. This simplifies the implementation and avoids
|
|
|
|
|
// potentially-unwanted web requests when opening a note with only the editor open.
|
|
|
|
|
test('should not render web images', async () => {
|
|
|
|
|
const editor = await createEditor('\n\n', true);
|
2025-09-08 04:01:54 -07:00
|
|
|
const images = findImages(editor);
|
|
|
|
|
expect(images).toHaveLength(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('should allow reloading specific images', async () => {
|
|
|
|
|
const editor = await createEditor('\n', true);
|
|
|
|
|
|
|
|
|
|
// Should have the expected original image URLs
|
|
|
|
|
expect(getImageBackgroundUrls(editor)).toMatchObject([
|
|
|
|
|
'url(:/a123456789abcdef0123456789abcdef?r=0)',
|
|
|
|
|
'url(:/b123456789abcdef0123456789abcde2?r=0)',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
editor.dispatch({
|
|
|
|
|
effects: [resetImageResourceEffect.of({ id: 'a123456789abcdef0123456789abcdef' })],
|
|
|
|
|
});
|
|
|
|
|
await allowImageUrlsToBeFetched();
|
|
|
|
|
|
|
|
|
|
expect(getImageBackgroundUrls(editor)).toMatchObject([
|
|
|
|
|
'url(:/a123456789abcdef0123456789abcdef?r=1)',
|
|
|
|
|
'url(:/b123456789abcdef0123456789abcde2?r=0)',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
editor.dispatch({
|
|
|
|
|
effects: [
|
|
|
|
|
resetImageResourceEffect.of({ id: 'a123456789abcdef0123456789abcdef' }),
|
|
|
|
|
resetImageResourceEffect.of({ id: 'b123456789abcdef0123456789abcde2' }),
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
await allowImageUrlsToBeFetched();
|
|
|
|
|
|
|
|
|
|
expect(getImageBackgroundUrls(editor)).toMatchObject([
|
|
|
|
|
'url(:/a123456789abcdef0123456789abcdef?r=2)',
|
|
|
|
|
'url(:/b123456789abcdef0123456789abcde2?r=1)',
|
|
|
|
|
]);
|
2025-08-10 01:17:12 -07:00
|
|
|
});
|
|
|
|
|
});
|