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 ? (
+
+
+
+ No Teams Found
+
+
+ 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 };