216 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			216 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| import axios from 'axios';
 | |
| import MockAdapter from 'axios-mock-adapter';
 | |
| import AutocompleteHelper, {
 | |
|   defaultSorter,
 | |
|   customSorter,
 | |
|   createDataSource,
 | |
| } from '~/content_editor/services/autocomplete_helper';
 | |
| import {
 | |
|   MOCK_MEMBERS,
 | |
|   MOCK_COMMANDS,
 | |
|   MOCK_EPICS,
 | |
|   MOCK_ISSUES,
 | |
|   MOCK_LABELS,
 | |
|   MOCK_MILESTONES,
 | |
|   MOCK_SNIPPETS,
 | |
|   MOCK_VULNERABILITIES,
 | |
|   MOCK_MERGE_REQUESTS,
 | |
|   MOCK_ASSIGNEES,
 | |
|   MOCK_REVIEWERS,
 | |
| } from './autocomplete_mock_data';
 | |
| 
 | |
| jest.mock('~/emoji');
 | |
| 
 | |
| describe('defaultSorter', () => {
 | |
|   it('returns items as is if query is empty', () => {
 | |
|     const items = [{ name: 'abc' }, { name: 'bcd' }, { name: 'cde' }];
 | |
|     const sorter = defaultSorter(['name']);
 | |
|     expect(sorter(items, '')).toEqual(items);
 | |
|   });
 | |
| 
 | |
|   it('sorts items based on query match', () => {
 | |
|     const items = [{ name: 'abc' }, { name: 'bcd' }, { name: 'cde' }];
 | |
|     const sorter = defaultSorter(['name']);
 | |
|     expect(sorter(items, 'b')).toEqual([{ name: 'bcd' }, { name: 'abc' }, { name: 'cde' }]);
 | |
|   });
 | |
| 
 | |
|   it('sorts items based on query match in multiple fields', () => {
 | |
|     const items = [
 | |
|       { name: 'wabc', description: 'xyz' },
 | |
|       { name: 'bcd', description: 'wxy' },
 | |
|       { name: 'cde', description: 'vwx' },
 | |
|     ];
 | |
|     const sorter = defaultSorter(['name', 'description']);
 | |
|     expect(sorter(items, 'w')).toEqual([
 | |
|       { name: 'wabc', description: 'xyz' },
 | |
|       { name: 'bcd', description: 'wxy' },
 | |
|       { name: 'cde', description: 'vwx' },
 | |
|     ]);
 | |
|   });
 | |
| });
 | |
| 
 | |
| describe('customSorter', () => {
 | |
|   it('sorts items based on custom sorter function', () => {
 | |
|     const items = [3, 1, 2];
 | |
|     const sorter = customSorter((a, b) => a - b);
 | |
|     expect(sorter(items)).toEqual([1, 2, 3]);
 | |
|   });
 | |
| });
 | |
| 
 | |
| describe('createDataSource', () => {
 | |
|   let mock;
 | |
| 
 | |
|   beforeEach(() => {
 | |
|     mock = new MockAdapter(axios);
 | |
|   });
 | |
| 
 | |
|   afterEach(() => {
 | |
|     mock.restore();
 | |
|   });
 | |
| 
 | |
|   it('fetches data from source and filters based on query', async () => {
 | |
|     const data = [
 | |
|       { name: 'abc', description: 'xyz' },
 | |
|       { name: 'bcd', description: 'wxy' },
 | |
|       { name: 'cde', description: 'vwx' },
 | |
|     ];
 | |
|     mock.onGet('/source').reply(200, data);
 | |
| 
 | |
|     const dataSource = createDataSource({
 | |
|       source: '/source',
 | |
|       searchFields: ['name', 'description'],
 | |
|     });
 | |
| 
 | |
|     const results = await dataSource.search('b');
 | |
|     expect(results).toEqual([
 | |
|       { name: 'bcd', description: 'wxy' },
 | |
|       { name: 'abc', description: 'xyz' },
 | |
|     ]);
 | |
|   });
 | |
| 
 | |
|   it('handles source fetch errors', async () => {
 | |
|     mock.onGet('/source').reply(500);
 | |
| 
 | |
|     const dataSource = createDataSource({
 | |
|       source: '/source',
 | |
|       searchFields: ['name', 'description'],
 | |
|       sorter: (items) => items,
 | |
|     });
 | |
| 
 | |
|     const results = await dataSource.search('b');
 | |
|     expect(results).toEqual([]);
 | |
|   });
 | |
| });
 | |
| 
 | |
| describe('AutocompleteHelper', () => {
 | |
|   let mock;
 | |
|   let autocompleteHelper;
 | |
|   let dateNowOld;
 | |
| 
 | |
|   beforeEach(() => {
 | |
|     mock = new MockAdapter(axios);
 | |
|     const dataSourceUrls = {
 | |
|       members: '/members',
 | |
|       issues: '/issues',
 | |
|       snippets: '/snippets',
 | |
|       labels: '/labels',
 | |
|       epics: '/epics',
 | |
|       milestones: '/milestones',
 | |
|       mergeRequests: '/mergeRequests',
 | |
|       vulnerabilities: '/vulnerabilities',
 | |
|       commands: '/commands',
 | |
|     };
 | |
| 
 | |
|     mock.onGet('/members').reply(200, MOCK_MEMBERS);
 | |
|     mock.onGet('/issues').reply(200, MOCK_ISSUES);
 | |
|     mock.onGet('/snippets').reply(200, MOCK_SNIPPETS);
 | |
|     mock.onGet('/labels').reply(200, MOCK_LABELS);
 | |
|     mock.onGet('/epics').reply(200, MOCK_EPICS);
 | |
|     mock.onGet('/milestones').reply(200, MOCK_MILESTONES);
 | |
|     mock.onGet('/mergeRequests').reply(200, MOCK_MERGE_REQUESTS);
 | |
|     mock.onGet('/vulnerabilities').reply(200, MOCK_VULNERABILITIES);
 | |
|     mock.onGet('/commands').reply(200, MOCK_COMMANDS);
 | |
| 
 | |
|     const sidebarMediator = {
 | |
|       store: {
 | |
|         assignees: MOCK_ASSIGNEES,
 | |
|         reviewers: MOCK_REVIEWERS,
 | |
|       },
 | |
|     };
 | |
| 
 | |
|     autocompleteHelper = new AutocompleteHelper({
 | |
|       dataSourceUrls,
 | |
|       sidebarMediator,
 | |
|     });
 | |
| 
 | |
|     dateNowOld = Date.now();
 | |
| 
 | |
|     jest.spyOn(Date, 'now').mockImplementation(() => new Date('2023-11-14').getTime());
 | |
|   });
 | |
| 
 | |
|   afterEach(() => {
 | |
|     mock.restore();
 | |
| 
 | |
|     jest.spyOn(Date, 'now').mockImplementation(() => dateNowOld);
 | |
|   });
 | |
| 
 | |
|   it.each`
 | |
|     referenceType      | query
 | |
|     ${'user'}          | ${'r'}
 | |
|     ${'issue'}         | ${'q'}
 | |
|     ${'snippet'}       | ${'s'}
 | |
|     ${'label'}         | ${'c'}
 | |
|     ${'epic'}          | ${'n'}
 | |
|     ${'milestone'}     | ${'16'}
 | |
|     ${'merge_request'} | ${'n'}
 | |
|     ${'vulnerability'} | ${'cross'}
 | |
|     ${'command'}       | ${'re'}
 | |
|   `(
 | |
|     'for reference type "$referenceType", searches for "$query" correctly',
 | |
|     async ({ referenceType, query }) => {
 | |
|       const dataSource = autocompleteHelper.getDataSource(referenceType);
 | |
|       const results = await dataSource.search(query);
 | |
| 
 | |
|       expect(
 | |
|         results.map(({ title, name, username }) => username || name || title),
 | |
|       ).toMatchSnapshot();
 | |
|     },
 | |
|   );
 | |
| 
 | |
|   it.each`
 | |
|     referenceType | command
 | |
|     ${'label'}    | ${'/label'}
 | |
|     ${'label'}    | ${'/unlabel'}
 | |
|     ${'label'}    | ${'/relabel'}
 | |
|     ${'user'}     | ${'/assign'}
 | |
|     ${'user'}     | ${'/reassign'}
 | |
|     ${'user'}     | ${'/unassign'}
 | |
|     ${'user'}     | ${'/assign_reviewer'}
 | |
|     ${'user'}     | ${'/unassign_reviewer'}
 | |
|     ${'user'}     | ${'/reassign_reviewer'}
 | |
|   `(
 | |
|     'filters items based on command "$command" for reference type "$referenceType" and command',
 | |
|     async ({ referenceType, command }) => {
 | |
|       const dataSource = autocompleteHelper.getDataSource(referenceType, { command });
 | |
|       const results = await dataSource.search();
 | |
| 
 | |
|       expect(
 | |
|         results.map(({ username, name, title }) => username || name || title),
 | |
|       ).toMatchSnapshot();
 | |
|     },
 | |
|   );
 | |
| 
 | |
|   it('loads default datasources if not passed', () => {
 | |
|     gl.GfmAutoComplete = {
 | |
|       dataSources: {
 | |
|         members: '/gitlab-org/gitlab-test/-/autocomplete_sources/members',
 | |
|       },
 | |
|     };
 | |
|     autocompleteHelper = new AutocompleteHelper({});
 | |
| 
 | |
|     expect(autocompleteHelper.dataSourceUrls.members).toBe(
 | |
|       '/gitlab-org/gitlab-test/-/autocomplete_sources/members',
 | |
|     );
 | |
|   });
 | |
| });
 |