Skip to content

Commit

Permalink
feat: replace use of enzyme with react-testing-library (graphql#1144)…
Browse files Browse the repository at this point in the history
… by @ryan-m-walker

- added @testing-library/react to graphiql package
- added @testing-libary/jest-dom to graphiql package
- removed usage of ENZYM env variable in test script in graphiql packaget
- removed enzyme adapter setup from enzyme.config.js file and renamed it
to test.config.js
- removed use of enzyme and replaced with react-testing-library in all
graphiql package test files and updated tests to use
react-testing-library patterns
- moved mockStorage to own file so it can be reused in other test files
and added other methods to it
- added a CodeMirror mock to be used in tests
- mocked out codemirror addon modules
- moved QueryHistory tests from own file into GraphiQL.spec.js file
- removed `cross-env` from test script
- moved CodeMirror mock to __mock__ file

Co-authored-by: Rikki Schulte <[email protected]>
Co-authored-by: Rikki Schulte <[email protected]>
  • Loading branch information
ryan-m-walker and acao committed Dec 27, 2019
1 parent 5765394 commit de73d6c
Show file tree
Hide file tree
Showing 14 changed files with 692 additions and 586 deletions.
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module.exports = {
},
clearMocks: true,
collectCoverage: true,
setupFiles: [path.join(__dirname, '/resources/enzyme.config.js')],
setupFiles: [path.join(__dirname, '/resources/test.config.js')],
testMatch: [
'<rootDir>/packages/*/src/**/*-test.{js,ts}',
'<rootDir>/packages/*/src/**/*.spec.{js,ts}',
Expand Down
59 changes: 59 additions & 0 deletions packages/graphiql/__mocks__/codemirror.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
function CodeMirror(node, { value, ...options }) {
let _eventListeners = {};
const mockTextArea = document.createElement('textarea');
mockTextArea.className = 'mockCodeMirror';
mockTextArea.addEventListener('change', e => {
_emit('change', e);
});
mockTextArea.value = value;
node.appendChild(mockTextArea);

function _emit(event, data) {
if (_eventListeners[event]) {
_eventListeners[event](data);
}
}

return {
options: {
...options,
},

on(event, handler) {
_eventListeners[event] = handler;
},

off(event) {
if (_eventListeners.hasOwnProperty(event)) {
const updatedEventListeners = {};
for (const e in _eventListeners) {
if (e !== event) {
updatedEventListeners[e] = _eventListeners[e];
}
}
_eventListeners = updatedEventListeners;
}
},

getValue() {
return mockTextArea.value;
},

setValue(newValue) {
mockTextArea.value = newValue;
},

setSize() {},

emit: _emit,
};
}

CodeMirror.defineExtension = () => {};
CodeMirror.registerHelper = () => {};
CodeMirror.defineOption = () => {};
CodeMirror.signal = (mockCodeMirror, event, ...args) => {
mockCodeMirror.emit(event, ...args);
};

module.exports = CodeMirror;
6 changes: 3 additions & 3 deletions packages/graphiql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"cypress-open": "yarn e2e-server 'cypress open'",
"e2e": "yarn e2e-server 'cypress run'",
"e2e-server": "start-server-and-test 'cross-env PORT=8080 node test/e2e-server' 'http-get://localhost:8080/graphql?query={test { id }}'",
"test": "cross-env ENZYME=true node ../../resources/runTests",
"test": "node ../../resources/runTests",
"storybook": "start-storybook"
},
"dependencies": {
Expand All @@ -61,11 +61,11 @@
},
"devDependencies": {
"@storybook/react": "^5.2.8",
"@testing-library/jest-dom": "4.2.4",
"@testing-library/react": "9.4.0",
"cross-env": "^6.0.3",
"css-loader": "3.4.0",
"cssnano": "^4.1.10",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"express": "4.17.1",
"express-graphql": "0.9.0",
"graphql": "14.5.8",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
*/

import React from 'react';
import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

import FieldDoc from '../FieldDoc';

Expand Down Expand Up @@ -35,47 +36,58 @@ const exampleObject = new GraphQLObjectType({

describe('FieldDoc', () => {
it('should render a simple string field', () => {
const W = mount(
const { container } = render(
<FieldDoc
field={exampleObject.getFields().string}
onClickType={jest.fn()}
/>,
);
expect(W.find('MarkdownContent').text()).toEqual('No Description\n');
expect(W.find('TypeLink').text()).toEqual('String');
expect(W.find('Argument').length).toEqual(0);
expect(container.querySelector('.doc-type-description')).toHaveTextContent(
'No Description',
);
expect(container.querySelector('.type-name')).toHaveTextContent('String');
expect(container.querySelector('.arg')).not.toBeInTheDocument();
});

it('should re-render on field change', () => {
const W = mount(
const { container, rerender } = render(
<FieldDoc
field={exampleObject.getFields().string}
onClickType={jest.fn()}
/>,
);
expect(W.find('MarkdownContent').text()).toEqual('No Description\n');
expect(W.find('TypeLink').text()).toEqual('String');
expect(W.find('Argument').length).toEqual(0);
expect(container.querySelector('.doc-type-description')).toHaveTextContent(
'No Description',
);
expect(container.querySelector('.type-name')).toHaveTextContent('String');
expect(container.querySelector('.arg')).not.toBeInTheDocument();

rerender(
<FieldDoc
field={exampleObject.getFields().stringWithArgs}
onClickType={jest.fn()}
/>,
);
expect(container.querySelector('.type-name')).toHaveTextContent('String');
expect(container.querySelector('.doc-type-description')).toHaveTextContent(
'Example String field with arguments',
);
});

it('should render a string field with arguments', () => {
const W = mount(
const { container } = render(
<FieldDoc
field={exampleObject.getFields().stringWithArgs}
onClickType={jest.fn()}
/>,
);
expect(
W.find('TypeLink')
.at(0)
.text(),
).toEqual('String');
expect(
W.find('.doc-type-description')
.at(0)
.text(),
).toEqual('Example String field with arguments\n');
expect(W.find('Argument').length).toEqual(1);
expect(W.find('Argument').text()).toEqual('stringArg: String');
expect(container.querySelector('.type-name')).toHaveTextContent('String');
expect(container.querySelector('.doc-type-description')).toHaveTextContent(
'Example String field with arguments',
);
expect(container.querySelectorAll('.arg')).toHaveLength(1);
expect(container.querySelector('.arg')).toHaveTextContent(
'stringArg: String',
);
});
});
117 changes: 55 additions & 62 deletions packages/graphiql/src/components/DocExplorer/__tests__/TypeDoc.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
*/

import React from 'react';
import { mount } from 'enzyme';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

import { GraphQLString } from 'graphql';

Expand All @@ -21,115 +22,107 @@ import {

describe('TypeDoc', () => {
it('renders a top-level query object type', () => {
const W = mount(
const { container } = render(
<TypeDoc
schema={ExampleSchema}
type={ExampleQuery}
onClickType={jest.fn()}
/>,
);
const cats = W.find('.doc-category-item');
expect(cats.at(0).text()).toEqual('string: String');
expect(cats.at(1).text()).toEqual('union: exampleUnion');
expect(cats.at(2).text()).toEqual(
const cats = container.querySelectorAll('.doc-category-item');
expect(cats[0]).toHaveTextContent('string: String');
expect(cats[1]).toHaveTextContent('union: exampleUnion');
expect(cats[2]).toHaveTextContent(
'fieldWithArgs(stringArg: String): String',
);
});

it('handles onClickField and onClickType', () => {
const onClickType = jest.fn();
const onClickField = jest.fn();
const W = mount(
const { container } = render(
<TypeDoc
schema={ExampleSchema}
type={ExampleQuery}
onClickType={onClickType}
onClickField={onClickField}
/>,
);
W.find('TypeLink')
.at(0)
.simulate('click');
fireEvent.click(container.querySelector('.type-name'));
expect(onClickType.mock.calls.length).toEqual(1);
expect(onClickType.mock.calls[0][0]).toEqual(GraphQLString);

W.find('.field-name')
.at(0)
.simulate('click');

fireEvent.click(container.querySelector('.field-name'));
expect(onClickField.mock.calls.length).toEqual(1);
expect(onClickField.mock.calls[0][0].name).toEqual('string');
expect(onClickField.mock.calls[0][0].type).toEqual(GraphQLString);
expect(onClickField.mock.calls[0][1]).toEqual(ExampleQuery);
});

it('renders deprecated fields when you click to see them', () => {
const W = mount(
const { container } = render(
<TypeDoc
schema={ExampleSchema}
type={ExampleQuery}
onClickType={jest.fn()}
/>,
);
let cats = W.find('.doc-category-item');
expect(cats.length).toEqual(3);
let cats = container.querySelectorAll('.doc-category-item');
expect(cats).toHaveLength(3);

W.find('.show-btn').simulate('click');
fireEvent.click(container.querySelector('.show-btn'));

cats = W.find('.doc-category-item');
expect(cats.length).toEqual(4);
expect(
W.find('.field-name')
.at(3)
.text(),
).toEqual('deprecatedField');
expect(
W.find('.doc-deprecation')
.at(0)
.text(),
).toEqual('example deprecation reason\n');
cats = container.querySelectorAll('.doc-category-item');
expect(cats).toHaveLength(4);
expect(container.querySelectorAll('.field-name')[3]).toHaveTextContent(
'deprecatedField',
);
expect(container.querySelector('.doc-deprecation')).toHaveTextContent(
'example deprecation reason',
);
});

it('renders a Union type', () => {
const W = mount(<TypeDoc schema={ExampleSchema} type={ExampleUnion} />);
expect(
W.find('.doc-category-title')
.at(0)
.text(),
).toEqual('possible types');
const { container } = render(
<TypeDoc schema={ExampleSchema} type={ExampleUnion} />,
);
expect(container.querySelector('.doc-category-title')).toHaveTextContent(
'possible types',
);
});

it('renders an Enum type', () => {
const W = mount(<TypeDoc schema={ExampleSchema} type={ExampleEnum} />);
expect(
W.find('.doc-category-title')
.at(0)
.text(),
).toEqual('values');
const enums = W.find('EnumValue');
expect(enums.at(0).props().value.value).toEqual('Value 1');
expect(enums.at(1).props().value.value).toEqual('Value 2');
const { container } = render(
<TypeDoc schema={ExampleSchema} type={ExampleEnum} />,
);
expect(container.querySelector('.doc-category-title')).toHaveTextContent(
'values',
);
const enums = container.querySelectorAll('.enum-value');
expect(enums[0]).toHaveTextContent('value1');
expect(enums[1]).toHaveTextContent('value2');
});

it('shows deprecated enum values on click', () => {
const W = mount(<TypeDoc schema={ExampleSchema} type={ExampleEnum} />);
expect(W.state().showDeprecated).toEqual(false);
const titles = W.find('.doc-category-title');
expect(titles.at(0).text()).toEqual('values');
expect(titles.at(1).text()).toEqual('deprecated values');
let enums = W.find('EnumValue');
expect(enums.length).toEqual(2);
const { getByText, container } = render(
<TypeDoc schema={ExampleSchema} type={ExampleEnum} />,
);
const showBtn = getByText('Show deprecated values...');
expect(showBtn).toBeInTheDocument();
const titles = container.querySelectorAll('.doc-category-title');
expect(titles[0]).toHaveTextContent('values');
expect(titles[1]).toHaveTextContent('deprecated values');
let enums = container.querySelectorAll('.enum-value');
expect(enums).toHaveLength(2);

// click button to show deprecated enum values
W.find('.show-btn').simulate('click');
expect(W.state().showDeprecated).toEqual(true);
enums = W.find('EnumValue');
expect(enums.length).toEqual(3);
expect(enums.at(2).props().value.value).toEqual('Value 3');
expect(
W.find('.doc-deprecation')
.at(1)
.text(),
).toEqual('Only two are needed\n');
fireEvent.click(showBtn);
expect(showBtn).not.toBeInTheDocument();
enums = container.querySelectorAll('.enum-value');
expect(enums).toHaveLength(3);
expect(enums[2]).toHaveTextContent('value3');
expect(container.querySelector('.doc-deprecation')).toHaveTextContent(
'Only two are needed',
);
});
});
Loading

0 comments on commit de73d6c

Please sign in to comment.