mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #17683 from overleaf/jel-unlink
[web] Unlink group SSO member GitOrigin-RevId: ac3498fc72538fd0eef639c783dc7675ac593b94
This commit is contained in:
parent
9d78fd0945
commit
d8f870555c
12 changed files with 257 additions and 2 deletions
|
@ -1229,6 +1229,7 @@
|
|||
"sso_link_invite_has_been_sent_to_email": "",
|
||||
"sso_logs": "",
|
||||
"sso_not_active": "",
|
||||
"sso_reauth_request": "",
|
||||
"sso_test_interstitial_info_1": "",
|
||||
"sso_test_interstitial_info_2": "",
|
||||
"sso_test_interstitial_title": "",
|
||||
|
@ -1446,6 +1447,8 @@
|
|||
"unlink_provider_account_warning": "",
|
||||
"unlink_reference": "",
|
||||
"unlink_the_project_from_the_current_github_repo": "",
|
||||
"unlink_user": "",
|
||||
"unlink_user_explanation": "",
|
||||
"unlink_users": "",
|
||||
"unlink_warning_reference": "",
|
||||
"unlinking": "",
|
||||
|
|
|
@ -22,6 +22,7 @@ type resendInviteResponse = {
|
|||
type ManagedUserDropdownButtonProps = {
|
||||
user: User
|
||||
openOffboardingModalForUser: (user: User) => void
|
||||
openUnlinkUserModal: (user: User) => void
|
||||
groupId: string
|
||||
setGroupUserAlert: Dispatch<SetStateAction<GroupUserAlert>>
|
||||
}
|
||||
|
@ -29,6 +30,7 @@ type ManagedUserDropdownButtonProps = {
|
|||
export default function DropdownButton({
|
||||
user,
|
||||
openOffboardingModalForUser,
|
||||
openUnlinkUserModal,
|
||||
groupId,
|
||||
setGroupUserAlert,
|
||||
}: ManagedUserDropdownButtonProps) {
|
||||
|
@ -178,6 +180,10 @@ export default function DropdownButton({
|
|||
removeMember(user)
|
||||
}
|
||||
|
||||
const onUnlinkUserClick = () => {
|
||||
openUnlinkUserModal(user)
|
||||
}
|
||||
|
||||
const buttons = []
|
||||
|
||||
if (userPending) {
|
||||
|
@ -208,6 +214,17 @@ export default function DropdownButton({
|
|||
</MenuItemButton>
|
||||
)
|
||||
}
|
||||
if (groupSSOActive && isGroupSSOLinked) {
|
||||
buttons.push(
|
||||
<MenuItemButton
|
||||
onClick={onUnlinkUserClick}
|
||||
key="unlink-user-action"
|
||||
data-testid="unlink-user-action"
|
||||
>
|
||||
{t('unlink_user')}
|
||||
</MenuItemButton>
|
||||
)
|
||||
}
|
||||
if (groupSSOActive && !isGroupSSOLinked && !userPending) {
|
||||
buttons.push(
|
||||
<MenuItemButton
|
||||
|
|
|
@ -2,6 +2,7 @@ import { type PropsWithChildren, useState } from 'react'
|
|||
import { Alert, type AlertProps } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import type { GroupUserAlertVariant } from '../../utils/types'
|
||||
import NotificationScrolledTo from '@/shared/components/notification-scrolled-to'
|
||||
|
||||
type GroupUsersListAlertProps = {
|
||||
variant: GroupUserAlertVariant
|
||||
|
@ -53,6 +54,25 @@ export default function ListAlert({
|
|||
)
|
||||
case 'resendInviteTooManyRequests':
|
||||
return <TooManyRequests onDismiss={onDismiss} userEmail={userEmail} />
|
||||
case 'unlinkedSSO':
|
||||
return (
|
||||
<NotificationScrolledTo
|
||||
type="success"
|
||||
content={
|
||||
<Trans
|
||||
i18nKey="sso_reauth_request"
|
||||
values={{ email: userEmail }}
|
||||
components={[<strong />]} // eslint-disable-line react/jsx-key
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
}
|
||||
id="sso-user-unlinked"
|
||||
ariaLive="polite"
|
||||
isDismissible
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import getMeta from '@/utils/meta'
|
|||
type ManagedUserRowProps = {
|
||||
user: User
|
||||
openOffboardingModalForUser: (user: User) => void
|
||||
openUnlinkUserModal: (user: User) => void
|
||||
groupId: string
|
||||
setGroupUserAlert: Dispatch<SetStateAction<GroupUserAlert>>
|
||||
}
|
||||
|
@ -21,6 +22,7 @@ type ManagedUserRowProps = {
|
|||
export default function MemberRow({
|
||||
user,
|
||||
openOffboardingModalForUser,
|
||||
openUnlinkUserModal,
|
||||
setGroupUserAlert,
|
||||
groupId,
|
||||
}: ManagedUserRowProps) {
|
||||
|
@ -95,6 +97,7 @@ export default function MemberRow({
|
|||
<DropdownButton
|
||||
user={user}
|
||||
openOffboardingModalForUser={openOffboardingModalForUser}
|
||||
openUnlinkUserModal={openUnlinkUserModal}
|
||||
setGroupUserAlert={setGroupUserAlert}
|
||||
groupId={groupId}
|
||||
/>
|
||||
|
|
|
@ -11,6 +11,7 @@ import ListAlert from './list-alert'
|
|||
import SelectAllCheckbox from './select-all-checkbox'
|
||||
import classNames from 'classnames'
|
||||
import getMeta from '@/utils/meta'
|
||||
import UnlinkUserModal from './unlink-user-modal'
|
||||
|
||||
type ManagedUsersListProps = {
|
||||
groupId: string
|
||||
|
@ -23,6 +24,7 @@ export default function MembersList({ groupId }: ManagedUsersListProps) {
|
|||
)
|
||||
const [groupUserAlert, setGroupUserAlert] =
|
||||
useState<GroupUserAlert>(undefined)
|
||||
const [userToUnlink, setUserToUnlink] = useState<User | undefined>(undefined)
|
||||
const { users } = useGroupMembersContext()
|
||||
const managedUsersActive: any = getMeta('ol-managedUsersActive')
|
||||
const groupSSOActive = getMeta('ol-groupSSOActive')
|
||||
|
@ -100,6 +102,7 @@ export default function MembersList({ groupId }: ManagedUsersListProps) {
|
|||
key={user.email}
|
||||
user={user}
|
||||
openOffboardingModalForUser={setUserToOffboard}
|
||||
openUnlinkUserModal={setUserToUnlink}
|
||||
setGroupUserAlert={setGroupUserAlert}
|
||||
groupId={groupId}
|
||||
/>
|
||||
|
@ -118,6 +121,13 @@ export default function MembersList({ groupId }: ManagedUsersListProps) {
|
|||
onClose={() => setUserToOffboard(undefined)}
|
||||
/>
|
||||
)}
|
||||
{userToUnlink && (
|
||||
<UnlinkUserModal
|
||||
user={userToUnlink}
|
||||
onClose={() => setUserToUnlink(undefined)}
|
||||
setGroupUserAlert={setGroupUserAlert}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
import { Modal } from 'react-bootstrap'
|
||||
import AccessibleModal from '@/shared/components/accessible-modal'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { User } from '../../../../../../types/group-management/user'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { SetStateAction, useCallback, useState, type Dispatch } from 'react'
|
||||
import useAsync from '@/shared/hooks/use-async'
|
||||
import { postJSON } from '@/infrastructure/fetch-json'
|
||||
import NotificationScrolledTo from '@/shared/components/notification-scrolled-to'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { GroupUserAlert } from '../../utils/types'
|
||||
import { useGroupMembersContext } from '../../context/group-members-context'
|
||||
|
||||
export type UnlinkUserModalProps = {
|
||||
onClose: () => void
|
||||
user: User
|
||||
setGroupUserAlert: Dispatch<SetStateAction<GroupUserAlert>>
|
||||
}
|
||||
|
||||
export default function UnlinkUserModal({
|
||||
onClose,
|
||||
user,
|
||||
setGroupUserAlert,
|
||||
}: UnlinkUserModalProps) {
|
||||
const { t } = useTranslation()
|
||||
const groupId = getMeta('ol-groupId')
|
||||
const [hasError, setHasError] = useState<string | undefined>()
|
||||
const { isLoading: unlinkInFlight, runAsync, reset } = useAsync()
|
||||
const { updateMemberView } = useGroupMembersContext()
|
||||
|
||||
const setUserAsUnlinked = useCallback(() => {
|
||||
if (!user.enrollment?.sso) {
|
||||
return
|
||||
}
|
||||
const updatedUser = Object.assign({}, user, {
|
||||
enrollment: {
|
||||
sso: user.enrollment.sso.filter(sso => sso.groupId !== groupId),
|
||||
},
|
||||
})
|
||||
updateMemberView(user._id, updatedUser)
|
||||
}, [groupId, updateMemberView, user])
|
||||
|
||||
const handleUnlink = useCallback(
|
||||
event => {
|
||||
event.preventDefault()
|
||||
setHasError(undefined)
|
||||
if (!user) {
|
||||
setHasError(t('generic_something_went_wrong'))
|
||||
return
|
||||
}
|
||||
runAsync(postJSON(`/manage/groups/${groupId}/unlink-user/${user._id}`))
|
||||
.then(() => {
|
||||
setUserAsUnlinked()
|
||||
setGroupUserAlert({
|
||||
variant: 'unlinkedSSO',
|
||||
email: user.email,
|
||||
})
|
||||
onClose()
|
||||
reset()
|
||||
})
|
||||
.catch(e => {
|
||||
debugConsole.error(e)
|
||||
setHasError(t('generic_something_went_wrong'))
|
||||
})
|
||||
},
|
||||
[
|
||||
groupId,
|
||||
onClose,
|
||||
reset,
|
||||
runAsync,
|
||||
setGroupUserAlert,
|
||||
setUserAsUnlinked,
|
||||
t,
|
||||
user,
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<AccessibleModal show onHide={onClose}>
|
||||
<Modal.Header>
|
||||
<Modal.Title>{t('unlink_user')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{hasError && (
|
||||
<div className="mb-3">
|
||||
<NotificationScrolledTo
|
||||
type="error"
|
||||
content={hasError}
|
||||
id="alert-unlink-user-error"
|
||||
ariaLive="polite"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="unlink_user_explanation"
|
||||
components={[<strong />]} // eslint-disable-line react/jsx-key
|
||||
values={{ email: user.email }}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
</p>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<button className="btn btn-secondary" disabled={unlinkInFlight}>
|
||||
{t('cancel')}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
onClick={e => handleUnlink(e)}
|
||||
disabled={unlinkInFlight}
|
||||
>
|
||||
{t('unlink_user')}
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
)
|
||||
}
|
|
@ -28,6 +28,7 @@ export type GroupMembersContextValue = {
|
|||
removeMember: (user: User) => Promise<void>
|
||||
removeMemberLoading: boolean
|
||||
removeMemberError?: APIError
|
||||
updateMemberView: (userId: string, updatedUser: User) => void
|
||||
inviteMemberLoading: boolean
|
||||
inviteError?: APIError
|
||||
paths: { [key: string]: string }
|
||||
|
@ -140,6 +141,21 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
|
|||
[selectedUsers, removeMember]
|
||||
)
|
||||
|
||||
const updateMemberView = useCallback(
|
||||
(userId, updatedUser) => {
|
||||
setUsers(
|
||||
users.map(u => {
|
||||
if (u._id === userId) {
|
||||
return updatedUser
|
||||
} else {
|
||||
return u
|
||||
}
|
||||
})
|
||||
)
|
||||
},
|
||||
[setUsers, users]
|
||||
)
|
||||
|
||||
const value = useMemo<GroupMembersContextValue>(
|
||||
() => ({
|
||||
users,
|
||||
|
@ -149,6 +165,7 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
|
|||
selectAllNonManagedUsers,
|
||||
selectUser,
|
||||
unselectUser,
|
||||
updateMemberView,
|
||||
addMembers,
|
||||
removeMembers,
|
||||
removeMember,
|
||||
|
@ -166,6 +183,7 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
|
|||
selectAllNonManagedUsers,
|
||||
selectUser,
|
||||
unselectUser,
|
||||
updateMemberView,
|
||||
addMembers,
|
||||
removeMembers,
|
||||
removeMember,
|
||||
|
|
|
@ -6,6 +6,7 @@ export type GroupUserAlertVariant =
|
|||
| 'resendInviteTooManyRequests'
|
||||
| 'resendSSOLinkInviteSuccess'
|
||||
| 'resendSSOLinkInviteFailed'
|
||||
| 'unlinkedSSO'
|
||||
|
||||
export type GroupUserAlert =
|
||||
| {
|
||||
|
|
|
@ -1780,6 +1780,7 @@
|
|||
"sso_logs": "SSO Logs",
|
||||
"sso_not_active": "SSO not active",
|
||||
"sso_not_linked": "You have not linked your account to __provider__. Please log in to your account another way and link your __provider__ account via your account settings.",
|
||||
"sso_reauth_request": "SSO reauthentication request has been sent to <0>__email__</0>",
|
||||
"sso_test_interstitial_info_1": "<0>Before starting this test</0>, please ensure you’ve <1>configured Overleaf as a Service Provider in your IdP</1>, and authorized access to the Overleaf service.",
|
||||
"sso_test_interstitial_info_2": "Clicking <0>Test configuration</0> will redirect you to your IdP’s login screen. <1>Read our documentation</1> for full details of what happens during the test. And check our <2>SSO troubleshooting advice</2> if you get stuck.",
|
||||
"sso_test_interstitial_title": "Let’s test your SSO configuration",
|
||||
|
@ -2060,6 +2061,8 @@
|
|||
"unlink_provider_account_warning": "Warning: When you unlink your account from __provider__ you will not be able to sign in using __provider__ anymore.",
|
||||
"unlink_reference": "Unlink References Provider",
|
||||
"unlink_the_project_from_the_current_github_repo": "Unlink the project from the current GitHub repository and create a connection to a repository you own. (You need an active __appName__ subscription to set up a GitHub Sync).",
|
||||
"unlink_user": "Unlink user",
|
||||
"unlink_user_explanation": "You’re about to remove the SSO login option for <0>__email__</0>. This will force them to reauthenticate their Overleaf account with your IdP. They’ll receive an email asking them to do this.",
|
||||
"unlink_users": "Unlink users",
|
||||
"unlink_warning_reference": "Warning: When you unlink your account from this provider you will not be able to import references into your projects.",
|
||||
"unlinking": "Unlinking",
|
||||
|
|
|
@ -23,6 +23,7 @@ function mountDropDownComponent(user: User, subscriptionId: string) {
|
|||
<DropdownButton
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -314,6 +315,7 @@ describe('DropdownButton', function () {
|
|||
|
||||
cy.findByTestId('resend-managed-user-invite-action').should('not.exist')
|
||||
cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
|
||||
cy.findByTestId('unlink-user-action').should('not.exist')
|
||||
cy.findByTestId('no-actions-available').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
@ -422,6 +424,7 @@ describe('DropdownButton', function () {
|
|||
|
||||
cy.findByTestId('resend-managed-user-invite-action').should('not.exist')
|
||||
cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
|
||||
cy.findByTestId('unlink-user-action').should('not.exist')
|
||||
cy.findByTestId('no-actions-available').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
@ -468,6 +471,7 @@ describe('DropdownButton', function () {
|
|||
'be.visible'
|
||||
)
|
||||
cy.findByTestId('remove-user-action').should('be.visible')
|
||||
cy.findByTestId('unlink-user-action').should('be.visible')
|
||||
|
||||
cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
|
||||
})
|
||||
|
@ -512,6 +516,7 @@ describe('DropdownButton', function () {
|
|||
cy.findByTestId('resend-sso-link-invite-action').should('be.visible')
|
||||
|
||||
cy.findByTestId('no-actions-available').should('not.exist')
|
||||
cy.findByTestId('unlink-user-action').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -557,6 +562,7 @@ describe('DropdownButton', function () {
|
|||
'be.visible'
|
||||
)
|
||||
cy.findByTestId('remove-user-action').should('be.visible')
|
||||
cy.findByTestId('unlink-user-action').should('be.visible')
|
||||
|
||||
cy.findByTestId('delete-user-action').should('not.exist')
|
||||
cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
|
||||
|
@ -779,11 +785,12 @@ describe('DropdownButton', function () {
|
|||
cy.get(`.action-btn`).should('exist')
|
||||
})
|
||||
|
||||
it('should show no actions when dropdown button is clicked', function () {
|
||||
it('should show no actions except to unlink when dropdown button is clicked', function () {
|
||||
cy.get('.action-btn').click()
|
||||
|
||||
cy.findByTestId('no-actions-available').should('exist')
|
||||
cy.findByTestId('unlink-user-action').should('exist')
|
||||
|
||||
cy.findByTestId('no-actions-available').should('not.exist')
|
||||
cy.findByTestId('delete-user-action').should('not.exist')
|
||||
cy.findByTestId('remove-user-action').should('not.exist')
|
||||
cy.findByTestId('resend-managed-user-invite-action').should('not.exist')
|
||||
|
|
|
@ -36,6 +36,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -87,6 +88,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -122,6 +124,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -157,6 +160,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -205,6 +209,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -255,6 +260,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -290,6 +296,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -325,6 +332,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -373,6 +381,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -425,6 +434,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -460,6 +470,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -495,6 +506,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -544,6 +556,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -596,6 +609,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -631,6 +645,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
@ -666,6 +681,7 @@ describe('MemberRow', function () {
|
|||
<MemberRow
|
||||
user={user}
|
||||
openOffboardingModalForUser={sinon.stub()}
|
||||
openUnlinkUserModal={sinon.stub()}
|
||||
groupId={subscriptionId}
|
||||
setGroupUserAlert={sinon.stub()}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { render, screen } from '@testing-library/react'
|
||||
import { ReactElement } from 'react'
|
||||
import sinon from 'sinon'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import UnlinkUserModal from '@/features/group-management/components/members-table/unlink-user-modal'
|
||||
import { GroupMembersProvider } from '@/features/group-management/context/group-members-context'
|
||||
|
||||
export function renderWithContext(component: ReactElement, props = {}) {
|
||||
const GroupMembersProviderWrapper = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactElement
|
||||
}) => <GroupMembersProvider {...props}>{children}</GroupMembersProvider>
|
||||
|
||||
return render(component, { wrapper: GroupMembersProviderWrapper })
|
||||
}
|
||||
|
||||
describe('<UnlinkUserModal />', function () {
|
||||
let defaultProps: any
|
||||
|
||||
beforeEach(function () {
|
||||
defaultProps = {
|
||||
onClose: sinon.stub(),
|
||||
user: {},
|
||||
setGroupUserAlert: sinon.stub(),
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.reset()
|
||||
})
|
||||
|
||||
it('displays the modal', async function () {
|
||||
renderWithContext(<UnlinkUserModal {...defaultProps} />)
|
||||
await screen.findByRole('heading', {
|
||||
name: 'Unlink user',
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue