summaryrefslogtreecommitdiff
path: root/spec/frontend/members
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 08:43:02 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 08:43:02 +0000
commitd9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch)
tree2341ef426af70ad1e289c38036737e04b0aa5007 /spec/frontend/members
parentd6e514dd13db8947884cd58fe2a9c2a063400a9b (diff)
downloadgitlab-ce-d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb.tar.gz
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'spec/frontend/members')
-rw-r--r--spec/frontend/members/components/action_buttons/remove_member_button_spec.js2
-rw-r--r--spec/frontend/members/components/action_buttons/user_action_buttons_spec.js5
-rw-r--r--spec/frontend/members/components/modals/leave_modal_spec.js29
-rw-r--r--spec/frontend/members/components/modals/remove_member_modal_spec.js33
-rw-r--r--spec/frontend/members/components/table/expires_at_spec.js86
-rw-r--r--spec/frontend/members/components/table/members_table_spec.js88
-rw-r--r--spec/frontend/members/mock_data.js7
7 files changed, 103 insertions, 147 deletions
diff --git a/spec/frontend/members/components/action_buttons/remove_member_button_spec.js b/spec/frontend/members/components/action_buttons/remove_member_button_spec.js
index d8453d453e7..7eb0ea37fe6 100644
--- a/spec/frontend/members/components/action_buttons/remove_member_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/remove_member_button_spec.js
@@ -45,7 +45,7 @@ describe('RemoveMemberButton', () => {
title: 'Remove member',
isAccessRequest: true,
isInvite: true,
- oncallSchedules: { name: 'user', schedules: [] },
+ userDeletionObstacles: { name: 'user', obstacles: [] },
...propsData,
},
directives: {
diff --git a/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js b/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js
index 0aa3780f030..10e451376c8 100644
--- a/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js
+++ b/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js
@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import LeaveButton from '~/members/components/action_buttons/leave_button.vue';
import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue';
import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue';
+import { parseUserDeletionObstacles } from '~/vue_shared/components/user_deletion_obstacles/utils';
import { member, orphanedMember } from '../../mock_data';
describe('UserActionButtons', () => {
@@ -45,9 +46,9 @@ describe('UserActionButtons', () => {
isAccessRequest: false,
isInvite: false,
icon: 'remove',
- oncallSchedules: {
+ userDeletionObstacles: {
name: member.user.name,
- schedules: member.user.oncallSchedules,
+ obstacles: parseUserDeletionObstacles(member.user),
},
});
});
diff --git a/spec/frontend/members/components/modals/leave_modal_spec.js b/spec/frontend/members/components/modals/leave_modal_spec.js
index 1dc913e5c78..f755f08dbf2 100644
--- a/spec/frontend/members/components/modals/leave_modal_spec.js
+++ b/spec/frontend/members/components/modals/leave_modal_spec.js
@@ -6,7 +6,8 @@ import { nextTick } from 'vue';
import Vuex from 'vuex';
import LeaveModal from '~/members/components/modals/leave_modal.vue';
import { LEAVE_MODAL_ID, MEMBER_TYPES } from '~/members/constants';
-import OncallSchedulesList from '~/vue_shared/components/oncall_schedules_list.vue';
+import UserDeletionObstaclesList from '~/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list.vue';
+import { parseUserDeletionObstacles } from '~/vue_shared/components/user_deletion_obstacles/utils';
import { member } from '../../mock_data';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
@@ -51,7 +52,7 @@ describe('LeaveModal', () => {
const findModal = () => wrapper.findComponent(GlModal);
const findForm = () => findModal().findComponent(GlForm);
- const findOncallSchedulesList = () => findModal().findComponent(OncallSchedulesList);
+ const findUserDeletionObstaclesList = () => findModal().findComponent(UserDeletionObstaclesList);
const getByText = (text, options) =>
createWrapper(within(findModal().element).getByText(text, options));
@@ -89,25 +90,27 @@ describe('LeaveModal', () => {
);
});
- describe('On-call schedules list', () => {
- it("displays oncall schedules list when member's user is part of on-call schedules ", () => {
- const schedulesList = findOncallSchedulesList();
- expect(schedulesList.exists()).toBe(true);
- expect(schedulesList.props()).toMatchObject({
+ describe('User deletion obstacles list', () => {
+ it("displays obstacles list when member's user is part of on-call management", () => {
+ const obstaclesList = findUserDeletionObstaclesList();
+ expect(obstaclesList.exists()).toBe(true);
+ expect(obstaclesList.props()).toMatchObject({
isCurrentUser: true,
- schedules: member.user.oncallSchedules,
+ obstacles: parseUserDeletionObstacles(member.user),
});
});
- it("does NOT display oncall schedules list when member's user is NOT a part of on-call schedules ", async () => {
+ it("does NOT display obstacles list when member's user is NOT a part of on-call management", async () => {
wrapper.destroy();
- const memberWithoutOncallSchedules = cloneDeep(member);
- delete memberWithoutOncallSchedules.user.oncallSchedules;
- createComponent({ member: memberWithoutOncallSchedules });
+ const memberWithoutOncall = cloneDeep(member);
+ delete memberWithoutOncall.user.oncallSchedules;
+ delete memberWithoutOncall.user.escalationPolicies;
+
+ createComponent({ member: memberWithoutOncall });
await nextTick();
- expect(findOncallSchedulesList().exists()).toBe(false);
+ expect(findUserDeletionObstaclesList().exists()).toBe(false);
});
});
diff --git a/spec/frontend/members/components/modals/remove_member_modal_spec.js b/spec/frontend/members/components/modals/remove_member_modal_spec.js
index 1dc41582c12..1d39c4b3175 100644
--- a/spec/frontend/members/components/modals/remove_member_modal_spec.js
+++ b/spec/frontend/members/components/modals/remove_member_modal_spec.js
@@ -4,15 +4,19 @@ import Vue from 'vue';
import Vuex from 'vuex';
import RemoveMemberModal from '~/members/components/modals/remove_member_modal.vue';
import { MEMBER_TYPES } from '~/members/constants';
-import OncallSchedulesList from '~/vue_shared/components/oncall_schedules_list.vue';
+import { OBSTACLE_TYPES } from '~/vue_shared/components/user_deletion_obstacles/constants';
+import UserDeletionObstaclesList from '~/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list.vue';
Vue.use(Vuex);
describe('RemoveMemberModal', () => {
const memberPath = '/gitlab-org/gitlab-test/-/project_members/90';
- const mockSchedules = {
+ const mockObstacles = {
name: 'User1',
- schedules: [{ id: 1, name: 'Schedule 1' }],
+ obstacles: [
+ { name: 'Schedule 1', type: OBSTACLE_TYPES.oncallSchedules },
+ { name: 'Policy 1', type: OBSTACLE_TYPES.escalationPolicies },
+ ],
};
let wrapper;
@@ -44,18 +48,18 @@ describe('RemoveMemberModal', () => {
const findForm = () => wrapper.find({ ref: 'form' });
const findGlModal = () => wrapper.findComponent(GlModal);
- const findOnCallSchedulesList = () => wrapper.findComponent(OncallSchedulesList);
+ const findUserDeletionObstaclesList = () => wrapper.findComponent(UserDeletionObstaclesList);
afterEach(() => {
wrapper.destroy();
});
describe.each`
- state | memberType | isAccessRequest | isInvite | actionText | removeSubMembershipsCheckboxExpected | unassignIssuablesCheckboxExpected | message | onCallSchedules
- ${'removing a group member'} | ${'GroupMember'} | ${false} | ${false} | ${'Remove member'} | ${true} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'} | ${{}}
- ${'removing a project member'} | ${'ProjectMember'} | ${false} | ${false} | ${'Remove member'} | ${false} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'} | ${mockSchedules}
- ${'denying an access request'} | ${'ProjectMember'} | ${true} | ${false} | ${'Deny access request'} | ${false} | ${false} | ${"Are you sure you want to deny Jane Doe's request to join the Gitlab Org / Gitlab Test project?"} | ${{}}
- ${'revoking invite'} | ${'ProjectMember'} | ${false} | ${true} | ${'Revoke invite'} | ${false} | ${false} | ${'Are you sure you want to revoke the invitation for foo@bar.com to join the Gitlab Org / Gitlab Test project?'} | ${mockSchedules}
+ state | memberType | isAccessRequest | isInvite | actionText | removeSubMembershipsCheckboxExpected | unassignIssuablesCheckboxExpected | message | userDeletionObstacles | isPartOfOncall
+ ${'removing a group member'} | ${'GroupMember'} | ${false} | ${false} | ${'Remove member'} | ${true} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'} | ${{}} | ${false}
+ ${'removing a project member'} | ${'ProjectMember'} | ${false} | ${false} | ${'Remove member'} | ${false} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'} | ${mockObstacles} | ${true}
+ ${'denying an access request'} | ${'ProjectMember'} | ${true} | ${false} | ${'Deny access request'} | ${false} | ${false} | ${"Are you sure you want to deny Jane Doe's request to join the Gitlab Org / Gitlab Test project?"} | ${{}} | ${false}
+ ${'revoking invite'} | ${'ProjectMember'} | ${false} | ${true} | ${'Revoke invite'} | ${false} | ${false} | ${'Are you sure you want to revoke the invitation for foo@bar.com to join the Gitlab Org / Gitlab Test project?'} | ${mockObstacles} | ${false}
`(
'when $state',
({
@@ -66,7 +70,8 @@ describe('RemoveMemberModal', () => {
message,
removeSubMembershipsCheckboxExpected,
unassignIssuablesCheckboxExpected,
- onCallSchedules,
+ userDeletionObstacles,
+ isPartOfOncall,
}) => {
beforeEach(() => {
createComponent({
@@ -75,12 +80,10 @@ describe('RemoveMemberModal', () => {
message,
memberPath,
memberType,
- onCallSchedules,
+ userDeletionObstacles,
});
});
- const isPartOfOncallSchedules = Boolean(isAccessRequest && onCallSchedules.schedules?.length);
-
it(`has the title ${actionText}`, () => {
expect(findGlModal().attributes('title')).toBe(actionText);
});
@@ -109,8 +112,8 @@ describe('RemoveMemberModal', () => {
);
});
- it(`shows ${isPartOfOncallSchedules ? 'all' : 'no'} related on-call schedules`, () => {
- expect(findOnCallSchedulesList().exists()).toBe(isPartOfOncallSchedules);
+ it(`shows ${isPartOfOncall ? 'all' : 'no'} related on-call schedules or policies`, () => {
+ expect(findUserDeletionObstaclesList().exists()).toBe(isPartOfOncall);
});
it('submits the form when the modal is submitted', () => {
diff --git a/spec/frontend/members/components/table/expires_at_spec.js b/spec/frontend/members/components/table/expires_at_spec.js
deleted file mode 100644
index 2b8e6ab8f2a..00000000000
--- a/spec/frontend/members/components/table/expires_at_spec.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import { within } from '@testing-library/dom';
-import { mount, createWrapper } from '@vue/test-utils';
-import { useFakeDate } from 'helpers/fake_date';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import ExpiresAt from '~/members/components/table/expires_at.vue';
-
-describe('ExpiresAt', () => {
- // March 15th, 2020
- useFakeDate(2020, 2, 15);
-
- let wrapper;
-
- const createComponent = (propsData) => {
- wrapper = mount(ExpiresAt, {
- propsData,
- directives: {
- GlTooltip: createMockDirective(),
- },
- });
- };
-
- const getByText = (text, options) =>
- createWrapper(within(wrapper.element).getByText(text, options));
-
- const getTooltipDirective = (elementWrapper) => getBinding(elementWrapper.element, 'gl-tooltip');
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('when no expiration date is set', () => {
- it('displays "No expiration set"', () => {
- createComponent({ date: null });
-
- expect(getByText('No expiration set').exists()).toBe(true);
- });
- });
-
- describe('when expiration date is in the past', () => {
- let expiredText;
-
- beforeEach(() => {
- createComponent({ date: '2019-03-15T00:00:00.000' });
-
- expiredText = getByText('Expired');
- });
-
- it('displays "Expired"', () => {
- expect(expiredText.exists()).toBe(true);
- expect(expiredText.classes()).toContain('gl-text-red-500');
- });
-
- it('displays tooltip with formatted date', () => {
- const tooltipDirective = getTooltipDirective(expiredText);
-
- expect(tooltipDirective).not.toBeUndefined();
- expect(expiredText.attributes('title')).toBe('Mar 15, 2019 12:00am UTC');
- });
- });
-
- describe('when expiration date is in the future', () => {
- it.each`
- date | expected | warningColor
- ${'2020-03-23T00:00:00.000'} | ${'in 8 days'} | ${false}
- ${'2020-03-20T00:00:00.000'} | ${'in 5 days'} | ${true}
- ${'2020-03-16T00:00:00.000'} | ${'in 1 day'} | ${true}
- ${'2020-03-15T05:00:00.000'} | ${'in about 5 hours'} | ${true}
- ${'2020-03-15T01:00:00.000'} | ${'in about 1 hour'} | ${true}
- ${'2020-03-15T00:30:00.000'} | ${'in 30 minutes'} | ${true}
- ${'2020-03-15T00:01:15.000'} | ${'in 1 minute'} | ${true}
- ${'2020-03-15T00:00:15.000'} | ${'in less than a minute'} | ${true}
- `('displays "$expected"', ({ date, expected, warningColor }) => {
- createComponent({ date });
-
- const expiredText = getByText(expected);
-
- expect(expiredText.exists()).toBe(true);
-
- if (warningColor) {
- expect(expiredText.classes()).toContain('gl-text-orange-500');
- } else {
- expect(expiredText.classes()).not.toContain('gl-text-orange-500');
- }
- });
- });
-});
diff --git a/spec/frontend/members/components/table/members_table_spec.js b/spec/frontend/members/components/table/members_table_spec.js
index 6885da53b26..580e5edd652 100644
--- a/spec/frontend/members/components/table/members_table_spec.js
+++ b/spec/frontend/members/components/table/members_table_spec.js
@@ -1,22 +1,24 @@
import { GlBadge, GlPagination, GlTable } from '@gitlab/ui';
-import {
- getByText as getByTextHelper,
- getByTestId as getByTestIdHelper,
- within,
-} from '@testing-library/dom';
-import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
+import { createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
import CreatedAt from '~/members/components/table/created_at.vue';
import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue';
-import ExpiresAt from '~/members/components/table/expires_at.vue';
import MemberActionButtons from '~/members/components/table/member_action_buttons.vue';
import MemberAvatar from '~/members/components/table/member_avatar.vue';
import MemberSource from '~/members/components/table/member_source.vue';
import MembersTable from '~/members/components/table/members_table.vue';
import RoleDropdown from '~/members/components/table/role_dropdown.vue';
-import { MEMBER_TYPES, TAB_QUERY_PARAM_VALUES } from '~/members/constants';
+import {
+ MEMBER_TYPES,
+ MEMBER_STATE_CREATED,
+ MEMBER_STATE_AWAITING,
+ MEMBER_STATE_ACTIVE,
+ USER_STATE_BLOCKED_PENDING_APPROVAL,
+ BADGE_LABELS_PENDING_OWNER_APPROVAL,
+ TAB_QUERY_PARAM_VALUES,
+} from '~/members/constants';
import * as initUserPopovers from '~/user_popovers';
import {
member as memberMock,
@@ -53,7 +55,7 @@ describe('MembersTable', () => {
};
const createComponent = (state, provide = {}) => {
- wrapper = mount(MembersTable, {
+ wrapper = mountExtended(MembersTable, {
localVue,
propsData: {
tabQueryParamValue: TAB_QUERY_PARAM_VALUES.invite,
@@ -68,7 +70,6 @@ describe('MembersTable', () => {
stubs: [
'member-avatar',
'member-source',
- 'expires-at',
'created-at',
'member-action-buttons',
'role-dropdown',
@@ -81,17 +82,11 @@ describe('MembersTable', () => {
const url = 'https://localhost/foo-bar/-/project_members?tab=invited';
- const getByText = (text, options) =>
- createWrapper(getByTextHelper(wrapper.element, text, options));
-
- const getByTestId = (id, options) =>
- createWrapper(getByTestIdHelper(wrapper.element, id, options));
-
const findTable = () => wrapper.find(GlTable);
const findTableCellByMemberId = (tableCellLabel, memberId) =>
- getByTestId(`members-table-row-${memberId}`).find(
- `[data-label="${tableCellLabel}"][role="cell"]`,
- );
+ wrapper
+ .findByTestId(`members-table-row-${memberId}`)
+ .find(`[data-label="${tableCellLabel}"][role="cell"]`);
const findPagination = () => extendedWrapper(wrapper.find(GlPagination));
@@ -103,7 +98,6 @@ describe('MembersTable', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
describe('fields', () => {
@@ -119,7 +113,6 @@ describe('MembersTable', () => {
${'granted'} | ${'Access granted'} | ${memberMock} | ${CreatedAt}
${'invited'} | ${'Invited'} | ${invite} | ${CreatedAt}
${'requested'} | ${'Requested'} | ${accessRequest} | ${CreatedAt}
- ${'expires'} | ${'Access expires'} | ${memberMock} | ${ExpiresAt}
${'maxRole'} | ${'Max role'} | ${memberCanUpdate} | ${RoleDropdown}
${'expiration'} | ${'Expiration'} | ${memberMock} | ${ExpirationDatepicker}
`('renders the $label field', ({ field, label, member, expectedComponent }) => {
@@ -128,7 +121,7 @@ describe('MembersTable', () => {
tableFields: [field],
});
- expect(getByText(label, { selector: '[role="columnheader"]' }).exists()).toBe(true);
+ expect(wrapper.findByText(label, { selector: '[role="columnheader"]' }).exists()).toBe(true);
if (expectedComponent) {
expect(
@@ -137,11 +130,50 @@ describe('MembersTable', () => {
}
});
+ describe('Invited column', () => {
+ describe.each`
+ state | userState | expectedBadgeLabel
+ ${MEMBER_STATE_CREATED} | ${null} | ${''}
+ ${MEMBER_STATE_CREATED} | ${USER_STATE_BLOCKED_PENDING_APPROVAL} | ${BADGE_LABELS_PENDING_OWNER_APPROVAL}
+ ${MEMBER_STATE_AWAITING} | ${''} | ${''}
+ ${MEMBER_STATE_AWAITING} | ${USER_STATE_BLOCKED_PENDING_APPROVAL} | ${BADGE_LABELS_PENDING_OWNER_APPROVAL}
+ ${MEMBER_STATE_AWAITING} | ${'something_else'} | ${BADGE_LABELS_PENDING_OWNER_APPROVAL}
+ ${MEMBER_STATE_ACTIVE} | ${null} | ${''}
+ ${MEMBER_STATE_ACTIVE} | ${'something_else'} | ${''}
+ `('Invited Badge', ({ state, userState, expectedBadgeLabel }) => {
+ it(`${
+ expectedBadgeLabel ? 'shows' : 'hides'
+ } invited badge if user status: '${userState}' and member state: '${state}'`, () => {
+ createComponent({
+ members: [
+ {
+ ...invite,
+ state,
+ invite: {
+ ...invite.invite,
+ userState,
+ },
+ },
+ ],
+ tableFields: ['invited'],
+ });
+
+ const invitedTab = wrapper.findByTestId('invited-badge');
+
+ if (expectedBadgeLabel) {
+ expect(invitedTab.text()).toBe(expectedBadgeLabel);
+ } else {
+ expect(invitedTab.exists()).toBe(false);
+ }
+ });
+ });
+ });
+
describe('"Actions" field', () => {
it('renders "Actions" field for screen readers', () => {
createComponent({ members: [memberCanUpdate], tableFields: ['actions'] });
- const actionField = getByTestId('col-actions');
+ const actionField = wrapper.findByTestId('col-actions');
expect(actionField.exists()).toBe(true);
expect(actionField.classes('gl-sr-only')).toBe(true);
@@ -154,7 +186,7 @@ describe('MembersTable', () => {
it('does not render the "Actions" field', () => {
createComponent({ tableFields: ['actions'] }, { currentUserId: null });
- expect(within(wrapper.element).queryByTestId('col-actions')).toBe(null);
+ expect(wrapper.findByTestId('col-actions').exists()).toBe(false);
});
});
@@ -177,7 +209,7 @@ describe('MembersTable', () => {
it('renders the "Actions" field', () => {
createComponent({ members, tableFields: ['actions'] });
- expect(getByTestId('col-actions').exists()).toBe(true);
+ expect(wrapper.findByTestId('col-actions').exists()).toBe(true);
expect(findTableCellByMemberId('Actions', members[0].id).classes()).toStrictEqual([
'col-actions',
@@ -199,7 +231,7 @@ describe('MembersTable', () => {
it('does not render the "Actions" field', () => {
createComponent({ members, tableFields: ['actions'] });
- expect(within(wrapper.element).queryByTestId('col-actions')).toBe(null);
+ expect(wrapper.findByTestId('col-actions').exists()).toBe(false);
});
});
});
@@ -209,7 +241,7 @@ describe('MembersTable', () => {
it('displays a "No members found" message', () => {
createComponent();
- expect(getByText('No members found').exists()).toBe(true);
+ expect(wrapper.findByText('No members found').exists()).toBe(true);
});
});
diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js
index eb9f905fea2..f42ee295511 100644
--- a/spec/frontend/members/mock_data.js
+++ b/spec/frontend/members/mock_data.js
@@ -1,4 +1,4 @@
-import { MEMBER_TYPES } from '~/members/constants';
+import { MEMBER_TYPES, MEMBER_STATE_CREATED } from '~/members/constants';
export const member = {
requestedAt: null,
@@ -14,6 +14,7 @@ export const member = {
webUrl: 'https://gitlab.com/groups/foo-bar',
},
type: 'GroupMember',
+ state: MEMBER_STATE_CREATED,
user: {
id: 123,
name: 'Administrator',
@@ -23,6 +24,7 @@ export const member = {
blocked: false,
twoFactorEnabled: false,
oncallSchedules: [{ name: 'schedule 1' }],
+ escalationPolicies: [{ name: 'policy 1' }],
},
id: 238,
createdAt: '2020-07-17T16:22:46.923Z',
@@ -63,12 +65,13 @@ export const modalData = {
memberPath: '/groups/foo-bar/-/group_members/1',
memberType: 'GroupMember',
message: 'Are you sure you want to remove John Smith?',
- oncallSchedules: { name: 'user', schedules: [] },
+ userDeletionObstacles: { name: 'user', obstacles: [] },
};
const { user, ...memberNoUser } = member;
export const invite = {
...memberNoUser,
+ state: MEMBER_STATE_CREATED,
invite: {
email: 'jewel@hudsonwalter.biz',
avatarUrl: 'https://www.gravatar.com/avatar/cbab7510da7eec2f60f638261b05436d?s=80&d=identicon',