Browse Source

test(ui): Convert IssueListActions to RTL (#38275)

Malachi Willey 2 years ago
parent
commit
757c636028
2 changed files with 168 additions and 365 deletions
  1. 163 363
      static/app/views/issueList/actions.spec.jsx
  2. 5 2
      static/app/views/issueList/actions/index.tsx

+ 163 - 363
static/app/views/issueList/actions.spec.jsx

@@ -1,68 +1,71 @@
-import {act} from 'react-dom/test-utils';
+import React from 'react';
 
-import {selectDropdownMenuItem} from 'sentry-test/dropdownMenu';
-import {mountWithTheme} from 'sentry-test/enzyme';
 import {initializeOrg} from 'sentry-test/initializeOrg';
-import {mountGlobalModal} from 'sentry-test/modal';
-import {selectByLabel} from 'sentry-test/select-new';
-import {triggerPress} from 'sentry-test/utils';
-
+import {
+  fireEvent,
+  render,
+  screen,
+  userEvent,
+  within,
+} from 'sentry-test/reactTestingLibrary';
+
+import GlobalModal from 'sentry/components/globalModal';
 import GroupStore from 'sentry/stores/groupStore';
 import SelectedGroupStore from 'sentry/stores/selectedGroupStore';
 import {IssueListActions} from 'sentry/views/issueList/actions';
 
 describe('IssueListActions', function () {
-  let actions;
-  let actionsWrapper;
-  let wrapper;
+  let org, defaultProps;
 
   afterEach(() => {
     jest.restoreAllMocks();
   });
 
+  beforeEach(() => {
+    const organization = initializeOrg();
+    org = organization.org;
+
+    GroupStore.reset();
+    SelectedGroupStore.reset();
+    SelectedGroupStore.add(['1', '2', '3']);
+
+    defaultProps = {
+      api: new MockApiClient(),
+      allResultsVisible: false,
+      query: '',
+      queryCount: 15,
+      organization: org,
+      projectId: 'project-slug',
+      selection: {
+        projects: [1],
+        environments: [],
+        datetime: {start: null, end: null, period: null, utc: true},
+      },
+      groupIds: ['1', '2', '3'],
+      onRealtimeChange: jest.fn(),
+      onSelectStatsPeriod: jest.fn(),
+      realtimeActive: false,
+      statsPeriod: '24h',
+    };
+  });
+
   describe('Bulk', function () {
     describe('Total results greater than bulk limit', function () {
-      beforeAll(function () {
-        const {routerContext, org} = initializeOrg();
-
-        SelectedGroupStore.reset();
-        SelectedGroupStore.add(['1', '2', '3']);
-        wrapper = mountWithTheme(
-          <IssueListActions
-            api={new MockApiClient()}
-            allResultsVisible={false}
-            query=""
-            queryCount={1500}
-            organization={org}
-            projectId="project-slug"
-            selection={{
-              projects: [1],
-              environments: [],
-              datetime: {start: null, end: null, period: null, utc: true},
-            }}
-            groupIds={['1', '2', '3']}
-            onRealtimeChange={function () {}}
-            onSelectStatsPeriod={function () {}}
-            realtimeActive={false}
-            statsPeriod="24h"
-          />,
-          routerContext
-        );
-      });
+      it('after checking "Select all" checkbox, displays bulk select message', function () {
+        render(<IssueListActions {...defaultProps} queryCount={1500} />);
 
-      afterAll(() => {
-        wrapper.unmount();
-      });
+        userEvent.click(screen.getByRole('checkbox'));
 
-      it('after checking "Select all" checkbox, displays bulk select message', function () {
-        wrapper.find('ActionsCheckbox Checkbox').simulate('change');
-        expect(wrapper.find('SelectAllNotice')).toSnapshot();
+        expect(screen.getByTestId('issue-list-select-all-notice')).toSnapshot();
       });
 
       it('can bulk select', function () {
-        wrapper.find('SelectAllNotice').find('a').simulate('click');
+        render(<IssueListActions {...defaultProps} queryCount={1500} />);
 
-        expect(wrapper.find('SelectAllNotice')).toSnapshot();
+        userEvent.click(screen.getByRole('checkbox'));
+        userEvent.click(screen.getByTestId('issue-list-select-all-notice-link'));
+
+        expect(screen.getByTestId('issue-list-select-all-notice')).toSnapshot();
       });
 
       it('bulk resolves', async function () {
@@ -70,15 +73,22 @@ describe('IssueListActions', function () {
           url: '/organizations/org-slug/issues/',
           method: 'PUT',
         });
-        wrapper.find('ResolveActions ResolveButton button').simulate('click');
 
-        const modal = await mountGlobalModal();
+        render(
+          <React.Fragment>
+            <GlobalModal />
+            <IssueListActions {...defaultProps} queryCount={1500} />
+          </React.Fragment>
+        );
+        userEvent.click(screen.getByRole('checkbox'));
+
+        userEvent.click(screen.getByTestId('issue-list-select-all-notice-link'));
+
+        userEvent.click(screen.getByRole('button', {name: 'Resolve'}));
 
-        // TODO(epurkhiser): This test will be rewrittein in RLT soon, this snapshot has
-        // been flakey so let's just turn it off for now.
-        // expect(modal.find('Modal')).toSnapshot();
+        await screen.findByRole('dialog');
 
-        modal.find('Button[priority="primary"]').simulate('click');
+        userEvent.click(screen.getByRole('button', {name: 'Bulk resolve issues'}));
 
         expect(apiMock).toHaveBeenCalledWith(
           expect.anything(),
@@ -89,63 +99,52 @@ describe('IssueListActions', function () {
             data: {status: 'resolved', statusDetails: {}},
           })
         );
-
-        await tick();
-        wrapper.update();
       });
     });
 
     describe('Total results less than bulk limit', function () {
-      beforeAll(function () {
-        SelectedGroupStore.reset();
-        SelectedGroupStore.add(['1', '2', '3']);
-        wrapper = mountWithTheme(
-          <IssueListActions
-            api={new MockApiClient()}
-            allResultsVisible={false}
-            query=""
-            queryCount={600}
-            organization={TestStubs.Organization()}
-            projectId="1"
-            selection={{
-              projects: [1],
-              environments: [],
-              datetime: {start: null, end: null, period: null, utc: true},
-            }}
-            groupIds={['1', '2', '3']}
-            onRealtimeChange={function () {}}
-            onSelectStatsPeriod={function () {}}
-            realtimeActive={false}
-            statsPeriod="24h"
-          />
-        );
-      });
+      it('after checking "Select all" checkbox, displays bulk select message', function () {
+        render(<IssueListActions {...defaultProps} queryCount={15} />);
 
-      afterAll(() => {
-        wrapper.unmount();
-      });
+        userEvent.click(screen.getByRole('checkbox'));
 
-      it('after checking "Select all" checkbox, displays bulk select message', function () {
-        wrapper.find('ActionsCheckbox Checkbox').simulate('change');
-        expect(wrapper.find('SelectAllNotice')).toSnapshot();
+        expect(screen.getByTestId('issue-list-select-all-notice')).toSnapshot();
       });
 
       it('can bulk select', function () {
-        wrapper.find('SelectAllNotice').find('a').simulate('click');
+        render(<IssueListActions {...defaultProps} queryCount={15} />);
+
+        userEvent.click(screen.getByRole('checkbox'));
+
+        userEvent.click(screen.getByTestId('issue-list-select-all-notice-link'));
 
-        expect(wrapper.find('SelectAllNotice')).toSnapshot();
+        expect(screen.getByTestId('issue-list-select-all-notice')).toSnapshot();
       });
 
-      it('bulk resolves', async function () {
+      it('bulk resolves', function () {
         const apiMock = MockApiClient.addMockResponse({
           url: '/organizations/org-slug/issues/',
           method: 'PUT',
         });
-        wrapper.find('ResolveActions ResolveButton button').simulate('click');
 
-        const modal = await mountGlobalModal();
-        expect(modal.find('Modal')).toSnapshot();
-        modal.find('Button[priority="primary"]').simulate('click');
+        render(
+          <React.Fragment>
+            <GlobalModal />
+            <IssueListActions {...defaultProps} queryCount={15} />
+          </React.Fragment>
+        );
+
+        userEvent.click(screen.getByRole('checkbox'));
+
+        userEvent.click(screen.getByTestId('issue-list-select-all-notice-link'));
+
+        userEvent.click(screen.getByRole('button', {name: 'Resolve'}));
+
+        const modal = screen.getByRole('dialog');
+
+        expect(modal).toSnapshot();
+
+        userEvent.click(within(modal).getByRole('button', {name: 'Bulk resolve issues'}));
 
         expect(apiMock).toHaveBeenCalledWith(
           expect.anything(),
@@ -156,61 +155,31 @@ describe('IssueListActions', function () {
             data: {status: 'resolved', statusDetails: {}},
           })
         );
-
-        await tick();
-        wrapper.update();
       });
     });
 
     describe('Selected on page', function () {
-      beforeAll(function () {
-        SelectedGroupStore.reset();
-        SelectedGroupStore.add(['1', '2', '3']);
-        wrapper = mountWithTheme(
-          <IssueListActions
-            api={new MockApiClient()}
-            allResultsVisible
-            query=""
-            queryCount={15}
-            organization={TestStubs.Organization()}
-            projectId="1"
-            selection={{
-              projects: [1],
-              environments: [],
-              datetime: {start: null, end: null, period: null, utc: true},
-            }}
-            groupIds={['1', '2', '3', '6', '9']}
-            onRealtimeChange={function () {}}
-            onSelectStatsPeriod={function () {}}
-            realtimeActive={false}
-            statsPeriod="24h"
-          />
-        );
-      });
-
-      afterAll(() => {
-        wrapper.unmount();
-      });
-
       it('resolves selected items', function () {
         const apiMock = MockApiClient.addMockResponse({
           url: '/organizations/org-slug/issues/',
           method: 'PUT',
         });
-        jest
-          .spyOn(SelectedGroupStore, 'getSelectedIds')
-          .mockImplementation(() => new Set(['3', '6', '9']));
 
-        wrapper
-          .find('IssueListActions')
-          .setState({allInQuerySelected: false, anySelected: true});
+        jest.spyOn(SelectedGroupStore, 'getSelectedIds').mockReturnValue(new Set(['1']));
+
+        render(
+          <IssueListActions {...defaultProps} groupIds={['1', '2', '3', '6', '9']} />
+        );
+
+        const resolveButton = screen.getByRole('button', {name: 'Resolve'});
+        expect(resolveButton).toBeEnabled();
+        userEvent.click(resolveButton);
 
-        wrapper.find('ResolveActions ResolveButton button').first().simulate('click');
         expect(apiMock).toHaveBeenCalledWith(
           expect.anything(),
           expect.objectContaining({
             query: {
-              id: ['3', '6', '9'],
+              id: ['1'],
               project: [1],
             },
             data: {status: 'resolved', statusDetails: {}},
@@ -218,36 +187,34 @@ describe('IssueListActions', function () {
         );
       });
 
-      it('ignores selected items', async function () {
+      it('can ignore selected items (custom)', async function () {
         const apiMock = MockApiClient.addMockResponse({
           url: '/organizations/org-slug/issues/',
           method: 'PUT',
         });
-        jest
-          .spyOn(SelectedGroupStore, 'getSelectedIds')
-          .mockImplementation(() => new Set(['1']));
-        wrapper
-          .find('IssueListActions')
-          .setState({allInQuerySelected: false, anySelected: true});
-
-        await selectDropdownMenuItem({
-          wrapper,
-          specifiers: {prefix: 'IgnoreActions'},
-          triggerSelector: 'DropdownTrigger',
-          itemKey: ['until-affect', 'until-affect-custom'],
-        });
+        jest.spyOn(SelectedGroupStore, 'getSelectedIds').mockReturnValue(new Set(['1']));
 
-        const modal = await mountGlobalModal();
+        render(
+          <React.Fragment>
+            <GlobalModal />
+            <IssueListActions {...defaultProps} />
+          </React.Fragment>
+        );
 
-        modal
-          .find('CustomIgnoreCountModal input[label="Number of users"]')
-          .simulate('change', {target: {value: 300}});
+        userEvent.click(screen.getByRole('button', {name: 'Ignore options'}));
+        fireEvent.click(screen.getByText(/Until this affects an additional/));
+        await screen.findByTestId('until-affect-custom');
+        userEvent.click(screen.getByTestId('until-affect-custom'));
 
-        selectByLabel(modal, 'per week', {
-          name: 'window',
-        });
+        const modal = screen.getByRole('dialog');
+
+        userEvent.clear(within(modal).getByLabelText('Number of users'));
+        userEvent.type(within(modal).getByLabelText('Number of users'), '300');
+
+        userEvent.click(within(modal).getByRole('textbox'));
+        userEvent.click(within(modal).getByText('per week'));
 
-        modal.find('Button[priority="primary"]').simulate('click');
+        userEvent.click(within(modal).getByRole('button', {name: 'Ignore'}));
 
         expect(apiMock).toHaveBeenCalledWith(
           expect.anything(),
@@ -269,241 +236,74 @@ describe('IssueListActions', function () {
     });
   });
 
-  describe('actionSelectedGroups()', function () {
-    beforeEach(function () {
-      jest.spyOn(SelectedGroupStore, 'deselectAll');
-      actionsWrapper = mountWithTheme(
-        <IssueListActions
-          api={new MockApiClient()}
-          query=""
-          organization={TestStubs.Organization()}
-          projectId="1"
-          selection={{
-            projects: [1],
-            environments: [],
-            datetime: {start: null, end: null, period: null, utc: true},
-          }}
-          groupIds={['1', '2', '3']}
-          onRealtimeChange={function () {}}
-          onSelectStatsPeriod={function () {}}
-          realtimeActive={false}
-          statsPeriod="24h"
-        />
-      );
-      actions = actionsWrapper.instance();
-    });
-
-    afterEach(() => {
-      actionsWrapper.unmount();
-    });
-
-    describe('for all items', function () {
-      it("should invoke the callback with 'undefined' and deselect all", function () {
-        const callback = jest.fn();
-
-        actions.state.allInQuerySelected = true;
-
-        actions.actionSelectedGroups(callback);
-
-        expect(callback).toHaveBeenCalledWith(undefined);
-        expect(callback).toHaveBeenCalledTimes(1);
-        expect(SelectedGroupStore.deselectAll).toHaveBeenCalledTimes(1);
-
-        // all selected is reset
-        expect(actions.state.allInQuerySelected).toBe(false);
-      });
+  it('can resolve but not merge issues from different projects', function () {
+    jest
+      .spyOn(SelectedGroupStore, 'getSelectedIds')
+      .mockImplementation(() => new Set(['1', '2', '3']));
+    jest.spyOn(GroupStore, 'get').mockImplementation(id => {
+      switch (id) {
+        case '1':
+          return TestStubs.Group({project: TestStubs.Project({slug: 'project-1'})});
+        default:
+          return TestStubs.Group({project: TestStubs.Project({slug: 'project-2'})});
+      }
     });
 
-    describe('for page-selected items', function () {
-      it('should invoke the callback with an array of selected items and deselect all', function () {
-        jest
-          .spyOn(SelectedGroupStore, 'getSelectedIds')
-          .mockImplementation(() => new Set(['1', '2', '3']));
-
-        actions.state.allInQuerySelected = false;
-        const callback = jest.fn();
-        actions.actionSelectedGroups(callback);
+    render(<IssueListActions {...defaultProps} />);
 
-        expect(callback).toHaveBeenCalledWith(['1', '2', '3']);
-        expect(callback).toHaveBeenCalledTimes(1);
-        expect(SelectedGroupStore.deselectAll).toHaveBeenCalledTimes(1);
-      });
-    });
+    // Can resolve but not merge issues from multiple projects
+    expect(screen.getByRole('button', {name: 'Resolve'})).toBeEnabled();
+    expect(screen.getByRole('button', {name: 'Merge Selected Issues'})).toBeDisabled();
   });
 
-  describe('multiple groups from different project', function () {
-    beforeEach(function () {
+  describe('mark reviewed', function () {
+    it('acknowledges group', function () {
+      const mockOnMarkReviewed = jest.fn();
+
       jest
         .spyOn(SelectedGroupStore, 'getSelectedIds')
         .mockImplementation(() => new Set(['1', '2', '3']));
-
-      wrapper = mountWithTheme(
-        <IssueListActions
-          api={new MockApiClient()}
-          query=""
-          organization={TestStubs.Organization()}
-          groupIds={['1', '2', '3']}
-          selection={{
-            projects: [],
-            environments: [],
-            datetime: {start: null, end: null, period: null, utc: true},
-          }}
-          onRealtimeChange={function () {}}
-          onSelectStatsPeriod={function () {}}
-          realtimeActive={false}
-          statsPeriod="24h"
-        />
-      );
-    });
-
-    afterEach(() => {
-      wrapper.unmount();
-    });
-
-    it('should disable resolve dropdown but not resolve action', function () {
-      const resolve = wrapper.find('ResolveActions').first();
-      expect(resolve.props().disabled).toBe(false);
-      expect(resolve.props().disableDropdown).toBe(true);
-    });
-
-    it('should disable merge button', function () {
-      expect(
-        wrapper.find('button[aria-label="Merge Selected Issues"]').props()[
-          'aria-disabled'
-        ]
-      ).toBe(true);
-    });
-  });
-
-  describe('mark reviewed', function () {
-    let issuesApiMock;
-    beforeEach(() => {
-      SelectedGroupStore.reset();
-      const organization = TestStubs.Organization();
-
-      wrapper = mountWithTheme(
-        <IssueListActions
-          api={new MockApiClient()}
-          query=""
-          organization={organization}
-          groupIds={['1', '2', '3']}
-          selection={{
-            projects: [],
-            environments: [],
-            datetime: {start: null, end: null, period: null, utc: true},
-          }}
-          onRealtimeChange={function () {}}
-          onSelectStatsPeriod={function () {}}
-          realtimeActive={false}
-          statsPeriod="24h"
-          queryCount={100}
-          displayCount="3 of 3"
-        />
-      );
-      MockApiClient.addMockResponse({
-        url: '/organizations/org-slug/projects/',
-        body: [TestStubs.Project({slug: 'earth', platform: 'javascript'})],
-      });
-      issuesApiMock = MockApiClient.addMockResponse({
-        url: '/organizations/org-slug/issues/',
-        method: 'PUT',
+      jest.spyOn(GroupStore, 'get').mockImplementation(id => {
+        return TestStubs.Group({
+          id,
+          inbox: {
+            date_added: '2020-11-24T13:17:42.248751Z',
+            reason: 0,
+            reason_details: null,
+          },
+        });
       });
-    });
 
-    afterEach(() => {
-      wrapper.unmount();
-    });
+      render(<IssueListActions {...defaultProps} onMarkReviewed={mockOnMarkReviewed} />);
 
-    it('acknowledges group', async function () {
-      await act(async () => {
-        wrapper.find('IssueListActions').setState({anySelected: true});
-        await tick();
-        wrapper.update();
-      });
-      SelectedGroupStore.add(['1', '2', '3']);
-      SelectedGroupStore.toggleSelectAll();
-      const inbox = {
-        date_added: '2020-11-24T13:17:42.248751Z',
-        reason: 0,
-        reason_details: null,
-      };
-      GroupStore.loadInitialData([
-        TestStubs.Group({id: '1', inbox}),
-        TestStubs.Group({id: '2', inbox}),
-        TestStubs.Group({id: '2', inbox}),
-      ]);
-
-      await tick();
-
-      wrapper.find('button[aria-label="Mark Reviewed"]').simulate('click');
-      expect(issuesApiMock).toHaveBeenCalledWith(
-        expect.anything(),
-        expect.objectContaining({
-          data: {inbox: false},
-        })
-      );
+      const reviewButton = screen.getByRole('button', {name: 'Mark Reviewed'});
+      expect(reviewButton).toBeEnabled();
+      userEvent.click(reviewButton);
+
+      expect(mockOnMarkReviewed).toHaveBeenCalledWith(['1', '2', '3']);
     });
 
-    it('mark reviewed disabled for group that is already reviewed', async function () {
-      await act(async () => {
-        wrapper.find('IssueListActions').setState({anySelected: true});
-        await tick();
-        wrapper.update();
-      });
+    it('mark reviewed disabled for group that is already reviewed', function () {
       SelectedGroupStore.add(['1']);
       SelectedGroupStore.toggleSelectAll();
       GroupStore.loadInitialData([TestStubs.Group({id: '1', inbox: null})]);
 
-      await tick();
+      render(<IssueListActions {...defaultProps} />);
 
-      expect(
-        wrapper.find('button[aria-label="Mark Reviewed"]').props()['aria-disabled']
-      ).toBe(true);
+      expect(screen.getByRole('button', {name: 'Mark Reviewed'})).toBeDisabled();
     });
   });
 
   describe('sort', function () {
-    let onSortChange;
-    afterEach(() => {
-      wrapper.unmount();
-    });
+    it('calls onSortChange with new sort value', function () {
+      const mockOnSortChange = jest.fn();
+      render(<IssueListActions {...defaultProps} onSortChange={mockOnSortChange} />);
 
-    beforeEach(function () {
-      const organization = TestStubs.Organization();
-
-      onSortChange = jest.fn();
-      wrapper = mountWithTheme(
-        <IssueListActions
-          api={new MockApiClient()}
-          query=""
-          organization={organization}
-          groupIds={['1', '2', '3']}
-          selection={{
-            projects: [],
-            environments: [],
-            datetime: {start: null, end: null, period: null, utc: true},
-          }}
-          onRealtimeChange={function () {}}
-          onSelectStatsPeriod={function () {}}
-          onSortChange={onSortChange}
-          realtimeActive={false}
-          statsPeriod="24h"
-          queryCount={100}
-          displayCount="3 of 3"
-          sort="date"
-        />
-      );
-    });
+      userEvent.click(screen.getByRole('button', {name: 'Last Seen'}));
 
-    it('calls onSortChange with new sort value', async function () {
-      await act(async () => {
-        triggerPress(wrapper.find('IssueListSortOptions button'));
-        await tick();
-        wrapper.update();
-      });
-      wrapper.find('Option').at(3).simulate('click');
+      userEvent.click(screen.getByText(/Number of events/));
 
-      expect(onSortChange).toHaveBeenCalledWith('freq');
+      expect(mockOnSortChange).toHaveBeenCalledWith('freq');
     });
   });
 });

+ 5 - 2
static/app/views/issueList/actions/index.tsx

@@ -296,7 +296,7 @@ class IssueListActions extends Component<Props, State> {
         </StyledFlex>
         {!allResultsVisible && pageSelected && (
           <Alert type="warning" system>
-            <SelectAllNotice>
+            <SelectAllNotice data-test-id="issue-list-select-all-notice">
               {allInQuerySelected ? (
                 queryCount >= BULK_LIMIT ? (
                   tct(
@@ -317,7 +317,10 @@ class IssueListActions extends Component<Props, State> {
                     '%s issues on this page selected.',
                     numIssues
                   )}
-                  <SelectAllLink onClick={this.handleApplyToAll}>
+                  <SelectAllLink
+                    onClick={this.handleApplyToAll}
+                    data-test-id="issue-list-select-all-notice-link"
+                  >
                     {queryCount >= BULK_LIMIT
                       ? tct(
                           'Select the first [count] issues that match this search query.',