ownershipRulesTable.spec.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import {CodeOwnerFixture} from 'sentry-fixture/codeOwner';
  2. import {UserFixture} from 'sentry-fixture/user';
  3. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  4. import ConfigStore from 'sentry/stores/configStore';
  5. import MemberListStore from 'sentry/stores/memberListStore';
  6. import type {Actor} from 'sentry/types/core';
  7. import type {ParsedOwnershipRule} from 'sentry/types/group';
  8. import {OwnershipRulesTable} from './ownershipRulesTable';
  9. describe('OwnershipRulesTable', () => {
  10. const user1 = UserFixture();
  11. const user2 = UserFixture({id: '2', name: 'Jane Doe'});
  12. beforeEach(() => {
  13. ConfigStore.init();
  14. ConfigStore.set('user', user1);
  15. MemberListStore.init();
  16. MemberListStore.loadInitialData([user1, user2]);
  17. });
  18. it('should render empty state', async () => {
  19. render(<OwnershipRulesTable projectRules={[]} codeowners={[]} />);
  20. expect(await screen.findByText('No ownership rules found')).toBeInTheDocument();
  21. });
  22. it('should render project owners members', async () => {
  23. const rules: ParsedOwnershipRule[] = [
  24. {
  25. matcher: {pattern: 'pattern', type: 'path'},
  26. owners: [{type: 'user', id: user1.id, name: user1.name}],
  27. },
  28. ];
  29. render(<OwnershipRulesTable projectRules={rules} codeowners={[]} />);
  30. expect(await screen.findByText('path')).toBeInTheDocument();
  31. expect(screen.getByText('pattern')).toBeInTheDocument();
  32. expect(screen.getByText(user1.name)).toBeInTheDocument();
  33. });
  34. it('should filter codeowners rules without actor names', async () => {
  35. const rules: ParsedOwnershipRule[] = [
  36. {
  37. matcher: {pattern: 'pattern', type: 'path'},
  38. // Name = undefined only seems to happen when adding a new codeowners file
  39. owners: [{type: 'user', id: user1.id, name: undefined as any}],
  40. },
  41. {
  42. matcher: {pattern: 'my/path', type: 'path'},
  43. owners: [{type: 'user', id: user2.id, name: user2.name}],
  44. },
  45. ];
  46. render(
  47. <OwnershipRulesTable
  48. projectRules={[]}
  49. codeowners={[CodeOwnerFixture({schema: {rules, version: 1}})]}
  50. />
  51. );
  52. expect(await screen.findByText('pattern')).toBeInTheDocument();
  53. expect(screen.getByText('my/path')).toBeInTheDocument();
  54. expect(screen.getByRole('button', {name: 'Everyone'})).toBeEnabled();
  55. });
  56. it('should render multiple project owners', async () => {
  57. const rules: ParsedOwnershipRule[] = [
  58. {
  59. matcher: {pattern: 'pattern', type: 'path'},
  60. owners: [
  61. {type: 'user', id: user1.id, name: user1.name},
  62. {type: 'user', id: user2.id, name: user2.name},
  63. ],
  64. },
  65. ];
  66. render(<OwnershipRulesTable projectRules={rules} codeowners={[]} />);
  67. expect(await screen.findByText('path')).toBeInTheDocument();
  68. expect(screen.getByText('pattern')).toBeInTheDocument();
  69. expect(screen.getByText(`${user1.name} and 1 other`)).toBeInTheDocument();
  70. expect(screen.queryByText(user2.name)).not.toBeInTheDocument();
  71. });
  72. it('should filter by rule type and pattern', async () => {
  73. const owners: Actor[] = [{type: 'user', id: user1.id, name: user1.name}];
  74. const rules: ParsedOwnershipRule[] = [
  75. {matcher: {pattern: 'filepath', type: 'path'}, owners},
  76. {matcher: {pattern: 'mytag', type: 'tag'}, owners},
  77. ];
  78. render(<OwnershipRulesTable projectRules={rules} codeowners={[]} />);
  79. const searchbar = screen.getByPlaceholderText('Search by type or rule');
  80. await userEvent.click(searchbar);
  81. await userEvent.paste('path');
  82. expect(screen.getByText('filepath')).toBeInTheDocument();
  83. expect(screen.queryByText('mytag')).not.toBeInTheDocument();
  84. // Change the filter to mytag
  85. await userEvent.clear(searchbar);
  86. await userEvent.paste('mytag');
  87. expect(screen.getByText('mytag')).toBeInTheDocument();
  88. expect(screen.queryByText('filepath')).not.toBeInTheDocument();
  89. });
  90. it('should filter by my teams by default', async () => {
  91. const rules: ParsedOwnershipRule[] = [
  92. {
  93. matcher: {pattern: 'filepath', type: 'path'},
  94. owners: [{type: 'user', id: user1.id, name: user1.name}],
  95. },
  96. {
  97. matcher: {pattern: 'mytag', type: 'tag'},
  98. owners: [{type: 'user', id: user2.id, name: user2.name}],
  99. },
  100. ];
  101. render(<OwnershipRulesTable projectRules={rules} codeowners={[]} />);
  102. expect(screen.getByText('filepath')).toBeInTheDocument();
  103. expect(screen.queryByText('mytag')).not.toBeInTheDocument();
  104. // Clear the filter
  105. await userEvent.click(screen.getByRole('button', {name: 'My Teams'}));
  106. await userEvent.click(screen.getByRole('button', {name: 'Clear'}));
  107. expect(screen.getByText('filepath')).toBeInTheDocument();
  108. expect(screen.queryByText('mytag')).toBeInTheDocument();
  109. });
  110. it('preserves selected teams when rules are updated', async () => {
  111. const rules: ParsedOwnershipRule[] = [
  112. {
  113. matcher: {pattern: 'filepath', type: 'path'},
  114. owners: [{type: 'user', id: user1.id, name: user1.name}],
  115. },
  116. {
  117. matcher: {pattern: 'anotherpath', type: 'path'},
  118. owners: [{type: 'user', id: user2.id, name: user2.name}],
  119. },
  120. ];
  121. const {rerender} = render(
  122. <OwnershipRulesTable projectRules={rules} codeowners={[]} />
  123. );
  124. // Clear the filter
  125. await userEvent.click(screen.getByRole('button', {name: 'My Teams'}));
  126. await userEvent.click(screen.getByRole('button', {name: 'Clear'}));
  127. expect(screen.getAllByText('path')).toHaveLength(2);
  128. const newRules: ParsedOwnershipRule[] = [
  129. ...rules,
  130. {
  131. matcher: {pattern: 'thirdpath', type: 'path'},
  132. owners: [{type: 'user', id: user2.id, name: user2.name}],
  133. },
  134. ];
  135. rerender(<OwnershipRulesTable projectRules={newRules} codeowners={[]} />);
  136. expect(screen.getAllByText('path')).toHaveLength(3);
  137. expect(screen.getByRole('button', {name: 'Everyone'})).toBeInTheDocument();
  138. });
  139. it('should paginate results', async () => {
  140. const owners: Actor[] = [{type: 'user', id: user1.id, name: user1.name}];
  141. const rules: ParsedOwnershipRule[] = Array(100)
  142. .fill(0)
  143. .map((_, i) => ({
  144. matcher: {pattern: `mytag${i}`, type: 'tag'},
  145. owners,
  146. }));
  147. render(<OwnershipRulesTable projectRules={rules} codeowners={[]} />);
  148. expect(screen.getByText('mytag1')).toBeInTheDocument();
  149. await userEvent.click(screen.getByRole('button', {name: 'Next page'}));
  150. expect(screen.getByText('mytag30')).toBeInTheDocument();
  151. expect(screen.queryByText('mytag1')).not.toBeInTheDocument();
  152. });
  153. });