一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
70 行
2.7 KiB
TypeScript
70 行
2.7 KiB
TypeScript
import { EditorSelection } from '@codemirror/state';
|
|
import createTestEditor from '../../testing/createTestEditor';
|
|
import renderBlockMedia from './renderBlockMedia';
|
|
import { EditorView } from '@codemirror/view';
|
|
|
|
const allowMediaInfoToResolve = async () => {
|
|
// Yield to the event loop. Since resource info is fetched asynchronously, this is
|
|
// needed to allow the asynchronous promise code to run
|
|
await Promise.resolve();
|
|
};
|
|
|
|
const resolveResourceInfo = jest.fn((src: string) => {
|
|
const match = src.match(/:\/([a-zA-Z0-9]{32})/);
|
|
const id = match ? match[1] : '';
|
|
const mimeById: Record<string, string> = {
|
|
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa': 'audio/mpeg',
|
|
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb': 'video/mp4',
|
|
'cccccccccccccccccccccccccccccccc': 'application/pdf',
|
|
};
|
|
const mime = mimeById[id];
|
|
if (!mime) return Promise.resolve(null);
|
|
return Promise.resolve({ url: `https://example.com/${id}`, mime });
|
|
});
|
|
|
|
const createEditor = async (initialMarkdown: string) => {
|
|
const editor = await createTestEditor(
|
|
initialMarkdown,
|
|
EditorSelection.cursor(0),
|
|
['Link'],
|
|
[renderBlockMedia({ resolveImageSrc: async () => '', openLink: () => {}, resolveResourceInfo })],
|
|
);
|
|
await allowMediaInfoToResolve();
|
|
return editor;
|
|
};
|
|
|
|
const findMedia = (editor: EditorView) => {
|
|
return editor.dom.querySelectorAll('div.cm-md-media > audio, div.cm-md-media > video');
|
|
};
|
|
|
|
describe('renderBlockMedia', () => {
|
|
test.each([
|
|
{ markup: '[recording.m4a](:/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)', tag: 'AUDIO' },
|
|
{ markup: '[video.mp4](:/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)', tag: 'VIDEO' },
|
|
{ markup: '[3月24日(1).mp4](:/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)', tag: 'BODY' },
|
|
])('should render a player below a standalone media link (case %#)', async ({ markup, tag }) => {
|
|
const editor = await createEditor(markup);
|
|
|
|
const media = findMedia(editor);
|
|
expect(media).toHaveLength(1);
|
|
expect(media[0].getAttribute('aria-label')).toContain(tag === 'BODY' ? '3月24日(1).mp4' : tag === 'AUDIO' ? 'recording.m4a' : 'video.mp4');
|
|
// The raw Markdown must stay editable above the player
|
|
expect(editor.state.doc.toString()).toContain(markup);
|
|
});
|
|
|
|
test('should not render a player for non-media attachments', async () => {
|
|
const editor = await createEditor('[doc.pdf](:/cccccccccccccccccccccccccccccccc)');
|
|
|
|
expect(findMedia(editor)).toHaveLength(0);
|
|
// The leftover placeholder wrapper must be removed
|
|
expect(editor.dom.querySelectorAll('div.cm-md-media')).toHaveLength(0);
|
|
});
|
|
|
|
test('should not render a player for web links', async () => {
|
|
const editor = await createEditor('[video](https://example.com/video.mp4)');
|
|
|
|
expect(findMedia(editor)).toHaveLength(0);
|
|
expect(editor.dom.querySelectorAll('div.cm-md-media')).toHaveLength(0);
|
|
});
|
|
});
|