Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Form tests - Phase 1 #196

Merged
merged 11 commits into from
Dec 14, 2017
Merged

Form tests - Phase 1 #196

merged 11 commits into from
Dec 14, 2017

Conversation

chrisronline
Copy link
Contributor

Relates to #177

Adds tests for:

  • Checkbox
  • CheckboxGroup
  • FieldNumber
  • FieldPassword
  • FieldSearch
  • FieldText

For CheckboxGroup, there are two separate test files. This is because for one, we want to mock Checkbox and the other we do not. It's difficult/complicated to handle mocking and unmocking in a single file.

@cjcenizal cjcenizal mentioned this pull request Dec 7, 2017
22 tasks
Copy link
Contributor

@cjcenizal cjcenizal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet! I have some suggestions, including ways we can reduce the size of these snapshots.

});

describe('label', () => {
[(<span>Label</span>)].forEach(value => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to change this to "Label" and wrap the value in <span> on line 71, or else the description text for this test outputs [object Object] in the snapshot.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if we're not actually iterating over multiple values, I think it makes sense to make the test simpler and terser. This makes the test more readable and eliminates any confusion that we may intend to support multiple different types of label inputs.

    describe('label', () => {
      test(`is rendered`, () => {
        const component = render(
          <EuiCheckbox
            {...checkboxRequiredProps}
            label={<span>Label</span>}
          />
        );

        expect(component)
          .toMatchSnapshot();
      });
    });

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!


describe('props', () => {
test('id is required', () => {
expect(() => render(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this render call since we're not generating a snapshot or simulating user interaction with the component.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, good call!

});

test('onChange is required', () => {
expect(() => render(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++

});

describe('checked', () => {
[true, false].forEach(value => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For boolean props like this, I've usually just been asserting the true value out of pragmatism, since it seems unlikely we'd break the behavior of this component when the prop is false. Also, since false is the default then we're already implicitly testing this behavior in all of our other tests. This reduces snapshot code a bit. What do you think? Do you think it's worth keeping both assertions?

    describe('checked', () => {
      test(`is rendered`, () => {
        const component = render(
          <EuiCheckbox
            {...checkboxRequiredProps}
            checked
          />
        );

        expect(component)
          .toMatchSnapshot();
      });
    });

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do it!

onChange: PropTypes.func.isRequired,
type: PropTypes.oneOf(TYPES),
disabled: PropTypes.bool,
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can delete this commented-out code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh I'm trying to not leave these in, but apparently need to do better

<EuiCheckboxGroup
options={[
{ id: '1', label: 'kibana', disabled: false },
{ id: '2', label: 'elastic', disabled: true },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like disabled is a prop for the EuiCheckboxGroup itself. So we can remove it from these options objects and on lines 38-39. We need to add another test for this prop though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call!

describe('EuiCheckboxGroupMockedCheckbox', () => {
test('is rendered', () => {
const component = render(
<EuiCheckboxGroup onChange={() => {}} {...requiredProps} id="foobar" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to pass in id?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm passing it in as an arbitrary property that will fall into the ...rest and ensure they are still rendered. I can use a different property, but I feel that's a valuable thing to ensure

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right that we should be testing that we're able to pass arbitrary props in and they'll pass-through. But that's also what the requiredProps are for, so I think passing those in already fulfills this intention. What do you think?

@@ -0,0 +1,32 @@
import React from 'react';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use sinon stubs to simplify our assertion:

import React from 'react';
import { mount } from 'enzyme';
import sinon from 'sinon';

import { EuiCheckboxGroup } from './checkbox_group';

// This exists because we need to run the following tests
// without mocking the Checkbox component, such as testing
// an interaction that is handled by the Checkbox component.
describe('EuiCheckboxGroupUnmockedCheckbox', () => {
  test('onChange receives id of changed checkbox', () => {
    const onChangeHandler = sinon.stub();

    const component = mount(
      <EuiCheckboxGroup
        options={[
          { id: '1', label: 'kibana', disabled: false },
        ]}
        idToSelectedMap={{
          '1': true,
        }}
        onChange={onChangeHandler}
      />
    );

    component.find('input[type="checkbox"]').simulate('change');
    sinon.assert.calledWith(onChangeHandler, '1');
  });
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fab!

@@ -0,0 +1,52 @@
import React from 'react';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this file contains our basic set of tests, I think we can treat this file as the "regular" test file and call it checkbox_group.test.js. And then we can treat the other one as the extension beyond these basic tests. Since it's mostly testing behavior in response to user interaction, how about we call it checkbox_group.behavior.test.js?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

describe('props', () => {
describe('isInvalid', () => {
[true, false].forEach(value => {
test(`${value} is rendered`, () => {
Copy link
Contributor

@cjcenizal cjcenizal Dec 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isInvalid will actually call setCustomValidity('Invalid') on the input under the hood (https://github.com/elastic/eui/blob/master/src/components/form/validatable_control/validatable_control.js#L19), so it won't render anything. I think this test should ideally look something like this:

    describe('isInvalid', () => {
      test(`sets custom validity error`, () => {
        const component = mount(
          <EuiFieldNumber
            isInvalid
          />
        );
        expect(component.find('input').hostNodes().getDOMNode().validity.customError).toBe(true);
      });
    });

Unfortunately, jsdom doesn't support this API yet so it doesn't work. What do you think of leaving a TODO for now?

    describe('isInvalid', () => {
      test('sets custom validity error', () => {
        // TODO: Assert that the input's validity.customError is set to true. Blocked by
        // https://github.com/tmpvar/jsdom/issues/544. What we want to do is:
        // expect(component.find('input').hostNodes().getDOMNode().validity.customError).toBe(true);
      });
    });

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice find for sure.

My thoughts are since this is a unit test, we shouldn't need to worry about what the dependencies are doing. We just need to verify that the property is indeed added, which I think the snapshot test does.

This also means we can freely change the implementation details of EuiValidatableControl without needing to update these tests.

Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh you're right. Sounds good!

Copy link
Contributor

@cjcenizal cjcenizal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! Just had 3 suggestions and 1 question.

});

describe('disabled', () => {
[true, false].forEach(value => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get away with just testing true?

// This exists because we need to run the following tests
// without mocking the Checkbox component, such as testing
// an interaction that is handled by the Checkbox component.
describe('EuiCheckboxGroupUnmockedCheckbox', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rephrase this as "EuiCheckboxGroup behavior" to make it a bit more readable and reflect the file name?

test('is rendered', () => {
const component = render(
<EuiCheckboxGroup onChange={() => {}} {...requiredProps} />
<EuiCheckboxGroup onChange={() => {}} {...requiredProps} id="foobar" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What were your thoughts on id here? I feel like requiredProps is sufficient for verifying that DOM attributes get passed-through to the element that gets rendered.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

describe('EuiCheckboxGroup', () => {
jest.mock('./checkbox', () => ({ EuiCheckbox: 'eui_checkbox' }));

describe('EuiCheckboxGroupMockedCheckbox', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rephrase this as "EuiCheckboxGroup (mocked checkbox)" to make it a bit more readable?

@chrisronline
Copy link
Contributor Author

@cjcenizal Ready for another pass!

Copy link
Contributor

@cjcenizal cjcenizal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎸 LGTM!

@chrisronline chrisronline merged commit 0efd099 into elastic:master Dec 14, 2017
@chrisronline chrisronline deleted the form-tests branch December 14, 2017 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants