assigneeSelector.spec.jsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import React from 'react';
  2. import {AssigneeSelectorComponent} from 'app/components/assigneeSelector';
  3. import {Client} from 'app/api';
  4. import {mount} from 'enzyme';
  5. import ConfigStore from 'app/stores/configStore';
  6. import GroupStore from 'app/stores/groupStore';
  7. import MemberListStore from 'app/stores/memberListStore';
  8. import ProjectsStore from 'app/stores/projectsStore';
  9. import TeamStore from 'app/stores/teamStore';
  10. describe('AssigneeSelector', function() {
  11. let assigneeSelector;
  12. let assignMock;
  13. let openMenu;
  14. let USER_1, USER_2, USER_3;
  15. let TEAM_1;
  16. let PROJECT_1;
  17. let GROUP_1;
  18. beforeEach(function() {
  19. USER_1 = TestStubs.User({
  20. id: '1',
  21. name: 'Jane Doe',
  22. email: 'janedoe@example.com',
  23. });
  24. USER_2 = TestStubs.User({
  25. id: '2',
  26. name: 'John Smith',
  27. email: 'johnsmith@example.com',
  28. });
  29. USER_3 = TestStubs.User({
  30. id: '3',
  31. name: 'J J',
  32. email: 'jj@example.com',
  33. });
  34. TEAM_1 = TestStubs.Team({
  35. id: '3',
  36. name: 'COOL TEAM',
  37. slug: 'cool-team',
  38. });
  39. PROJECT_1 = TestStubs.Project({
  40. teams: [TEAM_1],
  41. });
  42. GROUP_1 = TestStubs.Group({
  43. id: '1337',
  44. project: {
  45. id: PROJECT_1.id,
  46. slug: PROJECT_1.slug,
  47. },
  48. });
  49. jest.spyOn(MemberListStore, 'getAll').mockImplementation(() => null);
  50. jest.spyOn(TeamStore, 'getAll').mockImplementation(() => [TEAM_1]);
  51. jest.spyOn(ProjectsStore, 'getAll').mockImplementation(() => [PROJECT_1]);
  52. jest.spyOn(GroupStore, 'get').mockImplementation(() => GROUP_1);
  53. assignMock = Client.addMockResponse({
  54. method: 'PUT',
  55. url: `/issues/${GROUP_1.id}/`,
  56. body: {
  57. ...GROUP_1,
  58. assignedTo: USER_1,
  59. },
  60. });
  61. MemberListStore.items = null;
  62. MemberListStore.loaded = false;
  63. assigneeSelector = mount(
  64. <AssigneeSelectorComponent id={GROUP_1.id} />,
  65. TestStubs.routerContext()
  66. );
  67. openMenu = () => assigneeSelector.find('DropdownButton').simulate('click');
  68. });
  69. afterEach(function() {
  70. Client.clearMockResponses();
  71. });
  72. describe('render with props', function() {
  73. it('renders members from the prop when present', async function() {
  74. assigneeSelector = mount(
  75. <AssigneeSelectorComponent id={GROUP_1.id} memberList={[USER_2, USER_3]} />,
  76. TestStubs.routerContext()
  77. );
  78. MemberListStore.loadInitialData([USER_1]);
  79. openMenu();
  80. assigneeSelector.update();
  81. expect(assigneeSelector.find('LoadingIndicator')).toHaveLength(0);
  82. expect(assigneeSelector.find('Avatar')).toHaveLength(3);
  83. expect(assigneeSelector.find('UserAvatar')).toHaveLength(2);
  84. expect(assigneeSelector.find('TeamAvatar')).toHaveLength(1);
  85. const names = assigneeSelector
  86. .find('MenuItemWrapper Label Highlight')
  87. .map(el => el.text());
  88. expect(names).toEqual([`#${TEAM_1.slug}`, USER_2.name, USER_3.name]);
  89. });
  90. });
  91. describe('putSessionUserFirst()', function() {
  92. const putSessionUserFirst = AssigneeSelectorComponent.putSessionUserFirst;
  93. it('should place the session user at the top of the member list if present', function() {
  94. jest.spyOn(ConfigStore, 'get').mockImplementation(() => ({
  95. id: '2',
  96. name: 'John Smith',
  97. email: 'johnsmith@example.com',
  98. }));
  99. expect(putSessionUserFirst([USER_1, USER_2])).toEqual([USER_2, USER_1]);
  100. ConfigStore.get.mockRestore();
  101. });
  102. it("should return the same member list if the session user isn't present", function() {
  103. jest.spyOn(ConfigStore, 'get').mockImplementation(() => ({
  104. id: '555',
  105. name: 'Here Comes a New Challenger',
  106. email: 'guile@mail.us.af.mil',
  107. }));
  108. expect(putSessionUserFirst([USER_1, USER_2])).toEqual([USER_1, USER_2]);
  109. ConfigStore.get.mockRestore();
  110. });
  111. });
  112. it('should initially have loading state', function() {
  113. openMenu();
  114. expect(assigneeSelector.find('LoadingIndicator')).toHaveLength(1);
  115. });
  116. it('does not have loading state and shows member list after calling MemberListStore.loadInitialData', async function() {
  117. openMenu();
  118. MemberListStore.loadInitialData([USER_1, USER_2]);
  119. assigneeSelector.update();
  120. expect(assigneeSelector.instance().assignableTeams()).toHaveLength(1);
  121. expect(assigneeSelector.find('LoadingIndicator')).toHaveLength(0);
  122. expect(assigneeSelector.find('Avatar')).toHaveLength(3);
  123. expect(assigneeSelector.find('UserAvatar')).toHaveLength(2);
  124. expect(assigneeSelector.find('TeamAvatar')).toHaveLength(1);
  125. });
  126. it('does NOT update member list after initial load', function() {
  127. openMenu();
  128. MemberListStore.loadInitialData([USER_1, USER_2]);
  129. assigneeSelector.update();
  130. expect(assigneeSelector.find('Avatar')).toHaveLength(3);
  131. expect(assigneeSelector.find('LoadingIndicator').exists()).toBe(false);
  132. MemberListStore.loadInitialData([USER_1, USER_2, USER_3]);
  133. assigneeSelector.update();
  134. expect(assigneeSelector.find('Avatar')).toHaveLength(3);
  135. expect(assigneeSelector.find('LoadingIndicator').exists()).toBe(false);
  136. });
  137. it('successfully assigns users', async function() {
  138. openMenu();
  139. MemberListStore.loadInitialData([USER_1, USER_2]);
  140. assigneeSelector.update();
  141. expect(assigneeSelector.find('LoadingIndicator').exists()).toBe(false);
  142. assigneeSelector
  143. .find('UserAvatar')
  144. .first()
  145. .simulate('click');
  146. expect(assignMock).toHaveBeenLastCalledWith(
  147. '/issues/1337/',
  148. expect.objectContaining({
  149. data: {assignedTo: 'user:1'},
  150. })
  151. );
  152. assigneeSelector.update();
  153. expect(assigneeSelector.find('LoadingIndicator')).toHaveLength(1);
  154. // Flakey with 1 tick
  155. await tick();
  156. await tick();
  157. assigneeSelector.update();
  158. expect(assigneeSelector.find('LoadingIndicator')).toHaveLength(0);
  159. expect(assigneeSelector.find('ActorAvatar')).toHaveLength(1);
  160. });
  161. it('successfully assigns teams', async function() {
  162. openMenu();
  163. MemberListStore.loadInitialData([USER_1, USER_2]);
  164. assigneeSelector.update();
  165. expect(assigneeSelector.find('LoadingIndicator').exists()).toBe(false);
  166. assigneeSelector
  167. .find('TeamAvatar')
  168. .first()
  169. .simulate('click');
  170. assigneeSelector.update();
  171. expect(assigneeSelector.find('LoadingIndicator').exists()).toBe(true);
  172. expect(assignMock).toHaveBeenCalledWith(
  173. '/issues/1337/',
  174. expect.objectContaining({
  175. data: {assignedTo: 'team:3'},
  176. })
  177. );
  178. // Flakey with 1 tick
  179. await tick();
  180. await tick();
  181. assigneeSelector.update();
  182. expect(assigneeSelector.find('LoadingIndicator').exists()).toBe(false);
  183. expect(assigneeSelector.find('ActorAvatar')).toHaveLength(1);
  184. });
  185. it('successfully clears assignment', async function() {
  186. openMenu();
  187. MemberListStore.loadInitialData([USER_1, USER_2]);
  188. // Assign first item in list, which is TEAM_1
  189. assigneeSelector.update();
  190. assigneeSelector
  191. .find('Avatar')
  192. .first()
  193. .simulate('click');
  194. assigneeSelector.update();
  195. expect(assigneeSelector.find('LoadingIndicator').exists()).toBe(true);
  196. expect(assignMock).toHaveBeenCalledWith(
  197. '/issues/1337/',
  198. expect.objectContaining({
  199. data: {assignedTo: 'team:3'},
  200. })
  201. );
  202. // Waiting for assignment to finish updating
  203. // Flakey with 1 tick
  204. await tick();
  205. await tick();
  206. assigneeSelector.update();
  207. openMenu();
  208. assigneeSelector
  209. .find('MenuItemWrapper[data-test-id="clear-assignee"]')
  210. .simulate('click');
  211. // api was called with empty string, clearing assignment
  212. expect(assignMock).toHaveBeenLastCalledWith(
  213. '/issues/1337/',
  214. expect.objectContaining({
  215. data: {assignedTo: ''},
  216. })
  217. );
  218. });
  219. it('shows invite member button', async function() {
  220. const routerContext = TestStubs.routerContext();
  221. openMenu();
  222. MemberListStore.loadInitialData([USER_1, USER_2]);
  223. assigneeSelector.update();
  224. expect(assigneeSelector.find('LoadingIndicator').exists()).toBe(false);
  225. expect(
  226. assigneeSelector.find('InviteMemberLink[data-test-id="invite-member"]')
  227. ).toHaveLength(0);
  228. assigneeSelector.unmount();
  229. jest.spyOn(ConfigStore, 'get').mockImplementation(() => true);
  230. assigneeSelector = mount(
  231. <AssigneeSelectorComponent id={GROUP_1.id} />,
  232. routerContext
  233. );
  234. await tick();
  235. assigneeSelector.update();
  236. openMenu();
  237. expect(
  238. assigneeSelector.find('InviteMemberLink[data-test-id="invite-member"]')
  239. ).toHaveLength(1);
  240. ConfigStore.get.mockRestore();
  241. });
  242. it('requires org:write to invite member', async function() {
  243. MemberListStore.loadInitialData([USER_1, USER_2]);
  244. jest.spyOn(ConfigStore, 'get').mockImplementation(() => true);
  245. // Remove org:write access permission and make sure invite member button is not shown.
  246. assigneeSelector.unmount();
  247. assigneeSelector = mount(
  248. <AssigneeSelectorComponent id={GROUP_1.id} />,
  249. TestStubs.routerContext([{organization: TestStubs.Organization({access: []})}])
  250. );
  251. openMenu();
  252. assigneeSelector.update();
  253. expect(
  254. assigneeSelector.find('InviteMemberLink[data-test-id="invite-member"]')
  255. ).toHaveLength(0);
  256. ConfigStore.get.mockRestore();
  257. });
  258. it('filters user by email and selects with keyboard', async function() {
  259. openMenu();
  260. MemberListStore.loadInitialData([USER_1, USER_2]);
  261. assigneeSelector.update();
  262. expect(assigneeSelector.find('LoadingIndicator').exists()).toBe(false);
  263. assigneeSelector
  264. .find('StyledInput')
  265. .simulate('change', {target: {value: 'JohnSmith@example.com'}});
  266. expect(assigneeSelector.find('Avatar')).toHaveLength(1);
  267. expect(assigneeSelector.find('Avatar').prop('user')).toEqual(USER_2);
  268. assigneeSelector.find('StyledInput').simulate('keyDown', {key: 'Enter'});
  269. assigneeSelector.update();
  270. expect(assignMock).toHaveBeenLastCalledWith(
  271. '/issues/1337/',
  272. expect.objectContaining({
  273. data: {assignedTo: 'user:2'},
  274. })
  275. );
  276. expect(assigneeSelector.find('LoadingIndicator')).toHaveLength(1);
  277. await tick();
  278. await tick();
  279. assigneeSelector.update();
  280. expect(assigneeSelector.find('LoadingIndicator')).toHaveLength(0);
  281. expect(assigneeSelector.find('ActorAvatar')).toHaveLength(1);
  282. });
  283. });