mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 20:53:39 -05:00
Switch to useScopeValue
for project data in Share modal (#3823)
GitOrigin-RevId: f82170c241c59cf7b66fea7e1471004e46ab3547
This commit is contained in:
parent
445c850004
commit
d0d28524a2
7 changed files with 181 additions and 147 deletions
|
@ -129,8 +129,6 @@ header.toolbar.toolbar-header.toolbar-with-labels(
|
|||
handle-hide="handleHide"
|
||||
show="show"
|
||||
is-admin="isAdmin"
|
||||
project="clonedProject"
|
||||
update-project="updateProject"
|
||||
)
|
||||
!= moduleIncludes('publish:button', locals)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import {
|
||||
|
@ -25,6 +25,11 @@ export default function EditMember({ member }) {
|
|||
setConfirmingOwnershipTransfer
|
||||
] = useState(false)
|
||||
|
||||
// update the local state if the member's privileges change externally
|
||||
useEffect(() => {
|
||||
setPrivileges(member.privileges)
|
||||
}, [member.privileges])
|
||||
|
||||
const { updateProject, monitorRequest } = useShareProjectContext()
|
||||
const project = useProjectContext()
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import React, {
|
|||
} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ShareProjectModalContent from './share-project-modal-content'
|
||||
import useScopeValue from '../../../shared/context/util/scope-value-hook'
|
||||
|
||||
const ShareProjectContext = createContext()
|
||||
|
||||
|
@ -87,12 +88,13 @@ export default function ShareProjectModal({
|
|||
animation = true,
|
||||
isAdmin,
|
||||
eventTracking,
|
||||
project,
|
||||
updateProject
|
||||
ide
|
||||
}) {
|
||||
const [inFlight, setInFlight] = useState(false)
|
||||
const [error, setError] = useState()
|
||||
|
||||
const [project, setProject] = useScopeValue('project', ide.$scope, true)
|
||||
|
||||
// reset error when the modal is opened
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
|
@ -129,6 +131,14 @@ export default function ShareProjectModal({
|
|||
return promise
|
||||
}, [])
|
||||
|
||||
// merge the new data with the old project data
|
||||
const updateProject = useCallback(
|
||||
data => {
|
||||
setProject(project => Object.assign(project, data))
|
||||
},
|
||||
[setProject]
|
||||
)
|
||||
|
||||
if (!project) {
|
||||
return null
|
||||
}
|
||||
|
@ -162,10 +172,11 @@ ShareProjectModal.propTypes = {
|
|||
animation: PropTypes.bool,
|
||||
handleHide: PropTypes.func.isRequired,
|
||||
isAdmin: PropTypes.bool.isRequired,
|
||||
project: projectShape,
|
||||
ide: PropTypes.shape({
|
||||
$scope: PropTypes.object.isRequired
|
||||
}).isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
eventTracking: PropTypes.shape({
|
||||
sendMB: PropTypes.func.isRequired
|
||||
}),
|
||||
updateProject: PropTypes.func.isRequired
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import App from '../../../base'
|
||||
import { react2angular } from 'react2angular'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
import ShareProjectModal from '../components/share-project-modal'
|
||||
import { listProjectInvites, listProjectMembers } from '../utils/api'
|
||||
|
||||
App.component('shareProjectModal', react2angular(ShareProjectModal))
|
||||
App.component(
|
||||
'shareProjectModal',
|
||||
react2angular(ShareProjectModal, undefined, ['ide'])
|
||||
)
|
||||
|
||||
export default App.controller('ReactShareProjectModalController', function(
|
||||
$scope,
|
||||
|
@ -15,49 +17,20 @@ export default App.controller('ReactShareProjectModalController', function(
|
|||
$scope.isAdmin = false
|
||||
$scope.show = false
|
||||
|
||||
let deregisterProjectWatch
|
||||
|
||||
// deep watch $scope.project for changes
|
||||
function registerProjectWatch() {
|
||||
deregisterProjectWatch = $scope.$watch(
|
||||
'project',
|
||||
project => {
|
||||
$scope.clonedProject = cloneDeep(project)
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
$scope.handleHide = () => {
|
||||
$scope.$applyAsync(() => {
|
||||
$scope.show = false
|
||||
if (deregisterProjectWatch) {
|
||||
deregisterProjectWatch()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$scope.openShareProjectModal = isAdmin => {
|
||||
eventTracking.sendMBOnce('ide-open-share-modal-once')
|
||||
$scope.$applyAsync(() => {
|
||||
registerProjectWatch()
|
||||
|
||||
$scope.isAdmin = isAdmin
|
||||
$scope.show = true
|
||||
})
|
||||
}
|
||||
|
||||
// update $scope.project with new data
|
||||
$scope.updateProject = data => {
|
||||
if (!$scope.project) {
|
||||
return
|
||||
}
|
||||
|
||||
$scope.$applyAsync(() => {
|
||||
Object.assign($scope.project, data)
|
||||
})
|
||||
}
|
||||
|
||||
/* tokens */
|
||||
|
||||
ide.socket.on('project:tokens:changed', data => {
|
||||
|
|
|
@ -8,6 +8,7 @@ import _ from 'lodash'
|
|||
*
|
||||
* @param {string} path - dot '.' path of a property in `sourceScope`.
|
||||
* @param {object} $scope - Angular $scope containing the value to bind.
|
||||
* @param {boolean} deep
|
||||
* @returns {[any, function]} - Binded value and setter function tuple.
|
||||
*/
|
||||
export default function useScopeValue(path, $scope, deep = false) {
|
||||
|
@ -17,7 +18,7 @@ export default function useScopeValue(path, $scope, deep = false) {
|
|||
return $scope.$watch(
|
||||
path,
|
||||
newValue => {
|
||||
setValue(newValue)
|
||||
setValue(deep ? _.cloneDeep(newValue) : newValue)
|
||||
},
|
||||
deep
|
||||
)
|
||||
|
|
|
@ -73,6 +73,16 @@ const setupFetchMock = () => {
|
|||
})
|
||||
}
|
||||
|
||||
const ideWithProject = project => {
|
||||
return {
|
||||
$scope: {
|
||||
$watch: () => () => {},
|
||||
$applyAsync: () => {},
|
||||
project
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const LinkSharingOff = args => {
|
||||
setupFetchMock()
|
||||
|
||||
|
@ -81,7 +91,7 @@ export const LinkSharingOff = args => {
|
|||
publicAccesLevel: 'private'
|
||||
}
|
||||
|
||||
return <ShareProjectModal {...args} project={project} />
|
||||
return <ShareProjectModal {...args} ide={ideWithProject(project)} />
|
||||
}
|
||||
|
||||
export const LinkSharingOn = args => {
|
||||
|
@ -92,7 +102,7 @@ export const LinkSharingOn = args => {
|
|||
publicAccesLevel: 'tokenBased'
|
||||
}
|
||||
|
||||
return <ShareProjectModal {...args} project={project} />
|
||||
return <ShareProjectModal {...args} ide={ideWithProject(project)} />
|
||||
}
|
||||
|
||||
export const LinkSharingLoading = args => {
|
||||
|
@ -104,7 +114,7 @@ export const LinkSharingLoading = args => {
|
|||
tokens: undefined
|
||||
}
|
||||
|
||||
return <ShareProjectModal {...args} project={project} />
|
||||
return <ShareProjectModal {...args} ide={ideWithProject(project)} />
|
||||
}
|
||||
|
||||
export const NonAdminLinkSharingOff = args => {
|
||||
|
@ -113,7 +123,13 @@ export const NonAdminLinkSharingOff = args => {
|
|||
publicAccesLevel: 'private'
|
||||
}
|
||||
|
||||
return <ShareProjectModal {...args} isAdmin={false} project={project} />
|
||||
return (
|
||||
<ShareProjectModal
|
||||
{...args}
|
||||
isAdmin={false}
|
||||
ide={ideWithProject(project)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const NonAdminLinkSharingOn = args => {
|
||||
|
@ -122,7 +138,13 @@ export const NonAdminLinkSharingOn = args => {
|
|||
publicAccesLevel: 'tokenBased'
|
||||
}
|
||||
|
||||
return <ShareProjectModal {...args} isAdmin={false} project={project} />
|
||||
return (
|
||||
<ShareProjectModal
|
||||
{...args}
|
||||
isAdmin={false}
|
||||
ide={ideWithProject(project)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const RestrictedTokenMember = args => {
|
||||
|
@ -139,7 +161,7 @@ export const RestrictedTokenMember = args => {
|
|||
publicAccesLevel: 'tokenBased'
|
||||
}
|
||||
|
||||
return <ShareProjectModal {...args} project={project} />
|
||||
return <ShareProjectModal {...args} ide={ideWithProject(project)} />
|
||||
}
|
||||
|
||||
export const LegacyLinkSharingReadAndWrite = args => {
|
||||
|
@ -150,7 +172,7 @@ export const LegacyLinkSharingReadAndWrite = args => {
|
|||
publicAccesLevel: 'readAndWrite'
|
||||
}
|
||||
|
||||
return <ShareProjectModal {...args} project={project} />
|
||||
return <ShareProjectModal {...args} ide={ideWithProject(project)} />
|
||||
}
|
||||
|
||||
export const LegacyLinkSharingReadOnly = args => {
|
||||
|
@ -161,7 +183,7 @@ export const LegacyLinkSharingReadOnly = args => {
|
|||
publicAccesLevel: 'readOnly'
|
||||
}
|
||||
|
||||
return <ShareProjectModal {...args} project={project} />
|
||||
return <ShareProjectModal {...args} ide={ideWithProject(project)} />
|
||||
}
|
||||
|
||||
export const LimitedCollaborators = args => {
|
||||
|
@ -175,7 +197,7 @@ export const LimitedCollaborators = args => {
|
|||
}
|
||||
}
|
||||
|
||||
return <ShareProjectModal {...args} project={project} />
|
||||
return <ShareProjectModal {...args} ide={ideWithProject(project)} />
|
||||
}
|
||||
|
||||
const project = {
|
||||
|
@ -232,8 +254,7 @@ export default {
|
|||
animation: false,
|
||||
isAdmin: true,
|
||||
user: {},
|
||||
project,
|
||||
updateProject: () => null
|
||||
project
|
||||
},
|
||||
argTypes: {
|
||||
handleHide: { action: 'hide' }
|
||||
|
|
|
@ -2,12 +2,12 @@ import { expect } from 'chai'
|
|||
import sinon from 'sinon'
|
||||
import React from 'react'
|
||||
import {
|
||||
act,
|
||||
cleanup,
|
||||
render,
|
||||
screen,
|
||||
fireEvent,
|
||||
waitFor
|
||||
waitFor,
|
||||
waitForElementToBeRemoved
|
||||
} from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import ShareProjectModal from '../../../../../frontend/js/features/share-project-modal/components/share-project-modal'
|
||||
|
@ -69,12 +69,21 @@ describe('<ShareProjectModal/>', function() {
|
|||
}
|
||||
]
|
||||
|
||||
const ideWithProject = project => {
|
||||
return {
|
||||
$scope: {
|
||||
$watch: () => () => {},
|
||||
$applyAsync: () => {},
|
||||
project
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const modalProps = {
|
||||
ide: ideWithProject(project),
|
||||
show: true,
|
||||
isAdmin: true,
|
||||
project,
|
||||
handleHide: sinon.stub(),
|
||||
updateProject: sinon.stub()
|
||||
handleHide: sinon.stub()
|
||||
}
|
||||
|
||||
const originalExposedSettings = window.ExposedSettings
|
||||
|
@ -122,7 +131,7 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{ ...project, publicAccesLevel: 'private' }}
|
||||
ide={ideWithProject({ ...project, publicAccesLevel: 'private' })}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -141,7 +150,7 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{ ...project, publicAccesLevel: 'tokenBased' }}
|
||||
ide={ideWithProject({ ...project, publicAccesLevel: 'tokenBased' })}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -158,7 +167,7 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{ ...project, publicAccesLevel: 'readAndWrite' }}
|
||||
ide={ideWithProject({ ...project, publicAccesLevel: 'readAndWrite' })}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -172,7 +181,7 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{ ...project, publicAccesLevel: 'readOnly' }}
|
||||
ide={ideWithProject({ ...project, publicAccesLevel: 'readOnly' })}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -195,7 +204,11 @@ describe('<ShareProjectModal/>', function() {
|
|||
const { rerender } = render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{ ...project, invites, publicAccesLevel: 'tokenBased' }}
|
||||
ide={ideWithProject({
|
||||
...project,
|
||||
invites,
|
||||
publicAccesLevel: 'tokenBased'
|
||||
})}
|
||||
isAdmin
|
||||
/>
|
||||
)
|
||||
|
@ -207,7 +220,11 @@ describe('<ShareProjectModal/>', function() {
|
|||
rerender(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{ ...project, invites, publicAccesLevel: 'tokenBased' }}
|
||||
ide={ideWithProject({
|
||||
...project,
|
||||
invites,
|
||||
publicAccesLevel: 'tokenBased'
|
||||
})}
|
||||
isAdmin={false}
|
||||
/>
|
||||
)
|
||||
|
@ -222,17 +239,21 @@ describe('<ShareProjectModal/>', function() {
|
|||
.null
|
||||
expect(screen.queryByRole('button', { name: 'Resend' })).to.be.null
|
||||
|
||||
// render as non-admin, link sharing off: actions should be missing and message should be present
|
||||
// render as non-admin (non-owner), link sharing off: actions should be missing and message should be present
|
||||
rerender(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{ ...project, invites, publicAccesLevel: 'private' }}
|
||||
ide={ideWithProject({
|
||||
...project,
|
||||
invites,
|
||||
publicAccesLevel: 'private'
|
||||
})}
|
||||
isAdmin={false}
|
||||
/>
|
||||
)
|
||||
|
||||
await screen.findByText(
|
||||
'To add more collaborators or turn on link sharing, please ask the project owner'
|
||||
'To change access permissions, please ask the project owner'
|
||||
)
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'Turn off link sharing' })).to
|
||||
|
@ -248,7 +269,7 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{ ...project, publicAccesLevel: 'tokenBased' }}
|
||||
ide={ideWithProject({ ...project, publicAccesLevel: 'tokenBased' })}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -296,12 +317,12 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{
|
||||
ide={ideWithProject({
|
||||
...project,
|
||||
members,
|
||||
invites,
|
||||
publicAccesLevel: 'tokenBased'
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -340,11 +361,11 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{
|
||||
ide={ideWithProject({
|
||||
...project,
|
||||
invites,
|
||||
publicAccesLevel: 'tokenBased'
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -353,11 +374,9 @@ describe('<ShareProjectModal/>', function() {
|
|||
})
|
||||
|
||||
const resendButton = screen.getByRole('button', { name: 'Resend' })
|
||||
fireEvent.click(resendButton)
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(resendButton)
|
||||
expect(closeButton.disabled).to.be.true
|
||||
})
|
||||
await waitFor(() => expect(closeButton.disabled).to.be.true)
|
||||
|
||||
expect(fetchMock.done()).to.be.true
|
||||
expect(closeButton.disabled).to.be.false
|
||||
|
@ -377,11 +396,11 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{
|
||||
ide={ideWithProject({
|
||||
...project,
|
||||
invites,
|
||||
publicAccesLevel: 'tokenBased'
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -390,11 +409,8 @@ describe('<ShareProjectModal/>', function() {
|
|||
})
|
||||
|
||||
const revokeButton = screen.getByRole('button', { name: 'Revoke' })
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(revokeButton)
|
||||
expect(closeButton.disabled).to.be.true
|
||||
})
|
||||
fireEvent.click(revokeButton)
|
||||
await waitFor(() => expect(closeButton.disabled).to.be.true)
|
||||
|
||||
expect(fetchMock.done()).to.be.true
|
||||
expect(closeButton.disabled).to.be.false
|
||||
|
@ -414,11 +430,11 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{
|
||||
ide={ideWithProject({
|
||||
...project,
|
||||
members,
|
||||
publicAccesLevel: 'tokenBased'
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -433,13 +449,11 @@ describe('<ShareProjectModal/>', function() {
|
|||
|
||||
const changeButton = screen.getByRole('button', { name: 'Change' })
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(changeButton)
|
||||
expect(closeButton.disabled).to.be.true
|
||||
fireEvent.click(changeButton)
|
||||
await waitFor(() => expect(closeButton.disabled).to.be.true)
|
||||
|
||||
const { body } = fetchMock.lastOptions()
|
||||
expect(JSON.parse(body)).to.deep.equal({ privilegeLevel: 'readAndWrite' })
|
||||
})
|
||||
|
||||
expect(fetchMock.done()).to.be.true
|
||||
expect(closeButton.disabled).to.be.false
|
||||
|
@ -459,11 +473,11 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{
|
||||
ide={ideWithProject({
|
||||
...project,
|
||||
members,
|
||||
publicAccesLevel: 'tokenBased'
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -473,13 +487,16 @@ describe('<ShareProjectModal/>', function() {
|
|||
name: 'Remove from project'
|
||||
})
|
||||
|
||||
act(() => {
|
||||
removeButton.click()
|
||||
})
|
||||
fireEvent.click(removeButton)
|
||||
|
||||
const url = fetchMock.lastUrl()
|
||||
expect(url).to.equal('/project/test-project/users/member-viewer')
|
||||
|
||||
expect(fetchMock.done()).to.be.true
|
||||
|
||||
// TODO: once the project data is updated, assert that the member has been removed
|
||||
await waitForElementToBeRemoved(() =>
|
||||
screen.queryByText('member-viewer@example.com')
|
||||
)
|
||||
})
|
||||
|
||||
it('changes member privileges to owner with confirmation', async function() {
|
||||
|
@ -496,11 +513,11 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{
|
||||
ide={ideWithProject({
|
||||
...project,
|
||||
members,
|
||||
publicAccesLevel: 'tokenBased'
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -519,20 +536,16 @@ describe('<ShareProjectModal/>', function() {
|
|||
)
|
||||
})
|
||||
|
||||
const confirmButton = screen.getByRole('button', {
|
||||
name: 'Change owner',
|
||||
hidden: true
|
||||
})
|
||||
|
||||
const reloadStub = sinon.stub(locationModule, 'reload')
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(confirmButton)
|
||||
expect(confirmButton.disabled).to.be.true
|
||||
const confirmButton = screen.getByRole('button', {
|
||||
name: 'Change owner'
|
||||
})
|
||||
fireEvent.click(confirmButton)
|
||||
await waitFor(() => expect(confirmButton.disabled).to.be.true)
|
||||
|
||||
const { body } = fetchMock.lastOptions()
|
||||
expect(JSON.parse(body)).to.deep.equal({ user_id: 'member-viewer' })
|
||||
})
|
||||
|
||||
expect(fetchMock.done()).to.be.true
|
||||
expect(reloadStub.calledOnce).to.be.true
|
||||
|
@ -540,39 +553,13 @@ describe('<ShareProjectModal/>', function() {
|
|||
})
|
||||
|
||||
it('sends invites to input email addresses', async function() {
|
||||
// TODO: can't use this as the value of useProjectContext doesn't get updated
|
||||
// let mergedProject = {
|
||||
// ...project,
|
||||
// publicAccesLevel: 'tokenBased'
|
||||
// }
|
||||
//
|
||||
// const updateProject = value => {
|
||||
// mergedProject = { ...mergedProject, ...value }
|
||||
//
|
||||
// rerender(
|
||||
// <ShareProjectModal
|
||||
// {...modalProps}
|
||||
// project={mergedProject}
|
||||
// updateProject={updateProject}
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// const { rerender } = render(
|
||||
// <ShareProjectModal
|
||||
// {...modalProps}
|
||||
// project={mergedProject}
|
||||
// updateProject={updateProject}
|
||||
// />
|
||||
// )
|
||||
|
||||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{
|
||||
ide={ideWithProject({
|
||||
...project,
|
||||
publicAccesLevel: 'tokenBased'
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -661,13 +648,13 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{
|
||||
ide={ideWithProject({
|
||||
...project,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
features: {
|
||||
collaborators: 0
|
||||
}
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -684,10 +671,10 @@ describe('<ShareProjectModal/>', function() {
|
|||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
project={{
|
||||
ide={ideWithProject({
|
||||
...project,
|
||||
publicAccesLevel: 'tokenBased'
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -703,13 +690,11 @@ describe('<ShareProjectModal/>', function() {
|
|||
const submitButton = screen.getByRole('button', { name: 'Share' })
|
||||
|
||||
const respondWithError = async function(errorReason) {
|
||||
await act(async () => {
|
||||
inputElement.focus()
|
||||
await fireEvent.change(inputElement, {
|
||||
fireEvent.change(inputElement, {
|
||||
target: { value: 'invited-author-1@example.com' }
|
||||
})
|
||||
inputElement.blur()
|
||||
})
|
||||
|
||||
fetchMock.postOnce(
|
||||
'express:/project/:projectId/invite',
|
||||
|
@ -748,5 +733,45 @@ describe('<ShareProjectModal/>', function() {
|
|||
)
|
||||
})
|
||||
|
||||
// TODO: add test for switching between token-access and private, once project data is in React context
|
||||
it('handles switching between access levels', async function() {
|
||||
fetchMock.post('express:/project/:projectId/settings/admin', 204)
|
||||
|
||||
render(
|
||||
<ShareProjectModal
|
||||
{...modalProps}
|
||||
ide={ideWithProject({ ...project, publicAccesLevel: 'private' })}
|
||||
/>
|
||||
)
|
||||
|
||||
await screen.findByText(
|
||||
'Link sharing is off, only invited users can view this project.'
|
||||
)
|
||||
|
||||
const enableButton = await screen.findByRole('button', {
|
||||
name: 'Turn on link sharing'
|
||||
})
|
||||
fireEvent.click(enableButton)
|
||||
await waitFor(() => expect(enableButton.disabled).to.be.true)
|
||||
|
||||
const { body: tokenBody } = fetchMock.lastOptions()
|
||||
expect(JSON.parse(tokenBody)).to.deep.equal({
|
||||
publicAccessLevel: 'tokenBased'
|
||||
})
|
||||
|
||||
await screen.findByText('Link sharing is on')
|
||||
const disableButton = await screen.findByRole('button', {
|
||||
name: 'Turn off link sharing'
|
||||
})
|
||||
fireEvent.click(disableButton)
|
||||
await waitFor(() => expect(disableButton.disabled).to.be.true)
|
||||
|
||||
const { body: privateBody } = fetchMock.lastOptions()
|
||||
expect(JSON.parse(privateBody)).to.deep.equal({
|
||||
publicAccessLevel: 'private'
|
||||
})
|
||||
|
||||
await screen.findByText(
|
||||
'Link sharing is off, only invited users can view this project.'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue