inviteMember.spec.jsx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. import React from 'react';
  2. import {shallow, mount} from 'enzyme';
  3. import _ from 'lodash';
  4. import {InviteMember} from 'app/views/settings/organizationMembers/inviteMember';
  5. import ConfigStore from 'app/stores/configStore';
  6. jest.mock('app/api');
  7. jest.mock('jquery');
  8. describe('InviteMember', function() {
  9. let organization, baseProps, teams, baseContext;
  10. beforeEach(function() {
  11. organization = TestStubs.Organization({
  12. id: '1',
  13. slug: 'testOrg',
  14. teams: [
  15. {slug: 'bar', id: '1', name: 'bar', hasAccess: true},
  16. {slug: 'foo', id: '2', name: 'foo', hasAccess: false},
  17. ],
  18. });
  19. baseProps = {
  20. api: new MockApiClient(),
  21. params: {
  22. orgId: 'testOrg',
  23. },
  24. organization,
  25. location: {query: {}},
  26. };
  27. teams = [
  28. {slug: 'bar', id: '1', name: 'bar', hasAccess: true},
  29. {slug: 'foo', id: '2', name: 'foo', hasAccess: false},
  30. ];
  31. baseContext = TestStubs.routerContext([
  32. {
  33. organization,
  34. location: {query: {}},
  35. },
  36. ]);
  37. jest.spyOn(ConfigStore, 'getConfig').mockImplementation(() => ({
  38. id: 1,
  39. invitesEnabled: true,
  40. }));
  41. MockApiClient.clearMockResponses();
  42. MockApiClient.addMockResponse({
  43. url: '/organizations/testOrg/teams/',
  44. body: teams,
  45. });
  46. });
  47. it('should render loading', function() {
  48. const wrapper = shallow(<InviteMember {...baseProps} />, baseContext);
  49. expect(wrapper).toMatchSnapshot();
  50. });
  51. it('should render no team select when there is only one option', function() {
  52. MockApiClient.addMockResponse({
  53. url: '/organizations/testOrg/members/me/',
  54. body: {
  55. roles: [
  56. {
  57. id: '1',
  58. name: 'member',
  59. desc: 'a normal member',
  60. allowed: true,
  61. },
  62. ],
  63. },
  64. });
  65. const context = _.cloneDeep(baseContext);
  66. const team = organization.teams.slice(0, 1);
  67. organization.teams = team;
  68. const wrapper = mount(<InviteMember {...baseProps} />, context);
  69. expect(wrapper.state('selectedTeams').size).toBe(1);
  70. expect(wrapper.state('selectedTeams').has(team[0].slug)).toBe(true);
  71. });
  72. it('should use invite/add language based on config', function() {
  73. jest.spyOn(ConfigStore, 'getConfig').mockImplementation(() => ({
  74. id: 1,
  75. invitesEnabled: false,
  76. }));
  77. const wrapper = shallow(<InviteMember {...baseProps} />, baseContext);
  78. wrapper.setState({
  79. loading: false,
  80. });
  81. // Lets just target message
  82. expect(wrapper.find('TextBlock')).toMatchSnapshot();
  83. });
  84. it('should redirect when no roles available', function() {
  85. MockApiClient.addMockResponse({
  86. url: '/organizations/testOrg/members/me/',
  87. body: {
  88. roles: [
  89. {
  90. id: '1',
  91. name: 'member',
  92. desc: 'a normal member',
  93. allowed: false,
  94. },
  95. ],
  96. },
  97. });
  98. const pushMock = jest.fn();
  99. let wrapper = mount(
  100. <InviteMember
  101. router={{
  102. push: pushMock,
  103. location: {
  104. pathname: '/settings/testOrg/members/new/',
  105. },
  106. }}
  107. {...baseProps}
  108. />,
  109. baseContext
  110. );
  111. expect(pushMock).toHaveBeenCalledWith('/settings/testOrg/members/');
  112. expect(wrapper.state('loading')).toBe(false);
  113. wrapper = mount(
  114. <InviteMember
  115. router={{
  116. push: pushMock,
  117. location: {
  118. pathname: '/organizations/testOrg/members/new/',
  119. },
  120. }}
  121. {...baseProps}
  122. />,
  123. baseContext
  124. );
  125. expect(pushMock).toHaveBeenCalledWith('/organizations/testOrg/members/');
  126. });
  127. it('should render roles when available and allowed, and handle submitting', function() {
  128. MockApiClient.addMockResponse({
  129. url: '/organizations/testOrg/members/me/',
  130. body: {
  131. roles: [
  132. {id: '1', name: 'member', desc: 'a normal member', allowed: true},
  133. {id: '2', name: 'bar', desc: 'another role', allowed: true},
  134. ],
  135. },
  136. });
  137. const inviteRequest = {
  138. url: '/organizations/testOrg/members/',
  139. method: 'POST',
  140. statusCode: 200,
  141. body: {},
  142. };
  143. const mock = MockApiClient.addMockResponse(inviteRequest);
  144. const wrapper = mount(<InviteMember {...baseProps} />, baseContext);
  145. expect(wrapper.state('loading')).toBe(false);
  146. let node = wrapper.find('RoleSelect PanelItem').first();
  147. node.props().onClick();
  148. expect(wrapper).toMatchSnapshot();
  149. node = wrapper.find('.invite-member-submit').first();
  150. node.props().onClick({preventDefault: () => {}});
  151. expect(wrapper.state('busy')).toBe(false);
  152. wrapper.setState({email: 'test@email.com, test2@email.com, test3@email.com, '});
  153. node.props().onClick({preventDefault: () => {}});
  154. expect(wrapper.state('busy')).toBe(true);
  155. expect(wrapper.state('error')).toBe(undefined);
  156. expect(mock).toHaveBeenCalledTimes(3);
  157. });
  158. it('shows an error when submitting an invalid email', async function() {
  159. MockApiClient.addMockResponse({
  160. url: '/organizations/testOrg/members/me/',
  161. body: {
  162. roles: [
  163. {id: '1', name: 'member', desc: 'a normal member', allowed: true},
  164. {id: '2', name: 'bar', desc: 'another role', allowed: true},
  165. ],
  166. },
  167. });
  168. const inviteRequest = {
  169. url: '/organizations/testOrg/members/',
  170. method: 'POST',
  171. statusCode: 400,
  172. body: {
  173. email: ['Enter a valid email address.'],
  174. },
  175. };
  176. const mock = MockApiClient.addMockResponse(inviteRequest);
  177. const wrapper = mount(<InviteMember {...baseProps} />, baseContext);
  178. let node = wrapper.find('RoleSelect PanelItem').first();
  179. node.props().onClick();
  180. node = wrapper.find('.invite-member-submit').first();
  181. node.props().onClick({preventDefault: () => {}});
  182. expect(wrapper.state('busy')).toBe(false);
  183. wrapper.setState({email: 'invalid-email'});
  184. node.props().onClick({preventDefault: () => {}});
  185. expect(wrapper.state('busy')).toBe(true);
  186. expect(wrapper.state('error')).toBe(undefined);
  187. expect(mock).toHaveBeenCalledTimes(1);
  188. await tick();
  189. wrapper.update();
  190. expect(wrapper.state('error')).toBeDefined();
  191. expect(wrapper.find('.has-error')).toHaveLength(1);
  192. expect(wrapper.find('.has-error #id-email')).toHaveLength(1);
  193. expect(wrapper.find('.has-error .error').text()).toBe('Enter a valid email address.');
  194. });
  195. it('allows teams to be removed', async function() {
  196. MockApiClient.addMockResponse({
  197. url: '/organizations/testOrg/members/me/',
  198. body: {
  199. roles: [
  200. {id: '1', name: 'member', desc: 'a normal member', allowed: true},
  201. {id: '2', name: 'bar', desc: 'another role', allowed: true},
  202. ],
  203. },
  204. });
  205. const inviteRequest = MockApiClient.addMockResponse({
  206. url: '/organizations/testOrg/members/',
  207. method: 'POST',
  208. statusCode: 200,
  209. });
  210. const wrapper = mount(<InviteMember {...baseProps} />, baseContext);
  211. // Wait for team list to load
  212. await tick();
  213. // set the email address
  214. wrapper.find('input[name="email"]').simulate('change', {
  215. target: {value: 'test@example.com'},
  216. });
  217. // Select new team to join
  218. // Open the dropdown
  219. wrapper.find('TeamSelect DropdownButton').simulate('click');
  220. // Click the first item
  221. wrapper
  222. .find('TeamSelect TeamDropdownElement')
  223. .first()
  224. .simulate('click');
  225. // Remove our one team
  226. const button = wrapper.find('TeamSelect TeamRow Button');
  227. expect(button).toHaveLength(1);
  228. button.simulate('click');
  229. // Save Member
  230. wrapper.find('Button[priority="primary"]').simulate('click');
  231. expect(inviteRequest).toHaveBeenCalledWith(
  232. expect.anything(),
  233. expect.objectContaining({
  234. data: expect.objectContaining({
  235. teams: [],
  236. }),
  237. })
  238. );
  239. });
  240. it('allows teams to be added', async function() {
  241. MockApiClient.addMockResponse({
  242. url: '/organizations/testOrg/members/me/',
  243. body: {
  244. roles: [
  245. {id: '1', name: 'member', desc: 'a normal member', allowed: true},
  246. {id: '2', name: 'bar', desc: 'another role', allowed: true},
  247. ],
  248. },
  249. });
  250. const inviteRequest = MockApiClient.addMockResponse({
  251. url: '/organizations/testOrg/members/',
  252. method: 'POST',
  253. statusCode: 200,
  254. });
  255. const wrapper = mount(<InviteMember {...baseProps} />, baseContext);
  256. // Wait for team list to fetch.
  257. await wrapper.update();
  258. // set the email address
  259. wrapper.find('input[name="email"]').simulate('change', {
  260. target: {value: 'test@example.com'},
  261. });
  262. // Select new team to join
  263. // Open the dropdown
  264. wrapper.find('TeamSelect DropdownButton').simulate('click');
  265. // Click the first item
  266. wrapper
  267. .find('TeamSelect TeamDropdownElement')
  268. .first()
  269. .simulate('click');
  270. // Save Member
  271. wrapper.find('Button[priority="primary"]').simulate('click');
  272. expect(inviteRequest).toHaveBeenCalledWith(
  273. expect.anything(),
  274. expect.objectContaining({
  275. data: expect.objectContaining({
  276. teams: ['bar'],
  277. }),
  278. })
  279. );
  280. });
  281. });