2017-02-03 04:58:45 -05:00
|
|
|
|
/* eslint no-param-reassign: "off" */
|
|
|
|
|
|
2018-03-09 15:18:59 -05:00
|
|
|
|
import $ from 'jquery';
|
2020-10-13 05:08:27 -04:00
|
|
|
|
import { emojiFixtureMap, initEmojiMock, describeEmojiFields } from 'helpers/emoji';
|
2020-09-10 02:08:37 -04:00
|
|
|
|
import '~/lib/utils/jquery_at_who';
|
2019-12-05 13:07:51 -05:00
|
|
|
|
import GfmAutoComplete, { membersBeforeSave } from 'ee_else_ce/gfm_auto_complete';
|
2017-05-16 14:42:06 -04:00
|
|
|
|
|
2019-02-27 04:22:04 -05:00
|
|
|
|
import { TEST_HOST } from 'helpers/test_constants';
|
2019-03-11 09:47:36 -04:00
|
|
|
|
import { getJSONFixture } from 'helpers/fixtures';
|
2019-02-27 04:22:04 -05:00
|
|
|
|
|
2020-11-17 16:09:19 -05:00
|
|
|
|
import waitForPromises from 'jest/helpers/wait_for_promises';
|
|
|
|
|
|
|
|
|
|
import MockAdapter from 'axios-mock-adapter';
|
|
|
|
|
import AjaxCache from '~/lib/utils/ajax_cache';
|
|
|
|
|
import axios from '~/lib/utils/axios_utils';
|
|
|
|
|
|
2019-03-11 09:47:36 -04:00
|
|
|
|
const labelsFixture = getJSONFixture('autocomplete_sources/labels.json');
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
describe('GfmAutoComplete', () => {
|
2020-11-17 16:09:19 -05:00
|
|
|
|
const fetchDataMock = { fetchData: jest.fn() };
|
|
|
|
|
let gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call(fetchDataMock);
|
2017-05-16 14:42:06 -04:00
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
let atwhoInstance;
|
|
|
|
|
let sorterValue;
|
2020-11-17 16:09:19 -05:00
|
|
|
|
let filterValue;
|
|
|
|
|
|
|
|
|
|
describe('DefaultOptions.filter', () => {
|
|
|
|
|
let items;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
jest.spyOn(fetchDataMock, 'fetchData');
|
|
|
|
|
jest.spyOn($.fn.atwho.default.callbacks, 'filter').mockImplementation(() => {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('assets loading', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
atwhoInstance = { setting: {}, $inputor: 'inputor', at: '[vulnerability:' };
|
|
|
|
|
items = ['loading'];
|
|
|
|
|
|
|
|
|
|
filterValue = gfmAutoCompleteCallbacks.filter.call(atwhoInstance, '', items);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should call the fetchData function without query', () => {
|
|
|
|
|
expect(fetchDataMock.fetchData).toHaveBeenCalledWith('inputor', '[vulnerability:');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not call the default atwho filter', () => {
|
|
|
|
|
expect($.fn.atwho.default.callbacks.filter).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return the passed unfiltered items', () => {
|
|
|
|
|
expect(filterValue).toEqual(items);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('backend filtering', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
atwhoInstance = { setting: {}, $inputor: 'inputor', at: '[vulnerability:' };
|
|
|
|
|
items = [];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('when previous query is different from current one', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({
|
|
|
|
|
previousQuery: 'oldquery',
|
|
|
|
|
...fetchDataMock,
|
|
|
|
|
});
|
|
|
|
|
filterValue = gfmAutoCompleteCallbacks.filter.call(atwhoInstance, 'newquery', items);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should call the fetchData function with query', () => {
|
|
|
|
|
expect(fetchDataMock.fetchData).toHaveBeenCalledWith(
|
|
|
|
|
'inputor',
|
|
|
|
|
'[vulnerability:',
|
|
|
|
|
'newquery',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not call the default atwho filter', () => {
|
|
|
|
|
expect($.fn.atwho.default.callbacks.filter).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return the passed unfiltered items', () => {
|
|
|
|
|
expect(filterValue).toEqual(items);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('when previous query is not different from current one', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({
|
|
|
|
|
previousQuery: 'oldquery',
|
|
|
|
|
...fetchDataMock,
|
|
|
|
|
});
|
|
|
|
|
filterValue = gfmAutoCompleteCallbacks.filter.call(
|
|
|
|
|
atwhoInstance,
|
|
|
|
|
'oldquery',
|
|
|
|
|
items,
|
|
|
|
|
'searchKey',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not call the fetchData function', () => {
|
|
|
|
|
expect(fetchDataMock.fetchData).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should call the default atwho filter', () => {
|
|
|
|
|
expect($.fn.atwho.default.callbacks.filter).toHaveBeenCalledWith(
|
|
|
|
|
'oldquery',
|
|
|
|
|
items,
|
|
|
|
|
'searchKey',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('fetchData', () => {
|
|
|
|
|
const { fetchData } = GfmAutoComplete.prototype;
|
|
|
|
|
let mock;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
mock = new MockAdapter(axios);
|
|
|
|
|
jest.spyOn(axios, 'get');
|
|
|
|
|
jest.spyOn(AjaxCache, 'retrieve');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
mock.restore();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('already loading data', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
const context = {
|
|
|
|
|
isLoadingData: { '[vulnerability:': true },
|
|
|
|
|
dataSources: {},
|
|
|
|
|
cachedData: {},
|
|
|
|
|
};
|
|
|
|
|
fetchData.call(context, {}, '[vulnerability:', '');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not call either axios nor AjaxCache', () => {
|
|
|
|
|
expect(axios.get).not.toHaveBeenCalled();
|
|
|
|
|
expect(AjaxCache.retrieve).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('backend filtering', () => {
|
|
|
|
|
describe('data is not in cache', () => {
|
|
|
|
|
let context;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
context = {
|
|
|
|
|
isLoadingData: { '[vulnerability:': false },
|
|
|
|
|
dataSources: { vulnerabilities: 'vulnerabilities_autocomplete_url' },
|
|
|
|
|
cachedData: {},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should call axios with query', () => {
|
|
|
|
|
fetchData.call(context, {}, '[vulnerability:', 'query');
|
|
|
|
|
|
|
|
|
|
expect(axios.get).toHaveBeenCalledWith('vulnerabilities_autocomplete_url', {
|
|
|
|
|
params: { search: 'query' },
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2020-12-23 16:10:24 -05:00
|
|
|
|
it.each([200, 500])('should set the loading state', async (responseStatus) => {
|
2020-11-17 16:09:19 -05:00
|
|
|
|
mock.onGet('vulnerabilities_autocomplete_url').replyOnce(responseStatus);
|
|
|
|
|
|
|
|
|
|
fetchData.call(context, {}, '[vulnerability:', 'query');
|
|
|
|
|
|
|
|
|
|
expect(context.isLoadingData['[vulnerability:']).toBe(true);
|
|
|
|
|
|
|
|
|
|
await waitForPromises();
|
|
|
|
|
|
|
|
|
|
expect(context.isLoadingData['[vulnerability:']).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('data is in cache', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
const context = {
|
|
|
|
|
isLoadingData: { '[vulnerability:': false },
|
|
|
|
|
dataSources: { vulnerabilities: 'vulnerabilities_autocomplete_url' },
|
|
|
|
|
cachedData: { '[vulnerability:': [{}] },
|
|
|
|
|
};
|
|
|
|
|
fetchData.call(context, {}, '[vulnerability:', 'query');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should anyway call axios with query ignoring cache', () => {
|
|
|
|
|
expect(axios.get).toHaveBeenCalledWith('vulnerabilities_autocomplete_url', {
|
|
|
|
|
params: { search: 'query' },
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('frontend filtering', () => {
|
|
|
|
|
describe('data is not in cache', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
const context = {
|
|
|
|
|
isLoadingData: { '#': false },
|
|
|
|
|
dataSources: { issues: 'issues_autocomplete_url' },
|
|
|
|
|
cachedData: {},
|
|
|
|
|
};
|
|
|
|
|
fetchData.call(context, {}, '#', 'query');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should call AjaxCache', () => {
|
|
|
|
|
expect(AjaxCache.retrieve).toHaveBeenCalledWith('issues_autocomplete_url', true);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('data is in cache', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
const context = {
|
|
|
|
|
isLoadingData: { '#': false },
|
|
|
|
|
dataSources: { issues: 'issues_autocomplete_url' },
|
|
|
|
|
cachedData: { '#': [{}] },
|
|
|
|
|
loadData: () => {},
|
|
|
|
|
};
|
|
|
|
|
fetchData.call(context, {}, '#', 'query');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not call AjaxCache', () => {
|
|
|
|
|
expect(AjaxCache.retrieve).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-01-18 07:17:24 -05:00
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
describe('DefaultOptions.sorter', () => {
|
|
|
|
|
describe('assets loading', () => {
|
2019-02-27 04:22:04 -05:00
|
|
|
|
let items;
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
beforeEach(() => {
|
|
|
|
|
jest.spyOn(GfmAutoComplete, 'isLoading').mockReturnValue(true);
|
2017-01-18 07:17:24 -05:00
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
atwhoInstance = { setting: {} };
|
|
|
|
|
items = [];
|
|
|
|
|
|
|
|
|
|
sorterValue = gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, '', items);
|
2017-01-18 07:17:24 -05:00
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
it('should disable highlightFirst', () => {
|
|
|
|
|
expect(atwhoInstance.setting.highlightFirst).toBe(false);
|
2017-01-18 07:17:24 -05:00
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
it('should return the passed unfiltered items', () => {
|
|
|
|
|
expect(sorterValue).toEqual(items);
|
2017-01-18 07:17:24 -05:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
describe('assets finished loading', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
jest.spyOn(GfmAutoComplete, 'isLoading').mockReturnValue(false);
|
|
|
|
|
jest.spyOn($.fn.atwho.default.callbacks, 'sorter').mockImplementation(() => {});
|
2017-01-18 07:17:24 -05:00
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
it('should enable highlightFirst if alwaysHighlightFirst is set', () => {
|
|
|
|
|
atwhoInstance = { setting: { alwaysHighlightFirst: true } };
|
2017-01-18 07:17:24 -05:00
|
|
|
|
|
2017-05-16 14:42:06 -04:00
|
|
|
|
gfmAutoCompleteCallbacks.sorter.call(atwhoInstance);
|
2017-01-18 07:17:24 -05:00
|
|
|
|
|
|
|
|
|
expect(atwhoInstance.setting.highlightFirst).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
it('should enable highlightFirst if a query is present', () => {
|
|
|
|
|
atwhoInstance = { setting: {} };
|
2017-01-18 07:17:24 -05:00
|
|
|
|
|
2017-05-16 14:42:06 -04:00
|
|
|
|
gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, 'query');
|
2017-01-18 07:17:24 -05:00
|
|
|
|
|
|
|
|
|
expect(atwhoInstance.setting.highlightFirst).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
it('should call the default atwho sorter', () => {
|
|
|
|
|
atwhoInstance = { setting: {} };
|
2017-01-18 07:17:24 -05:00
|
|
|
|
|
|
|
|
|
const query = 'query';
|
2019-02-27 04:22:04 -05:00
|
|
|
|
const items = [];
|
2017-01-18 07:17:24 -05:00
|
|
|
|
const searchKey = 'searchKey';
|
|
|
|
|
|
2017-05-16 14:42:06 -04:00
|
|
|
|
gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, query, items, searchKey);
|
2017-01-18 07:17:24 -05:00
|
|
|
|
|
|
|
|
|
expect($.fn.atwho.default.callbacks.sorter).toHaveBeenCalledWith(query, items, searchKey);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-01-20 12:38:06 -05:00
|
|
|
|
|
2017-11-13 09:30:22 -05:00
|
|
|
|
describe('DefaultOptions.beforeInsert', () => {
|
2018-10-17 03:13:26 -04:00
|
|
|
|
const beforeInsert = (context, value) =>
|
|
|
|
|
gfmAutoCompleteCallbacks.beforeInsert.call(context, value);
|
2017-11-13 09:30:22 -05:00
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
beforeEach(() => {
|
|
|
|
|
atwhoInstance = { setting: { skipSpecialCharacterTest: false } };
|
|
|
|
|
});
|
2017-11-13 09:30:22 -05:00
|
|
|
|
|
|
|
|
|
it('should not quote if value only contains alphanumeric charecters', () => {
|
|
|
|
|
expect(beforeInsert(atwhoInstance, '@user1')).toBe('@user1');
|
|
|
|
|
expect(beforeInsert(atwhoInstance, '~label1')).toBe('~label1');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should quote if value contains any non-alphanumeric characters', () => {
|
Only use backslash escapes in autocomplete when needed
Autocompletion for references happens on the frontend. Those references
are turned into actual references on the backend, but only after
Markdown processing has happened. That means that if a reference
contains a character that Markdown might consume, it won't render
correctly. So we need to do some escaping on the frontend.
We have these potential problem characters:
https://docs.gitlab.com/ee/user/markdown.html#emphasis
1. ~ - this is ~~strikethrough~~, but only when doubled.
2. _ - used for _emphasis_, doubled is __bold__.
3. * - also used for *emphasis*, doubled is **bold** also.
4. ` - used for `code spans`, any number works.
We don't need to escape `-` any more. When it comes to being inside a
word:
1. a~~b~~ has strikethrough, so it needs to be escaped everywhere.
2. a_b_ has no emphasis (see [a]) so it only needs to be escaped at the
start and end of words.
3. a*b* has emphasis, so it needs to be escaped everywhere.
4. a`b` has a code span, so it needs to be escaped everywhere.
Or, in code terms:
1. Always escape ~~, *, and ` when being inserted by autocomplete.
2. Escape _ when it's either at the beginning or the end of a word.
[a]: https://docs.gitlab.com/ee/user/markdown.html#multiple-underscores-in-words
2019-04-17 07:52:25 -04:00
|
|
|
|
expect(beforeInsert(atwhoInstance, '~label-20')).toBe('~"label-20"');
|
2017-11-13 09:30:22 -05:00
|
|
|
|
expect(beforeInsert(atwhoInstance, '~label 20')).toBe('~"label 20"');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should quote integer labels', () => {
|
|
|
|
|
expect(beforeInsert(atwhoInstance, '~1234')).toBe('~"1234"');
|
|
|
|
|
});
|
2018-03-28 10:45:16 -04:00
|
|
|
|
|
Only use backslash escapes in autocomplete when needed
Autocompletion for references happens on the frontend. Those references
are turned into actual references on the backend, but only after
Markdown processing has happened. That means that if a reference
contains a character that Markdown might consume, it won't render
correctly. So we need to do some escaping on the frontend.
We have these potential problem characters:
https://docs.gitlab.com/ee/user/markdown.html#emphasis
1. ~ - this is ~~strikethrough~~, but only when doubled.
2. _ - used for _emphasis_, doubled is __bold__.
3. * - also used for *emphasis*, doubled is **bold** also.
4. ` - used for `code spans`, any number works.
We don't need to escape `-` any more. When it comes to being inside a
word:
1. a~~b~~ has strikethrough, so it needs to be escaped everywhere.
2. a_b_ has no emphasis (see [a]) so it only needs to be escaped at the
start and end of words.
3. a*b* has emphasis, so it needs to be escaped everywhere.
4. a`b` has a code span, so it needs to be escaped everywhere.
Or, in code terms:
1. Always escape ~~, *, and ` when being inserted by autocomplete.
2. Escape _ when it's either at the beginning or the end of a word.
[a]: https://docs.gitlab.com/ee/user/markdown.html#multiple-underscores-in-words
2019-04-17 07:52:25 -04:00
|
|
|
|
it('escapes Markdown strikethroughs when needed', () => {
|
|
|
|
|
expect(beforeInsert(atwhoInstance, '~a~bug')).toEqual('~"a~bug"');
|
|
|
|
|
expect(beforeInsert(atwhoInstance, '~a~~bug~~')).toEqual('~"a\\~~bug\\~~"');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('escapes Markdown emphasis when needed', () => {
|
|
|
|
|
expect(beforeInsert(atwhoInstance, '~a_bug_')).toEqual('~a_bug\\_');
|
|
|
|
|
expect(beforeInsert(atwhoInstance, '~a _bug_')).toEqual('~"a \\_bug\\_"');
|
|
|
|
|
expect(beforeInsert(atwhoInstance, '~a*bug*')).toEqual('~"a\\*bug\\*"');
|
|
|
|
|
expect(beforeInsert(atwhoInstance, '~a *bug*')).toEqual('~"a \\*bug\\*"');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('escapes Markdown code spans when needed', () => {
|
|
|
|
|
expect(beforeInsert(atwhoInstance, '~a`bug`')).toEqual('~"a\\`bug\\`"');
|
2018-03-28 10:45:16 -04:00
|
|
|
|
expect(beforeInsert(atwhoInstance, '~a `bug`')).toEqual('~"a \\`bug\\`"');
|
|
|
|
|
});
|
2017-11-13 09:30:22 -05:00
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
describe('DefaultOptions.matcher', () => {
|
2018-10-17 03:13:26 -04:00
|
|
|
|
const defaultMatcher = (context, flag, subtext) =>
|
|
|
|
|
gfmAutoCompleteCallbacks.matcher.call(context, flag, subtext);
|
2017-02-03 04:58:45 -05:00
|
|
|
|
|
2020-10-16 05:09:06 -04:00
|
|
|
|
const flagsUseDefaultMatcher = ['@', '#', '!', '~', '%', '$'];
|
2017-02-03 04:58:45 -05:00
|
|
|
|
const otherFlags = ['/', ':'];
|
|
|
|
|
const flags = flagsUseDefaultMatcher.concat(otherFlags);
|
|
|
|
|
|
2018-10-17 03:13:26 -04:00
|
|
|
|
const flagsHash = flags.reduce((hash, el) => {
|
|
|
|
|
hash[el] = null;
|
|
|
|
|
return hash;
|
|
|
|
|
}, {});
|
2019-02-21 08:26:27 -05:00
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
atwhoInstance = { setting: {}, app: { controllers: flagsHash } };
|
|
|
|
|
});
|
2017-02-03 04:58:45 -05:00
|
|
|
|
|
|
|
|
|
const minLen = 1;
|
|
|
|
|
const maxLen = 20;
|
|
|
|
|
const argumentSize = [minLen, maxLen / 2, maxLen];
|
|
|
|
|
|
2018-10-17 03:13:26 -04:00
|
|
|
|
const allowedSymbols = [
|
|
|
|
|
'',
|
|
|
|
|
'a',
|
|
|
|
|
'n',
|
|
|
|
|
'z',
|
|
|
|
|
'A',
|
|
|
|
|
'Z',
|
|
|
|
|
'N',
|
|
|
|
|
'0',
|
|
|
|
|
'5',
|
|
|
|
|
'9',
|
|
|
|
|
'А',
|
|
|
|
|
'а',
|
|
|
|
|
'Я',
|
|
|
|
|
'я',
|
|
|
|
|
'.',
|
|
|
|
|
"'",
|
|
|
|
|
'-',
|
|
|
|
|
'_',
|
|
|
|
|
];
|
2017-02-03 04:58:45 -05:00
|
|
|
|
const jointAllowedSymbols = allowedSymbols.join('');
|
|
|
|
|
|
|
|
|
|
describe('should match regular symbols', () => {
|
2020-12-23 16:10:24 -05:00
|
|
|
|
flagsUseDefaultMatcher.forEach((flag) => {
|
|
|
|
|
allowedSymbols.forEach((symbol) => {
|
|
|
|
|
argumentSize.forEach((size) => {
|
2017-02-03 04:58:45 -05:00
|
|
|
|
const query = new Array(size + 1).join(symbol);
|
|
|
|
|
const subtext = flag + query;
|
|
|
|
|
|
|
|
|
|
it(`matches argument "${flag}" with query "${subtext}"`, () => {
|
|
|
|
|
expect(defaultMatcher(atwhoInstance, flag, subtext)).toBe(query);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it(`matches combination of allowed symbols for flag "${flag}"`, () => {
|
|
|
|
|
const subtext = flag + jointAllowedSymbols;
|
|
|
|
|
|
|
|
|
|
expect(defaultMatcher(atwhoInstance, flag, subtext)).toBe(jointAllowedSymbols);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('should not match special sequences', () => {
|
2018-02-01 05:35:03 -05:00
|
|
|
|
const shouldNotBeFollowedBy = flags.concat(['\x00', '\x10', '\x3f', '\n', ' ']);
|
|
|
|
|
const shouldNotBePrependedBy = ['`'];
|
2017-02-03 04:58:45 -05:00
|
|
|
|
|
2020-12-23 16:10:24 -05:00
|
|
|
|
flagsUseDefaultMatcher.forEach((atSign) => {
|
|
|
|
|
shouldNotBeFollowedBy.forEach((followedSymbol) => {
|
2017-02-03 04:58:45 -05:00
|
|
|
|
const seq = atSign + followedSymbol;
|
2017-12-20 06:39:40 -05:00
|
|
|
|
|
2018-09-11 17:03:05 -04:00
|
|
|
|
it(`should not match ${JSON.stringify(seq)}`, () => {
|
2017-12-20 06:39:40 -05:00
|
|
|
|
expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2020-12-23 16:10:24 -05:00
|
|
|
|
shouldNotBePrependedBy.forEach((prependedSymbol) => {
|
2017-12-20 06:39:40 -05:00
|
|
|
|
const seq = prependedSymbol + atSign;
|
2017-02-03 04:58:45 -05:00
|
|
|
|
|
|
|
|
|
it(`should not match "${seq}"`, () => {
|
|
|
|
|
expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2019-04-25 04:11:20 -04:00
|
|
|
|
describe('DefaultOptions.highlighter', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
atwhoInstance = { setting: {} };
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return li if no query is given', () => {
|
|
|
|
|
const liTag = '<li></li>';
|
|
|
|
|
|
|
|
|
|
const highlightedTag = gfmAutoCompleteCallbacks.highlighter.call(atwhoInstance, liTag);
|
|
|
|
|
|
|
|
|
|
expect(highlightedTag).toEqual(liTag);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should highlight search query in li element', () => {
|
|
|
|
|
const liTag = '<li><img src="" />string</li>';
|
|
|
|
|
const query = 's';
|
|
|
|
|
|
|
|
|
|
const highlightedTag = gfmAutoCompleteCallbacks.highlighter.call(atwhoInstance, liTag, query);
|
|
|
|
|
|
|
|
|
|
expect(highlightedTag).toEqual('<li><img src="" /> <strong>s</strong>tring </li>');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should highlight search query with special char in li element', () => {
|
|
|
|
|
const liTag = '<li><img src="" />te.st</li>';
|
|
|
|
|
const query = '.';
|
|
|
|
|
|
|
|
|
|
const highlightedTag = gfmAutoCompleteCallbacks.highlighter.call(atwhoInstance, liTag, query);
|
|
|
|
|
|
|
|
|
|
expect(highlightedTag).toEqual('<li><img src="" /> te<strong>.</strong>st </li>');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
describe('isLoading', () => {
|
|
|
|
|
it('should be true with loading data object item', () => {
|
2017-01-20 12:38:06 -05:00
|
|
|
|
expect(GfmAutoComplete.isLoading({ name: 'loading' })).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
it('should be true with loading data array', () => {
|
2017-01-20 12:38:06 -05:00
|
|
|
|
expect(GfmAutoComplete.isLoading(['loading'])).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
it('should be true with loading data object array', () => {
|
2017-01-20 12:38:06 -05:00
|
|
|
|
expect(GfmAutoComplete.isLoading([{ name: 'loading' }])).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
it('should be false with actual array data', () => {
|
2018-10-17 03:13:26 -04:00
|
|
|
|
expect(
|
|
|
|
|
GfmAutoComplete.isLoading([{ title: 'Foo' }, { title: 'Bar' }, { title: 'Qux' }]),
|
|
|
|
|
).toBe(false);
|
2017-01-20 12:38:06 -05:00
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
it('should be false with actual data item', () => {
|
2017-01-20 12:38:06 -05:00
|
|
|
|
expect(GfmAutoComplete.isLoading({ title: 'Foo' })).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
});
|
2018-12-21 03:49:44 -05:00
|
|
|
|
|
2019-12-03 07:06:34 -05:00
|
|
|
|
describe('membersBeforeSave', () => {
|
|
|
|
|
const mockGroup = {
|
|
|
|
|
username: 'my-group',
|
|
|
|
|
name: 'My Group',
|
|
|
|
|
count: 2,
|
|
|
|
|
avatar_url: './group.jpg',
|
|
|
|
|
type: 'Group',
|
|
|
|
|
mentionsDisabled: false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('should return the original object when username is null', () => {
|
|
|
|
|
expect(membersBeforeSave([{ ...mockGroup, username: null }])).toEqual([
|
|
|
|
|
{ ...mockGroup, username: null },
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should set the text avatar if avatar_url is null', () => {
|
|
|
|
|
expect(membersBeforeSave([{ ...mockGroup, avatar_url: null }])).toEqual([
|
|
|
|
|
{
|
|
|
|
|
username: 'my-group',
|
|
|
|
|
avatarTag: '<div class="avatar rect-avatar center avatar-inline s26">M</div>',
|
|
|
|
|
title: 'My Group (2)',
|
|
|
|
|
search: 'my-group My Group',
|
|
|
|
|
icon: '',
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should set the image avatar if avatar_url is given', () => {
|
|
|
|
|
expect(membersBeforeSave([mockGroup])).toEqual([
|
|
|
|
|
{
|
|
|
|
|
username: 'my-group',
|
|
|
|
|
avatarTag:
|
|
|
|
|
'<img src="./group.jpg" alt="my-group" class="avatar rect-avatar avatar-inline center s26"/>',
|
|
|
|
|
title: 'My Group (2)',
|
|
|
|
|
search: 'my-group My Group',
|
|
|
|
|
icon: '',
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should set mentions disabled icon if mentionsDisabled is set', () => {
|
|
|
|
|
expect(membersBeforeSave([{ ...mockGroup, mentionsDisabled: true }])).toEqual([
|
|
|
|
|
{
|
|
|
|
|
username: 'my-group',
|
|
|
|
|
avatarTag:
|
|
|
|
|
'<img src="./group.jpg" alt="my-group" class="avatar rect-avatar avatar-inline center s26"/>',
|
|
|
|
|
title: 'My Group',
|
|
|
|
|
search: 'my-group My Group',
|
|
|
|
|
icon:
|
2020-07-14 02:09:17 -04:00
|
|
|
|
'<svg class="s16 vertical-align-middle gl-ml-2"><use xlink:href="undefined#notifications-off" /></svg>',
|
2019-12-03 07:06:34 -05:00
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should set the right image classes for User type members', () => {
|
|
|
|
|
expect(
|
|
|
|
|
membersBeforeSave([
|
|
|
|
|
{ username: 'my-user', name: 'My User', avatar_url: './users.jpg', type: 'User' },
|
|
|
|
|
]),
|
|
|
|
|
).toEqual([
|
|
|
|
|
{
|
|
|
|
|
username: 'my-user',
|
|
|
|
|
avatarTag:
|
|
|
|
|
'<img src="./users.jpg" alt="my-user" class="avatar avatar-inline center s26"/>',
|
|
|
|
|
title: 'My User',
|
|
|
|
|
search: 'my-user My User',
|
|
|
|
|
icon: '',
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
describe('Issues.insertTemplateFunction', () => {
|
|
|
|
|
it('should return default template', () => {
|
2018-12-21 03:49:44 -05:00
|
|
|
|
expect(GfmAutoComplete.Issues.insertTemplateFunction({ id: 5, title: 'Some Issue' })).toBe(
|
|
|
|
|
'${atwho-at}${id}', // eslint-disable-line no-template-curly-in-string
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
it('should return reference when reference is set', () => {
|
2018-12-21 03:49:44 -05:00
|
|
|
|
expect(
|
|
|
|
|
GfmAutoComplete.Issues.insertTemplateFunction({
|
|
|
|
|
id: 5,
|
|
|
|
|
title: 'Some Issue',
|
|
|
|
|
reference: 'grp/proj#5',
|
|
|
|
|
}),
|
|
|
|
|
).toBe('grp/proj#5');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
describe('Issues.templateFunction', () => {
|
|
|
|
|
it('should return html with id and title', () => {
|
2018-12-21 03:49:44 -05:00
|
|
|
|
expect(GfmAutoComplete.Issues.templateFunction({ id: 5, title: 'Some Issue' })).toBe(
|
|
|
|
|
'<li><small>5</small> Some Issue</li>',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2019-02-21 08:26:27 -05:00
|
|
|
|
it('should replace id with reference if reference is set', () => {
|
2018-12-21 03:49:44 -05:00
|
|
|
|
expect(
|
|
|
|
|
GfmAutoComplete.Issues.templateFunction({
|
|
|
|
|
id: 5,
|
|
|
|
|
title: 'Some Issue',
|
|
|
|
|
reference: 'grp/proj#5',
|
|
|
|
|
}),
|
|
|
|
|
).toBe('<li><small>grp/proj#5</small> Some Issue</li>');
|
|
|
|
|
});
|
|
|
|
|
});
|
2019-02-27 04:22:04 -05:00
|
|
|
|
|
2019-12-03 07:06:34 -05:00
|
|
|
|
describe('Members.templateFunction', () => {
|
|
|
|
|
it('should return html with avatarTag and username', () => {
|
|
|
|
|
expect(
|
|
|
|
|
GfmAutoComplete.Members.templateFunction({
|
|
|
|
|
avatarTag: 'IMG',
|
|
|
|
|
username: 'my-group',
|
|
|
|
|
title: '',
|
|
|
|
|
icon: '',
|
2020-11-17 10:09:28 -05:00
|
|
|
|
availabilityStatus: '',
|
2019-12-03 07:06:34 -05:00
|
|
|
|
}),
|
|
|
|
|
).toBe('<li>IMG my-group <small></small> </li>');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should add icon if icon is set', () => {
|
|
|
|
|
expect(
|
|
|
|
|
GfmAutoComplete.Members.templateFunction({
|
|
|
|
|
avatarTag: 'IMG',
|
|
|
|
|
username: 'my-group',
|
|
|
|
|
title: '',
|
|
|
|
|
icon: '<i class="icon"/>',
|
2020-11-17 10:09:28 -05:00
|
|
|
|
availabilityStatus: '',
|
2019-12-03 07:06:34 -05:00
|
|
|
|
}),
|
|
|
|
|
).toBe('<li>IMG my-group <small></small> <i class="icon"/></li>');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should add escaped title if title is set', () => {
|
|
|
|
|
expect(
|
|
|
|
|
GfmAutoComplete.Members.templateFunction({
|
|
|
|
|
avatarTag: 'IMG',
|
|
|
|
|
username: 'my-group',
|
|
|
|
|
title: 'MyGroup+',
|
|
|
|
|
icon: '<i class="icon"/>',
|
2020-11-17 10:09:28 -05:00
|
|
|
|
availabilityStatus: '',
|
2019-12-03 07:06:34 -05:00
|
|
|
|
}),
|
|
|
|
|
).toBe('<li>IMG my-group <small>MyGroup+</small> <i class="icon"/></li>');
|
|
|
|
|
});
|
2020-11-17 10:09:28 -05:00
|
|
|
|
|
|
|
|
|
it('should add user availability status if availabilityStatus is set', () => {
|
|
|
|
|
expect(
|
|
|
|
|
GfmAutoComplete.Members.templateFunction({
|
|
|
|
|
avatarTag: 'IMG',
|
|
|
|
|
username: 'my-group',
|
|
|
|
|
title: '',
|
|
|
|
|
icon: '<i class="icon"/>',
|
|
|
|
|
availabilityStatus: '<span class="gl-text-gray-500"> (Busy)</span>',
|
|
|
|
|
}),
|
|
|
|
|
).toBe(
|
|
|
|
|
'<li>IMG my-group <small><span class="gl-text-gray-500"> (Busy)</span></small> <i class="icon"/></li>',
|
|
|
|
|
);
|
|
|
|
|
});
|
2019-12-03 07:06:34 -05:00
|
|
|
|
});
|
|
|
|
|
|
2019-02-27 04:22:04 -05:00
|
|
|
|
describe('labels', () => {
|
|
|
|
|
const dataSources = {
|
|
|
|
|
labels: `${TEST_HOST}/autocomplete_sources/labels`,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const allLabels = labelsFixture;
|
2020-12-23 16:10:24 -05:00
|
|
|
|
const assignedLabels = allLabels.filter((label) => label.set);
|
|
|
|
|
const unassignedLabels = allLabels.filter((label) => !label.set);
|
2019-02-27 04:22:04 -05:00
|
|
|
|
|
|
|
|
|
let autocomplete;
|
|
|
|
|
let $textarea;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
2020-10-07 17:08:21 -04:00
|
|
|
|
setFixtures('<textarea></textarea>');
|
2019-02-27 04:22:04 -05:00
|
|
|
|
autocomplete = new GfmAutoComplete(dataSources);
|
2020-10-07 17:08:21 -04:00
|
|
|
|
$textarea = $('textarea');
|
2019-02-27 04:22:04 -05:00
|
|
|
|
autocomplete.setup($textarea, { labels: true });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
autocomplete.destroy();
|
|
|
|
|
});
|
|
|
|
|
|
2020-12-23 16:10:24 -05:00
|
|
|
|
const triggerDropdown = (text) => {
|
2020-12-23 07:10:26 -05:00
|
|
|
|
$textarea.trigger('focus').val(text).caret('pos', -1);
|
2019-02-27 04:22:04 -05:00
|
|
|
|
$textarea.trigger('keyup');
|
|
|
|
|
|
|
|
|
|
return new Promise(window.requestAnimationFrame);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getDropdownItems = () => {
|
|
|
|
|
const dropdown = document.getElementById('at-view-labels');
|
|
|
|
|
const items = dropdown.getElementsByTagName('li');
|
2020-12-23 16:10:24 -05:00
|
|
|
|
return [].map.call(items, (item) => item.textContent.trim());
|
2019-02-27 04:22:04 -05:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const expectLabels = ({ input, output }) =>
|
|
|
|
|
triggerDropdown(input).then(() => {
|
2020-12-23 16:10:24 -05:00
|
|
|
|
expect(getDropdownItems()).toEqual(output.map((label) => label.title));
|
2019-02-27 04:22:04 -05:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('with no labels assigned', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
autocomplete.cachedData['~'] = [...unassignedLabels];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it.each`
|
|
|
|
|
input | output
|
|
|
|
|
${'~'} | ${unassignedLabels}
|
|
|
|
|
${'/label ~'} | ${unassignedLabels}
|
|
|
|
|
${'/relabel ~'} | ${unassignedLabels}
|
|
|
|
|
${'/unlabel ~'} | ${[]}
|
|
|
|
|
`('$input shows $output.length labels', expectLabels);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('with some labels assigned', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
autocomplete.cachedData['~'] = allLabels;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it.each`
|
|
|
|
|
input | output
|
|
|
|
|
${'~'} | ${allLabels}
|
|
|
|
|
${'/label ~'} | ${unassignedLabels}
|
|
|
|
|
${'/relabel ~'} | ${allLabels}
|
|
|
|
|
${'/unlabel ~'} | ${assignedLabels}
|
|
|
|
|
`('$input shows $output.length labels', expectLabels);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('with all labels assigned', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
autocomplete.cachedData['~'] = [...assignedLabels];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it.each`
|
|
|
|
|
input | output
|
|
|
|
|
${'~'} | ${assignedLabels}
|
|
|
|
|
${'/label ~'} | ${[]}
|
|
|
|
|
${'/relabel ~'} | ${assignedLabels}
|
|
|
|
|
${'/unlabel ~'} | ${assignedLabels}
|
|
|
|
|
`('$input shows $output.length labels', expectLabels);
|
|
|
|
|
});
|
|
|
|
|
});
|
2020-10-13 05:08:27 -04:00
|
|
|
|
|
|
|
|
|
describe('emoji', () => {
|
2020-10-14 11:08:42 -04:00
|
|
|
|
const { atom, heart, star } = emojiFixtureMap;
|
2020-10-13 05:08:27 -04:00
|
|
|
|
const assertInserted = ({ input, subject, emoji }) =>
|
|
|
|
|
expect(subject).toBe(`:${emoji?.name || input}:`);
|
2020-10-14 11:08:42 -04:00
|
|
|
|
const assertTemplated = ({ input, subject, emoji, field }) =>
|
2020-10-13 05:08:27 -04:00
|
|
|
|
expect(subject.replace(/\s+/g, ' ')).toBe(
|
2020-10-14 11:08:42 -04:00
|
|
|
|
`<li>${field || input} <gl-emoji data-name="${emoji?.name || input}"></gl-emoji> </li>`,
|
2020-10-13 05:08:27 -04:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let mock;
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
mock = await initEmojiMock();
|
|
|
|
|
|
|
|
|
|
await new GfmAutoComplete({}).loadEmojiData({ atwho() {}, trigger() {} }, ':');
|
|
|
|
|
if (!GfmAutoComplete.glEmojiTag) throw new Error('emoji not loaded');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
mock.restore();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe.each`
|
2020-12-23 16:10:24 -05:00
|
|
|
|
name | inputFormat | assert
|
|
|
|
|
${'insertTemplateFunction'} | ${(name) => ({ name })} | ${assertInserted}
|
|
|
|
|
${'templateFunction'} | ${(name) => name} | ${assertTemplated}
|
2020-10-13 05:08:27 -04:00
|
|
|
|
`('Emoji.$name', ({ name, inputFormat, assert }) => {
|
2020-10-14 11:08:42 -04:00
|
|
|
|
const execute = (accessor, input, emoji) =>
|
2020-10-13 05:08:27 -04:00
|
|
|
|
assert({
|
|
|
|
|
input,
|
|
|
|
|
emoji,
|
2020-10-14 11:08:42 -04:00
|
|
|
|
field: accessor && accessor(emoji),
|
2020-10-13 05:08:27 -04:00
|
|
|
|
subject: GfmAutoComplete.Emoji[name](inputFormat(input)),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describeEmojiFields('for $field', ({ accessor }) => {
|
|
|
|
|
it('should work with lowercase', () => {
|
2020-10-14 11:08:42 -04:00
|
|
|
|
execute(accessor, accessor(atom), atom);
|
2020-10-13 05:08:27 -04:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should work with uppercase', () => {
|
2020-10-14 11:08:42 -04:00
|
|
|
|
execute(accessor, accessor(atom).toUpperCase(), atom);
|
2020-10-13 05:08:27 -04:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should work with partial value', () => {
|
2020-10-14 11:08:42 -04:00
|
|
|
|
execute(accessor, accessor(atom).slice(1), atom);
|
2020-10-13 05:08:27 -04:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should work with unicode value', () => {
|
2020-10-14 11:08:42 -04:00
|
|
|
|
execute(null, atom.moji, atom);
|
2020-10-13 05:08:27 -04:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should pass through unknown value', () => {
|
2020-10-14 11:08:42 -04:00
|
|
|
|
execute(null, 'foo bar baz');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const expectEmojiOrder = (first, second) => {
|
|
|
|
|
const keys = Object.keys(emojiFixtureMap);
|
|
|
|
|
const firstIndex = keys.indexOf(first);
|
|
|
|
|
const secondIndex = keys.indexOf(second);
|
|
|
|
|
expect(firstIndex).toBeGreaterThanOrEqual(0);
|
|
|
|
|
expect(secondIndex).toBeGreaterThanOrEqual(0);
|
|
|
|
|
expect(firstIndex).toBeLessThan(secondIndex);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
describe('Emoji.insertTemplateFunction', () => {
|
|
|
|
|
it('should map ":heart" to :heart: [regression]', () => {
|
|
|
|
|
// the bug mapped heart to black_heart because the latter sorted first
|
|
|
|
|
expectEmojiOrder('black_heart', 'heart');
|
|
|
|
|
|
|
|
|
|
const item = GfmAutoComplete.Emoji.insertTemplateFunction({ name: 'heart' });
|
|
|
|
|
expect(item).toEqual(`:${heart.name}:`);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should map ":star" to :star: [regression]', () => {
|
|
|
|
|
// the bug mapped star to custard because the latter sorted first
|
|
|
|
|
expectEmojiOrder('custard', 'star');
|
|
|
|
|
|
|
|
|
|
const item = GfmAutoComplete.Emoji.insertTemplateFunction({ name: 'star' });
|
|
|
|
|
expect(item).toEqual(`:${star.name}:`);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Emoji.templateFunction', () => {
|
|
|
|
|
it('should map ":heart" to ❤ [regression]', () => {
|
|
|
|
|
// the bug mapped heart to black_heart because the latter sorted first
|
|
|
|
|
expectEmojiOrder('black_heart', 'heart');
|
|
|
|
|
|
|
|
|
|
const item = GfmAutoComplete.Emoji.templateFunction('heart')
|
|
|
|
|
.replace(/(<gl-emoji)\s+(data-name)/, '$1 $2')
|
2020-12-23 16:10:24 -05:00
|
|
|
|
.replace(/>\s+|\s+</g, (s) => s.trim());
|
2020-10-14 11:08:42 -04:00
|
|
|
|
expect(item).toEqual(
|
|
|
|
|
`<li>${heart.name}<gl-emoji data-name="${heart.name}"></gl-emoji></li>`,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should map ":star" to ⭐ [regression]', () => {
|
|
|
|
|
// the bug mapped star to custard because the latter sorted first
|
|
|
|
|
expectEmojiOrder('custard', 'star');
|
|
|
|
|
|
|
|
|
|
const item = GfmAutoComplete.Emoji.templateFunction('star')
|
|
|
|
|
.replace(/(<gl-emoji)\s+(data-name)/, '$1 $2')
|
2020-12-23 16:10:24 -05:00
|
|
|
|
.replace(/>\s+|\s+</g, (s) => s.trim());
|
2020-10-14 11:08:42 -04:00
|
|
|
|
expect(item).toEqual(`<li>${star.name}<gl-emoji data-name="${star.name}"></gl-emoji></li>`);
|
2020-10-13 05:08:27 -04:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-01-18 07:17:24 -05:00
|
|
|
|
});
|