From c2a223bbb4674515024bb005231492d4d4d5d94c Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Thu, 4 Apr 2019 09:17:33 -0400 Subject: [PATCH 1/4] delete unused method --- .../NotificationsList/Notifications.list.jsx | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/components/NotificationsList/Notifications.list.jsx b/src/components/NotificationsList/Notifications.list.jsx index 1c53ec6f31..80ed1c2407 100644 --- a/src/components/NotificationsList/Notifications.list.jsx +++ b/src/components/NotificationsList/Notifications.list.jsx @@ -12,10 +12,7 @@ import DataListToolbar from '../DataListToolbar'; import NotificationListItem from './NotificationListItem'; import Pagination from '../Pagination'; -import { - encodeQueryString, - parseQueryString, -} from '../../qs'; +import { parseQueryString } from '../../qs'; class Notifications extends Component { columns = [ @@ -55,7 +52,6 @@ class Notifications extends Component { this.handleSetPage = this.handleSetPage.bind(this); this.handleSelectAll = this.handleSelectAll.bind(this); this.toggleNotification = this.toggleNotification.bind(this); - this.updateUrl = this.updateUrl.bind(this); this.createError = this.createError.bind(this); this.createSuccess = this.createSuccess.bind(this); this.readNotifications = this.readNotifications.bind(this); @@ -120,16 +116,6 @@ class Notifications extends Component { this.handleSort(sortedColumnKey, sortOrder); } - updateUrl (queryParams) { - const { history, location, match } = this.props; - const pathname = match.url; - const search = `?${encodeQueryString(queryParams)}`; - - if (search !== location.search) { - history.replace({ pathname, search }); - } - } - async createError (id, isCurrentlyOn) { const { onCreateError, match } = this.props; const postParams = { id }; From 89ecddf6624a302f26da77635f84d2c22bcaba81 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Fri, 5 Apr 2019 10:52:52 -0400 Subject: [PATCH 2/4] refactor OrganizationTeams/OrganizationTeamsList --- .../components/OrganizationTeamsList.jsx | 160 +++++------------- .../screens/Organization/Organization.jsx | 5 +- .../Organization/OrganizationTeams.jsx | 85 ++++++++-- 3 files changed, 119 insertions(+), 131 deletions(-) diff --git a/src/pages/Organizations/components/OrganizationTeamsList.jsx b/src/pages/Organizations/components/OrganizationTeamsList.jsx index 83a5616198..0691d42a65 100644 --- a/src/pages/Organizations/components/OrganizationTeamsList.jsx +++ b/src/pages/Organizations/components/OrganizationTeamsList.jsx @@ -1,24 +1,17 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; - import { DataList, DataListItem, DataListCell, Text, TextContent, TextVariants } from '@patternfly/react-core'; - import { I18n, i18nMark } from '@lingui/react'; import { t } from '@lingui/macro'; - -import { - Link -} from 'react-router-dom'; +import { withRouter, Link } from 'react-router-dom'; import Pagination from '../../../components/Pagination'; import DataListToolbar from '../../../components/DataListToolbar'; -import { - parseQueryString, -} from '../../../qs'; +import { encodeQueryString } from '../../../qs'; const detailWrapperStyle = { display: 'grid', @@ -45,125 +38,55 @@ class OrganizationTeamsList extends React.Component { constructor (props) { super(props); - const { page, page_size } = this.readQueryParams(); - this.state = { - page, - page_size, - count: 0, - sortOrder: 'ascending', - sortedColumnKey: 'name', - results: [], + error: null, }; - this.readOrganizationTeamsList = this.readOrganizationTeamsList.bind(this); this.handleSetPage = this.handleSetPage.bind(this); this.handleSort = this.handleSort.bind(this); - this.readQueryParams = this.readQueryParams.bind(this); } - componentDidMount () { - const queryParams = this.readQueryParams(); - try { - this.readOrganizationTeamsList(queryParams); - } catch (error) { - this.setState({ error }); + getPageCount () { + const { itemCount, queryParams: { page_size } } = this.props; + return Math.ceil(itemCount / page_size); + } + + getSortOrder () { + const { queryParams } = this.props; + if (queryParams.order_by && queryParams.order_by.startsWith('-')) { + return 'descending'; } + return 'ascending'; } handleSetPage (pageNumber, pageSize) { - const { sortOrder, sortedColumnKey } = this.state; - const page = parseInt(pageNumber, 10); - const page_size = parseInt(pageSize, 10); - let order_by = sortedColumnKey; - - // Preserve sort order when paginating - if (sortOrder === 'descending') { - order_by = `-${order_by}`; - } - - const queryParams = this.readQueryParams({ page, page_size, order_by }); - - this.readOrganizationTeamsList(queryParams); + this.pushHistoryState({ + page: pageNumber, + page_size: pageSize, + }); } handleSort (sortedColumnKey, sortOrder) { - const { page_size } = this.state; - - let order_by = sortedColumnKey; - - if (sortOrder === 'descending') { - order_by = `-${order_by}`; - } - - const queryParams = this.readQueryParams({ order_by, page_size }); - - this.readOrganizationTeamsList(queryParams); + this.pushHistoryState({ + order_by: sortOrder === 'ascending' ? sortedColumnKey : `-${sortedColumnKey}`, + }); } - readQueryParams (overrides = {}) { - const { location } = this.props; - const { search } = location; - - const searchParams = parseQueryString(search.substring(1)); - - return Object.assign({}, this.defaultParams, searchParams, overrides); - } - - async readOrganizationTeamsList (queryParams) { - const { match, onReadTeamsList } = this.props; - - const { page, page_size, order_by } = queryParams; - - let sortOrder = 'ascending'; - let sortedColumnKey = order_by; - - if (order_by.startsWith('-')) { - sortOrder = 'descending'; - sortedColumnKey = order_by.substring(1); - } - - try { - const { data: - { count = 0, results = [] } - } = await onReadTeamsList(match.params.id, queryParams); - const pageCount = Math.ceil(count / page_size); - - const stateToUpdate = { - count, - page, - pageCount, - page_size, - sortOrder, - sortedColumnKey, - results - }; - - this.setState(stateToUpdate); - } catch (error) { - this.setState({ error }); - } + pushHistoryState (params) { + const { history } = this.props; + const { pathname } = history.location; + const qs = encodeQueryString(params); + history.push(`${pathname}?${qs}`); } render () { - const { - results, - error, - count, - page_size, - pageCount, - page, - sortedColumnKey, - sortOrder - } = this.state; + const { teams, itemCount, queryParams } = this.props; + const { error } = this.state; return ( {({ i18n }) => ( - {!error && results.length <= 0 && ( -

Loading...

// TODO: replace with proper loading state - )} - {error && results.length <= 0 && ( + {error && (
{error.message}
{error.response && ( @@ -171,17 +94,17 @@ class OrganizationTeamsList extends React.Component { )}
// TODO: replace with proper error handling )} - {results.length > 0 && ( + {teams.length > 0 && ( { }} onSort={this.handleSort} /> - {results.map(({ url, id, name }) => ( + {teams.map(({ url, id, name }) => ( @@ -194,10 +117,10 @@ class OrganizationTeamsList extends React.Component { ))} @@ -210,7 +133,14 @@ class OrganizationTeamsList extends React.Component { } OrganizationTeamsList.propTypes = { - onReadTeamsList: PropTypes.func.isRequired, + teams: PropTypes.arrayOf({}).isRequired, + itemCount: PropTypes.number.isRequired, + queryParams: PropTypes.shape({ + page: PropTypes.number, + page_size: PropTypes.number, + order_by: PropTypes.string, + }).isRequired }; -export default OrganizationTeamsList; +export { OrganizationTeamsList as _OrganizationTeamsList }; +export default withRouter(OrganizationTeamsList); diff --git a/src/pages/Organizations/screens/Organization/Organization.jsx b/src/pages/Organizations/screens/Organization/Organization.jsx index 33788d0890..e1bf14eabc 100644 --- a/src/pages/Organizations/screens/Organization/Organization.jsx +++ b/src/pages/Organizations/screens/Organization/Organization.jsx @@ -159,10 +159,9 @@ class Organization extends Component { path="/organizations/:id/teams" render={() => ( )} /> diff --git a/src/pages/Organizations/screens/Organization/OrganizationTeams.jsx b/src/pages/Organizations/screens/Organization/OrganizationTeams.jsx index 0fd6f9e517..b6cf169e39 100644 --- a/src/pages/Organizations/screens/Organization/OrganizationTeams.jsx +++ b/src/pages/Organizations/screens/Organization/OrganizationTeams.jsx @@ -1,34 +1,93 @@ import React from 'react'; +import PropTypes from 'prop-types'; +import { withRouter } from 'react-router-dom'; import OrganizationTeamsList from '../../components/OrganizationTeamsList'; +import { parseQueryString } from '../../../../qs'; + +const DEFAULT_QUERY_PARAMS = { + page: 1, + page_size: 5, + order_by: 'name', +}; class OrganizationTeams extends React.Component { constructor (props) { super(props); this.readOrganizationTeamsList = this.readOrganizationTeamsList.bind(this); + + this.state = { + isLoading: false, + error: null, + itemCount: 0, + teams: [], + }; } - readOrganizationTeamsList (id, params) { - const { api } = this.props; - return api.readOrganizationTeamsList(id, params); + componentDidMount () { + this.readOrganizationTeamsList(); + } + + componentDidUpdate (prevProps) { + const { location } = this.props; + if (location !== prevProps.location) { + this.readOrganizationTeamsList(); + } + } + + getQueryParams () { + const { searchString } = this.props; + const searchParams = parseQueryString(searchString.substring(1)); + + return { + ...DEFAULT_QUERY_PARAMS, + ...searchParams, + }; + } + + async readOrganizationTeamsList () { + const { api, id } = this.props; + const params = this.getQueryParams(); + this.setState({ isLoading: true }); + try { + const { + data: { count = 0, results = [] }, + } = await api.readOrganizationTeamsList(id, params); + this.setState({ + itemCount: count, + teams: results, + isLoading: false, + }); + } catch (error) { + this.setState({ + error, + isLoading: false + }); + } } render () { - const { - location, - match, - history, - } = this.props; + const { teams, itemCount, isLoading } = this.state; + + if (isLoading) { + return
Loading...
; + } return ( ); } } -export default OrganizationTeams; +OrganizationTeams.propTypes = { + id: PropTypes.number.isRequired, + searchString: PropTypes.string.isRequired, + api: PropTypes.shape().isRequired, // TODO: remove? +}; + +export { OrganizationTeams as _OrganizationTeams }; +export default withRouter(OrganizationTeams); From 70137dea5a60a8d4d4f91523bbcddc8e7da947fb Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Mon, 8 Apr 2019 10:05:22 -0400 Subject: [PATCH 3/4] fix tests for OrganizationTeams, OrganizationTeamsList --- .../components/OrganizationForm.test.jsx | 3 +- .../components/OrganizationTeamsList.test.jsx | 145 ++++++++-------- .../Organization/OrganizationTeams.test.jsx | 158 ++++++++++++++---- __tests__/testUtils.js | 4 + jest.config.js | 2 +- package-lock.json | 43 +++-- package.json | 1 + .../components/OrganizationTeamsList.jsx | 47 ++++-- .../screens/Organization/Organization.jsx | 1 - .../Organization/OrganizationTeams.jsx | 37 ++-- 10 files changed, 289 insertions(+), 152 deletions(-) create mode 100644 __tests__/testUtils.js diff --git a/__tests__/pages/Organizations/components/OrganizationForm.test.jsx b/__tests__/pages/Organizations/components/OrganizationForm.test.jsx index 55c30fae3d..8ef06dcdfd 100644 --- a/__tests__/pages/Organizations/components/OrganizationForm.test.jsx +++ b/__tests__/pages/Organizations/components/OrganizationForm.test.jsx @@ -4,8 +4,7 @@ import { MemoryRouter } from 'react-router-dom'; import { I18nProvider } from '@lingui/react'; import { ConfigContext } from '../../../../src/context'; import OrganizationForm from '../../../../src/pages/Organizations/components/OrganizationForm'; - -const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); +import { sleep } from '../../../testUtils'; describe('', () => { let api; diff --git a/__tests__/pages/Organizations/components/OrganizationTeamsList.test.jsx b/__tests__/pages/Organizations/components/OrganizationTeamsList.test.jsx index 87da53fb38..41282262f5 100644 --- a/__tests__/pages/Organizations/components/OrganizationTeamsList.test.jsx +++ b/__tests__/pages/Organizations/components/OrganizationTeamsList.test.jsx @@ -1,16 +1,17 @@ import React from 'react'; import { mount } from 'enzyme'; -import { MemoryRouter } from 'react-router-dom'; +import { MemoryRouter, Router } from 'react-router-dom'; +import { createMemoryHistory } from 'history'; import { I18nProvider } from '@lingui/react'; - +import { sleep } from '../../../testUtils'; import OrganizationTeamsList from '../../../../src/pages/Organizations/components/OrganizationTeamsList'; const mockData = [ - { - id: 1, - name: 'boo', - url: '/foo/bar/' - } + { id: 1, name: 'one', url: '/org/team/1' }, + { id: 2, name: 'two', url: '/org/team/2' }, + { id: 3, name: 'three', url: '/org/team/3' }, + { id: 4, name: 'four', url: '/org/team/4' }, + { id: 5, name: 'five', url: '/org/team/5' }, ]; describe('', () => { @@ -23,85 +24,81 @@ describe('', () => { {}} - removeRole={() => {}} + teams={mockData} + itemCount={7} + queryParams={{ + page: 1, + page_size: 5, + order_by: 'name', + }} /> ); }); - test('api response data passed to component gets set to state properly', (done) => { - const wrapper = mount( - - - ({ data: { count: 1, results: mockData } })} - /> - - - ).find('OrganizationTeamsList'); - - setImmediate(() => { - expect(wrapper.state().results).toEqual(mockData); - done(); + // should navigate when datalisttoolbar changes sorting + test('should navigate when DataListToolbar calls onSort prop', async () => { + const history = createMemoryHistory({ + initialEntries: ['/organizations/1/teams'], }); + const wrapper = mount( + + + + + + ); + + const toolbar = wrapper.find('DataListToolbar'); + expect(toolbar.prop('sortedColumnKey')).toEqual('name'); + expect(toolbar.prop('sortOrder')).toEqual('ascending'); + toolbar.prop('onSort')('name', 'descending'); + expect(history.location.search).toEqual('?order_by=-name'); + await sleep(0); + wrapper.update(); + + expect(toolbar.prop('sortedColumnKey')).toEqual('name'); + // TODO: this assertion required updating queryParams prop. Consider + // fixing after #147 is done: + // expect(toolbar.prop('sortOrder')).toEqual('descending'); + toolbar.prop('onSort')('name', 'ascending'); + expect(history.location.search).toEqual('?order_by=name'); }); - test('handleSort being passed properly to DataListToolbar component', async (done) => { - const handleSort = jest.spyOn(OrganizationTeamsList.prototype, 'handleSort'); - const wrapper = mount( - - - ({ data: { count: 1, results: mockData } })} - /> - - - ).find('OrganizationTeamsList'); - expect(handleSort).not.toHaveBeenCalled(); - - setImmediate(() => { - const rendered = wrapper.update(); - rendered.find('button[aria-label="Sort"]').simulate('click'); - expect(handleSort).toHaveBeenCalled(); - done(); + test('should navigate to page when Pagination calls onSetPage prop', () => { + const history = createMemoryHistory({ + initialEntries: ['/organizations/1/teams'], }); - }); - - test('handleSetPage calls readQueryParams and readOrganizationTeamsList ', () => { - const spyQueryParams = jest.spyOn(OrganizationTeamsList.prototype, 'readQueryParams'); - const spyFetch = jest.spyOn(OrganizationTeamsList.prototype, 'readOrganizationTeamsList'); const wrapper = mount( - - + + ({ data: { count: 1, results: mockData } })} + teams={mockData} + itemCount={7} + queryParams={{ + page: 1, + page_size: 5, + order_by: 'name', + }} /> - - - ).find('OrganizationTeamsList'); - wrapper.instance().handleSetPage(2, 10); - expect(spyQueryParams).toHaveBeenCalled(); - expect(spyFetch).toHaveBeenCalled(); - wrapper.setState({ sortOrder: 'descending' }); - wrapper.instance().handleSetPage(3, 5); - expect(spyQueryParams).toHaveBeenCalled(); - expect(spyFetch).toHaveBeenCalled(); - const queryParamCalls = spyQueryParams.mock.calls; - // make sure last two readQueryParams calls - // were called with the correct arguments - expect(queryParamCalls[queryParamCalls.length - 2][0]) - .toEqual({ order_by: 'name', page: 2, page_size: 10 }); - expect(queryParamCalls[queryParamCalls.length - 1][0]) - .toEqual({ order_by: '-name', page: 3, page_size: 5 }); + + + ); + + const pagination = wrapper.find('Pagination'); + pagination.prop('onSetPage')(2, 5); + expect(history.location.search).toEqual('?page=2&page_size=5'); + wrapper.update(); + pagination.prop('onSetPage')(1, 25); + expect(history.location.search).toEqual('?page=1&page_size=25'); }); }); diff --git a/__tests__/pages/Organizations/screens/Organization/OrganizationTeams.test.jsx b/__tests__/pages/Organizations/screens/Organization/OrganizationTeams.test.jsx index d0ef146694..d9f8a1c7c1 100644 --- a/__tests__/pages/Organizations/screens/Organization/OrganizationTeams.test.jsx +++ b/__tests__/pages/Organizations/screens/Organization/OrganizationTeams.test.jsx @@ -1,45 +1,135 @@ import React from 'react'; -import { mount } from 'enzyme'; -import { MemoryRouter } from 'react-router-dom'; +import { mount, shallow } from 'enzyme'; +import { MemoryRouter, Router } from 'react-router-dom'; +import { I18nProvider } from '@lingui/react'; +import { createMemoryHistory } from 'history'; +import { sleep } from '../../../../testUtils'; +import OrganizationTeams, { _OrganizationTeams } from '../../../../../src/pages/Organizations/screens/Organization/OrganizationTeams'; +import OrganizationTeamsList from '../../../../../src/pages/Organizations/components/OrganizationTeamsList'; -import OrganizationTeams from '../../../../../src/pages/Organizations/screens/Organization/OrganizationTeams'; - -const mockAPITeamsList = { - foo: 'bar', +const listData = { + data: { + count: 7, + results: [ + { id: 1, name: 'one', url: '/org/team/1' }, + { id: 2, name: 'two', url: '/org/team/2' }, + { id: 3, name: 'three', url: '/org/team/3' }, + { id: 4, name: 'four', url: '/org/team/4' }, + { id: 5, name: 'five', url: '/org/team/5' }, + ] + } }; -const readOrganizationTeamsList = () => Promise.resolve(mockAPITeamsList); - describe('', () => { - test('initially renders succesfully', () => { - mount( - - - + test('renders succesfully', () => { + shallow( + <_OrganizationTeams + id={1} + searchString="" + location={{ search: '', pathname: '/organizations/1/teams' }} + api={{ + readOrganizationTeamsList: jest.fn(), + }} + /> ); }); - test('passed methods as props are called appropriately', async () => { - const wrapper = mount( - - - + test('should load teams on mount', () => { + const readOrganizationTeamsList = jest.fn(() => Promise.resolve(listData)); + mount( + + + + + ).find('OrganizationTeams'); - const teamsList = await wrapper.instance().readOrganizationTeamsList(); - expect(teamsList).toEqual(mockAPITeamsList); + expect(readOrganizationTeamsList).toHaveBeenCalledWith(1, { + page: 1, + page_size: 5, + order_by: 'name', + }); + }); + + test('should pass fetched teams to list component', async () => { + const readOrganizationTeamsList = jest.fn(() => Promise.resolve(listData)); + const wrapper = mount( + + + + + + ); + + await sleep(0); + wrapper.update(); + + const list = wrapper.find('OrganizationTeamsList'); + expect(list.prop('teams')).toEqual(listData.data.results); + expect(list.prop('itemCount')).toEqual(listData.data.count); + expect(list.prop('queryParams')).toEqual({ + page: 1, + page_size: 5, + order_by: 'name', + }); + }); + + test('should pass queryParams to OrganizationTeamsList', async () => { + const page1Data = listData; + const page2Data = { + data: { + count: 7, + results: [ + { id: 6, name: 'six', url: '/org/team/6' }, + { id: 7, name: 'seven', url: '/org/team/7' }, + ] + } + }; + const readOrganizationTeamsList = jest.fn(); + readOrganizationTeamsList.mockReturnValueOnce(page1Data); + const history = createMemoryHistory({ + initialEntries: ['/organizations/1/teams'], + }); + const wrapper = mount( + + + + + + ); + + await sleep(0); + wrapper.update(); + + const list = wrapper.find(OrganizationTeamsList); + expect(list.prop('queryParams')).toEqual({ + page: 1, + page_size: 5, + order_by: 'name', + }); + + readOrganizationTeamsList.mockReturnValueOnce(page2Data); + history.push('/organizations/1/teams?page=2'); + wrapper.setProps({ history }); + + await sleep(0); + wrapper.update(); + const list2 = wrapper.find(OrganizationTeamsList); + expect(list2.prop('queryParams')).toEqual({ + page: 2, + page_size: 5, + order_by: 'name', + }); }); }); diff --git a/__tests__/testUtils.js b/__tests__/testUtils.js new file mode 100644 index 0000000000..6666d69844 --- /dev/null +++ b/__tests__/testUtils.js @@ -0,0 +1,4 @@ + +const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); +/* eslint-disable-next-line import/prefer-default-export */ +export { sleep }; diff --git a/jest.config.js b/jest.config.js index db3216cbcb..3e11502a18 100644 --- a/jest.config.js +++ b/jest.config.js @@ -10,7 +10,7 @@ module.exports = { }, setupTestFrameworkScriptFile: '/jest.setup.js', testMatch: [ - '/__tests__/**/*.{js,jsx}' + '/__tests__/**/*.test.{js,jsx}' ], testEnvironment: 'jsdom', testURL: 'http://127.0.0.1:3001', diff --git a/package-lock.json b/package-lock.json index 9f91d8a84e..ccb49a211b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1015,6 +1015,21 @@ "@babel/plugin-transform-react-jsx-source": "^7.0.0" } }, + "@babel/runtime": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.3.tgz", + "integrity": "sha512-9lsJwJLxDh/T3Q3SZszfWOTkk3pHbkmH+3KY+zwIDmsNlxsumuhS2TH3NIpktU4kNvfzy+k3eLT7aTJSPTo0OA==", + "requires": { + "regenerator-runtime": "^0.13.2" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + } + } + }, "@babel/template": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", @@ -7043,25 +7058,16 @@ } }, "history": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", - "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", + "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==", "requires": { - "invariant": "^2.2.1", + "@babel/runtime": "^7.1.2", "loose-envify": "^1.2.0", "resolve-pathname": "^2.2.0", - "value-equal": "^0.4.0", - "warning": "^3.0.0" - }, - "dependencies": { - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - } + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^0.4.0" } }, "hmac-drbg": { @@ -14022,6 +14028,11 @@ "setimmediate": "^1.0.4" } }, + "tiny-invariant": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.4.tgz", + "integrity": "sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g==" + }, "tiny-warning": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.2.tgz", diff --git a/package.json b/package.json index 5e6e4398c1..f59720a3f6 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "eslint-plugin-jsx-a11y": "^6.1.1", "eslint-plugin-react": "^7.11.1", "file-loader": "^2.0.0", + "history": "^4.9.0", "jest": "^23.6.0", "node-sass": "^4.9.3", "react-hot-loader": "^4.3.3", diff --git a/src/pages/Organizations/components/OrganizationTeamsList.jsx b/src/pages/Organizations/components/OrganizationTeamsList.jsx index 0691d42a65..5f30e4b7e1 100644 --- a/src/pages/Organizations/components/OrganizationTeamsList.jsx +++ b/src/pages/Organizations/components/OrganizationTeamsList.jsx @@ -1,11 +1,20 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { - DataList, DataListItem, DataListCell, Text, - TextContent, TextVariants + DataList, + DataListItem, + DataListCell, + Text, + TextContent, + TextVariants, + Title, + EmptyState, + EmptyStateIcon, + EmptyStateBody, } from '@patternfly/react-core'; +import { CubesIcon } from '@patternfly/react-icons'; import { I18n, i18nMark } from '@lingui/react'; -import { t } from '@lingui/macro'; +import { Trans, t } from '@lingui/macro'; import { withRouter, Link } from 'react-router-dom'; import Pagination from '../../../components/Pagination'; @@ -94,7 +103,17 @@ class OrganizationTeamsList extends React.Component { )}
// TODO: replace with proper error handling )} - {teams.length > 0 && ( + {teams.length === 0 ? ( + + + + <Trans>No Teams Found</Trans> + + + Please add a team to populate this list + + + ) : ( ( )} diff --git a/src/pages/Organizations/screens/Organization/OrganizationTeams.jsx b/src/pages/Organizations/screens/Organization/OrganizationTeams.jsx index b6cf169e39..debe28f78c 100644 --- a/src/pages/Organizations/screens/Organization/OrganizationTeams.jsx +++ b/src/pages/Organizations/screens/Organization/OrganizationTeams.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router-dom'; import OrganizationTeamsList from '../../components/OrganizationTeamsList'; @@ -17,6 +17,7 @@ class OrganizationTeams extends React.Component { this.readOrganizationTeamsList = this.readOrganizationTeamsList.bind(this); this.state = { + isInitialized: false, isLoading: false, error: null, itemCount: 0, @@ -36,8 +37,8 @@ class OrganizationTeams extends React.Component { } getQueryParams () { - const { searchString } = this.props; - const searchParams = parseQueryString(searchString.substring(1)); + const { location } = this.props; + const searchParams = parseQueryString(location.search.substring(1)); return { ...DEFAULT_QUERY_PARAMS, @@ -57,36 +58,44 @@ class OrganizationTeams extends React.Component { itemCount: count, teams: results, isLoading: false, + isInitialized: true, }); } catch (error) { this.setState({ error, - isLoading: false + isLoading: false, + isInitialized: true, }); } } render () { - const { teams, itemCount, isLoading } = this.state; + const { teams, itemCount, isLoading, isInitialized, error } = this.state; - if (isLoading) { - return
Loading...
; + if (error) { + // TODO: better error state + return
{error.message}
; } + // TODO: better loading state return ( - + + {isLoading && (
Loading...
)} + {isInitialized && ( + + )} +
); } } OrganizationTeams.propTypes = { id: PropTypes.number.isRequired, - searchString: PropTypes.string.isRequired, - api: PropTypes.shape().isRequired, // TODO: remove? + api: PropTypes.shape().isRequired, }; export { OrganizationTeams as _OrganizationTeams }; From 909c5e77c402941a950e8ea2d06d27c62ef0d62b Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Wed, 10 Apr 2019 09:55:57 -0400 Subject: [PATCH 4/4] rename all test files to *.test.js --- __tests__/pages/{Applications.jsx => Applications.test.jsx} | 0 __tests__/pages/{AuthSettings.jsx => AuthSettings.test.jsx} | 0 __tests__/pages/{CredentialTypes.jsx => CredentialTypes.test.jsx} | 0 __tests__/pages/{Credentials.jsx => Credentials.test.jsx} | 0 __tests__/pages/{Dashboard.jsx => Dashboard.test.jsx} | 0 __tests__/pages/{InstanceGroups.jsx => InstanceGroups.test.jsx} | 0 __tests__/pages/{Inventories.jsx => Inventories.test.jsx} | 0 .../pages/{InventoryScripts.jsx => InventoryScripts.test.jsx} | 0 __tests__/pages/{Jobs.jsx => Jobs.test.jsx} | 0 __tests__/pages/{JobsSettings.jsx => JobsSettings.test.jsx} | 0 __tests__/pages/{License.jsx => License.test.jsx} | 0 __tests__/pages/{Login.jsx => Login.test.jsx} | 0 __tests__/pages/{ManagementJobs.jsx => ManagementJobs.test.jsx} | 0 .../{NotifcationTemplates.jsx => NotifcationTemplates.test.jsx} | 0 ...zationNotifications.jsx => OrganizationNotifications.test.jsx} | 0 __tests__/pages/{Portal.jsx => Portal.test.jsx} | 0 __tests__/pages/{Projects.jsx => Projects.test.jsx} | 0 __tests__/pages/{Schedules.jsx => Schedules.test.jsx} | 0 __tests__/pages/{SystemSettings.jsx => SystemSettings.test.jsx} | 0 __tests__/pages/{Teams.jsx => Teams.test.jsx} | 0 __tests__/pages/{Templates.jsx => Templates.test.jsx} | 0 __tests__/pages/{UISettings.jsx => UISettings.test.jsx} | 0 __tests__/pages/{Users.jsx => Users.test.jsx} | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename __tests__/pages/{Applications.jsx => Applications.test.jsx} (100%) rename __tests__/pages/{AuthSettings.jsx => AuthSettings.test.jsx} (100%) rename __tests__/pages/{CredentialTypes.jsx => CredentialTypes.test.jsx} (100%) rename __tests__/pages/{Credentials.jsx => Credentials.test.jsx} (100%) rename __tests__/pages/{Dashboard.jsx => Dashboard.test.jsx} (100%) rename __tests__/pages/{InstanceGroups.jsx => InstanceGroups.test.jsx} (100%) rename __tests__/pages/{Inventories.jsx => Inventories.test.jsx} (100%) rename __tests__/pages/{InventoryScripts.jsx => InventoryScripts.test.jsx} (100%) rename __tests__/pages/{Jobs.jsx => Jobs.test.jsx} (100%) rename __tests__/pages/{JobsSettings.jsx => JobsSettings.test.jsx} (100%) rename __tests__/pages/{License.jsx => License.test.jsx} (100%) rename __tests__/pages/{Login.jsx => Login.test.jsx} (100%) rename __tests__/pages/{ManagementJobs.jsx => ManagementJobs.test.jsx} (100%) rename __tests__/pages/{NotifcationTemplates.jsx => NotifcationTemplates.test.jsx} (100%) rename __tests__/pages/Organizations/screens/Organization/{OrganizationNotifications.jsx => OrganizationNotifications.test.jsx} (100%) rename __tests__/pages/{Portal.jsx => Portal.test.jsx} (100%) rename __tests__/pages/{Projects.jsx => Projects.test.jsx} (100%) rename __tests__/pages/{Schedules.jsx => Schedules.test.jsx} (100%) rename __tests__/pages/{SystemSettings.jsx => SystemSettings.test.jsx} (100%) rename __tests__/pages/{Teams.jsx => Teams.test.jsx} (100%) rename __tests__/pages/{Templates.jsx => Templates.test.jsx} (100%) rename __tests__/pages/{UISettings.jsx => UISettings.test.jsx} (100%) rename __tests__/pages/{Users.jsx => Users.test.jsx} (100%) diff --git a/__tests__/pages/Applications.jsx b/__tests__/pages/Applications.test.jsx similarity index 100% rename from __tests__/pages/Applications.jsx rename to __tests__/pages/Applications.test.jsx diff --git a/__tests__/pages/AuthSettings.jsx b/__tests__/pages/AuthSettings.test.jsx similarity index 100% rename from __tests__/pages/AuthSettings.jsx rename to __tests__/pages/AuthSettings.test.jsx diff --git a/__tests__/pages/CredentialTypes.jsx b/__tests__/pages/CredentialTypes.test.jsx similarity index 100% rename from __tests__/pages/CredentialTypes.jsx rename to __tests__/pages/CredentialTypes.test.jsx diff --git a/__tests__/pages/Credentials.jsx b/__tests__/pages/Credentials.test.jsx similarity index 100% rename from __tests__/pages/Credentials.jsx rename to __tests__/pages/Credentials.test.jsx diff --git a/__tests__/pages/Dashboard.jsx b/__tests__/pages/Dashboard.test.jsx similarity index 100% rename from __tests__/pages/Dashboard.jsx rename to __tests__/pages/Dashboard.test.jsx diff --git a/__tests__/pages/InstanceGroups.jsx b/__tests__/pages/InstanceGroups.test.jsx similarity index 100% rename from __tests__/pages/InstanceGroups.jsx rename to __tests__/pages/InstanceGroups.test.jsx diff --git a/__tests__/pages/Inventories.jsx b/__tests__/pages/Inventories.test.jsx similarity index 100% rename from __tests__/pages/Inventories.jsx rename to __tests__/pages/Inventories.test.jsx diff --git a/__tests__/pages/InventoryScripts.jsx b/__tests__/pages/InventoryScripts.test.jsx similarity index 100% rename from __tests__/pages/InventoryScripts.jsx rename to __tests__/pages/InventoryScripts.test.jsx diff --git a/__tests__/pages/Jobs.jsx b/__tests__/pages/Jobs.test.jsx similarity index 100% rename from __tests__/pages/Jobs.jsx rename to __tests__/pages/Jobs.test.jsx diff --git a/__tests__/pages/JobsSettings.jsx b/__tests__/pages/JobsSettings.test.jsx similarity index 100% rename from __tests__/pages/JobsSettings.jsx rename to __tests__/pages/JobsSettings.test.jsx diff --git a/__tests__/pages/License.jsx b/__tests__/pages/License.test.jsx similarity index 100% rename from __tests__/pages/License.jsx rename to __tests__/pages/License.test.jsx diff --git a/__tests__/pages/Login.jsx b/__tests__/pages/Login.test.jsx similarity index 100% rename from __tests__/pages/Login.jsx rename to __tests__/pages/Login.test.jsx diff --git a/__tests__/pages/ManagementJobs.jsx b/__tests__/pages/ManagementJobs.test.jsx similarity index 100% rename from __tests__/pages/ManagementJobs.jsx rename to __tests__/pages/ManagementJobs.test.jsx diff --git a/__tests__/pages/NotifcationTemplates.jsx b/__tests__/pages/NotifcationTemplates.test.jsx similarity index 100% rename from __tests__/pages/NotifcationTemplates.jsx rename to __tests__/pages/NotifcationTemplates.test.jsx diff --git a/__tests__/pages/Organizations/screens/Organization/OrganizationNotifications.jsx b/__tests__/pages/Organizations/screens/Organization/OrganizationNotifications.test.jsx similarity index 100% rename from __tests__/pages/Organizations/screens/Organization/OrganizationNotifications.jsx rename to __tests__/pages/Organizations/screens/Organization/OrganizationNotifications.test.jsx diff --git a/__tests__/pages/Portal.jsx b/__tests__/pages/Portal.test.jsx similarity index 100% rename from __tests__/pages/Portal.jsx rename to __tests__/pages/Portal.test.jsx diff --git a/__tests__/pages/Projects.jsx b/__tests__/pages/Projects.test.jsx similarity index 100% rename from __tests__/pages/Projects.jsx rename to __tests__/pages/Projects.test.jsx diff --git a/__tests__/pages/Schedules.jsx b/__tests__/pages/Schedules.test.jsx similarity index 100% rename from __tests__/pages/Schedules.jsx rename to __tests__/pages/Schedules.test.jsx diff --git a/__tests__/pages/SystemSettings.jsx b/__tests__/pages/SystemSettings.test.jsx similarity index 100% rename from __tests__/pages/SystemSettings.jsx rename to __tests__/pages/SystemSettings.test.jsx diff --git a/__tests__/pages/Teams.jsx b/__tests__/pages/Teams.test.jsx similarity index 100% rename from __tests__/pages/Teams.jsx rename to __tests__/pages/Teams.test.jsx diff --git a/__tests__/pages/Templates.jsx b/__tests__/pages/Templates.test.jsx similarity index 100% rename from __tests__/pages/Templates.jsx rename to __tests__/pages/Templates.test.jsx diff --git a/__tests__/pages/UISettings.jsx b/__tests__/pages/UISettings.test.jsx similarity index 100% rename from __tests__/pages/UISettings.jsx rename to __tests__/pages/UISettings.test.jsx diff --git a/__tests__/pages/Users.jsx b/__tests__/pages/Users.test.jsx similarity index 100% rename from __tests__/pages/Users.jsx rename to __tests__/pages/Users.test.jsx