Browse Source

fix(vsts): Fix VSTS project select (#8894)

Fixes JAVASCRIPT-3ZH
Billy Vong 6 years ago
parent
commit
e3fb986060

+ 1 - 0
src/sentry/static/sentry/app/components/forms/genericField.jsx

@@ -40,6 +40,7 @@ export default class GenericField extends React.Component {
       required,
       name: config.name,
       error: (this.props.formErrors || {})[config.name],
+      defaultValue: config.default,
       disabled: config.readonly,
       key: config.name,
       formState: this.props.formState,

+ 13 - 2
src/sentry/static/sentry/app/components/forms/selectCreatableField.jsx

@@ -43,8 +43,19 @@ export default class SelectCreatableField extends FormField {
       // This is the only thing that is different from parent, we compare newValue against coerved value in state
       // To remain compatible with react-select, we need to store the option object that
       // includes `value` and `label`, but when we submit the format, we need to coerce it
-      // to just return `value`. Also when field changes, it propagates the coerved value up
-      if (newValue !== this.coerceValue(this.state.value)) {
+      // to just return `value`. Also when field changes, it propagates the coerced value up
+      let coercedValue = this.coerceValue(this.state.value);
+
+      // newValue can be empty string because of `getValue`, while coerceValue needs to return null (to differentiate
+      // empty string from cleared item). We could use `!=` to compare, but lets be a bit more explicit with strict equality
+      //
+      // This can happen when this is apart of a field, and it re-renders onChange for a different field,
+      // there will be a mismatch between this component's state.value and `this.getValue` result above
+      if (
+        newValue !== coercedValue &&
+        !!newValue !== !!coercedValue &&
+        newValue !== this.state.value
+      ) {
         this.setValue(newValue);
       }
     }

+ 55 - 0
tests/js/fixtures/vsts-old.js

@@ -0,0 +1,55 @@
+function VstsPlugin(params) {
+  return {
+    status: 'unknown',
+    description: 'Integrate Visual Studio Team Services work items by linking a project.',
+    isTestable: false,
+    hasConfiguration: true,
+    shortName: 'VSTS',
+    slug: 'vsts',
+    name: 'Visual Studio Team Services',
+    assets: [],
+    title: 'Visual Studio Team Services',
+    contexts: [],
+    doc: '',
+    resourceLinks: [
+      {url: 'https://github.com/getsentry/sentry-plugins/issues', title: 'Bug Tracker'},
+      {url: 'https://github.com/getsentry/sentry-plugins', title: 'Source'},
+    ],
+    allowed_actions: ['create', 'link', 'unlink'],
+    enabled: true,
+    id: 'vsts',
+    version: '9.1.0.dev0',
+    canDisable: true,
+    author: {url: 'https://github.com/getsentry/sentry-plugins', name: 'Sentry Team'},
+    type: 'issue-tracking',
+    metadata: {},
+  };
+}
+
+function VstsCreate(params) {
+  return [
+    {
+      name: 'project',
+      default: 'Sentry Testing Team',
+      required: true,
+      choices: ['Test', 'Sentry Testing'],
+      label: 'Project',
+      type: 'text',
+    },
+    {
+      default: "TypeError: Cannot read property 'secondsElapsed' of undefined",
+      type: 'text',
+      name: 'title',
+      label: 'Title',
+    },
+    {
+      default:
+        "https://sentry.io/sentry-billy/react/issues/590943704/\n\n```\nTypeError: Cannot read property 'secondsElapsed' of undefined\n  at value (/Users/billy/Dev/raven-js-examples/commonjs-react/dist/scripts/app.js:1:4193)\n  at r (/Users/billy/Dev/raven-js-examples/commonjs-react/dist/scripts/app.js:1:17533)\n```",
+      type: 'textarea',
+      name: 'description',
+      label: 'Description',
+    },
+  ];
+}
+
+export {VstsPlugin, VstsCreate};

+ 3 - 0
tests/js/setup.js

@@ -12,6 +12,7 @@ import theme from 'app/utils/theme';
 import RoleList from './fixtures/roleList';
 import Release from './fixtures/release';
 import {AsanaPlugin, AsanaCreate, AsanaAutocomplete} from './fixtures/asana';
+import {VstsPlugin, VstsCreate} from './fixtures/vsts-old';
 
 jest.mock('lodash/debounce', () => jest.fn(fn => fn));
 jest.mock('app/utils/recreateRoute');
@@ -1082,6 +1083,8 @@ window.TestStubs = {
   AsanaPlugin,
   AsanaCreate,
   AsanaAutocomplete,
+  VstsPlugin,
+  VstsCreate,
 };
 
 // this is very commonly used, so expose it globally

+ 170 - 0
tests/js/spec/integrations/vsts-plugin.spec.jsx

@@ -0,0 +1,170 @@
+import React from 'react';
+import {mount} from 'enzyme';
+
+import IssuePluginActions from 'app/components/group/issuePluginActions';
+
+jest.mock('jquery');
+
+describe('Vsts', function() {
+  let plugin = TestStubs.VstsPlugin();
+  // Note group is different than group in VstsCreate fixture
+  let group = TestStubs.Group();
+  let TITLE = 'input[id="id-title"]';
+  let NOTES = 'textarea[id="id-description"]';
+  // let WORKSPACE = '[id="id-workspace"]';
+  let PROJECT = '[id="id-project"]';
+  let VstsCreateResponse = TestStubs.VstsCreate();
+  let createMock = jest.fn();
+
+  beforeEach(function() {
+    MockApiClient.addMockResponse({
+      url: `/issues/${group.id}/plugins/vsts/create/`,
+      body: VstsCreateResponse,
+    });
+  });
+
+  it('can create a new issue', async function() {
+    let wrapper = mount(
+      <IssuePluginActions plugin={plugin} />,
+      TestStubs.routerContext([
+        {
+          group,
+        },
+      ])
+    );
+
+    wrapper
+      .find('MenuItem a')
+      .first()
+      .simulate('click');
+
+    expect(wrapper.find(TITLE).prop('value')).toBe(
+      "TypeError: Cannot read property 'secondsElapsed' of undefined"
+    );
+    wrapper.find(TITLE).simulate('change', {target: {value: 'Sentry Issue Title'}});
+    wrapper.find(NOTES).simulate('change', {target: {value: 'Notes'}});
+
+    wrapper.find(`input${PROJECT}`).simulate('change', {target: {value: 'b'}});
+    await tick();
+    wrapper.update();
+
+    wrapper.find(`input${PROJECT}`).simulate('keyDown', {keyCode: 13});
+
+    await tick();
+    wrapper.update();
+
+    createMock = MockApiClient.addMockResponse({
+      url: `/issues/${group.id}/plugins/vsts/create/`,
+      body: VstsCreateResponse,
+    });
+
+    wrapper.find('Modal Form').simulate('submit');
+    await tick();
+    wrapper.update();
+
+    expect(createMock).toHaveBeenCalledWith(
+      expect.anything(),
+      expect.objectContaining({
+        data: expect.objectContaining({
+          description: 'Notes',
+          title: 'Sentry Issue Title',
+          project: 'b',
+        }),
+      })
+    );
+  });
+
+  it('uses the default project', async function() {
+    let wrapper = mount(
+      <IssuePluginActions plugin={plugin} />,
+      TestStubs.routerContext([
+        {
+          group,
+        },
+      ])
+    );
+
+    wrapper
+      .find('MenuItem a')
+      .first()
+      .simulate('click');
+
+    // Default value should be set
+    expect(
+      wrapper
+        .find('Select')
+        .first()
+        .prop('value')
+    ).toEqual('Sentry Testing Team');
+
+    createMock = MockApiClient.addMockResponse({
+      url: `/issues/${group.id}/plugins/vsts/create/`,
+      body: VstsCreateResponse,
+    });
+
+    wrapper.find('Modal Form').simulate('submit');
+    await tick();
+    wrapper.update();
+
+    expect(createMock).toHaveBeenCalledWith(
+      expect.anything(),
+      expect.objectContaining({
+        data: expect.objectContaining({
+          description:
+            "https://sentry.io/sentry-billy/react/issues/590943704/\n\n```\nTypeError: Cannot read property 'secondsElapsed' of undefined\n  at value (/Users/billy/Dev/raven-js-examples/commonjs-react/dist/scripts/app.js:1:4193)\n  at r (/Users/billy/Dev/raven-js-examples/commonjs-react/dist/scripts/app.js:1:17533)\n```",
+          title: "TypeError: Cannot read property 'secondsElapsed' of undefined",
+          project: 'Sentry Testing Team',
+        }),
+      })
+    );
+  });
+
+  it('can switch project to "Test"', async function() {
+    let wrapper = mount(
+      <IssuePluginActions plugin={plugin} />,
+      TestStubs.routerContext([
+        {
+          group,
+        },
+      ])
+    );
+
+    wrapper
+      .find('MenuItem a')
+      .first()
+      .simulate('click');
+
+    // Default value should be set
+    wrapper.find(`input${PROJECT}`).simulate('change', {target: {value: ''}});
+    wrapper.find(`input${PROJECT}`).simulate('keyDown', {keyCode: 13});
+    // wrapper.find('Select Option[children="Test"]').simulate('click');
+
+    expect(
+      wrapper
+        .find('Select')
+        .first()
+        .prop('value')
+    ).toEqual({label: 'Test', value: 'Test'});
+
+    createMock = MockApiClient.addMockResponse({
+      url: `/issues/${group.id}/plugins/vsts/create/`,
+      body: VstsCreateResponse,
+    });
+
+    wrapper.find('Modal Form').simulate('submit');
+    await tick();
+    wrapper.update();
+
+    expect(createMock).toHaveBeenCalledWith(
+      expect.anything(),
+      expect.objectContaining({
+        data: expect.objectContaining({
+          description:
+            "https://sentry.io/sentry-billy/react/issues/590943704/\n\n```\nTypeError: Cannot read property 'secondsElapsed' of undefined\n  at value (/Users/billy/Dev/raven-js-examples/commonjs-react/dist/scripts/app.js:1:4193)\n  at r (/Users/billy/Dev/raven-js-examples/commonjs-react/dist/scripts/app.js:1:17533)\n```",
+          title: "TypeError: Cannot read property 'secondsElapsed' of undefined",
+          project: 'Test',
+        }),
+      })
+    );
+  });
+});