Unverified Commit e1f4cf0d authored by Marcos Iglesias's avatar Marcos Iglesias Committed by GitHub

refactor: Component file structure reorganization (#816)

* Moving Footer to the features folder
Signed-off-by: 's avatarMarcos Iglesias <miglesiasvalle@lyft.com>

* Moving NavBar into features folder
Signed-off-by: 's avatarMarcos Iglesias <miglesiasvalle@lyft.com>

* Moving Navbar
Signed-off-by: 's avatarMarcos Iglesias <miglesiasvalle@lyft.com>

* Moving feedback into features
Signed-off-by: 's avatarMarcos Iglesias <miglesiasvalle@lyft.com>

* Moving constants file to the root
Signed-off-by: 's avatarMarcos Iglesias <miglesiasvalle@lyft.com>

* Moving ColumnList into the features folder
Signed-off-by: 's avatarMarcos Iglesias <miglesiasvalle@lyft.com>

* Updates doc about testing
Signed-off-by: 's avatarMarcos Iglesias <miglesiasvalle@lyft.com>

* Adding some eslint ignore comments
Signed-off-by: 's avatarMarcos Iglesias <miglesiasvalle@lyft.com>

* Updating betterer results
Signed-off-by: 's avatarMarcos Iglesias <miglesiasvalle@lyft.com>
parent 31e565d6
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -12,9 +12,13 @@ const setup = (propOverrides?: Partial<AlertProps>) => {
onAction: jest.fn(),
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = mount(<Alert {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('Alert', () => {
......
......@@ -13,8 +13,12 @@ describe('AvatarLabel', () => {
const props: AvatarLabelProps = {
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow(<AvatarLabel {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('render', () => {
......
......@@ -44,10 +44,13 @@ const setup = (propOverrides?: Partial<BadgeListProps>) => {
onBadgeClick: () => {},
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = mount<BadgeListProps>(<BadgeList {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('BadgeList', () => {
......
......@@ -16,6 +16,7 @@ describe('EditableSection', () => {
...propOverrides,
};
const wrapper = shallow<EditableSection>(
// eslint-disable-next-line react/jsx-props-no-spreading
<EditableSection {...props}>{children}</EditableSection>
);
return { wrapper, props };
......
......@@ -27,8 +27,12 @@ describe('EditableText', () => {
value: 'currentValue',
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<EditableText>(<EditableText {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
const { props, wrapper } = setup();
const instance = wrapper.instance();
......
......@@ -18,6 +18,7 @@ describe('EntityCardSection', () => {
contentRenderer: jest.fn(() => <div>HI!</div>),
isEditable: true,
};
// eslint-disable-next-line react/jsx-props-no-spreading
subject = shallow(<EntityCardSection {...props} />);
});
......
......@@ -16,6 +16,7 @@ describe('Flag', () => {
props = {
text: 'Testing',
};
// eslint-disable-next-line react/jsx-props-no-spreading
subject = shallow(<Flag {...props} />);
});
......
......@@ -19,6 +19,7 @@ describe('CheckBoxItem', () => {
...propOverrides,
};
const wrapper = shallow(
// eslint-disable-next-line react/jsx-props-no-spreading
<CheckBoxItem {...props}>{expectedChild}</CheckBoxItem>
);
return { props, wrapper };
......
......@@ -26,8 +26,12 @@ describe('OwnerEditor', () => {
resourceType: ResourceType.table,
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = mount<OwnerEditor>(<OwnerEditor {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('render', () => {
......
......@@ -27,9 +27,13 @@ const setup = (propOverrides?: Partial<PopularTablesProps>) => {
getPopularTables: jest.fn(),
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<PopularTables>(<PopularTables {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('PopularTables', () => {
......
......@@ -13,8 +13,12 @@ describe('Preloader', () => {
getBookmarks: jest.fn(),
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<Preloader>(<Preloader {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('componentDidMount', () => {
......
......@@ -33,6 +33,7 @@ describe('PaginatedApiResourceList', () => {
...propOverrides,
};
const wrapper = shallow<PaginatedApiResourceList>(
// eslint-disable-next-line react/jsx-props-no-spreading
<PaginatedApiResourceList {...props} />
);
return { props, wrapper };
......
......@@ -16,19 +16,35 @@ describe('ResourceList', () => {
const setup = (propOverrides?: Partial<ResourceListProps>) => {
const props: ResourceListProps = {
allItems: [
{ type: ResourceType.table },
{ type: ResourceType.table },
{ type: ResourceType.table },
{ type: ResourceType.table },
{ type: ResourceType.table },
{ type: ResourceType.table },
{
type: ResourceType.table,
},
{
type: ResourceType.table,
},
{
type: ResourceType.table,
},
{
type: ResourceType.table,
},
{
type: ResourceType.table,
},
{
type: ResourceType.table,
},
],
itemsPerPage: 4,
source: 'testSource',
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<ResourceList>(<ResourceList {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('onViewAllToggle', () => {
......
......@@ -7,14 +7,14 @@ import { shallow } from 'enzyme';
import { Link } from 'react-router-dom';
import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon';
import { ResourceType, DashboardResource } from 'interfaces';
import { ResourceType } from 'interfaces';
import * as ConfigUtils from 'config/config-utils';
import * as DateUtils from 'utils/dateUtils';
import { NO_TIMESTAMP_TEXT } from 'components/constants';
import { dashboardSummary } from 'fixtures/metadata/dashboard';
import { NO_TIMESTAMP_TEXT } from '../../../../constants';
import * as Constants from './constants';
import DashboardListItem, { DashboardListItemProps } from './index';
......@@ -44,6 +44,7 @@ describe('DashboardListItem', () => {
...propOverrides,
};
const wrapper = shallow<DashboardListItem>(
// eslint-disable-next-line react/jsx-props-no-spreading
<DashboardListItem {...props} />
);
return { props, wrapper };
......
......@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
import * as React from 'react';
import * as Avatar from 'react-avatar';
import { Link } from 'react-router-dom';
import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon';
......@@ -13,7 +12,7 @@ import { formatDate } from 'utils/dateUtils';
import { ResourceType, DashboardResource } from 'interfaces';
import { NO_TIMESTAMP_TEXT } from 'components/constants';
import { NO_TIMESTAMP_TEXT } from '../../../../constants';
import * as Constants from './constants';
import { LoggingParams } from '../types';
......
......@@ -20,8 +20,12 @@ describe('SchemaInfo', () => {
placement: 'bottom',
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow(<SchemaInfo {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('render', () => {
......
......@@ -31,7 +31,10 @@ const getDBIconClassSpy = jest.spyOn(ConfigUtils, 'getSourceIconClass');
describe('TableListItem', () => {
const setup = (propOverrides?: Partial<TableListItemProps>) => {
const props: TableListItemProps = {
logging: { source: 'src', index: 0 },
logging: {
source: 'src',
index: 0,
},
table: {
type: ResourceType.table,
cluster: '',
......@@ -39,15 +42,23 @@ describe('TableListItem', () => {
description: 'I am the description',
key: '',
last_updated_timestamp: 1553829681,
badges: [{ tag_name: 'badgeName' }],
badges: [
{
tag_name: 'badgeName',
},
],
name: 'tableName',
schema: 'tableSchema',
schema_description: 'schemaDescription',
},
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<TableListItem>(<TableListItem {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('getLink', () => {
......
......@@ -16,7 +16,10 @@ import UserListItem, { UserListItemProps } from '.';
describe('UserListItem', () => {
const setup = (propOverrides?: Partial<UserListItemProps>) => {
const props: UserListItemProps = {
logging: { source: 'src', index: 0 },
logging: {
source: 'src',
index: 0,
},
user: {
type: ResourceType.user,
display_name: 'firstname lastname',
......@@ -36,8 +39,12 @@ describe('UserListItem', () => {
},
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<UserListItem>(<UserListItem {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('renderUserInfo', () => {
......
......@@ -20,6 +20,7 @@ describe('ResourceListItem', () => {
logging: { source: 'src', index: 0 },
item: { type: ResourceType.table },
};
// eslint-disable-next-line react/jsx-props-no-spreading
subject = shallow(<ResourceListItem {...props} />);
});
......
......@@ -14,6 +14,7 @@ const setup = (propsOverrides?: Partial<StatusMarkerProps>) => {
...propsOverrides,
};
const wrapper = mount<typeof ResourceStatusMarker>(
// eslint-disable-next-line react/jsx-props-no-spreading
<ResourceStatusMarker {...props} />
);
return { props, wrapper };
......
......@@ -22,6 +22,7 @@ describe('ResultItem', () => {
titleNode: <div>Hello</div>,
type: 'User',
};
// eslint-disable-next-line react/jsx-props-no-spreading
subject = shallow(<ResultItem {...props} />);
});
......
......@@ -36,8 +36,12 @@ describe('SearchItem', () => {
hasResults: true,
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<SearchItem>(<SearchItem {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('onViewAllResults', () => {
......
......@@ -56,6 +56,7 @@ describe('InlineSearchResults', () => {
...propOverrides,
};
const wrapper = shallow<InlineSearchResults>(
// eslint-disable-next-line react/jsx-props-no-spreading
<InlineSearchResults {...props} />
);
return { props, wrapper };
......
......@@ -14,6 +14,7 @@ const setup = (propOverrides?: Partial<ShimmeringResourceLoaderProps>) => {
...propOverrides,
};
const wrapper = mount<ShimmeringResourceLoaderProps>(
// eslint-disable-next-line react/jsx-props-no-spreading
<ShimmeringResourceLoader {...props} />
);
......
......@@ -14,6 +14,7 @@ const setup = (propOverrides?: Partial<ShimmeringTagListLoaderProps>) => {
...propOverrides,
};
const wrapper = mount<ShimmeringTagListLoaderProps>(
// eslint-disable-next-line react/jsx-props-no-spreading
<ShimmeringTagListLoader {...props} />
);
......
......@@ -27,8 +27,12 @@ describe('TagInfo', () => {
searchTag: jest.fn(),
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<TagInfo>(<TagInfo {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('onClick', () => {
......
......@@ -14,9 +14,13 @@ const setup = (propOverrides?: Partial<ColumnStatsProps>) => {
stats: [],
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = mount<typeof ColumnStats>(<ColumnStats {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('ColumnStats', () => {
......
......@@ -3,7 +3,6 @@
import * as React from 'react';
import { mount } from 'enzyme';
import { Modal } from 'react-bootstrap';
import * as UtilMethods from 'ducks/utilMethods';
......@@ -20,8 +19,12 @@ const setup = (propOverrides?: Partial<ColumnTypeProps>) => {
'row(test_id varchar,test2 row(test2_id varchar,started_at timestamp,ended_at timestamp))',
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = mount<ColumnType>(<ColumnType {...props} />);
return { wrapper, props };
return {
wrapper,
props,
};
};
const { wrapper, props } = setup();
......
......@@ -7,7 +7,7 @@ import { shallow } from 'enzyme';
import AbstractFeedbackForm, {
FeedbackFormProps,
} from 'components/Feedback/FeedbackForm';
} from 'features/Feedback/FeedbackForm';
import { SendingState } from 'interfaces';
import {
BUG_SUMMARY_LABEL,
......@@ -17,7 +17,7 @@ import {
SUBJECT_LABEL,
SUBJECT_PLACEHOLDER,
SUBMIT_TEXT,
} from 'components/Feedback/constants';
} from 'features/Feedback/constants';
import globalState from 'fixtures/globalState';
import { BugReportFeedbackForm, mapDispatchToProps, mapStateToProps } from '.';
......
......@@ -7,7 +7,7 @@ import { shallow } from 'enzyme';
import AbstractFeedbackForm, {
FeedbackFormProps,
} from 'components/Feedback/FeedbackForm';
} from 'features/Feedback/FeedbackForm';
import { SendingState } from 'interfaces';
import {
COMMENTS_PLACEHOLDER,
......@@ -15,7 +15,7 @@ import {
RATING_LOW_TEXT,
RATING_HIGH_TEXT,
SUBMIT_TEXT,
} from 'components/Feedback/constants';
} from 'features/Feedback/constants';
import globalState from 'fixtures/globalState';
import { RatingFeedbackForm, mapDispatchToProps, mapStateToProps } from '.';
......
......@@ -7,7 +7,7 @@ import { shallow } from 'enzyme';
import AbstractFeedbackForm, {
FeedbackFormProps,
} from 'components/Feedback/FeedbackForm';
} from 'features/Feedback/FeedbackForm';
import { SendingState } from 'interfaces';
import {
FEATURE_SUMMARY_LABEL,
......@@ -17,7 +17,7 @@ import {
SUBJECT_LABEL,
SUBJECT_PLACEHOLDER,
SUBMIT_TEXT,
} from 'components/Feedback/constants';
} from 'features/Feedback/constants';
import globalState from 'fixtures/globalState';
import { RequestFeedbackForm, mapDispatchToProps, mapStateToProps } from '.';
......@@ -29,6 +29,7 @@ describe('RequestFeedbackForm', () => {
submitFeedback: jest.fn(),
resetFeedback: jest.fn(),
};
// eslint-disable-next-line react/jsx-props-no-spreading
return shallow(<RequestFeedbackForm {...props} />);
};
......
......@@ -4,14 +4,15 @@
import * as React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
// TODO: Use css-modules instead of 'import'
import './styles.scss';
import { GlobalState } from 'ducks/rootReducer';
import { getLastIndexed } from 'ducks/lastIndexed/reducer';
import { GetLastIndexedRequest } from 'ducks/lastIndexed/types';
import { formatDateTimeLong } from 'utils/dateUtils';
import './styles.scss';
// Props
interface StateFromProps {
lastIndexed: number | null;
......
......@@ -11,7 +11,7 @@ import { Dropdown, MenuItem } from 'react-bootstrap';
import { Link, NavLink } from 'react-router-dom';
import { getMockRouterProps } from 'fixtures/mockRouter';
import Feedback from 'components/Feedback';
import Feedback from 'features/Feedback';
import SearchBar from 'components/common/SearchBar';
import { logClick } from 'ducks/utilMethods';
......
......@@ -21,7 +21,7 @@ import {
getNavLinks,
} from 'config/config-utils';
import Feedback from 'components/Feedback';
import Feedback from 'features/Feedback';
import SearchBar from 'components/common/SearchBar';
import './styles.scss';
......
......@@ -26,8 +26,8 @@ import ProfilePage from './pages/ProfilePage';
import TableDetail from './pages/TableDetailPage';
import Preloader from './components/common/Preloader';
import Footer from './components/Footer';
import NavBar from './components/NavBar';
import Footer from './features/Footer';
import NavBar from './features/NavBar';
import rootReducer from './ducks/rootReducer';
import rootSaga from './ducks/rootSaga';
......
......@@ -13,8 +13,8 @@ import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon';
import ResourceList from 'components/common/ResourceList';
import TabsComponent from 'components/common/TabsComponent';
import { dashboardMetadata } from 'fixtures/metadata/dashboard';
import { NO_TIMESTAMP_TEXT } from 'components/constants';
import { ResourceType } from 'interfaces';
import { NO_TIMESTAMP_TEXT } from '../../constants';
import ChartList from './ChartList';
import DashboardOwnerEditor from './DashboardOwnerEditor';
import ImagePreview from './ImagePreview';
......
......@@ -22,7 +22,6 @@ import TabsComponent, { TabInfo } from 'components/common/TabsComponent';
import ResourceStatusMarker from 'components/common/ResourceStatusMarker';
import ResourceList from 'components/common/ResourceList';
import TagInput from 'components/common/Tags/TagInput';
import { NO_TIMESTAMP_TEXT } from 'components/constants';
import { getSourceDisplayName, getSourceIconClass } from 'config/config-utils';
import { formatDateTimeShort } from 'utils/dateUtils';
......@@ -30,6 +29,7 @@ import { getLoggingParams } from 'utils/logUtils';
import { ResourceType } from 'interfaces';
import { DashboardMetadata } from 'interfaces/Dashboard';
import { NO_TIMESTAMP_TEXT } from '../../constants';
import {
ADD_DESC_TEXT,
EDIT_DESC_TEXT,
......
......@@ -38,7 +38,9 @@ jest.mock('config/config-utils', () => ({
describe('ProfilePage', () => {
const setup = (propOverrides?: Partial<ProfilePageProps>) => {
const routerProps = getMockRouterProps<RouteProps>(
{ userId: 'test0' },
{
userId: 'test0',
},
undefined
);
const props: ProfilePageProps = {
......@@ -46,10 +48,18 @@ describe('ProfilePage', () => {
resourceRelations: {
[ResourceType.table]: {
bookmarks: [
{ type: ResourceType.table },
{ type: ResourceType.table },
{ type: ResourceType.table },
{ type: ResourceType.table },
{
type: ResourceType.table,
},
{
type: ResourceType.table,
},
{
type: ResourceType.table,
},
{
type: ResourceType.table,
},
],
read: [],
own: [],
......@@ -67,8 +77,12 @@ describe('ProfilePage', () => {
...routerProps,
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<ProfilePage>(<ProfilePage {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('componentDidMount', () => {
......
......@@ -38,8 +38,12 @@ describe('CheckBoxFilter', () => {
updateFilter: jest.fn(),
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<CheckBoxFilter>(<CheckBoxFilter {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('createCheckBoxItem', () => {
......
......@@ -28,8 +28,12 @@ const setup = (propOverrides?: Partial<FilterSectionProps>) => {
type: FilterType.INPUT_SELECT,
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<FilterSection>(<FilterSection {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('FilterSection', () => {
......
......@@ -27,8 +27,12 @@ describe('InputFilter', () => {
updateFilter: jest.fn(),
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<InputFilter>(<InputFilter {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('constructor', () => {
......
......@@ -28,8 +28,14 @@ describe('SearchFilter', () => {
categoryId: 'database',
helpText: 'This is what to do',
options: [
{ value: 'bigquery', label: 'BigQuery' },
{ value: 'hive', label: 'Hive' },
{
value: 'bigquery',
label: 'BigQuery',
},
{
value: 'hive',
label: 'Hive',
},
],
title: 'Source',
type: FilterType.CHECKBOX_SELECT,
......@@ -43,8 +49,12 @@ describe('SearchFilter', () => {
],
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<SearchFilter>(<SearchFilter {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('createFilterSection', () => {
......
......@@ -52,15 +52,22 @@ describe('ExploreButton', () => {
value: 'partition_value',
},
table_readers: [],
source: { source: '', source_type: '' },
source: {
source: '',
source_type: '',
},
resource_reports: [],
watermarks: [],
programmatic_descriptions: {},
...tableDataOverrides,
},
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<ExploreButton>(<ExploreButton {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('generateUrl', () => {});
......
......@@ -21,6 +21,7 @@ describe('RequestDescriptionText', () => {
...propOverrides,
};
const wrapper = shallow<RequestDescriptionText>(
// eslint-disable-next-line react/jsx-props-no-spreading
<RequestDescriptionText {...props} />
);
return { props, wrapper };
......
......@@ -64,6 +64,7 @@ describe('RequestMetadataForm', () => {
...propOverrides,
};
const wrapper = shallow<RequestMetadataForm>(
// eslint-disable-next-line react/jsx-props-no-spreading
<RequestMetadataForm {...props} />
);
return { props, wrapper };
......
......@@ -27,6 +27,7 @@ const setup = (propOverrides?: Partial<TableDashboardResourceListProps>) => {
...propOverrides,
};
const wrapper = shallow<TableDashboardResourceList>(
// eslint-disable-next-line react/jsx-props-no-spreading
<TableDashboardResourceList {...props} />
);
......
......@@ -27,8 +27,12 @@ describe('TableIssues', () => {
getIssues: jest.fn(),
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = shallow<TableIssues>(<TableIssues {...props} />);
return { props, wrapper };
return {
props,
wrapper,
};
};
describe('render', () => {
......
......@@ -45,6 +45,7 @@ const setup = (
...routerProps,
...propOverrides,
};
// eslint-disable-next-line react/jsx-props-no-spreading
const wrapper = mount<TableDetail>(<TableDetail {...props} />);
return { props, wrapper };
......
......@@ -35,7 +35,7 @@ import TagInput from 'components/common/Tags/TagInput';
import EditableText from 'components/common/EditableText';
import LoadingSpinner from 'components/common/LoadingSpinner';
import EditableSection from 'components/common/EditableSection';
import ColumnList from 'components/ColumnList';
import ColumnList from 'features/ColumnList';
import { formatDateTimeShort } from 'utils/dateUtils';
import { getLoggingParams } from 'utils/logUtils';
......
......@@ -5,31 +5,36 @@ This document serves as reference for current practices and patterns that we wan
We aim to maintain a reasonably consistent code base through these practices and welcome PRs to update and improve these recommendations.
## Application
### Unit Testing
We use [Jest](https://jestjs.io/) as our test framework. We leverage utility methods from [Enzyme](https://airbnb.io/enzyme/) to test React components, and use [redux-saga-test-plan](https://github.com/jfairbank/redux-saga-test-plan#documentation) to test our `redux-saga` middleware logic.
#### General
1. Leverage TypeScript to prevent bugs in unit tests and ensure that code is tested with inputs that match the defined interfaces and types. Adding and updating test [fixtures](https://github.com/lyft/amundsenfrontendlibrary/tree/master/amundsen_application/static/js/fixtures) helps to provide re-useable pieces of typed test data or mock implementations for this purpose.
2. Leverage `beforeAll()`/`beforeEach()` for test setup when applicable. Leverage `afterAll()`/`afterEach` for test teardown when applicable to remove any side effects of the test block. For example if a mock implementation of a method was created in `beforeAll()`, the original implementation should be restored in `afterAll()`. See Jest's [setup-teardown documentation](https://jestjs.io/docs/en/setup-teardown) for further understanding.
3. Use descriptive title strings. To assist with debugging we should be able to understand what a test is checking for and under what conditions.
4. Become familiar with the variety of Jest [matchers](https://jestjs.io/docs/en/expect) that are available. Understanding the nuances of different matchers and the cases they are each ideal for assists with writing more robust tests. For example, there are many different ways to verify objects and the best matcher to use will depend on what exactly we are testing for. Examples:
* If asserting that `inputObject` is assigned to variable `x`, asserting the equivalence of `x` using `.toBe()` creates a more robust test for this case because `.toBe()` will verify that the variable is actually referencing the given object. Contrast this to a matcher like `.toEqual()` which will verify whether or not the object happens to have a particular set of properties and values. In this case using `.toEqual()` would risk hiding bugs where `x` is not actually referencing `inputObject` as expected, yet happens to have the same key value pairs perhaps due to side effects in the code.
* If asserting that `outputObject` matches `expectedObject`, asserting the equivalence of each property on `outputObject` using `.toBe()` or asserting the equality of the two objects using `.toMatchObject()` is useful when we only care that certain values exist on `outputObject`. However if it matters that certain values **do not** exist on `outputObject` -- as is the case with reducer outputs -- `.toEqual()` is a more robust alternative as it compares all properties on both objects for equivalence.
5. When testing logic that makes use of JavaScript's *Date* object, note that our Jest scripts are configured to run in the UTC timezone. Developers should either:
* Mock the *Date* object and its methods' return values, and run assertions based on the mock values.
* Create assertions knowing that the unit test suite will run as if we are in the UTC timezone.
- If asserting that `inputObject` is assigned to variable `x`, asserting the equivalence of `x` using `.toBe()` creates a more robust test for this case because `.toBe()` will verify that the variable is actually referencing the given object. Contrast this to a matcher like `.toEqual()` which will verify whether or not the object happens to have a particular set of properties and values. In this case using `.toEqual()` would risk hiding bugs where `x` is not actually referencing `inputObject` as expected, yet happens to have the same key value pairs perhaps due to side effects in the code.
- If asserting that `outputObject` matches `expectedObject`, asserting the equivalence of each property on `outputObject` using `.toBe()` or asserting the equality of the two objects using `.toMatchObject()` is useful when we only care that certain values exist on `outputObject`. However if it matters that certain values **do not** exist on `outputObject` -- as is the case with reducer outputs -- `.toEqual()` is a more robust alternative as it compares all properties on both objects for equivalence.
5. When testing logic that makes use of JavaScript's _Date_ object, note that our Jest scripts are configured to run in the UTC timezone. Developers should either:
- Mock the _Date_ object and its methods' return values, and run assertions based on the mock values.
- Create assertions knowing that the unit test suite will run as if we are in the UTC timezone.
6. Code coverage is important to track but it only informs us of what code was actually run and executed during the test. The onus is on the developer to focus on use case coverage and make sure that right assertions are run so that all logic is adequately tested.
#### React
1. Enzyme provides 3 different utilities for rendering React components for testing. We recommend using `shallow` rendering to start off. If a component has a use case that requires full DOM rendering, those cases will become apparent. See Enzyme's [api documentation](https://airbnb.io/enzyme/docs/api/) to read more about the recommendations for each option.
1. Enzyme provides 3 different utilities for rendering React components for testing. We recommend using `mount` rendering so you can dive deep on the rendered output.
2. Create a re-useable `setup()` function that will take any arguments needed to test conditional logic.
3. Look for opportunities to organize tests a way such that one `setup()` can be used to test assertions that occur under the same conditions. For example, a test block for a method that has no conditional logic should only have one `setup()`. However, it is **not** recommended to share a `setup()` result across tests for different methods, or across tests for a method that has a dependency on a mutable piece of state. The reason is that we risk propagating side effects from one test block to another.
4. Consider refactoring components or other files if they become burdensome to test. Potential options include (but are not limited to):
* Create subcomponents for large components. This is also especially useful for reducing the burden of updating tests when component layouts are changed.
* Break down large functions into smaller functions. Unit test the logic of the smaller functions individually, and mock their results when testing the larger function.
* Export constants from a separate file for hardcoded values and import them into the relevant source files and test files. This is especially helpful for strings.
- Create subcomponents for large components. This is also especially useful for reducing the burden of updating tests when component layouts are changed.
- Break down large functions into smaller functions. Unit test the logic of the smaller functions individually, and mock their results when testing the larger function.
- Export constants from a separate file for hardcoded values and import them into the relevant source files and test files. This is especially helpful for strings.
#### Redux
1. Because the majority of Redux code consists of functions, we unit test those methods as usual and ensure the functions produce the expected output for any given input. See Redux's documentation on testing [action creators](https://redux.js.org/recipes/writing-tests#action-creators), [async action creators](https://redux.js.org/recipes/writing-tests#async-action-creators), and [reducers](https://redux.js.org/recipes/writing-tests#reducers), or check out examples in our code.
2. Unless an action creator includes any logic other than returning the action, unit testing the reducer and saga middleware logic is sufficient and provides the most value.
3. `redux-saga` generator functions can be tested by iterating through it step-by-step and running assertions at each step, or by executing the entire saga and running assertions on the side effects. See redux-saga's documentation on [testing sagas](https://redux-saga.js.org/docs/advanced/Testing.html) for a wider breadth of examples.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment