158 paginated data list (#180)

* working: rename OrganizationTeamsList to PaginatedDataList

* convert org notifications list fully to PaginatedDataList

* update NotificationList tests

* refactor org access to use PaginatedDataList

* update tests for org access refactor; fix pagination & sorting

* restore Add Role functionality to Org roles

* fix displayed text when list of items is empty

* preserve query params when navigating through pagination

* fix bugs after RBAC rebase

* fix lint errors, fix add org access button
This commit is contained in:
Keith Grant
2019-04-29 10:08:50 -04:00
committed by GitHub
parent 3c06c97c32
commit 9d66b583b7
36 changed files with 4133 additions and 1427 deletions

View File

@@ -1,34 +1,173 @@
import React from 'react';
import { mountWithContexts } from '../../../../enzymeHelpers';
import OrganizationAccess from '../../../../../src/pages/Organizations/screens/Organization/OrganizationAccess';
import { sleep } from '../../../../testUtils';
describe('<OrganizationAccess />', () => {
let network;
const organization = {
id: 1,
name: 'Default'
};
test('initially renders succesfully', () => {
mountWithContexts(<OrganizationAccess organization={organization} />);
const data = {
count: 2,
results: [{
id: 1,
username: 'joe',
url: '/foo',
first_name: 'joe',
last_name: 'smith',
summary_fields: {
direct_access: [{
role: {
id: 1,
name: 'Member',
resource_name: 'Org',
resource_type: 'organization',
user_capabilities: { unattach: true },
}
}],
indirect_access: [],
}
}, {
id: 2,
username: 'jane',
url: '/bar',
first_name: 'jane',
last_name: 'brown',
summary_fields: {
direct_access: [{
role: {
id: 3,
name: 'Member',
resource_name: 'Org',
resource_type: 'organization',
team_id: 5,
team_name: 'The Team',
user_capabilities: { unattach: true },
}
}],
indirect_access: [],
}
}]
};
beforeEach(() => {
network = {
api: {
getOrganizationAccessList: jest.fn()
.mockReturnValue(Promise.resolve({ data })),
disassociateTeamRole: jest.fn(),
disassociateUserRole: jest.fn(),
toJSON: () => '/api/',
},
};
});
test('passed methods as props are called appropriately', async () => {
const mockAPIAccessList = {
foo: 'bar',
};
const mockResponse = {
status: 'success',
};
const wrapper = mountWithContexts(<OrganizationAccess organization={organization} />,
{ context: { network: {
api: {
getOrganizationAccessList: () => Promise.resolve(mockAPIAccessList),
disassociate: () => Promise.resolve(mockResponse)
},
handleHttpError: () => {}
} } }).find('OrganizationAccess');
const accessList = await wrapper.instance().getOrgAccessList();
expect(accessList).toEqual(mockAPIAccessList);
const resp = await wrapper.instance().removeRole(2, 3, 'users');
expect(resp).toEqual(mockResponse);
test.only('initially renders succesfully', () => {
const wrapper = mountWithContexts(
<OrganizationAccess id={1} organization={organization} />,
{ context: { network } }
);
expect(wrapper.find('OrganizationAccess')).toMatchSnapshot();
});
test('should fetch and display access records on mount', async () => {
const wrapper = mountWithContexts(
<OrganizationAccess id={1} />,
{ context: { network } }
);
await sleep(0);
wrapper.update();
expect(network.api.getOrganizationAccessList).toHaveBeenCalled();
expect(wrapper.find('OrganizationAccess').state('isInitialized')).toBe(true);
expect(wrapper.find('PaginatedDataList').prop('items')).toEqual(data.results);
expect(wrapper.find('OrganizationAccessItem')).toHaveLength(2);
});
test('should open confirmation dialog when deleting role', async () => {
const wrapper = mountWithContexts(
<OrganizationAccess id={1} />,
{ context: { network } }
);
await sleep(0);
wrapper.update();
const button = wrapper.find('ChipButton').at(0);
button.prop('onClick')();
wrapper.update();
const component = wrapper.find('OrganizationAccess');
expect(component.state('roleToDelete'))
.toEqual(data.results[0].summary_fields.direct_access[0].role);
expect(component.state('roleToDeleteAccessRecord'))
.toEqual(data.results[0]);
expect(component.find('DeleteRoleConfirmationModal')).toHaveLength(1);
});
it('should close dialog when cancel button clicked', async () => {
const wrapper = mountWithContexts(
<OrganizationAccess id={1} />,
{ context: { network } }
);
await sleep(0);
wrapper.update();
const button = wrapper.find('ChipButton').at(0);
button.prop('onClick')();
wrapper.update();
wrapper.find('DeleteRoleConfirmationModal').prop('onCancel')();
const component = wrapper.find('OrganizationAccess');
expect(component.state('roleToDelete')).toBeNull();
expect(component.state('roleToDeleteAccessRecord')).toBeNull();
expect(network.api.disassociateTeamRole).not.toHaveBeenCalled();
expect(network.api.disassociateUserRole).not.toHaveBeenCalled();
});
it('should delete user role', async () => {
const wrapper = mountWithContexts(
<OrganizationAccess id={1} />,
{ context: { network } }
);
await sleep(0);
wrapper.update();
const button = wrapper.find('ChipButton').at(0);
button.prop('onClick')();
wrapper.update();
wrapper.find('DeleteRoleConfirmationModal').prop('onConfirm')();
await sleep(0);
wrapper.update();
const component = wrapper.find('OrganizationAccess');
expect(component.state('roleToDelete')).toBeNull();
expect(component.state('roleToDeleteAccessRecord')).toBeNull();
expect(network.api.disassociateTeamRole).not.toHaveBeenCalled();
expect(network.api.disassociateUserRole).toHaveBeenCalledWith(1, 1);
expect(network.api.getOrganizationAccessList).toHaveBeenCalledTimes(2);
});
it('should delete team role', async () => {
const wrapper = mountWithContexts(
<OrganizationAccess id={1} />,
{ context: { network } }
);
await sleep(0);
wrapper.update();
const button = wrapper.find('ChipButton').at(1);
button.prop('onClick')();
wrapper.update();
wrapper.find('DeleteRoleConfirmationModal').prop('onConfirm')();
await sleep(0);
wrapper.update();
const component = wrapper.find('OrganizationAccess');
expect(component.state('roleToDelete')).toBeNull();
expect(component.state('roleToDeleteAccessRecord')).toBeNull();
expect(network.api.disassociateTeamRole).toHaveBeenCalledWith(5, 3);
expect(network.api.disassociateUserRole).not.toHaveBeenCalled();
expect(network.api.getOrganizationAccessList).toHaveBeenCalledTimes(2);
});
});

View File

@@ -1,45 +1,170 @@
import React from 'react';
import { mountWithContexts } from '../../../../enzymeHelpers';
import OrganizationNotifications from '../../../../../src/pages/Organizations/screens/Organization/OrganizationNotifications';
import { sleep } from '../../../../testUtils';
describe('<OrganizationNotifications />', () => {
let api;
let data;
let network;
beforeEach(() => {
api = {
getOrganizationNotifications: jest.fn(),
getOrganizationNotificationSuccess: jest.fn(),
getOrganizationNotificationError: jest.fn(),
createOrganizationNotificationSuccess: jest.fn(),
createOrganizationNotificationError: jest.fn()
data = {
count: 2,
results: [{
id: 1,
name: 'Notification one',
url: '/api/v2/notification_templates/1/',
notification_type: 'email',
}, {
id: 2,
name: 'Notification two',
url: '/api/v2/notification_templates/2/',
notification_type: 'email',
}]
};
network = {
api: {
getOrganizationNotifications: jest.fn()
.mockReturnValue(Promise.resolve({ data })),
getOrganizationNotificationSuccess: jest.fn()
.mockReturnValue(Promise.resolve({
data: { results: [{ id: 1 }] },
})),
getOrganizationNotificationError: jest.fn()
.mockReturnValue(Promise.resolve({
data: { results: [{ id: 2 }] },
})),
createOrganizationNotificationSuccess: jest.fn(),
createOrganizationNotificationError: jest.fn(),
toJSON: () => '/api/',
}
};
});
test('initially renders succesfully', () => {
mountWithContexts(
<OrganizationNotifications canToggleNotifications />, { context: { network: {
api,
handleHttpError: () => {}
} } }
);
afterEach(() => {
jest.clearAllMocks();
});
test('handles api requests', () => {
test('initially renders succesfully', async () => {
const wrapper = mountWithContexts(
<OrganizationNotifications canToggleNotifications />, { context: { network: {
api,
handleHttpError: () => {}
} } }
).find('OrganizationNotifications');
wrapper.instance().readOrgNotifications(1, { foo: 'bar' });
expect(api.getOrganizationNotifications).toHaveBeenCalledWith(1, { foo: 'bar' });
wrapper.instance().readOrgNotificationSuccess(1, { foo: 'bar' });
expect(api.getOrganizationNotificationSuccess).toHaveBeenCalledWith(1, { foo: 'bar' });
wrapper.instance().readOrgNotificationError(1, { foo: 'bar' });
expect(api.getOrganizationNotificationError).toHaveBeenCalledWith(1, { foo: 'bar' });
wrapper.instance().createOrgNotificationSuccess(1, { id: 2 });
expect(api.createOrganizationNotificationSuccess).toHaveBeenCalledWith(1, { id: 2 });
wrapper.instance().createOrgNotificationError(1, { id: 2 });
expect(api.createOrganizationNotificationError).toHaveBeenCalledWith(1, { id: 2 });
<OrganizationNotifications id={1} canToggleNotifications />,
{ context: { network } }
);
await sleep(0);
wrapper.update();
expect(wrapper).toMatchSnapshot();
});
test('should render list fetched of items', async () => {
const wrapper = mountWithContexts(
<OrganizationNotifications id={1} canToggleNotifications />,
{
context: { network }
}
);
await sleep(0);
wrapper.update();
expect(network.api.getOrganizationNotifications).toHaveBeenCalled();
expect(wrapper.find('OrganizationNotifications').state('notifications'))
.toEqual(data.results);
const items = wrapper.find('NotificationListItem');
expect(items).toHaveLength(2);
expect(items.at(0).prop('successTurnedOn')).toEqual(true);
expect(items.at(0).prop('errorTurnedOn')).toEqual(false);
expect(items.at(1).prop('successTurnedOn')).toEqual(false);
expect(items.at(1).prop('errorTurnedOn')).toEqual(true);
});
test('should enable success notification', async () => {
const wrapper = mountWithContexts(
<OrganizationNotifications id={1} canToggleNotifications />,
{
context: { network }
}
);
await sleep(0);
wrapper.update();
expect(
wrapper.find('OrganizationNotifications').state('successTemplateIds')
).toEqual([1]);
const items = wrapper.find('NotificationListItem');
items.at(1).find('Switch').at(0).prop('onChange')();
expect(network.api.createOrganizationNotificationSuccess).toHaveBeenCalled();
await sleep(0);
wrapper.update();
expect(
wrapper.find('OrganizationNotifications').state('successTemplateIds')
).toEqual([1, 2]);
});
test('should enable error notification', async () => {
const wrapper = mountWithContexts(
<OrganizationNotifications id={1} canToggleNotifications />,
{
context: { network }
}
);
await sleep(0);
wrapper.update();
expect(
wrapper.find('OrganizationNotifications').state('errorTemplateIds')
).toEqual([2]);
const items = wrapper.find('NotificationListItem');
items.at(0).find('Switch').at(1).prop('onChange')();
expect(network.api.createOrganizationNotificationError).toHaveBeenCalled();
await sleep(0);
wrapper.update();
expect(
wrapper.find('OrganizationNotifications').state('errorTemplateIds')
).toEqual([2, 1]);
});
test('should disable success notification', async () => {
const wrapper = mountWithContexts(
<OrganizationNotifications id={1} canToggleNotifications />,
{
context: { network }
}
);
await sleep(0);
wrapper.update();
expect(
wrapper.find('OrganizationNotifications').state('successTemplateIds')
).toEqual([1]);
const items = wrapper.find('NotificationListItem');
items.at(0).find('Switch').at(0).prop('onChange')();
expect(network.api.createOrganizationNotificationSuccess).toHaveBeenCalled();
await sleep(0);
wrapper.update();
expect(
wrapper.find('OrganizationNotifications').state('successTemplateIds')
).toEqual([]);
});
test('should disable error notification', async () => {
const wrapper = mountWithContexts(
<OrganizationNotifications id={1} canToggleNotifications />,
{
context: { network }
}
);
await sleep(0);
wrapper.update();
expect(
wrapper.find('OrganizationNotifications').state('errorTemplateIds')
).toEqual([2]);
const items = wrapper.find('NotificationListItem');
items.at(1).find('Switch').at(1).prop('onChange')();
expect(network.api.createOrganizationNotificationError).toHaveBeenCalled();
await sleep(0);
wrapper.update();
expect(
wrapper.find('OrganizationNotifications').state('errorTemplateIds')
).toEqual([]);
});
});

View File

@@ -5,7 +5,6 @@ import { createMemoryHistory } from 'history';
import { mountWithContexts } from '../../../../enzymeHelpers';
import { sleep } from '../../../../testUtils';
import OrganizationTeams, { _OrganizationTeams } from '../../../../../src/pages/Organizations/screens/Organization/OrganizationTeams';
import OrganizationTeamsList from '../../../../../src/pages/Organizations/components/OrganizationTeamsList';
const listData = {
data: {
@@ -52,7 +51,7 @@ describe('<OrganizationTeams />', () => {
});
});
test('should pass fetched teams to list component', async () => {
test('should pass fetched teams to PaginatedDatalist', async () => {
const readOrganizationTeamsList = jest.fn(() => Promise.resolve(listData));
const wrapper = mountWithContexts(
<OrganizationTeams
@@ -66,8 +65,8 @@ describe('<OrganizationTeams />', () => {
await sleep(0);
wrapper.update();
const list = wrapper.find('OrganizationTeamsList');
expect(list.prop('teams')).toEqual(listData.data.results);
const list = wrapper.find('PaginatedDataList');
expect(list.prop('items')).toEqual(listData.data.results);
expect(list.prop('itemCount')).toEqual(listData.data.count);
expect(list.prop('queryParams')).toEqual({
page: 1,
@@ -76,7 +75,7 @@ describe('<OrganizationTeams />', () => {
});
});
test('should pass queryParams to OrganizationTeamsList', async () => {
test('should pass queryParams to PaginatedDataList', async () => {
const page1Data = listData;
const page2Data = {
data: {
@@ -111,7 +110,7 @@ describe('<OrganizationTeams />', () => {
await sleep(0);
wrapper.update();
const list = wrapper.find(OrganizationTeamsList);
const list = wrapper.find('PaginatedDataList');
expect(list.prop('queryParams')).toEqual({
page: 1,
page_size: 5,
@@ -123,7 +122,7 @@ describe('<OrganizationTeams />', () => {
await sleep(0);
wrapper.update();
const list2 = wrapper.find(OrganizationTeamsList);
const list2 = wrapper.find('PaginatedDataList');
expect(list2.prop('queryParams')).toEqual({
page: 2,
page_size: 5,

View File

@@ -0,0 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
<OrganizationAccess
api={"/api/"}
handleHttpError={[Function]}
history={"/history/"}
id={1}
location={
Object {
"hash": "",
"pathname": "",
"search": "",
"state": "",
}
}
match={
Object {
"isExact": false,
"params": Object {},
"path": "",
"url": "",
}
}
organization={
Object {
"id": 1,
"name": "Default",
}
}
>
<div>
Loading...
</div>
</OrganizationAccess>
`;