Просмотр исходного кода

test(ui): Convert project install tests to RTL (#38866)

Scott Cooper 2 лет назад
Родитель
Сommit
eae0114703

+ 1 - 6
static/app/components/forms/controls/radioGroup.tsx

@@ -64,12 +64,7 @@ const RadioGroup = <C extends string>({
       const disabled = !!disabledChoice || groupDisabled;
       const content = (
         <Fragment>
-          <RadioLineItem
-            role="radio"
-            index={index}
-            aria-checked={value === id}
-            disabled={disabled}
-          >
+          <RadioLineItem index={index} aria-checked={value === id} disabled={disabled}>
             <Radio
               aria-label={t('Select %s', name)}
               disabled={disabled}

+ 83 - 114
static/app/views/projectInstall/createProject.spec.jsx

@@ -1,4 +1,4 @@
-import {mountWithTheme} from 'sentry-test/enzyme';
+import {fireEvent, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
 
 import {openCreateTeamModal} from 'sentry/actionCreators/modal';
 import {CreateProject} from 'sentry/views/projectInstall/createProject';
@@ -20,61 +20,57 @@ describe('CreateProject', function () {
     },
   };
 
-  it('should block if you have access to no teams', function () {
-    const props = {
-      ...baseProps,
-    };
+  beforeEach(() => {
+    MockApiClient.addMockResponse({
+      url: `/projects/testOrg/rule-conditions/`,
+      body: {},
+      // Not required for these tests
+      statusCode: 500,
+    });
+  });
+
+  afterEach(() => {
+    MockApiClient.clearMockResponses();
+  });
 
-    const wrapper = mountWithTheme(
-      <CreateProject {...props} />,
-      TestStubs.routerContext([{organization: {id: '1', slug: 'testOrg'}}])
-    );
+  it('should block if you have access to no teams', function () {
+    const wrapper = render(<CreateProject {...baseProps} />, {
+      context: TestStubs.routerContext([{organization: {id: '1', slug: 'testOrg'}}]),
+    });
 
-    expect(wrapper).toSnapshot();
+    expect(wrapper.container).toSnapshot();
   });
 
   it('can create a new team', function () {
-    const props = {
-      ...baseProps,
-    };
-
-    const wrapper = mountWithTheme(
-      <CreateProject {...props} />,
-      TestStubs.routerContext([{organization: {id: '1', slug: 'testOrg'}}])
-    );
+    render(<CreateProject {...baseProps} />, {
+      context: TestStubs.routerContext([{organization: {id: '1', slug: 'testOrg'}}]),
+    });
 
-    wrapper.find('TeamSelectInput Button button').simulate('click');
+    userEvent.click(screen.getByRole('button', {name: 'Create a team'}));
     expect(openCreateTeamModal).toHaveBeenCalled();
   });
 
   it('should fill in project name if its empty when platform is chosen', function () {
-    const props = {
-      ...baseProps,
-    };
-
-    const wrapper = mountWithTheme(
-      <CreateProject {...props} teams={[teamWithAccess]} />,
-      TestStubs.routerContext([
+    const wrapper = render(<CreateProject {...baseProps} teams={[teamWithAccess]} />, {
+      context: TestStubs.routerContext([
         {organization: {id: '1', slug: 'testOrg'}, location: {query: {}}},
-      ])
-    );
+      ]),
+    });
 
-    let node = wrapper.find('PlatformCard').first();
-    node.simulate('click');
-    expect(wrapper.find('ProjectNameInput input').props().value).toBe('iOS');
+    userEvent.click(screen.getByTestId('platform-apple-ios'));
+    expect(screen.getByPlaceholderText('project-name')).toHaveValue('iOS');
 
-    node = wrapper.find('PlatformCard').last();
-    node.simulate('click');
-    expect(wrapper.find('ProjectNameInput input').props().value).toBe('Rails');
+    userEvent.click(screen.getByTestId('platform-ruby-rails'));
+    expect(screen.getByPlaceholderText('project-name')).toHaveValue('Rails');
 
     // but not replace it when project name is something else:
-    wrapper.setState({projectName: 'another'});
+    userEvent.clear(screen.getByPlaceholderText('project-name'));
+    userEvent.type(screen.getByPlaceholderText('project-name'), 'another');
 
-    node = wrapper.find('PlatformCard').first();
-    node.simulate('click');
-    expect(wrapper.find('ProjectNameInput input').props().value).toBe('another');
+    userEvent.click(screen.getByTestId('platform-apple-ios'));
+    expect(screen.getByPlaceholderText('project-name')).toHaveValue('another');
 
-    expect(wrapper).toSnapshot();
+    expect(wrapper.container).toSnapshot();
   });
 
   it('should fill in platform name if its provided by url', function () {
@@ -83,14 +79,13 @@ describe('CreateProject', function () {
       location: {query: {platform: 'ruby-rails'}},
     };
 
-    const wrapper = mountWithTheme(
-      <CreateProject {...props} teams={[teamWithAccess]} />,
-      TestStubs.routerContext([{organization: {id: '1', slug: 'testOrg'}}])
-    );
+    const wrapper = render(<CreateProject {...props} teams={[teamWithAccess]} />, {
+      context: TestStubs.routerContext([{organization: {id: '1', slug: 'testOrg'}}]),
+    });
 
-    expect(wrapper.find('ProjectNameInput input').props().value).toBe('Rails');
+    expect(screen.getByPlaceholderText('project-name')).toHaveValue('Rails');
 
-    expect(wrapper).toSnapshot();
+    expect(wrapper.container).toSnapshot();
   });
 
   it('should fill in category name if its provided by url', function () {
@@ -99,41 +94,35 @@ describe('CreateProject', function () {
       location: {query: {category: 'mobile'}},
     };
 
-    const wrapper = mountWithTheme(
-      <CreateProject {...props} teams={[teamWithAccess]} />,
-      TestStubs.routerContext([{organization: {id: '1', slug: 'testOrg'}}])
-    );
+    render(<CreateProject {...props} teams={[teamWithAccess]} />, {
+      context: TestStubs.routerContext([{organization: {id: '1', slug: 'testOrg'}}]),
+    });
 
-    expect(wrapper.find('PlatformPicker').state('category')).toBe('mobile');
+    expect(screen.getByTestId('platform-apple-ios')).toBeInTheDocument();
+    expect(screen.queryByTestId('platform-ruby-rails')).not.toBeInTheDocument();
   });
 
   it('should deal with incorrect platform name if its provided by url', function () {
-    const props = {
-      ...baseProps,
-    };
-
-    const wrapper = mountWithTheme(
-      <CreateProject {...props} teams={[teamWithAccess]} />,
-      TestStubs.routerContext([
+    const wrapper = render(<CreateProject {...baseProps} teams={[teamWithAccess]} />, {
+      context: TestStubs.routerContext([
         {
           organization: {id: '1', slug: 'testOrg'},
           location: {query: {platform: 'XrubyROOLs'}},
         },
-      ])
-    );
+      ]),
+    });
 
-    expect(wrapper.find('ProjectNameInput input').props().value).toBe('');
+    expect(screen.getByPlaceholderText('project-name')).toHaveValue('');
 
-    expect(wrapper).toSnapshot();
+    expect(wrapper.container).toSnapshot();
   });
 
   describe('Issue Alerts Options', () => {
-    let props = {};
+    const props = {
+      ...baseProps,
+      teams: [teamWithAccess],
+    };
     beforeEach(() => {
-      props = {
-        ...baseProps,
-      };
-      props.teams = [teamWithAccess];
       MockApiClient.addMockResponse({
         url: `/projects/${props.organization.slug}/rule-conditions/`,
         body: TestStubs.MOCK_RESP_VERBOSE,
@@ -145,57 +134,37 @@ describe('CreateProject', function () {
     });
 
     it('should enabled the submit button if and only if all the required information has been filled', () => {
-      const wrapper = mountWithTheme(
-        <CreateProject {...props} />,
-        TestStubs.routerContext([
+      render(<CreateProject {...props} />, {
+        context: TestStubs.routerContext([
           {
             location: {query: {}},
           },
-        ])
-      );
-
-      const expectSubmitButtonToBeDisabled = isDisabled => {
-        expect(
-          wrapper.find('Button[data-test-id="create-project"]').props().disabled
-        ).toBe(isDisabled);
-      };
-
-      wrapper
-        .find('SelectControl[data-test-id="metric-select-control"]')
-        .closest('RadioLineItem')
-        .find('Radio input')
-        .simulate('change');
-      expectSubmitButtonToBeDisabled(true);
-
-      wrapper
-        .find('input[data-test-id="range-input"]')
-        .first()
-        .simulate('change', {target: {value: '2'}});
-      expectSubmitButtonToBeDisabled(true);
-
-      wrapper.find('PlatformCard').first().simulate('click');
-      expectSubmitButtonToBeDisabled(false);
-
-      wrapper
-        .find('input[data-test-id="range-input"]')
-        .first()
-        .simulate('change', {target: {value: ''}});
-      expectSubmitButtonToBeDisabled(true);
-
-      wrapper
-        .find('input[data-test-id="range-input"]')
-        .first()
-        .simulate('change', {target: {value: '2712'}});
-      expectSubmitButtonToBeDisabled(false);
-
-      wrapper
-        .find('input[data-test-id="range-input"]')
-        .first()
-        .simulate('change', {target: {value: ''}});
-      expectSubmitButtonToBeDisabled(true);
-
-      wrapper.find('Radio input').first().simulate('change');
-      expectSubmitButtonToBeDisabled(false);
+        ]),
+      });
+
+      const createProjectButton = screen.getByTestId('create-project');
+
+      userEvent.click(screen.getByText(/When there are more than/));
+      expect(createProjectButton).toBeDisabled();
+
+      userEvent.paste(screen.getByTestId('range-input'), '2', {skipClick: true});
+      expect(screen.getByTestId('range-input')).toHaveValue(2);
+      expect(createProjectButton).toBeDisabled();
+
+      userEvent.click(screen.getByTestId('platform-apple-ios'));
+      expect(createProjectButton).toBeEnabled();
+
+      userEvent.clear(screen.getByTestId('range-input'));
+      expect(createProjectButton).toBeEnabled();
+
+      userEvent.paste(screen.getByTestId('range-input'), '2712', {skipClick: true});
+      expect(createProjectButton).toBeEnabled();
+
+      fireEvent.change(screen.getByTestId('range-input'), {target: {value: ''}});
+      expect(createProjectButton).toBeDisabled();
+
+      userEvent.click(screen.getByText("I'll create my own alerts later"));
+      expect(createProjectButton).toBeEnabled();
     });
   });
 });

+ 41 - 58
static/app/views/projectInstall/issueAlertOptions.spec.jsx

@@ -1,46 +1,26 @@
-import {mountWithTheme} from 'sentry-test/enzyme';
+import selectEvent from 'react-select-event';
+
 import {initializeOrg} from 'sentry-test/initializeOrg';
+import {render, screen} from 'sentry-test/reactTestingLibrary';
 
 import IssueAlertOptions from 'sentry/views/projectInstall/issueAlertOptions';
 
 describe('IssueAlertOptions', function () {
-  const {organization, routerContext} = initializeOrg();
+  const {organization} = initializeOrg();
   const URL = `/projects/${organization.slug}/rule-conditions/`;
-  let props;
-  const baseProps = {
-    onChange: _ => {},
+  const props = {
+    onChange: jest.fn(),
   };
   beforeEach(() => {
-    MockApiClient.clearMockResponses();
     MockApiClient.addMockResponse({
       url: `/projects/${organization.slug}/rule-conditions/`,
       body: TestStubs.MOCK_RESP_VERBOSE,
     });
-    props = {...baseProps};
   });
-
-  const selectControlVerifier = async (wrapper, dataTestId, optionsText) => {
-    wrapper
-      .find(`[data-test-id="${dataTestId}"] input[id*="react-select"]`)
-      .last()
-      .simulate('focus');
-
-    await tick();
-    wrapper.update();
-
-    expect(
-      wrapper.find(`InlineSelectControl[data-test-id="${dataTestId}"] Option`)
-    ).toHaveLength(optionsText.length);
-
-    optionsText.forEach((metricText, idx) =>
-      expect(
-        wrapper
-          .find(`InlineSelectControl[data-test-id="${dataTestId}"] Option`)
-          .at(idx)
-          .text()
-      ).toBe(metricText)
-    );
-  };
+  afterEach(() => {
+    MockApiClient.clearMockResponses();
+    jest.clearAllMocks();
+  });
 
   it('should render only the `Default Rule` and `Create Later` option on empty response:[]', () => {
     MockApiClient.addMockResponse({
@@ -48,8 +28,8 @@ describe('IssueAlertOptions', function () {
       body: [],
     });
 
-    const wrapper = mountWithTheme(<IssueAlertOptions {...props} />, routerContext);
-    expect(wrapper.find('RadioLineItem')).toHaveLength(2);
+    render(<IssueAlertOptions {...props} />, {organization});
+    expect(screen.getAllByRole('radio')).toHaveLength(2);
   });
 
   it('should render only the `Default Rule` and `Create Later` option on empty response:{}', () => {
@@ -58,8 +38,8 @@ describe('IssueAlertOptions', function () {
       body: {},
     });
 
-    const wrapper = mountWithTheme(<IssueAlertOptions {...props} />, routerContext);
-    expect(wrapper.find('RadioLineItem')).toHaveLength(2);
+    render(<IssueAlertOptions {...props} />, {organization});
+    expect(screen.getAllByRole('radio')).toHaveLength(2);
   });
 
   it('should render only the `Default Rule` and `Create Later` option on responses with different allowable intervals', () => {
@@ -68,8 +48,8 @@ describe('IssueAlertOptions', function () {
       body: TestStubs.MOCK_RESP_INCONSISTENT_INTERVALS,
     });
 
-    const wrapper = mountWithTheme(<IssueAlertOptions {...props} />, routerContext);
-    expect(wrapper.find('RadioLineItem')).toHaveLength(2);
+    render(<IssueAlertOptions {...props} />, {organization});
+    expect(screen.getAllByRole('radio')).toHaveLength(2);
   });
 
   it('should render all(three) options on responses with different placeholder values', () => {
@@ -77,19 +57,25 @@ describe('IssueAlertOptions', function () {
       url: URL,
       body: TestStubs.MOCK_RESP_INCONSISTENT_PLACEHOLDERS,
     });
-    const wrapper = mountWithTheme(<IssueAlertOptions {...props} />, routerContext);
-    expect(wrapper.find('RadioLineItem')).toHaveLength(3);
+    render(<IssueAlertOptions {...props} />, {organization});
+    expect(screen.getAllByRole('radio')).toHaveLength(3);
   });
 
-  it('should ignore conditions that are not `sentry.rules.conditions.event_frequency.EventFrequencyCondition` and `sentry.rules.conditions.event_frequency.EventUniqueUserFrequencyCondition`', () => {
+  it('should ignore conditions that are not `sentry.rules.conditions.event_frequency.EventFrequencyCondition` and `sentry.rules.conditions.event_frequency.EventUniqueUserFrequencyCondition`', async () => {
     MockApiClient.addMockResponse({
       url: URL,
       body: TestStubs.MOCK_RESP_ONLY_IGNORED_CONDITIONS_INVALID,
     });
 
-    const wrapper = mountWithTheme(<IssueAlertOptions {...props} />, routerContext);
-    expect(wrapper.find('RadioLineItem')).toHaveLength(3);
-    selectControlVerifier(wrapper, 'metric-select-control', ['users affected by']);
+    render(<IssueAlertOptions {...props} />, {organization});
+    expect(screen.getAllByRole('radio')).toHaveLength(3);
+    await selectEvent.select(screen.getByText('Select...'), 'users affected by');
+    expect(props.onChange).toHaveBeenCalledWith(
+      expect.objectContaining({
+        defaultRules: false,
+        shouldCreateCustomRule: true,
+      })
+    );
   });
 
   it('should render all(three) options on a valid response', () => {
@@ -98,26 +84,24 @@ describe('IssueAlertOptions', function () {
       body: TestStubs.MOCK_RESP_VERBOSE,
     });
 
-    const wrapper = mountWithTheme(<IssueAlertOptions {...props} />, routerContext);
-    expect(wrapper.find('RadioLineItem')).toHaveLength(3);
+    render(<IssueAlertOptions {...props} />);
+    expect(screen.getAllByRole('radio')).toHaveLength(3);
   });
 
-  it('should pre-populate fields from server response', () => {
+  it('should pre-populate fields from server response', async () => {
     MockApiClient.addMockResponse({
       url: URL,
       body: TestStubs.MOCK_RESP_VERBOSE,
     });
 
-    const wrapper = mountWithTheme(<IssueAlertOptions {...props} />, routerContext);
-
-    [
-      ['metric-select-control', ['occurrences of', 'users affected by']],
-      [
-        'interval-select-control',
-        ['one minute', 'one hour', 'one day', 'one week', '30 days'],
-      ],
-    ].forEach(([dataTestId, options]) =>
-      selectControlVerifier(wrapper, dataTestId, options)
+    render(<IssueAlertOptions {...props} />);
+    await selectEvent.select(screen.getByText('occurrences of'), 'users affected by');
+    await selectEvent.select(screen.getByText('one minute'), '30 days');
+    expect(props.onChange).toHaveBeenCalledWith(
+      expect.objectContaining({
+        defaultRules: false,
+        shouldCreateCustomRule: true,
+      })
     );
   });
 
@@ -127,8 +111,7 @@ describe('IssueAlertOptions', function () {
       body: TestStubs.MOCK_RESP_VERBOSE,
     });
 
-    const wrapper = mountWithTheme(<IssueAlertOptions {...props} />, routerContext);
-
-    expect(wrapper.find('input[data-test-id="range-input"]').props().value).toBe('');
+    render(<IssueAlertOptions {...props} />);
+    expect(screen.getByTestId('range-input')).toHaveValue(null);
   });
 });

+ 1 - 5
static/app/views/projectInstall/issueAlertOptions.tsx

@@ -171,7 +171,6 @@ class IssueAlertOptions extends AsyncComponent<Props, State> {
             value={this.state.metric}
             options={this.getAvailableMetricOptions()}
             onChange={metric => this.setStateAndUpdateParents({metric: metric.value})}
-            data-test-id="metric-select-control"
           />
           {t('a unique error in')}
           <InlineSelectControl
@@ -183,7 +182,6 @@ class IssueAlertOptions extends AsyncComponent<Props, State> {
             onChange={interval =>
               this.setStateAndUpdateParents({interval: interval.value})
             }
-            data-test-id="interval-select-control"
           />
         </CustomizeAlertsGrid>,
       ]);
@@ -244,11 +242,9 @@ class IssueAlertOptions extends AsyncComponent<Props, State> {
         ) => Pick<State, K> | State | null)
       | Pick<State, K>
       | State
-      | null,
-    callback?: () => void
+      | null
   ): void {
     this.setState(state, () => {
-      callback?.();
       this.props.onChange(this.getUpdatedData());
     });
   }

+ 14 - 10
static/app/views/projectInstall/newProject.spec.jsx

@@ -1,19 +1,23 @@
-import {mountWithTheme} from 'sentry-test/enzyme';
 import {initializeOrg} from 'sentry-test/initializeOrg';
+import {render} from 'sentry-test/reactTestingLibrary';
 
-import {Client} from 'sentry/api';
 import NewProject from 'sentry/views/projectInstall/newProject';
 
 describe('NewProjectPlatform', function () {
-  beforeEach(function () {
-    this.stubbedApiRequest = jest.spyOn(Client.prototype, 'request');
+  beforeEach(() => {
+    MockApiClient.addMockResponse({
+      url: `/projects/org-slug/rule-conditions/`,
+      body: TestStubs.MOCK_RESP_VERBOSE,
+    });
   });
 
-  describe('render()', function () {
-    it('should render', function () {
-      const {routerContext} = initializeOrg();
-      const wrapper = mountWithTheme(<NewProject />, routerContext);
-      expect(wrapper).toSnapshot();
-    });
+  afterEach(() => {
+    MockApiClient.clearMockResponses();
+  });
+
+  it('should render', function () {
+    const {routerContext} = initializeOrg();
+    const wrapper = render(<NewProject />, {context: routerContext});
+    expect(wrapper.container).toSnapshot();
   });
 });

+ 12 - 25
static/app/views/projectInstall/platform.spec.jsx

@@ -1,6 +1,6 @@
 import {browserHistory} from 'react-router';
 
-import {mountWithTheme} from 'sentry-test/enzyme';
+import {render, screen} from 'sentry-test/reactTestingLibrary';
 
 import {ProjectInstallPlatform} from 'sentry/views/projectInstall/platform';
 
@@ -28,10 +28,9 @@ describe('ProjectInstallPlatform', function () {
         body: {},
       });
 
-      mountWithTheme(
-        <ProjectInstallPlatform {...props} />,
-        TestStubs.routerContext([{organization: {id: '1337'}}])
-      );
+      render(<ProjectInstallPlatform {...props} />, {
+        context: TestStubs.routerContext([{organization: {id: '1337'}}]),
+      });
 
       expect(browserHistory.push).toHaveBeenCalledTimes(1);
     });
@@ -51,15 +50,11 @@ describe('ProjectInstallPlatform', function () {
         statusCode: 404,
       });
 
-      const wrapper = mountWithTheme(
-        <ProjectInstallPlatform {...props} />,
-        TestStubs.routerContext([{organization: {id: '1337'}}])
-      );
-
-      await tick();
-      wrapper.update();
+      render(<ProjectInstallPlatform {...props} />, {
+        context: TestStubs.routerContext([{organization: {id: '1337'}}]),
+      });
 
-      expect(wrapper.find('NotFound')).toHaveLength(1);
+      expect(await screen.findByText('Page Not Found')).toBeInTheDocument();
     });
 
     it('should render documentation', async function () {
@@ -77,19 +72,11 @@ describe('ProjectInstallPlatform', function () {
         body: {html: '<h1>Documentation here</h1>'},
       });
 
-      const wrapper = mountWithTheme(
-        <ProjectInstallPlatform {...props} />,
-        TestStubs.routerContext([{organization: {id: '1337'}}])
-      );
-
-      // Initially has loading indicator
-      expect(wrapper.find('LoadingIndicator')).toHaveLength(1);
-
-      await tick();
-      wrapper.update();
+      render(<ProjectInstallPlatform {...props} />, {
+        context: TestStubs.routerContext([{organization: {id: '1337'}}]),
+      });
 
-      expect(wrapper.find('DocumentationWrapper')).toHaveLength(1);
-      expect(wrapper.find('DocumentationWrapper').text()).toBe('Documentation here');
+      expect(await screen.findByText('Documentation here')).toBeInTheDocument();
     });
   });
 });