blob: a287659d5fca7a3157bd1714e566c14c35ae6564 [file] [log] [blame]
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import '../../../test/common-test-setup';
import {assert, fixture, html} from '@open-wc/testing';
import {changeModelToken} from '../../../models/change/change-model';
import {
ConfigModel,
configModelToken,
} from '../../../models/config/config-model';
import {wrapInProvider} from '../../../models/di-provider-element';
import {getAppContext} from '../../../services/app-context';
import './gr-formatted-text';
import {GrFormattedText} from './gr-formatted-text';
import {createComment, createConfig} from '../../../test/test-data-generators';
import {queryAndAssert, waitUntilObserved} from '../../../test/test-utils';
import {CommentLinks, EmailAddress} from '../../../api/rest-api';
import {testResolver} from '../../../test/common-test-setup';
import {GrAccountChip} from '../gr-account-chip/gr-account-chip';
import {
CommentModel,
commentModelToken,
} from '../gr-comment-model/gr-comment-model';
suite('gr-formatted-text tests', () => {
let element: GrFormattedText;
let configModel: ConfigModel;
async function setCommentLinks(commentlinks: CommentLinks) {
configModel.updateRepoConfig({...createConfig(), commentlinks});
await waitUntilObserved(
configModel.repoCommentLinks$,
links => links === commentlinks
);
}
setup(async () => {
configModel = new ConfigModel(
testResolver(changeModelToken),
getAppContext().restApiService
);
const commentModel = new CommentModel(getAppContext().restApiService);
commentModel.updateState({
comment: createComment(),
});
await setCommentLinks({
customLinkRewrite: {
match: '(LinkRewriteMe)',
link: 'http://google.com/$1',
},
complexLinkRewrite: {
match: '(^|\\s)A Link (\\d+)($|\\s)',
link: '/page?id=$2',
text: 'Link $2',
prefix: '$1A ',
suffix: '$3',
},
});
self.CANONICAL_PATH = 'http://localhost';
element = (
await fixture(
wrapInProvider(
wrapInProvider(
html`<gr-formatted-text></gr-formatted-text>`,
configModelToken,
configModel
),
commentModelToken,
commentModel
)
)
).querySelector('gr-formatted-text')!;
});
suite('as plaintext', () => {
setup(async () => {
element.markdown = false;
await element.updateComplete;
});
test('does not apply rewrites within links', async () => {
element.content = 'http://google.com/LinkRewriteMe';
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<pre class="plaintext">
<a
href="http://google.com/LinkRewriteMe"
rel="noopener noreferrer"
target="_blank"
>
http://google.com/LinkRewriteMe
</a>
</pre>
`
);
});
test('does not apply rewrites on rewritten text', async () => {
await setCommentLinks({
capitalizeFoo: {
match: 'foo',
prefix: 'FOO',
link: 'a.b.c',
},
lowercaseFoo: {
match: 'FOO',
prefix: 'foo',
link: 'c.d.e',
},
});
element.content = 'foo';
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<pre class="plaintext">
FOO<a href="a.b.c" rel="noopener noreferrer" target="_blank">foo</a>
</pre>
`
);
});
test('supports overlapping rewrites', async () => {
await setCommentLinks({
bracketNum: {
match: '(Start:) ([0-9]+)',
prefix: '$1 ',
link: 'bug/$2',
text: 'bug/$2',
},
bracketNum2: {
match: '(Start: [0-9]+) ([0-9]+)',
prefix: '$1 ',
link: 'bug/$2',
text: 'bug/$2',
},
});
element.content = 'Start: 123 456';
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<pre class="plaintext">
Start:
<a href="bug/123" rel="noopener noreferrer" target="_blank">
bug/123
</a>
<a href="bug/456" rel="noopener noreferrer" target="_blank">
bug/456
</a>
</pre>
`
);
});
test('renders text with links and rewrites', async () => {
element.content = `
text with plain link: http://google.com
text with config link: LinkRewriteMe
text with complex link: A Link 12`;
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<pre class="plaintext">
text with plain link:
<a
href="http://google.com"
rel="noopener noreferrer"
target="_blank"
>
http://google.com
</a>
text with config link:
<a
href="http://google.com/LinkRewriteMe"
rel="noopener noreferrer"
target="_blank"
>
LinkRewriteMe
</a>
text with complex link: A
<a
href="http://localhost/page?id=12"
rel="noopener noreferrer"
target="_blank"
>
Link 12
</a>
</pre>
`
);
});
test('does not render typed html', async () => {
element.content = 'plain text <div>foo</div>';
await element.updateComplete;
const escapedDiv = '&lt;div&gt;foo&lt;/div&gt;';
assert.shadowDom.equal(
element,
/* HTML */ `<pre class="plaintext">plain text ${escapedDiv}</pre>`
);
});
test('does not render markdown', async () => {
element.content = '# A Markdown Heading';
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ '<pre class="plaintext"># A Markdown Heading</pre>'
);
});
test('does default linking', async () => {
const checkLinking = async (url: string) => {
element.content = url;
await element.updateComplete;
const a = queryAndAssert<HTMLElement>(element, 'a');
assert.equal(a.getAttribute('href'), url);
assert.equal(a.innerText, url);
};
await checkLinking('http://www.google.com');
await checkLinking('https://www.google.com');
await checkLinking('https://www.google.com/');
await checkLinking('https://www.google.com/asdf~');
await checkLinking('https://www.google.com/asdf-');
});
});
suite('as markdown', () => {
setup(async () => {
element.markdown = true;
await element.updateComplete;
});
test('renders text with links and rewrites', async () => {
element.content = `text
\ntext with plain link: http://google.com
\ntext with config link: LinkRewriteMe
\ntext without a link: NotA Link 15 cats
\ntext with complex link: A Link 12`;
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<marked-element>
<div slot="markdown-html" class="markdown-html">
<p>text</p>
<p>
text with plain link:
<a
href="http://google.com"
rel="noopener noreferrer"
target="_blank"
>
http://google.com
</a>
</p>
<p>
text with config link:
<a
href="http://google.com/LinkRewriteMe"
rel="noopener noreferrer"
target="_blank"
>
LinkRewriteMe
</a>
</p>
<p>text without a link: NotA Link 15 cats</p>
<p>
text with complex link: A
<a
href="http://localhost/page?id=12"
rel="noopener noreferrer"
target="_blank"
>
Link 12
</a>
</p>
</div>
</marked-element>
`
);
});
test('does not render if too long', async () => {
element.content = `text
text with plain link: http://google.com
text with config link: LinkRewriteMe
text without a link: NotA Link 15 cats
text with complex link: A Link 12`;
element.MARKDOWN_LIMIT = 10;
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<pre class="plaintext">
text
text with plain link:
<a
href="http://google.com"
rel="noopener noreferrer"
target="_blank"
>
http://google.com
</a>
text with config link:
<a
href="http://google.com/LinkRewriteMe"
rel="noopener noreferrer"
target="_blank"
>
LinkRewriteMe
</a>
text without a link: NotA Link 15 cats
text with complex link: A
<a
href="http://localhost/page?id=12"
rel="noopener noreferrer"
target="_blank"
>
Link 12
</a>
</pre>
`
);
});
test('renders headings with links and rewrites', async () => {
element.content = `# h1-heading
\n## h2-heading
\n### h3-heading
\n#### h4-heading
\n##### h5-heading
\n###### h6-heading
\n# heading with plain link: http://google.com
\n# heading with config link: LinkRewriteMe`;
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<marked-element>
<div slot="markdown-html" class="markdown-html">
<h1>h1-heading</h1>
<h2>h2-heading</h2>
<h3>h3-heading</h3>
<h4>h4-heading</h4>
<h5>h5-heading</h5>
<h6>h6-heading</h6>
<h1>
heading with plain link:
<a
href="http://google.com"
rel="noopener noreferrer"
target="_blank"
>
http://google.com
</a>
</h1>
<h1>
heading with config link:
<a
href="http://google.com/LinkRewriteMe"
rel="noopener noreferrer"
target="_blank"
>
LinkRewriteMe
</a>
</h1>
</div>
</marked-element>
`
);
});
test('renders inline-code without linking or rewriting', async () => {
element.content = `\`inline code\`
\n\`inline code with plain link: google.com\`
\n\`inline code with config link: LinkRewriteMe\``;
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<marked-element>
<div slot="markdown-html" class="markdown-html">
<p>
<code>inline code</code>
</p>
<p>
<code>inline code with plain link: google.com</code>
</p>
<p>
<code>inline code with config link: LinkRewriteMe</code>
</p>
</div>
</marked-element>
`
);
});
test('renders multiline-code without linking or rewriting', async () => {
element.content = `\`\`\`\nmultiline code\n\`\`\`
\n\`\`\`\nmultiline code with plain link: google.com\n\`\`\`
\n\`\`\`\nmultiline code with config link: LinkRewriteMe\n\`\`\``;
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<marked-element>
<div slot="markdown-html" class="markdown-html">
<pre>
<code>multiline code</code>
</pre>
<pre>
<code>multiline code with plain link: google.com</code>
</pre>
<pre>
<code>multiline code with config link: LinkRewriteMe</code>
</pre>
</div>
</marked-element>
`
);
});
test('does not render inline images into <img> tags', async () => {
element.content = '![img](google.com/img.png)';
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<marked-element>
<div slot="markdown-html" class="markdown-html">
<p>![img](google.com/img.png)</p>
</div>
</marked-element>
`
);
});
test('handles @mentions', async () => {
element.content = '@someone@google.com';
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<marked-element>
<div slot="markdown-html" class="markdown-html">
<p>
<gr-account-chip></gr-account-chip>
</p>
</div>
</marked-element>
`
);
const accountChip = queryAndAssert<GrAccountChip>(
element,
'gr-account-chip'
);
assert.equal(
accountChip.account?.email,
'someone@google.com' as EmailAddress
);
});
test('does not handle @mentions that is part of a code block', async () => {
element.content = '`@`someone@google.com';
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<marked-element>
<div slot="markdown-html" class="markdown-html">
<p>
<code>@</code>
<a
href="mailto:someone@google.com"
rel="noopener noreferrer"
target="_blank"
>
someone@google.com
</a>
</p>
</div>
</marked-element>
`
);
});
test('renders inline links into <a> tags', async () => {
const origin = window.location.origin;
element.content = `[myLink1](https://www.google.com)
[myLink2](/destiny)
[myLink3](${origin}/destiny)
`;
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<marked-element>
<div slot="markdown-html" class="markdown-html">
<p>
<a
href="https://www.google.com"
rel="noopener noreferrer"
target="_blank"
>myLink1</a
>
<br />
<a href="/destiny">myLink2</a>
<br />
<a href="${origin}/destiny">myLink3</a>
</p>
</div>
</marked-element>
`
);
});
test('renders block quotes with links and rewrites', async () => {
element.content = `> block quote
\n> block quote with plain link: http://google.com
\n> block quote with config link: LinkRewriteMe`;
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<marked-element>
<div slot="markdown-html" class="markdown-html">
<blockquote>
<p>block quote</p>
</blockquote>
<blockquote>
<p>
block quote with plain link:
<a
href="http://google.com"
rel="noopener noreferrer"
target="_blank"
>
http://google.com
</a>
</p>
</blockquote>
<blockquote>
<p>
block quote with config link:
<a
href="http://google.com/LinkRewriteMe"
rel="noopener noreferrer"
target="_blank"
>
LinkRewriteMe
</a>
</p>
</blockquote>
</div>
</marked-element>
`
);
});
test('never renders typed html', async () => {
element.content = `plain text <div>foo</div>
\n\`inline code <div>foo</div>\`
\n\`\`\`\nmultiline code <div>foo</div>\`\`\`
\n> block quote <div>foo</div>
\n[inline link <div>foo</div>](http://google.com)`;
await element.updateComplete;
const escapedDiv = '&lt;div&gt;foo&lt;/div&gt;';
assert.shadowDom.equal(
element,
/* HTML */ `
<marked-element>
<div slot="markdown-html" class="markdown-html">
<p>plain text ${escapedDiv}</p>
<p>
<code>inline code ${escapedDiv}</code>
</p>
<pre>
<code>
multiline code ${escapedDiv}
</code>
</pre>
<blockquote>
<p>block quote ${escapedDiv}</p>
</blockquote>
<p>
<a
href="http://google.com"
rel="noopener noreferrer"
target="_blank"
>inline link ${escapedDiv}</a
>
</p>
</div>
</marked-element>
`
);
});
test('renders nested block quotes', async () => {
element.content = '> > > block quote';
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<marked-element>
<div slot="markdown-html" class="markdown-html">
<blockquote>
<blockquote>
<blockquote>
<p>block quote</p>
</blockquote>
</blockquote>
</blockquote>
</div>
</marked-element>
`
);
});
test('renders rewrites with an asterisk', async () => {
await setCommentLinks({
customLinkRewrite: {
match: 'asterisks (\\*) rule',
link: 'http://google.com',
},
});
element.content = 'I think asterisks * rule';
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `
<marked-element>
<div slot="markdown-html" class="markdown-html">
<p>
I think
<a
href="http://google.com"
rel="noopener noreferrer"
target="_blank"
>asterisks * rule</a
>
</p>
</div>
</marked-element>
`
);
});
test('does default linking', async () => {
const checkLinking = async (url: string) => {
element.content = url;
await element.updateComplete;
const a = queryAndAssert<HTMLElement>(element, 'a');
const p = queryAndAssert<HTMLElement>(element, 'p');
assert.equal(a.getAttribute('href'), url);
assert.equal(p.innerText, url);
};
await checkLinking('http://www.google.com');
await checkLinking('https://www.google.com');
await checkLinking('https://www.google.com/');
});
suite('user suggest fix', () => {
setup(async () => {
const flagsService = getAppContext().flagsService;
sinon.stub(flagsService, 'isEnabled').returns(true);
});
test('renders', async () => {
element.content = '```suggestion\nHello World```';
await element.updateComplete;
assert.shadowDom.equal(
element,
/* HTML */ `<marked-element>
<div class="markdown-html" slot="markdown-html">
<gr-user-suggestion-fix>Hello World</gr-user-suggestion-fix>
</div>
</marked-element>`
);
});
});
});
});