blob: fcb2af21e00c99545e253f836e7d5f75bdc69dec [file] [log] [blame]
import sinon from 'sinon';
import {assert} from 'chai';
import {autolink} from './autolink.js';
import {prpcClient} from 'prpc-client-instance.js';
const components = autolink.Components;
const markupAutolinks = autolink.markupAutolinks;
describe('autolink', () => {
describe('crbug component functions', () => {
const {extractRefs, refRegs, replacer} = components.get('01-tracker-crbug');
it('Extract crbug project and local ids', () => {
const match = refRegs[0].exec('https://crbug.com/monorail/1234');
refRegs[0].lastIndex = 0;
const ref = extractRefs(match);
assert.deepEqual(ref, [{projectName: 'monorail', localId: '1234'}]);
});
it('Extract crbug default project name', () => {
const match = refRegs[0].exec('http://crbug.com/1234');
refRegs[0].lastIndex = 0;
const ref = extractRefs(match);
assert.deepEqual(ref, [{projectName: 'chromium', localId: '1234'}]);
});
it('Extract crbug passed project name is ignored', () => {
const match = refRegs[0].exec('https://crbug.com/1234');
refRegs[0].lastIndex = 0;
const ref = extractRefs(match, 'foo');
assert.deepEqual(ref, [{projectName: 'chromium', localId: '1234'}]);
});
it('Replace crbug with found components', () => {
const str = 'crbug.com/monorail/1234';
const match = refRegs[0].exec(str);
refRegs[0].lastIndex = 0;
const components = {
closedRefs: [
{summary: 'Issue summary', localId: 1234, projectName: 'monorail'},
{},
]};
const actualRun = replacer(match, components);
assert.deepEqual(
actualRun,
[{
tag: 'a',
css: 'strike-through',
href: '/p/monorail/issues/detail?id=1234',
title: 'Issue summary',
content: str,
}],
);
});
it('Replace crbug with found components, with comment', () => {
const str = 'crbug.com/monorail/1234#c1';
const match = refRegs[0].exec(str);
refRegs[0].lastIndex = 0;
const components = {
closedRefs: [
{summary: 'Issue summary', localId: 1234, projectName: 'monorail'},
{},
]};
const actualRun = replacer(match, components);
assert.deepEqual(
actualRun,
[{
tag: 'a',
css: 'strike-through',
href: '/p/monorail/issues/detail?id=1234#c1',
title: 'Issue summary',
content: str,
}],
);
});
it('Replace crbug with default project_name', () => {
const str = 'crbug.com/1234';
const match = refRegs[0].exec(str);
refRegs[0].lastIndex = 0;
const components = {
openRefs: [
{localId: 134},
{summary: 'Issue 1234', localId: 1234, projectName: 'chromium'},
],
};
const actualRun = replacer(match, components);
assert.deepEqual(
actualRun,
[{
tag: 'a',
href: '/p/chromium/issues/detail?id=1234',
css: '',
title: 'Issue 1234',
content: str,
}],
);
});
it('Replace crbug incomplete responses', () => {
const str = 'crbug.com/1234';
const match = refRegs[0].exec(str);
refRegs[0].lastIndex = 0;
const components = {
openRefs: [{localId: 1234}, {projectName: 'chromium'}],
closedRefs: [{localId: 1234}, {projectName: 'chromium'}],
};
const actualRun = replacer(match, components);
assert.deepEqual(actualRun, [{content: str}]);
});
it('Replace crbug passed project name is ignored', () => {
const str = 'crbug.com/1234';
const match = refRegs[0].exec(str);
refRegs[0].lastIndex = 0;
const components = {
openRefs: [
{localId: 134},
{summary: 'Issue 1234', localId: 1234, projectName: 'chromium'},
],
};
const actualRun = replacer(match, components, 'foo');
assert.deepEqual(
actualRun,
[{
tag: 'a',
href: '/p/chromium/issues/detail?id=1234',
css: '',
title: 'Issue 1234',
content: str,
}],
);
});
it('Replace crbug with no found components', () => {
const str = 'crbug.com/1234';
const match = refRegs[0].exec(str);
refRegs[0].lastIndex = 0;
const components = {};
const actualRun = replacer(match, components);
assert.deepEqual(actualRun, [{content: str}]);
});
it('Replace crbug with no issue summary', () => {
const str = 'crbug.com/monorail/1234';
const match = refRegs[0].exec(str);
refRegs[0].lastIndex = 0;
const components = {
closedRefs: [
{localId: 1234, projectName: 'monorail'},
{},
]};
const actualRun = replacer(match, components);
assert.deepEqual(
actualRun,
[{
tag: 'a',
css: 'strike-through',
href: '/p/monorail/issues/detail?id=1234',
title: '',
content: str,
}],
);
});
});
describe('regular tracker component functions', () => {
const {extractRefs, refRegs, replacer} =
components.get('04-tracker-regular');
const str = 'bugs=123, monorail:234 or #345 and PROJ:#456';
const match = refRegs[0].exec(str);
refRegs[0].lastIndex = 0;
it('Extract tracker projects and local ids', () => {
const actualRefs = extractRefs(match, 'foo-project');
assert.deepEqual(
actualRefs,
[{projectName: 'foo-project', localId: '123'},
{projectName: 'monorail', localId: '234'},
{projectName: 'monorail', localId: '345'},
{projectName: 'PROJ', localId: '456'}]);
});
it('Replace tracker refs.', () => {
const components = {
openRefs: [
{summary: 'sum', projectName: 'monorail', localId: 888},
{summary: 'ma', projectName: 'chromium', localId: '123'},
],
closedRefs: [
{summary: 'ry', projectName: 'proj', localId: 456},
],
};
const actualTextRuns = replacer(match, components, 'chromium');
assert.deepEqual(
actualTextRuns,
[
{content: 'bugs='},
{
tag: 'a',
href: '/p/chromium/issues/detail?id=123',
css: '',
title: 'ma',
content: '123',
},
{content: ', '},
{content: 'monorail:234'},
{content: ' or '},
{content: '#345'},
{content: ' and '},
{
tag: 'a',
href: '/p/PROJ/issues/detail?id=456',
css: 'strike-through',
title: 'ry',
content: 'PROJ:#456',
},
],
);
});
it('Replace tracker refs mixed case refs.', () => {
const components = {
openRefs: [
{projectName: 'mOnOrAIl', localId: 234},
],
closedRefs: [
{projectName: 'LeMuR', localId: 123},
],
};
const actualTextRuns = replacer(match, components, 'lEmUr');
assert.deepEqual(
actualTextRuns,
[
{content: 'bugs='},
{
tag: 'a',
href: '/p/lEmUr/issues/detail?id=123',
css: 'strike-through',
title: '',
content: '123',
},
{content: ', '},
{
tag: 'a',
href: '/p/monorail/issues/detail?id=234',
css: '',
title: '',
content: 'monorail:234',
},
{content: ' or '},
{content: '#345'},
{content: ' and '},
{content: 'PROJ:#456'},
],
);
});
it('Recognizes Fixed: syntax', () => {
const str = 'Fixed : 123, proj:456';
const match = refRegs[0].exec(str);
refRegs[0].lastIndex = 0;
const components = {
openRefs: [
{summary: 'summ', projectName: 'chromium', localId: 123},
],
closedRefs: [
{summary: 'ary', projectName: 'proj', localId: 456},
],
};
const actualTextRuns = replacer(match, components, 'chromium');
assert.deepEqual(
actualTextRuns,
[
{
tag: 'a',
href: '/p/chromium/issues/detail?id=123',
css: '',
title: 'summ',
content: 'Fixed : 123',
},
{content: ', '},
{
tag: 'a',
href: '/p/proj/issues/detail?id=456',
css: 'strike-through',
title: 'ary',
content: 'proj:456',
},
],
);
});
});
describe('user email component functions', () => {
const {extractRefs, refRegs, replacer} = components.get('03-user-emails');
const str = 'We should ask User1@gmail.com to confirm.';
const match = refRegs[0].exec(str);
refRegs[0].lastIndex = 0;
it('Extract user email', () => {
const actualEmail = extractRefs(match, 'unusedProjectName');
assert.equal('User1@gmail.com', actualEmail);
});
it('Replace existing user.', () => {
const components = {
users: [{displayName: 'user2@gmail.com'},
{displayName: 'user1@gmail.com'}]};
const actualTextRun = replacer(match, components);
assert.deepEqual(
actualTextRun,
[{tag: 'a', href: '/u/User1@gmail.com', content: 'User1@gmail.com'}],
);
});
it('Replace non-existent user.', () => {
const actualTextRun = replacer(match, {});
assert.deepEqual(
actualTextRun,
[{
tag: 'a',
href: 'mailto:User1@gmail.com',
content: 'User1@gmail.com',
}],
);
});
});
describe('full url component functions.', () => {
const {refRegs, replacer} = components.get('02-full-urls');
it('test full link regex string', () => {
const isLinkRE = refRegs[0];
const str =
'https://www.go.com ' +
'nospacehttps://www.blah.com http://website.net/other="(}])"><)';
let match;
const actualMatches = [];
while ((match = isLinkRE.exec(str)) !== null) {
actualMatches.push(match[0]);
}
assert.deepEqual(
actualMatches,
['https://www.go.com', 'http://website.net/other="(}])">']);
});
it('Replace URL existing http', () => {
const match = refRegs[0].exec('link here: (https://website.net/other="here").');
refRegs[0].lastIndex = 0;
const actualTextRuns = replacer(match);
assert.deepEqual(
actualTextRuns,
[{tag: 'a',
href: 'https://website.net/other="here"',
content: 'https://website.net/other="here"',
},
{content: ').'}],
);
});
it('Replace URL with short-link as substring', () => {
const match = refRegs[0].exec('https://website.net/who/me/yes/you');
refRegs[0].lastIndex = 0;
const actualTextRuns = replacer(match);
assert.deepEqual(
actualTextRuns,
[{tag: 'a',
href: 'https://website.net/who/me/yes/you',
content: 'https://website.net/who/me/yes/you',
}],
);
});
it('Replace URL with email as substring', () => {
const match = refRegs[0].exec('https://website.net/who/foo@example.com');
refRegs[0].lastIndex = 0;
const actualTextRuns = replacer(match);
assert.deepEqual(
actualTextRuns,
[{tag: 'a',
href: 'https://website.net/who/foo@example.com',
content: 'https://website.net/who/foo@example.com',
}],
);
});
});
describe('shorthand url component functions.', () => {
const {refRegs, replacer} = components.get('05-linkify-shorthand');
it('Short link does not match URL with short-link as substring', () => {
refRegs[0].lastIndex = 0;
assert.isNull(refRegs[0].exec('https://website.net/who/me/yes/you'));
});
it('test short link regex string', () => {
const shortLinkRE = refRegs[0];
const str =
'go/shortlinks ./_go/shortlinks bo/short bo/1234 ' +
'https://who/shortlinks go/hey/?wct=(go)';
let match;
const actualMatches = [];
while ((match = shortLinkRE.exec(str)) !== null) {
actualMatches.push(match[0]);
}
assert.deepEqual(
actualMatches,
['go/shortlinks', ' https://who/shortlinks', ' go/hey/?wct=(go)'],
);
});
it('test numeric short link regex string', () => {
const shortNumLinkRE = refRegs[1];
const str = 'go/nono omg/ohno omg/123 .cl/123 b/1234';
let match;
const actualMatches = [];
while ((match = shortNumLinkRE.exec(str)) !== null) {
actualMatches.push(match[0]);
}
assert.deepEqual(actualMatches, [' omg/123', ' b/1234']);
});
it('test fuchsia short links', () => {
const shortNumLinkRE = refRegs[1];
const str = 'ignore fxr/123 fxrev/789 fxb/456 tqr/123 ';
let match;
const actualMatches = [];
while ((match = shortNumLinkRE.exec(str)) !== null) {
actualMatches.push(match[0]);
}
assert.deepEqual(actualMatches, [' fxr/123', ' fxrev/789', ' fxb/456',
' tqr/123']);
});
it('test implied link regex string', () => {
const impliedLinkRE = refRegs[2];
const str = 'incomplete.com .help.com hey.net/other="(blah)"';
let match;
const actualMatches = [];
while ((match = impliedLinkRE.exec(str)) !== null) {
actualMatches.push(match[0]);
}
assert.deepEqual(
actualMatches, ['incomplete.com', ' hey.net/other="(blah)"']);
});
it('test implied link alternate domains', () => {
const impliedLinkRE = refRegs[2];
const str = 'what.net hey.edu google.org fuchsia.dev ignored.domain';
let match;
const actualMatches = [];
while ((match = impliedLinkRE.exec(str)) !== null) {
actualMatches.push(match[0]);
}
assert.deepEqual(
actualMatches, ['what.net', ' hey.edu', ' google.org',
' fuchsia.dev']);
});
it('Replace URL plain text', () => {
const match = refRegs[2].exec('link here: (website.net/other="here").');
refRegs[2].lastIndex = 0;
const actualTextRuns = replacer(match);
assert.deepEqual(
actualTextRuns,
[{content: '('},
{tag: 'a',
href: 'https://website.net/other="here"',
content: 'website.net/other="here"',
},
{content: ').'}],
);
});
it('Replace short link existing http', () => {
const match = refRegs[0].exec('link here: (http://who/me).');
refRegs[0].lastIndex = 0;
const actualTextRuns = replacer(match);
assert.deepEqual(
actualTextRuns,
[{content: '('},
{tag: 'a',
href: 'http://who/me',
content: 'http://who/me',
},
{content: ').'}],
);
});
it('Replace short-link plain text', () => {
const match = refRegs[0].exec('link here: (who/me).');
refRegs[0].lastIndex = 0;
const actualTextRuns = replacer(match);
assert.deepEqual(
actualTextRuns,
[{content: '('},
{tag: 'a',
href: 'http://who/me',
content: 'who/me',
},
{content: ').'}],
);
});
it('Replace short-link plain text initial characters', () => {
const match = refRegs[0].exec('link here: who/me');
refRegs[0].lastIndex = 0;
const actualTextRuns = replacer(match);
assert.deepEqual(
actualTextRuns,
[{content: ' '},
{tag: 'a',
href: 'http://who/me',
content: 'who/me',
}],
);
});
it('Replace URL short link', () => {
['go', 'g', 'shortn', 'who', 'teams'].forEach((prefix) => {
const match = refRegs[0].exec(`link here: (${prefix}/abcd).`);
refRegs[0].lastIndex = 0;
const actualTextRuns = replacer(match);
assert.deepEqual(
actualTextRuns,
[{content: '('},
{tag: 'a',
href: `http://${prefix}/abcd`,
content: `${prefix}/abcd`,
},
{content: ').'}],
);
});
});
it('Replace URL numeric short link', () => {
['b', 't', 'o', 'omg', 'cl', 'cr'].forEach((prefix) => {
const match = refRegs[1].exec(`link here: (${prefix}/1234).`);
refRegs[1].lastIndex = 0;
const actualTextRuns = replacer(match);
assert.deepEqual(
actualTextRuns,
[{content: '('},
{tag: 'a',
href: `http://${prefix}/1234`,
content: `${prefix}/1234`,
}],
);
});
});
});
describe('versioncontrol component functions.', () => {
const {refRegs, replacer} = components.get('06-versioncontrol');
it('test git hash regex', () => {
const gitHashRE = refRegs[0];
const str =
'r63b72a71d5fbce6739c51c3846dd94bd62b91091 blha blah ' +
'Revision 63b72a71d5fbce6739c51c3846dd94bd62b91091 blah balh ' +
'63b72a71d5fbce6739c51c3846dd94bd62b91091 ' +
'Revision63b72a71d5fbce6739c51c3846dd94bd62b91091';
let match;
const actualMatches = [];
while ((match = gitHashRE.exec(str)) !== null) {
actualMatches.push(match[0]);
}
assert.deepEqual(
actualMatches, [
'r63b72a71d5fbce6739c51c3846dd94bd62b91091',
'Revision 63b72a71d5fbce6739c51c3846dd94bd62b91091',
'63b72a71d5fbce6739c51c3846dd94bd62b91091',
]);
});
it('test svn regex', () => {
const svnRE = refRegs[1];
const str =
'r1234 blah blah ' +
'Revision 123456 blah balh ' +
'r12345678' +
'1234';
let match;
const actualMatches = [];
while ((match = svnRE.exec(str)) !== null) {
actualMatches.push(match[0]);
}
assert.deepEqual(
actualMatches, [
'r1234',
'Revision 123456',
]);
});
it('replace revision refs plain text', () => {
const str = 'r63b72a71d5fbce6739c51c3846dd94bd62b91091';
const match = refRegs[0].exec(str);
const actualTextRuns = replacer(
match, null, null, 'https://crrev.com/{revnum}');
refRegs[0].lastIndex = 0;
assert.deepEqual(
actualTextRuns,
[{
content: 'r63b72a71d5fbce6739c51c3846dd94bd62b91091',
tag: 'a',
href: 'https://crrev.com/63b72a71d5fbce6739c51c3846dd94bd62b91091',
}]);
});
it('replace revision refs plain text different template', () => {
const str = 'r63b72a71d5fbce6739c51c3846dd94bd62b91091';
const match = refRegs[0].exec(str);
const actualTextRuns = replacer(
match, null, null, 'https://foo.bar/{revnum}/baz');
refRegs[0].lastIndex = 0;
assert.deepEqual(
actualTextRuns,
[{
content: 'r63b72a71d5fbce6739c51c3846dd94bd62b91091',
tag: 'a',
href: 'https://foo.bar/63b72a71d5fbce6739c51c3846dd94bd62b91091/baz',
}]);
});
});
describe('markupAutolinks tests', () => {
const componentRefs = new Map();
componentRefs.set('01-tracker-crbug', {
openRefs: [],
closedRefs: [{projectName: 'chromium', localId: 99}],
});
componentRefs.set('04-tracker-regular', {
openRefs: [{summary: 'monorail', projectName: 'monorail', localId: 123}],
closedRefs: [{projectName: 'chromium', localId: 456}],
});
componentRefs.set('03-user-emails', {
users: [{displayName: 'user2@example.com'}],
});
it('empty string does not cause error', () => {
const actualTextRuns = markupAutolinks('', componentRefs);
assert.deepEqual(actualTextRuns, []);
});
it('no nested autolinking', () => {
const plainString = 'test <b>autolinking go/testlink</b> is not nested';
const actualTextRuns = markupAutolinks(plainString, componentRefs);
assert.deepEqual(
actualTextRuns, [
{content: 'test '},
{content: 'autolinking go/testlink', tag: 'b'},
{content: ' is not nested'},
]);
});
it('URLs are autolinked', () => {
const plainString = 'this http string contains http://google.com for you';
const actualTextRuns = markupAutolinks(plainString, componentRefs);
assert.deepEqual(
actualTextRuns, [
{content: 'this http string contains '},
{content: 'http://google.com', tag: 'a', href: 'http://google.com'},
{content: ' for you'},
]);
});
it('different component types are correctly linked', () => {
const plainString = 'test (User2@example.com and crbug.com/99) get link';
const actualTextRuns = markupAutolinks(plainString, componentRefs);
assert.deepEqual(
actualTextRuns,
[
{
content: 'test (',
},
{
content: 'User2@example.com',
tag: 'a',
href: '/u/User2@example.com',
},
{
content: ' and ',
},
{
content: 'crbug.com/99',
tag: 'a',
href: '/p/chromium/issues/detail?id=99',
title: '',
css: 'strike-through',
},
{
content: ') get link',
},
],
);
});
it('Invalid issue refs do not get linked', () => {
const plainString =
'bug123, bug 123a, bug-123 and https://bug:123.example.com ' +
'do not get linked.';
const actualTextRuns= markupAutolinks(
plainString, componentRefs, 'chromium');
assert.deepEqual(
actualTextRuns,
[
{
content: 'bug123, bug 123a, bug-123 and ',
},
{
content: 'https://bug:123.example.com',
tag: 'a',
href: 'https://bug:123.example.com',
},
{
content: ' do not get linked.',
},
]);
});
it('Only existing issues get linked', () => {
const plainString =
'only existing bugs = 456, monorail:123, 234 and chromium:345 get ' +
'linked';
const actualTextRuns = markupAutolinks(
plainString, componentRefs, 'chromium');
assert.deepEqual(
actualTextRuns,
[
{
content: 'only existing ',
},
{
content: 'bugs = ',
},
{
content: '456',
tag: 'a',
href: '/p/chromium/issues/detail?id=456',
title: '',
css: 'strike-through',
},
{
content: ', ',
},
{
content: 'monorail:123',
tag: 'a',
href: '/p/monorail/issues/detail?id=123',
title: 'monorail',
css: '',
},
{
content: ', ',
},
{
content: '234',
},
{
content: ' and ',
},
{
content: 'chromium:345',
},
{
content: ' ',
},
{
content: 'get linked',
},
],
);
});
it('multilined bolds are not bolded', () => {
const plainString =
'<b>no multiline bolding \n' +
'not allowed go/survey is still linked</b>';
const actualTextRuns = markupAutolinks(plainString, componentRefs);
assert.deepEqual(
actualTextRuns, [
{content: '<b>no multiline bolding '},
{tag: 'br'},
{content: 'not allowed'},
{content: ' '},
{content: 'go/survey', tag: 'a', href: 'http://go/survey'},
{content: ' is still linked</b>'},
]);
const plainString2 =
'<b>no multiline bold \rwith carriage \r\nreturns</b>';
const actualTextRuns2 = markupAutolinks(plainString2, componentRefs);
assert.deepEqual(
actualTextRuns2, [
{content: '<b>no multiline bold '},
{tag: 'br'},
{content: 'with carriage '},
{tag: 'br'},
{content: 'returns</b>'},
]);
});
// Check that comment references are properly linked.
it('comments are correctly linked', () => {
const plainString =
'comment1, comment : 5, Comment =10, comment #4, #c57';
const actualTextRuns = markupAutolinks(plainString, componentRefs);
assert.deepEqual(
actualTextRuns, [
{
content: 'comment1',
tag: 'a',
href: '#c1',
},
{
content: ', ',
},
{
content: 'comment : 5',
tag: 'a',
href: '#c5',
},
{
content: ', ',
},
{
content: 'Comment =10',
tag: 'a',
href: '#c10',
},
{
content: ', ',
},
{
content: 'comment #4',
tag: 'a',
href: '#c4',
},
{
content: ', ',
},
{
content: '#c57',
tag: 'a',
href: '#c57',
},
],
);
});
// Check that improperly formatted comment references do not get linked.
it('comments that should not be linked', () => {
const plainString =
'comment number 4, comment-4, comment= # 5, comment#c56';
const actualTextRuns = markupAutolinks(plainString, componentRefs);
assert.deepEqual(
actualTextRuns, [
{
content: 'comment number 4, comment-4, comment= # 5, comment#c56',
},
],
);
});
// Check that issue/comment references are properly linked.
it('issue/comment that should be linked', () => {
const plainString =
'issue 2 comment 3, issue2 comment 9, bug #3 comment=4';
const actualTextRuns = markupAutolinks(plainString, componentRefs);
assert.deepEqual(
actualTextRuns, [
{
content: 'issue 2 comment 3',
tag: 'a',
href: '?id=2#c3',
},
{
content: ', ',
},
{
content: 'issue2 comment 9',
tag: 'a',
href: '?id=2#c9',
},
{
content: ', ',
},
{
content: 'bug #3 comment=4',
tag: 'a',
href: '?id=3#c4',
},
],
);
});
// Check that improperly formatted issue/comment references do not get linked.
it('issue/comment that should not be linked', () => {
const plainString =
'theissue 2comment 3, issue2comment 9';
const actualTextRuns = markupAutolinks(plainString, componentRefs);
assert.deepEqual(
actualTextRuns, [
{
content: 'theissue 2comment 3, issue2comment 9',
},
],
);
});
});
describe('getReferencedArtifacts', () => {
beforeEach(() => {
sinon.stub(prpcClient, 'call').returns(Promise.resolve({}));
});
afterEach(() => {
prpcClient.call.restore();
});
it('filters invalid issue refs', async () => {
const comments = [
{
content: 'issue 0 issue 1 Bug: chromium:3 Bug: chromium:0',
},
];
autolink.getReferencedArtifacts(comments, 'proj');
assert.isTrue(prpcClient.call.calledWith(
'monorail.Issues',
'ListReferencedIssues',
{
issueRefs: [
{projectName: 'proj', localId: '1'},
{projectName: 'chromium', localId: '3'},
],
},
));
});
});
});