/
githubmirror
/
obsidian-importer
Обзор
Документация
Войти
/
githubmirror
/
obsidian-importer
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
tests/html/convert.test.ts
139 строк
5 KB
Steph Ango
Test the importers' conversions, and let them run without the dialog (#598)
05 авг 2026, 18:15
Не верифицирован
05 авг 2026, 18:15
2dbc429
Код
Авторство
О чём код?
/** * The HTML conversion, outside Obsidian. * * An HTML file goes in and markdown comes out. Fetching an attachment and * deciding where it lands is the importer's, passed in as a callback, so the * resolver here reads the ones sitting next to the fixture and skips anything * remote - the recordings are then the same with or without a network. * * The vault paths in the recordings come from the resolver below rather than * from a real vault, but they follow the same rule the vault does: a name * already taken gets a number. */ import '../shims/dom'; import { test } from 'node:test'; import assert from 'node:assert/strict'; import * as nodeFs from 'node:fs'; import * as nodePath from 'node:path'; import * as nodeUrl from 'node:url'; import { convertHtmlDocument, ResolvedAttachment } from '../../src/formats/html/convert'; import { expectedFor, expectFile, fixtures } from '../helpers'; const FIXTURES = __dirname; /** What the importer falls back to when the source has no usable extension. */ const FALLBACK_EXTENSION: Record<string, string> = { IMG: 'png', AUDIO: 'mp3', VIDEO: 'mp4' }; /** * Stands in for the importer's downloadAttachment: reads what is on disk, * refuses to leave the fixture directory, and names the file the way the vault * would. */ function resolver(baseDirUrl: string, folder: string) { const taken = new Set<string>(); return async (url: URL, el: HTMLElement): Promise<ResolvedAttachment | null> => { // Nothing remote, so a recording does not depend on a network. if (url.protocol !== 'file:') return null; if (!url.href.startsWith(baseDirUrl)) throw new Error('File path is outside the allowed directory'); const filepath = nodeUrl.fileURLToPath(url.href); // Reading it is the point: a reference to a file that is not there // should fail rather than be recorded as an embed. nodeFs.readFileSync(filepath); const name = nodePath.basename(filepath); const ext = nodePath.extname(name).slice(1); const basename = ext ? nodePath.basename(name, `.${ext}`) : name; const extension = ext || FALLBACK_EXTENSION[el.tagName]; if (!extension) return null; let candidate = `${basename}.${extension}`; for (let i = 1; taken.has(candidate); i++) candidate = `${basename} ${i}.${extension}`; taken.add(candidate); return { path: `${folder}/${candidate}`, name: candidate }; }; } const documents = fixtures(FIXTURES, '.html'); test('there are documents to convert', () => { assert.ok(documents.length > 0, 'expected at least one .html in tests/html'); }); for (const document of documents) { test(`converts ${document.name}`, async () => { const baseUrl = nodeUrl.pathToFileURL(document.path); const baseDirUrl = new URL('./', baseUrl.href).href; const skipped: string[] = []; const { markdown } = await convertHtmlDocument(nodeFs.readFileSync(document.path, 'utf8'), { baseUrl, resolveAttachment: resolver(baseDirUrl, 'Attachments'), onSkipped: src => skipped.push(src), onFailed: (src, e) => assert.fail(`${src}: ${e}`), }); expectFile(markdown, expectedFor(document, `${nodePath.basename(document.name, '.html')}.md`), document.name); // Everything skipped should be remote: a local attachment failing to // resolve would otherwise disappear from the recording unremarked. for (const src of skipped) { assert.ok(/^(https?:)?\/\//.test(src), `unexpectedly skipped ${src}`); } }); } test('resolves an attachment referred to twice only once', async () => { let calls = 0; const { markdown, attachments } = await convertHtmlDocument( '<p><img src="a.png"><img src="a.png"></p>', { baseUrl: new URL('file:///doc/index.html'), resolveAttachment: async () => { calls++; return { path: 'Attachments/a.png', name: 'a.png' }; }, }); assert.equal(calls, 1); assert.equal(attachments.size, 1); assert.equal(markdown.match(/Attachments\/a\.png/g)?.length, 2); }); test('leaves an inline data image alone', async () => { const { attachments } = await convertHtmlDocument( '<p><img src="data:image/png;base64,iVBORw0KGgo="></p>', { resolveAttachment: async () => assert.fail('should not resolve a data: url') }); assert.equal(attachments.size, 0); }); test('reports a source it could not resolve rather than throwing', async () => { const failed: string[] = []; await convertHtmlDocument('<p><img src="a.png"></p>', { baseUrl: new URL('file:///doc/index.html'), resolveAttachment: async () => { throw new Error('no'); }, onFailed: src => failed.push(src), }); assert.deepEqual(failed, ['a.png']); }); test('carries audio and video through as embeds', async () => { const { markdown } = await convertHtmlDocument( '<p><audio src="a.mp3"></audio><video src="b.mp4"></video></p>', { baseUrl: new URL('file:///doc/index.html'), resolveAttachment: async url => { const name = nodePath.basename(url.pathname); return { path: `Attachments/${name}`, name }; }, }); assert.match(markdown, /!\[\]\(Attachments\/a\.mp3\)/); assert.match(markdown, /!\[\]\(Attachments\/b\.mp4\)/); });