mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 14:13:43 +01:00
Move project context out of scope value store (#26615)
* Refactor project context to not use scope store * Fix Cypress tests for project context changes * Fix frontend React Testing Library tests for project context changes * Remove redundant code * Fix some project types in tests * Remove unused import and fix a type * Throw an error if updating the project in the project context before joining the project * Fix some review panel tests * Remove unused imports GitOrigin-RevId: 2f0c928b651f387aa980c29aef7d1ba0649790a7
This commit is contained in:
@@ -4,11 +4,16 @@ import {
|
||||
OverallThemeMeta,
|
||||
SpellCheckLanguage,
|
||||
} from '../../../../types/project-settings'
|
||||
import { EditorProviders } from '../../helpers/editor-providers'
|
||||
import {
|
||||
EditorProviders,
|
||||
makeProjectProvider,
|
||||
} from '../../helpers/editor-providers'
|
||||
import { mockScope } from './scope'
|
||||
import { Folder } from '../../../../types/folder'
|
||||
import { docsInFolder } from '@/features/file-tree/util/docs-in-folder'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { mockProject } from '../../features/source-editor/helpers/mock-project'
|
||||
import { UserId } from '../../../../types/user'
|
||||
|
||||
describe('<EditorLeftMenu />', function () {
|
||||
beforeEach(function () {
|
||||
@@ -57,9 +62,14 @@ describe('<EditorLeftMenu />', function () {
|
||||
|
||||
it('render full menu', function () {
|
||||
const scope = mockScope()
|
||||
const project = mockProject()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders scope={scope} layoutContext={{ leftMenuShown: true }}>
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
providers={{ ProjectProvider: makeProjectProvider(project) }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
@@ -209,12 +219,6 @@ describe('<EditorLeftMenu />', function () {
|
||||
})
|
||||
|
||||
const scope = mockScope({
|
||||
project: {
|
||||
members: [],
|
||||
owner: {
|
||||
_id: '123',
|
||||
},
|
||||
},
|
||||
user: {
|
||||
features: {
|
||||
dropbox: false,
|
||||
@@ -226,6 +230,14 @@ describe('<EditorLeftMenu />', function () {
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
projectOwner={{
|
||||
_id: '123' as UserId,
|
||||
email: 'owner@example.com',
|
||||
first_name: 'Test',
|
||||
last_name: 'Owner',
|
||||
privileges: 'owner',
|
||||
signUpDate: new Date('2025-07-07').toISOString(),
|
||||
}}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
@@ -236,21 +248,25 @@ describe('<EditorLeftMenu />', function () {
|
||||
})
|
||||
|
||||
it('shows git modal correctly', function () {
|
||||
const scope = mockScope({
|
||||
project: {
|
||||
owner: {
|
||||
_id: '123',
|
||||
},
|
||||
features: {
|
||||
gitBridge: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
projectOwner={{
|
||||
_id: '123' as UserId,
|
||||
email: 'owner@example.com',
|
||||
first_name: 'Test',
|
||||
last_name: 'Owner',
|
||||
privileges: 'owner',
|
||||
signUpDate: new Date('2025-07-07').toISOString(),
|
||||
}}
|
||||
projectFeatures={
|
||||
{
|
||||
gitBridge: true,
|
||||
} as any
|
||||
}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
@@ -262,21 +278,25 @@ describe('<EditorLeftMenu />', function () {
|
||||
})
|
||||
|
||||
it('shows git modal paywall correctly', function () {
|
||||
const scope = mockScope({
|
||||
project: {
|
||||
owner: {
|
||||
_id: '123',
|
||||
},
|
||||
features: {
|
||||
gitBridge: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
projectOwner={{
|
||||
_id: '123' as UserId,
|
||||
email: 'owner@example.com',
|
||||
first_name: 'Test',
|
||||
last_name: 'Owner',
|
||||
privileges: 'owner',
|
||||
signUpDate: new Date('2025-07-07').toISOString(),
|
||||
}}
|
||||
projectFeatures={
|
||||
{
|
||||
gitBridge: false,
|
||||
} as any
|
||||
}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '../../../../frontend/js/shared/context/layout-context'
|
||||
import { FC, PropsWithChildren, useEffect } from 'react'
|
||||
import { useLocalCompileContext } from '@/shared/context/local-compile-context'
|
||||
import { ProjectCompiler } from '../../../../types/project-settings'
|
||||
|
||||
const storeAndFireEvent = (win: typeof window, key: string, value: unknown) => {
|
||||
localStorage.setItem(key, value)
|
||||
@@ -215,7 +216,7 @@ describe('<PdfPreview/>', function () {
|
||||
cached: false,
|
||||
setup: () => {},
|
||||
props: {
|
||||
compiler: 'lualatex',
|
||||
compiler: 'lualatex' as ProjectCompiler,
|
||||
},
|
||||
},
|
||||
'ignores the compile from cache when draft mode changed': {
|
||||
|
||||
+7
-7
@@ -15,9 +15,9 @@ describe('<EditorCloneProjectModalWrapper />', function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
const project = {
|
||||
_id: 'project-1',
|
||||
name: 'Test Project',
|
||||
const contextProps = {
|
||||
projectId: 'project-1',
|
||||
projectName: 'Test Project',
|
||||
}
|
||||
|
||||
it('renders the translated modal title', async function () {
|
||||
@@ -30,7 +30,7 @@ describe('<EditorCloneProjectModalWrapper />', function () {
|
||||
openProject={openProject}
|
||||
show
|
||||
/>,
|
||||
{ scope: { project } }
|
||||
contextProps
|
||||
)
|
||||
|
||||
await screen.findByText('Copy project')
|
||||
@@ -55,7 +55,7 @@ describe('<EditorCloneProjectModalWrapper />', function () {
|
||||
openProject={openProject}
|
||||
show
|
||||
/>,
|
||||
{ scope: { project } }
|
||||
contextProps
|
||||
)
|
||||
|
||||
const cancelButton = await screen.findByRole('button', { name: 'Cancel' })
|
||||
@@ -123,7 +123,7 @@ describe('<EditorCloneProjectModalWrapper />', function () {
|
||||
openProject={openProject}
|
||||
show
|
||||
/>,
|
||||
{ scope: { project } }
|
||||
contextProps
|
||||
)
|
||||
|
||||
const button = await screen.findByRole('button', { name: 'Copy' })
|
||||
@@ -160,7 +160,7 @@ describe('<EditorCloneProjectModalWrapper />', function () {
|
||||
openProject={openProject}
|
||||
show
|
||||
/>,
|
||||
{ scope: { project } }
|
||||
contextProps
|
||||
)
|
||||
|
||||
const button = await screen.findByRole('button', { name: 'Copy' })
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import CodeMirrorEditor from '../../../../frontend/js/features/source-editor/components/codemirror-editor'
|
||||
import {
|
||||
EditorProviders,
|
||||
makeProjectProvider,
|
||||
USER_EMAIL,
|
||||
USER_ID,
|
||||
} from '../../helpers/editor-providers'
|
||||
import { mockScope } from '../source-editor/helpers/mock-scope'
|
||||
import { TestContainer } from '../source-editor/helpers/test-container'
|
||||
import { docId } from '../source-editor/helpers/mock-doc'
|
||||
import { mockProject } from '../source-editor/helpers/mock-project'
|
||||
|
||||
const userData = {
|
||||
avatar_text: 'User',
|
||||
@@ -181,14 +183,22 @@ describe('<ReviewPanel />', function () {
|
||||
removeChangeIds,
|
||||
},
|
||||
},
|
||||
projectFeatures: { trackChangesVisible: true },
|
||||
})
|
||||
const project = mockProject({
|
||||
projectOwner: {
|
||||
_id: USER_ID,
|
||||
},
|
||||
projectFeatures: { trackChanges: false, trackChangesVisible: true },
|
||||
})
|
||||
|
||||
cy.wrap(scope).as('scope')
|
||||
|
||||
cy.mount(
|
||||
<TestContainer className="rp-size-expanded">
|
||||
<EditorProviders scope={scope}>
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
providers={{ ProjectProvider: makeProjectProvider(project) }}
|
||||
>
|
||||
<CodeMirrorEditor />
|
||||
</EditorProviders>
|
||||
</TestContainer>
|
||||
@@ -653,6 +663,10 @@ describe('<ReviewPanel /> in mini mode', function () {
|
||||
projectFeatures: { trackChangesVisible: true },
|
||||
})
|
||||
|
||||
const project = mockProject({
|
||||
projectFeatures: { trackChangesVisible: true },
|
||||
})
|
||||
|
||||
cy.intercept('GET', '/project/*/ranges', [
|
||||
{
|
||||
id: docId,
|
||||
@@ -672,7 +686,10 @@ describe('<ReviewPanel /> in mini mode', function () {
|
||||
|
||||
cy.mount(
|
||||
<TestContainer>
|
||||
<EditorProviders scope={scope}>
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
providers={{ ProjectProvider: makeProjectProvider(project) }}
|
||||
>
|
||||
<CodeMirrorEditor />
|
||||
</EditorProviders>
|
||||
</TestContainer>
|
||||
@@ -717,7 +734,7 @@ describe('<ReviewPanel /> in mini mode', function () {
|
||||
cy.get('.review-panel-mini').should('not.exist')
|
||||
})
|
||||
|
||||
it("doesn't render mini when a resolved comments is present in document", function () {
|
||||
it("doesn't render mini when a resolved comment is present in document", function () {
|
||||
render({
|
||||
comments: [
|
||||
{
|
||||
@@ -747,7 +764,7 @@ describe('<ReviewPanel /> in mini mode', function () {
|
||||
cy.get('.review-panel-mini').should('not.exist')
|
||||
})
|
||||
|
||||
it('renders mini when a unresolved comments is present in document', function () {
|
||||
it('renders mini when an unresolved comment is present in document', function () {
|
||||
render({
|
||||
comments: [
|
||||
{
|
||||
@@ -796,6 +813,8 @@ describe('<ReviewPanel /> for free users', function () {
|
||||
function mountEditor(ownerId = USER_ID) {
|
||||
const scope = mockScope(undefined, {
|
||||
permissions: { write: true, trackedWrite: false, comment: true },
|
||||
})
|
||||
const project = mockProject({
|
||||
projectFeatures: { trackChanges: false, trackChangesVisible: true },
|
||||
projectOwner: {
|
||||
_id: ownerId,
|
||||
@@ -806,7 +825,10 @@ describe('<ReviewPanel /> for free users', function () {
|
||||
|
||||
cy.mount(
|
||||
<TestContainer className="rp-size-expanded">
|
||||
<EditorProviders scope={scope}>
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
providers={{ ProjectProvider: makeProjectProvider(project) }}
|
||||
>
|
||||
<CodeMirrorEditor />
|
||||
</EditorProviders>
|
||||
</TestContainer>
|
||||
|
||||
+162
-215
@@ -1,18 +1,19 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { screen, fireEvent, render, waitFor } from '@testing-library/react'
|
||||
import { screen, fireEvent, waitFor } from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
|
||||
import ShareProjectModal from '../../../../../frontend/js/features/share-project-modal/components/share-project-modal'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
import {
|
||||
EditorProviders,
|
||||
makeProjectProvider,
|
||||
projectDefaults,
|
||||
USER_EMAIL,
|
||||
USER_ID,
|
||||
} from '../../../helpers/editor-providers'
|
||||
import { location } from '@/shared/components/location'
|
||||
import useScopeValue from '@/shared/hooks/use-scope-value'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
|
||||
async function changePrivilegeLevel(screen, { current, next }) {
|
||||
const select = screen.getByDisplayValue(current)
|
||||
@@ -23,22 +24,25 @@ async function changePrivilegeLevel(screen, { current, next }) {
|
||||
fireEvent.click(option)
|
||||
}
|
||||
|
||||
describe('<ShareProjectModal/>', function () {
|
||||
const project = {
|
||||
_id: 'test-project',
|
||||
name: 'Test Project',
|
||||
features: {
|
||||
collaborators: 10,
|
||||
compileGroup: 'standard',
|
||||
},
|
||||
owner: {
|
||||
_id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
},
|
||||
members: [],
|
||||
invites: [],
|
||||
}
|
||||
const shareModalProjectDefaults = Object.assign({}, projectDefaults, {
|
||||
_id: 'test-project',
|
||||
name: 'Test Project',
|
||||
features: {
|
||||
collaborators: 10,
|
||||
compileGroup: 'standard',
|
||||
},
|
||||
owner: {
|
||||
_id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
},
|
||||
})
|
||||
|
||||
function createContextProps(projectOverrides) {
|
||||
const project = Object.assign({}, shareModalProjectDefaults, projectOverrides)
|
||||
return { providers: { ProjectProvider: makeProjectProvider(project) } }
|
||||
}
|
||||
|
||||
describe('<ShareProjectModal/>', function () {
|
||||
const contacts = [
|
||||
// user with edited name
|
||||
{
|
||||
@@ -101,9 +105,10 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('renders the modal', async function () {
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: { project },
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps()
|
||||
)
|
||||
|
||||
await screen.findByText('Share Project')
|
||||
})
|
||||
@@ -113,7 +118,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} handleHide={handleHide} />,
|
||||
{ scope: { project } }
|
||||
createContextProps()
|
||||
)
|
||||
|
||||
const [headerCloseButton, footerCloseButton] = await screen.findAllByRole(
|
||||
@@ -128,9 +133,10 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('handles access level "private"', async function () {
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: { project: { ...project, publicAccesLevel: 'private' } },
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'private' })
|
||||
)
|
||||
|
||||
await screen.findByText('Link sharing is off')
|
||||
await screen.findByRole('button', { name: 'Turn on link sharing' })
|
||||
@@ -149,10 +155,11 @@ describe('<ShareProjectModal/>', function () {
|
||||
readAndWriteHashPrefix: 'taEVki',
|
||||
readOnlyHashPrefix: 'j2xYbL',
|
||||
}
|
||||
fetchMock.get(`/project/${project._id}/tokens`, tokens)
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: { project: { ...project, publicAccesLevel: 'tokenBased' } },
|
||||
})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, tokens)
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'tokenBased' })
|
||||
)
|
||||
|
||||
await screen.findByText('Link sharing is on')
|
||||
await screen.findByRole('button', { name: 'Turn off link sharing' })
|
||||
@@ -171,9 +178,10 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('handles legacy access level "readAndWrite"', async function () {
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: { project: { ...project, publicAccesLevel: 'readAndWrite' } },
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'readAndWrite' })
|
||||
)
|
||||
|
||||
await screen.findByText(
|
||||
'This project is public and can be edited by anyone with the URL.'
|
||||
@@ -182,9 +190,10 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('handles legacy access level "readOnly"', async function () {
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: { project: { ...project, publicAccesLevel: 'readOnly' } },
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'readOnly' })
|
||||
)
|
||||
|
||||
await screen.findByText(
|
||||
'This project is public and can be viewed but not edited by anyone with the URL'
|
||||
@@ -193,7 +202,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('displays actions for project-owners', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
|
||||
const invites = [
|
||||
{
|
||||
@@ -204,18 +213,9 @@ describe('<ShareProjectModal/>', function () {
|
||||
]
|
||||
|
||||
// render as project owner: actions should be present
|
||||
render(
|
||||
<EditorProviders
|
||||
scope={{
|
||||
project: {
|
||||
...project,
|
||||
invites,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ShareProjectModal {...modalProps} />
|
||||
</EditorProviders>
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'tokenBased', invites })
|
||||
)
|
||||
|
||||
await screen.findByRole('button', { name: 'Turn off link sharing' })
|
||||
@@ -231,23 +231,13 @@ describe('<ShareProjectModal/>', function () {
|
||||
},
|
||||
]
|
||||
|
||||
render(
|
||||
<EditorProviders
|
||||
scope={{
|
||||
project: {
|
||||
...project,
|
||||
invites,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
},
|
||||
}}
|
||||
user={{
|
||||
id: 'non-project-owner',
|
||||
email: 'non-project-owner@example.com',
|
||||
}}
|
||||
>
|
||||
<ShareProjectModal {...modalProps} />
|
||||
</EditorProviders>
|
||||
)
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
...createContextProps({ publicAccessLevel: 'tokenBased', invites }),
|
||||
user: {
|
||||
id: 'non-project-owner',
|
||||
email: 'non-project-owner@example.com',
|
||||
},
|
||||
})
|
||||
|
||||
await screen.findByText(
|
||||
'To change access permissions, please ask the project owner'
|
||||
@@ -269,23 +259,13 @@ describe('<ShareProjectModal/>', function () {
|
||||
},
|
||||
]
|
||||
|
||||
render(
|
||||
<EditorProviders
|
||||
scope={{
|
||||
project: {
|
||||
...project,
|
||||
invites,
|
||||
publicAccesLevel: 'private',
|
||||
},
|
||||
}}
|
||||
user={{
|
||||
id: 'non-project-owner',
|
||||
email: 'non-project-owner@example.com',
|
||||
}}
|
||||
>
|
||||
<ShareProjectModal {...modalProps} />
|
||||
</EditorProviders>
|
||||
)
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
...createContextProps({ publicAccessLevel: 'private', invites }),
|
||||
user: {
|
||||
id: 'non-project-owner',
|
||||
email: 'non-project-owner@example.com',
|
||||
},
|
||||
})
|
||||
|
||||
await screen.findByText(
|
||||
'To add more collaborators or turn on link sharing, please ask the project owner'
|
||||
@@ -300,11 +280,11 @@ describe('<ShareProjectModal/>', function () {
|
||||
|
||||
it('only shows read-only token link to restricted token members', async function () {
|
||||
window.metaAttributesCache.set('ol-isRestrictedTokenMember', true)
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
...createContextProps({ publicAccessLevel: 'private' }),
|
||||
isRestrictedTokenMember: true,
|
||||
scope: { project: { ...project, publicAccesLevel: 'tokenBased' } },
|
||||
})
|
||||
|
||||
// no buttons
|
||||
@@ -346,18 +326,12 @@ describe('<ShareProjectModal/>', function () {
|
||||
},
|
||||
]
|
||||
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
...project,
|
||||
members,
|
||||
invites,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
},
|
||||
},
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'tokenBased', members, invites })
|
||||
)
|
||||
|
||||
const projectOwnerEmail = USER_EMAIL
|
||||
|
||||
@@ -381,7 +355,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('resends an invite', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
fetchMock.postOnce(
|
||||
'express:/project/:projectId/invite/:inviteId/resend',
|
||||
204
|
||||
@@ -395,15 +369,10 @@ describe('<ShareProjectModal/>', function () {
|
||||
},
|
||||
]
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
...project,
|
||||
invites,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
},
|
||||
},
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'tokenBased', invites })
|
||||
)
|
||||
|
||||
const [, closeButton] = screen.getAllByRole('button', {
|
||||
name: 'Close',
|
||||
@@ -419,7 +388,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('revokes an invite', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
fetchMock.deleteOnce('express:/project/:projectId/invite/:inviteId', 204)
|
||||
|
||||
const invites = [
|
||||
@@ -430,15 +399,10 @@ describe('<ShareProjectModal/>', function () {
|
||||
},
|
||||
]
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
...project,
|
||||
invites,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
},
|
||||
},
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'tokenBased', invites })
|
||||
)
|
||||
|
||||
const [, closeButton] = screen.getAllByRole('button', {
|
||||
name: 'Close',
|
||||
@@ -453,7 +417,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('changes member privileges to read + write', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
fetchMock.putOnce('express:/project/:projectId/users/:userId', 204)
|
||||
|
||||
const members = [
|
||||
@@ -464,17 +428,12 @@ describe('<ShareProjectModal/>', function () {
|
||||
},
|
||||
]
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
...project,
|
||||
members,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
},
|
||||
},
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'tokenBased', members })
|
||||
)
|
||||
|
||||
const [, closeButton] = await screen.getAllByRole('button', {
|
||||
const [, closeButton] = screen.getAllByRole('button', {
|
||||
name: 'Close',
|
||||
})
|
||||
|
||||
@@ -494,7 +453,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('removes a member from the project', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
fetchMock.deleteOnce('express:/project/:projectId/users/:userId', 204)
|
||||
|
||||
const members = [
|
||||
@@ -505,15 +464,10 @@ describe('<ShareProjectModal/>', function () {
|
||||
},
|
||||
]
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
...project,
|
||||
members,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
},
|
||||
},
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'tokenBased', members })
|
||||
)
|
||||
|
||||
expect(
|
||||
await screen.findAllByText('member-viewer@example.com')
|
||||
@@ -537,7 +491,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('changes member privileges to owner with confirmation', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
fetchMock.postOnce('express:/project/:projectId/transfer-ownership', 204)
|
||||
|
||||
const members = [
|
||||
@@ -548,15 +502,10 @@ describe('<ShareProjectModal/>', function () {
|
||||
},
|
||||
]
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
...project,
|
||||
members,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
},
|
||||
},
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'tokenBased', members })
|
||||
)
|
||||
|
||||
await screen.findByText('member-viewer@example.com')
|
||||
expect(screen.queryAllByText('member-viewer@example.com')).to.have.length(1)
|
||||
@@ -586,16 +535,12 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('sends invites to input email addresses', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
...project,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
},
|
||||
},
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({ publicAccessLevel: 'tokenBased' })
|
||||
)
|
||||
|
||||
const [inputElement] = await screen.findAllByLabelText('Add people')
|
||||
|
||||
@@ -680,26 +625,24 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('displays a message when the collaborator limit is reached', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
fetchMock.post(
|
||||
'/event/paywall-prompt',
|
||||
{},
|
||||
{ body: { 'paywall-type': 'project-sharing' } }
|
||||
)
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
...project,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
features: {
|
||||
collaborators: 0,
|
||||
compileGroup: 'standard',
|
||||
trackChangesVisible: true,
|
||||
},
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({
|
||||
publicAccessLevel: 'tokenBased',
|
||||
features: {
|
||||
collaborators: 0,
|
||||
compileGroup: 'standard',
|
||||
trackChangesVisible: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
await screen.findByText('Add more collaborators')
|
||||
|
||||
@@ -719,24 +662,22 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('counts reviewers towards the collaborator limit', async function () {
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
...project,
|
||||
features: {
|
||||
collaborators: 1,
|
||||
trackChangesVisible: true,
|
||||
},
|
||||
members: [
|
||||
{
|
||||
_id: 'reviewer-id',
|
||||
email: 'reviewer@example.com',
|
||||
privileges: 'review',
|
||||
},
|
||||
],
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({
|
||||
features: {
|
||||
collaborators: 1,
|
||||
trackChangesVisible: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
members: [
|
||||
{
|
||||
_id: 'reviewer-id',
|
||||
email: 'reviewer@example.com',
|
||||
privileges: 'review',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
await screen.findByText('Add more collaborators')
|
||||
|
||||
@@ -757,16 +698,14 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('handles server error responses', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
...project,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
},
|
||||
},
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps({
|
||||
publicAccessLevel: 'tokenBased',
|
||||
})
|
||||
)
|
||||
|
||||
// loading contacts
|
||||
await waitFor(() => {
|
||||
@@ -818,21 +757,25 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('handles switching between access levels', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
|
||||
fetchMock.post('express:/project/:projectId/settings/admin', 204)
|
||||
|
||||
let setPublicAccessLevel = function () {}
|
||||
|
||||
function WrappedModal() {
|
||||
setPublicAccessLevel = useScopeValue('project.publicAccesLevel')[1]
|
||||
const { updateProject } = useProjectContext()
|
||||
setPublicAccessLevel = publicAccessLevel => {
|
||||
updateProject({ publicAccessLevel })
|
||||
}
|
||||
return <ShareProjectModal {...modalProps} />
|
||||
}
|
||||
|
||||
renderWithEditorContext(<WrappedModal />, {
|
||||
scope: {
|
||||
project: { ...project, publicAccesLevel: 'private' },
|
||||
},
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<WrappedModal />,
|
||||
createContextProps({
|
||||
publicAccessLevel: 'private',
|
||||
})
|
||||
)
|
||||
|
||||
await screen.findByText('Link sharing is off')
|
||||
|
||||
@@ -848,8 +791,8 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
// NOTE: the project data is usually updated via the websocket connection
|
||||
// but we can't do that so we're doing it via the scope value store (this
|
||||
// will be via the project context when this value has been migrated)
|
||||
// but we can't do that so we're doing it via the project context, which is
|
||||
// exposed in a hacky way here
|
||||
setPublicAccessLevel('tokenBased')
|
||||
|
||||
await screen.findByText('Link sharing is on')
|
||||
@@ -870,9 +813,10 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('avoids selecting unmatched contact', async function () {
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: { project },
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps()
|
||||
)
|
||||
|
||||
const [inputElement] = await screen.findAllByLabelText('Add people')
|
||||
|
||||
@@ -924,9 +868,10 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('selects contact by typing the entire email and blurring the input', async function () {
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: { project },
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps()
|
||||
)
|
||||
|
||||
const [inputElement] = await screen.findAllByLabelText('Add people')
|
||||
|
||||
@@ -959,9 +904,10 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('selects contact by typing a partial email and selecting the suggestion', async function () {
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: { project },
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps()
|
||||
)
|
||||
|
||||
const [inputElement] = await screen.findAllByLabelText('Add people')
|
||||
|
||||
@@ -993,9 +939,10 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('allows an email address to be selected, removed, then re-added', async function () {
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: { project },
|
||||
})
|
||||
renderWithEditorContext(
|
||||
<ShareProjectModal {...modalProps} />,
|
||||
createContextProps()
|
||||
)
|
||||
|
||||
const [inputElement] = await screen.findAllByLabelText('Add people')
|
||||
|
||||
|
||||
-3
@@ -64,7 +64,6 @@ describe('autocomplete', { scrollBehavior: false }, function () {
|
||||
]
|
||||
|
||||
const scope = mockScope()
|
||||
scope.project.rootFolder = rootFolder
|
||||
|
||||
cy.mount(
|
||||
<TestContainer>
|
||||
@@ -446,7 +445,6 @@ describe('autocomplete', { scrollBehavior: false }, function () {
|
||||
]
|
||||
|
||||
const scope = mockScope()
|
||||
scope.project.rootFolder = rootFolder
|
||||
|
||||
cy.mount(
|
||||
<TestContainer>
|
||||
@@ -910,7 +908,6 @@ describe('autocomplete', { scrollBehavior: false }, function () {
|
||||
]
|
||||
|
||||
const scope = mockScope()
|
||||
scope.project.rootFolder = rootFolder
|
||||
|
||||
cy.mount(
|
||||
<TestContainer>
|
||||
|
||||
+9
@@ -2,12 +2,15 @@ import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/
|
||||
import {
|
||||
EditorProviders,
|
||||
makeEditorPropertiesProvider,
|
||||
makeProjectProvider,
|
||||
USER_ID,
|
||||
} from '../../../helpers/editor-providers'
|
||||
import { mockScope, rootFolderId } from '../helpers/mock-scope'
|
||||
import { FC } from 'react'
|
||||
import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
import { TestContainer } from '../helpers/test-container'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { mockProject } from '../helpers/mock-project'
|
||||
|
||||
const clickToolbarButton = (text: string) => {
|
||||
cy.findByLabelText(text).click()
|
||||
@@ -44,6 +47,11 @@ describe('<FigureModal />', function () {
|
||||
function mount() {
|
||||
const content = ''
|
||||
const scope = mockScope(content)
|
||||
const project = mockProject({
|
||||
projectOwner: {
|
||||
_id: USER_ID,
|
||||
},
|
||||
})
|
||||
|
||||
const FileTreePathProvider: FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
@@ -69,6 +77,7 @@ describe('<FigureModal />', function () {
|
||||
scope={scope}
|
||||
providers={{
|
||||
FileTreePathProvider,
|
||||
ProjectProvider: makeProjectProvider(project),
|
||||
EditorPropertiesProvider: makeEditorPropertiesProvider({
|
||||
showVisual: true,
|
||||
showSymbolPalette: false,
|
||||
|
||||
+10
-3
@@ -1,9 +1,13 @@
|
||||
import { mockScope } from '../helpers/mock-scope'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import {
|
||||
EditorProviders,
|
||||
makeProjectProvider,
|
||||
} from '../../../helpers/editor-providers'
|
||||
import CodeMirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
|
||||
import { TestContainer } from '../helpers/test-container'
|
||||
import forEach from 'mocha-each'
|
||||
import PackageVersions from '../../../../../app/src/infrastructure/PackageVersions'
|
||||
import { mockProject } from '../helpers/mock-project'
|
||||
|
||||
const languages = [
|
||||
{ code: 'af', dic: 'af_ZA', name: 'Afrikaans' },
|
||||
@@ -125,11 +129,14 @@ forEach(Object.keys(suggestions)).describe(
|
||||
cy.interceptEvents()
|
||||
|
||||
const scope = mockScope(content)
|
||||
scope.project.spellCheckLanguage = spellCheckLanguage
|
||||
const project = mockProject({ spellCheckLanguage })
|
||||
|
||||
cy.mount(
|
||||
<TestContainer>
|
||||
<EditorProviders scope={scope}>
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
providers={{ ProjectProvider: makeProjectProvider(project) }}
|
||||
>
|
||||
<CodeMirrorEditor />
|
||||
</EditorProviders>
|
||||
</TestContainer>
|
||||
|
||||
+5
@@ -1,19 +1,24 @@
|
||||
import {
|
||||
EditorProviders,
|
||||
makeEditorPropertiesProvider,
|
||||
makeProjectProvider,
|
||||
} from '../../../helpers/editor-providers'
|
||||
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
|
||||
import { mockScope } from '../helpers/mock-scope'
|
||||
import { TestContainer } from '../helpers/test-container'
|
||||
import { mockProject } from '../helpers/mock-project'
|
||||
|
||||
const mountEditor = (content: string) => {
|
||||
const scope = mockScope(content)
|
||||
|
||||
const project = mockProject()
|
||||
|
||||
cy.mount(
|
||||
<TestContainer>
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
providers={{
|
||||
ProjectProvider: makeProjectProvider(project),
|
||||
EditorPropertiesProvider: makeEditorPropertiesProvider({
|
||||
showVisual: true,
|
||||
showSymbolPalette: false,
|
||||
|
||||
+4
@@ -1,20 +1,24 @@
|
||||
import {
|
||||
EditorProviders,
|
||||
makeEditorPropertiesProvider,
|
||||
makeProjectProvider,
|
||||
} from '../../../helpers/editor-providers'
|
||||
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
|
||||
import { mockScope } from '../helpers/mock-scope'
|
||||
import { TestContainer } from '../helpers/test-container'
|
||||
import { isMac } from '@/shared/utils/os'
|
||||
import { mockProject } from '../helpers/mock-project'
|
||||
|
||||
const mountEditor = (content: string) => {
|
||||
const scope = mockScope(content)
|
||||
const project = mockProject()
|
||||
|
||||
cy.mount(
|
||||
<TestContainer>
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
providers={{
|
||||
ProjectProvider: makeProjectProvider(project),
|
||||
EditorPropertiesProvider: makeEditorPropertiesProvider({
|
||||
showVisual: true,
|
||||
showSymbolPalette: false,
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { docId } from './mock-doc'
|
||||
import { Folder } from '../../../../../types/folder'
|
||||
import { UserId } from '../../../../../types/user'
|
||||
import { ProjectCompiler } from '../../../../../types/project-settings'
|
||||
|
||||
export const rootFolderId = '012345678901234567890123'
|
||||
export const figuresFolderId = '123456789012345678901234'
|
||||
export const figureId = '234567890123456789012345'
|
||||
export const mockProject = ({
|
||||
projectFeatures = {},
|
||||
projectOwner = undefined,
|
||||
spellCheckLanguage = 'en',
|
||||
rootFolder = null,
|
||||
}: any = {}) => {
|
||||
return {
|
||||
_id: 'test-project',
|
||||
name: 'Test Project',
|
||||
spellCheckLanguage,
|
||||
rootDocId: '_root_doc_id',
|
||||
rootFolder:
|
||||
rootFolder ||
|
||||
([
|
||||
{
|
||||
_id: rootFolderId,
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{
|
||||
_id: docId,
|
||||
name: 'test.tex',
|
||||
},
|
||||
],
|
||||
folders: [
|
||||
{
|
||||
_id: figuresFolderId,
|
||||
name: 'figures',
|
||||
docs: [
|
||||
{
|
||||
_id: 'fake-nested-doc-id',
|
||||
name: 'foo.tex',
|
||||
},
|
||||
],
|
||||
folders: [],
|
||||
fileRefs: [
|
||||
{
|
||||
_id: figureId,
|
||||
name: 'frog.jpg',
|
||||
hash: '42',
|
||||
},
|
||||
{
|
||||
_id: 'fake-figure-id',
|
||||
name: 'unicorn.png',
|
||||
hash: '43',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
fileRefs: [],
|
||||
},
|
||||
] as Folder[]),
|
||||
features: {
|
||||
trackChanges: true,
|
||||
...projectFeatures,
|
||||
},
|
||||
compiler: 'pdflatex' as ProjectCompiler,
|
||||
imageName: 'texlive-full:2024.1',
|
||||
trackChangesState: false,
|
||||
invites: [],
|
||||
members: [],
|
||||
owner: projectOwner || {
|
||||
_id: '124abd' as UserId,
|
||||
email: 'owner@example.com',
|
||||
first_name: 'Test',
|
||||
last_name: 'Owner',
|
||||
privileges: 'owner',
|
||||
signUpDate: new Date('2025-07-07').toISOString(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,10 @@
|
||||
import { docId, mockDoc } from './mock-doc'
|
||||
import { sleep } from '../../../helpers/sleep'
|
||||
import { Folder } from '../../../../../types/folder'
|
||||
|
||||
export const rootFolderId = '012345678901234567890123'
|
||||
export const figuresFolderId = '123456789012345678901234'
|
||||
export const figureId = '234567890123456789012345'
|
||||
export const mockScope = (
|
||||
content?: string,
|
||||
{
|
||||
docOptions = {},
|
||||
projectFeatures = {},
|
||||
permissions = {},
|
||||
projectOwner = undefined,
|
||||
}: any = {}
|
||||
{ docOptions = {}, permissions = {} }: any = {}
|
||||
) => {
|
||||
return {
|
||||
editor: {
|
||||
@@ -21,55 +13,8 @@ export const mockScope = (
|
||||
currentDocumentId: docId,
|
||||
wantTrackChanges: false,
|
||||
},
|
||||
project: {
|
||||
_id: 'test-project',
|
||||
name: 'Test Project',
|
||||
spellCheckLanguage: 'en',
|
||||
rootFolder: [
|
||||
{
|
||||
_id: rootFolderId,
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{
|
||||
_id: docId,
|
||||
name: 'test.tex',
|
||||
},
|
||||
],
|
||||
folders: [
|
||||
{
|
||||
_id: figuresFolderId,
|
||||
name: 'figures',
|
||||
docs: [
|
||||
{
|
||||
_id: 'fake-nested-doc-id',
|
||||
name: 'foo.tex',
|
||||
},
|
||||
],
|
||||
folders: [],
|
||||
fileRefs: [
|
||||
{
|
||||
_id: figureId,
|
||||
name: 'frog.jpg',
|
||||
hash: '42',
|
||||
},
|
||||
{
|
||||
_id: 'fake-figure-id',
|
||||
name: 'unicorn.png',
|
||||
hash: '43',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
fileRefs: [],
|
||||
},
|
||||
] as Folder[],
|
||||
features: {
|
||||
trackChanges: true,
|
||||
...projectFeatures,
|
||||
},
|
||||
trackChangesState: {},
|
||||
members: [],
|
||||
owner: projectOwner,
|
||||
pdf: {
|
||||
logEntryAnnotations: {},
|
||||
},
|
||||
permissions: {
|
||||
comment: true,
|
||||
|
||||
@@ -23,10 +23,12 @@ import {
|
||||
EditorOpenDocContext,
|
||||
type EditorOpenDocContextState,
|
||||
} from '@/features/ide-react/context/editor-open-doc-context'
|
||||
import { ProjectContext } from '@/shared/context/project-context'
|
||||
import { ReactContextRoot } from '@/features/ide-react/context/react-context-root'
|
||||
import useEventListener from '@/shared/hooks/use-event-listener'
|
||||
import useDetachLayout from '@/shared/hooks/use-detach-layout'
|
||||
import useExposedState from '@/shared/hooks/use-exposed-state'
|
||||
import { ProjectSnapshot } from '@/infrastructure/project-snapshot'
|
||||
import {
|
||||
EditorPropertiesContext,
|
||||
EditorPropertiesContextValue,
|
||||
@@ -42,6 +44,12 @@ import type { PermissionsLevel } from '@/features/ide-react/types/permissions'
|
||||
import type { Folder } from '../../../types/folder'
|
||||
import type { SocketDebuggingInfo } from '@/features/ide-react/connection/types/connection-state'
|
||||
import type { DocumentContainer } from '@/features/ide-react/editor/document-container'
|
||||
import {
|
||||
ProjectMetadata,
|
||||
ProjectUpdate,
|
||||
} from '@/shared/context/types/project-metadata'
|
||||
import { UserId } from '../../../types/user'
|
||||
import { ProjectCompiler } from '../../../types/project-settings'
|
||||
|
||||
// these constants can be imported in tests instead of
|
||||
// using magic strings
|
||||
@@ -68,10 +76,11 @@ const defaultUserSettings = {
|
||||
export type EditorProvidersProps = {
|
||||
user?: { id: string; email: string }
|
||||
projectId?: string
|
||||
projectOwner?: { _id: string; email: string }
|
||||
projectName?: string
|
||||
projectOwner?: ProjectMetadata['owner']
|
||||
rootDocId?: string
|
||||
imageName?: string
|
||||
compiler?: string
|
||||
compiler?: ProjectCompiler
|
||||
socket?: Socket
|
||||
isRestrictedTokenMember?: boolean
|
||||
scope?: Record<string, any>
|
||||
@@ -85,6 +94,46 @@ export type EditorProvidersProps = {
|
||||
providers?: Record<string, React.FC<React.PropsWithChildren<any>>>
|
||||
}
|
||||
|
||||
export const projectDefaults = {
|
||||
_id: PROJECT_ID,
|
||||
name: PROJECT_NAME,
|
||||
owner: {
|
||||
_id: '124abd' as UserId,
|
||||
email: 'owner@example.com',
|
||||
first_name: 'Test',
|
||||
last_name: 'Owner',
|
||||
privileges: 'owner',
|
||||
signUpDate: new Date('2025-07-07').toISOString(),
|
||||
},
|
||||
features: {
|
||||
referencesSearch: true,
|
||||
gitBridge: false,
|
||||
},
|
||||
rootDocId: '_root_doc_id',
|
||||
rootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{
|
||||
_id: '_root_doc_id',
|
||||
name: 'main.tex',
|
||||
},
|
||||
],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
],
|
||||
imageName: 'texlive-full:2024.1',
|
||||
compiler: 'pdflatex' as ProjectCompiler,
|
||||
members: [],
|
||||
invites: [],
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@/shared/context/layout-context').LayoutContextValue} LayoutContextValue
|
||||
* @type Partial<LayoutContextValue>
|
||||
*/
|
||||
const layoutContextDefault = {
|
||||
view: 'editor',
|
||||
openFile: null,
|
||||
@@ -99,37 +148,23 @@ const layoutContextDefault = {
|
||||
|
||||
export function EditorProviders({
|
||||
user = { id: USER_ID, email: USER_EMAIL },
|
||||
projectId = PROJECT_ID,
|
||||
projectOwner = {
|
||||
_id: '124abd',
|
||||
email: 'owner@example.com',
|
||||
},
|
||||
rootDocId = '_root_doc_id',
|
||||
imageName = 'texlive-full:2024.1',
|
||||
compiler = 'pdflatex',
|
||||
projectId = projectDefaults._id,
|
||||
projectName = projectDefaults.name,
|
||||
projectOwner = projectDefaults.owner,
|
||||
rootDocId = projectDefaults.rootDocId,
|
||||
imageName = projectDefaults.imageName,
|
||||
compiler = projectDefaults.compiler,
|
||||
socket = new SocketIOMock() as any as Socket,
|
||||
isRestrictedTokenMember = false,
|
||||
scope: defaultScope = {},
|
||||
features = {
|
||||
referencesSearch: true,
|
||||
gitBridge: false,
|
||||
},
|
||||
projectFeatures = features,
|
||||
permissionsLevel = 'owner',
|
||||
children,
|
||||
rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{
|
||||
_id: '_root_doc_id',
|
||||
name: 'main.tex',
|
||||
},
|
||||
],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
],
|
||||
rootFolder = projectDefaults.rootFolder,
|
||||
/** @type {Partial<LayoutContext>} */
|
||||
layoutContext = layoutContextDefault,
|
||||
userSettings = {},
|
||||
@@ -166,21 +201,26 @@ export function EditorProviders({
|
||||
currentDocumentId: null,
|
||||
wantTrackChanges: false,
|
||||
},
|
||||
project: {
|
||||
_id: projectId,
|
||||
name: PROJECT_NAME,
|
||||
owner: projectOwner,
|
||||
features: projectFeatures,
|
||||
rootDocId,
|
||||
rootFolder,
|
||||
imageName,
|
||||
compiler,
|
||||
},
|
||||
permissionsLevel,
|
||||
},
|
||||
defaultScope
|
||||
)
|
||||
|
||||
const project = {
|
||||
_id: projectId,
|
||||
name: projectName,
|
||||
owner: projectOwner,
|
||||
features: projectFeatures,
|
||||
rootDocId,
|
||||
rootFolder,
|
||||
imageName,
|
||||
compiler,
|
||||
members: [],
|
||||
invites: [],
|
||||
trackChangesState: false,
|
||||
spellCheckLanguage: 'en',
|
||||
}
|
||||
|
||||
// Add details for useUserContext
|
||||
window.metaAttributesCache.set('ol-user', { ...user, features })
|
||||
window.metaAttributesCache.set('ol-project_id', projectId)
|
||||
@@ -199,6 +239,7 @@ export function EditorProviders({
|
||||
wantTrackChanges: scope.editor.wantTrackChanges,
|
||||
}),
|
||||
LayoutProvider: makeLayoutProvider(layoutContext),
|
||||
ProjectProvider: makeProjectProvider(project),
|
||||
...providers,
|
||||
}}
|
||||
>
|
||||
@@ -503,3 +544,35 @@ export function makeEditorPropertiesProvider(
|
||||
|
||||
return EditorPropertiesProvider
|
||||
}
|
||||
|
||||
export function makeProjectProvider(initialProject: ProjectMetadata) {
|
||||
const ProjectProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
const [project, setProject] = useState(initialProject)
|
||||
|
||||
const updateProject = useCallback((projectUpdateData: ProjectUpdate) => {
|
||||
setProject(projectData =>
|
||||
Object.assign({}, projectData, projectUpdateData)
|
||||
)
|
||||
}, [])
|
||||
|
||||
const value = {
|
||||
projectId: project._id,
|
||||
project,
|
||||
joinProject: () => {},
|
||||
updateProject,
|
||||
joinedOnce: true,
|
||||
projectSnapshot: new ProjectSnapshot(project._id),
|
||||
tags: [],
|
||||
features: project.features,
|
||||
name: project.name,
|
||||
}
|
||||
|
||||
return (
|
||||
<ProjectContext.Provider value={value}>
|
||||
{children}
|
||||
</ProjectContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
return ProjectProvider
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user