PageRenderTime 130ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/spec/frontend/filtered_search/visual_token_value_spec.js

https://gitlab.com/523/gitlab-ce
JavaScript | 354 lines | 273 code | 81 blank | 0 comment | 8 complexity | 949c21a33227173b445b1836219d8d5d MD5 | raw file
  1. import { escape } from 'lodash';
  2. import labelData from 'test_fixtures/labels/project_labels.json';
  3. import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
  4. import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper';
  5. import { TEST_HOST } from 'helpers/test_constants';
  6. import DropdownUtils from '~/filtered_search/dropdown_utils';
  7. import VisualTokenValue from '~/filtered_search/visual_token_value';
  8. import createFlash from '~/flash';
  9. import AjaxCache from '~/lib/utils/ajax_cache';
  10. import UsersCache from '~/lib/utils/users_cache';
  11. jest.mock('~/flash');
  12. describe('Filtered Search Visual Tokens', () => {
  13. const findElements = (tokenElement) => {
  14. const tokenNameElement = tokenElement.querySelector('.name');
  15. const tokenValueContainer = tokenElement.querySelector('.value-container');
  16. const tokenValueElement = tokenValueContainer.querySelector('.value');
  17. const tokenOperatorElement = tokenElement.querySelector('.operator');
  18. const tokenType = tokenNameElement.innerText.toLowerCase();
  19. const tokenValue = tokenValueElement.innerText;
  20. const tokenOperator = tokenOperatorElement.innerText;
  21. const subject = new VisualTokenValue(tokenValue, tokenType, tokenOperator);
  22. return { subject, tokenValueContainer, tokenValueElement };
  23. };
  24. let tokensContainer;
  25. let authorToken;
  26. let bugLabelToken;
  27. beforeEach(() => {
  28. setHTMLFixture(`
  29. <ul class="tokens-container">
  30. ${FilteredSearchSpecHelper.createInputHTML()}
  31. </ul>
  32. `);
  33. tokensContainer = document.querySelector('.tokens-container');
  34. authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '=', '@user');
  35. bugLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '=', '~bug');
  36. });
  37. afterEach(() => {
  38. resetHTMLFixture();
  39. });
  40. describe('updateUserTokenAppearance', () => {
  41. let usersCacheSpy;
  42. beforeEach(() => {
  43. jest.spyOn(UsersCache, 'retrieve').mockImplementation((username) => usersCacheSpy(username));
  44. });
  45. it('ignores error if UsersCache throws', async () => {
  46. const dummyError = new Error('Earth rotated backwards');
  47. const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
  48. const tokenValue = tokenValueElement.innerText;
  49. usersCacheSpy = (username) => {
  50. expect(`@${username}`).toBe(tokenValue);
  51. return Promise.reject(dummyError);
  52. };
  53. await subject.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue);
  54. expect(createFlash.mock.calls.length).toBe(0);
  55. });
  56. it('does nothing if user cannot be found', async () => {
  57. const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
  58. const tokenValue = tokenValueElement.innerText;
  59. usersCacheSpy = (username) => {
  60. expect(`@${username}`).toBe(tokenValue);
  61. return Promise.resolve(undefined);
  62. };
  63. await subject.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue);
  64. expect(tokenValueElement.innerText).toBe(tokenValue);
  65. });
  66. it('replaces author token with avatar and display name', async () => {
  67. const dummyUser = {
  68. name: 'Important Person',
  69. avatar_url: 'https://host.invalid/mypics/avatar.png',
  70. };
  71. const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
  72. const tokenValue = tokenValueElement.innerText;
  73. usersCacheSpy = (username) => {
  74. expect(`@${username}`).toBe(tokenValue);
  75. return Promise.resolve(dummyUser);
  76. };
  77. await subject.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue);
  78. expect(tokenValueContainer.dataset.originalValue).toBe(tokenValue);
  79. expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name);
  80. const avatar = tokenValueElement.querySelector('img.avatar');
  81. expect(avatar.getAttribute('src')).toBe(dummyUser.avatar_url);
  82. expect(avatar.getAttribute('alt')).toBe('');
  83. });
  84. it('escapes user name when creating token', async () => {
  85. const dummyUser = {
  86. name: '<script>',
  87. avatar_url: `${TEST_HOST}/mypics/avatar.png`,
  88. };
  89. const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
  90. const tokenValue = tokenValueElement.innerText;
  91. usersCacheSpy = (username) => {
  92. expect(`@${username}`).toBe(tokenValue);
  93. return Promise.resolve(dummyUser);
  94. };
  95. await subject.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue);
  96. expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name);
  97. tokenValueElement.querySelector('.avatar').remove();
  98. expect(tokenValueElement.innerHTML.trim()).toBe(escape(dummyUser.name));
  99. });
  100. });
  101. describe('updateLabelTokenColor', () => {
  102. const dummyEndpoint = '/dummy/endpoint';
  103. const missingLabelToken = FilteredSearchSpecHelper.createFilterVisualToken(
  104. 'label',
  105. '=',
  106. '~doesnotexist',
  107. );
  108. const spaceLabelToken = FilteredSearchSpecHelper.createFilterVisualToken(
  109. 'label',
  110. '=',
  111. '~"some space"',
  112. );
  113. beforeEach(() => {
  114. tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
  115. ${bugLabelToken.outerHTML}
  116. ${missingLabelToken.outerHTML}
  117. ${spaceLabelToken.outerHTML}
  118. `);
  119. const filteredSearchInput = document.querySelector('.filtered-search');
  120. filteredSearchInput.dataset.runnerTagsEndpoint = `${dummyEndpoint}/admin/runners/tag_list`;
  121. filteredSearchInput.dataset.labelsEndpoint = `${dummyEndpoint}/-/labels`;
  122. filteredSearchInput.dataset.milestonesEndpoint = `${dummyEndpoint}/-/milestones`;
  123. AjaxCache.internalStorage = {};
  124. AjaxCache.internalStorage[`${filteredSearchInput.dataset.labelsEndpoint}.json`] = labelData;
  125. });
  126. const parseColor = (color) => {
  127. const dummyElement = document.createElement('div');
  128. dummyElement.style.color = color;
  129. return dummyElement.style.color;
  130. };
  131. const expectValueContainerStyle = (tokenValueContainer, label) => {
  132. expect(tokenValueContainer.getAttribute('style')).not.toBe(null);
  133. expect(tokenValueContainer.style.backgroundColor).toBe(parseColor(label.color));
  134. expect(tokenValueContainer.style.color).toBe(parseColor(label.text_color));
  135. };
  136. const findLabel = (tokenValue) =>
  137. labelData.find((label) => tokenValue === `~${DropdownUtils.getEscapedText(label.title)}`);
  138. it('updates the color of a label token', async () => {
  139. const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
  140. const tokenValue = tokenValueElement.innerText;
  141. const matchingLabel = findLabel(tokenValue);
  142. await subject.updateLabelTokenColor(tokenValueContainer, tokenValue);
  143. expectValueContainerStyle(tokenValueContainer, matchingLabel);
  144. });
  145. it('updates the color of a label token with spaces', async () => {
  146. const { subject, tokenValueContainer, tokenValueElement } = findElements(spaceLabelToken);
  147. const tokenValue = tokenValueElement.innerText;
  148. const matchingLabel = findLabel(tokenValue);
  149. await subject.updateLabelTokenColor(tokenValueContainer, tokenValue);
  150. expectValueContainerStyle(tokenValueContainer, matchingLabel);
  151. });
  152. it('does not change color of a missing label', async () => {
  153. const { subject, tokenValueContainer, tokenValueElement } = findElements(missingLabelToken);
  154. const tokenValue = tokenValueElement.innerText;
  155. const matchingLabel = findLabel(tokenValue);
  156. expect(matchingLabel).toBe(undefined);
  157. await subject.updateLabelTokenColor(tokenValueContainer, tokenValue);
  158. expect(tokenValueContainer.getAttribute('style')).toBe(null);
  159. });
  160. });
  161. describe('setTokenStyle', () => {
  162. let originalTextColor;
  163. beforeEach(() => {
  164. originalTextColor = bugLabelToken.style.color;
  165. });
  166. it('should set backgroundColor', () => {
  167. const originalBackgroundColor = bugLabelToken.style.backgroundColor;
  168. const token = VisualTokenValue.setTokenStyle(bugLabelToken, 'blue', 'white');
  169. expect(token.style.backgroundColor).toEqual('blue');
  170. expect(token.style.backgroundColor).not.toEqual(originalBackgroundColor);
  171. });
  172. it('should set textColor', () => {
  173. const token = VisualTokenValue.setTokenStyle(bugLabelToken, 'white', 'black');
  174. expect(token.style.color).toEqual('black');
  175. expect(token.style.color).not.toEqual(originalTextColor);
  176. });
  177. it('should add inverted class when textColor is #FFFFFF', () => {
  178. const token = VisualTokenValue.setTokenStyle(bugLabelToken, 'black', '#FFFFFF');
  179. expect(token.style.color).toEqual('rgb(255, 255, 255)');
  180. expect(token.style.color).not.toEqual(originalTextColor);
  181. expect(token.querySelector('.remove-token').classList.contains('inverted')).toEqual(true);
  182. });
  183. });
  184. describe('render', () => {
  185. const setupSpies = (subject) => {
  186. jest.spyOn(subject, 'updateLabelTokenColor').mockImplementation(() => {});
  187. const updateLabelTokenColorSpy = subject.updateLabelTokenColor;
  188. jest.spyOn(subject, 'updateUserTokenAppearance').mockImplementation(() => {});
  189. const updateUserTokenAppearanceSpy = subject.updateUserTokenAppearance;
  190. return { updateLabelTokenColorSpy, updateUserTokenAppearanceSpy };
  191. };
  192. const keywordToken = FilteredSearchSpecHelper.createFilterVisualToken('search');
  193. const milestoneToken = FilteredSearchSpecHelper.createFilterVisualToken(
  194. 'milestone',
  195. 'upcoming',
  196. );
  197. beforeEach(() => {
  198. tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
  199. ${authorToken.outerHTML}
  200. ${bugLabelToken.outerHTML}
  201. ${keywordToken.outerHTML}
  202. ${milestoneToken.outerHTML}
  203. `);
  204. });
  205. it('renders a author token value element', () => {
  206. const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
  207. const { updateLabelTokenColorSpy, updateUserTokenAppearanceSpy } = setupSpies(subject);
  208. subject.render(tokenValueContainer, tokenValueElement);
  209. expect(updateUserTokenAppearanceSpy.mock.calls.length).toBe(1);
  210. const expectedArgs = [tokenValueContainer, tokenValueElement];
  211. expect(updateUserTokenAppearanceSpy.mock.calls[0]).toEqual(expectedArgs);
  212. expect(updateLabelTokenColorSpy.mock.calls.length).toBe(0);
  213. });
  214. it('renders a label token value element', () => {
  215. const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
  216. const { updateLabelTokenColorSpy, updateUserTokenAppearanceSpy } = setupSpies(subject);
  217. subject.render(tokenValueContainer, tokenValueElement);
  218. expect(updateLabelTokenColorSpy.mock.calls.length).toBe(1);
  219. const expectedArgs = [tokenValueContainer];
  220. expect(updateLabelTokenColorSpy.mock.calls[0]).toEqual(expectedArgs);
  221. expect(updateUserTokenAppearanceSpy.mock.calls.length).toBe(0);
  222. });
  223. it('renders a milestone token value element', () => {
  224. const { subject, tokenValueContainer, tokenValueElement } = findElements(milestoneToken);
  225. const { updateLabelTokenColorSpy, updateUserTokenAppearanceSpy } = setupSpies(subject);
  226. subject.render(tokenValueContainer, tokenValueElement);
  227. expect(updateLabelTokenColorSpy.mock.calls.length).toBe(0);
  228. expect(updateUserTokenAppearanceSpy.mock.calls.length).toBe(0);
  229. });
  230. it('does not update user token appearance for `none` filter', () => {
  231. const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
  232. subject.tokenValue = 'none';
  233. const { updateUserTokenAppearanceSpy } = setupSpies(subject);
  234. subject.render(tokenValueContainer, tokenValueElement);
  235. expect(updateUserTokenAppearanceSpy.mock.calls.length).toBe(0);
  236. });
  237. it('does not update user token appearance for `None` filter', () => {
  238. const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
  239. subject.tokenValue = 'None';
  240. const { updateUserTokenAppearanceSpy } = setupSpies(subject);
  241. subject.render(tokenValueContainer, tokenValueElement);
  242. expect(updateUserTokenAppearanceSpy.mock.calls.length).toBe(0);
  243. });
  244. it('does not update user token appearance for `any` filter', () => {
  245. const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
  246. subject.tokenValue = 'any';
  247. const { updateUserTokenAppearanceSpy } = setupSpies(subject);
  248. subject.render(tokenValueContainer, tokenValueElement);
  249. expect(updateUserTokenAppearanceSpy.mock.calls.length).toBe(0);
  250. });
  251. it('does not update label token color for `None` filter', () => {
  252. const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
  253. subject.tokenValue = 'None';
  254. const { updateLabelTokenColorSpy } = setupSpies(subject);
  255. subject.render(tokenValueContainer, tokenValueElement);
  256. expect(updateLabelTokenColorSpy.mock.calls.length).toBe(0);
  257. });
  258. it('does not update label token color for `none` filter', () => {
  259. const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
  260. subject.tokenValue = 'none';
  261. const { updateLabelTokenColorSpy } = setupSpies(subject);
  262. subject.render(tokenValueContainer, tokenValueElement);
  263. expect(updateLabelTokenColorSpy.mock.calls.length).toBe(0);
  264. });
  265. it('does not update label token color for `any` filter', () => {
  266. const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
  267. subject.tokenValue = 'any';
  268. const { updateLabelTokenColorSpy } = setupSpies(subject);
  269. subject.render(tokenValueContainer, tokenValueElement);
  270. expect(updateLabelTokenColorSpy.mock.calls.length).toBe(0);
  271. });
  272. });
  273. });