From d868a3bcaacc7f1c49bae855c401a61439ca2be1 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 26 Feb 2021 01:08:57 -0500 Subject: [PATCH 01/37] miraculously got something working with redux-mock-store for testing redux referenced this blog: https://www.robinwieruch.de/react-connected-component-test --- .../IDE/components/SketchList.test.jsx | 87 +++++++++++++++++++ client/test-utils.js | 28 +++++- package.json | 1 + 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 client/modules/IDE/components/SketchList.test.jsx diff --git a/client/modules/IDE/components/SketchList.test.jsx b/client/modules/IDE/components/SketchList.test.jsx new file mode 100644 index 0000000000..6091c61162 --- /dev/null +++ b/client/modules/IDE/components/SketchList.test.jsx @@ -0,0 +1,87 @@ +import React from 'react'; +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { unmountComponentAtNode } from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import { reduxRender, prettyDOM } from '../../../test-utils'; +import SketchList from './SketchList'; + +const mockStore = configureStore([thunk]); + +const initialTestState = { + ide: null, + files: [], + preferences: {}, + user: { + email: 'happydog@example.com', + username: 'happydog', + preferences: {}, + apiKeys: [], + verified: 'sent', + id: '123456789', + totalSize: 0, + authenticated: true + }, + project: null, + sketches: [ + { + name: 'testsketch1', + _id: 'testid1', + updatedAt: '2021-02-26T04:58:29.390Z', + files: [], + createdAt: '2021-02-26T04:58:14.136Z', + id: 'testid1' + }, + { + name: 'testsketch2', + _id: 'testid2', + updatedAt: '2021-02-23T17:40:43.789Z', + files: [], + createdAt: '2021-02-23T17:40:43.789Z', + id: 'testid2' + } + ], + search: { + collectionSearchTerm: '', + sketchSearchTerm: '' + }, + sorting: { + field: 'createdAt', + direction: 'DESCENDING' + }, + editorAccessibility: {}, + toast: {}, + console: [], + assets: {}, + loading: false, + collections: [] +}; + +describe('', () => { + let container = null; + let store; + beforeEach(() => { + // setup a DOM element as a render target + container = document.createElement('div'); + document.body.appendChild(container); + store = mockStore(initialTestState); + }); + + afterEach(() => { + // cleanup on exiting + unmountComponentAtNode(container); + container.remove(); + container = null; + }); + + describe('', () => { + it('render', () => { + let component; + // render the component with autosave set to false as default + act(() => { + component = reduxRender(, { store, container }); + }); + console.log(prettyDOM(container)); + }); + }); +}); diff --git a/client/test-utils.js b/client/test-utils.js index 8847383068..05369e013b 100644 --- a/client/test-utils.js +++ b/client/test-utils.js @@ -13,9 +13,12 @@ import { render } from '@testing-library/react'; import React from 'react'; import PropTypes from 'prop-types'; +import { createStore } from 'redux'; +import { Provider } from 'react-redux'; import { I18nextProvider } from 'react-i18next'; import i18n from './i18n-test'; +import rootReducer from './reducers'; // re-export everything // eslint-disable-next-line import/no-extraneous-dependencies @@ -30,8 +33,31 @@ Providers.propTypes = { children: PropTypes.element.isRequired }; +function reduxRender( + ui, + { + initialState, + store = createStore(rootReducer, initialState), + ...renderOptions + } = {} +) { + function Wrapper({ children }) { + return ( + + {children} + + ); + } + + Wrapper.propTypes = { + children: PropTypes.element.isRequired + }; + + return render(ui, { wrapper: Wrapper, ...renderOptions }); +} + const customRender = (ui, options) => render(ui, { wrapper: Providers, ...options }); // override render method -export { customRender as render }; +export { customRender as render, reduxRender }; diff --git a/package.json b/package.json index 29ef1c35a5..044ce25f7a 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,7 @@ "postcss-preset-env": "^6.7.0", "prettier": "^2.2.1", "react-test-renderer": "^16.12.0", + "redux-mock-store": "^1.5.4", "rimraf": "^2.7.1", "sass-loader": "^10.1.1", "storybook-addon-theme-playground": "^1.2.0", From dc69ed49c65afd9212fa0b65853d35bb6a39b7ee Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 26 Feb 2021 14:00:14 -0500 Subject: [PATCH 02/37] synchronous tests working, async ones are not --- client/modules/IDE/components/SketchList.jsx | 1 + .../IDE/components/SketchList.test.jsx | 118 +++++++++++++----- package.json | 1 + 3 files changed, 92 insertions(+), 28 deletions(-) diff --git a/client/modules/IDE/components/SketchList.jsx b/client/modules/IDE/components/SketchList.jsx index 757bacd904..cd54b39342 100644 --- a/client/modules/IDE/components/SketchList.jsx +++ b/client/modules/IDE/components/SketchList.jsx @@ -449,6 +449,7 @@ class SketchList extends React.Component { className="sketch-list__sort-button" onClick={() => this.props.toggleDirectionForField(fieldName)} aria-label={buttonLabel} + data-testid={`toggle-direction-${fieldName}`} > {displayName} {field === fieldName && diff --git a/client/modules/IDE/components/SketchList.test.jsx b/client/modules/IDE/components/SketchList.test.jsx index 6091c61162..50c85ec1eb 100644 --- a/client/modules/IDE/components/SketchList.test.jsx +++ b/client/modules/IDE/components/SketchList.test.jsx @@ -3,11 +3,33 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { unmountComponentAtNode } from 'react-dom'; import { act } from 'react-dom/test-utils'; -import { reduxRender, prettyDOM } from '../../../test-utils'; +import moxios from 'moxios'; +import * as ProjectActions from '../actions/projects'; +import * as ActionTypes from '../../../constants'; import SketchList from './SketchList'; +import { reduxRender, fireEvent, screen } from '../../../test-utils'; const mockStore = configureStore([thunk]); +const mockProjects = [ + { + name: 'testsketch1', + _id: 'testid1', + updatedAt: '2021-02-26T04:58:29.390Z', + files: [], + createdAt: '2021-02-26T04:58:14.136Z', + id: 'testid1' + }, + { + name: 'testsketch2', + _id: 'testid2', + updatedAt: '2021-02-23T17:40:43.789Z', + files: [], + createdAt: '2021-02-23T17:40:43.789Z', + id: 'testid2' + } +]; + const initialTestState = { ide: null, files: [], @@ -23,24 +45,7 @@ const initialTestState = { authenticated: true }, project: null, - sketches: [ - { - name: 'testsketch1', - _id: 'testid1', - updatedAt: '2021-02-26T04:58:29.390Z', - files: [], - createdAt: '2021-02-26T04:58:14.136Z', - id: 'testid1' - }, - { - name: 'testsketch2', - _id: 'testid2', - updatedAt: '2021-02-23T17:40:43.789Z', - files: [], - createdAt: '2021-02-23T17:40:43.789Z', - id: 'testid2' - } - ], + sketches: mockProjects, search: { collectionSearchTerm: '', sketchSearchTerm: '' @@ -64,7 +69,6 @@ describe('', () => { // setup a DOM element as a render target container = document.createElement('div'); document.body.appendChild(container); - store = mockStore(initialTestState); }); afterEach(() => { @@ -72,16 +76,74 @@ describe('', () => { unmountComponentAtNode(container); container.remove(); container = null; + store.clearActions(); }); - describe('', () => { - it('render', () => { - let component; - // render the component with autosave set to false as default - act(() => { - component = reduxRender(, { store, container }); - }); - console.log(prettyDOM(container)); + // it('creates GET_PROJECTS after successfuly fetching projects', async () => { + // moxios.install(); + // function resolveAfter2Seconds() { + // console.log("called resolve"); + // return new Promise(resolve => { + // setTimeout(() => { + // resolve('resolved'); + // }, 5000); + // }); + // } + // console.log("moxios", moxios); + // moxios.wait(() => { + + // const request = moxios.requests.mostRecent(); + // console.log(moxios.requests, request) + // console.log("recieved request for get projects") + // request.respondWith({ + // status: 200, + // response: mockProjects, + // }).then(() => done()); + // }); + + // const expectedActions = [ + // {type: ActionTypes.START_LOADING}, + // { type: ActionTypes.SET_PROJECTS, + // projects: mockProjects } + // ]; + + // store = mockStore(initialTestState); + // console.log("dispatching action"); + // //store.dispatch(ProjectActions.getProjects("happydog")) + // act(() => { + // reduxRender(, { store, container }); + // }); + + // const hasSetProjectKey = (currActions) => { + // return currActions.filter(ac => ac.type === ActionTypes.SET_PROJECTS).length > 0; + // } + + // await waitFor(() => expect(hasSetProjectKey(store.getActions())).toEqual(true)); + // moxios.uninstall(); + // //expect(store.getActions()).toEqual(expectedActions); + // //return resolveAfter2Seconds().then(res => console.log("resolved")) + // }); + + it('has both of the sample projects', () => { + store = mockStore(initialTestState); + let component; + act(() => { + component = reduxRender(, { store, container }); + }); + expect(screen.getByText('testsketch1')).toBeInTheDocument(); + expect(screen.getByText('testsketch2')).toBeInTheDocument(); + }); + + it('clicking on date created row header dispatches a reordering action', () => { + store = mockStore(initialTestState); + let component; + act(() => { + component = reduxRender(, { store, container }); + }); + act(() => { + fireEvent.click(screen.getByTestId('toggle-direction-createdAt')); }); + const expectedAction = [{ type: 'TOGGLE_DIRECTION', field: 'createdAt' }]; + expect(store.getActions()).toEqual(expect.arrayContaining(expectedAction)); }); }); diff --git a/package.json b/package.json index 044ce25f7a..e84f8e0d05 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,7 @@ "jest": "^26.0.1", "lint-staged": "^10.1.3", "mini-css-extract-plugin": "^1.3.4", + "moxios": "^0.4.0", "node-sass": "^5.0.0", "nodemon": "^2.0.7", "optimize-css-assets-webpack-plugin": "^5.0.3", From 48269698cf208e510d8e5260178078c4c4e7daf6 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Thu, 4 Mar 2021 12:32:42 -0500 Subject: [PATCH 03/37] remove moxios, add redux-mock-store to test redux --- package-lock.json | 15 +++++++++++++++ package.json | 1 - 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 49bb0f9e17..71afac118c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27432,6 +27432,12 @@ "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=" }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -34917,6 +34923,15 @@ "base16": "^1.0.0" } }, + "redux-mock-store": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.4.tgz", + "integrity": "sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA==", + "dev": true, + "requires": { + "lodash.isplainobject": "^4.0.6" + } + }, "redux-thunk": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", diff --git a/package.json b/package.json index e84f8e0d05..044ce25f7a 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,6 @@ "jest": "^26.0.1", "lint-staged": "^10.1.3", "mini-css-extract-plugin": "^1.3.4", - "moxios": "^0.4.0", "node-sass": "^5.0.0", "nodemon": "^2.0.7", "optimize-css-assets-webpack-plugin": "^5.0.3", From 79eab165d1fa2588bd1208081487a979d81a62ed Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 12 Mar 2021 14:59:42 -0500 Subject: [PATCH 04/37] Sketchlist and projects action creator tests --- client/__mocks__/axios.js | 6 + client/__mocks__/i18n.js | 28 ++ .../modules/IDE/actions/__mocks__/projects.js | 16 + .../IDE/actions/__tests__/projects.test.jsx | 43 +++ client/modules/IDE/actions/projects.js | 2 +- client/modules/IDE/components/SketchList.jsx | 2 +- .../IDE/components/SketchList.test.jsx | 192 +++++------ .../__snapshots__/SketchList.test.jsx.snap | 136 ++++++++ client/redux_test_stores/test_store.js | 52 +++ developer_docs/testing.md | 301 ++++++++++++++++++ 10 files changed, 671 insertions(+), 107 deletions(-) create mode 100644 client/__mocks__/axios.js create mode 100644 client/__mocks__/i18n.js create mode 100644 client/modules/IDE/actions/__mocks__/projects.js create mode 100644 client/modules/IDE/actions/__tests__/projects.test.jsx create mode 100644 client/modules/IDE/components/__snapshots__/SketchList.test.jsx.snap create mode 100644 client/redux_test_stores/test_store.js create mode 100644 developer_docs/testing.md diff --git a/client/__mocks__/axios.js b/client/__mocks__/axios.js new file mode 100644 index 0000000000..1448466e75 --- /dev/null +++ b/client/__mocks__/axios.js @@ -0,0 +1,6 @@ +const mockAxios = jest.genMockFromModule('axios'); + +// this is the key to fix the axios.create() undefined error! +mockAxios.create = jest.fn(() => mockAxios); + +export default mockAxios; diff --git a/client/__mocks__/i18n.js b/client/__mocks__/i18n.js new file mode 100644 index 0000000000..4ccdd10dfe --- /dev/null +++ b/client/__mocks__/i18n.js @@ -0,0 +1,28 @@ +import { enUS, es, ja, hi } from 'date-fns/locale'; +import i18n from '../i18n-test'; + +export function languageKeyToLabel(lang) { + const languageMap = { + 'en-US': 'English', + 'es-419': 'Español', + ja: '日本語', + hi: 'हिन्दी' + }; + return languageMap[lang]; +} + +export function languageKeyToDateLocale(lang) { + const languageMap = { + 'en-US': enUS, + 'es-419': es, + ja, + hi + }; + return languageMap[lang]; +} + +export function currentDateLocale() { + return languageKeyToDateLocale(i18n.language); +} + +export default i18n; diff --git a/client/modules/IDE/actions/__mocks__/projects.js b/client/modules/IDE/actions/__mocks__/projects.js new file mode 100644 index 0000000000..6ec855639a --- /dev/null +++ b/client/modules/IDE/actions/__mocks__/projects.js @@ -0,0 +1,16 @@ +import * as ActionTypes from '../../../../constants'; +import { startLoader, stopLoader } from '../loader'; +import { mockProjects } from '../../../../redux_test_stores/test_store'; + +// eslint-disable-next-line +export function getProjects(username) { + console.log(`mocked getProjects call with ${username}`); + return (dispatch) => { + dispatch(startLoader()); + dispatch({ + type: ActionTypes.SET_PROJECTS, + projects: mockProjects + }); + dispatch(stopLoader()); + }; +} diff --git a/client/modules/IDE/actions/__tests__/projects.test.jsx b/client/modules/IDE/actions/__tests__/projects.test.jsx new file mode 100644 index 0000000000..1f8b720885 --- /dev/null +++ b/client/modules/IDE/actions/__tests__/projects.test.jsx @@ -0,0 +1,43 @@ +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import axios from 'axios'; +import { unmountComponentAtNode } from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import * as ProjectActions from '../projects'; +import * as ActionTypes from '../../../../constants'; +import { + initialTestState, + mockProjects +} from '../../../../redux_test_stores/test_store'; + +// look into this +// https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach + +const mockStore = configureStore([thunk]); + +describe('projects action creator tests', () => { + let store; + + afterEach(() => { + store.clearActions(); + }); + + it('creates GET_PROJECTS after successfuly fetching projects', () => { + store = mockStore(initialTestState); + + axios.get.mockImplementationOnce((x) => { + console.log('get was called with ', x); + return Promise.resolve({ data: mockProjects }); + }); + + const expectedActions = [ + { type: ActionTypes.START_LOADING }, + { type: ActionTypes.SET_PROJECTS, projects: mockProjects }, + { type: ActionTypes.STOP_LOADING } + ]; + + return store + .dispatch(ProjectActions.getProjects('happydog')) + .then(() => expect(store.getActions()).toEqual(expectedActions)); + }); +}); diff --git a/client/modules/IDE/actions/projects.js b/client/modules/IDE/actions/projects.js index fabda631a0..4072429af4 100644 --- a/client/modules/IDE/actions/projects.js +++ b/client/modules/IDE/actions/projects.js @@ -12,7 +12,7 @@ export function getProjects(username) { } else { url = '/projects'; } - apiClient + return apiClient .get(url) .then((response) => { dispatch({ diff --git a/client/modules/IDE/components/SketchList.jsx b/client/modules/IDE/components/SketchList.jsx index cd54b39342..05cc1d92b6 100644 --- a/client/modules/IDE/components/SketchList.jsx +++ b/client/modules/IDE/components/SketchList.jsx @@ -179,7 +179,6 @@ class SketchListRowBase extends React.Component { renderDropdown = () => { const { optionsOpen } = this.state; const userIsOwner = this.props.user.username === this.props.username; - return ( diff --git a/client/modules/IDE/components/SketchList.test.jsx b/client/modules/IDE/components/SketchList.test.jsx index 50c85ec1eb..afd3feb7d0 100644 --- a/client/modules/IDE/components/SketchList.test.jsx +++ b/client/modules/IDE/components/SketchList.test.jsx @@ -1,74 +1,39 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; +import axios from 'axios'; import { unmountComponentAtNode } from 'react-dom'; import { act } from 'react-dom/test-utils'; -import moxios from 'moxios'; -import * as ProjectActions from '../actions/projects'; -import * as ActionTypes from '../../../constants'; import SketchList from './SketchList'; import { reduxRender, fireEvent, screen } from '../../../test-utils'; +import { + initialTestState, + mockProjects +} from '../../../redux_test_stores/test_store'; -const mockStore = configureStore([thunk]); +jest.mock('../../../i18n'); -const mockProjects = [ - { - name: 'testsketch1', - _id: 'testid1', - updatedAt: '2021-02-26T04:58:29.390Z', - files: [], - createdAt: '2021-02-26T04:58:14.136Z', - id: 'testid1' - }, - { - name: 'testsketch2', - _id: 'testid2', - updatedAt: '2021-02-23T17:40:43.789Z', - files: [], - createdAt: '2021-02-23T17:40:43.789Z', - id: 'testid2' - } -]; - -const initialTestState = { - ide: null, - files: [], - preferences: {}, - user: { - email: 'happydog@example.com', - username: 'happydog', - preferences: {}, - apiKeys: [], - verified: 'sent', - id: '123456789', - totalSize: 0, - authenticated: true - }, - project: null, - sketches: mockProjects, - search: { - collectionSearchTerm: '', - sketchSearchTerm: '' - }, - sorting: { - field: 'createdAt', - direction: 'DESCENDING' - }, - editorAccessibility: {}, - toast: {}, - console: [], - assets: {}, - loading: false, - collections: [] -}; +/* + * there seem to be polarizing opinions about whether or not + * we should test the unconnected component or the + * connected one. For the sake of not editing the original SketchList file + * with an unneccessary export statement, I'm testing + * the connected component with redux-mock-store. + * this approach is outlined here - + * https://www.robinwieruch.de/react-connected-component-test + */ describe('', () => { - let container = null; let store; + let container; + const mockStore = configureStore([thunk]); + beforeEach(() => { // setup a DOM element as a render target container = document.createElement('div'); document.body.appendChild(container); + axios.get.mockImplementationOnce((x) => Promise.resolve({ data: 'foo' })); + store = mockStore(initialTestState); }); afterEach(() => { @@ -79,66 +44,25 @@ describe('', () => { store.clearActions(); }); - // it('creates GET_PROJECTS after successfuly fetching projects', async () => { - // moxios.install(); - // function resolveAfter2Seconds() { - // console.log("called resolve"); - // return new Promise(resolve => { - // setTimeout(() => { - // resolve('resolved'); - // }, 5000); - // }); - // } - // console.log("moxios", moxios); - // moxios.wait(() => { - - // const request = moxios.requests.mostRecent(); - // console.log(moxios.requests, request) - // console.log("recieved request for get projects") - // request.respondWith({ - // status: 200, - // response: mockProjects, - // }).then(() => done()); - // }); - - // const expectedActions = [ - // {type: ActionTypes.START_LOADING}, - // { type: ActionTypes.SET_PROJECTS, - // projects: mockProjects } - // ]; - - // store = mockStore(initialTestState); - // console.log("dispatching action"); - // //store.dispatch(ProjectActions.getProjects("happydog")) - // act(() => { - // reduxRender(, { store, container }); - // }); - - // const hasSetProjectKey = (currActions) => { - // return currActions.filter(ac => ac.type === ActionTypes.SET_PROJECTS).length > 0; - // } - - // await waitFor(() => expect(hasSetProjectKey(store.getActions())).toEqual(true)); - // moxios.uninstall(); - // //expect(store.getActions()).toEqual(expectedActions); - // //return resolveAfter2Seconds().then(res => console.log("resolved")) - // }); - - it('has both of the sample projects', () => { - store = mockStore(initialTestState); + it('has sample projects', () => { let component; act(() => { - component = reduxRender(, { store, container }); + component = reduxRender(, { + store, + container + }); }); expect(screen.getByText('testsketch1')).toBeInTheDocument(); expect(screen.getByText('testsketch2')).toBeInTheDocument(); }); it('clicking on date created row header dispatches a reordering action', () => { - store = mockStore(initialTestState); let component; act(() => { - component = reduxRender(, { store, container }); + component = reduxRender(, { + store, + container + }); }); act(() => { fireEvent.click(screen.getByTestId('toggle-direction-createdAt')); @@ -146,4 +70,62 @@ describe('', () => { const expectedAction = [{ type: 'TOGGLE_DIRECTION', field: 'createdAt' }]; expect(store.getActions()).toEqual(expect.arrayContaining(expectedAction)); }); + + it('clicking on dropdown arrow opens sketch options', () => { + let component; + act(() => { + component = reduxRender(, { + store, + container + }); + }); + const dropdown = screen.queryAllByTestId( + 'sketch-list-toggle-options-arrow' + ); + + if (dropdown.length > 0) { + act(() => { + fireEvent.click(dropdown[0]); + }); + + expect(screen.queryByText('Rename')).not.toBeInTheDocument(); + expect(screen.queryByText('Duplicate')).toBeInTheDocument(); + expect(screen.queryByText('Download')).toBeInTheDocument(); + expect(screen.queryByText('Add to collection')).toBeInTheDocument(); + expect(screen.queryByText('Delete')).not.toBeInTheDocument(); + } + }); + + it('clicking on dropdown arrow opens sketch options - sketches belong to user', () => { + let component; + act(() => { + component = reduxRender(, { + store, + container + }); + }); + const dropdown = screen.queryAllByTestId( + 'sketch-list-toggle-options-arrow' + ); + + if (dropdown.length > 0) { + act(() => { + fireEvent.click(dropdown[0]); + }); + + expect(screen.queryByText('Rename')).toBeInTheDocument(); + expect(screen.queryByText('Duplicate')).toBeInTheDocument(); + expect(screen.queryByText('Download')).toBeInTheDocument(); + expect(screen.queryByText('Add to collection')).toBeInTheDocument(); + expect(screen.queryByText('Delete')).toBeInTheDocument(); + } + }); + + it('snapshot testing', () => { + const { asFragment } = reduxRender(, { + store, + container + }); + expect(asFragment()).toMatchSnapshot(); + }); }); diff --git a/client/modules/IDE/components/__snapshots__/SketchList.test.jsx.snap b/client/modules/IDE/components/__snapshots__/SketchList.test.jsx.snap new file mode 100644 index 0000000000..81d6a0ec54 --- /dev/null +++ b/client/modules/IDE/components/__snapshots__/SketchList.test.jsx.snap @@ -0,0 +1,136 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` snapshot testing 1`] = ` + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + testsketch1 + + + Feb 25, 2021, 11:58:14 PM + + Feb 25, 2021, 11:58:29 PM + + +
+ + testsketch2 + + + Feb 23, 2021, 12:40:43 PM + + Feb 23, 2021, 12:40:43 PM + + +
+
+
+`; diff --git a/client/redux_test_stores/test_store.js b/client/redux_test_stores/test_store.js new file mode 100644 index 0000000000..46a6d2856d --- /dev/null +++ b/client/redux_test_stores/test_store.js @@ -0,0 +1,52 @@ +const mockProjects = [ + { + name: 'testsketch1', + _id: 'testid1', + updatedAt: '2021-02-26T04:58:29.390Z', + files: [], + createdAt: '2021-02-26T04:58:14.136Z', + id: 'testid1' + }, + { + name: 'testsketch2', + _id: 'testid2', + updatedAt: '2021-02-23T17:40:43.789Z', + files: [], + createdAt: '2021-02-23T17:40:43.789Z', + id: 'testid2' + } +]; + +const initialTestState = { + ide: null, + files: [], + preferences: {}, + user: { + email: 'happydog@example.com', + username: 'happydog', + preferences: {}, + apiKeys: [], + verified: 'sent', + id: '123456789', + totalSize: 0, + authenticated: true + }, + project: null, + sketches: mockProjects, + search: { + collectionSearchTerm: '', + sketchSearchTerm: '' + }, + sorting: { + field: 'createdAt', + direction: 'DESCENDING' + }, + editorAccessibility: {}, + toast: {}, + console: [], + assets: {}, + loading: false, + collections: [] +}; + +export { mockProjects, initialTestState }; diff --git a/developer_docs/testing.md b/developer_docs/testing.md new file mode 100644 index 0000000000..59866784e0 --- /dev/null +++ b/developer_docs/testing.md @@ -0,0 +1,301 @@ +# Testing +For an initial basic overview of testing for React apps, [you can read what the React developers have to say about it](https://reactjs.org/docs/testing.html). + +We are testing React components by rendering the component trees in a simplified test environment and making assertions on what gets rendered and what functions get called. + +Many files still don't have tests, so if you're looking to get started as a contributor, this would be a great place to start! + +## What's in this document +- [Testing dependencies](#testing-dependencies) +- [Useful testing commands](#Useful-testing-commands) +- [When to run tests](#When-to-run-tests) +- [Why write tests](#Why-write-tests) +- [Writing a test](#Writing-a-test) +- [Files to be aware of](#Files-to-be-aware-of) +- [Testing plain components](#Testing-plain-components) +- [Testing Redux](#Testing-Redux) +- [How to handle API calls in tests](#How-to-handle-API-calls-in-tests) +- [Some more background on tests](#Some-more-background-on-tests) +- [Internationalization](#internationalization) +- [Tips](#Tips) +- [More resources](#More-resources) +- [References](#References) + + +## Testing dependencies +1. [Jest](https://jestjs.io/) +2. [react-testing-library](https://testing-library.com/docs/react-testing-library/intro/) +3. [redux-mock-store](https://github.com/reduxjs/redux-mock-store) + +## Useful testing commands +Run the whole test suite +``` +npm run test +``` + +----- + +[Run tests that match the pattern](https://stackoverflow.com/questions/28725955/how-do-i-test-a-single-file-using-jest/28775887). Useful if you're writing one specific test and want to only run that one. +``` +npm run test -- someFileName +``` + +----- +Run the tests but update the snapshot if they don't match. + +``` +npm run test -- -u +``` + +---- +For example, if you wanted to run just the SketchList test but also update the snapshot, you could do: +``` +npm run test -- Sketchlist.test.js -u +``` + +Find more commands in the [Jest documentation](https://jestjs.io/docs/cli). + +## When to run tests + +Are they actually being run automaticlly??? + +## Why write tests +- Good place to start if you're learning the codebase because it's harder to mess up production code +- Benefits all future contributors by allowing them to check their changes for errors +- Catches easy-to-miss errors +- Lets you check your own work and feel more comfortable sumbitting PRs +- Good practice for large projects +- Many of the existing components don't have tests yet, and you could write one :-) + +## Writing a test +Want to get started writing a test for a new file or an existing file, but not sure how? +### For React components + +1. Make a new file in the ``__tests__`` folder that's directly adjacent to your file. For example, if ``example.jsx`` is in ``src/components``, then you would make a file called ``example.test.jsx`` in ``src/components/__tests__`` +2. Check if the component is connected to redux or not. +3. If it is, see the redux section below on how to write tests for that. +4. If it's not, see the section below on writing tests for unconnected components. + +### For Redux action creators or reducers +See the redux section below :) + +### For server side code +lol no clue how to do this yet + +## Files to be aware of + +### Proposed folder structure 1 +All tests in ``__tests__`` folders that are directly adjacent to the files that they are testing. For example, if you're testing ``examplefolder/Sketchlist.test.jsx``, the test would be in ``examplefolder/__tests__/Sketchlist.test.jsx``. This is so that the tests are close to the files they're testing but are still hidden away from view most of the time. This also means that any snapshot files will be stored in the testing folder, such as ``examplefolder/__tests__/__snapshots__/Sketchlist.test.jsx.snap`` + +Manual mocks are in ``__mocks__`` folders are adjacent to the modules that they're mocking. + +Note: Even if you mock a user module in a ``__mocks__`` folder, user modules have to be explictly mocked in the test too, with ``Jest.mock("path_to_module")`` + +Node modules are mocked in the ``__mocks__`` folder at the root of the client folder, which also includes any mocks that are needed for user modules at the root of the folder directory. + +``` +. +└── client + ├── __mocks__ + │ ├── axios.js + | ├── i18n.js + | └── ...other Node modules you want to mock + ├── modules + │ ├── IDE + │ │ ├── actions + │ │ │ ├── __mocks__ + │ │ │ │ ├── projects.js + │ │ │ │ └─ ... other action creator mocks + │ │ │ ├── __tests__ + │ │ │ │ ├── projects.test.js + │ │ │ │ └─ ... other redux action creator tests + │ │ │ └── ... action creators + │ │ ├── components + │ │ │ ├── __tests__ + │ │ │ │ ├── __snapshots__ + │ │ │ │ │ ├── SketchList.test.jsx.snap + │ │ │ │ │ └─ ... other snapshots + │ │ │ │ ├── SketchList.test.jsx + │ │ │ ├── SketchList.test.jsx + │ │ │ └── ... and more component files + │ │ ├── reducers + │ │ │ ├── __tests__ + │ │ │ │ ├── assets.test.js + │ │ │ │ └─ ... other reducer tests + │ │ │ ├── assets.js + │ │ │ └── ...more reducers + │ └── ... more folders + ├── redux_test_stores + | ├── test_store.js + │ └── ...any other redux states you want to test + ├── i18n-test.js + ├── jest.setup.js + ├── test-utils.js + └──... other files and folders +``` + +### Proposed folder structure 2 +All tests are directly adjacent to the files that they are testing. For example, if you're testing ``examplefolder/Sketchlist.test.jsx``, the test would be in ``examplefolder/Sketchlist.test.jsx``. This is so that the tests are as close as possible to the files. This also means that any snapshot files will be stored in the same folder, such as ``examplefolder/__snapshots__/Sketchlist.test.jsx.snap`` + +Manual mocks are in ``__mocks__`` folders are adjacent to the modules that they're mocking. + +Note: Even if you mock a user module in a ``__mocks__`` folder, user modules have to be explictly mocked in the test too, with ``Jest.mock("path_to_module")`` + +Node modules are mocked in the ``__mocks__`` folder at the root of the client folder, which also includes any mocks that are needed for user modules at the root of the folder directory. + +``` +. +└── client + ├── __mocks__ + │ ├── axios.js + | ├── i18n.js + | └── ...other Node modules you want to mock + ├── modules + │ ├── IDE + │ │ ├── actions + │ │ │ ├── __mocks__ + │ │ │ │ ├── projects.js + │ │ │ │ └─ ... other action creator mocks + │ │ │ ├── projects.js + │ │ │ ├── projects.test.js + │ │ │ └─ ... other action creator files + │ │ ├── components + │ │ │ ├── __snapshots__ + │ │ │ │ ├── SketchList.test.jsx.snap + │ │ │ │ └─ ... other snapshots + │ │ │ ├── SketchList.jsx + │ │ │ ├── SketchList.test.jsx + │ │ │ └── ... and more component files + │ │ ├── reducers + │ │ │ ├── assets.test.js + │ │ │ ├── assets.js + │ │ │ └── ...more reducers + │ └── ... more folders + ├── redux_test_stores + | ├── test_store.js + │ └── ...any other redux states you want to test + ├── i18n-test.js + ├── jest.setup.js + ├── test-utils.js + └──... other files and folders +``` + +### test-utils.js +This file overwrites the default react-testing-library's render function so that components rendered through the new render function have access i18next and redux. It exports the rest of react-testing-library as is. + +It exports a render function with a i18n wrapper as ``render`` and a render function with a wrapper for both redux and i18n as ``reduxRender`` + +Thus, in your component test files, instead of calling ``import {functions you want} from 'react-testing-libary'`` importing react-testing library might look something like this: + +If your component only needs i18n and not redux: +``` +import { render, fireEvent, screen, waitFor } from '../../../test-utils'; +``` +If your component needs i18n and redux: +``` +import { reduxRender, fireEvent, screen, waitFor } from '../../../test-utils'; +``` + +Redux and i18next are made accessible by placing wrappers around the component. We can do this by replacing the render function with one that renders the requested component WITH an additional wrapper added around it. + +For example, the exported render function that adds a wrapper for both redux and i18n looks roughly like this: + +``` +function reduxRender( + ui, + { + initialState, + store = createStore(rootReducer, initialState), + ...renderOptions + } = {} +) { + function Wrapper({ children }) { + return ( + + + {children} + + + ); + } + + return render(ui, { wrapper: Wrapper, ...renderOptions }); +} +``` + + + +### redux_test_stores +This folder contains the inital redux states that you can provide to the ``reduxRender`` function when testing. For example, if you want to render the SketchList component with a username of ``happydog`` and some sample sketches, ``redux_test_stores\test_store.js`` contains a definition for that state that you can import and provide to the renderer. + +### jest configs in package.json + +this i dont know much about yet but want to understand + +## Testing plain components +If it doesn't export connect()__stuffhere_ or use redux hooks like adfasdf, then testing your component will be simpler and will look like this: + + +## Testing Redux + +split up testing between: +1. action creators +2. reducers +3. connected components + + +4. unconnected components + +### action creators +write example code here +- can show cassie projects.test.js because that one is working :) + +### reducers +write example code here +### connected components +3 approaches im trying for sketchlist +- mock all of axios, let it run the action creators as usual, redux-mock-store and then render component +- export unconnected component and use that + - this method didn't work because the subcomponent that was also redux connected failed. I could use shallow rendering but that's not supported in react-testing-library (I think) +- mock getProjects itself so it never calls apiClient at all + +each has its own errors :/ i realized that the third approach is flawed because a lot of the functions rely on apiClient. Also, apiClient calls axios.create before any of the tests even run at all. overall, only mocking getProjects is a fragile solution + +## How to handle API calls in tests + +doesnt seem to like it when you make calls in a test +so we mock axios +also a little trickery in i18n .use(Backend) +- editor uses axios, we mock the whole library and jest automatically does this since we have a axios.js file in the __mocks__ folder at the root of the client folder. +- the benefit of this is that you can control exactly what happens when any axios function gets called, and you can check how many times it's been called. +- [see this for more](https://stackoverflow.com/questions/51393952/mock-inner-axios-create/51414152#51414152) +- [and this too](https://medium.com/asos-techblog/how-to-test-your-react-redux-application-48d90481a253) + +## Some more background on tests + +### test driven development (TDD) + +### snapshot testing +want to make an example + +### integration tests + +### unit tests + +### mocking functions +Sometimes you might want to mock a function + +## Internationalization + +## Tips +1. Make test fail at least once to make sure it was a meaningful test +2. "If you or another developer change the component in a way that it changes its behaviour at least one test should fail." - [How to Unit Test in React](https://itnext.io/how-to-unit-test-in-react-72e911e2b8d) + +## More Resources +- stuff +- stuff + +## References +[1] stuff here + +[2] stuff here \ No newline at end of file From 598095f8521203ca63105d45998e008888b987d9 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Tue, 16 Mar 2021 19:13:17 -0400 Subject: [PATCH 05/37] update documentation, rename projects.test file, fix small bug in nav test --- client/components/__test__/Nav.test.jsx | 2 + .../{projects.test.jsx => projects.test.js} | 3 - .../IDE/components/SketchList.test.jsx | 3 +- developer_docs/testing.md | 259 +++++++++++++++--- 4 files changed, 223 insertions(+), 44 deletions(-) rename client/modules/IDE/actions/__tests__/{projects.test.jsx => projects.test.js} (91%) diff --git a/client/components/__test__/Nav.test.jsx b/client/components/__test__/Nav.test.jsx index dfb122fbc9..c699f2e4a9 100644 --- a/client/components/__test__/Nav.test.jsx +++ b/client/components/__test__/Nav.test.jsx @@ -3,6 +3,8 @@ import { render } from '@testing-library/react'; import { NavComponent } from '../Nav'; +jest.mock('../../i18n'); + describe('Nav', () => { const props = { newProject: jest.fn(), diff --git a/client/modules/IDE/actions/__tests__/projects.test.jsx b/client/modules/IDE/actions/__tests__/projects.test.js similarity index 91% rename from client/modules/IDE/actions/__tests__/projects.test.jsx rename to client/modules/IDE/actions/__tests__/projects.test.js index 1f8b720885..1f29d92b56 100644 --- a/client/modules/IDE/actions/__tests__/projects.test.jsx +++ b/client/modules/IDE/actions/__tests__/projects.test.js @@ -10,9 +10,6 @@ import { mockProjects } from '../../../../redux_test_stores/test_store'; -// look into this -// https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach - const mockStore = configureStore([thunk]); describe('projects action creator tests', () => { diff --git a/client/modules/IDE/components/SketchList.test.jsx b/client/modules/IDE/components/SketchList.test.jsx index afd3feb7d0..091f7e1fcd 100644 --- a/client/modules/IDE/components/SketchList.test.jsx +++ b/client/modules/IDE/components/SketchList.test.jsx @@ -24,16 +24,15 @@ jest.mock('../../../i18n'); */ describe('', () => { - let store; let container; const mockStore = configureStore([thunk]); + const store = mockStore(initialTestState); beforeEach(() => { // setup a DOM element as a render target container = document.createElement('div'); document.body.appendChild(container); axios.get.mockImplementationOnce((x) => Promise.resolve({ data: 'foo' })); - store = mockStore(initialTestState); }); afterEach(() => { diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 59866784e0..d09f27820d 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -8,8 +8,8 @@ Many files still don't have tests, so if you're looking to get started as a cont ## What's in this document - [Testing dependencies](#testing-dependencies) - [Useful testing commands](#Useful-testing-commands) -- [When to run tests](#When-to-run-tests) - [Why write tests](#Why-write-tests) +- [When to run tests](#When-to-run-tests) - [Writing a test](#Writing-a-test) - [Files to be aware of](#Files-to-be-aware-of) - [Testing plain components](#Testing-plain-components) @@ -55,10 +55,6 @@ npm run test -- Sketchlist.test.js -u Find more commands in the [Jest documentation](https://jestjs.io/docs/cli). -## When to run tests - -Are they actually being run automaticlly??? - ## Why write tests - Good place to start if you're learning the codebase because it's harder to mess up production code - Benefits all future contributors by allowing them to check their changes for errors @@ -67,20 +63,39 @@ Are they actually being run automaticlly??? - Good practice for large projects - Many of the existing components don't have tests yet, and you could write one :-) +## When to run tests + +When you make a git commit, the tests will be run automatically for you. + +When you modify an existing component, it's a good idea to run the test suite to make sure it didn't make any changes that break the rest of the application. If they did break some tests, you would either have to fix a bug component or update the tests to match the new expected functionality. + ## Writing a test Want to get started writing a test for a new file or an existing file, but not sure how? ### For React components - +(the below assumes we're using proposed folder structure 1) 1. Make a new file in the ``__tests__`` folder that's directly adjacent to your file. For example, if ``example.jsx`` is in ``src/components``, then you would make a file called ``example.test.jsx`` in ``src/components/__tests__`` 2. Check if the component is connected to redux or not. 3. If it is, see the redux section below on how to write tests for that. 4. If it's not, see the section below on writing tests for unconnected components. ### For Redux action creators or reducers -See the redux section below :) +See the [redux section](#Testing-Redux) below :) + +### Troubleshooting +1. Check if the component makes any API calls. If it's using axios, jest should already be set up to replace the axios library with a mocked version; however, you may want to [mock](https://jestjs.io/docs/mock-function-api#mockfnmockimplementationoncefn) the axios.get() function with your own version so that GET calls "return" whatever data makes sense for that test. + + ``` + axios.get.mockImplementationOnce( + (x) => Promise.resolve({ data: 'foo' }) + ); + ``` +You can see it used in the context of a test [here](../client/modules/IDE/components/SketchList.test.jsx). -### For server side code -lol no clue how to do this yet +2. If the component makes use of the formatDate util, some of the functions in that rely on the ``./client/i18n.js`` file that also makes an ajax request, which sometimes leads to an ERRCONNECTED error on the console, even though your tests pass. You can fix it by adding a mock for that specific i18n file: + ``` + jest.mock('_path_to_file_/i18n'); + ``` +You can see it used in the context of a test [here](../client/modules/IDE/components/SketchList.test.jsx). ## Files to be aware of @@ -224,7 +239,6 @@ function reduxRender( ``` - ### redux_test_stores This folder contains the inital redux states that you can provide to the ``reduxRender`` function when testing. For example, if you want to render the SketchList component with a username of ``happydog`` and some sample sketches, ``redux_test_stores\test_store.js`` contains a definition for that state that you can import and provide to the renderer. @@ -233,59 +247,226 @@ This folder contains the inital redux states that you can provide to the ``redux this i dont know much about yet but want to understand ## Testing plain components -If it doesn't export connect()__stuffhere_ or use redux hooks like adfasdf, then testing your component will be simpler and will look like this: +If it doesn't export connect()___ or use redux hooks like ___, then testing your component will be simpler and might look something like this: + +``` +import React from 'react'; +import { unmountComponentAtNode } from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import { fireEvent, render, screen } from '../../../../test-utils'; +import FakePreferences from './index'; + +/* a helper function to render the components with the + * props that that component needs to be passed in + * if you want to access the rendered component itself + * you'd have to modify it a little to return the what + * gets returned from the render function, along with the props, which is what it's returning now. + * the default props in this can be overwritten by using extraProps + */ +const renderComponent = (extraProps = {}, container) => { + // if we want to overwrite any of these props, we can do it with extraProps because later keys overwrite earlier ones in the spread operator + const props = { + t: jest.fn(), + fontSize: 12, + autosave: false, + setFontSize: jest.fn(), + setAutosave: jest.fn(), + ...extraProps + }; + render(, { container }); + + return props; +}; + +describe('', () => { + let container = null; + beforeEach(() => { + // setup a DOM element as a render target + container = document.createElement('div'); + container.classList.add('testing-container'); + document.body.appendChild(container); + }); + + afterEach(() => { + // cleanup on exiting + unmountComponentAtNode(container); + container.remove(); + container = null; + }); + + describe('font tests', () => { + it('font size increase button says increase', () => { + let props; + // render the component + act(() => { + props = renderComponent({fontSize: 15}, container); + }); + + //I do tests here. + //you can access mock functions from props + //for example, props.setFontSize + + }); + }); +``` +Consider what you want to test. Some possible things might be: +- User input results in the expected function being called with the expected argument. + ``` + act(() => { + fireEvent.click(screen.getByTestId("testid")); + }); + expect(yourMockFunction).toHaveBeenCalledTimes(1); + expect(yourMockFunction.mock.calls[0][0]).toBe(argument); + ``` +- User input results in the class's method being called. + ``` + //component is the return value of calling render() + const spy1 = jest.spyOn(component.instance(), 'func1'); + act(() => { + fireEvent.click(screen.getByTestId("testid")); + }); + expect(spy1).toHaveBeenCalledTimes(1); + ``` +- The text or divs that you expect to be on the page are actually there. +- a previously saved snapshot of the HTML matches a snapshot taken during testing. +- what else???? help! ## Testing Redux -split up testing between: +When testing redux, the general guidance [1] seems to suggest splitting up testing between: 1. action creators 2. reducers 3. connected components +Testing reducers and action creators is covered pretty well in [Redux's documentation](https://redux.js.org/recipes/writing-tests). An example of testing an action creator can be found at [projects.test.js](../client/modules/IDE/components/actions/__tests__/projects.test.jsx) -4. unconnected components +### Connected Components -### action creators -write example code here -- can show cassie projects.test.js because that one is working :) +Although it's possible to export the components as unconnected components for testing (and in this case you would just manually pass in the props that redux provides), the codebase is being migrated to use hooks, and in this case, that approach no longer works. It also doesn't work if we render components that have connected subcomponents. Thus, for consistency, we suggest testing all redux components while they're connected to redux. We can do this with redux-mock-store. -### reducers -write example code here -### connected components -3 approaches im trying for sketchlist -- mock all of axios, let it run the action creators as usual, redux-mock-store and then render component -- export unconnected component and use that - - this method didn't work because the subcomponent that was also redux connected failed. I could use shallow rendering but that's not supported in react-testing-library (I think) -- mock getProjects itself so it never calls apiClient at all +This works like so: +1. Import the reduxRender function from ``client/test_utils.js`` +2. Configure the mock store. +``` +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; -each has its own errors :/ i realized that the third approach is flawed because a lot of the functions rely on apiClient. Also, apiClient calls axios.create before any of the tests even run at all. overall, only mocking getProjects is a fragile solution + +const mockStore = configureStore([thunk]); +``` +3. Create a mock store. There's an initial state that you can import from ``client/redux_test_stores/test_store.js`` +``` +store = mockStore(initialTestState); +``` +3. Render the component with reduxRender and the store that you just created. +``` +reduxRender(, {store, container}); +``` +4. Test things! You may need to use jest to mock certain functions if the component is making API calls. + +All together, it might look something like this. + +``` +import React from 'react'; +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { unmountComponentAtNode } from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import MyComponent from './MyComponent'; +import { reduxRender, fireEvent, screen } from '../../../test-utils'; +import { initialTestState } from '../../../redux_test_stores/test_store'; + +describe(, () => { + let store; + let container; + const mockStore = configureStore([thunk]); + store = mockStore(initialTestState); + + beforeEach(() => { + // setup a DOM element as a render target + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + // cleanup on exiting + unmountComponentAtNode(container); + container.remove(); + container = null; + store.clearActions(); + }); + + it('stuff about the test', () => { + let component; + act(() => { + component = reduxRender(, { + store, + container + }); + }); + + //your tests go here + }); + +}) +``` + +Some things to consider testing: +- User input results in the expected redux action. + ``` + act(() => { + component = reduxRender(, { + store, + container + }); + }); + act(() => { + fireEvent.click(screen.getByTestId('toggle-direction-createdAt')); + }); + const expectedAction = [{ type: 'TOGGLE_DIRECTION', field: 'createdAt' }]; + + expect(store.getActions()).toEqual(expect.arrayContaining(expectedAction)); + ``` +- User input results in the class's method being called. + ``` + //component is the return value of calling render() + const spy1 = jest.spyOn(component.instance(), 'func1'); + act(() => { + fireEvent.click(screen.getByTestId("testid")); + }); + expect(spy1).toHaveBeenCalledTimes(1); + ``` +- The text or divs that you expect to be on the page are actually there. +- a previously saved snapshot of the HTML matches a snapshot taken during testing. +- what else???? help! ## How to handle API calls in tests -doesnt seem to like it when you make calls in a test -so we mock axios -also a little trickery in i18n .use(Backend) -- editor uses axios, we mock the whole library and jest automatically does this since we have a axios.js file in the __mocks__ folder at the root of the client folder. -- the benefit of this is that you can control exactly what happens when any axios function gets called, and you can check how many times it's been called. -- [see this for more](https://stackoverflow.com/questions/51393952/mock-inner-axios-create/51414152#51414152) -- [and this too](https://medium.com/asos-techblog/how-to-test-your-react-redux-application-48d90481a253) +Some tests throw errors if a part of the client-side code tries to make an API call or AJAX request. Our solution to this is to use jest to replace those functions with [mock functions](https://jestjs.io/docs/mock-functions). + +The code in question for the client side is mostly related to the axios library. We mock the whole library - jest automatically does this since we have an ``axios.js`` file in the ``__mocks__`` folder at the root of the client folder. [1][2] + +The benefit of this is that you can control exactly what happens when any axios function gets called, and you can check how many times it's been called. + +A few components also import ``./client/i18n.js`` (or ``./client/utils/formatDate``, which imports the first file), in which the ``i18n.use(Backend)`` line can sometimes throw a sneaky ERRCONNECTED error. You can resolve this by mocking that file as described in [this section](#Troubleshooting). ## Some more background on tests ### test driven development (TDD) +BDD??? ### snapshot testing -want to make an example +You can save a snapshot of what the HTML looks like when the component is rendered. ### integration tests +Testing multiple components together. A small example is rendering a parent component and a child component within that. ### unit tests - -### mocking functions -Sometimes you might want to mock a function +Most of our tests are of this type. In this, you're testing a the functionality of a single component and no more. ## Internationalization +Project uses i18n. ## Tips 1. Make test fail at least once to make sure it was a meaningful test @@ -296,6 +477,6 @@ Sometimes you might want to mock a function - stuff ## References -[1] stuff here - -[2] stuff here \ No newline at end of file +-https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach +- [More info on this method](https://stackoverflow.com/questions/51393952/mock-inner-axios-create/51414152#51414152) +- [and this too](https://medium.com/asos-techblog/how-to-test-your-react-redux-application-48d90481a253) \ No newline at end of file From 47598237023cea1e4c9c911eadf82439757e0580 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Tue, 16 Mar 2021 19:26:32 -0400 Subject: [PATCH 06/37] Update testing.md --- developer_docs/testing.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index d09f27820d..d4130e8f51 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -334,7 +334,7 @@ Consider what you want to test. Some possible things might be: ## Testing Redux -When testing redux, the general guidance [1] seems to suggest splitting up testing between: +When testing redux, the general guidance [[1]](#References) seems to suggest splitting up testing between: 1. action creators 2. reducers 3. connected components @@ -378,10 +378,9 @@ import { reduxRender, fireEvent, screen } from '../../../test-utils'; import { initialTestState } from '../../../redux_test_stores/test_store'; describe(, () => { - let store; let container; const mockStore = configureStore([thunk]); - store = mockStore(initialTestState); + const store = mockStore(initialTestState); beforeEach(() => { // setup a DOM element as a render target @@ -445,7 +444,7 @@ Some things to consider testing: Some tests throw errors if a part of the client-side code tries to make an API call or AJAX request. Our solution to this is to use jest to replace those functions with [mock functions](https://jestjs.io/docs/mock-functions). -The code in question for the client side is mostly related to the axios library. We mock the whole library - jest automatically does this since we have an ``axios.js`` file in the ``__mocks__`` folder at the root of the client folder. [1][2] +The code in question for the client side is mostly related to the axios library. We mock the whole library - jest automatically does this since we have an ``axios.js`` file in the ``__mocks__`` folder at the root of the client folder. [[2]](#References) The benefit of this is that you can control exactly what happens when any axios function gets called, and you can check how many times it's been called. @@ -466,7 +465,7 @@ Testing multiple components together. A small example is rendering a parent comp Most of our tests are of this type. In this, you're testing a the functionality of a single component and no more. ## Internationalization -Project uses i18n. +This project uses i18next for internationalization. If you import the render function with the i18n wrapper from ``test_utils.js``, it's set up to use English, so the components with be rendered with English text and you should be able to count on this to test for specific strings. ## Tips 1. Make test fail at least once to make sure it was a meaningful test @@ -477,6 +476,6 @@ Project uses i18n. - stuff ## References --https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach -- [More info on this method](https://stackoverflow.com/questions/51393952/mock-inner-axios-create/51414152#51414152) -- [and this too](https://medium.com/asos-techblog/how-to-test-your-react-redux-application-48d90481a253) \ No newline at end of file +1. [Best practices for unit testing with a react redux approach](https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach) + +2. [How to test your react-redux application (this article also references axios)](https://medium.com/asos-techblog/how-to-test-your-react-redux-application-48d90481a253) \ No newline at end of file From 7bfebf7de14a6060332655a7ea9b33aa587152b2 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Tue, 16 Mar 2021 19:27:01 -0400 Subject: [PATCH 07/37] Update testing.md --- developer_docs/testing.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index d4130e8f51..eecb1c9283 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -472,8 +472,7 @@ This project uses i18next for internationalization. If you import the render fun 2. "If you or another developer change the component in a way that it changes its behaviour at least one test should fail." - [How to Unit Test in React](https://itnext.io/how-to-unit-test-in-react-72e911e2b8d) ## More Resources -- stuff -- stuff +- any other resources for learning about tests here? ## References 1. [Best practices for unit testing with a react redux approach](https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach) From 57d0fcf3743e374d45f745b64c450c0b4eb9c30b Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Tue, 16 Mar 2021 19:31:42 -0400 Subject: [PATCH 08/37] Update testing.md --- developer_docs/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index eecb1c9283..ca86ca5f09 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -247,7 +247,7 @@ This folder contains the inital redux states that you can provide to the ``redux this i dont know much about yet but want to understand ## Testing plain components -If it doesn't export connect()___ or use redux hooks like ___, then testing your component will be simpler and might look something like this: +If it doesn't contain ``connect(mapStateToProps, mapDispatchToProps)(ComponentName)`` or use hooks like ``useSelector``, then your component is not connected to Redux and testing your component will be simpler and might look something like this: ``` import React from 'react'; From fe2d725e16c23612fcd91d32ca8e65d705c3d261 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Tue, 16 Mar 2021 19:36:47 -0400 Subject: [PATCH 09/37] moved around the "what to test" suggestions --- developer_docs/testing.md | 41 +++++++++++++++------------------------ 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index ca86ca5f09..70d0c295b4 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -78,6 +78,20 @@ Want to get started writing a test for a new file or an existing file, but not s 3. If it is, see the redux section below on how to write tests for that. 4. If it's not, see the section below on writing tests for unconnected components. +For any type of component, you might want to consider testing: +- User input results in the class's method being called. + ``` + //component is the return value of calling render() + const spy1 = jest.spyOn(component.instance(), 'func1'); + act(() => { + fireEvent.click(screen.getByTestId("testid")); + }); + expect(spy1).toHaveBeenCalledTimes(1); + ``` +- The text or divs that you expect to be on the page are actually there. +- a previously saved snapshot of the HTML matches a snapshot taken during testing. +- what else?? help! + ### For Redux action creators or reducers See the [redux section](#Testing-Redux) below :) @@ -244,10 +258,10 @@ This folder contains the inital redux states that you can provide to the ``redux ### jest configs in package.json -this i dont know much about yet but want to understand +in progress ## Testing plain components -If it doesn't contain ``connect(mapStateToProps, mapDispatchToProps)(ComponentName)`` or use hooks like ``useSelector``, then your component is not connected to Redux and testing your component will be simpler and might look something like this: +If it doesn't contain ``connect(mapStateToProps, mapDispatchToProps)(ComponentName)`` or use hooks like ``useSelector``, then your component is not directly using Redux and testing your component will be simpler and might look something like this: ``` import React from 'react'; @@ -319,17 +333,6 @@ Consider what you want to test. Some possible things might be: expect(yourMockFunction).toHaveBeenCalledTimes(1); expect(yourMockFunction.mock.calls[0][0]).toBe(argument); ``` -- User input results in the class's method being called. - ``` - //component is the return value of calling render() - const spy1 = jest.spyOn(component.instance(), 'func1'); - act(() => { - fireEvent.click(screen.getByTestId("testid")); - }); - expect(spy1).toHaveBeenCalledTimes(1); - ``` -- The text or divs that you expect to be on the page are actually there. -- a previously saved snapshot of the HTML matches a snapshot taken during testing. - what else???? help! ## Testing Redux @@ -427,18 +430,6 @@ Some things to consider testing: expect(store.getActions()).toEqual(expect.arrayContaining(expectedAction)); ``` -- User input results in the class's method being called. - ``` - //component is the return value of calling render() - const spy1 = jest.spyOn(component.instance(), 'func1'); - act(() => { - fireEvent.click(screen.getByTestId("testid")); - }); - expect(spy1).toHaveBeenCalledTimes(1); - ``` -- The text or divs that you expect to be on the page are actually there. -- a previously saved snapshot of the HTML matches a snapshot taken during testing. -- what else???? help! ## How to handle API calls in tests From f57e7c1198246b9e5686e634dbf3b1f3b2ea96c1 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Tue, 16 Mar 2021 19:38:08 -0400 Subject: [PATCH 10/37] added header --- developer_docs/testing.md | 1 + 1 file changed, 1 insertion(+) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 70d0c295b4..c61572fb68 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -78,6 +78,7 @@ Want to get started writing a test for a new file or an existing file, but not s 3. If it is, see the redux section below on how to write tests for that. 4. If it's not, see the section below on writing tests for unconnected components. +### What to test For any type of component, you might want to consider testing: - User input results in the class's method being called. ``` From ab7d9c3cafb0fe5a5d8bf569b4b2f460c416c062 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Wed, 17 Mar 2021 14:19:39 -0400 Subject: [PATCH 11/37] formatting fixes --- developer_docs/testing.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index c61572fb68..db1f30c8fb 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -30,27 +30,27 @@ Many files still don't have tests, so if you're looking to get started as a cont ## Useful testing commands Run the whole test suite ``` -npm run test +$ npm run test ``` ----- [Run tests that match the pattern](https://stackoverflow.com/questions/28725955/how-do-i-test-a-single-file-using-jest/28775887). Useful if you're writing one specific test and want to only run that one. ``` -npm run test -- someFileName +$ npm run test -- someFileName ``` ----- Run the tests but update the snapshot if they don't match. ``` -npm run test -- -u +$ npm run test -- -u ``` ---- For example, if you wanted to run just the SketchList test but also update the snapshot, you could do: ``` -npm run test -- Sketchlist.test.js -u +$ npm run test -- Sketchlist.test.js -u ``` Find more commands in the [Jest documentation](https://jestjs.io/docs/cli). @@ -81,7 +81,7 @@ Want to get started writing a test for a new file or an existing file, but not s ### What to test For any type of component, you might want to consider testing: - User input results in the class's method being called. - ``` + ```js //component is the return value of calling render() const spy1 = jest.spyOn(component.instance(), 'func1'); act(() => { @@ -99,18 +99,18 @@ See the [redux section](#Testing-Redux) below :) ### Troubleshooting 1. Check if the component makes any API calls. If it's using axios, jest should already be set up to replace the axios library with a mocked version; however, you may want to [mock](https://jestjs.io/docs/mock-function-api#mockfnmockimplementationoncefn) the axios.get() function with your own version so that GET calls "return" whatever data makes sense for that test. - ``` + ```js axios.get.mockImplementationOnce( (x) => Promise.resolve({ data: 'foo' }) ); ``` -You can see it used in the context of a test [here](../client/modules/IDE/components/SketchList.test.jsx). +You can see it used in the context of a test [in the SketchList.test.jsx file](../client/modules/IDE/components/SketchList.test.jsx). 2. If the component makes use of the formatDate util, some of the functions in that rely on the ``./client/i18n.js`` file that also makes an ajax request, which sometimes leads to an ERRCONNECTED error on the console, even though your tests pass. You can fix it by adding a mock for that specific i18n file: - ``` + ```js jest.mock('_path_to_file_/i18n'); ``` -You can see it used in the context of a test [here](../client/modules/IDE/components/SketchList.test.jsx). +You can also see it used in the context of a test [in the SketchList.test.jsx file](../client/modules/IDE/components/SketchList.test.jsx). ## Files to be aware of @@ -218,11 +218,11 @@ It exports a render function with a i18n wrapper as ``render`` and a render func Thus, in your component test files, instead of calling ``import {functions you want} from 'react-testing-libary'`` importing react-testing library might look something like this: If your component only needs i18n and not redux: -``` +```js import { render, fireEvent, screen, waitFor } from '../../../test-utils'; ``` If your component needs i18n and redux: -``` +```js import { reduxRender, fireEvent, screen, waitFor } from '../../../test-utils'; ``` @@ -230,7 +230,7 @@ Redux and i18next are made accessible by placing wrappers around the component. For example, the exported render function that adds a wrapper for both redux and i18n looks roughly like this: -``` +```js function reduxRender( ui, { @@ -264,7 +264,7 @@ in progress ## Testing plain components If it doesn't contain ``connect(mapStateToProps, mapDispatchToProps)(ComponentName)`` or use hooks like ``useSelector``, then your component is not directly using Redux and testing your component will be simpler and might look something like this: -``` +```js import React from 'react'; import { unmountComponentAtNode } from 'react-dom'; import { act } from 'react-dom/test-utils'; @@ -327,7 +327,7 @@ describe('', () => { Consider what you want to test. Some possible things might be: - User input results in the expected function being called with the expected argument. - ``` + ```js act(() => { fireEvent.click(screen.getByTestId("testid")); }); @@ -352,7 +352,7 @@ Although it's possible to export the components as unconnected components for te This works like so: 1. Import the reduxRender function from ``client/test_utils.js`` 2. Configure the mock store. -``` +```js import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; @@ -360,18 +360,18 @@ import thunk from 'redux-thunk'; const mockStore = configureStore([thunk]); ``` 3. Create a mock store. There's an initial state that you can import from ``client/redux_test_stores/test_store.js`` -``` +```js store = mockStore(initialTestState); ``` 3. Render the component with reduxRender and the store that you just created. -``` +```js reduxRender(, {store, container}); ``` 4. Test things! You may need to use jest to mock certain functions if the component is making API calls. All together, it might look something like this. -``` +```js import React from 'react'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; @@ -417,7 +417,7 @@ describe(, () => { Some things to consider testing: - User input results in the expected redux action. - ``` + ```js act(() => { component = reduxRender(, { store, From f841150c3a2193a12361b053f6ce5c2ef691a583 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Wed, 17 Mar 2021 14:20:46 -0400 Subject: [PATCH 12/37] Update testing.md --- developer_docs/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index db1f30c8fb..2bf555f616 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -65,7 +65,7 @@ Find more commands in the [Jest documentation](https://jestjs.io/docs/cli). ## When to run tests -When you make a git commit, the tests will be run automatically for you. +When you make a git commit, the tests will be run automatically for you (maybe?). Tests will also be made when you make a PR. When you modify an existing component, it's a good idea to run the test suite to make sure it didn't make any changes that break the rest of the application. If they did break some tests, you would either have to fix a bug component or update the tests to match the new expected functionality. From 8782fbd3de6710ef97cc543a2c0469c9ea17e0c1 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Wed, 17 Mar 2021 14:53:05 -0400 Subject: [PATCH 13/37] Update testing.md --- developer_docs/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 2bf555f616..cce18ffd65 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -65,7 +65,7 @@ Find more commands in the [Jest documentation](https://jestjs.io/docs/cli). ## When to run tests -When you make a git commit, the tests will be run automatically for you (maybe?). Tests will also be made when you make a PR. +When you make a git commit, the tests will be run automatically for you (maybe? check with Cassie again). Tests will also be run when you make a PR and if you fail any tests it blocks the merge. When you modify an existing component, it's a good idea to run the test suite to make sure it didn't make any changes that break the rest of the application. If they did break some tests, you would either have to fix a bug component or update the tests to match the new expected functionality. From 8de92121a501043fdb7deea05555dadce9b50677 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Wed, 17 Mar 2021 14:58:17 -0400 Subject: [PATCH 14/37] add links --- developer_docs/testing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index cce18ffd65..74ce98d94e 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -75,8 +75,8 @@ Want to get started writing a test for a new file or an existing file, but not s (the below assumes we're using proposed folder structure 1) 1. Make a new file in the ``__tests__`` folder that's directly adjacent to your file. For example, if ``example.jsx`` is in ``src/components``, then you would make a file called ``example.test.jsx`` in ``src/components/__tests__`` 2. Check if the component is connected to redux or not. -3. If it is, see the redux section below on how to write tests for that. -4. If it's not, see the section below on writing tests for unconnected components. +3. If it is, see the [redux section](#Testing-Redux) below on how to write tests for that. +4. If it's not, see the [section below on writing tests for unconnected components](#Testing-plain-components). ### What to test For any type of component, you might want to consider testing: From a25c649ef6087ce0618d8ba761f98b1c21e10ebb Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Wed, 17 Mar 2021 15:31:10 -0400 Subject: [PATCH 15/37] added description of testid --- developer_docs/testing.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 74ce98d94e..939b280cac 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -77,6 +77,8 @@ Want to get started writing a test for a new file or an existing file, but not s 2. Check if the component is connected to redux or not. 3. If it is, see the [redux section](#Testing-Redux) below on how to write tests for that. 4. If it's not, see the [section below on writing tests for unconnected components](#Testing-plain-components). +5. If you're testing UI elements, there are many ways of querying for them, but you can add a data-testid attribute to the component in the source code and use ``getByTestId("testid")`` to retrieve it. +) ### What to test For any type of component, you might want to consider testing: From 5368538114cb2039e7fbb26f12b4e2db35871657 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 26 Mar 2021 14:57:40 -0400 Subject: [PATCH 16/37] Update testing.md --- developer_docs/testing.md | 152 ++++++++++++++++++++------------------ 1 file changed, 79 insertions(+), 73 deletions(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 939b280cac..bf6e68a657 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -58,8 +58,9 @@ Find more commands in the [Jest documentation](https://jestjs.io/docs/cli). ## Why write tests - Good place to start if you're learning the codebase because it's harder to mess up production code - Benefits all future contributors by allowing them to check their changes for errors -- Catches easy-to-miss errors +- Increased usage: Most code with only ever have a single invocation point, but this means that code might not be particularly robust and lead to bugs if a different devleoper reuses it in a different context. Writing tests increases the usage of the code in question and may improve the long-term durability, along with leading developers to refactor their code to be more usable. [[3]](#References) - Lets you check your own work and feel more comfortable sumbitting PRs +- Catches easy-to-miss errors - Good practice for large projects - Many of the existing components don't have tests yet, and you could write one :-) @@ -71,30 +72,62 @@ When you modify an existing component, it's a good idea to run the test suite to ## Writing a test Want to get started writing a test for a new file or an existing file, but not sure how? + + ### For React components -(the below assumes we're using proposed folder structure 1) -1. Make a new file in the ``__tests__`` folder that's directly adjacent to your file. For example, if ``example.jsx`` is in ``src/components``, then you would make a file called ``example.test.jsx`` in ``src/components/__tests__`` +1. Make a new file directly adjacent to your file. For example, if ``example.jsx`` is ``src/components/example.jsx``, then you would make a file called ``example.test.jsx`` at ``src/components/example.test.jsx`` 2. Check if the component is connected to redux or not. 3. If it is, see the [redux section](#Testing-Redux) below on how to write tests for that. 4. If it's not, see the [section below on writing tests for unconnected components](#Testing-plain-components). -5. If you're testing UI elements, there are many ways of querying for them, but you can add a data-testid attribute to the component in the source code and use ``getByTestId("testid")`` to retrieve it. ) +5. "Arange, Act, Assert:" In other words, *arrange* the set up for the test, *act* out whatever the subject's supposed to do, and *assert* on the results. [[3]](#References) + +### In every test file +Maybe we want to add these as comments?? +- What behavior is and isn't covered by the suites What dependencies should be replaced with mocks and what should be left realistic +- The primary design benefit (if any) of these tests +- The primary regression protection (if any) provided by these tests +- What an example test should look like +- The maximum permissible elapsed time for a run of an individual test or for the full suit + +### Consistency across tests +> "Teams that adopt a rigid and consistent structure to each test tend to more readily understand each test, because every deviation from the norm can be trusted to be meaningful and somehow specific to the nature of the subject." +- We want to default to using meaningless test data stored in the redux-test-stores folder. +- Be sure to follow the folder structure +- Follow the rendering guidelines set up for the components in this README. + +### Querying for elements +Read about the recommended order of priority for queries in [the testing library docs](https://testing-library.com/docs/guide-which-query/#priority). We recommend using roles and text, or labels. You can use this [handy extension](https://chrome.google.com/webstore/detail/testing-playground/hejbmebodbijjdhflfknehhcgaklhano/related) to do this. + ### What to test For any type of component, you might want to consider testing: -- User input results in the class's method being called. - ```js - //component is the return value of calling render() - const spy1 = jest.spyOn(component.instance(), 'func1'); - act(() => { - fireEvent.click(screen.getByTestId("testid")); - }); - expect(spy1).toHaveBeenCalledTimes(1); - ``` -- The text or divs that you expect to be on the page are actually there. +- The text or divs that you expect to be on the page are actually there. You can use [Queries](https://testing-library.com/docs/queries/about/) for this. - a previously saved snapshot of the HTML matches a snapshot taken during testing. - what else?? help! +>Only test the behaviors you know you need to care about. For example, if the desired behavior of a particular edge case doesn't truly matter yet or isn't fully understood, don't write a test for it yet. Doing so would restrict the freedom to refactor the implementation. Additionally, it will send the signal to future readers that this behavior is actually critical, when it very well might not be (perhaps a form of [accidental creativity]()). [[3]](#References) + +**Don't test unreachable edge cases:** You would have to add code to your original implementation to guard against these cases. The future proofing and the added cost to the codebase "is generally not worth their preceived potential benefits" [[3]](#References) + +**Make sure your tests are sufficient:** You want to make sure your test actually specifies all the behaviors you want to ensure the code exhibits. For example, testing that ``1+1 > 0`` would be correct, but insufficient. [[3]](#References) + +### File structure +Each test should have a top-level ```describe`` block to group related blocks together, with the name of the component under test. +*example.test.ts* + +```js +import example from './example'; + +describe('example', () => { + it('creates a new example', () => { + //your tests here + }); +}); + +``` + + ### For Redux action creators or reducers See the [redux section](#Testing-Redux) below :) @@ -116,58 +149,8 @@ You can also see it used in the context of a test [in the SketchList.test.jsx fi ## Files to be aware of -### Proposed folder structure 1 -All tests in ``__tests__`` folders that are directly adjacent to the files that they are testing. For example, if you're testing ``examplefolder/Sketchlist.test.jsx``, the test would be in ``examplefolder/__tests__/Sketchlist.test.jsx``. This is so that the tests are close to the files they're testing but are still hidden away from view most of the time. This also means that any snapshot files will be stored in the testing folder, such as ``examplefolder/__tests__/__snapshots__/Sketchlist.test.jsx.snap`` - -Manual mocks are in ``__mocks__`` folders are adjacent to the modules that they're mocking. - -Note: Even if you mock a user module in a ``__mocks__`` folder, user modules have to be explictly mocked in the test too, with ``Jest.mock("path_to_module")`` - -Node modules are mocked in the ``__mocks__`` folder at the root of the client folder, which also includes any mocks that are needed for user modules at the root of the folder directory. - -``` -. -└── client - ├── __mocks__ - │ ├── axios.js - | ├── i18n.js - | └── ...other Node modules you want to mock - ├── modules - │ ├── IDE - │ │ ├── actions - │ │ │ ├── __mocks__ - │ │ │ │ ├── projects.js - │ │ │ │ └─ ... other action creator mocks - │ │ │ ├── __tests__ - │ │ │ │ ├── projects.test.js - │ │ │ │ └─ ... other redux action creator tests - │ │ │ └── ... action creators - │ │ ├── components - │ │ │ ├── __tests__ - │ │ │ │ ├── __snapshots__ - │ │ │ │ │ ├── SketchList.test.jsx.snap - │ │ │ │ │ └─ ... other snapshots - │ │ │ │ ├── SketchList.test.jsx - │ │ │ ├── SketchList.test.jsx - │ │ │ └── ... and more component files - │ │ ├── reducers - │ │ │ ├── __tests__ - │ │ │ │ ├── assets.test.js - │ │ │ │ └─ ... other reducer tests - │ │ │ ├── assets.js - │ │ │ └── ...more reducers - │ └── ... more folders - ├── redux_test_stores - | ├── test_store.js - │ └── ...any other redux states you want to test - ├── i18n-test.js - ├── jest.setup.js - ├── test-utils.js - └──... other files and folders -``` - -### Proposed folder structure 2 -All tests are directly adjacent to the files that they are testing. For example, if you're testing ``examplefolder/Sketchlist.test.jsx``, the test would be in ``examplefolder/Sketchlist.test.jsx``. This is so that the tests are as close as possible to the files. This also means that any snapshot files will be stored in the same folder, such as ``examplefolder/__snapshots__/Sketchlist.test.jsx.snap`` +### Folder structure +All tests are directly adjacent to the files that they are testing, as described in the [React docs](https://reactjs.org/docs/faq-structure.html#grouping-by-file-type). For example, if you're testing ``examplefolder/Sketchlist.test.jsx``, the test would be in ``examplefolder/Sketchlist.test.jsx``. This is so that the tests are as close as possible to the files. This also means that any snapshot files will be stored in the same folder, such as ``examplefolder/__snapshots__/Sketchlist.test.jsx.snap`` Manual mocks are in ``__mocks__`` folders are adjacent to the modules that they're mocking. @@ -331,7 +314,7 @@ Consider what you want to test. Some possible things might be: - User input results in the expected function being called with the expected argument. ```js act(() => { - fireEvent.click(screen.getByTestId("testid")); + fireEvent.click(screen.getByLabelText('Username')); }); expect(yourMockFunction).toHaveBeenCalledTimes(1); expect(yourMockFunction.mock.calls[0][0]).toBe(argument); @@ -446,17 +429,34 @@ A few components also import ``./client/i18n.js`` (or ``./client/utils/formatDat ## Some more background on tests -### test driven development (TDD) -BDD??? +### Test Driven Development (TDD) +Do we want a section here about TDD history? ### snapshot testing You can save a snapshot of what the HTML looks like when the component is rendered. ### integration tests -Testing multiple components together. A small example is rendering a parent component and a child component within that. +Testing multiple components together. A small example is rendering a parent component in order to test the interactions between children components. For frontend development, integration tests might focus on end-to-end flows using Puppeter or another type of headless browser testing. We don't do this just yet. ### unit tests -Most of our tests are of this type. In this, you're testing a the functionality of a single component and no more. +Most of our tests are of this type. In this, you're testing a the functionality of a single component and no more. They provide lots of feedback on the specific component that you're testing, with the cost of high [redundant coverage](https://github.com/testdouble/contributing-tests/wiki/Redundant-Coverage) and more time spent refactoring tests when components get rewritten. + +### Other terminology for mocking +Thanks [Test Double Wiki](https://github.com/testdouble/contributing-tests/wiki/Test-Double) for the definitions. +#### Test double +Broadest available term to describe any fake thing used in place of a real thing for a test. +#### Stub +Any test double that uses a preconfigured response, such always responding with placeholder json to a certain fetch call. +#### Fake +A test double that provides an alternate implementation of a real thing for the purpose of a test. +#### Mock +Colloquially can mean any of the above, just used generally for test doubles. +#### Partial mock +Refers to any actual object which has been wrapped or changed to provide artificial responses to +some methods but not others. Partial mocks are widely considered to be an anti-pattern of test double usage. + +#### Spy +Records every invocation made against it and can verify certain interactions took place after the fact. ## Internationalization This project uses i18next for internationalization. If you import the render function with the i18n wrapper from ``test_utils.js``, it's set up to use English, so the components with be rendered with English text and you should be able to count on this to test for specific strings. @@ -464,11 +464,17 @@ This project uses i18next for internationalization. If you import the render fun ## Tips 1. Make test fail at least once to make sure it was a meaningful test 2. "If you or another developer change the component in a way that it changes its behaviour at least one test should fail." - [How to Unit Test in React](https://itnext.io/how-to-unit-test-in-react-72e911e2b8d) +3. Avoid using numbers or data that seem "special" in your tests. For example, if you were checking the "age" variable in a component is a integer, but checked it as so ``expect(person.ageValidator(18)).toBe(true)``, the reader might assume that the number 18 had some significance to the function because it's a significant age. It would be better to have used 1234. ## More Resources -- any other resources for learning about tests here? +- [React Testing Library Cheatsheet](https://testing-library.com/docs/react-testing-library/cheatsheet/) ## References 1. [Best practices for unit testing with a react redux approach](https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach) -2. [How to test your react-redux application (this article also references axios)](https://medium.com/asos-techblog/how-to-test-your-react-redux-application-48d90481a253) \ No newline at end of file +2. [How to test your react-redux application (this article also references axios)](https://medium.com/asos-techblog/how-to-test-your-react-redux-application-48d90481a253) + +3. [Testing Double Wiki (Special thanks to this wiki for being such a comprehensive guide to the history of testing and best practices.)](https://github.com/testdouble/contributing-tests/wiki/Tests%27-Influence-on-Design) + +## Special thanks +Thank you to HipsterBrown for helping us out with writing this documentation. \ No newline at end of file From 88326b5451e1fbfa8ca2de20b6dc43503ca1e08b Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Thu, 1 Apr 2021 11:50:27 -0400 Subject: [PATCH 17/37] Update testing.md --- developer_docs/testing.md | 81 ++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index bf6e68a657..101d03aafe 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -1,13 +1,14 @@ # Testing -For an initial basic overview of testing for React apps, [you can read what the React developers have to say about it](https://reactjs.org/docs/testing.html). +For an initial basic overview of testing for React apps, [you can read what the React developers have to say about it](https://reactjs.org/docs/testing.html). We use both unit tests and integration tests. We are testing React components by rendering the component trees in a simplified test environment and making assertions on what gets rendered and what functions get called. -Many files still don't have tests, so if you're looking to get started as a contributor, this would be a great place to start! +Many files still don't have tests, so **if you're looking to get started as a contributor, this would be a great place to start!** ## What's in this document - [Testing dependencies](#testing-dependencies) - [Useful testing commands](#Useful-testing-commands) +- [Our testing methods](#Our-testing-methods) - [Why write tests](#Why-write-tests) - [When to run tests](#When-to-run-tests) - [Writing a test](#Writing-a-test) @@ -15,8 +16,8 @@ Many files still don't have tests, so if you're looking to get started as a cont - [Testing plain components](#Testing-plain-components) - [Testing Redux](#Testing-Redux) - [How to handle API calls in tests](#How-to-handle-API-calls-in-tests) -- [Some more background on tests](#Some-more-background-on-tests) - [Internationalization](#internationalization) +- [Useful terminology to know](#Useful-terminology-to-know) - [Tips](#Tips) - [More resources](#More-resources) - [References](#References) @@ -55,46 +56,56 @@ $ npm run test -- Sketchlist.test.js -u Find more commands in the [Jest documentation](https://jestjs.io/docs/cli). +## Our testing methods + +### Unit tests +In unit tests, you're testing a the functionality of a single component and no more. They provide lots of feedback on the specific component that you're testing, with the cost of high [redundant coverage](https://github.com/testdouble/contributing-tests/wiki/Redundant-Coverage) and more time spent refactoring tests when components get rewritten. **Not every file needs a unit test.** Unit tests are most important for components that are either: +1. User facing (like a text input field or a user form component) +2. Used across multiple components like a reusable dropdown menu or reusable table element + +In both of these cases, the component being tested is not merely an implementation detail and is being used more extensively, it's important for the unit tests to test the error cases that could occur to ensure that the component is robust. For example, for a user-facing input field that should only take positive numbers, a unit test would want to cover what happens when users enter negative numbers or letters. + +### Integration tests +Testing multiple components together. A small example is rendering a parent component in order to test the interactions between children components. Generally, they validate how multiple units of your application work together. Jest, which is what we use, uses jsdom under the hood to emulate common browser APIs with less overhead than automation like a headless browser, and its mocking tools can stub out external API calls. We use integration tests to maximize coverage and to make sure all the pieces play nice together. We want our integration tests to cover the testing of components that don't have unit tests because they're only used in one place and are merely an implementation detail. The integration tests can test the "happy path" flow, while we expect the unit tests to have tested the error cases already. + +See [this great article on CSS tricks](https://css-tricks.com/react-integration-testing-greater-coverage-fewer-tests/) about integration tests for more information about this. + +To reiterate, we use integration tests to test our "happy path" flows and maximize coverage on individual components that are only used once. We use unit tests to test the robustness of user-facing components and reusable components. + +### Snapshot testing +You can save a snapshot of what the HTML looks like when the component is rendered. + ## Why write tests -- Good place to start if you're learning the codebase because it's harder to mess up production code -- Benefits all future contributors by allowing them to check their changes for errors -- Increased usage: Most code with only ever have a single invocation point, but this means that code might not be particularly robust and lead to bugs if a different devleoper reuses it in a different context. Writing tests increases the usage of the code in question and may improve the long-term durability, along with leading developers to refactor their code to be more usable. [[3]](#References) -- Lets you check your own work and feel more comfortable sumbitting PRs -- Catches easy-to-miss errors -- Good practice for large projects +- Good place to start if you're learning the codebase. +- Benefits all future contributors by allowing them to check their changes for errors. +- Increased usage: Most code with only ever have a single invocation point, but this means that code might not be particularly robust and lead to bugs if a different developer reuses it in a different context. Writing tests increases the usage of the code in question and may improve the long-term durability, along with leading developers to refactor their code to be more usable. [[3]](#References) +- Lets you check your own work and feel more comfortable sumbitting PRs. +- Catches easy-to-miss errors. +- Good practice for large projects. - Many of the existing components don't have tests yet, and you could write one :-) ## When to run tests -When you make a git commit, the tests will be run automatically for you (maybe? check with Cassie again). Tests will also be run when you make a PR and if you fail any tests it blocks the merge. +When you ``git push`` your code, the tests will be run automatically for you. Tests will also be run when you make a PR and if you fail any tests it blocks the merge. When you modify an existing component, it's a good idea to run the test suite to make sure it didn't make any changes that break the rest of the application. If they did break some tests, you would either have to fix a bug component or update the tests to match the new expected functionality. ## Writing a test Want to get started writing a test for a new file or an existing file, but not sure how? - ### For React components 1. Make a new file directly adjacent to your file. For example, if ``example.jsx`` is ``src/components/example.jsx``, then you would make a file called ``example.test.jsx`` at ``src/components/example.test.jsx`` 2. Check if the component is connected to redux or not. 3. If it is, see the [redux section](#Testing-Redux) below on how to write tests for that. 4. If it's not, see the [section below on writing tests for unconnected components](#Testing-plain-components). ) -5. "Arange, Act, Assert:" In other words, *arrange* the set up for the test, *act* out whatever the subject's supposed to do, and *assert* on the results. [[3]](#References) - -### In every test file -Maybe we want to add these as comments?? -- What behavior is and isn't covered by the suites What dependencies should be replaced with mocks and what should be left realistic -- The primary design benefit (if any) of these tests -- The primary regression protection (if any) provided by these tests -- What an example test should look like -- The maximum permissible elapsed time for a run of an individual test or for the full suit +5. "Arange, Act, Assert:" In other words, *arrange* the setup for the test, *act* out whatever the subject's supposed to do, and *assert* on the results. [[3]](#References) ### Consistency across tests > "Teams that adopt a rigid and consistent structure to each test tend to more readily understand each test, because every deviation from the norm can be trusted to be meaningful and somehow specific to the nature of the subject." - We want to default to using meaningless test data stored in the redux-test-stores folder. - Be sure to follow the folder structure -- Follow the rendering guidelines set up for the components in this README. +- Follow the rendering guidelines set up for the components in this ["Writing a Test"](#Writing-a-test) section. ### Querying for elements Read about the recommended order of priority for queries in [the testing library docs](https://testing-library.com/docs/guide-which-query/#priority). We recommend using roles and text, or labels. You can use this [handy extension](https://chrome.google.com/webstore/detail/testing-playground/hejbmebodbijjdhflfknehhcgaklhano/related) to do this. @@ -103,10 +114,11 @@ Read about the recommended order of priority for queries in [the testing library ### What to test For any type of component, you might want to consider testing: - The text or divs that you expect to be on the page are actually there. You can use [Queries](https://testing-library.com/docs/queries/about/) for this. -- a previously saved snapshot of the HTML matches a snapshot taken during testing. -- what else?? help! +- If it's an integration test, you could consider testing the "happy path" flow. For example, in a login form, you would test how a user might enter their username and password and then enter that information. +- Generally, you want to focus your testing on "user input" -> "expected output" instead of making sure the middle steps work as you would expect. This might mean that you don't need to check that the state changes or class-specific methods occur. This is so that if some of the small details in the implementation of the component changes in the future, the tests can remain the same. +- more details on testing behavior in the component-specific sections ->Only test the behaviors you know you need to care about. For example, if the desired behavior of a particular edge case doesn't truly matter yet or isn't fully understood, don't write a test for it yet. Doing so would restrict the freedom to refactor the implementation. Additionally, it will send the signal to future readers that this behavior is actually critical, when it very well might not be (perhaps a form of [accidental creativity]()). [[3]](#References) +>Only test the behaviors you know you need to care about. For example, if the desired behavior of a particular edge case doesn't truly matter yet or isn't fully understood, don't write a test for it yet. Doing so would restrict the freedom to refactor the implementation. Additionally, it will send the signal to future readers that this behavior is actually critical, when it very well might not be. [[3]](#References) **Don't test unreachable edge cases:** You would have to add code to your original implementation to guard against these cases. The future proofing and the added cost to the codebase "is generally not worth their preceived potential benefits" [[3]](#References) @@ -242,10 +254,6 @@ function reduxRender( ### redux_test_stores This folder contains the inital redux states that you can provide to the ``reduxRender`` function when testing. For example, if you want to render the SketchList component with a username of ``happydog`` and some sample sketches, ``redux_test_stores\test_store.js`` contains a definition for that state that you can import and provide to the renderer. -### jest configs in package.json - -in progress - ## Testing plain components If it doesn't contain ``connect(mapStateToProps, mapDispatchToProps)(ComponentName)`` or use hooks like ``useSelector``, then your component is not directly using Redux and testing your component will be simpler and might look something like this: @@ -319,7 +327,6 @@ Consider what you want to test. Some possible things might be: expect(yourMockFunction).toHaveBeenCalledTimes(1); expect(yourMockFunction.mock.calls[0][0]).toBe(argument); ``` -- what else???? help! ## Testing Redux @@ -427,21 +434,8 @@ The benefit of this is that you can control exactly what happens when any axios A few components also import ``./client/i18n.js`` (or ``./client/utils/formatDate``, which imports the first file), in which the ``i18n.use(Backend)`` line can sometimes throw a sneaky ERRCONNECTED error. You can resolve this by mocking that file as described in [this section](#Troubleshooting). -## Some more background on tests - -### Test Driven Development (TDD) -Do we want a section here about TDD history? - -### snapshot testing -You can save a snapshot of what the HTML looks like when the component is rendered. - -### integration tests -Testing multiple components together. A small example is rendering a parent component in order to test the interactions between children components. For frontend development, integration tests might focus on end-to-end flows using Puppeter or another type of headless browser testing. We don't do this just yet. - -### unit tests -Most of our tests are of this type. In this, you're testing a the functionality of a single component and no more. They provide lots of feedback on the specific component that you're testing, with the cost of high [redundant coverage](https://github.com/testdouble/contributing-tests/wiki/Redundant-Coverage) and more time spent refactoring tests when components get rewritten. -### Other terminology for mocking +## Useful terminology to know Thanks [Test Double Wiki](https://github.com/testdouble/contributing-tests/wiki/Test-Double) for the definitions. #### Test double Broadest available term to describe any fake thing used in place of a real thing for a test. @@ -465,6 +459,7 @@ This project uses i18next for internationalization. If you import the render fun 1. Make test fail at least once to make sure it was a meaningful test 2. "If you or another developer change the component in a way that it changes its behaviour at least one test should fail." - [How to Unit Test in React](https://itnext.io/how-to-unit-test-in-react-72e911e2b8d) 3. Avoid using numbers or data that seem "special" in your tests. For example, if you were checking the "age" variable in a component is a integer, but checked it as so ``expect(person.ageValidator(18)).toBe(true)``, the reader might assume that the number 18 had some significance to the function because it's a significant age. It would be better to have used 1234. +4. Tests should help other developers understand the expected behavior of the component that it's testing ## More Resources - [React Testing Library Cheatsheet](https://testing-library.com/docs/react-testing-library/cheatsheet/) From 1972a2a8309dcba5c5e41340178125ceab41044b Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Thu, 1 Apr 2021 12:49:03 -0400 Subject: [PATCH 18/37] Update testing.md --- developer_docs/testing.md | 66 ++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 101d03aafe..016de52bc5 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -262,36 +262,26 @@ import React from 'react'; import { unmountComponentAtNode } from 'react-dom'; import { act } from 'react-dom/test-utils'; import { fireEvent, render, screen } from '../../../../test-utils'; -import FakePreferences from './index'; - -/* a helper function to render the components with the - * props that that component needs to be passed in - * if you want to access the rendered component itself - * you'd have to modify it a little to return the what - * gets returned from the render function, along with the props, which is what it's returning now. - * the default props in this can be overwritten by using extraProps - */ -const renderComponent = (extraProps = {}, container) => { - // if we want to overwrite any of these props, we can do it with extraProps because later keys overwrite earlier ones in the spread operator - const props = { +import MyComponent from './MyComponent'; + +describe('', () => { + let container = null; + + let subjectProps = { t: jest.fn(), fontSize: 12, autosave: false, setFontSize: jest.fn(), setAutosave: jest.fn(), - ...extraProps }; - render(, { container }); - return props; -}; + const subject = () => { + render(, { container }); + }; -describe('', () => { - let container = null; beforeEach(() => { // setup a DOM element as a render target container = document.createElement('div'); - container.classList.add('testing-container'); document.body.appendChild(container); }); @@ -300,22 +290,34 @@ describe('', () => { unmountComponentAtNode(container); container.remove(); container = null; - }); - describe('font tests', () => { - it('font size increase button says increase', () => { - let props; - // render the component - act(() => { - props = renderComponent({fontSize: 15}, container); - }); - - //I do tests here. - //you can access mock functions from props - //for example, props.setFontSize - + //reset the mocks in subjectProps + jest.clearAllMocks(); + }); + + it('I am the test description', () => { + // render the component + act(() => { + subject(); }); + + //I do tests here. + //you can access mock functions from subjectProps. For example, subjectProps.setFontSize + + }); + + describe('test with a different prop', () => { + let subjectProps = {...subjectProps, fontSize: 14} + + it("here's that test with a different prop", () => { + act(() => { + subject(); + }); + //test here + }) }); + +}); ``` Consider what you want to test. Some possible things might be: From 93951c215db5d332c521708f72800292a7e85f9b Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Thu, 1 Apr 2021 13:12:53 -0400 Subject: [PATCH 19/37] Update testing.md --- developer_docs/testing.md | 80 +++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 016de52bc5..4034b676de 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -270,9 +270,7 @@ describe('', () => { let subjectProps = { t: jest.fn(), fontSize: 12, - autosave: false, - setFontSize: jest.fn(), - setAutosave: jest.fn(), + setFontSize: jest.fn() }; const subject = () => { @@ -301,20 +299,22 @@ describe('', () => { subject(); }); - //I do tests here. - //you can access mock functions from subjectProps. For example, subjectProps.setFontSize + /* Tests go here! + * You can access mock functions from subjectProps. + * For example, subjectProps.setFontSize + */ }); describe('test with a different prop', () => { - let subjectProps = {...subjectProps, fontSize: 14} - - it("here's that test with a different prop", () => { - act(() => { - subject(); - }); - //test here - }) + let subjectProps = {...subjectProps, fontSize: 14} + + it("here's that test with a different prop", () => { + act(() => { + subject(); + }); + //test here + }); }); }); @@ -365,21 +365,34 @@ reduxRender(, {store, container}); All together, it might look something like this. + +*MyReduxComponent.test.jsx* ```js import React from 'react'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { unmountComponentAtNode } from 'react-dom'; import { act } from 'react-dom/test-utils'; -import MyComponent from './MyComponent'; +import MyReduxComponent from './MyReduxComponent'; import { reduxRender, fireEvent, screen } from '../../../test-utils'; import { initialTestState } from '../../../redux_test_stores/test_store'; -describe(, () => { - let container; +describe('', () => { + let container = null; const mockStore = configureStore([thunk]); const store = mockStore(initialTestState); + let subjectProps = { + sampleprop: "foo" + }; + + const subject = () => { + reduxRender(, { + store, + container + }); + }; + beforeEach(() => { // setup a DOM element as a render target container = document.createElement('div'); @@ -391,22 +404,39 @@ describe(, () => { unmountComponentAtNode(container); container.remove(); container = null; + + //reset the mocks in subjectProps + jest.clearAllMocks(); + + //clear the mock store too store.clearActions(); }); - - it('stuff about the test', () => { - let component; + + it('I am the test description', () => { + // render the component act(() => { - component = reduxRender(, { - store, - container - }); + subject(); }); + + /* Tests go here! + * You can access mock functions from subjectProps. + * For example, subjectProps.setFontSize + */ + + }); + + describe('test with a different prop', () => { + let subjectProps = {...subjectProps, fontSize: 14} - //your tests go here + it("here's that test with a different prop", () => { + act(() => { + subject(); + }); + //test here + }); }); -}) +}); ``` Some things to consider testing: From 8439e70a0bf9aabcee845b982c5fe2913d1bed7a Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Thu, 1 Apr 2021 13:44:06 -0400 Subject: [PATCH 20/37] fix typos, add links --- developer_docs/testing.md | 54 ++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 4034b676de..75cf60abbd 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -59,11 +59,11 @@ Find more commands in the [Jest documentation](https://jestjs.io/docs/cli). ## Our testing methods ### Unit tests -In unit tests, you're testing a the functionality of a single component and no more. They provide lots of feedback on the specific component that you're testing, with the cost of high [redundant coverage](https://github.com/testdouble/contributing-tests/wiki/Redundant-Coverage) and more time spent refactoring tests when components get rewritten. **Not every file needs a unit test.** Unit tests are most important for components that are either: +In unit tests, you're testing the functionality of a single component and no more. They provide lots of feedback on the specific component that you're testing, with the cost of high [redundant coverage](https://github.com/testdouble/contributing-tests/wiki/Redundant-Coverage) and more time spent refactoring tests when components get rewritten. **Not every file needs a unit test.** Unit tests are most important for components that are either: 1. User facing (like a text input field or a user form component) 2. Used across multiple components like a reusable dropdown menu or reusable table element -In both of these cases, the component being tested is not merely an implementation detail and is being used more extensively, it's important for the unit tests to test the error cases that could occur to ensure that the component is robust. For example, for a user-facing input field that should only take positive numbers, a unit test would want to cover what happens when users enter negative numbers or letters. +In both of these cases, the component being tested is not merely an implementation detail. Thus, it's important for the unit tests to test the error cases that could occur to ensure that the component is robust. For example, for a user-facing input field that should only take positive numbers, a unit test would want to cover what happens when users enter negative numbers or letters. ### Integration tests Testing multiple components together. A small example is rendering a parent component in order to test the interactions between children components. Generally, they validate how multiple units of your application work together. Jest, which is what we use, uses jsdom under the hood to emulate common browser APIs with less overhead than automation like a headless browser, and its mocking tools can stub out external API calls. We use integration tests to maximize coverage and to make sure all the pieces play nice together. We want our integration tests to cover the testing of components that don't have unit tests because they're only used in one place and are merely an implementation detail. The integration tests can test the "happy path" flow, while we expect the unit tests to have tested the error cases already. @@ -98,14 +98,14 @@ Want to get started writing a test for a new file or an existing file, but not s 2. Check if the component is connected to redux or not. 3. If it is, see the [redux section](#Testing-Redux) below on how to write tests for that. 4. If it's not, see the [section below on writing tests for unconnected components](#Testing-plain-components). -) + 5. "Arange, Act, Assert:" In other words, *arrange* the setup for the test, *act* out whatever the subject's supposed to do, and *assert* on the results. [[3]](#References) -### Consistency across tests -> "Teams that adopt a rigid and consistent structure to each test tend to more readily understand each test, because every deviation from the norm can be trusted to be meaningful and somehow specific to the nature of the subject." -- We want to default to using meaningless test data stored in the redux-test-stores folder. -- Be sure to follow the folder structure -- Follow the rendering guidelines set up for the components in this ["Writing a Test"](#Writing-a-test) section. +### For Redux action creators or reducers +See the [redux section](#Testing-Redux) below :) + +### For utility files +You might still want to write tests for non-component or non-redux files, such as modules with utility functions. What gets tested in this case depends a lot on the module itself, but generally, you would import the module and test the functions within it. ### Querying for elements Read about the recommended order of priority for queries in [the testing library docs](https://testing-library.com/docs/guide-which-query/#priority). We recommend using roles and text, or labels. You can use this [handy extension](https://chrome.google.com/webstore/detail/testing-playground/hejbmebodbijjdhflfknehhcgaklhano/related) to do this. @@ -113,7 +113,11 @@ Read about the recommended order of priority for queries in [the testing library ### What to test For any type of component, you might want to consider testing: -- The text or divs that you expect to be on the page are actually there. You can use [Queries](https://testing-library.com/docs/queries/about/) for this. +- The text or divs that you expect to be on the page are actually there. You can use [Queries](https://testing-library.com/docs/queries/about/) for this. Assertions should make use of the toBeInTheDocument() matcher when asserting that an element exists: + ``` + expect(screen.getByText('Hello World')).toBeInTheDocument(); + expect(screen.queryByText('Does not exist')).not.toBeInTheDocument(); + ``` - If it's an integration test, you could consider testing the "happy path" flow. For example, in a login form, you would test how a user might enter their username and password and then enter that information. - Generally, you want to focus your testing on "user input" -> "expected output" instead of making sure the middle steps work as you would expect. This might mean that you don't need to check that the state changes or class-specific methods occur. This is so that if some of the small details in the implementation of the component changes in the future, the tests can remain the same. - more details on testing behavior in the component-specific sections @@ -125,13 +129,14 @@ For any type of component, you might want to consider testing: **Make sure your tests are sufficient:** You want to make sure your test actually specifies all the behaviors you want to ensure the code exhibits. For example, testing that ``1+1 > 0`` would be correct, but insufficient. [[3]](#References) ### File structure -Each test should have a top-level ```describe`` block to group related blocks together, with the name of the component under test. -*example.test.ts* +Each test should have a top-level ``describe`` block to group related blocks together, with the name of the component under test. + +*Example.test.ts* ```js -import example from './example'; +import Example from './Example'; -describe('example', () => { +describe('', () => { it('creates a new example', () => { //your tests here }); @@ -139,9 +144,12 @@ describe('example', () => { ``` +### Consistency across tests +> "Teams that adopt a rigid and consistent structure to each test tend to more readily understand each test, because every deviation from the norm can be trusted to be meaningful and somehow specific to the nature of the subject." +- We want to default to using meaningless test data stored in the redux-test-stores folder. +- Be sure to follow the [folder structure](#Folder-structure) +- Follow the rendering guidelines set up for the components in this [Writing a Test](#Writing-a-test) section. -### For Redux action creators or reducers -See the [redux section](#Testing-Redux) below :) ### Troubleshooting 1. Check if the component makes any API calls. If it's using axios, jest should already be set up to replace the axios library with a mocked version; however, you may want to [mock](https://jestjs.io/docs/mock-function-api#mockfnmockimplementationoncefn) the axios.get() function with your own version so that GET calls "return" whatever data makes sense for that test. @@ -164,6 +172,9 @@ You can also see it used in the context of a test [in the SketchList.test.jsx fi ### Folder structure All tests are directly adjacent to the files that they are testing, as described in the [React docs](https://reactjs.org/docs/faq-structure.html#grouping-by-file-type). For example, if you're testing ``examplefolder/Sketchlist.test.jsx``, the test would be in ``examplefolder/Sketchlist.test.jsx``. This is so that the tests are as close as possible to the files. This also means that any snapshot files will be stored in the same folder, such as ``examplefolder/__snapshots__/Sketchlist.test.jsx.snap`` +CASSIE - WHERE DO WE PUT THE INTEGRATION TESTS? +Integration tests can be placed in the ``__tests__`` folder that's at the root of the client folder. They should be called ``ComponentName.test.integration.jsx`` + Manual mocks are in ``__mocks__`` folders are adjacent to the modules that they're mocking. Note: Even if you mock a user module in a ``__mocks__`` folder, user modules have to be explictly mocked in the test too, with ``Jest.mock("path_to_module")`` @@ -255,8 +266,9 @@ function reduxRender( This folder contains the inital redux states that you can provide to the ``reduxRender`` function when testing. For example, if you want to render the SketchList component with a username of ``happydog`` and some sample sketches, ``redux_test_stores\test_store.js`` contains a definition for that state that you can import and provide to the renderer. ## Testing plain components -If it doesn't contain ``connect(mapStateToProps, mapDispatchToProps)(ComponentName)`` or use hooks like ``useSelector``, then your component is not directly using Redux and testing your component will be simpler and might look something like this: +If it doesn't contain ``connect(mapStateToProps, mapDispatchToProps)(ComponentName)`` or use hooks like ``useSelector``, then your component is not directly using Redux and testing your component will be simpler and might look something like the code below. Notably, we descibe the component being tested as the [subject under test](http://xunitpatterns.com/SUT.html) by creating a function called ``subject`` that renders the component with the subject dependencies (the props) that are defined in the same scope. They're declared with ``let`` so that they can be overwritten in a nested ``describe``block that tests different dependencies. This keeps the subject function consistent between test suites and explicitly declares variables that can affect the outcome of the test. +*MyComponent.test.jsx* ```js import React from 'react'; import { unmountComponentAtNode } from 'react-dom'; @@ -274,7 +286,7 @@ describe('', () => { }; const subject = () => { - render(, { container }); + render(, { container }); }; beforeEach(() => { @@ -307,7 +319,7 @@ describe('', () => { }); describe('test with a different prop', () => { - let subjectProps = {...subjectProps, fontSize: 14} + subjectProps = {...subjectProps, fontSize: 14} it("here's that test with a different prop", () => { act(() => { @@ -321,7 +333,7 @@ describe('', () => { ``` Consider what you want to test. Some possible things might be: -- User input results in the expected function being called with the expected argument. +- User input results in the [expected function being called with the expected argument](https://jestjs.io/docs/mock-functions). ```js act(() => { fireEvent.click(screen.getByLabelText('Username')); @@ -426,7 +438,9 @@ describe('', () => { }); describe('test with a different prop', () => { - let subjectProps = {...subjectProps, fontSize: 14} + subjectProps = { + sampleprop: "boo!" + } it("here's that test with a different prop", () => { act(() => { From 9477cc378b53bbdc3baf26bc1264c9000ec09dec Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Thu, 1 Apr 2021 15:42:11 -0400 Subject: [PATCH 21/37] remove duplicate cleanup functions --- client/modules/App/App.test.jsx | 55 ++++++++ .../IDE/components/SketchList.test.jsx | 132 ++++++++---------- developer_docs/testing.md | 59 ++------ 3 files changed, 128 insertions(+), 118 deletions(-) create mode 100644 client/modules/App/App.test.jsx diff --git a/client/modules/App/App.test.jsx b/client/modules/App/App.test.jsx new file mode 100644 index 0000000000..92b1618755 --- /dev/null +++ b/client/modules/App/App.test.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import axios from 'axios'; +import { unmountComponentAtNode } from 'react-dom'; +import { act } from 'react-dom/test-utils'; +import App from './App'; +import { + reduxRender, + fireEvent, + screen, + within, + prettyDOM +} from '../../test-utils'; +import { initialTestState } from '../../redux_test_stores/test_store'; + +jest.mock('../../i18n'); + +describe('', () => { + // to attach the rendered DOM element to + let container; + + const mockStore = configureStore([thunk]); + const store = mockStore(initialTestState); + + const subject = () => + reduxRender(, { + store, + container + }); + + beforeEach(() => { + // setup a DOM element as a render target + container = document.createElement('div'); + document.body.appendChild(container); + // axios.get.mockImplementationOnce((x) => Promise.resolve({ data: 'foo' })); + }); + + afterEach(() => { + // cleanup on exiting + unmountComponentAtNode(container); + container.remove(); + container = null; + store.clearActions(); + }); + + it('renders', () => { + act(() => { + subject(); + }); + console.log(prettyDOM(container)); + // expect(screen.getByText('testsketch1')).toBeInTheDocument(); + // expect(screen.getByText('testsketch2')).toBeInTheDocument(); + }); +}); diff --git a/client/modules/IDE/components/SketchList.test.jsx b/client/modules/IDE/components/SketchList.test.jsx index 091f7e1fcd..1dd5016ac4 100644 --- a/client/modules/IDE/components/SketchList.test.jsx +++ b/client/modules/IDE/components/SketchList.test.jsx @@ -2,129 +2,113 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import axios from 'axios'; -import { unmountComponentAtNode } from 'react-dom'; import { act } from 'react-dom/test-utils'; import SketchList from './SketchList'; -import { reduxRender, fireEvent, screen } from '../../../test-utils'; import { - initialTestState, - mockProjects -} from '../../../redux_test_stores/test_store'; + reduxRender, + fireEvent, + screen, + within, + prettyDOM +} from '../../../test-utils'; +import { initialTestState } from '../../../redux_test_stores/test_store'; jest.mock('../../../i18n'); -/* - * there seem to be polarizing opinions about whether or not - * we should test the unconnected component or the - * connected one. For the sake of not editing the original SketchList file - * with an unneccessary export statement, I'm testing - * the connected component with redux-mock-store. - * this approach is outlined here - - * https://www.robinwieruch.de/react-connected-component-test - */ - describe('', () => { - let container; const mockStore = configureStore([thunk]); const store = mockStore(initialTestState); + let subjectProps = { username: initialTestState.user.username }; + + const subject = () => + reduxRender(, { store }); + beforeEach(() => { - // setup a DOM element as a render target - container = document.createElement('div'); - document.body.appendChild(container); axios.get.mockImplementationOnce((x) => Promise.resolve({ data: 'foo' })); }); afterEach(() => { - // cleanup on exiting - unmountComponentAtNode(container); - container.remove(); - container = null; store.clearActions(); }); it('has sample projects', () => { - let component; act(() => { - component = reduxRender(, { - store, - container - }); + subject(); }); expect(screen.getByText('testsketch1')).toBeInTheDocument(); expect(screen.getByText('testsketch2')).toBeInTheDocument(); }); it('clicking on date created row header dispatches a reordering action', () => { - let component; act(() => { - component = reduxRender(, { - store, - container - }); + subject(); }); + act(() => { - fireEvent.click(screen.getByTestId('toggle-direction-createdAt')); + fireEvent.click(screen.getByText(/date created/i)); }); + const expectedAction = [{ type: 'TOGGLE_DIRECTION', field: 'createdAt' }]; expect(store.getActions()).toEqual(expect.arrayContaining(expectedAction)); }); - it('clicking on dropdown arrow opens sketch options', () => { - let component; + it('clicking on dropdown arrow opens sketch options - sketches belong to user', () => { act(() => { - component = reduxRender(, { - store, - container - }); + subject(); + }); + + const row = screen.getByRole('row', { + name: /testsketch1/ + }); + + const dropdown = within(row).getByRole('button', { + name: 'Toggle Open/Close Sketch Options' + }); + + act(() => { + fireEvent.click(dropdown); + }); + + expect(screen.queryByText('Rename')).toBeInTheDocument(); + expect(screen.queryByText('Duplicate')).toBeInTheDocument(); + expect(screen.queryByText('Download')).toBeInTheDocument(); + expect(screen.queryByText('Add to collection')).toBeInTheDocument(); + expect(screen.queryByText('Delete')).toBeInTheDocument(); + }); + + it('snapshot testing', () => { + const { asFragment } = subject(); + expect(asFragment()).toMatchSnapshot(); + }); + + describe('different user than the one who created the sketches', () => { + beforeAll(() => { + subjectProps = { username: 'notthesameusername' }; }); - const dropdown = screen.queryAllByTestId( - 'sketch-list-toggle-options-arrow' - ); - if (dropdown.length > 0) { + it('clicking on dropdown arrow opens sketch options without Rename or Delete option', () => { act(() => { - fireEvent.click(dropdown[0]); + subject(); }); - expect(screen.queryByText('Rename')).not.toBeInTheDocument(); - expect(screen.queryByText('Duplicate')).toBeInTheDocument(); - expect(screen.queryByText('Download')).toBeInTheDocument(); - expect(screen.queryByText('Add to collection')).toBeInTheDocument(); - expect(screen.queryByText('Delete')).not.toBeInTheDocument(); - } - }); + const row = screen.getByRole('row', { + name: /testsketch1/ + }); - it('clicking on dropdown arrow opens sketch options - sketches belong to user', () => { - let component; - act(() => { - component = reduxRender(, { - store, - container + const dropdown = within(row).getByRole('button', { + name: 'Toggle Open/Close Sketch Options' }); - }); - const dropdown = screen.queryAllByTestId( - 'sketch-list-toggle-options-arrow' - ); - if (dropdown.length > 0) { act(() => { - fireEvent.click(dropdown[0]); + fireEvent.click(dropdown); }); - expect(screen.queryByText('Rename')).toBeInTheDocument(); + expect(screen.queryByText('Rename')).not.toBeInTheDocument(); expect(screen.queryByText('Duplicate')).toBeInTheDocument(); expect(screen.queryByText('Download')).toBeInTheDocument(); expect(screen.queryByText('Add to collection')).toBeInTheDocument(); - expect(screen.queryByText('Delete')).toBeInTheDocument(); - } - }); - - it('snapshot testing', () => { - const { asFragment } = reduxRender(, { - store, - container + expect(screen.queryByText('Delete')).not.toBeInTheDocument(); }); - expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 75cf60abbd..1b42fbfb4f 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -119,6 +119,7 @@ For any type of component, you might want to consider testing: expect(screen.queryByText('Does not exist')).not.toBeInTheDocument(); ``` - If it's an integration test, you could consider testing the "happy path" flow. For example, in a login form, you would test how a user might enter their username and password and then enter that information. +- If it's a unit test, you could test possible error cases to ensure that the module being tested is robust and resistant to user or developer error. - Generally, you want to focus your testing on "user input" -> "expected output" instead of making sure the middle steps work as you would expect. This might mean that you don't need to check that the state changes or class-specific methods occur. This is so that if some of the small details in the implementation of the component changes in the future, the tests can remain the same. - more details on testing behavior in the component-specific sections @@ -173,7 +174,7 @@ You can also see it used in the context of a test [in the SketchList.test.jsx fi All tests are directly adjacent to the files that they are testing, as described in the [React docs](https://reactjs.org/docs/faq-structure.html#grouping-by-file-type). For example, if you're testing ``examplefolder/Sketchlist.test.jsx``, the test would be in ``examplefolder/Sketchlist.test.jsx``. This is so that the tests are as close as possible to the files. This also means that any snapshot files will be stored in the same folder, such as ``examplefolder/__snapshots__/Sketchlist.test.jsx.snap`` CASSIE - WHERE DO WE PUT THE INTEGRATION TESTS? -Integration tests can be placed in the ``__tests__`` folder that's at the root of the client folder. They should be called ``ComponentName.test.integration.jsx`` +Integration tests should be adjacent to the components they're testing. They should be called ``ComponentName.integration.test.jsx`` Manual mocks are in ``__mocks__`` folders are adjacent to the modules that they're mocking. @@ -271,13 +272,11 @@ If it doesn't contain ``connect(mapStateToProps, mapDispatchToProps)(ComponentNa *MyComponent.test.jsx* ```js import React from 'react'; -import { unmountComponentAtNode } from 'react-dom'; import { act } from 'react-dom/test-utils'; import { fireEvent, render, screen } from '../../../../test-utils'; import MyComponent from './MyComponent'; describe('', () => { - let container = null; let subjectProps = { t: jest.fn(), @@ -286,21 +285,10 @@ describe('', () => { }; const subject = () => { - render(, { container }); + render(); }; - beforeEach(() => { - // setup a DOM element as a render target - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - // cleanup on exiting - unmountComponentAtNode(container); - container.remove(); - container = null; - //reset the mocks in subjectProps jest.clearAllMocks(); }); @@ -319,7 +307,10 @@ describe('', () => { }); describe('test with a different prop', () => { - subjectProps = {...subjectProps, fontSize: 14} + + beforeAll(() => { + subjectProps = {...subjectProps, fontSize: 14} + }); it("here's that test with a different prop", () => { act(() => { @@ -371,7 +362,7 @@ store = mockStore(initialTestState); ``` 3. Render the component with reduxRender and the store that you just created. ```js -reduxRender(, {store, container}); +reduxRender(, {store}); ``` 4. Test things! You may need to use jest to mock certain functions if the component is making API calls. @@ -383,14 +374,12 @@ All together, it might look something like this. import React from 'react'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { unmountComponentAtNode } from 'react-dom'; import { act } from 'react-dom/test-utils'; import MyReduxComponent from './MyReduxComponent'; import { reduxRender, fireEvent, screen } from '../../../test-utils'; import { initialTestState } from '../../../redux_test_stores/test_store'; describe('', () => { - let container = null; const mockStore = configureStore([thunk]); const store = mockStore(initialTestState); @@ -399,27 +388,10 @@ describe('', () => { }; const subject = () => { - reduxRender(, { - store, - container - }); + reduxRender(, {store}); }; - beforeEach(() => { - // setup a DOM element as a render target - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - // cleanup on exiting - unmountComponentAtNode(container); - container.remove(); - container = null; - - //reset the mocks in subjectProps - jest.clearAllMocks(); - //clear the mock store too store.clearActions(); }); @@ -438,10 +410,11 @@ describe('', () => { }); describe('test with a different prop', () => { - subjectProps = { - sampleprop: "boo!" - } + beforeAll(() => { + subjectProps = {...subjectProps, fontSize: 14} + }); + it("here's that test with a different prop", () => { act(() => { subject(); @@ -457,10 +430,7 @@ Some things to consider testing: - User input results in the expected redux action. ```js act(() => { - component = reduxRender(, { - store, - container - }); + component = reduxRender(, {store}); }); act(() => { fireEvent.click(screen.getByTestId('toggle-direction-createdAt')); @@ -509,6 +479,7 @@ This project uses i18next for internationalization. If you import the render fun ## More Resources - [React Testing Library Cheatsheet](https://testing-library.com/docs/react-testing-library/cheatsheet/) +- [React connected component test](https://www.robinwieruch.de/react-connected-component-test) ## References 1. [Best practices for unit testing with a react redux approach](https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach) From 0cf005e3811aa10dc49fbebe4b0c7df1442e31e8 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Sun, 11 Apr 2021 15:11:13 -0400 Subject: [PATCH 22/37] started writing tests, edited testing.md --- client/__test__/mocks/styleMock.js | 1 + client/index.integration.test.jsx | 26 + .../IDE/components/Editor.unit.test.jsx | 39 + client/redux_test_stores/test_store.js | 105 +- developer_docs/testing.md | 5 +- package-lock.json | 1667 +++++++++++++---- package.json | 6 +- 7 files changed, 1423 insertions(+), 426 deletions(-) create mode 100644 client/__test__/mocks/styleMock.js create mode 100644 client/index.integration.test.jsx create mode 100644 client/modules/IDE/components/Editor.unit.test.jsx diff --git a/client/__test__/mocks/styleMock.js b/client/__test__/mocks/styleMock.js new file mode 100644 index 0000000000..f053ebf797 --- /dev/null +++ b/client/__test__/mocks/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/client/index.integration.test.jsx b/client/index.integration.test.jsx new file mode 100644 index 0000000000..b49da20db2 --- /dev/null +++ b/client/index.integration.test.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; + +jest.mock('./i18n'); + +const server = setupServer( + rest.get('/session', (req, res, ctx) => + res(ctx.json({ greeting: 'hello there' })) + ) +); + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); + +describe('Application root', () => { + it('should render without crashing', () => { + const div = document.createElement('div'); + div.id = 'root'; + document.body.appendChild(div); + // require("./index.jsx"); + // expect(ReactDOM.render).toHaveBeenCalledWith(, div); + }); +}); diff --git a/client/modules/IDE/components/Editor.unit.test.jsx b/client/modules/IDE/components/Editor.unit.test.jsx new file mode 100644 index 0000000000..ca7bb31591 --- /dev/null +++ b/client/modules/IDE/components/Editor.unit.test.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import axios from 'axios'; +import { act } from 'react-dom/test-utils'; +import Editor from './Editor'; +import { + reduxRender, + fireEvent, + screen, + within, + prettyDOM +} from '../../../test-utils'; +import { initialTestState } from '../../../redux_test_stores/test_store'; + +jest.mock('../../../i18n'); + +describe('', () => { + const mockStore = configureStore([thunk]); + const store = mockStore(initialTestState); + + const subjectProps = { provideController: jest.fn() }; + + const subject = () => reduxRender(, { store }); + + beforeEach(() => { + axios.get.mockImplementationOnce((x) => Promise.resolve({ data: 'foo' })); + }); + + afterEach(() => { + store.clearActions(); + }); + + it('renders successfully', () => { + act(() => { + subject(); + }); + }); +}); diff --git a/client/redux_test_stores/test_store.js b/client/redux_test_stores/test_store.js index 46a6d2856d..f9786208c4 100644 --- a/client/redux_test_stores/test_store.js +++ b/client/redux_test_stores/test_store.js @@ -18,9 +18,87 @@ const mockProjects = [ ]; const initialTestState = { - ide: null, - files: [], - preferences: {}, + ide: { + isPlaying: false, + isAccessibleOutputPlaying: false, + modalIsVisible: false, + sidebarIsExpanded: false, + consoleIsExpanded: true, + preferencesIsVisible: false, + projectOptionsVisible: false, + newFolderModalVisible: false, + uploadFileModalVisible: false, + shareModalVisible: false, + shareModalProjectId: 'abcd', + shareModalProjectName: 'My Cute Sketch', + shareModalProjectUsername: 'p5_user', + editorOptionsVisible: false, + keyboardShortcutVisible: false, + unsavedChanges: false, + infiniteLoop: false, + previewIsRefreshing: false, + infiniteLoopMessage: '', + justOpenedProject: false, + previousPath: '/', + errorType: undefined, + runtimeErrorWarningVisible: true, + parentId: undefined + }, + files: [ + { + name: 'root', + id: '606fc1c46045e19ca2ee2648', + _id: '606fc1c46045e19ca2ee2648', + children: [ + '606fc1c46045e19ca2ee2646', + '606fc1c46045e19ca2ee2645', + '606fc1c46045e19ca2ee2647' + ], + fileType: 'folder', + content: '' + }, + { + name: 'sketch.js', + content: + 'function setup() { createCanvas(400, 400); } function draw() { background(220); }', + id: '606fc1c46045e19ca2ee2645', + _id: '606fc1c46045e19ca2ee2645', + isSelectedFile: true, + fileType: 'file', + children: [] + }, + { + name: 'index.html', + content: ` `, + id: '606fc1c46045e19ca2ee2646', + _id: '606fc1c46045e19ca2ee2646', + fileType: 'file', + children: [] + }, + { + name: 'style.css', + content: + 'html, body { margin: 0; padding: 0; } canvas { display: block; } ', + id: '606fc1c46045e19ca2ee2647', + _id: '606fc1c46045e19ca2ee2647', + fileType: 'file', + children: [] + } + ], + preferences: { + fontSize: 18, + autosave: true, + linewrap: true, + lineNumbers: true, + lintWarning: false, + textOutput: false, + gridOutput: false, + soundOutput: false, + theme: 'light', + autorefresh: false, + language: 'en-US', + autocloseBracketsQuotes: true + }, user: { email: 'happydog@example.com', username: 'happydog', @@ -31,7 +109,11 @@ const initialTestState = { totalSize: 0, authenticated: true }, - project: null, + project: { + name: 'Zealous sunflower', + updatedAt: '', + isSaving: false + }, sketches: mockProjects, search: { collectionSearchTerm: '', @@ -41,10 +123,19 @@ const initialTestState = { field: 'createdAt', direction: 'DESCENDING' }, - editorAccessibility: {}, - toast: {}, + editorAccessibility: { + lintMessages: [], + forceDesktop: false + }, + toast: { + isVisible: false, + text: '' + }, console: [], - assets: {}, + assets: { + list: [], + totalSize: 0 + }, loading: false, collections: [] }; diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 1b42fbfb4f..53f8fceb3f 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -66,11 +66,11 @@ In unit tests, you're testing the functionality of a single component and no mor In both of these cases, the component being tested is not merely an implementation detail. Thus, it's important for the unit tests to test the error cases that could occur to ensure that the component is robust. For example, for a user-facing input field that should only take positive numbers, a unit test would want to cover what happens when users enter negative numbers or letters. ### Integration tests -Testing multiple components together. A small example is rendering a parent component in order to test the interactions between children components. Generally, they validate how multiple units of your application work together. Jest, which is what we use, uses jsdom under the hood to emulate common browser APIs with less overhead than automation like a headless browser, and its mocking tools can stub out external API calls. We use integration tests to maximize coverage and to make sure all the pieces play nice together. We want our integration tests to cover the testing of components that don't have unit tests because they're only used in one place and are merely an implementation detail. The integration tests can test the "happy path" flow, while we expect the unit tests to have tested the error cases already. +Testing multiple components together. A small example is rendering a parent component in order to test the interactions between children components. Generally, they validate how multiple units of your application work together. Jest, which is what we use, uses jsdom under the hood to emulate common browser APIs with less overhead than automation like a headless browser, and its mocking tools can stub out external API calls. We use integration tests to maximize coverage and to make sure all the pieces play nice together. We want our integration tests to cover the testing of components that don't have unit tests because they're only used in one place and are merely an implementation detail. The integration tests can test the expected user flows, while we expect the unit tests to have tested the error cases more rigorously. See [this great article on CSS tricks](https://css-tricks.com/react-integration-testing-greater-coverage-fewer-tests/) about integration tests for more information about this. -To reiterate, we use integration tests to test our "happy path" flows and maximize coverage on individual components that are only used once. We use unit tests to test the robustness of user-facing components and reusable components. +To reiterate, we use integration tests to maximize coverage on individual components that are only used once. We use unit tests to test the robustness of user-facing components and reusable components. ### Snapshot testing You can save a snapshot of what the HTML looks like when the component is rendered. @@ -173,7 +173,6 @@ You can also see it used in the context of a test [in the SketchList.test.jsx fi ### Folder structure All tests are directly adjacent to the files that they are testing, as described in the [React docs](https://reactjs.org/docs/faq-structure.html#grouping-by-file-type). For example, if you're testing ``examplefolder/Sketchlist.test.jsx``, the test would be in ``examplefolder/Sketchlist.test.jsx``. This is so that the tests are as close as possible to the files. This also means that any snapshot files will be stored in the same folder, such as ``examplefolder/__snapshots__/Sketchlist.test.jsx.snap`` -CASSIE - WHERE DO WE PUT THE INTEGRATION TESTS? Integration tests should be adjacent to the components they're testing. They should be called ``ComponentName.integration.test.jsx`` Manual mocks are in ``__mocks__`` folders are adjacent to the modules that they're mocking. diff --git a/package-lock.json b/package-lock.json index 71afac118c..4cec156d8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5924,6 +5924,60 @@ "glob-to-regexp": "^0.3.0" } }, + "@mswjs/cookies": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.1.4.tgz", + "integrity": "sha512-gdtmSv21D4wHTnqF4rrZVX6ye7mQ4nRCTIHYnHBr4SkgoXaiqe3sMvUzXm43+H4PnL0EAKvUTxRVSSXz2xebeg==", + "dev": true, + "requires": { + "@types/set-cookie-parser": "^2.4.0", + "set-cookie-parser": "^2.4.6" + } + }, + "@mswjs/interceptors": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.8.1.tgz", + "integrity": "sha512-OI9FYmtURESZG3QDNz4Yt3osy3HY4T3FjlRw+AG4QS1UDdTSZ0tuPFAkp23nGR9ojmbSSj4gSMjf5+R8Oi/qtQ==", + "dev": true, + "requires": { + "@open-draft/until": "^1.0.3", + "debug": "^4.3.0", + "headers-utils": "^3.0.2", + "strict-event-emitter": "^0.2.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "strict-event-emitter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.0.tgz", + "integrity": "sha512-zv7K2egoKwkQkZGEaH8m+i2D0XiKzx5jNsiSul6ja2IYFvil10A59Z9Y7PPAAe5OW53dQUf9CfsHKzjZzKkm1w==", + "dev": true, + "requires": { + "events": "^3.3.0" + } + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -5971,6 +6025,12 @@ } } }, + "@open-draft/until": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", + "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==", + "dev": true + }, "@reach/router": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@reach/router/-/router-1.3.3.tgz", @@ -10096,12 +10156,6 @@ "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", "dev": true }, - "@types/babel-types": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.7.tgz", - "integrity": "sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ==", - "dev": true - }, "@types/babel__core": { "version": "7.1.8", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.8.tgz", @@ -10143,13 +10197,12 @@ "@babel/types": "^7.3.0" } }, - "@types/babylon": { - "version": "6.16.5", - "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz", - "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==", - "dev": true, + "@types/bson": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", + "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", "requires": { - "@types/babel-types": "*" + "@types/node": "*" } }, "@types/color-name": { @@ -10158,6 +10211,12 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==", + "dev": true + }, "@types/eslint": { "version": "7.2.6", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.6.tgz", @@ -10193,6 +10252,16 @@ "integrity": "sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==", "dev": true }, + "@types/inquirer": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.1.tgz", + "integrity": "sha512-osD38QVIfcdgsPCT0V3lD7eH0OFurX71Jft18bZrsVQWVRt6TuxRzlr0GJLrxoHZR2V5ph7/qP8se/dcnI7o0g==", + "dev": true, + "requires": { + "@types/through": "*", + "rxjs": "^6.4.0" + } + }, "@types/is-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.0.tgz", @@ -10355,11 +10424,26 @@ } } }, + "@types/js-levenshtein": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.0.tgz", + "integrity": "sha512-14t0v1ICYRtRVcHASzes0v/O+TIeASb8aD55cWF1PidtInhFWSXcmhzhHqGjUWf9SUq1w70cvd1cWKUULubAfQ==", + "dev": true + }, "@types/json-schema": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==" }, + "@types/mongodb": { + "version": "3.6.12", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.12.tgz", + "integrity": "sha512-49aEzQD5VdHPxyd5dRyQdqEveAg9LanwrH8RQipnMuulwzKmODXIZRp0umtxi1eBUfEusRkoy8AVOMr+kVuFog==", + "requires": { + "@types/bson": "*", + "@types/node": "*" + } + }, "@types/node": { "version": "8.10.48", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.48.tgz", @@ -10447,6 +10531,15 @@ "@types/react": "*" } }, + "@types/set-cookie-parser": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.0.tgz", + "integrity": "sha512-w7BFUq81sy7H/0jN0K5cax8MwRN6NOSURpY4YuO4+mOgoicxCZ33BUYz+gyF/sUf7uDl2We2yGJfppxzEXoAXQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -10474,6 +10567,15 @@ "@types/jest": "*" } }, + "@types/through": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", + "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/uglify-js": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.0.tgz", @@ -10559,6 +10661,167 @@ "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", "dev": true }, + "@vue/compiler-core": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.0.11.tgz", + "integrity": "sha512-6sFj6TBac1y2cWCvYCA8YzHJEbsVkX7zdRs/3yK/n1ilvRqcn983XvpBbnN3v4mZ1UiQycTvOiajJmOgN9EVgw==", + "dev": true, + "requires": { + "@babel/parser": "^7.12.0", + "@babel/types": "^7.12.0", + "@vue/shared": "3.0.11", + "estree-walker": "^2.0.1", + "source-map": "^0.6.1" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@vue/compiler-dom": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.0.11.tgz", + "integrity": "sha512-+3xB50uGeY5Fv9eMKVJs2WSRULfgwaTJsy23OIltKgMrynnIj8hTYY2UL97HCoz78aDw1VDXdrBQ4qepWjnQcw==", + "dev": true, + "requires": { + "@vue/compiler-core": "3.0.11", + "@vue/shared": "3.0.11" + } + }, + "@vue/compiler-sfc": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.0.11.tgz", + "integrity": "sha512-7fNiZuCecRleiyVGUWNa6pn8fB2fnuJU+3AGjbjl7r1P5wBivfl02H4pG+2aJP5gh2u+0wXov1W38tfWOphsXw==", + "dev": true, + "requires": { + "@babel/parser": "^7.13.9", + "@babel/types": "^7.13.0", + "@vue/compiler-core": "3.0.11", + "@vue/compiler-dom": "3.0.11", + "@vue/compiler-ssr": "3.0.11", + "@vue/shared": "3.0.11", + "consolidate": "^0.16.0", + "estree-walker": "^2.0.1", + "hash-sum": "^2.0.0", + "lru-cache": "^5.1.1", + "magic-string": "^0.25.7", + "merge-source-map": "^1.1.0", + "postcss": "^8.1.10", + "postcss-modules": "^4.0.0", + "postcss-selector-parser": "^6.0.4", + "source-map": "^0.6.1" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "consolidate": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.16.0.tgz", + "integrity": "sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==", + "dev": true, + "requires": { + "bluebird": "^3.7.2" + } + }, + "hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@vue/compiler-ssr": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.0.11.tgz", + "integrity": "sha512-66yUGI8SGOpNvOcrQybRIhl2M03PJ+OrDPm78i7tvVln86MHTKhM3ERbALK26F7tXl0RkjX4sZpucCpiKs3MnA==", + "dev": true, + "requires": { + "@vue/compiler-dom": "3.0.11", + "@vue/shared": "3.0.11" + } + }, + "@vue/shared": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.0.11.tgz", + "integrity": "sha512-b+zB8A2so8eCE0JsxjL24J7vdGl8rzPQ09hZNhystm+KqSbKcAej1A+Hbva1rCMmTTqA+hFnUSDc5kouEo0JzA==", + "dev": true + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -10872,17 +11135,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==" }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - } - }, "alphanum-sort": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", @@ -11443,6 +11695,12 @@ } } }, + "assert-never": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", + "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==", + "dev": true + }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -12483,32 +12741,34 @@ } } }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" + "@babel/types": "^7.9.6" }, "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } } } }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, "bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -12575,8 +12835,7 @@ }, "kind-of": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "resolved": "" } } }, @@ -13259,16 +13518,6 @@ "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==", "dev": true }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - } - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -13312,6 +13561,12 @@ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", "dev": true }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, "cheerio": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", @@ -13845,18 +14100,6 @@ "bluebird": "^3.1.1" } }, - "constantinople": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", - "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==", - "dev": true, - "requires": { - "@types/babel-types": "^7.0.0", - "@types/babylon": "^6.16.2", - "babel-types": "^6.26.0", - "babylon": "^6.18.0" - } - }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -15606,12 +15849,6 @@ "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" }, - "de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", - "dev": true - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -15777,9 +16014,9 @@ "dev": true }, "denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" }, "depd": { "version": "1.1.2", @@ -16173,23 +16410,28 @@ } }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" } } }, @@ -17421,6 +17663,12 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", @@ -17820,6 +18068,17 @@ } } }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", @@ -19275,8 +19534,7 @@ }, "ini": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "resolved": "", "optional": true }, "is-fullwidth-code-point": { @@ -19792,6 +20050,15 @@ "globule": "^1.0.0" } }, + "generic-names": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz", + "integrity": "sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0" + } + }, "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", @@ -20069,6 +20336,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" }, + "graphql": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.0.tgz", + "integrity": "sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA==", + "dev": true + }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -20343,6 +20616,12 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "headers-utils": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/headers-utils/-/headers-utils-3.0.2.tgz", + "integrity": "sha512-xAxZkM1dRyGV2Ou5bzMxBPNLoRCjcX+ya7KSWybQD2KwLphxsapUVK6x/02o7f4VU6GPSXch9vNY2+gkU8tYWQ==", + "dev": true + }, "hex-color-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", @@ -20992,6 +21271,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, "icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", @@ -21201,6 +21486,201 @@ "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", "dev": true }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, "internal-slot": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", @@ -21377,6 +21857,15 @@ "rgba-regex": "^1.0.0" } }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -21447,16 +21936,6 @@ "is-primitive": "^2.0.0" } }, - "is-expression": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", - "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", - "dev": true, - "requires": { - "acorn": "~4.0.2", - "object-assign": "^4.0.1" - } - }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -26074,6 +26553,12 @@ } } }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true + }, "js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", @@ -26671,9 +27156,9 @@ "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==" }, "kareem": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", - "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", + "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" }, "keyv": { "version": "3.1.0", @@ -27389,6 +27874,12 @@ "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, "lodash.curry": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", @@ -27569,12 +28060,6 @@ "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==" }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, "loop-protect": { "version": "github:catarak/loop-protect", "from": "loop-protect@github:catarak/loop-protect" @@ -27627,6 +28112,15 @@ "yallist": "^2.1.2" } }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, "mailcomposer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-4.0.1.tgz", @@ -28032,6 +28526,23 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -29321,19 +29832,20 @@ } }, "mongoose": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.2.tgz", - "integrity": "sha512-Sa1qfqBvUfAgsrXpZjbBoIx8PEDUJSKF5Ous8gnBFI7TPiueSgJjg6GRA7A0teU8AB/vd0h8rl1rD5RQNfWhIw==", - "requires": { - "bson": "~1.1.1", - "kareem": "2.3.1", - "mongodb": "3.5.3", + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.12.3.tgz", + "integrity": "sha512-frsSR9yeldaRpSUeTegXCSB0Tu5UGq8sHuHBuEV31Jk3COyxlKFQPL7UsdMhxPUCmk74FpOYSmNwxhWBEqgzQg==", + "requires": { + "@types/mongodb": "^3.5.27", + "bson": "^1.1.4", + "kareem": "2.3.2", + "mongodb": "3.6.5", "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.6.0", - "mquery": "3.2.2", + "mpath": "0.8.3", + "mquery": "3.2.5", "ms": "2.1.2", "regexp-clone": "1.0.0", - "safe-buffer": "5.1.2", + "safe-buffer": "5.2.1", "sift": "7.0.1", "sliced": "1.0.1" }, @@ -29348,17 +29860,17 @@ } }, "bson": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.3.tgz", - "integrity": "sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg==" + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" }, "mongodb": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.3.tgz", - "integrity": "sha512-II7P7A3XUdPiXRgcN96qIoRa1oesM6qLNZkzfPluNZjVkgQk3jnQwOT6/uDk4USRDTTLjNFw2vwfmbRGTA7msg==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.5.tgz", + "integrity": "sha512-mQlYKw1iGbvJJejcPuyTaytq0xxlYbIoVDm2FODR+OHxyEiMR021vc32bTvamgBjCswsD54XIRwhg3yBaWqJjg==", "requires": { - "bl": "^2.2.0", - "bson": "^1.1.1", + "bl": "^2.2.1", + "bson": "^1.1.4", "denque": "^1.4.1", "require_optional": "^1.0.1", "safe-buffer": "^5.1.2", @@ -29369,6 +29881,11 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -29391,14 +29908,14 @@ } }, "mpath": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.6.0.tgz", - "integrity": "sha512-i75qh79MJ5Xo/sbhxrDrPSEG0H/mr1kcZXJ8dH6URU5jD/knFxCVqVC/gVSW7GIXL/9hHWlT9haLbCXWOll3qw==" + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz", + "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==" }, "mquery": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", - "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", + "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", "requires": { "bluebird": "3.5.1", "debug": "3.1.0", @@ -29432,6 +29949,308 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, + "msw": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/msw/-/msw-0.28.1.tgz", + "integrity": "sha512-vkq1Oh7CZlQjdrVZdDJ0L5hPnUakwRKYXWRtTLh9uaD4sCzckEStSgqAgcvM7nxb3ViakcRu0BXKU6JQM1KxbA==", + "dev": true, + "requires": { + "@mswjs/cookies": "^0.1.4", + "@mswjs/interceptors": "^0.8.0", + "@open-draft/until": "^1.0.3", + "@types/cookie": "^0.4.0", + "@types/inquirer": "^7.3.1", + "@types/js-levenshtein": "^1.1.0", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cookie": "^0.4.1", + "graphql": "^15.4.0", + "headers-utils": "^3.0.2", + "inquirer": "^7.3.3", + "js-levenshtein": "^1.1.6", + "node-fetch": "^2.6.1", + "node-match-path": "^0.6.1", + "statuses": "^2.0.0", + "strict-event-emitter": "^0.1.0", + "yargs": "^16.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", + "dev": true + } + } + }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -29717,6 +30536,12 @@ } } }, + "node-match-path": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/node-match-path/-/node-match-path-0.6.2.tgz", + "integrity": "sha512-2VYsUKiovaCZDq1t/3kEqh09743H91WE6B3RzSdjsKh+S/a5z+LQoujMI1JI/RYXqNKFvoqMfye1H0g3Dg9u+g==", + "dev": true + }, "node-modules-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", @@ -31980,6 +32805,22 @@ } } }, + "postcss-modules": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.0.0.tgz", + "integrity": "sha512-ghS/ovDzDqARm4Zj6L2ntadjyQMoyJmi0JkLlYtH2QFLrvNlxH5OAVRPWPeKilB0pY7SbuhO173KOWkPAxRJcw==", + "dev": true, + "requires": { + "generic-names": "^2.0.1", + "icss-replace-symbols": "^1.1.0", + "lodash.camelcase": "^4.3.0", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "string-hash": "^1.1.1" + } + }, "postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", @@ -32785,6 +33626,18 @@ } } }, + "postcss-selector-parser": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" + } + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", @@ -32957,18 +33810,18 @@ } }, "prismjs": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.20.0.tgz", - "integrity": "sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz", + "integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==", "dev": true, "requires": { "clipboard": "^2.0.0" }, "dependencies": { "clipboard": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz", - "integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz", + "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==", "dev": true, "optional": true, "requires": { @@ -33266,180 +34119,6 @@ } } }, - "pug": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.4.tgz", - "integrity": "sha512-XhoaDlvi6NIzL49nu094R2NA6P37ijtgMDuWE+ofekDChvfKnzFal60bhSdiy8y2PBO6fmz3oMEIcfpBVRUdvw==", - "dev": true, - "requires": { - "pug-code-gen": "^2.0.2", - "pug-filters": "^3.1.1", - "pug-lexer": "^4.1.0", - "pug-linker": "^3.0.6", - "pug-load": "^2.0.12", - "pug-parser": "^5.0.1", - "pug-runtime": "^2.0.5", - "pug-strip-comments": "^1.0.4" - } - }, - "pug-attrs": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.4.tgz", - "integrity": "sha512-TaZ4Z2TWUPDJcV3wjU3RtUXMrd3kM4Wzjbe3EWnSsZPsJ3LDI0F3yCnf2/W7PPFF+edUFQ0HgDL1IoxSz5K8EQ==", - "dev": true, - "requires": { - "constantinople": "^3.0.1", - "js-stringify": "^1.0.1", - "pug-runtime": "^2.0.5" - } - }, - "pug-code-gen": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.2.tgz", - "integrity": "sha512-kROFWv/AHx/9CRgoGJeRSm+4mLWchbgpRzTEn8XCiwwOy6Vh0gAClS8Vh5TEJ9DBjaP8wCjS3J6HKsEsYdvaCw==", - "dev": true, - "requires": { - "constantinople": "^3.1.2", - "doctypes": "^1.1.0", - "js-stringify": "^1.0.1", - "pug-attrs": "^2.0.4", - "pug-error": "^1.3.3", - "pug-runtime": "^2.0.5", - "void-elements": "^2.0.1", - "with": "^5.0.0" - } - }, - "pug-error": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.3.tgz", - "integrity": "sha512-qE3YhESP2mRAWMFJgKdtT5D7ckThRScXRwkfo+Erqga7dyJdY3ZquspprMCj/9sJ2ijm5hXFWQE/A3l4poMWiQ==", - "dev": true - }, - "pug-filters": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.1.tgz", - "integrity": "sha512-lFfjNyGEyVWC4BwX0WyvkoWLapI5xHSM3xZJFUhx4JM4XyyRdO8Aucc6pCygnqV2uSgJFaJWW3Ft1wCWSoQkQg==", - "dev": true, - "requires": { - "clean-css": "^4.1.11", - "constantinople": "^3.0.1", - "jstransformer": "1.0.0", - "pug-error": "^1.3.3", - "pug-walk": "^1.1.8", - "resolve": "^1.1.6", - "uglify-js": "^2.6.1" - }, - "dependencies": { - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" - } - }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } - } - } - }, - "pug-lexer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.1.0.tgz", - "integrity": "sha512-i55yzEBtjm0mlplW4LoANq7k3S8gDdfC6+LThGEvsK4FuobcKfDAwt6V4jKPH9RtiE3a2Akfg5UpafZ1OksaPA==", - "dev": true, - "requires": { - "character-parser": "^2.1.1", - "is-expression": "^3.0.0", - "pug-error": "^1.3.3" - } - }, - "pug-linker": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.6.tgz", - "integrity": "sha512-bagfuHttfQOpANGy1Y6NJ+0mNb7dD2MswFG2ZKj22s8g0wVsojpRlqveEQHmgXXcfROB2RT6oqbPYr9EN2ZWzg==", - "dev": true, - "requires": { - "pug-error": "^1.3.3", - "pug-walk": "^1.1.8" - } - }, - "pug-load": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.12.tgz", - "integrity": "sha512-UqpgGpyyXRYgJs/X60sE6SIf8UBsmcHYKNaOccyVLEuT6OPBIMo6xMPhoJnqtB3Q3BbO4Z3Bjz5qDsUWh4rXsg==", - "dev": true, - "requires": { - "object-assign": "^4.1.0", - "pug-walk": "^1.1.8" - } - }, - "pug-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.1.tgz", - "integrity": "sha512-nGHqK+w07p5/PsPIyzkTQfzlYfuqoiGjaoqHv1LjOv2ZLXmGX1O+4Vcvps+P4LhxZ3drYSljjq4b+Naid126wA==", - "dev": true, - "requires": { - "pug-error": "^1.3.3", - "token-stream": "0.0.1" - } - }, - "pug-runtime": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.5.tgz", - "integrity": "sha512-P+rXKn9un4fQY77wtpcuFyvFaBww7/91f3jHa154qU26qFAnOe6SW1CbIDcxiG5lLK9HazYrMCCuDvNgDQNptw==", - "dev": true - }, - "pug-strip-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.4.tgz", - "integrity": "sha512-i5j/9CS4yFhSxHp5iKPHwigaig/VV9g+FgReLJWWHEHbvKsbqL0oP/K5ubuLco6Wu3Kan5p7u7qk8A4oLLh6vw==", - "dev": true, - "requires": { - "pug-error": "^1.3.3" - } - }, - "pug-walk": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.8.tgz", - "integrity": "sha512-GMu3M5nUL3fju4/egXwZO0XLi6fW/K3T3VTgFQ14GxNi8btlxgT5qZL//JwZFm/2Fa64J/PNS8AZeys3wiMkVA==", - "dev": true - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -34768,32 +35447,6 @@ "util.promisify": "^1.0.0" } }, - "recast": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.17.6.tgz", - "integrity": "sha512-yoQRMRrK1lszNtbkGyM4kN45AwylV5hMiuEveUBlxytUViWevjvX6w+tzJt1LH4cfUhWt4NZvy3ThIhu6+m5wQ==", - "dev": true, - "requires": { - "ast-types": "0.12.4", - "esprima": "~4.0.0", - "private": "^0.1.8", - "source-map": "~0.6.1" - }, - "dependencies": { - "ast-types": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.12.4.tgz", - "integrity": "sha512-ky/YVYCbtVAS8TdMIaTiPFHwEpRB5z1hctepJplTr3UW5q8TDrpIMCILyk8pmLxGtn2KCtC/lSn7zOsaI7nzDw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -35732,15 +36385,6 @@ "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", "dev": true }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "requires": { - "align-text": "^0.1.1" - } - }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -36535,6 +37179,12 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "set-cookie-parser": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", + "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==", + "dev": true + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -36958,8 +37608,7 @@ }, "kind-of": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "resolved": "" } } }, @@ -37082,6 +37731,12 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, "space-separated-tokens": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", @@ -37289,6 +37944,12 @@ "resolved": "https://registry.npmjs.org/streamsink/-/streamsink-1.2.0.tgz", "integrity": "sha1-76/unx4i01ke1949yqlcP1559zw=" }, + "strict-event-emitter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.1.0.tgz", + "integrity": "sha512-8hSYfU+WKLdNcHVXJ0VxRXiPESalzRe7w1l8dg9+/22Ry+iZQUoQuoJ27R30GMD1TiyYINWsIEGY05WrskhSKw==", + "dev": true + }, "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", @@ -37300,6 +37961,12 @@ "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", "dev": true }, + "string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=", + "dev": true + }, "string-length": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", @@ -38760,12 +39427,6 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, - "token-stream": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", - "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=", - "dev": true - }, "touch": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/touch/-/touch-2.0.2.tgz", @@ -38959,13 +39620,6 @@ } } }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, "uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -39639,44 +40293,254 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" }, "vue-docgen-api": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/vue-docgen-api/-/vue-docgen-api-4.20.0.tgz", - "integrity": "sha512-s4UzWEz3srwLNbCNl0WaAT6Ry+k2huGRZloh+oA5OymA/R/MDsd/H1rcwitAXSR4uWwKuMoghRYDUNTf6rwM3g==", + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/vue-docgen-api/-/vue-docgen-api-4.37.0.tgz", + "integrity": "sha512-gboztqCgroNFExbkaOKpd9ihQErSToa28dUYxuMepXrfCOscJ+adhP8wn69TGlL6x44xdTZenm0+x5sZ39/q/Q==", "dev": true, "requires": { - "@babel/parser": "^7.6.0", - "@babel/types": "^7.6.0", - "ast-types": "^0.12.2", + "@babel/parser": "^7.13.12", + "@babel/types": "^7.13.12", + "@vue/compiler-dom": "^3.0.7", + "@vue/compiler-sfc": "^3.0.7", + "ast-types": "0.13.3", "hash-sum": "^1.0.2", "lru-cache": "^4.1.5", - "pug": "^2.0.3", - "recast": "^0.17.3", + "pug": "^3.0.2", + "recast": "0.19.1", "ts-map": "^1.0.3", - "vue-template-compiler": "^2.0.0" + "vue-inbrowser-compiler-utils": "^4.37.0" }, "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, "@babel/parser": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", - "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", "dev": true }, "@babel/types": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", - "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.9.5", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, "ast-types": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.12.4.tgz", - "integrity": "sha512-ky/YVYCbtVAS8TdMIaTiPFHwEpRB5z1hctepJplTr3UW5q8TDrpIMCILyk8pmLxGtn2KCtC/lSn7zOsaI7nzDw==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.3.tgz", + "integrity": "sha512-XTZ7xGML849LkQP86sWdQzfhwbt3YwIO6MqbX9mUNYY98VKaaVZP7YNNm70IpwecbkkxmfC5IYAzOQ/2p29zRA==", + "dev": true + }, + "constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "dev": true, + "requires": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "pug": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz", + "integrity": "sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==", + "dev": true, + "requires": { + "pug-code-gen": "^3.0.2", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "dev": true, + "requires": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "pug-code-gen": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.2.tgz", + "integrity": "sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==", + "dev": true, + "requires": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.0.0", + "pug-runtime": "^3.0.0", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "pug-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", + "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==", + "dev": true + }, + "pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "dev": true, + "requires": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "dev": true, + "requires": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "dev": true, + "requires": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "dev": true, + "requires": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "dev": true + }, + "pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "dev": true, + "requires": { + "pug-error": "^2.0.0" + } + }, + "pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "dev": true + }, + "recast": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.19.1.tgz", + "integrity": "sha512-8FCjrBxjeEU2O6I+2hyHyBFH1siJbMBLwIRvVr1T3FD2cL754sOaJDsJ/8h3xYltasbJ8jqWRIhMuDGBSiSbjw==", + "dev": true, + "requires": { + "ast-types": "0.13.3", + "esprima": "~4.0.0", + "private": "^0.1.8", + "source-map": "~0.6.1" + } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=", + "dev": true + }, + "void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=", "dev": true + }, + "with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "dev": true, + "requires": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + } } } }, @@ -39692,14 +40556,13 @@ "querystring": "^0.2.0" } }, - "vue-template-compiler": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz", - "integrity": "sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==", + "vue-inbrowser-compiler-utils": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/vue-inbrowser-compiler-utils/-/vue-inbrowser-compiler-utils-4.37.0.tgz", + "integrity": "sha512-Zm2jpBSNnsuOOny6717MzEjZz6Z28/W+nFeYT4EJR0b/T2GLBGYOoSvoTvczxfc430gpQfzY6hYwOJMs68+qOQ==", "dev": true, "requires": { - "de-indent": "^1.0.2", - "he": "^1.1.0" + "camelcase": "^5.3.1" } }, "w3c-hr-time": { @@ -41191,30 +42054,6 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true - }, - "with": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", - "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=", - "dev": true, - "requires": { - "acorn": "^3.1.0", - "acorn-globals": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, "with-callback": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/with-callback/-/with-callback-1.0.2.tgz", @@ -41361,9 +42200,9 @@ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yallist": { "version": "2.1.2", diff --git a/package.json b/package.json index 044ce25f7a..4277b965a8 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,8 @@ "/client/jest.setup.js" ], "moduleNameMapper": { - "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/client/__test__/mocks/fileMock.js" + "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)(|\\?byContent|\\?byUrl)$": "/client/__test__/mocks/fileMock.js", + "\\.(css|less|scss)$": "/client/__test__/mocks/styleMock.js" }, "testMatch": [ "/client/**/*.test.(js|jsx)" @@ -115,6 +116,7 @@ "jest": "^26.0.1", "lint-staged": "^10.1.3", "mini-css-extract-plugin": "^1.3.4", + "msw": "^0.28.1", "node-sass": "^5.0.0", "nodemon": "^2.0.7", "optimize-css-assets-webpack-plugin": "^5.0.3", @@ -185,7 +187,7 @@ "mime-types": "^2.1.26", "mjml": "^3.3.2", "mockingoose": "^2.13.2", - "mongoose": "^5.9.2", + "mongoose": "^5.12.3", "node-uuid": "^1.4.7", "nodemailer": "^2.6.4", "nodemailer-mailgun-transport": "^1.4.0", From 9aa2350b55ac09504d8e2916389a9fd8bb2e0f59 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Mon, 12 Apr 2021 02:06:02 -0400 Subject: [PATCH 23/37] integration test --- client/__mocks__/axios.js | 6 ---- client/index.integration.test.jsx | 19 ++++++++---- .../IDE/components/SketchList.test.jsx | 28 +++++++++-------- .../test_server_responses.js | 30 +++++++++++++++++++ 4 files changed, 59 insertions(+), 24 deletions(-) delete mode 100644 client/__mocks__/axios.js create mode 100644 client/redux_test_stores/test_server_responses.js diff --git a/client/__mocks__/axios.js b/client/__mocks__/axios.js deleted file mode 100644 index 1448466e75..0000000000 --- a/client/__mocks__/axios.js +++ /dev/null @@ -1,6 +0,0 @@ -const mockAxios = jest.genMockFromModule('axios'); - -// this is the key to fix the axios.create() undefined error! -mockAxios.create = jest.fn(() => mockAxios); - -export default mockAxios; diff --git a/client/index.integration.test.jsx b/client/index.integration.test.jsx index b49da20db2..0af7497534 100644 --- a/client/index.integration.test.jsx +++ b/client/index.integration.test.jsx @@ -1,14 +1,15 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; +import { act, render } from '@testing-library/react'; +import { userResponse } from './redux_test_stores/test_server_responses'; jest.mock('./i18n'); const server = setupServer( - rest.get('/session', (req, res, ctx) => - res(ctx.json({ greeting: 'hello there' })) - ) + rest.get('/session', (req, res, ctx) => { + console.log('called'); + return res(ctx.json(userResponse)); + }) ); beforeAll(() => server.listen()); @@ -16,11 +17,17 @@ afterEach(() => server.resetHandlers()); afterAll(() => server.close()); describe('Application root', () => { + // eslint-disable-next-line global-require + const subject = () => require('./index'); + it('should render without crashing', () => { const div = document.createElement('div'); div.id = 'root'; document.body.appendChild(div); - // require("./index.jsx"); + act(() => { + subject(); + }); + // expect(ReactDOM.render).toHaveBeenCalledWith(, div); }); }); diff --git a/client/modules/IDE/components/SketchList.test.jsx b/client/modules/IDE/components/SketchList.test.jsx index 1dd5016ac4..8afff9e44f 100644 --- a/client/modules/IDE/components/SketchList.test.jsx +++ b/client/modules/IDE/components/SketchList.test.jsx @@ -1,20 +1,28 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import axios from 'axios'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; import { act } from 'react-dom/test-utils'; import SketchList from './SketchList'; -import { - reduxRender, - fireEvent, - screen, - within, - prettyDOM -} from '../../../test-utils'; +import { reduxRender, fireEvent, screen, within } from '../../../test-utils'; import { initialTestState } from '../../../redux_test_stores/test_store'; jest.mock('../../../i18n'); +const server = setupServer( + rest.get(`/${initialTestState.user.username}/projects`, (req, res, ctx) => + // it just needs to return something so it doesn't throw an error + // Sketchlist tries to grab projects on creation but for the unit test + // we just feed those in as part of the initial state + res(ctx.json({ data: 'foo' })) + ) +); + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); + describe('', () => { const mockStore = configureStore([thunk]); const store = mockStore(initialTestState); @@ -24,10 +32,6 @@ describe('', () => { const subject = () => reduxRender(, { store }); - beforeEach(() => { - axios.get.mockImplementationOnce((x) => Promise.resolve({ data: 'foo' })); - }); - afterEach(() => { store.clearActions(); }); diff --git a/client/redux_test_stores/test_server_responses.js b/client/redux_test_stores/test_server_responses.js new file mode 100644 index 0000000000..f56ab8b32c --- /dev/null +++ b/client/redux_test_stores/test_server_responses.js @@ -0,0 +1,30 @@ +// test data to use with msw + +const userResponse = { + email: 'happydog@example.com', + username: 'happydog', + preferences: { + fontSize: 18, + lineNumbers: true, + indentationAmount: 2, + isTabIndent: false, + autosave: true, + linewrap: true, + lintWarning: false, + textOutput: false, + gridOutput: false, + soundOutput: false, + theme: 'light', + autorefresh: false, + language: 'en-US', + autocloseBracketsQuotes: true + }, + apiKeys: [], + verified: 'verified', + id: 'testid', + totalSize: 0, + github: 'githubusername', + google: 'googleusername' +}; + +export default { userResponse }; From 0ed1c1608a527616cbd0769051009f40dd68ec2a Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Mon, 12 Apr 2021 13:31:50 -0400 Subject: [PATCH 24/37] integration test ONE IS FAILING --- client/index.integration.test.jsx | 171 +++++++++++++++++++++++++++--- 1 file changed, 155 insertions(+), 16 deletions(-) diff --git a/client/index.integration.test.jsx b/client/index.integration.test.jsx index 0af7497534..207b2b7895 100644 --- a/client/index.integration.test.jsx +++ b/client/index.integration.test.jsx @@ -1,33 +1,172 @@ import { setupServer } from 'msw/node'; import { rest } from 'msw'; -import { act, render } from '@testing-library/react'; -import { userResponse } from './redux_test_stores/test_server_responses'; +import { + act, + fireEvent, + prettyDOM, + render, + screen, + within +} from '@testing-library/react'; +import userResponse from './redux_test_stores/test_server_responses'; +// need to mock this file or it'll throw ERRCONNECTED jest.mock('./i18n'); +// setup for the msw fake server const server = setupServer( - rest.get('/session', (req, res, ctx) => { - console.log('called'); - return res(ctx.json(userResponse)); - }) + rest.get('/session', (req, res, ctx) => + res(ctx.json(userResponse.userResponse)) + ) ); -beforeAll(() => server.listen()); +beforeAll(() => + server.listen({ + onUnhandledRequest: 'warn' + }) +); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); -describe('Application root', () => { - // eslint-disable-next-line global-require - const subject = () => require('./index'); +// https://stackoverflow.com/questions/57311971/error-not-implemented-window-scrollto-how-do-we-remove-this-error-from-jest-t +const noop = () => {}; +Object.defineProperty(window, 'focus', { value: noop, writable: true }); + +// https://github.com/jsdom/jsdom/issues/3002 +document.createRange = () => { + const range = new Range(); + + range.getBoundingClientRect = jest.fn(); + + range.getClientRects = () => ({ + item: () => null, + length: 0, + [Symbol.iterator]: jest.fn() + }); + + return range; +}; + +describe('index.jsx integration', () => { + let container = null; + + // we only run the setup once because require only works once + beforeAll(() => { + // setup a DOM element as a render target + container = document.createElement('div'); + container.id = 'root'; + document.body.appendChild(container); + // eslint-disable-next-line global-require + require('./index'); + }); + + it('navbar items and the dropdowns in the navbar exist', () => { + const navigation = screen.getByRole('navigation'); + expect(navigation).toBeInTheDocument(); + + const fileButton = within(navigation).getByRole('button', { + name: /^file$/i + }); + expect(fileButton).toBeInTheDocument(); + + const newFileButton = within(navigation).getByRole('button', { + name: /^new$/i + }); + expect(newFileButton).toBeInTheDocument(); + + // save file button not shown? + + // const exampleFileButton = within(navigation).getByRole('link', {name: /^examples$/i}); + // expect(exampleFileButton).toBeInTheDocument(); + + const editButton = within(navigation).getByRole('button', { + name: /^edit$/i + }); + expect(editButton).toBeInTheDocument(); - it('should render without crashing', () => { - const div = document.createElement('div'); - div.id = 'root'; - document.body.appendChild(div); + const sketchButton = within(navigation).getByRole('button', { + name: /^sketch$/i + }); + expect(sketchButton).toBeInTheDocument(); + + const helpButton = within(navigation).getByRole('button', { + name: /^help$/i + }); + expect(helpButton).toBeInTheDocument(); + }); + + it('toolbar elements exist', () => { + const playButton = screen.getByRole('button', { + name: /play only visual sketch/i + }); + expect(playButton).toBeInTheDocument(); + + const stopButton = screen.getByRole('button', { + name: /stop sketch/i + }); + expect(stopButton).toBeInTheDocument(); + + const editSketchNameButton = screen.getByRole('button', { + name: /edit sketch name/i + }); + expect(editSketchNameButton).toBeInTheDocument(); + + expect(screen.getByText('Auto-refresh')).toBeInTheDocument(); + }); + + it('preview exists', () => { + expect( + screen.getByRole('heading', { name: /preview/i }) + ).toBeInTheDocument(); + const preview = screen.getByRole('main', { name: /sketch output/i }); + expect(preview).toBeInTheDocument(); + }); + + it('code editor exists', () => { + const codeeditor = screen.getByRole('article'); + expect(codeeditor).toBeInTheDocument(); + }); + + it('sidebar exists', () => { + expect(screen.getByText('Sketch Files')).toBeInTheDocument(); + }); + + it('clicking on play updates the preview iframe with a srcdoc, stop clears it', () => { + const playButton = screen.getByRole('button', { + name: /play only visual sketch/i + }); + const preview = screen.getByRole('main', { name: /sketch output/i }); + expect(preview.getAttribute('srcdoc')).toBeFalsy(); + act(() => { + fireEvent.click(playButton); + }); + + expect(preview.getAttribute('srcdoc')).toBeTruthy(); + + const stopButton = screen.getByRole('button', { + name: /stop sketch/i + }); act(() => { - subject(); + fireEvent.click(stopButton); + }); + expect(preview.getAttribute('srcdoc')).toMatch(/(^|")\s*($|")/); + }); + + it('clicking on a file in the sidebar changes the text content of the codemirror editor', () => { + const indexHTMLButton = screen.getByRole('button', { + name: 'index.html' }); - // expect(ReactDOM.render).toHaveBeenCalledWith(, div); + // expect(screen.getByText("createCanvas")).toBeInTheDocument(); + const codeeditor = screen.getByRole('article'); + console.log(prettyDOM(codeeditor)); + expect(indexHTMLButton).toBeInTheDocument(); + + const startingeditorcode = codeeditor.textContent; + console.log(startingeditorcode); + act(() => { + fireEvent.click(indexHTMLButton); + }); + expect(startingeditorcode).not.toBe(codeeditor.textContent); }); }); From 00b02d87abee40a3fcea692a07f09e3a518baf38 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Mon, 12 Apr 2021 16:48:29 -0400 Subject: [PATCH 25/37] Rename files, edit testing.md --- client/index.integration.test.jsx | 3 +- client/modules/App/App.test.jsx | 2 +- .../modules/IDE/actions/__mocks__/projects.js | 2 +- .../IDE/actions/__tests__/projects.test.js | 2 +- .../IDE/components/Editor.unit.test.jsx | 2 +- .../IDE/components/SketchList.test.jsx | 2 +- .../testReduxStore.js} | 0 .../testServerResponses.js} | 0 developer_docs/testing.md | 111 ++++++++++-------- 9 files changed, 68 insertions(+), 56 deletions(-) rename client/{redux_test_stores/test_store.js => testData/testReduxStore.js} (100%) rename client/{redux_test_stores/test_server_responses.js => testData/testServerResponses.js} (100%) diff --git a/client/index.integration.test.jsx b/client/index.integration.test.jsx index 207b2b7895..9f66422dfb 100644 --- a/client/index.integration.test.jsx +++ b/client/index.integration.test.jsx @@ -4,11 +4,10 @@ import { act, fireEvent, prettyDOM, - render, screen, within } from '@testing-library/react'; -import userResponse from './redux_test_stores/test_server_responses'; +import userResponse from './testData/testServerResponses'; // need to mock this file or it'll throw ERRCONNECTED jest.mock('./i18n'); diff --git a/client/modules/App/App.test.jsx b/client/modules/App/App.test.jsx index 92b1618755..7d308830ba 100644 --- a/client/modules/App/App.test.jsx +++ b/client/modules/App/App.test.jsx @@ -12,7 +12,7 @@ import { within, prettyDOM } from '../../test-utils'; -import { initialTestState } from '../../redux_test_stores/test_store'; +import { initialTestState } from '../../testData/testReduxStore'; jest.mock('../../i18n'); diff --git a/client/modules/IDE/actions/__mocks__/projects.js b/client/modules/IDE/actions/__mocks__/projects.js index 6ec855639a..8cb34215a7 100644 --- a/client/modules/IDE/actions/__mocks__/projects.js +++ b/client/modules/IDE/actions/__mocks__/projects.js @@ -1,6 +1,6 @@ import * as ActionTypes from '../../../../constants'; import { startLoader, stopLoader } from '../loader'; -import { mockProjects } from '../../../../redux_test_stores/test_store'; +import { mockProjects } from '../../../../testData/testReduxStore'; // eslint-disable-next-line export function getProjects(username) { diff --git a/client/modules/IDE/actions/__tests__/projects.test.js b/client/modules/IDE/actions/__tests__/projects.test.js index 1f29d92b56..a522b30385 100644 --- a/client/modules/IDE/actions/__tests__/projects.test.js +++ b/client/modules/IDE/actions/__tests__/projects.test.js @@ -8,7 +8,7 @@ import * as ActionTypes from '../../../../constants'; import { initialTestState, mockProjects -} from '../../../../redux_test_stores/test_store'; +} from '../../../../testData/testReduxStore'; const mockStore = configureStore([thunk]); diff --git a/client/modules/IDE/components/Editor.unit.test.jsx b/client/modules/IDE/components/Editor.unit.test.jsx index ca7bb31591..e660f86104 100644 --- a/client/modules/IDE/components/Editor.unit.test.jsx +++ b/client/modules/IDE/components/Editor.unit.test.jsx @@ -11,7 +11,7 @@ import { within, prettyDOM } from '../../../test-utils'; -import { initialTestState } from '../../../redux_test_stores/test_store'; +import { initialTestState } from '../../../testData/testReduxStore'; jest.mock('../../../i18n'); diff --git a/client/modules/IDE/components/SketchList.test.jsx b/client/modules/IDE/components/SketchList.test.jsx index 8afff9e44f..162d12bcc1 100644 --- a/client/modules/IDE/components/SketchList.test.jsx +++ b/client/modules/IDE/components/SketchList.test.jsx @@ -6,7 +6,7 @@ import { rest } from 'msw'; import { act } from 'react-dom/test-utils'; import SketchList from './SketchList'; import { reduxRender, fireEvent, screen, within } from '../../../test-utils'; -import { initialTestState } from '../../../redux_test_stores/test_store'; +import { initialTestState } from '../../../testData/testReduxStore'; jest.mock('../../../i18n'); diff --git a/client/redux_test_stores/test_store.js b/client/testData/testReduxStore.js similarity index 100% rename from client/redux_test_stores/test_store.js rename to client/testData/testReduxStore.js diff --git a/client/redux_test_stores/test_server_responses.js b/client/testData/testServerResponses.js similarity index 100% rename from client/redux_test_stores/test_server_responses.js rename to client/testData/testServerResponses.js diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 53f8fceb3f..48c6a02135 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -1,4 +1,6 @@ # Testing +This guide lists out the tools and methods that we use to test the p5 editor project. It focuses mainly on the client side project, but some of it applies to the server tests too. + For an initial basic overview of testing for React apps, [you can read what the React developers have to say about it](https://reactjs.org/docs/testing.html). We use both unit tests and integration tests. We are testing React components by rendering the component trees in a simplified test environment and making assertions on what gets rendered and what functions get called. @@ -12,6 +14,7 @@ Many files still don't have tests, so **if you're looking to get started as a co - [Why write tests](#Why-write-tests) - [When to run tests](#When-to-run-tests) - [Writing a test](#Writing-a-test) +- [What to test](#What-to-test) - [Files to be aware of](#Files-to-be-aware-of) - [Testing plain components](#Testing-plain-components) - [Testing Redux](#Testing-Redux) @@ -27,6 +30,7 @@ Many files still don't have tests, so **if you're looking to get started as a co 1. [Jest](https://jestjs.io/) 2. [react-testing-library](https://testing-library.com/docs/react-testing-library/intro/) 3. [redux-mock-store](https://github.com/reduxjs/redux-mock-store) +4. [msw](https://github.com/mswjs/msw) ## Useful testing commands Run the whole test suite @@ -73,7 +77,7 @@ See [this great article on CSS tricks](https://css-tricks.com/react-integration- To reiterate, we use integration tests to maximize coverage on individual components that are only used once. We use unit tests to test the robustness of user-facing components and reusable components. ### Snapshot testing -You can save a snapshot of what the HTML looks like when the component is rendered. +You can save a snapshot of what the HTML looks like when the component is rendered. It doesn't hurt to add them to your tests, but they can be brittle. ## Why write tests - Good place to start if you're learning the codebase. @@ -110,25 +114,6 @@ You might still want to write tests for non-component or non-redux files, such a ### Querying for elements Read about the recommended order of priority for queries in [the testing library docs](https://testing-library.com/docs/guide-which-query/#priority). We recommend using roles and text, or labels. You can use this [handy extension](https://chrome.google.com/webstore/detail/testing-playground/hejbmebodbijjdhflfknehhcgaklhano/related) to do this. - -### What to test -For any type of component, you might want to consider testing: -- The text or divs that you expect to be on the page are actually there. You can use [Queries](https://testing-library.com/docs/queries/about/) for this. Assertions should make use of the toBeInTheDocument() matcher when asserting that an element exists: - ``` - expect(screen.getByText('Hello World')).toBeInTheDocument(); - expect(screen.queryByText('Does not exist')).not.toBeInTheDocument(); - ``` -- If it's an integration test, you could consider testing the "happy path" flow. For example, in a login form, you would test how a user might enter their username and password and then enter that information. -- If it's a unit test, you could test possible error cases to ensure that the module being tested is robust and resistant to user or developer error. -- Generally, you want to focus your testing on "user input" -> "expected output" instead of making sure the middle steps work as you would expect. This might mean that you don't need to check that the state changes or class-specific methods occur. This is so that if some of the small details in the implementation of the component changes in the future, the tests can remain the same. -- more details on testing behavior in the component-specific sections - ->Only test the behaviors you know you need to care about. For example, if the desired behavior of a particular edge case doesn't truly matter yet or isn't fully understood, don't write a test for it yet. Doing so would restrict the freedom to refactor the implementation. Additionally, it will send the signal to future readers that this behavior is actually critical, when it very well might not be. [[3]](#References) - -**Don't test unreachable edge cases:** You would have to add code to your original implementation to guard against these cases. The future proofing and the added cost to the codebase "is generally not worth their preceived potential benefits" [[3]](#References) - -**Make sure your tests are sufficient:** You want to make sure your test actually specifies all the behaviors you want to ensure the code exhibits. For example, testing that ``1+1 > 0`` would be correct, but insufficient. [[3]](#References) - ### File structure Each test should have a top-level ``describe`` block to group related blocks together, with the name of the component under test. @@ -153,27 +138,40 @@ describe('', () => { ### Troubleshooting -1. Check if the component makes any API calls. If it's using axios, jest should already be set up to replace the axios library with a mocked version; however, you may want to [mock](https://jestjs.io/docs/mock-function-api#mockfnmockimplementationoncefn) the axios.get() function with your own version so that GET calls "return" whatever data makes sense for that test. +1. If you are having network errors like ERRCONNECTED or something like ``Cannot read property 'then' of undefined`` as a result of an ``apiClient`` function, then please view the [How to handle API calls in tests](#How-to-handle-API-calls-in-tests) section. +2. In some cases, window functions are not defined because the client tests run in the context of ``jsdom`` and not a real browser. In this case, you want to define the function as a no op. [See this post for more information.](https://stackoverflow.com/questions/57311971/error-not-implemented-window-scrollto-how-do-we-remove-this-error-from-jest-t) ```js - axios.get.mockImplementationOnce( - (x) => Promise.resolve({ data: 'foo' }) - ); + const noop = () => {}; + Object.defineProperty(window, 'focus', { value: noop, writable: true }); ``` -You can see it used in the context of a test [in the SketchList.test.jsx file](../client/modules/IDE/components/SketchList.test.jsx). -2. If the component makes use of the formatDate util, some of the functions in that rely on the ``./client/i18n.js`` file that also makes an ajax request, which sometimes leads to an ERRCONNECTED error on the console, even though your tests pass. You can fix it by adding a mock for that specific i18n file: - ```js - jest.mock('_path_to_file_/i18n'); +3. If you see a ``range(...).getBoundingClientRect is not a function`` error, this is probably related to the CodeMirror code editor, and there is a fix in [this Github Issues post](https://github.com/jsdom/jsdom/issues/3002). + +## What to test +For any type of component, you might want to consider testing: +- The text or divs that you expect to be on the page are actually there. You can use [Queries](https://testing-library.com/docs/queries/about/) for this. Assertions should make use of the toBeInTheDocument() matcher when asserting that an element exists: + ``` + expect(screen.getByText('Hello World')).toBeInTheDocument(); + expect(screen.queryByText('Does not exist')).not.toBeInTheDocument(); ``` -You can also see it used in the context of a test [in the SketchList.test.jsx file](../client/modules/IDE/components/SketchList.test.jsx). +- If it's an integration test, you could consider testing the "happy path" flow. For example, in a login form, you would test how a user might enter their username and password and then enter that information. +- If it's a unit test, you could test possible error cases to ensure that the module being tested is robust and resistant to user or developer error. +- Generally, you want to focus your testing on "user input" -> "expected output" instead of making sure the middle steps work as you would expect. This might mean that you don't need to check that the state changes or class-specific methods occur. This is so that if some of the small details in the implementation of the component changes in the future, the tests can remain the same. +- more details on testing behavior in the component-specific sections + +>Only test the behaviors you know you need to care about. For example, if the desired behavior of a particular edge case doesn't truly matter yet or isn't fully understood, don't write a test for it yet. Doing so would restrict the freedom to refactor the implementation. Additionally, it will send the signal to future readers that this behavior is actually critical, when it very well might not be. [[3]](#References) + +**Don't test unreachable edge cases:** You would have to add code to your original implementation to guard against these cases. The future proofing and the added cost to the codebase "is generally not worth their preceived potential benefits" [[3]](#References) + +**Make sure your tests are sufficient:** You want to make sure your test actually specifies all the behaviors you want to ensure the code exhibits. For example, testing that ``1+1 > 0`` would be correct, but insufficient. [[3]](#References) ## Files to be aware of ### Folder structure -All tests are directly adjacent to the files that they are testing, as described in the [React docs](https://reactjs.org/docs/faq-structure.html#grouping-by-file-type). For example, if you're testing ``examplefolder/Sketchlist.test.jsx``, the test would be in ``examplefolder/Sketchlist.test.jsx``. This is so that the tests are as close as possible to the files. This also means that any snapshot files will be stored in the same folder, such as ``examplefolder/__snapshots__/Sketchlist.test.jsx.snap`` +All tests are directly adjacent to the files that they are testing, as described in the [React docs](https://reactjs.org/docs/faq-structure.html#grouping-by-file-type). For example, if you're testing ``examplefolder/Sketchlist.jsx``, the test would be in ``examplefolder/Sketchlist.unit.test.jsx``. This is so that the tests are as close as possible to the files. This also means that any snapshot files will be stored in the same folder, such as ``examplefolder/__snapshots__/Sketchlist.unit.test.jsx.snap`` -Integration tests should be adjacent to the components they're testing. They should be called ``ComponentName.integration.test.jsx`` +Integration tests should be adjacent to the components they're testing. They should be called ``ComponentName.integration.test.jsx``. Unit tests should be called ``ComponentName.unit.test.jsx``. Manual mocks are in ``__mocks__`` folders are adjacent to the modules that they're mocking. @@ -185,7 +183,6 @@ Node modules are mocked in the ``__mocks__`` folder at the root of the client fo . └── client ├── __mocks__ - │ ├── axios.js | ├── i18n.js | └── ...other Node modules you want to mock ├── modules @@ -195,23 +192,24 @@ Node modules are mocked in the ``__mocks__`` folder at the root of the client fo │ │ │ │ ├── projects.js │ │ │ │ └─ ... other action creator mocks │ │ │ ├── projects.js - │ │ │ ├── projects.test.js + │ │ │ ├── projects.unit.test.js │ │ │ └─ ... other action creator files │ │ ├── components │ │ │ ├── __snapshots__ - │ │ │ │ ├── SketchList.test.jsx.snap + │ │ │ │ ├── SketchList.unit.test.jsx.snap │ │ │ │ └─ ... other snapshots │ │ │ ├── SketchList.jsx - │ │ │ ├── SketchList.test.jsx + │ │ │ ├── SketchList.unit.test.jsx │ │ │ └── ... and more component files │ │ ├── reducers - │ │ │ ├── assets.test.js + │ │ │ ├── assets.unit.test.js │ │ │ ├── assets.js │ │ │ └── ...more reducers │ └── ... more folders - ├── redux_test_stores - | ├── test_store.js - │ └── ...any other redux states you want to test + ├── testData + | ├── testReduxStore.js + | ├── testServerResponses.js + │ └── ...any other placeholder data ├── i18n-test.js ├── jest.setup.js ├── test-utils.js @@ -262,8 +260,8 @@ function reduxRender( ``` -### redux_test_stores -This folder contains the inital redux states that you can provide to the ``reduxRender`` function when testing. For example, if you want to render the SketchList component with a username of ``happydog`` and some sample sketches, ``redux_test_stores\test_store.js`` contains a definition for that state that you can import and provide to the renderer. +### testData +This folder contains the test data that you can use in your tests, including inital redux states that you can provide to the ``reduxRender`` function when testing. For example, if you want to render the SketchList component with a username of ``happydog`` and some sample sketches, ``redux_test_stores\test_store.js`` contains a definition for that state that you can import and provide to the renderer. The folder also contains test data that you can use for msw server so that the server returns json with the correct format and fields. ## Testing plain components If it doesn't contain ``connect(mapStateToProps, mapDispatchToProps)(ComponentName)`` or use hooks like ``useSelector``, then your component is not directly using Redux and testing your component will be simpler and might look something like the code below. Notably, we descibe the component being tested as the [subject under test](http://xunitpatterns.com/SUT.html) by creating a function called ``subject`` that renders the component with the subject dependencies (the props) that are defined in the same scope. They're declared with ``let`` so that they can be overwritten in a nested ``describe``block that tests different dependencies. This keeps the subject function consistent between test suites and explicitly declares variables that can affect the outcome of the test. @@ -376,7 +374,7 @@ import thunk from 'redux-thunk'; import { act } from 'react-dom/test-utils'; import MyReduxComponent from './MyReduxComponent'; import { reduxRender, fireEvent, screen } from '../../../test-utils'; -import { initialTestState } from '../../../redux_test_stores/test_store'; +import { initialTestState } from '../../../testData/testReduxStore'; describe('', () => { const mockStore = configureStore([thunk]); @@ -441,17 +439,31 @@ Some things to consider testing: ## How to handle API calls in tests -Some tests throw errors if a part of the client-side code tries to make an API call or AJAX request. Our solution to this is to use jest to replace those functions with [mock functions](https://jestjs.io/docs/mock-functions). - -The code in question for the client side is mostly related to the axios library. We mock the whole library - jest automatically does this since we have an ``axios.js`` file in the ``__mocks__`` folder at the root of the client folder. [[2]](#References) +Some tests throw errors if a part of the client-side code tries to make an API call or AJAX request. Our solution to this is to use the [Mock Service Worker library](https://mswjs.io/) to mock the API requests by intercepting requests on the network level [[2]](#References). It can handle API calls and return appropriate data (you can see what shape of data gets returned by looking through the server files). There is some test data available in the ``client/testData/testServerResponse.js`` file, but you may need to edit the file to add a new json response if an appropriate one doesn't exist already. The example code below sets up a server to respond to a GET request at ``/exampleendpoint`` by returning ``{data: foo}`` You can see it in the context of a test [in the SketchList.test.jsx file](../client/modules/IDE/components/SketchList.test.jsx). -The benefit of this is that you can control exactly what happens when any axios function gets called, and you can check how many times it's been called. +```js +// setup for the msw +const server = setupServer( + rest.get(`/exampleendpoint`, (req, res, ctx) => + res(ctx.json({ data: 'foo' })) + ) +); + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); +``` -A few components also import ``./client/i18n.js`` (or ``./client/utils/formatDate``, which imports the first file), in which the ``i18n.use(Backend)`` line can sometimes throw a sneaky ERRCONNECTED error. You can resolve this by mocking that file as described in [this section](#Troubleshooting). +If the component makes use of the formatDate util, some of the functions in that rely on the ``./client/i18n.js`` file that also makes an ajax request, which sometimes leads to an ERRCONNECTED error on the console, even though your tests pass. You can fix it by adding a mock for that specific i18n file: +```js +jest.mock('_path_to_file_/i18n'); +``` +You can see it used in the context of a test [in the SketchList.test.jsx file](../client/modules/IDE/components/SketchList.test.jsx). ## Useful terminology to know -Thanks [Test Double Wiki](https://github.com/testdouble/contributing-tests/wiki/Test-Double) for the definitions. +Thanks [Test Double Wiki](https://github.com/testdouble/contributing-tests/wiki/Test-Double) for the definitions. You might see some of these words used in testing library documentation, so here are short definitions for them. + #### Test double Broadest available term to describe any fake thing used in place of a real thing for a test. #### Stub @@ -479,11 +491,12 @@ This project uses i18next for internationalization. If you import the render fun ## More Resources - [React Testing Library Cheatsheet](https://testing-library.com/docs/react-testing-library/cheatsheet/) - [React connected component test](https://www.robinwieruch.de/react-connected-component-test) +- https://blog.bitsrc.io/testing-a-redux-hooked-app-a8e9d1609061 ## References 1. [Best practices for unit testing with a react redux approach](https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach) -2. [How to test your react-redux application (this article also references axios)](https://medium.com/asos-techblog/how-to-test-your-react-redux-application-48d90481a253) +2. [React testing library example intro](https://testing-library.com/docs/react-testing-library/example-intro/#full-example) 3. [Testing Double Wiki (Special thanks to this wiki for being such a comprehensive guide to the history of testing and best practices.)](https://github.com/testdouble/contributing-tests/wiki/Tests%27-Influence-on-Design) From a8299eacba6a799d1fac9019c90b55e370bb1124 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Mon, 12 Apr 2021 16:58:15 -0400 Subject: [PATCH 26/37] small formatting fixes --- developer_docs/testing.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 48c6a02135..70351b475c 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -261,7 +261,7 @@ function reduxRender( ### testData -This folder contains the test data that you can use in your tests, including inital redux states that you can provide to the ``reduxRender`` function when testing. For example, if you want to render the SketchList component with a username of ``happydog`` and some sample sketches, ``redux_test_stores\test_store.js`` contains a definition for that state that you can import and provide to the renderer. The folder also contains test data that you can use for msw server so that the server returns json with the correct format and fields. +This folder contains the test data that you can use in your tests, including inital redux states that you can provide to the ``reduxRender`` function when testing. For example, if you want to render the SketchList component with a username of ``happydog`` and some sample sketches, ``testData/testReduxStore.js`` contains a definition for that state that you can import and provide to the renderer. The folder also contains test data that you can use for ``msw`` server so that the server returns json with the correct format and fields. ## Testing plain components If it doesn't contain ``connect(mapStateToProps, mapDispatchToProps)(ComponentName)`` or use hooks like ``useSelector``, then your component is not directly using Redux and testing your component will be simpler and might look something like the code below. Notably, we descibe the component being tested as the [subject under test](http://xunitpatterns.com/SUT.html) by creating a function called ``subject`` that renders the component with the subject dependencies (the props) that are defined in the same scope. They're declared with ``let`` so that they can be overwritten in a nested ``describe``block that tests different dependencies. This keeps the subject function consistent between test suites and explicitly declares variables that can affect the outcome of the test. @@ -341,7 +341,7 @@ Testing reducers and action creators is covered pretty well in [Redux's document ### Connected Components -Although it's possible to export the components as unconnected components for testing (and in this case you would just manually pass in the props that redux provides), the codebase is being migrated to use hooks, and in this case, that approach no longer works. It also doesn't work if we render components that have connected subcomponents. Thus, for consistency, we suggest testing all redux components while they're connected to redux. We can do this with redux-mock-store. +Although it's possible to export the components as unconnected components for testing (and in this case you would just manually pass in the props that redux provides), the codebase is being migrated to use hooks, and in this case, that approach no longer works. It also doesn't work if we render components that have connected subcomponents. Thus, for consistency, we suggest testing all redux components while they're connected to redux. We can do this with ``redux-mock-store``. This works like so: 1. Import the reduxRender function from ``client/test_utils.js`` @@ -353,7 +353,7 @@ import thunk from 'redux-thunk'; const mockStore = configureStore([thunk]); ``` -3. Create a mock store. There's an initial state that you can import from ``client/redux_test_stores/test_store.js`` +3. Create a mock store. There's an initial state that you can import from ``client/testData/testReduxStore.js`` ```js store = mockStore(initialTestState); ``` @@ -441,6 +441,8 @@ Some things to consider testing: Some tests throw errors if a part of the client-side code tries to make an API call or AJAX request. Our solution to this is to use the [Mock Service Worker library](https://mswjs.io/) to mock the API requests by intercepting requests on the network level [[2]](#References). It can handle API calls and return appropriate data (you can see what shape of data gets returned by looking through the server files). There is some test data available in the ``client/testData/testServerResponse.js`` file, but you may need to edit the file to add a new json response if an appropriate one doesn't exist already. The example code below sets up a server to respond to a GET request at ``/exampleendpoint`` by returning ``{data: foo}`` You can see it in the context of a test [in the SketchList.test.jsx file](../client/modules/IDE/components/SketchList.test.jsx). +There's a longer explaination of the benefits of ``msw`` in [this article by Kent C Dodds](https://kentcdodds.com/blog/stop-mocking-fetch). + ```js // setup for the msw const server = setupServer( @@ -454,7 +456,7 @@ afterEach(() => server.resetHandlers()); afterAll(() => server.close()); ``` -If the component makes use of the formatDate util, some of the functions in that rely on the ``./client/i18n.js`` file that also makes an ajax request, which sometimes leads to an ERRCONNECTED error on the console, even though your tests pass. You can fix it by adding a mock for that specific i18n file: +If the component makes use of the ``formatDate`` util, some of the functions in that rely on the ``./client/i18n.js`` file that also makes an ajax request, which sometimes leads to an ERRCONNECTED error on the console, even though your tests pass. You can fix it by adding a mock for that specific i18n file: ```js jest.mock('_path_to_file_/i18n'); ``` From 255e4f8250171bbae27c072ff1e1cd5e84f4a7f8 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 16 Apr 2021 13:34:49 -0400 Subject: [PATCH 27/37] rename files, fix index and editor tests --- client/index.integration.test.jsx | 56 ++++++++++++++----- client/modules/App/App.test.jsx | 55 ------------------ ...projects.test.js => projects.unit.test.js} | 21 ++++--- client/modules/IDE/components/Editor.jsx | 1 + .../IDE/components/Editor.unit.test.jsx | 18 ++++-- ...List.test.jsx => SketchList.unit.test.jsx} | 0 client/test-utils.js | 5 +- client/testData/testServerResponses.js | 6 +- developer_docs/testing.md | 4 +- 9 files changed, 77 insertions(+), 89 deletions(-) delete mode 100644 client/modules/App/App.test.jsx rename client/modules/IDE/actions/__tests__/{projects.test.js => projects.unit.test.js} (73%) rename client/modules/IDE/components/{SketchList.test.jsx => SketchList.unit.test.jsx} (100%) diff --git a/client/index.integration.test.jsx b/client/index.integration.test.jsx index 9f66422dfb..f3a68efb31 100644 --- a/client/index.integration.test.jsx +++ b/client/index.integration.test.jsx @@ -1,13 +1,25 @@ import { setupServer } from 'msw/node'; import { rest } from 'msw'; +import React from 'react'; +import { Router, browserHistory } from 'react-router'; + import { + reduxRender, act, + waitFor, fireEvent, - prettyDOM, screen, within -} from '@testing-library/react'; -import userResponse from './testData/testServerResponses'; +} from './test-utils'; +import configureStore from './store'; +import routes from './routes'; +import * as Actions from './modules/User/actions'; +import { userResponse } from './testData/testServerResponses'; + +// setup for the app +const history = browserHistory; +const initialState = window.__INITIAL_STATE__; +const store = configureStore(initialState); // need to mock this file or it'll throw ERRCONNECTED jest.mock('./i18n'); @@ -15,7 +27,8 @@ jest.mock('./i18n'); // setup for the msw fake server const server = setupServer( rest.get('/session', (req, res, ctx) => - res(ctx.json(userResponse.userResponse)) + // console.log("called server get session"); + res(ctx.json(userResponse)) ) ); @@ -27,6 +40,7 @@ beforeAll(() => afterEach(() => server.resetHandlers()); afterAll(() => server.close()); +// below are fixes for jsdom-specific errors // https://stackoverflow.com/questions/57311971/error-not-implemented-window-scrollto-how-do-we-remove-this-error-from-jest-t const noop = () => {}; Object.defineProperty(window, 'focus', { value: noop, writable: true }); @@ -46,17 +60,29 @@ document.createRange = () => { return range; }; +// start testing describe('index.jsx integration', () => { - let container = null; - - // we only run the setup once because require only works once - beforeAll(() => { - // setup a DOM element as a render target - container = document.createElement('div'); - container.id = 'root'; - document.body.appendChild(container); - // eslint-disable-next-line global-require - require('./index'); + // the subject under test + const subject = () => + reduxRender(, { store }); + + // spy on this function and wait for it to be called before making assertions + const spy = jest.spyOn(Actions, 'getUser'); + + beforeEach(async () => { + // console.log("TRYING TO SPY ON GETUSER"); + + act(() => { + subject(); + }); + + // console.log("WAITING...."); + await waitFor(() => expect(spy).toHaveBeenCalledTimes(1)); + // console.log("SPY DONE"); + }); + + afterEach(() => { + spy.mockClear(); }); it('navbar items and the dropdowns in the navbar exist', () => { @@ -158,7 +184,7 @@ describe('index.jsx integration', () => { // expect(screen.getByText("createCanvas")).toBeInTheDocument(); const codeeditor = screen.getByRole('article'); - console.log(prettyDOM(codeeditor)); + // console.log(prettyDOM(codeeditor)); expect(indexHTMLButton).toBeInTheDocument(); const startingeditorcode = codeeditor.textContent; diff --git a/client/modules/App/App.test.jsx b/client/modules/App/App.test.jsx deleted file mode 100644 index 7d308830ba..0000000000 --- a/client/modules/App/App.test.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import configureStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import axios from 'axios'; -import { unmountComponentAtNode } from 'react-dom'; -import { act } from 'react-dom/test-utils'; -import App from './App'; -import { - reduxRender, - fireEvent, - screen, - within, - prettyDOM -} from '../../test-utils'; -import { initialTestState } from '../../testData/testReduxStore'; - -jest.mock('../../i18n'); - -describe('', () => { - // to attach the rendered DOM element to - let container; - - const mockStore = configureStore([thunk]); - const store = mockStore(initialTestState); - - const subject = () => - reduxRender(, { - store, - container - }); - - beforeEach(() => { - // setup a DOM element as a render target - container = document.createElement('div'); - document.body.appendChild(container); - // axios.get.mockImplementationOnce((x) => Promise.resolve({ data: 'foo' })); - }); - - afterEach(() => { - // cleanup on exiting - unmountComponentAtNode(container); - container.remove(); - container = null; - store.clearActions(); - }); - - it('renders', () => { - act(() => { - subject(); - }); - console.log(prettyDOM(container)); - // expect(screen.getByText('testsketch1')).toBeInTheDocument(); - // expect(screen.getByText('testsketch2')).toBeInTheDocument(); - }); -}); diff --git a/client/modules/IDE/actions/__tests__/projects.test.js b/client/modules/IDE/actions/__tests__/projects.unit.test.js similarity index 73% rename from client/modules/IDE/actions/__tests__/projects.test.js rename to client/modules/IDE/actions/__tests__/projects.unit.test.js index a522b30385..ce844cd26b 100644 --- a/client/modules/IDE/actions/__tests__/projects.test.js +++ b/client/modules/IDE/actions/__tests__/projects.unit.test.js @@ -1,8 +1,8 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import axios from 'axios'; -import { unmountComponentAtNode } from 'react-dom'; -import { act } from 'react-dom/test-utils'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; + import * as ProjectActions from '../projects'; import * as ActionTypes from '../../../../constants'; import { @@ -12,6 +12,16 @@ import { const mockStore = configureStore([thunk]); +const server = setupServer( + rest.get(`/${initialTestState.user.username}/projects`, (req, res, ctx) => + res(ctx.json(mockProjects)) + ) +); + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); + describe('projects action creator tests', () => { let store; @@ -22,11 +32,6 @@ describe('projects action creator tests', () => { it('creates GET_PROJECTS after successfuly fetching projects', () => { store = mockStore(initialTestState); - axios.get.mockImplementationOnce((x) => { - console.log('get was called with ', x); - return Promise.resolve({ data: mockProjects }); - }); - const expectedActions = [ { type: ActionTypes.START_LOADING }, { type: ActionTypes.SET_PROJECTS, projects: mockProjects }, diff --git a/client/modules/IDE/components/Editor.jsx b/client/modules/IDE/components/Editor.jsx index bc121e7a20..2a323a8732 100644 --- a/client/modules/IDE/components/Editor.jsx +++ b/client/modules/IDE/components/Editor.jsx @@ -298,6 +298,7 @@ class Editor extends React.Component { getContent() { const content = this._cm.getValue(); + console.log(content); const updatedFile = Object.assign({}, this.props.file, { content }); return updatedFile; } diff --git a/client/modules/IDE/components/Editor.unit.test.jsx b/client/modules/IDE/components/Editor.unit.test.jsx index e660f86104..77daac9ef4 100644 --- a/client/modules/IDE/components/Editor.unit.test.jsx +++ b/client/modules/IDE/components/Editor.unit.test.jsx @@ -1,7 +1,6 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import axios from 'axios'; import { act } from 'react-dom/test-utils'; import Editor from './Editor'; import { @@ -15,6 +14,19 @@ import { initialTestState } from '../../../testData/testReduxStore'; jest.mock('../../../i18n'); +// const server = setupServer( +// rest.get(`/${initialTestState.user.username}/projects`, (req, res, ctx) => +// // it just needs to return something so it doesn't throw an error +// // Sketchlist tries to grab projects on creation but for the unit test +// // we just feed those in as part of the initial state +// res(ctx.json({ data: 'foo' })) +// ) +// ); + +// beforeAll(() => server.listen()); +// afterEach(() => server.resetHandlers()); +// afterAll(() => server.close()); + describe('', () => { const mockStore = configureStore([thunk]); const store = mockStore(initialTestState); @@ -23,10 +35,6 @@ describe('', () => { const subject = () => reduxRender(, { store }); - beforeEach(() => { - axios.get.mockImplementationOnce((x) => Promise.resolve({ data: 'foo' })); - }); - afterEach(() => { store.clearActions(); }); diff --git a/client/modules/IDE/components/SketchList.test.jsx b/client/modules/IDE/components/SketchList.unit.test.jsx similarity index 100% rename from client/modules/IDE/components/SketchList.test.jsx rename to client/modules/IDE/components/SketchList.unit.test.jsx diff --git a/client/test-utils.js b/client/test-utils.js index 05369e013b..5d251e48ce 100644 --- a/client/test-utils.js +++ b/client/test-utils.js @@ -19,6 +19,7 @@ import { Provider } from 'react-redux'; import { I18nextProvider } from 'react-i18next'; import i18n from './i18n-test'; import rootReducer from './reducers'; +import ThemeProvider from './modules/App/components/ThemeProvider'; // re-export everything // eslint-disable-next-line import/no-extraneous-dependencies @@ -44,7 +45,9 @@ function reduxRender( function Wrapper({ children }) { return ( - {children} + + {children} + ); } diff --git a/client/testData/testServerResponses.js b/client/testData/testServerResponses.js index f56ab8b32c..1f06adee9e 100644 --- a/client/testData/testServerResponses.js +++ b/client/testData/testServerResponses.js @@ -1,6 +1,8 @@ // test data to use with msw -const userResponse = { +export const sketchResponse = {}; + +export const userResponse = { email: 'happydog@example.com', username: 'happydog', preferences: { @@ -26,5 +28,3 @@ const userResponse = { github: 'githubusername', google: 'googleusername' }; - -export default { userResponse }; diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 70351b475c..1b6c19071f 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -112,7 +112,7 @@ See the [redux section](#Testing-Redux) below :) You might still want to write tests for non-component or non-redux files, such as modules with utility functions. What gets tested in this case depends a lot on the module itself, but generally, you would import the module and test the functions within it. ### Querying for elements -Read about the recommended order of priority for queries in [the testing library docs](https://testing-library.com/docs/guide-which-query/#priority). We recommend using roles and text, or labels. You can use this [handy extension](https://chrome.google.com/webstore/detail/testing-playground/hejbmebodbijjdhflfknehhcgaklhano/related) to do this. +Read about the recommended order of priority for queries in [the testing library docs](https://testing-library.com/docs/guide-which-query/#priority). We recommend using roles and text, or labels. You can use this [handy extension called Testing Playground](https://chrome.google.com/webstore/detail/testing-playground/hejbmebodbijjdhflfknehhcgaklhano/related) to do this. ### File structure Each test should have a top-level ``describe`` block to group related blocks together, with the name of the component under test. @@ -439,7 +439,7 @@ Some things to consider testing: ## How to handle API calls in tests -Some tests throw errors if a part of the client-side code tries to make an API call or AJAX request. Our solution to this is to use the [Mock Service Worker library](https://mswjs.io/) to mock the API requests by intercepting requests on the network level [[2]](#References). It can handle API calls and return appropriate data (you can see what shape of data gets returned by looking through the server files). There is some test data available in the ``client/testData/testServerResponse.js`` file, but you may need to edit the file to add a new json response if an appropriate one doesn't exist already. The example code below sets up a server to respond to a GET request at ``/exampleendpoint`` by returning ``{data: foo}`` You can see it in the context of a test [in the SketchList.test.jsx file](../client/modules/IDE/components/SketchList.test.jsx). +Some tests throw errors if a part of the client-side code tries to make an API call. Our solution to this is to use the [Mock Service Worker library](https://mswjs.io/) to mock the API requests by intercepting requests on the network level [[2]](#References). It can handle API calls and return appropriate data (you can see what shape of data gets returned by looking through the server files). There is some test data available in the ``client/testData/testServerResponse.js`` file, but you may need to edit the file to add a new json response if an appropriate one doesn't exist already. The example code below sets up a server to respond to a GET request at ``/exampleendpoint`` by returning ``{data: foo}`` You can see it in the context of a test [in the SketchList.test.jsx file](../client/modules/IDE/components/SketchList.test.jsx). There's a longer explaination of the benefits of ``msw`` in [this article by Kent C Dodds](https://kentcdodds.com/blog/stop-mocking-fetch). From 50604196721c6186b1bb3dd2abe7c626c0658c84 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 16 Apr 2021 13:35:30 -0400 Subject: [PATCH 28/37] Delete projects.js --- client/modules/IDE/actions/__mocks__/projects.js | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 client/modules/IDE/actions/__mocks__/projects.js diff --git a/client/modules/IDE/actions/__mocks__/projects.js b/client/modules/IDE/actions/__mocks__/projects.js deleted file mode 100644 index 8cb34215a7..0000000000 --- a/client/modules/IDE/actions/__mocks__/projects.js +++ /dev/null @@ -1,16 +0,0 @@ -import * as ActionTypes from '../../../../constants'; -import { startLoader, stopLoader } from '../loader'; -import { mockProjects } from '../../../../testData/testReduxStore'; - -// eslint-disable-next-line -export function getProjects(username) { - console.log(`mocked getProjects call with ${username}`); - return (dispatch) => { - dispatch(startLoader()); - dispatch({ - type: ActionTypes.SET_PROJECTS, - projects: mockProjects - }); - dispatch(stopLoader()); - }; -} From bda1507a034e8b9b72b60ce4fe961c15226315da Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 16 Apr 2021 13:39:43 -0400 Subject: [PATCH 29/37] fix path after moving folders --- .../{__test__/mocks => __mocks__}/fileMock.js | 0 .../mocks => __mocks__}/styleMock.js | 0 .../{__tests__ => }/projects.unit.test.js | 6 +- .../__snapshots__/SketchList.test.jsx.snap | 136 ------------------ package.json | 4 +- 5 files changed, 5 insertions(+), 141 deletions(-) rename client/{__test__/mocks => __mocks__}/fileMock.js (100%) rename client/{__test__/mocks => __mocks__}/styleMock.js (100%) rename client/modules/IDE/actions/{__tests__ => }/projects.unit.test.js (87%) delete mode 100644 client/modules/IDE/components/__snapshots__/SketchList.test.jsx.snap diff --git a/client/__test__/mocks/fileMock.js b/client/__mocks__/fileMock.js similarity index 100% rename from client/__test__/mocks/fileMock.js rename to client/__mocks__/fileMock.js diff --git a/client/__test__/mocks/styleMock.js b/client/__mocks__/styleMock.js similarity index 100% rename from client/__test__/mocks/styleMock.js rename to client/__mocks__/styleMock.js diff --git a/client/modules/IDE/actions/__tests__/projects.unit.test.js b/client/modules/IDE/actions/projects.unit.test.js similarity index 87% rename from client/modules/IDE/actions/__tests__/projects.unit.test.js rename to client/modules/IDE/actions/projects.unit.test.js index ce844cd26b..6fce08c796 100644 --- a/client/modules/IDE/actions/__tests__/projects.unit.test.js +++ b/client/modules/IDE/actions/projects.unit.test.js @@ -3,12 +3,12 @@ import thunk from 'redux-thunk'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; -import * as ProjectActions from '../projects'; -import * as ActionTypes from '../../../../constants'; +import * as ProjectActions from './projects'; +import * as ActionTypes from '../../../constants'; import { initialTestState, mockProjects -} from '../../../../testData/testReduxStore'; +} from '../../../testData/testReduxStore'; const mockStore = configureStore([thunk]); diff --git a/client/modules/IDE/components/__snapshots__/SketchList.test.jsx.snap b/client/modules/IDE/components/__snapshots__/SketchList.test.jsx.snap deleted file mode 100644 index 81d6a0ec54..0000000000 --- a/client/modules/IDE/components/__snapshots__/SketchList.test.jsx.snap +++ /dev/null @@ -1,136 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` snapshot testing 1`] = ` - -
- - - - - - - - - - - - - - - - - - - - - - -
- - - - - - -
- - testsketch1 - - - Feb 25, 2021, 11:58:14 PM - - Feb 25, 2021, 11:58:29 PM - - -
- - testsketch2 - - - Feb 23, 2021, 12:40:43 PM - - Feb 23, 2021, 12:40:43 PM - - -
-
-
-`; diff --git a/package.json b/package.json index 4277b965a8..16e9527277 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ "/client/jest.setup.js" ], "moduleNameMapper": { - "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)(|\\?byContent|\\?byUrl)$": "/client/__test__/mocks/fileMock.js", - "\\.(css|less|scss)$": "/client/__test__/mocks/styleMock.js" + "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)(|\\?byContent|\\?byUrl)$": "/client/__mocks__/fileMock.js", + "\\.(css|less|scss)$": "/client/__mocks__/styleMock.js" }, "testMatch": [ "/client/**/*.test.(js|jsx)" From 90a6f8726f0972b6e7f3a56b5a3787e7931c6f24 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 16 Apr 2021 13:45:02 -0400 Subject: [PATCH 30/37] revert changes on Sketchlist --- client/modules/IDE/components/Editor.unit.test.jsx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/client/modules/IDE/components/Editor.unit.test.jsx b/client/modules/IDE/components/Editor.unit.test.jsx index 77daac9ef4..3e22ea3fa5 100644 --- a/client/modules/IDE/components/Editor.unit.test.jsx +++ b/client/modules/IDE/components/Editor.unit.test.jsx @@ -14,19 +14,6 @@ import { initialTestState } from '../../../testData/testReduxStore'; jest.mock('../../../i18n'); -// const server = setupServer( -// rest.get(`/${initialTestState.user.username}/projects`, (req, res, ctx) => -// // it just needs to return something so it doesn't throw an error -// // Sketchlist tries to grab projects on creation but for the unit test -// // we just feed those in as part of the initial state -// res(ctx.json({ data: 'foo' })) -// ) -// ); - -// beforeAll(() => server.listen()); -// afterEach(() => server.resetHandlers()); -// afterAll(() => server.close()); - describe('', () => { const mockStore = configureStore([thunk]); const store = mockStore(initialTestState); From aa6ded81260ed7e5f5544ebdace1f3e7ae6d02ae Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 16 Apr 2021 13:49:55 -0400 Subject: [PATCH 31/37] reset sketchlist to last commit???? --- client/modules/IDE/components/SketchList.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/modules/IDE/components/SketchList.jsx b/client/modules/IDE/components/SketchList.jsx index 05cc1d92b6..757bacd904 100644 --- a/client/modules/IDE/components/SketchList.jsx +++ b/client/modules/IDE/components/SketchList.jsx @@ -179,6 +179,7 @@ class SketchListRowBase extends React.Component { renderDropdown = () => { const { optionsOpen } = this.state; const userIsOwner = this.props.user.username === this.props.username; + return ( @@ -449,7 +449,6 @@ class SketchList extends React.Component { className="sketch-list__sort-button" onClick={() => this.props.toggleDirectionForField(fieldName)} aria-label={buttonLabel} - data-testid={`toggle-direction-${fieldName}`} > {displayName} {field === fieldName && From e8d69dd91c34b29d44cd6d234770c8d433528757 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 16 Apr 2021 14:06:30 -0400 Subject: [PATCH 32/37] update documentation and snapshot --- .../SketchList.unit.test.jsx.snap | 131 ++++++++++++++++++ developer_docs/testing.md | 23 ++- 2 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap diff --git a/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap b/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap new file mode 100644 index 0000000000..ccff886e1a --- /dev/null +++ b/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap @@ -0,0 +1,131 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` snapshot testing 1`] = ` + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + testsketch1 + + + Feb 25, 2021, 11:58:14 PM + + Feb 25, 2021, 11:58:29 PM + + +
+ + testsketch2 + + + Feb 23, 2021, 12:40:43 PM + + Feb 23, 2021, 12:40:43 PM + + +
+
+
+`; diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 1b6c19071f..8443f4c6aa 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -22,6 +22,7 @@ Many files still don't have tests, so **if you're looking to get started as a co - [Internationalization](#internationalization) - [Useful terminology to know](#Useful-terminology-to-know) - [Tips](#Tips) +- [Files to start with](#Files-to-start-with) - [More resources](#More-resources) - [References](#References) @@ -219,7 +220,7 @@ Node modules are mocked in the ``__mocks__`` folder at the root of the client fo ### test-utils.js This file overwrites the default react-testing-library's render function so that components rendered through the new render function have access i18next and redux. It exports the rest of react-testing-library as is. -It exports a render function with a i18n wrapper as ``render`` and a render function with a wrapper for both redux and i18n as ``reduxRender`` +It exports a render function with a i18n wrapper as ``render`` and a render function with a wrapper for both redux and i18n as ``reduxRender``. Thus, in your component test files, instead of calling ``import {functions you want} from 'react-testing-libary'`` importing react-testing library might look something like this: @@ -259,6 +260,12 @@ function reduxRender( } ``` +Then, if you want to call the render function with the wrapper with the Redux Provider, you can do this, once you have a [store made with redux-mock-store](#Connected-Components): + +```js +reduxRender(, { store }); +``` + ### testData This folder contains the test data that you can use in your tests, including inital redux states that you can provide to the ``reduxRender`` function when testing. For example, if you want to render the SketchList component with a username of ``happydog`` and some sample sketches, ``testData/testReduxStore.js`` contains a definition for that state that you can import and provide to the renderer. The folder also contains test data that you can use for ``msw`` server so that the server returns json with the correct format and fields. @@ -462,6 +469,9 @@ jest.mock('_path_to_file_/i18n'); ``` You can see it used in the context of a test [in the SketchList.test.jsx file](../client/modules/IDE/components/SketchList.test.jsx). +## Internationalization +This project uses i18next for internationalization. If you import the render function with the i18n wrapper from ``test_utils.js``, it's set up to use English, so the components with be rendered with English text and you should be able to count on this to test for specific strings. + ## Useful terminology to know Thanks [Test Double Wiki](https://github.com/testdouble/contributing-tests/wiki/Test-Double) for the definitions. You might see some of these words used in testing library documentation, so here are short definitions for them. @@ -481,15 +491,20 @@ some methods but not others. Partial mocks are widely considered to be an anti-p #### Spy Records every invocation made against it and can verify certain interactions took place after the fact. -## Internationalization -This project uses i18next for internationalization. If you import the render function with the i18n wrapper from ``test_utils.js``, it's set up to use English, so the components with be rendered with English text and you should be able to count on this to test for specific strings. - ## Tips 1. Make test fail at least once to make sure it was a meaningful test 2. "If you or another developer change the component in a way that it changes its behaviour at least one test should fail." - [How to Unit Test in React](https://itnext.io/how-to-unit-test-in-react-72e911e2b8d) 3. Avoid using numbers or data that seem "special" in your tests. For example, if you were checking the "age" variable in a component is a integer, but checked it as so ``expect(person.ageValidator(18)).toBe(true)``, the reader might assume that the number 18 had some significance to the function because it's a significant age. It would be better to have used 1234. 4. Tests should help other developers understand the expected behavior of the component that it's testing +## Files to start with + +These files still need tests! If you want to contribute by writing tests, please consider starting with these: + +1. Integration test for LoginView.jsx +2. Integration test for SignupView.jsx +3. Tests for route switching in routes.jsx + ## More Resources - [React Testing Library Cheatsheet](https://testing-library.com/docs/react-testing-library/cheatsheet/) - [React connected component test](https://www.robinwieruch.de/react-connected-component-test) From d2619ce236d6445fe9913cc18ace8c5d97de51a6 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 16 Apr 2021 14:08:45 -0400 Subject: [PATCH 33/37] checklist --- developer_docs/testing.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/developer_docs/testing.md b/developer_docs/testing.md index 8443f4c6aa..67ccccc3b0 100644 --- a/developer_docs/testing.md +++ b/developer_docs/testing.md @@ -501,9 +501,11 @@ Records every invocation made against it and can verify certain interactions too These files still need tests! If you want to contribute by writing tests, please consider starting with these: -1. Integration test for LoginView.jsx -2. Integration test for SignupView.jsx -3. Tests for route switching in routes.jsx +- [ ] Integration test for LoginView.jsx + +- [ ] Integration test for SignupView.jsx + +- [ ] Tests for route switching in routes.jsx ## More Resources - [React Testing Library Cheatsheet](https://testing-library.com/docs/react-testing-library/cheatsheet/) From 4d025065389d5d060a87ed64da23c4c971738e62 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 16 Apr 2021 14:45:53 -0400 Subject: [PATCH 34/37] remove testing.md and move it to another branch --- developer_docs/testing.md | 523 -------------------------------------- 1 file changed, 523 deletions(-) delete mode 100644 developer_docs/testing.md diff --git a/developer_docs/testing.md b/developer_docs/testing.md deleted file mode 100644 index 67ccccc3b0..0000000000 --- a/developer_docs/testing.md +++ /dev/null @@ -1,523 +0,0 @@ -# Testing -This guide lists out the tools and methods that we use to test the p5 editor project. It focuses mainly on the client side project, but some of it applies to the server tests too. - -For an initial basic overview of testing for React apps, [you can read what the React developers have to say about it](https://reactjs.org/docs/testing.html). We use both unit tests and integration tests. - -We are testing React components by rendering the component trees in a simplified test environment and making assertions on what gets rendered and what functions get called. - -Many files still don't have tests, so **if you're looking to get started as a contributor, this would be a great place to start!** - -## What's in this document -- [Testing dependencies](#testing-dependencies) -- [Useful testing commands](#Useful-testing-commands) -- [Our testing methods](#Our-testing-methods) -- [Why write tests](#Why-write-tests) -- [When to run tests](#When-to-run-tests) -- [Writing a test](#Writing-a-test) -- [What to test](#What-to-test) -- [Files to be aware of](#Files-to-be-aware-of) -- [Testing plain components](#Testing-plain-components) -- [Testing Redux](#Testing-Redux) -- [How to handle API calls in tests](#How-to-handle-API-calls-in-tests) -- [Internationalization](#internationalization) -- [Useful terminology to know](#Useful-terminology-to-know) -- [Tips](#Tips) -- [Files to start with](#Files-to-start-with) -- [More resources](#More-resources) -- [References](#References) - - -## Testing dependencies -1. [Jest](https://jestjs.io/) -2. [react-testing-library](https://testing-library.com/docs/react-testing-library/intro/) -3. [redux-mock-store](https://github.com/reduxjs/redux-mock-store) -4. [msw](https://github.com/mswjs/msw) - -## Useful testing commands -Run the whole test suite -``` -$ npm run test -``` - ------ - -[Run tests that match the pattern](https://stackoverflow.com/questions/28725955/how-do-i-test-a-single-file-using-jest/28775887). Useful if you're writing one specific test and want to only run that one. -``` -$ npm run test -- someFileName -``` - ------ -Run the tests but update the snapshot if they don't match. - -``` -$ npm run test -- -u -``` - ----- -For example, if you wanted to run just the SketchList test but also update the snapshot, you could do: -``` -$ npm run test -- Sketchlist.test.js -u -``` - -Find more commands in the [Jest documentation](https://jestjs.io/docs/cli). - -## Our testing methods - -### Unit tests -In unit tests, you're testing the functionality of a single component and no more. They provide lots of feedback on the specific component that you're testing, with the cost of high [redundant coverage](https://github.com/testdouble/contributing-tests/wiki/Redundant-Coverage) and more time spent refactoring tests when components get rewritten. **Not every file needs a unit test.** Unit tests are most important for components that are either: -1. User facing (like a text input field or a user form component) -2. Used across multiple components like a reusable dropdown menu or reusable table element - -In both of these cases, the component being tested is not merely an implementation detail. Thus, it's important for the unit tests to test the error cases that could occur to ensure that the component is robust. For example, for a user-facing input field that should only take positive numbers, a unit test would want to cover what happens when users enter negative numbers or letters. - -### Integration tests -Testing multiple components together. A small example is rendering a parent component in order to test the interactions between children components. Generally, they validate how multiple units of your application work together. Jest, which is what we use, uses jsdom under the hood to emulate common browser APIs with less overhead than automation like a headless browser, and its mocking tools can stub out external API calls. We use integration tests to maximize coverage and to make sure all the pieces play nice together. We want our integration tests to cover the testing of components that don't have unit tests because they're only used in one place and are merely an implementation detail. The integration tests can test the expected user flows, while we expect the unit tests to have tested the error cases more rigorously. - -See [this great article on CSS tricks](https://css-tricks.com/react-integration-testing-greater-coverage-fewer-tests/) about integration tests for more information about this. - -To reiterate, we use integration tests to maximize coverage on individual components that are only used once. We use unit tests to test the robustness of user-facing components and reusable components. - -### Snapshot testing -You can save a snapshot of what the HTML looks like when the component is rendered. It doesn't hurt to add them to your tests, but they can be brittle. - -## Why write tests -- Good place to start if you're learning the codebase. -- Benefits all future contributors by allowing them to check their changes for errors. -- Increased usage: Most code with only ever have a single invocation point, but this means that code might not be particularly robust and lead to bugs if a different developer reuses it in a different context. Writing tests increases the usage of the code in question and may improve the long-term durability, along with leading developers to refactor their code to be more usable. [[3]](#References) -- Lets you check your own work and feel more comfortable sumbitting PRs. -- Catches easy-to-miss errors. -- Good practice for large projects. -- Many of the existing components don't have tests yet, and you could write one :-) - -## When to run tests - -When you ``git push`` your code, the tests will be run automatically for you. Tests will also be run when you make a PR and if you fail any tests it blocks the merge. - -When you modify an existing component, it's a good idea to run the test suite to make sure it didn't make any changes that break the rest of the application. If they did break some tests, you would either have to fix a bug component or update the tests to match the new expected functionality. - -## Writing a test -Want to get started writing a test for a new file or an existing file, but not sure how? - -### For React components -1. Make a new file directly adjacent to your file. For example, if ``example.jsx`` is ``src/components/example.jsx``, then you would make a file called ``example.test.jsx`` at ``src/components/example.test.jsx`` -2. Check if the component is connected to redux or not. -3. If it is, see the [redux section](#Testing-Redux) below on how to write tests for that. -4. If it's not, see the [section below on writing tests for unconnected components](#Testing-plain-components). - -5. "Arange, Act, Assert:" In other words, *arrange* the setup for the test, *act* out whatever the subject's supposed to do, and *assert* on the results. [[3]](#References) - -### For Redux action creators or reducers -See the [redux section](#Testing-Redux) below :) - -### For utility files -You might still want to write tests for non-component or non-redux files, such as modules with utility functions. What gets tested in this case depends a lot on the module itself, but generally, you would import the module and test the functions within it. - -### Querying for elements -Read about the recommended order of priority for queries in [the testing library docs](https://testing-library.com/docs/guide-which-query/#priority). We recommend using roles and text, or labels. You can use this [handy extension called Testing Playground](https://chrome.google.com/webstore/detail/testing-playground/hejbmebodbijjdhflfknehhcgaklhano/related) to do this. - -### File structure -Each test should have a top-level ``describe`` block to group related blocks together, with the name of the component under test. - -*Example.test.ts* - -```js -import Example from './Example'; - -describe('', () => { - it('creates a new example', () => { - //your tests here - }); -}); - -``` - -### Consistency across tests -> "Teams that adopt a rigid and consistent structure to each test tend to more readily understand each test, because every deviation from the norm can be trusted to be meaningful and somehow specific to the nature of the subject." -- We want to default to using meaningless test data stored in the redux-test-stores folder. -- Be sure to follow the [folder structure](#Folder-structure) -- Follow the rendering guidelines set up for the components in this [Writing a Test](#Writing-a-test) section. - - -### Troubleshooting -1. If you are having network errors like ERRCONNECTED or something like ``Cannot read property 'then' of undefined`` as a result of an ``apiClient`` function, then please view the [How to handle API calls in tests](#How-to-handle-API-calls-in-tests) section. - -2. In some cases, window functions are not defined because the client tests run in the context of ``jsdom`` and not a real browser. In this case, you want to define the function as a no op. [See this post for more information.](https://stackoverflow.com/questions/57311971/error-not-implemented-window-scrollto-how-do-we-remove-this-error-from-jest-t) - ```js - const noop = () => {}; - Object.defineProperty(window, 'focus', { value: noop, writable: true }); - ``` - -3. If you see a ``range(...).getBoundingClientRect is not a function`` error, this is probably related to the CodeMirror code editor, and there is a fix in [this Github Issues post](https://github.com/jsdom/jsdom/issues/3002). - -## What to test -For any type of component, you might want to consider testing: -- The text or divs that you expect to be on the page are actually there. You can use [Queries](https://testing-library.com/docs/queries/about/) for this. Assertions should make use of the toBeInTheDocument() matcher when asserting that an element exists: - ``` - expect(screen.getByText('Hello World')).toBeInTheDocument(); - expect(screen.queryByText('Does not exist')).not.toBeInTheDocument(); - ``` -- If it's an integration test, you could consider testing the "happy path" flow. For example, in a login form, you would test how a user might enter their username and password and then enter that information. -- If it's a unit test, you could test possible error cases to ensure that the module being tested is robust and resistant to user or developer error. -- Generally, you want to focus your testing on "user input" -> "expected output" instead of making sure the middle steps work as you would expect. This might mean that you don't need to check that the state changes or class-specific methods occur. This is so that if some of the small details in the implementation of the component changes in the future, the tests can remain the same. -- more details on testing behavior in the component-specific sections - ->Only test the behaviors you know you need to care about. For example, if the desired behavior of a particular edge case doesn't truly matter yet or isn't fully understood, don't write a test for it yet. Doing so would restrict the freedom to refactor the implementation. Additionally, it will send the signal to future readers that this behavior is actually critical, when it very well might not be. [[3]](#References) - -**Don't test unreachable edge cases:** You would have to add code to your original implementation to guard against these cases. The future proofing and the added cost to the codebase "is generally not worth their preceived potential benefits" [[3]](#References) - -**Make sure your tests are sufficient:** You want to make sure your test actually specifies all the behaviors you want to ensure the code exhibits. For example, testing that ``1+1 > 0`` would be correct, but insufficient. [[3]](#References) - -## Files to be aware of - -### Folder structure -All tests are directly adjacent to the files that they are testing, as described in the [React docs](https://reactjs.org/docs/faq-structure.html#grouping-by-file-type). For example, if you're testing ``examplefolder/Sketchlist.jsx``, the test would be in ``examplefolder/Sketchlist.unit.test.jsx``. This is so that the tests are as close as possible to the files. This also means that any snapshot files will be stored in the same folder, such as ``examplefolder/__snapshots__/Sketchlist.unit.test.jsx.snap`` - -Integration tests should be adjacent to the components they're testing. They should be called ``ComponentName.integration.test.jsx``. Unit tests should be called ``ComponentName.unit.test.jsx``. - -Manual mocks are in ``__mocks__`` folders are adjacent to the modules that they're mocking. - -Note: Even if you mock a user module in a ``__mocks__`` folder, user modules have to be explictly mocked in the test too, with ``Jest.mock("path_to_module")`` - -Node modules are mocked in the ``__mocks__`` folder at the root of the client folder, which also includes any mocks that are needed for user modules at the root of the folder directory. - -``` -. -└── client - ├── __mocks__ - | ├── i18n.js - | └── ...other Node modules you want to mock - ├── modules - │ ├── IDE - │ │ ├── actions - │ │ │ ├── __mocks__ - │ │ │ │ ├── projects.js - │ │ │ │ └─ ... other action creator mocks - │ │ │ ├── projects.js - │ │ │ ├── projects.unit.test.js - │ │ │ └─ ... other action creator files - │ │ ├── components - │ │ │ ├── __snapshots__ - │ │ │ │ ├── SketchList.unit.test.jsx.snap - │ │ │ │ └─ ... other snapshots - │ │ │ ├── SketchList.jsx - │ │ │ ├── SketchList.unit.test.jsx - │ │ │ └── ... and more component files - │ │ ├── reducers - │ │ │ ├── assets.unit.test.js - │ │ │ ├── assets.js - │ │ │ └── ...more reducers - │ └── ... more folders - ├── testData - | ├── testReduxStore.js - | ├── testServerResponses.js - │ └── ...any other placeholder data - ├── i18n-test.js - ├── jest.setup.js - ├── test-utils.js - └──... other files and folders -``` - -### test-utils.js -This file overwrites the default react-testing-library's render function so that components rendered through the new render function have access i18next and redux. It exports the rest of react-testing-library as is. - -It exports a render function with a i18n wrapper as ``render`` and a render function with a wrapper for both redux and i18n as ``reduxRender``. - -Thus, in your component test files, instead of calling ``import {functions you want} from 'react-testing-libary'`` importing react-testing library might look something like this: - -If your component only needs i18n and not redux: -```js -import { render, fireEvent, screen, waitFor } from '../../../test-utils'; -``` -If your component needs i18n and redux: -```js -import { reduxRender, fireEvent, screen, waitFor } from '../../../test-utils'; -``` - -Redux and i18next are made accessible by placing wrappers around the component. We can do this by replacing the render function with one that renders the requested component WITH an additional wrapper added around it. - -For example, the exported render function that adds a wrapper for both redux and i18n looks roughly like this: - -```js -function reduxRender( - ui, - { - initialState, - store = createStore(rootReducer, initialState), - ...renderOptions - } = {} -) { - function Wrapper({ children }) { - return ( - - - {children} - - - ); - } - - return render(ui, { wrapper: Wrapper, ...renderOptions }); -} -``` - -Then, if you want to call the render function with the wrapper with the Redux Provider, you can do this, once you have a [store made with redux-mock-store](#Connected-Components): - -```js -reduxRender(, { store }); -``` - - -### testData -This folder contains the test data that you can use in your tests, including inital redux states that you can provide to the ``reduxRender`` function when testing. For example, if you want to render the SketchList component with a username of ``happydog`` and some sample sketches, ``testData/testReduxStore.js`` contains a definition for that state that you can import and provide to the renderer. The folder also contains test data that you can use for ``msw`` server so that the server returns json with the correct format and fields. - -## Testing plain components -If it doesn't contain ``connect(mapStateToProps, mapDispatchToProps)(ComponentName)`` or use hooks like ``useSelector``, then your component is not directly using Redux and testing your component will be simpler and might look something like the code below. Notably, we descibe the component being tested as the [subject under test](http://xunitpatterns.com/SUT.html) by creating a function called ``subject`` that renders the component with the subject dependencies (the props) that are defined in the same scope. They're declared with ``let`` so that they can be overwritten in a nested ``describe``block that tests different dependencies. This keeps the subject function consistent between test suites and explicitly declares variables that can affect the outcome of the test. - -*MyComponent.test.jsx* -```js -import React from 'react'; -import { act } from 'react-dom/test-utils'; -import { fireEvent, render, screen } from '../../../../test-utils'; -import MyComponent from './MyComponent'; - -describe('', () => { - - let subjectProps = { - t: jest.fn(), - fontSize: 12, - setFontSize: jest.fn() - }; - - const subject = () => { - render(); - }; - - afterEach(() => { - //reset the mocks in subjectProps - jest.clearAllMocks(); - }); - - it('I am the test description', () => { - // render the component - act(() => { - subject(); - }); - - /* Tests go here! - * You can access mock functions from subjectProps. - * For example, subjectProps.setFontSize - */ - - }); - - describe('test with a different prop', () => { - - beforeAll(() => { - subjectProps = {...subjectProps, fontSize: 14} - }); - - it("here's that test with a different prop", () => { - act(() => { - subject(); - }); - //test here - }); - }); - -}); -``` - -Consider what you want to test. Some possible things might be: -- User input results in the [expected function being called with the expected argument](https://jestjs.io/docs/mock-functions). - ```js - act(() => { - fireEvent.click(screen.getByLabelText('Username')); - }); - expect(yourMockFunction).toHaveBeenCalledTimes(1); - expect(yourMockFunction.mock.calls[0][0]).toBe(argument); - ``` - -## Testing Redux - -When testing redux, the general guidance [[1]](#References) seems to suggest splitting up testing between: -1. action creators -2. reducers -3. connected components - -Testing reducers and action creators is covered pretty well in [Redux's documentation](https://redux.js.org/recipes/writing-tests). An example of testing an action creator can be found at [projects.test.js](../client/modules/IDE/components/actions/__tests__/projects.test.jsx) - -### Connected Components - -Although it's possible to export the components as unconnected components for testing (and in this case you would just manually pass in the props that redux provides), the codebase is being migrated to use hooks, and in this case, that approach no longer works. It also doesn't work if we render components that have connected subcomponents. Thus, for consistency, we suggest testing all redux components while they're connected to redux. We can do this with ``redux-mock-store``. - -This works like so: -1. Import the reduxRender function from ``client/test_utils.js`` -2. Configure the mock store. -```js -import configureStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; - - -const mockStore = configureStore([thunk]); -``` -3. Create a mock store. There's an initial state that you can import from ``client/testData/testReduxStore.js`` -```js -store = mockStore(initialTestState); -``` -3. Render the component with reduxRender and the store that you just created. -```js -reduxRender(, {store}); -``` -4. Test things! You may need to use jest to mock certain functions if the component is making API calls. - -All together, it might look something like this. - - -*MyReduxComponent.test.jsx* -```js -import React from 'react'; -import configureStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { act } from 'react-dom/test-utils'; -import MyReduxComponent from './MyReduxComponent'; -import { reduxRender, fireEvent, screen } from '../../../test-utils'; -import { initialTestState } from '../../../testData/testReduxStore'; - -describe('', () => { - const mockStore = configureStore([thunk]); - const store = mockStore(initialTestState); - - let subjectProps = { - sampleprop: "foo" - }; - - const subject = () => { - reduxRender(, {store}); - }; - - afterEach(() => { - //clear the mock store too - store.clearActions(); - }); - - it('I am the test description', () => { - // render the component - act(() => { - subject(); - }); - - /* Tests go here! - * You can access mock functions from subjectProps. - * For example, subjectProps.setFontSize - */ - - }); - - describe('test with a different prop', () => { - - beforeAll(() => { - subjectProps = {...subjectProps, fontSize: 14} - }); - - it("here's that test with a different prop", () => { - act(() => { - subject(); - }); - //test here - }); - }); - -}); -``` - -Some things to consider testing: -- User input results in the expected redux action. - ```js - act(() => { - component = reduxRender(, {store}); - }); - act(() => { - fireEvent.click(screen.getByTestId('toggle-direction-createdAt')); - }); - const expectedAction = [{ type: 'TOGGLE_DIRECTION', field: 'createdAt' }]; - - expect(store.getActions()).toEqual(expect.arrayContaining(expectedAction)); - ``` - -## How to handle API calls in tests - -Some tests throw errors if a part of the client-side code tries to make an API call. Our solution to this is to use the [Mock Service Worker library](https://mswjs.io/) to mock the API requests by intercepting requests on the network level [[2]](#References). It can handle API calls and return appropriate data (you can see what shape of data gets returned by looking through the server files). There is some test data available in the ``client/testData/testServerResponse.js`` file, but you may need to edit the file to add a new json response if an appropriate one doesn't exist already. The example code below sets up a server to respond to a GET request at ``/exampleendpoint`` by returning ``{data: foo}`` You can see it in the context of a test [in the SketchList.test.jsx file](../client/modules/IDE/components/SketchList.test.jsx). - -There's a longer explaination of the benefits of ``msw`` in [this article by Kent C Dodds](https://kentcdodds.com/blog/stop-mocking-fetch). - -```js -// setup for the msw -const server = setupServer( - rest.get(`/exampleendpoint`, (req, res, ctx) => - res(ctx.json({ data: 'foo' })) - ) -); - -beforeAll(() => server.listen()); -afterEach(() => server.resetHandlers()); -afterAll(() => server.close()); -``` - -If the component makes use of the ``formatDate`` util, some of the functions in that rely on the ``./client/i18n.js`` file that also makes an ajax request, which sometimes leads to an ERRCONNECTED error on the console, even though your tests pass. You can fix it by adding a mock for that specific i18n file: -```js -jest.mock('_path_to_file_/i18n'); -``` -You can see it used in the context of a test [in the SketchList.test.jsx file](../client/modules/IDE/components/SketchList.test.jsx). - -## Internationalization -This project uses i18next for internationalization. If you import the render function with the i18n wrapper from ``test_utils.js``, it's set up to use English, so the components with be rendered with English text and you should be able to count on this to test for specific strings. - - -## Useful terminology to know -Thanks [Test Double Wiki](https://github.com/testdouble/contributing-tests/wiki/Test-Double) for the definitions. You might see some of these words used in testing library documentation, so here are short definitions for them. - -#### Test double -Broadest available term to describe any fake thing used in place of a real thing for a test. -#### Stub -Any test double that uses a preconfigured response, such always responding with placeholder json to a certain fetch call. -#### Fake -A test double that provides an alternate implementation of a real thing for the purpose of a test. -#### Mock -Colloquially can mean any of the above, just used generally for test doubles. -#### Partial mock -Refers to any actual object which has been wrapped or changed to provide artificial responses to -some methods but not others. Partial mocks are widely considered to be an anti-pattern of test double usage. - -#### Spy -Records every invocation made against it and can verify certain interactions took place after the fact. - -## Tips -1. Make test fail at least once to make sure it was a meaningful test -2. "If you or another developer change the component in a way that it changes its behaviour at least one test should fail." - [How to Unit Test in React](https://itnext.io/how-to-unit-test-in-react-72e911e2b8d) -3. Avoid using numbers or data that seem "special" in your tests. For example, if you were checking the "age" variable in a component is a integer, but checked it as so ``expect(person.ageValidator(18)).toBe(true)``, the reader might assume that the number 18 had some significance to the function because it's a significant age. It would be better to have used 1234. -4. Tests should help other developers understand the expected behavior of the component that it's testing - -## Files to start with - -These files still need tests! If you want to contribute by writing tests, please consider starting with these: - -- [ ] Integration test for LoginView.jsx - -- [ ] Integration test for SignupView.jsx - -- [ ] Tests for route switching in routes.jsx - -## More Resources -- [React Testing Library Cheatsheet](https://testing-library.com/docs/react-testing-library/cheatsheet/) -- [React connected component test](https://www.robinwieruch.de/react-connected-component-test) -- https://blog.bitsrc.io/testing-a-redux-hooked-app-a8e9d1609061 - -## References -1. [Best practices for unit testing with a react redux approach](https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach) - -2. [React testing library example intro](https://testing-library.com/docs/react-testing-library/example-intro/#full-example) - -3. [Testing Double Wiki (Special thanks to this wiki for being such a comprehensive guide to the history of testing and best practices.)](https://github.com/testdouble/contributing-tests/wiki/Tests%27-Influence-on-Design) - -## Special thanks -Thank you to HipsterBrown for helping us out with writing this documentation. \ No newline at end of file From 71751586ef4f77d2ea2dde64d9db6a849c15c8e7 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 16 Apr 2021 14:51:03 -0400 Subject: [PATCH 35/37] fix linting error --- client/modules/IDE/components/Editor.unit.test.jsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/client/modules/IDE/components/Editor.unit.test.jsx b/client/modules/IDE/components/Editor.unit.test.jsx index 3e22ea3fa5..073a357fdb 100644 --- a/client/modules/IDE/components/Editor.unit.test.jsx +++ b/client/modules/IDE/components/Editor.unit.test.jsx @@ -3,13 +3,7 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { act } from 'react-dom/test-utils'; import Editor from './Editor'; -import { - reduxRender, - fireEvent, - screen, - within, - prettyDOM -} from '../../../test-utils'; +import { reduxRender } from '../../../test-utils'; import { initialTestState } from '../../../testData/testReduxStore'; jest.mock('../../../i18n'); From 28ada7e403a560acb4a70460dc4c3f04967735d4 Mon Sep 17 00:00:00 2001 From: Connie Ye Date: Fri, 16 Apr 2021 15:30:42 -0400 Subject: [PATCH 36/37] fix integeation test --- client/index.integration.test.jsx | 22 ---------------------- client/modules/IDE/components/Editor.jsx | 1 - 2 files changed, 23 deletions(-) diff --git a/client/index.integration.test.jsx b/client/index.integration.test.jsx index f3a68efb31..d3750bb94a 100644 --- a/client/index.integration.test.jsx +++ b/client/index.integration.test.jsx @@ -70,15 +70,11 @@ describe('index.jsx integration', () => { const spy = jest.spyOn(Actions, 'getUser'); beforeEach(async () => { - // console.log("TRYING TO SPY ON GETUSER"); - act(() => { subject(); }); - // console.log("WAITING...."); await waitFor(() => expect(spy).toHaveBeenCalledTimes(1)); - // console.log("SPY DONE"); }); afterEach(() => { @@ -176,22 +172,4 @@ describe('index.jsx integration', () => { }); expect(preview.getAttribute('srcdoc')).toMatch(/(^|")\s*($|")/); }); - - it('clicking on a file in the sidebar changes the text content of the codemirror editor', () => { - const indexHTMLButton = screen.getByRole('button', { - name: 'index.html' - }); - - // expect(screen.getByText("createCanvas")).toBeInTheDocument(); - const codeeditor = screen.getByRole('article'); - // console.log(prettyDOM(codeeditor)); - expect(indexHTMLButton).toBeInTheDocument(); - - const startingeditorcode = codeeditor.textContent; - console.log(startingeditorcode); - act(() => { - fireEvent.click(indexHTMLButton); - }); - expect(startingeditorcode).not.toBe(codeeditor.textContent); - }); }); diff --git a/client/modules/IDE/components/Editor.jsx b/client/modules/IDE/components/Editor.jsx index 2a323a8732..bc121e7a20 100644 --- a/client/modules/IDE/components/Editor.jsx +++ b/client/modules/IDE/components/Editor.jsx @@ -298,7 +298,6 @@ class Editor extends React.Component { getContent() { const content = this._cm.getValue(); - console.log(content); const updatedFile = Object.assign({}, this.props.file, { content }); return updatedFile; } From beebe7eba487b4cf22262157ecbf8a337337ac89 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Thu, 22 Apr 2021 17:04:38 -0400 Subject: [PATCH 37/37] Fix the SketchList snapshot test (maybe) --- .../__snapshots__/SketchList.unit.test.jsx.snap | 8 ++++---- client/testData/testReduxStore.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap b/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap index ccff886e1a..c4e9752218 100644 --- a/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap +++ b/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap @@ -75,10 +75,10 @@ exports[` snapshot testing 1`] = ` - Feb 25, 2021, 11:58:14 PM + Feb 26, 2021, 4:58:14 AM - Feb 25, 2021, 11:58:29 PM + Feb 26, 2021, 4:58:29 AM snapshot testing 1`] = ` - Feb 23, 2021, 12:40:43 PM + Feb 23, 2021, 5:40:43 PM - Feb 23, 2021, 12:40:43 PM + Feb 23, 2021, 5:40:43 PM