teamSelector.spec.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import selectEvent from 'react-select-event';
  2. import {Organization} from 'sentry-fixture/organization';
  3. import {act, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  4. import {openCreateTeamModal} from 'sentry/actionCreators/modal';
  5. import {addTeamToProject} from 'sentry/actionCreators/projects';
  6. import {TeamSelector} from 'sentry/components/teamSelector';
  7. import OrganizationStore from 'sentry/stores/organizationStore';
  8. import TeamStore from 'sentry/stores/teamStore';
  9. jest.mock('sentry/actionCreators/projects', () => ({
  10. addTeamToProject: jest.fn(),
  11. }));
  12. jest.mock('sentry/actionCreators/modal', () => ({
  13. openCreateTeamModal: jest.fn(),
  14. }));
  15. const teamData = [
  16. {
  17. id: '1',
  18. slug: 'team1',
  19. name: 'Team 1',
  20. },
  21. {
  22. id: '2',
  23. slug: 'team2',
  24. name: 'Team 2',
  25. },
  26. {
  27. id: '3',
  28. slug: 'team3',
  29. name: 'Team 3',
  30. },
  31. ];
  32. const teams = teamData.map(data => TestStubs.Team(data));
  33. const project = TestStubs.Project({teams: [teams[0]]});
  34. const organization = Organization({access: ['project:write']});
  35. act(() => OrganizationStore.onUpdate(organization, {replace: true}));
  36. function createWrapper(props: Partial<React.ComponentProps<typeof TeamSelector>> = {}) {
  37. return render(
  38. <TeamSelector
  39. organization={organization}
  40. name="teamSelector"
  41. aria-label="Select a team"
  42. onChange={() => {}}
  43. {...props}
  44. />
  45. );
  46. }
  47. describe('Team Selector', function () {
  48. beforeEach(function () {
  49. TeamStore.loadInitialData(teams);
  50. });
  51. it('renders options', async function () {
  52. createWrapper();
  53. await userEvent.type(screen.getByText('Select...'), '{keyDown}');
  54. expect(screen.getByText('#team1')).toBeInTheDocument();
  55. expect(screen.getByText('#team2')).toBeInTheDocument();
  56. expect(screen.getByText('#team3')).toBeInTheDocument();
  57. });
  58. it('selects an option', async function () {
  59. const onChangeMock = jest.fn();
  60. createWrapper({onChange: onChangeMock});
  61. await userEvent.type(screen.getByText('Select...'), '{keyDown}');
  62. const option = screen.getByText('#team1');
  63. await userEvent.click(option);
  64. expect(onChangeMock).toHaveBeenCalledWith(expect.objectContaining({value: 'team1'}));
  65. });
  66. it('respects the team filter', async function () {
  67. const teamFilter = team => team.slug === 'team1';
  68. createWrapper({teamFilter});
  69. await userEvent.type(screen.getByText('Select...'), '{keyDown}');
  70. expect(screen.getByText('#team1')).toBeInTheDocument();
  71. // These options should be filtered out
  72. expect(screen.queryByText('#team2')).not.toBeInTheDocument();
  73. expect(screen.queryByText('#team3')).not.toBeInTheDocument();
  74. });
  75. it('respects the project filter', async function () {
  76. createWrapper({project});
  77. await userEvent.type(screen.getByText('Select...'), '{keyDown}');
  78. expect(screen.getByText('#team1')).toBeInTheDocument();
  79. // team2 and team3 should have add to project buttons
  80. expect(screen.getAllByRole('button').length).toBe(2);
  81. });
  82. it('respects the team and project filter', async function () {
  83. const teamFilter = team => team.slug === 'team1' || team.slug === 'team2';
  84. createWrapper({teamFilter, project});
  85. await userEvent.type(screen.getByText('Select...'), '{keyDown}');
  86. expect(screen.getByText('#team1')).toBeInTheDocument();
  87. // team3 should be filtered out
  88. expect(screen.queryByText('#team3')).not.toBeInTheDocument();
  89. // team2 should have add to project buttons
  90. expect(screen.getAllByRole('button').length).toBe(1);
  91. });
  92. it('allows you to add teams outside of project', async function () {
  93. createWrapper({project});
  94. await userEvent.type(screen.getByText('Select...'), '{keyDown}');
  95. expect(screen.getByText('#team1')).toBeInTheDocument();
  96. // team2 and team3 should have add to project buttons
  97. const addToProjectButtons = screen.getAllByRole('button');
  98. await userEvent.click(addToProjectButtons[0]);
  99. expect(addTeamToProject).toHaveBeenCalled();
  100. });
  101. it('allows searching by slug with useId', async function () {
  102. const onChangeMock = jest.fn();
  103. createWrapper({useId: true, onChange: onChangeMock});
  104. await userEvent.type(screen.getByText('Select...'), '{keyDown}');
  105. MockApiClient.addMockResponse({
  106. url: `/organizations/${organization.slug}/teams/`,
  107. });
  108. await userEvent.type(screen.getByLabelText('Select a team'), 'team2');
  109. expect(screen.getByText('#team2')).toBeInTheDocument();
  110. await userEvent.click(screen.getByText('#team2'));
  111. expect(onChangeMock).toHaveBeenCalledWith(expect.objectContaining({value: '2'}));
  112. // Wait for store to be updated from API response
  113. await act(tick);
  114. });
  115. it('allows to create a new team if org admin', async function () {
  116. MockApiClient.addMockResponse({
  117. url: `/organizations/${organization.slug}/teams/`,
  118. });
  119. const onChangeMock = jest.fn();
  120. const orgWithAccess = Organization({access: ['project:admin']});
  121. createWrapper({
  122. allowCreate: true,
  123. onChange: onChangeMock,
  124. organization: orgWithAccess,
  125. });
  126. await userEvent.type(screen.getByText('Select...'), '{keyDown}');
  127. await userEvent.click(screen.getByText('Create team'));
  128. // it opens the create team modal
  129. expect(openCreateTeamModal).toHaveBeenCalled();
  130. });
  131. it('allows to create a new team if org admin (multiple select)', async function () {
  132. MockApiClient.addMockResponse({
  133. url: `/organizations/${organization.slug}/teams/`,
  134. });
  135. const onChangeMock = jest.fn();
  136. const orgWithAccess = Organization({access: ['project:admin']});
  137. createWrapper({
  138. allowCreate: true,
  139. onChange: onChangeMock,
  140. organization: orgWithAccess,
  141. });
  142. await selectEvent.select(screen.getByText('Select...'), '#team1');
  143. // it does no open the create team modal yet
  144. expect(openCreateTeamModal).not.toHaveBeenCalled();
  145. await selectEvent.select(screen.getByText('#team1'), ['#team2', 'Create team']);
  146. // it opens the create team modal since the create team option is selected
  147. expect(openCreateTeamModal).toHaveBeenCalled();
  148. });
  149. it('does not allow to create a new team if not org owner', async function () {
  150. MockApiClient.addMockResponse({
  151. url: `/organizations/${organization.slug}/teams/`,
  152. });
  153. const onChangeMock = jest.fn();
  154. const orgWithoutAccess = Organization({access: ['project:write']});
  155. createWrapper({
  156. allowCreate: true,
  157. onChange: onChangeMock,
  158. organization: orgWithoutAccess,
  159. });
  160. await userEvent.type(screen.getByText('Select...'), '{keyDown}');
  161. await userEvent.click(screen.getByText('Create team'));
  162. // it does no open the create team modal
  163. expect(openCreateTeamModal).not.toHaveBeenCalled();
  164. });
  165. });