Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ dist

# Published package
*.tgz
.build_complete

# jest
/coverage


50 changes: 37 additions & 13 deletions __tests__/ExpensiMark-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,30 @@ test('Test text is unescaped', () => {
expect(parser.htmlToText(htmlString)).toBe(resultString);
});

describe('codeFence', () => {

test('Test standard code fence', () => {
const markdownString = '```\ncodeblock\nsecond line\n```';
const htmlString = '<pre>codeblock<br />second&#32;line<br /></pre>';
const rawInputHtmlString = '<pre>\ncodeblock\nsecond&#32;line\n</pre>';
expect(parser.replace(markdownString)).toBe(htmlString);
expect(parser.htmlToMarkdown(htmlString)).toBe(markdownString);
expect(parser.replace(markdownString, {shouldKeepRawInput: true})).toBe(rawInputHtmlString);
expect(parser.htmlToMarkdown(htmlString)).toBe(markdownString);
});

test('Test code fence with code variant', () => {
const markdownString = '\n```bash\ncodeblock\nsecond line\n```';
const htmlString = '<br /><pre data-info-string="bash">codeblock<br />second&#32;line<br /></pre>';
const rawInputHtmlString = '\n<pre data-info-string="bash">\ncodeblock\nsecond&#32;line\n</pre>';

expect(parser.replace(markdownString)).toBe(htmlString);
expect(parser.htmlToMarkdown(htmlString)).toBe(markdownString);
expect(parser.replace(markdownString, {shouldKeepRawInput: true})).toBe(rawInputHtmlString);
expect(parser.htmlToMarkdown(htmlString)).toBe(markdownString);
});
});

test('Test with regex Maximum regex stack depth reached error', () => {
const testString =
'<h1>heading</h1> asjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfidekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdicdjejajasjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfisjksksjsjssskssjskskssksksksksskdkddkddkdksskskdkdkdksskskskdkdkdkdkekeekdkddenejeodxkdndekkdjddkeemdjxkdenendkdjddekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdicdjejajasjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfidekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdi.cdjd';
Expand Down Expand Up @@ -56,47 +80,47 @@ describe('Test ExpensiMark getAttributeCache', () => {
test('If mediaAttributeCachingFn is provided, returns it', () => {
const extras = {
mediaAttributeCachingFn: jest.fn(),
}
};
expect(expensiMark.getAttributeCache(extras).attrCachingFn).toBe(extras.mediaAttributeCachingFn);
})
});

test('If mediaAttributeCachingFn is not provided, returns cacheVideoAttributes', () => {
const extras = {
cacheVideoAttributes: jest.fn(),
}
};
expect(expensiMark.getAttributeCache(extras).attrCachingFn).toBe(extras.cacheVideoAttributes);
})
});

test('If mediaAttributeCachingFn and cacheVideoAttributes are not provided, returns undefined', () => {
const extras = {}
const extras = {};
expect(expensiMark.getAttributeCache(extras).attrCachingFn).toBe(undefined);
})
});
});

describe('For attrCache', () => {
test('If mediaAttributeCache is provided, returns it', () => {
const extras = {
mediaAttributeCache: jest.fn(),
}
};
expect(expensiMark.getAttributeCache(extras).attrCache).toBe(extras.mediaAttributeCache);
})
});

test('If mediaAttributeCache is not provided, returns videoAttributeCache', () => {
const extras = {
videoAttributeCache: jest.fn(),
}
};
expect(expensiMark.getAttributeCache(extras).attrCache).toBe(extras.videoAttributeCache);
})
});

test('If mediaAttributeCache and videoAttributeCache are not provided, returns undefined', () => {
const extras = {}
const extras = {};
expect(expensiMark.getAttributeCache(extras).attrCache).toBe(undefined);
})
});
});

test('If no extras are undefined, returns undefined for both attrCachingFn and attrCache', () => {
const {attrCachingFn, attrCache} = expensiMark.getAttributeCache(undefined);
expect(attrCachingFn).toBe(undefined);
expect(attrCache).toBe(undefined);
})
});
});
16 changes: 9 additions & 7 deletions lib/ExpensiMark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,21 @@ export default class ExpensiMark {
name: 'codeFence',

// &#x60; is a backtick symbol we are matching on three of them before then after a new line character
regex: /(&#x60;&#x60;&#x60;.*?(\r\n|\n))((?:\s*?(?!(?:\r\n|\n)?&#x60;&#x60;&#x60;(?!&#x60;))[\S])+\s*?(?:\r\n|\n))(&#x60;&#x60;&#x60;)/g,
regex: /(?<![^^\r\n])(&#x60;&#x60;&#x60;)([^\s\r\n`]+)?((?:\r\n|\n))((?:\s*?(?!(?:\r\n|\n)?&#x60;&#x60;&#x60;(?!&#x60;))[\S])+\s*?(?:\r\n|\n))(&#x60;&#x60;&#x60;)/g,

// We're using a function here to perform an additional replace on the content
// inside the backticks because Android is not able to use <pre> tags and does
// not respect whitespace characters at all like HTML does. We do not want to mess
// with the new lines here since they need to be converted into <br>. And we don't
// want to do this anywhere else since that would break HTML.
// &nbsp; will create styling issues so use &#32;
replacement: (_extras, _match, _g1, _g2, textWithinFences) => {
replacement: (_extras, _match, _g1, code, _g2, textWithinFences) => {
const group = textWithinFences.replace(/(?:(?![\n\r])\s)/g, '&#32;');
return `<pre>${group}</pre>`;
return `<pre${code ? ` data-info-string="${code}"` : ''}>${group}</pre>`;
},
rawInputReplacement: (_extras, _match, _g1, newLineCharacter, textWithinFences) => {
rawInputReplacement: (_extras, _match, _g1, code, newLineCharacter, textWithinFences) => {
const group = textWithinFences.replace(/(?:(?![\n\r])\s)/g, '&#32;').replace(/<emoji>|<\/emoji>/g, '');
return `<pre>${newLineCharacter}${group}</pre>`;
return `<pre${code ? ` data-info-string="${code}"` : ''}>${newLineCharacter}${group}</pre>`;
},
},

Expand Down Expand Up @@ -691,8 +691,10 @@ export default class ExpensiMark {
},
{
name: 'codeFence',
regex: /<(pre)(?:"[^"]*"|'[^']*'|[^'">])*>([\s\S]*?)(\n?)<\/\1>(?![^<]*(<\/pre>|<\/code>))/gi,
replacement: (_extras, _match, _g1, g2) => `\`\`\`\n${g2}\n\`\`\``,
regex: /<(pre)(?:\s+[^>]*?\b(?:data-info-string)\s*=\s*["']?([^\s"'<>]+)["']?[^>]*)?\s*>([\s\S]*?)(\n?)<\/\1>(?![^<]*(<\/pre>|<\/code>))/gi,
replacement: (_extras, _match, _g1, code, g2) => {
return `\`\`\`${code ?? ''}\n${g2}\n\`\`\``;
},
},
{
name: 'anchor',
Expand Down
Loading
Loading