mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #6209 from overleaf/ta-file-tree-rework
File Tree Misc Code Changes GitOrigin-RevId: dce64a5378ecee5c8a2e25e02502ae631d87f36b
This commit is contained in:
parent
55829a3382
commit
392410390e
37 changed files with 509 additions and 429 deletions
|
@ -12,14 +12,9 @@ aside.editor-sidebar.full-size(
|
|||
vertical-resizable-top
|
||||
)
|
||||
file-tree-root(
|
||||
project-id="projectId"
|
||||
root-folder="rootFolder"
|
||||
root-doc-id="rootDocId"
|
||||
has-write-permissions="hasWritePermissions"
|
||||
on-select="onSelect"
|
||||
on-init="onInit"
|
||||
is-connected="isConnected"
|
||||
user-has-feature="userHasFeature"
|
||||
ref-providers="refProviders"
|
||||
reindex-references="reindexReferences"
|
||||
set-ref-provider-enabled="setRefProviderEnabled"
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import { useEditorContext } from '../../../shared/context/editor-context'
|
||||
import { useFileTreeMainContext } from '../contexts/file-tree-main'
|
||||
|
||||
import FileTreeItemMenuItems from './file-tree-item/file-tree-item-menu-items'
|
||||
|
||||
function FileTreeContextMenu() {
|
||||
const { hasWritePermissions, contextMenuCoords, setContextMenuCoords } =
|
||||
useFileTreeMainContext()
|
||||
const { permissionsLevel } = useEditorContext(editorContextPropTypes)
|
||||
const { contextMenuCoords, setContextMenuCoords } = useFileTreeMainContext()
|
||||
|
||||
if (!hasWritePermissions || !contextMenuCoords) return null
|
||||
if (permissionsLevel === 'readOnly' || !contextMenuCoords) return null
|
||||
|
||||
function close() {
|
||||
// reset context menu
|
||||
|
@ -41,6 +43,10 @@ function FileTreeContextMenu() {
|
|||
)
|
||||
}
|
||||
|
||||
const editorContextPropTypes = {
|
||||
permissionsLevel: PropTypes.oneOf(['readOnly', 'readAndWrite', 'owner']),
|
||||
}
|
||||
|
||||
// fake component required as Dropdowns require a Toggle, even tho we don't want
|
||||
// one for the context menu
|
||||
const FakeDropDownToggle = React.forwardRef((props, ref) => {
|
||||
|
|
|
@ -12,11 +12,6 @@ import { FileTreeDraggableProvider } from '../contexts/file-tree-draggable'
|
|||
// FileTreeMutable: provides entities mutation operations
|
||||
// FileTreeSelectable: handles selection and multi-selection
|
||||
function FileTreeContext({
|
||||
projectId,
|
||||
rootFolder,
|
||||
hasWritePermissions,
|
||||
rootDocId,
|
||||
userHasFeature,
|
||||
refProviders,
|
||||
reindexReferences,
|
||||
setRefProviderEnabled,
|
||||
|
@ -26,21 +21,14 @@ function FileTreeContext({
|
|||
}) {
|
||||
return (
|
||||
<FileTreeMainProvider
|
||||
projectId={projectId}
|
||||
hasWritePermissions={hasWritePermissions}
|
||||
userHasFeature={userHasFeature}
|
||||
refProviders={refProviders}
|
||||
setRefProviderEnabled={setRefProviderEnabled}
|
||||
setStartedFreeTrial={setStartedFreeTrial}
|
||||
reindexReferences={reindexReferences}
|
||||
>
|
||||
<FileTreeMutableProvider rootFolder={rootFolder}>
|
||||
<FileTreeSelectableProvider
|
||||
hasWritePermissions={hasWritePermissions}
|
||||
rootDocId={rootDocId}
|
||||
onSelect={onSelect}
|
||||
>
|
||||
<FileTreeActionableProvider hasWritePermissions={hasWritePermissions}>
|
||||
<FileTreeMutableProvider>
|
||||
<FileTreeSelectableProvider onSelect={onSelect}>
|
||||
<FileTreeActionableProvider>
|
||||
<FileTreeDraggableProvider>{children}</FileTreeDraggableProvider>
|
||||
</FileTreeActionableProvider>
|
||||
</FileTreeSelectableProvider>
|
||||
|
@ -50,15 +38,10 @@ function FileTreeContext({
|
|||
}
|
||||
|
||||
FileTreeContext.propTypes = {
|
||||
projectId: PropTypes.string.isRequired,
|
||||
rootFolder: PropTypes.array.isRequired,
|
||||
hasWritePermissions: PropTypes.bool.isRequired,
|
||||
userHasFeature: PropTypes.func.isRequired,
|
||||
reindexReferences: PropTypes.func.isRequired,
|
||||
refProviders: PropTypes.object.isRequired,
|
||||
setRefProviderEnabled: PropTypes.func.isRequired,
|
||||
setStartedFreeTrial: PropTypes.func.isRequired,
|
||||
rootDocId: PropTypes.string,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
|
|
|
@ -10,7 +10,7 @@ import { useProjectOutputFiles } from '../../../hooks/use-project-output-files'
|
|||
import { useFileTreeActionable } from '../../../contexts/file-tree-actionable'
|
||||
import { useFileTreeCreateName } from '../../../contexts/file-tree-create-name'
|
||||
import { useFileTreeCreateForm } from '../../../contexts/file-tree-create-form'
|
||||
import { useFileTreeMainContext } from '../../../contexts/file-tree-main'
|
||||
import { useProjectContext } from '../../../../../shared/context/project-context'
|
||||
import ErrorMessage from '../error-message'
|
||||
|
||||
export default function FileTreeImportFromProject() {
|
||||
|
@ -23,7 +23,6 @@ export default function FileTreeImportFromProject() {
|
|||
|
||||
const { name, setName, validName } = useFileTreeCreateName()
|
||||
const { setValid } = useFileTreeCreateForm()
|
||||
const { projectId } = useFileTreeMainContext()
|
||||
const { error, finishCreatingLinkedFile } = useFileTreeActionable()
|
||||
|
||||
const [selectedProject, setSelectedProject] = useState()
|
||||
|
@ -112,7 +111,6 @@ export default function FileTreeImportFromProject() {
|
|||
return (
|
||||
<form className="form-controls" id="create-file" onSubmit={handleSubmit}>
|
||||
<SelectProject
|
||||
projectId={projectId}
|
||||
selectedProject={selectedProject}
|
||||
setSelectedProject={setSelectedProject}
|
||||
/>
|
||||
|
@ -162,8 +160,9 @@ export default function FileTreeImportFromProject() {
|
|||
)
|
||||
}
|
||||
|
||||
function SelectProject({ projectId, selectedProject, setSelectedProject }) {
|
||||
function SelectProject({ selectedProject, setSelectedProject }) {
|
||||
const { t } = useTranslation()
|
||||
const { _id: projectId } = useProjectContext(projectContextPropTypes)
|
||||
|
||||
const { data, error, loading } = useUserProjects()
|
||||
|
||||
|
@ -219,11 +218,14 @@ function SelectProject({ projectId, selectedProject, setSelectedProject }) {
|
|||
)
|
||||
}
|
||||
SelectProject.propTypes = {
|
||||
projectId: PropTypes.string.isRequired,
|
||||
selectedProject: PropTypes.object,
|
||||
setSelectedProject: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
const projectContextPropTypes = {
|
||||
_id: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
function SelectProjectOutputFile({
|
||||
selectedProjectId,
|
||||
selectedProjectOutputFile,
|
||||
|
|
|
@ -6,7 +6,7 @@ import Uppy from '@uppy/core'
|
|||
import XHRUpload from '@uppy/xhr-upload'
|
||||
import { Dashboard, useUppy } from '@uppy/react'
|
||||
import { useFileTreeActionable } from '../../../contexts/file-tree-actionable'
|
||||
import { useFileTreeMainContext } from '../../../contexts/file-tree-main'
|
||||
import { useProjectContext } from '../../../../../shared/context/project-context'
|
||||
|
||||
import '@uppy/core/dist/style.css'
|
||||
import '@uppy/dashboard/dist/style.css'
|
||||
|
@ -15,7 +15,7 @@ import ErrorMessage from '../error-message'
|
|||
|
||||
export default function FileTreeUploadDoc() {
|
||||
const { parentFolderId, cancel, isDuplicate } = useFileTreeActionable()
|
||||
const { projectId } = useFileTreeMainContext()
|
||||
const { _id: projectId } = useProjectContext(projectContextPropTypes)
|
||||
|
||||
const [error, setError] = useState()
|
||||
|
||||
|
@ -162,6 +162,10 @@ export default function FileTreeUploadDoc() {
|
|||
)
|
||||
}
|
||||
|
||||
const projectContextPropTypes = {
|
||||
_id: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
function UploadErrorMessage({ error, maxNumberOfFiles }) {
|
||||
switch (error) {
|
||||
case 'too-many-files':
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useFileTreeMainContext } from '../../contexts/file-tree-main'
|
||||
import { useProjectContext } from '../../../../shared/context/project-context'
|
||||
|
||||
// handle "not-logged-in" errors by redirecting to the login page
|
||||
export default function RedirectToLogin() {
|
||||
const { projectId } = useFileTreeMainContext()
|
||||
const { _id: projectId } = useProjectContext(projectContextPropTypes)
|
||||
|
||||
const [secondsToRedirect, setSecondsToRedirect] = useState(10)
|
||||
|
||||
|
@ -35,3 +36,7 @@ export default function RedirectToLogin() {
|
|||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const projectContextPropTypes = {
|
||||
_id: PropTypes.string.isRequired,
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
|
|||
import classNames from 'classnames'
|
||||
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
|
||||
|
||||
import { useEditorContext } from '../../../../shared/context/editor-context'
|
||||
import { useFileTreeMainContext } from '../../contexts/file-tree-main'
|
||||
import { useDraggable } from '../../contexts/file-tree-draggable'
|
||||
|
||||
|
@ -11,12 +12,15 @@ import FileTreeItemMenu from './file-tree-item-menu'
|
|||
import { useFileTreeSelectable } from '../../contexts/file-tree-selectable'
|
||||
|
||||
function FileTreeItemInner({ id, name, isSelected, icons }) {
|
||||
const { hasWritePermissions, setContextMenuCoords } = useFileTreeMainContext()
|
||||
const { permissionsLevel } = useEditorContext(editorContextPropTypes)
|
||||
const { setContextMenuCoords } = useFileTreeMainContext()
|
||||
|
||||
const { selectedEntityIds } = useFileTreeSelectable()
|
||||
|
||||
const hasMenu =
|
||||
hasWritePermissions && isSelected && selectedEntityIds.size === 1
|
||||
permissionsLevel !== 'readOnly' &&
|
||||
isSelected &&
|
||||
selectedEntityIds.size === 1
|
||||
|
||||
const { isDragging, dragRef, setIsDraggable } = useDraggable(id)
|
||||
|
||||
|
@ -82,4 +86,8 @@ FileTreeItemInner.propTypes = {
|
|||
icons: PropTypes.node,
|
||||
}
|
||||
|
||||
const editorContextPropTypes = {
|
||||
permissionsLevel: PropTypes.oneOf(['readOnly', 'readAndWrite', 'owner']),
|
||||
}
|
||||
|
||||
export default FileTreeItemInner
|
||||
|
|
|
@ -4,19 +4,16 @@ import PropTypes from 'prop-types'
|
|||
import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
|
||||
|
||||
import { useFileTreeActionable } from '../../contexts/file-tree-actionable'
|
||||
import { useFileTreeMainContext } from '../../contexts/file-tree-main'
|
||||
|
||||
function FileTreeItemName({ name, isSelected, setIsDraggable }) {
|
||||
const { hasWritePermissions } = useFileTreeMainContext()
|
||||
|
||||
const { isRenaming, startRenaming, finishRenaming, error, cancel } =
|
||||
useFileTreeActionable()
|
||||
|
||||
const isRenamingEntity = isRenaming && isSelected && !error
|
||||
|
||||
useEffect(() => {
|
||||
setIsDraggable(hasWritePermissions && !isRenamingEntity)
|
||||
}, [setIsDraggable, hasWritePermissions, isRenamingEntity])
|
||||
setIsDraggable(!isRenamingEntity)
|
||||
}, [setIsDraggable, isRenamingEntity])
|
||||
|
||||
if (isRenamingEntity) {
|
||||
return (
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { useEffect } from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
|
||||
import withErrorBoundary from '../../../infrastructure/error-boundary'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import FileTreeContext from './file-tree-context'
|
||||
import FileTreeDraggablePreviewLayer from './file-tree-draggable-preview-layer'
|
||||
import FileTreeFolderList from './file-tree-folder-list'
|
||||
|
@ -19,11 +20,6 @@ import { useFileTreeSocketListener } from '../hooks/file-tree-socket-listener'
|
|||
import FileTreeModalCreateFile from './modals/file-tree-modal-create-file'
|
||||
|
||||
const FileTreeRoot = React.memo(function FileTreeRoot({
|
||||
projectId,
|
||||
rootFolder,
|
||||
rootDocId,
|
||||
hasWritePermissions,
|
||||
userHasFeature,
|
||||
refProviders,
|
||||
reindexReferences,
|
||||
setRefProviderEnabled,
|
||||
|
@ -32,6 +28,9 @@ const FileTreeRoot = React.memo(function FileTreeRoot({
|
|||
onInit,
|
||||
isConnected,
|
||||
}) {
|
||||
const { _id: projectId, rootFolder } = useProjectContext(
|
||||
projectContextPropTypes
|
||||
)
|
||||
const isReady = projectId && rootFolder
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -41,15 +40,10 @@ const FileTreeRoot = React.memo(function FileTreeRoot({
|
|||
|
||||
return (
|
||||
<FileTreeContext
|
||||
projectId={projectId}
|
||||
hasWritePermissions={hasWritePermissions}
|
||||
userHasFeature={userHasFeature}
|
||||
refProviders={refProviders}
|
||||
setRefProviderEnabled={setRefProviderEnabled}
|
||||
setStartedFreeTrial={setStartedFreeTrial}
|
||||
reindexReferences={reindexReferences}
|
||||
rootFolder={rootFolder}
|
||||
rootDocId={rootDocId}
|
||||
onSelect={onSelect}
|
||||
>
|
||||
{isConnected ? null : <div className="disconnected-overlay" />}
|
||||
|
@ -90,18 +84,18 @@ function FileTreeRootFolder() {
|
|||
}
|
||||
|
||||
FileTreeRoot.propTypes = {
|
||||
projectId: PropTypes.string,
|
||||
rootFolder: PropTypes.array,
|
||||
rootDocId: PropTypes.string,
|
||||
hasWritePermissions: PropTypes.bool.isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
onInit: PropTypes.func.isRequired,
|
||||
isConnected: PropTypes.bool.isRequired,
|
||||
setRefProviderEnabled: PropTypes.func.isRequired,
|
||||
userHasFeature: PropTypes.func.isRequired,
|
||||
setStartedFreeTrial: PropTypes.func.isRequired,
|
||||
reindexReferences: PropTypes.func.isRequired,
|
||||
refProviders: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
const projectContextPropTypes = {
|
||||
_id: PropTypes.string.isRequired,
|
||||
rootFolder: PropTypes.array.isRequired,
|
||||
}
|
||||
|
||||
export default withErrorBoundary(FileTreeRoot, FileTreeError)
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import TooltipButton from '../../../shared/components/tooltip-button'
|
||||
|
||||
import { useFileTreeMainContext } from '../contexts/file-tree-main'
|
||||
import { useEditorContext } from '../../../shared/context/editor-context'
|
||||
import { useFileTreeActionable } from '../contexts/file-tree-actionable'
|
||||
|
||||
function FileTreeToolbar() {
|
||||
const { hasWritePermissions } = useFileTreeMainContext()
|
||||
const { permissionsLevel } = useEditorContext(editorContextPropTypes)
|
||||
|
||||
if (!hasWritePermissions) return null
|
||||
if (permissionsLevel === 'readOnly') return null
|
||||
|
||||
return (
|
||||
<div className="toolbar toolbar-filetree">
|
||||
|
@ -19,6 +20,10 @@ function FileTreeToolbar() {
|
|||
)
|
||||
}
|
||||
|
||||
const editorContextPropTypes = {
|
||||
permissionsLevel: PropTypes.oneOf(['readOnly', 'readAndWrite', 'owner']),
|
||||
}
|
||||
|
||||
function FileTreeToolbarLeft() {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
|
|
|
@ -20,7 +20,8 @@ import { findInTree, findInTreeOrThrow } from '../util/find-in-tree'
|
|||
import { isNameUniqueInFolder } from '../util/is-name-unique-in-folder'
|
||||
import { isBlockedFilename, isCleanFilename } from '../util/safe-path'
|
||||
|
||||
import { useFileTreeMainContext } from './file-tree-main'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import { useEditorContext } from '../../../shared/context/editor-context'
|
||||
import { useFileTreeMutable } from './file-tree-mutable'
|
||||
import { useFileTreeSelectable } from './file-tree-selectable'
|
||||
|
||||
|
@ -117,15 +118,17 @@ function fileTreeActionableReducer(state, action) {
|
|||
}
|
||||
}
|
||||
|
||||
export function FileTreeActionableProvider({ hasWritePermissions, children }) {
|
||||
export function FileTreeActionableProvider({ children }) {
|
||||
const { _id: projectId } = useProjectContext(projectContextPropTypes)
|
||||
const { permissionsLevel } = useEditorContext(editorContextPropTypes)
|
||||
|
||||
const [state, dispatch] = useReducer(
|
||||
hasWritePermissions
|
||||
? fileTreeActionableReducer
|
||||
: fileTreeActionableReadOnlyReducer,
|
||||
permissionsLevel === 'readOnly'
|
||||
? fileTreeActionableReadOnlyReducer
|
||||
: fileTreeActionableReducer,
|
||||
defaultState
|
||||
)
|
||||
|
||||
const { projectId } = useFileTreeMainContext()
|
||||
const { fileTreeData, dispatchRename, dispatchMove } = useFileTreeMutable()
|
||||
const { selectedEntityIds } = useFileTreeSelectable()
|
||||
|
||||
|
@ -370,13 +373,20 @@ export function FileTreeActionableProvider({ hasWritePermissions, children }) {
|
|||
}
|
||||
|
||||
FileTreeActionableProvider.propTypes = {
|
||||
hasWritePermissions: PropTypes.bool.isRequired,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
}
|
||||
|
||||
const projectContextPropTypes = {
|
||||
_id: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
const editorContextPropTypes = {
|
||||
permissionsLevel: PropTypes.oneOf(['readOnly', 'readAndWrite', 'owner']),
|
||||
}
|
||||
|
||||
export function useFileTreeActionable() {
|
||||
const context = useContext(FileTreeActionableContext)
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
import { useFileTreeActionable } from './file-tree-actionable'
|
||||
import { useFileTreeMutable } from './file-tree-mutable'
|
||||
import { useFileTreeSelectable } from '../contexts/file-tree-selectable'
|
||||
import { useFileTreeMainContext } from './file-tree-main'
|
||||
import { useEditorContext } from '../../../shared/context/editor-context'
|
||||
|
||||
// HACK ALERT
|
||||
// DnD binds drag and drop events on window and stop propagation if the dragged
|
||||
|
@ -76,11 +76,11 @@ FileTreeDraggableProvider.propTypes = {
|
|||
export function useDraggable(draggedEntityId) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { hasWritePermissions } = useFileTreeMainContext()
|
||||
const { permissionsLevel } = useEditorContext(editorContextPropTypes)
|
||||
const { fileTreeData } = useFileTreeMutable()
|
||||
const { selectedEntityIds } = useFileTreeSelectable()
|
||||
|
||||
const [isDraggable, setIsDraggable] = useState(hasWritePermissions)
|
||||
const [isDraggable, setIsDraggable] = useState(true)
|
||||
|
||||
const item = { type: DRAGGABLE_TYPE }
|
||||
const [{ isDragging }, dragRef, preview] = useDrag({
|
||||
|
@ -98,7 +98,7 @@ export function useDraggable(draggedEntityId) {
|
|||
collect: monitor => ({
|
||||
isDragging: !!monitor.isDragging(),
|
||||
}),
|
||||
canDrag: () => isDraggable,
|
||||
canDrag: () => permissionsLevel !== 'readOnly' && isDraggable,
|
||||
})
|
||||
|
||||
// remove the automatic preview as we're using a custom preview via
|
||||
|
@ -114,6 +114,10 @@ export function useDraggable(draggedEntityId) {
|
|||
}
|
||||
}
|
||||
|
||||
const editorContextPropTypes = {
|
||||
permissionsLevel: PropTypes.oneOf(['readOnly', 'readAndWrite', 'owner']),
|
||||
}
|
||||
|
||||
export function useDroppable(droppedEntityId) {
|
||||
const { finishMoving } = useFileTreeActionable()
|
||||
|
||||
|
|
|
@ -16,9 +16,6 @@ export function useFileTreeMainContext() {
|
|||
}
|
||||
|
||||
export const FileTreeMainProvider = function ({
|
||||
projectId,
|
||||
hasWritePermissions,
|
||||
userHasFeature,
|
||||
refProviders,
|
||||
reindexReferences,
|
||||
setRefProviderEnabled,
|
||||
|
@ -30,9 +27,6 @@ export const FileTreeMainProvider = function ({
|
|||
return (
|
||||
<FileTreeMainContext.Provider
|
||||
value={{
|
||||
projectId,
|
||||
hasWritePermissions,
|
||||
userHasFeature,
|
||||
refProviders,
|
||||
reindexReferences,
|
||||
setRefProviderEnabled,
|
||||
|
@ -47,9 +41,6 @@ export const FileTreeMainProvider = function ({
|
|||
}
|
||||
|
||||
FileTreeMainProvider.propTypes = {
|
||||
projectId: PropTypes.string.isRequired,
|
||||
hasWritePermissions: PropTypes.bool.isRequired,
|
||||
userHasFeature: PropTypes.func.isRequired,
|
||||
reindexReferences: PropTypes.func.isRequired,
|
||||
refProviders: PropTypes.object.isRequired,
|
||||
setRefProviderEnabled: PropTypes.func.isRequired,
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
moveInTree,
|
||||
createEntityInTree,
|
||||
} from '../util/mutate-in-tree'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
|
||||
const FileTreeMutableContext = createContext()
|
||||
|
||||
|
@ -21,7 +22,7 @@ const ACTION_TYPES = {
|
|||
RESET: 'RESET',
|
||||
DELETE: 'DELETE',
|
||||
MOVE: 'MOVE',
|
||||
CREATE_ENTITY: 'CREATE_ENTITY',
|
||||
CREATE: 'CREATE',
|
||||
}
|
||||
|
||||
function fileTreeMutableReducer({ fileTreeData }, action) {
|
||||
|
@ -68,7 +69,7 @@ function fileTreeMutableReducer({ fileTreeData }, action) {
|
|||
}
|
||||
}
|
||||
|
||||
case ACTION_TYPES.CREATE_ENTITY: {
|
||||
case ACTION_TYPES.CREATE: {
|
||||
const newFileTreeData = createEntityInTree(
|
||||
fileTreeData,
|
||||
action.parentFolderId,
|
||||
|
@ -92,7 +93,9 @@ const initialState = rootFolder => ({
|
|||
fileCount: countFiles(rootFolder[0]),
|
||||
})
|
||||
|
||||
export const FileTreeMutableProvider = function ({ rootFolder, children }) {
|
||||
export const FileTreeMutableProvider = function ({ children }) {
|
||||
const { rootFolder } = useProjectContext(projectContextPropTypes)
|
||||
|
||||
const [{ fileTreeData, fileCount }, dispatch] = useReducer(
|
||||
fileTreeMutableReducer,
|
||||
rootFolder,
|
||||
|
@ -109,7 +112,7 @@ export const FileTreeMutableProvider = function ({ rootFolder, children }) {
|
|||
const dispatchCreateFolder = useCallback((parentFolderId, entity) => {
|
||||
entity.type = 'folder'
|
||||
dispatch({
|
||||
type: ACTION_TYPES.CREATE_ENTITY,
|
||||
type: ACTION_TYPES.CREATE,
|
||||
parentFolderId,
|
||||
entity,
|
||||
})
|
||||
|
@ -118,7 +121,7 @@ export const FileTreeMutableProvider = function ({ rootFolder, children }) {
|
|||
const dispatchCreateDoc = useCallback((parentFolderId, entity) => {
|
||||
entity.type = 'doc'
|
||||
dispatch({
|
||||
type: ACTION_TYPES.CREATE_ENTITY,
|
||||
type: ACTION_TYPES.CREATE,
|
||||
parentFolderId,
|
||||
entity,
|
||||
})
|
||||
|
@ -127,7 +130,7 @@ export const FileTreeMutableProvider = function ({ rootFolder, children }) {
|
|||
const dispatchCreateFile = useCallback((parentFolderId, entity) => {
|
||||
entity.type = 'fileRef'
|
||||
dispatch({
|
||||
type: ACTION_TYPES.CREATE_ENTITY,
|
||||
type: ACTION_TYPES.CREATE,
|
||||
parentFolderId,
|
||||
entity,
|
||||
})
|
||||
|
@ -168,13 +171,24 @@ export const FileTreeMutableProvider = function ({ rootFolder, children }) {
|
|||
}
|
||||
|
||||
FileTreeMutableProvider.propTypes = {
|
||||
rootFolder: PropTypes.array.isRequired,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
}
|
||||
|
||||
const projectContextPropTypes = {
|
||||
rootFolder: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
_id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
docs: PropTypes.array.isRequired,
|
||||
fileRefs: PropTypes.array.isRequired,
|
||||
folders: PropTypes.array.isRequired,
|
||||
})
|
||||
),
|
||||
}
|
||||
|
||||
export function useFileTreeMutable() {
|
||||
const context = useContext(FileTreeMutableContext)
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ import _ from 'lodash'
|
|||
|
||||
import { findInTree } from '../util/find-in-tree'
|
||||
import { useFileTreeMutable } from './file-tree-mutable'
|
||||
import { useFileTreeMainContext } from './file-tree-main'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import { useEditorContext } from '../../../shared/context/editor-context'
|
||||
import usePersistedState from '../../../shared/hooks/use-persisted-state'
|
||||
import usePreviousValue from '../../../shared/hooks/use-previous-value'
|
||||
|
||||
|
@ -73,13 +74,11 @@ function fileTreeSelectableReadOnlyReducer(selectedEntityIds, action) {
|
|||
}
|
||||
}
|
||||
|
||||
export function FileTreeSelectableProvider({
|
||||
hasWritePermissions,
|
||||
rootDocId,
|
||||
onSelect,
|
||||
children,
|
||||
}) {
|
||||
const { projectId } = useFileTreeMainContext()
|
||||
export function FileTreeSelectableProvider({ onSelect, children }) {
|
||||
const { _id: projectId, rootDoc_id: rootDocId } = useProjectContext(
|
||||
projectContextPropTypes
|
||||
)
|
||||
const { permissionsLevel } = useEditorContext(editorContextPropTypes)
|
||||
|
||||
const [initialSelectedEntityId] = usePersistedState(
|
||||
`doc.open_id.${projectId}`,
|
||||
|
@ -89,9 +88,9 @@ export function FileTreeSelectableProvider({
|
|||
const { fileTreeData } = useFileTreeMutable()
|
||||
|
||||
const [selectedEntityIds, dispatch] = useReducer(
|
||||
hasWritePermissions
|
||||
? fileTreeSelectableReadWriteReducer
|
||||
: fileTreeSelectableReadOnlyReducer,
|
||||
permissionsLevel === 'readOnly'
|
||||
? fileTreeSelectableReadOnlyReducer
|
||||
: fileTreeSelectableReadWriteReducer,
|
||||
null,
|
||||
() => {
|
||||
if (!initialSelectedEntityId) return new Set()
|
||||
|
@ -179,8 +178,6 @@ export function FileTreeSelectableProvider({
|
|||
}
|
||||
|
||||
FileTreeSelectableProvider.propTypes = {
|
||||
hasWritePermissions: PropTypes.bool.isRequired,
|
||||
rootDocId: PropTypes.string,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
|
@ -188,6 +185,14 @@ FileTreeSelectableProvider.propTypes = {
|
|||
]).isRequired,
|
||||
}
|
||||
|
||||
const projectContextPropTypes = {
|
||||
_id: PropTypes.string.isRequired,
|
||||
rootDoc_id: PropTypes.string,
|
||||
}
|
||||
|
||||
const editorContextPropTypes = {
|
||||
permissionsLevel: PropTypes.oneOf(['readOnly', 'readAndWrite', 'owner']),
|
||||
}
|
||||
export function useSelectableEntity(id) {
|
||||
const { selectedEntityIds, selectOrMultiSelectEntity } = useContext(
|
||||
FileTreeSelectableContext
|
||||
|
|
|
@ -13,22 +13,12 @@ App.controller(
|
|||
ide
|
||||
// eventTracking
|
||||
) {
|
||||
$scope.projectId = ide.project_id
|
||||
$scope.rootFolder = null
|
||||
$scope.rootDocId = null
|
||||
$scope.hasWritePermissions = false
|
||||
$scope.isConnected = true
|
||||
|
||||
$scope.$on('project:joined', () => {
|
||||
$scope.rootFolder = $scope.project.rootFolder
|
||||
$scope.rootDocId = $scope.project.rootDoc_id
|
||||
$scope.$emit('file-tree:initialized')
|
||||
})
|
||||
|
||||
$scope.$watch('permissions.write', hasWritePermissions => {
|
||||
$scope.hasWritePermissions = hasWritePermissions
|
||||
})
|
||||
|
||||
$scope.$watch('editor.open_doc_id', openDocId => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('editor.openDoc', { detail: openDocId })
|
||||
|
@ -85,12 +75,6 @@ App.controller(
|
|||
}
|
||||
}
|
||||
|
||||
$scope.userHasFeature = feature => ide.$scope.user.features[feature]
|
||||
|
||||
$scope.$watch('permissions.write', hasWritePermissions => {
|
||||
$scope.hasWritePermissions = hasWritePermissions
|
||||
})
|
||||
|
||||
$scope.refProviders = ide.$scope.user.refProviders || {}
|
||||
|
||||
ide.$scope.$watch(
|
||||
|
|
|
@ -4,6 +4,14 @@ import useScopeValue from '../hooks/use-scope-value'
|
|||
|
||||
const ProjectContext = createContext()
|
||||
|
||||
const fileTreeDataPropType = PropTypes.shape({
|
||||
_id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
docs: PropTypes.array.isRequired,
|
||||
fileRefs: PropTypes.array.isRequired,
|
||||
folders: PropTypes.array.isRequired,
|
||||
})
|
||||
|
||||
ProjectContext.Provider.propTypes = {
|
||||
value: PropTypes.shape({
|
||||
_id: PropTypes.string.isRequired,
|
||||
|
@ -23,6 +31,9 @@ ProjectContext.Provider.propTypes = {
|
|||
collaborators: PropTypes.number,
|
||||
compileGroup: PropTypes.oneOf(['alpha', 'standard', 'priority']),
|
||||
trackChangesVisible: PropTypes.bool,
|
||||
references: PropTypes.bool,
|
||||
mendeley: PropTypes.bool,
|
||||
zotero: PropTypes.bool,
|
||||
}),
|
||||
publicAccesLevel: PropTypes.string,
|
||||
tokens: PropTypes.shape({
|
||||
|
@ -33,6 +44,7 @@ ProjectContext.Provider.propTypes = {
|
|||
_id: PropTypes.string.isRequired,
|
||||
email: PropTypes.string.isRequired,
|
||||
}),
|
||||
rootFolder: PropTypes.arrayOf(fileTreeDataPropType),
|
||||
}),
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import MockedSocket from 'socket.io-mock'
|
||||
|
||||
import { ContextRoot } from '../js/shared/context/root-context'
|
||||
import { withContextRoot } from './utils/with-context-root'
|
||||
import { rootFolderBase } from './fixtures/file-tree-base'
|
||||
import { rootFolderLimit } from './fixtures/file-tree-limit'
|
||||
import FileTreeRoot from '../js/features/file-tree/components/file-tree-root'
|
||||
|
@ -12,6 +12,12 @@ const MOCK_DELAY = 2000
|
|||
window._ide = {
|
||||
socket: new MockedSocket(),
|
||||
}
|
||||
const DEFAULT_PROJECT = {
|
||||
_id: '123abc',
|
||||
name: 'Some Project',
|
||||
rootDocId: '5e74f1a7ce17ae0041dfd056',
|
||||
rootFolder: rootFolderBase,
|
||||
}
|
||||
|
||||
function defaultSetupMocks(fetchMock) {
|
||||
fetchMock
|
||||
|
@ -80,13 +86,25 @@ function defaultSetupMocks(fetchMock) {
|
|||
export const FullTree = args => {
|
||||
useFetchMock(defaultSetupMocks)
|
||||
|
||||
return <FileTreeRoot {...args} />
|
||||
return withContextRoot(<FileTreeRoot {...args} />, {
|
||||
project: DEFAULT_PROJECT,
|
||||
permissionsLevel: 'owner',
|
||||
})
|
||||
}
|
||||
|
||||
export const ReadOnly = args => <FileTreeRoot {...args} />
|
||||
ReadOnly.args = { hasWritePermissions: false }
|
||||
export const ReadOnly = args => {
|
||||
return withContextRoot(<FileTreeRoot {...args} />, {
|
||||
project: DEFAULT_PROJECT,
|
||||
permissionsLevel: 'readOnly',
|
||||
})
|
||||
}
|
||||
|
||||
export const Disconnected = args => <FileTreeRoot {...args} />
|
||||
export const Disconnected = args => {
|
||||
return withContextRoot(<FileTreeRoot {...args} />, {
|
||||
project: DEFAULT_PROJECT,
|
||||
permissionsLevel: 'owner',
|
||||
})
|
||||
}
|
||||
Disconnected.args = { isConnected: false }
|
||||
|
||||
export const NetworkErrors = args => {
|
||||
|
@ -106,24 +124,31 @@ export const NetworkErrors = args => {
|
|||
})
|
||||
})
|
||||
|
||||
return <FileTreeRoot {...args} />
|
||||
return withContextRoot(<FileTreeRoot {...args} />, {
|
||||
project: DEFAULT_PROJECT,
|
||||
permissionsLevel: 'owner',
|
||||
})
|
||||
}
|
||||
|
||||
export const FallbackError = args => <FileTreeError {...args} />
|
||||
export const FallbackError = args => {
|
||||
return withContextRoot(<FileTreeError {...args} />, {
|
||||
project: DEFAULT_PROJECT,
|
||||
})
|
||||
}
|
||||
|
||||
export const FilesLimit = args => {
|
||||
useFetchMock(defaultSetupMocks)
|
||||
|
||||
return <FileTreeRoot {...args} />
|
||||
return withContextRoot(<FileTreeRoot {...args} />, {
|
||||
project: { ...DEFAULT_PROJECT, rootFolder: rootFolderLimit },
|
||||
permissionsLevel: 'owner',
|
||||
})
|
||||
}
|
||||
FilesLimit.args = { rootFolder: rootFolderLimit }
|
||||
|
||||
export default {
|
||||
title: 'File Tree',
|
||||
component: FileTreeRoot,
|
||||
args: {
|
||||
rootFolder: rootFolderBase,
|
||||
hasWritePermissions: true,
|
||||
setStartedFreeTrial: () => {
|
||||
console.log('started free trial')
|
||||
},
|
||||
|
@ -131,12 +156,9 @@ export default {
|
|||
reindexReferences: () => {
|
||||
console.log('reindex references')
|
||||
},
|
||||
userHasFeature: () => true,
|
||||
setRefProviderEnabled: provider => {
|
||||
console.log(`ref provider ${provider} enabled`)
|
||||
},
|
||||
projectId: '123abc',
|
||||
rootDocId: '5e74f1a7ce17ae0041dfd056',
|
||||
isConnected: true,
|
||||
},
|
||||
argTypes: {
|
||||
|
@ -149,9 +171,7 @@ export default {
|
|||
<style>{'html, body, .file-tree { height: 100%; width: 100%; }'}</style>
|
||||
<div className="editor-sidebar full-size">
|
||||
<div className="file-tree">
|
||||
<ContextRoot ide={window._ide} settings={{}}>
|
||||
<Story />
|
||||
</ContextRoot>
|
||||
<Story />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -13,6 +13,15 @@ export function setupContext() {
|
|||
user: window.user,
|
||||
project: {
|
||||
features: {},
|
||||
rootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
$watch: () => {},
|
||||
$applyAsync: () => {},
|
||||
|
@ -25,7 +34,6 @@ export function setupContext() {
|
|||
pdfViewer: 'js',
|
||||
},
|
||||
toggleHistory: () => {},
|
||||
rootFolder: { type: 'folder', children: [] },
|
||||
}
|
||||
}
|
||||
window._ide = {
|
||||
|
|
|
@ -1,15 +1,29 @@
|
|||
import { useEffect } from 'react'
|
||||
import { withContextRoot } from './../../utils/with-context-root'
|
||||
import FileTreeContext from '../../../js/features/file-tree/components/file-tree-context'
|
||||
import FileTreeCreateNameProvider from '../../../js/features/file-tree/contexts/file-tree-create-name'
|
||||
import FileTreeCreateFormProvider from '../../../js/features/file-tree/contexts/file-tree-create-form'
|
||||
import { useFileTreeActionable } from '../../../js/features/file-tree/contexts/file-tree-actionable'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const defaultContextProps = {
|
||||
projectId: 'project-1',
|
||||
hasWritePermissions: true,
|
||||
userHasFeature: () => true,
|
||||
refProviders: {},
|
||||
export const DEFAULT_PROJECT = {
|
||||
_id: '123abc',
|
||||
name: 'Some Project',
|
||||
rootDocId: '5e74f1a7ce17ae0041dfd056',
|
||||
rootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
],
|
||||
features: { mendeley: true, zotero: true },
|
||||
}
|
||||
|
||||
const defaultFileTreeContextProps = {
|
||||
refProviders: { mendeley: false, zotero: false },
|
||||
reindexReferences: () => {
|
||||
console.log('reindex references')
|
||||
},
|
||||
|
@ -19,17 +33,6 @@ const defaultContextProps = {
|
|||
setStartedFreeTrial: () => {
|
||||
console.log('started free trial')
|
||||
},
|
||||
rootFolder: [
|
||||
{
|
||||
docs: [
|
||||
{
|
||||
_id: 'entity-1',
|
||||
},
|
||||
],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
],
|
||||
initialSelectedEntityId: 'entity-1',
|
||||
onSelect: () => {
|
||||
console.log('selected')
|
||||
|
@ -99,11 +102,18 @@ export const mockCreateFileModalFetch = fetchMock =>
|
|||
})
|
||||
|
||||
export const createFileModalDecorator =
|
||||
(contextProps = {}, createMode = 'doc') =>
|
||||
// eslint-disable-next-line react/display-name
|
||||
(
|
||||
fileTreeContextProps = {},
|
||||
projectProps = {},
|
||||
createMode = 'doc'
|
||||
// eslint-disable-next-line react/display-name
|
||||
) =>
|
||||
Story => {
|
||||
return (
|
||||
<FileTreeContext {...defaultContextProps} {...contextProps}>
|
||||
return withContextRoot(
|
||||
<FileTreeContext
|
||||
{...defaultFileTreeContextProps}
|
||||
{...fileTreeContextProps}
|
||||
>
|
||||
<FileTreeCreateNameProvider>
|
||||
<FileTreeCreateFormProvider>
|
||||
<OpenCreateFileModal createMode={createMode}>
|
||||
|
@ -111,7 +121,11 @@ export const createFileModalDecorator =
|
|||
</OpenCreateFileModal>
|
||||
</FileTreeCreateFormProvider>
|
||||
</FileTreeCreateNameProvider>
|
||||
</FileTreeContext>
|
||||
</FileTreeContext>,
|
||||
{
|
||||
project: { ...DEFAULT_PROJECT, ...projectProps },
|
||||
permissionsLevel: 'owner',
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -11,11 +11,7 @@ export const MinimalFeatures = args => {
|
|||
|
||||
return <FileTreeModalCreateFile {...args} />
|
||||
}
|
||||
MinimalFeatures.decorators = [
|
||||
createFileModalDecorator({
|
||||
userHasFeature: () => false,
|
||||
}),
|
||||
]
|
||||
MinimalFeatures.decorators = [createFileModalDecorator()]
|
||||
|
||||
export const WithExtraFeatures = args => {
|
||||
useFetchMock(mockCreateFileModalFetch)
|
||||
|
@ -82,17 +78,22 @@ export const FileLimitReached = args => {
|
|||
return <FileTreeModalCreateFile {...args} />
|
||||
}
|
||||
FileLimitReached.decorators = [
|
||||
createFileModalDecorator({
|
||||
rootFolder: [
|
||||
{
|
||||
docs: Array.from({ length: 10 }, (_, index) => ({
|
||||
_id: `entity-${index}`,
|
||||
})),
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
createFileModalDecorator(
|
||||
{},
|
||||
{
|
||||
rootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: Array.from({ length: 10 }, (_, index) => ({
|
||||
_id: `entity-${index}`,
|
||||
})),
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
]
|
||||
|
||||
export default {
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import sinon from 'sinon'
|
||||
|
||||
export const contextProps = {
|
||||
projectId: 'test-project',
|
||||
hasWritePermissions: true,
|
||||
userHasFeature: () => true,
|
||||
refProviders: { mendeley: false, zotero: false },
|
||||
reindexReferences: () => {
|
||||
console.log('reindex references')
|
||||
},
|
||||
setRefProviderEnabled: provider => {
|
||||
console.log(`ref provider ${provider} enabled`)
|
||||
},
|
||||
setStartedFreeTrial: () => {
|
||||
console.log('started free trial')
|
||||
},
|
||||
rootFolder: [
|
||||
{
|
||||
docs: [{ _id: 'entity-1' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
],
|
||||
initialSelectedEntityId: 'entity-1',
|
||||
onSelect: sinon.stub(),
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import { expect } from 'chai'
|
||||
import { screen, render, waitFor, cleanup } from '@testing-library/react'
|
||||
import { screen, waitFor, cleanup } from '@testing-library/react'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import { contextProps } from './context-props'
|
||||
import renderWithContext from '../../helpers/render-with-context'
|
||||
|
||||
import FileTreeCreateNameInput from '../../../../../../frontend/js/features/file-tree/components/file-tree-create/file-tree-create-name-input'
|
||||
import FileTreeContext from '../../../../../../frontend/js/features/file-tree/components/file-tree-context'
|
||||
import FileTreeCreateNameProvider from '../../../../../../frontend/js/features/file-tree/contexts/file-tree-create-name'
|
||||
|
||||
describe('<FileTreeCreateNameInput/>', function () {
|
||||
|
@ -21,12 +20,10 @@ describe('<FileTreeCreateNameInput/>', function () {
|
|||
})
|
||||
|
||||
it('renders an empty input', async function () {
|
||||
render(
|
||||
<FileTreeContext {...contextProps}>
|
||||
<FileTreeCreateNameProvider>
|
||||
<FileTreeCreateNameInput />
|
||||
</FileTreeCreateNameProvider>
|
||||
</FileTreeContext>
|
||||
renderWithContext(
|
||||
<FileTreeCreateNameProvider>
|
||||
<FileTreeCreateNameInput />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
|
||||
await screen.getByLabelText('File Name')
|
||||
|
@ -34,15 +31,13 @@ describe('<FileTreeCreateNameInput/>', function () {
|
|||
})
|
||||
|
||||
it('renders a custom label and placeholder', async function () {
|
||||
render(
|
||||
<FileTreeContext {...contextProps}>
|
||||
<FileTreeCreateNameProvider>
|
||||
<FileTreeCreateNameInput
|
||||
label="File name in this project"
|
||||
placeholder="Enter a file name…"
|
||||
/>
|
||||
</FileTreeCreateNameProvider>
|
||||
</FileTreeContext>
|
||||
renderWithContext(
|
||||
<FileTreeCreateNameProvider>
|
||||
<FileTreeCreateNameInput
|
||||
label="File name in this project"
|
||||
placeholder="Enter a file name…"
|
||||
/>
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
|
||||
await screen.getByLabelText('File name in this project')
|
||||
|
@ -50,12 +45,10 @@ describe('<FileTreeCreateNameInput/>', function () {
|
|||
})
|
||||
|
||||
it('uses an initial name', async function () {
|
||||
render(
|
||||
<FileTreeContext {...contextProps}>
|
||||
<FileTreeCreateNameProvider initialName="test.tex">
|
||||
<FileTreeCreateNameInput />
|
||||
</FileTreeCreateNameProvider>
|
||||
</FileTreeContext>
|
||||
renderWithContext(
|
||||
<FileTreeCreateNameProvider initialName="test.tex">
|
||||
<FileTreeCreateNameInput />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
|
||||
const input = await screen.getByLabelText('File Name')
|
||||
|
@ -63,12 +56,10 @@ describe('<FileTreeCreateNameInput/>', function () {
|
|||
})
|
||||
|
||||
it('focuses the name', async function () {
|
||||
render(
|
||||
<FileTreeContext {...contextProps}>
|
||||
<FileTreeCreateNameProvider initialName="test.tex">
|
||||
<FileTreeCreateNameInput focusName />
|
||||
</FileTreeCreateNameProvider>
|
||||
</FileTreeContext>
|
||||
renderWithContext(
|
||||
<FileTreeCreateNameProvider initialName="test.tex">
|
||||
<FileTreeCreateNameInput focusName />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
|
||||
const input = await screen.getByLabelText('File Name')
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
import { expect } from 'chai'
|
||||
import * as sinon from 'sinon'
|
||||
import { useEffect } from 'react'
|
||||
import {
|
||||
screen,
|
||||
render,
|
||||
fireEvent,
|
||||
cleanup,
|
||||
waitFor,
|
||||
} from '@testing-library/react'
|
||||
import { screen, fireEvent, cleanup, waitFor } from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { contextProps } from './context-props'
|
||||
import renderWithContext from '../../helpers/render-with-context'
|
||||
import FileTreeModalCreateFile from '../../../../../../frontend/js/features/file-tree/components/modals/file-tree-modal-create-file'
|
||||
import FileTreeContext from '../../../../../../frontend/js/features/file-tree/components/file-tree-context'
|
||||
import { useFileTreeActionable } from '../../../../../../frontend/js/features/file-tree/contexts/file-tree-actionable'
|
||||
import { useFileTreeMutable } from '../../../../../../frontend/js/features/file-tree/contexts/file-tree-mutable'
|
||||
|
||||
|
@ -29,11 +22,7 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
})
|
||||
|
||||
it('handles invalid file names', async function () {
|
||||
render(
|
||||
<FileTreeContext {...contextProps}>
|
||||
<OpenWithMode mode="doc" />
|
||||
</FileTreeContext>
|
||||
)
|
||||
renderWithContext(<OpenWithMode mode="doc" />)
|
||||
|
||||
const submitButton = screen.getByRole('button', { name: 'Create' })
|
||||
|
||||
|
@ -65,6 +54,8 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
it('displays an error when the file limit is reached', async function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: Array.from({ length: 10 }, (_, index) => ({
|
||||
_id: `entity-${index}`,
|
||||
})),
|
||||
|
@ -73,15 +64,9 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
},
|
||||
]
|
||||
|
||||
render(
|
||||
<FileTreeContext
|
||||
{...contextProps}
|
||||
rootFolder={rootFolder}
|
||||
initialSelectedEntityId="entity-1"
|
||||
>
|
||||
<OpenWithMode mode="doc" />
|
||||
</FileTreeContext>
|
||||
)
|
||||
renderWithContext(<OpenWithMode mode="doc" />, {
|
||||
contextProps: { projectRootFolder: rootFolder },
|
||||
})
|
||||
|
||||
screen.getByRole(
|
||||
(role, element) =>
|
||||
|
@ -93,6 +78,8 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
it('displays a warning when the file limit is nearly reached', async function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: Array.from({ length: 9 }, (_, index) => ({
|
||||
_id: `entity-${index}`,
|
||||
})),
|
||||
|
@ -101,15 +88,9 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
},
|
||||
]
|
||||
|
||||
render(
|
||||
<FileTreeContext
|
||||
{...contextProps}
|
||||
rootFolder={rootFolder}
|
||||
initialSelectedEntityId="entity-1"
|
||||
>
|
||||
<OpenWithMode mode="doc" />
|
||||
</FileTreeContext>
|
||||
)
|
||||
renderWithContext(<OpenWithMode mode="doc" />, {
|
||||
contextProps: { projectRootFolder: rootFolder },
|
||||
})
|
||||
|
||||
screen.getByText(/This project is approaching the file limit \(\d+\/\d+\)/)
|
||||
})
|
||||
|
@ -117,6 +98,8 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
it('counts files in nested folders', async function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: 'entity-1' }],
|
||||
fileRefs: [],
|
||||
folders: [
|
||||
|
@ -143,15 +126,9 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
},
|
||||
]
|
||||
|
||||
render(
|
||||
<FileTreeContext
|
||||
{...contextProps}
|
||||
rootFolder={rootFolder}
|
||||
initialSelectedEntityId="entity-1"
|
||||
>
|
||||
<OpenWithMode mode="doc" />
|
||||
</FileTreeContext>
|
||||
)
|
||||
renderWithContext(<OpenWithMode mode="doc" />, {
|
||||
contextProps: { projectRootFolder: rootFolder },
|
||||
})
|
||||
|
||||
screen.getByText(/This project is approaching the file limit \(\d+\/\d+\)/)
|
||||
})
|
||||
|
@ -159,11 +136,7 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
it('creates a new file when the form is submitted', async function () {
|
||||
fetchMock.post('express:/project/:projectId/doc', () => 204)
|
||||
|
||||
render(
|
||||
<FileTreeContext {...contextProps}>
|
||||
<OpenWithMode mode="doc" />
|
||||
</FileTreeContext>
|
||||
)
|
||||
renderWithContext(<OpenWithMode mode="doc" />)
|
||||
|
||||
const input = screen.getByLabelText('File Name')
|
||||
await fireEvent.change(input, { target: { value: 'test.tex' } })
|
||||
|
@ -174,7 +147,10 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
|
||||
expect(
|
||||
fetchMock.called('express:/project/:projectId/doc', {
|
||||
body: { name: 'test.tex' },
|
||||
body: {
|
||||
parent_folder_id: 'root-folder-id',
|
||||
name: 'test.tex',
|
||||
},
|
||||
})
|
||||
).to.be.true
|
||||
})
|
||||
|
@ -222,11 +198,7 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
})
|
||||
.post('express:/project/:projectId/linked_file', () => 204)
|
||||
|
||||
render(
|
||||
<FileTreeContext {...contextProps}>
|
||||
<OpenWithMode mode="project" />
|
||||
</FileTreeContext>
|
||||
)
|
||||
renderWithContext(<OpenWithMode mode="project" />)
|
||||
|
||||
// initial state, no project selected
|
||||
const projectInput = screen.getByLabelText('Select a Project')
|
||||
|
@ -286,6 +258,7 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
body: {
|
||||
name: 'ball.jpg',
|
||||
provider: 'project_output_file',
|
||||
parent_folder_id: 'root-folder-id',
|
||||
data: {
|
||||
source_project_id: 'project-2',
|
||||
source_output_file_path: 'ball.jpg',
|
||||
|
@ -323,11 +296,7 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
],
|
||||
})
|
||||
|
||||
render(
|
||||
<FileTreeContext {...contextProps}>
|
||||
<OpenWithMode mode="project" />
|
||||
</FileTreeContext>
|
||||
)
|
||||
renderWithContext(<OpenWithMode mode="project" />)
|
||||
|
||||
// should not show the toggle
|
||||
expect(
|
||||
|
@ -341,11 +310,7 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
it('import from a URL when the form is submitted', async function () {
|
||||
fetchMock.post('express:/project/:projectId/linked_file', () => 204)
|
||||
|
||||
render(
|
||||
<FileTreeContext {...contextProps}>
|
||||
<OpenWithMode mode="url" />
|
||||
</FileTreeContext>
|
||||
)
|
||||
renderWithContext(<OpenWithMode mode="url" />)
|
||||
|
||||
const urlInput = screen.getByLabelText('URL to fetch the file from')
|
||||
const nameInput = screen.getByLabelText('File Name In This Project')
|
||||
|
@ -373,6 +338,7 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
body: {
|
||||
name: 'test.tex',
|
||||
provider: 'url',
|
||||
parent_folder_id: 'root-folder-id',
|
||||
data: { url: 'https://example.com/example.tex' },
|
||||
},
|
||||
})
|
||||
|
@ -386,11 +352,7 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
requests.push(request)
|
||||
}
|
||||
|
||||
render(
|
||||
<FileTreeContext {...contextProps}>
|
||||
<OpenWithMode mode="upload" />
|
||||
</FileTreeContext>
|
||||
)
|
||||
renderWithContext(<OpenWithMode mode="upload" />)
|
||||
|
||||
// the submit button should not be present
|
||||
expect(screen.queryByRole('button', { name: 'Create' })).to.be.null
|
||||
|
@ -408,7 +370,9 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
await waitFor(() => expect(requests).to.have.length(1))
|
||||
|
||||
const [request] = requests
|
||||
expect(request.url).to.equal('/project/test-project/upload')
|
||||
expect(request.url).to.equal(
|
||||
'/project/123abc/upload?folder_id=root-folder-id'
|
||||
)
|
||||
expect(request.method).to.equal('POST')
|
||||
|
||||
xhr.restore()
|
||||
|
@ -421,11 +385,7 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
requests.push(request)
|
||||
}
|
||||
|
||||
render(
|
||||
<FileTreeContext {...contextProps}>
|
||||
<OpenWithMode mode="upload" />
|
||||
</FileTreeContext>
|
||||
)
|
||||
renderWithContext(<OpenWithMode mode="upload" />)
|
||||
|
||||
// the submit button should not be present
|
||||
expect(screen.queryByRole('button', { name: 'Create' })).to.be.null
|
||||
|
@ -443,7 +403,9 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
await waitFor(() => expect(requests).to.have.length(1))
|
||||
|
||||
const [request] = requests
|
||||
expect(request.url).to.equal('/project/test-project/upload')
|
||||
expect(request.url).to.equal(
|
||||
'/project/123abc/upload?folder_id=root-folder-id'
|
||||
)
|
||||
expect(request.method).to.equal('POST')
|
||||
|
||||
xhr.restore()
|
||||
|
@ -456,11 +418,7 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
requests.push(request)
|
||||
}
|
||||
|
||||
render(
|
||||
<FileTreeContext {...contextProps}>
|
||||
<OpenWithMode mode="upload" />
|
||||
</FileTreeContext>
|
||||
)
|
||||
renderWithContext(<OpenWithMode mode="upload" />)
|
||||
|
||||
// the submit button should not be present
|
||||
expect(screen.queryByRole('button', { name: 'Create' })).to.be.null
|
||||
|
@ -478,7 +436,9 @@ describe('<FileTreeModalCreateFile/>', function () {
|
|||
await waitFor(() => expect(requests).to.have.length(1))
|
||||
|
||||
const [request] = requests
|
||||
expect(request.url).to.equal('/project/test-project/upload')
|
||||
expect(request.url).to.equal(
|
||||
'/project/123abc/upload?folder_id=root-folder-id'
|
||||
)
|
||||
expect(request.method).to.equal('POST')
|
||||
|
||||
request.respond(
|
||||
|
|
|
@ -19,8 +19,10 @@ describe('<FileTreeDoc/>', function () {
|
|||
<FileTreeDoc name="foo.tex" id="123abc" isLinkedFile={false} />,
|
||||
{
|
||||
contextProps: {
|
||||
rootFolder: [
|
||||
projectRootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
|
@ -48,8 +50,10 @@ describe('<FileTreeDoc/>', function () {
|
|||
it('selects', function () {
|
||||
renderWithContext(<FileTreeDoc name="foo.tex" id="123abc" expanded />, {
|
||||
contextProps: {
|
||||
rootFolder: [
|
||||
projectRootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
|
@ -67,8 +71,10 @@ describe('<FileTreeDoc/>', function () {
|
|||
it('multi-selects', function () {
|
||||
renderWithContext(<FileTreeDoc name="foo.tex" id="123abc" expanded />, {
|
||||
contextProps: {
|
||||
rootFolder: [
|
||||
projectRootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
|
|
|
@ -42,9 +42,11 @@ describe('<FileTreeFolderList/>', function () {
|
|||
<FileTreeFolderList folders={[]} docs={docs} files={[]} />,
|
||||
{
|
||||
contextProps: {
|
||||
hasWritePermissions: false,
|
||||
rootFolder: [
|
||||
permissionsLevel: 'readOnly',
|
||||
projectRootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '1' }, { _id: '2' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
|
@ -78,8 +80,10 @@ describe('<FileTreeFolderList/>', function () {
|
|||
<FileTreeFolderList folders={[]} docs={docs} files={[]} />,
|
||||
{
|
||||
contextProps: {
|
||||
rootFolder: [
|
||||
projectRootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '1' }, { _id: '2' }, { _id: '3' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
|
|
|
@ -35,8 +35,10 @@ describe('<FileTreeFolder/>', function () {
|
|||
/>,
|
||||
{
|
||||
contextProps: {
|
||||
rootFolder: [
|
||||
projectRootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
|
@ -64,8 +66,10 @@ describe('<FileTreeFolder/>', function () {
|
|||
/>,
|
||||
{
|
||||
contextProps: {
|
||||
rootFolder: [
|
||||
projectRootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
|
@ -93,8 +97,10 @@ describe('<FileTreeFolder/>', function () {
|
|||
/>,
|
||||
{
|
||||
contextProps: {
|
||||
rootFolder: [
|
||||
projectRootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
|
|
|
@ -31,12 +31,19 @@ describe('<FileTreeitemInner />', function () {
|
|||
|
||||
describe('context menu', function () {
|
||||
it('does not display without write permissions', function () {
|
||||
renderWithContext(
|
||||
<FileTreeitemInner id="123abc" name="bar.tex" isSelected />,
|
||||
{ contextProps: { hasWritePermissions: false } }
|
||||
const { container } = renderWithContext(
|
||||
<>
|
||||
<FileTreeitemInner id="123abc" name="bar.tex" isSelected />
|
||||
<FileTreeContextMenu />
|
||||
</>,
|
||||
{
|
||||
contextProps: { permissionsLevel: 'readOnly' },
|
||||
}
|
||||
)
|
||||
|
||||
expect(screen.queryByRole('menu', { visible: false })).to.not.exist
|
||||
const entityElement = container.querySelector('div.entity')
|
||||
fireEvent.contextMenu(entityElement)
|
||||
expect(screen.queryByRole('menu')).to.not.exist
|
||||
})
|
||||
|
||||
it('open / close', function () {
|
||||
|
@ -79,9 +86,10 @@ describe('<FileTreeitemInner />', function () {
|
|||
{
|
||||
contextProps: {
|
||||
rootDocId: '123abc',
|
||||
rootFolder: [
|
||||
projectRootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc', name: 'bar.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
|
|
|
@ -75,7 +75,7 @@ describe('<FileTreeItemName />', function () {
|
|||
setIsDraggable={setIsDraggable}
|
||||
/>,
|
||||
{
|
||||
contextProps: { hasWritePermissions: false },
|
||||
contextProps: { permissionsLevel: 'readOnly' },
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ describe('<FileTreeRoot/>', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
|
@ -37,19 +38,21 @@ describe('<FileTreeRoot/>', function () {
|
|||
]
|
||||
const { container } = renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions={false}
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
setStartedFreeTrial={() => null}
|
||||
rootDocId="456def"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
isConnected
|
||||
/>
|
||||
/>,
|
||||
{
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
rootDocId: '456def',
|
||||
features: {},
|
||||
permissionsLevel: 'owner',
|
||||
}
|
||||
)
|
||||
|
||||
screen.queryByRole('tree')
|
||||
|
@ -66,6 +69,7 @@ describe('<FileTreeRoot/>', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
|
@ -73,19 +77,21 @@ describe('<FileTreeRoot/>', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
setStartedFreeTrial={() => null}
|
||||
rootDocId="456def"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
isConnected
|
||||
/>
|
||||
/>,
|
||||
{
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
rootDocId: '456def',
|
||||
features: {},
|
||||
permissionsLevel: 'owner',
|
||||
}
|
||||
)
|
||||
|
||||
// as a proxy to check that the invalid entity ha not been select we start
|
||||
|
@ -104,6 +110,7 @@ describe('<FileTreeRoot/>', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
|
@ -112,19 +119,21 @@ describe('<FileTreeRoot/>', function () {
|
|||
|
||||
const { container } = renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions={false}
|
||||
rootDocId="456def"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
isConnected={false}
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
setStartedFreeTrial={() => null}
|
||||
/>
|
||||
/>,
|
||||
{
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
rootDocId: '456def',
|
||||
features: {},
|
||||
permissionsLevel: 'owner',
|
||||
}
|
||||
)
|
||||
|
||||
expect(container.querySelector('.disconnected-overlay')).to.exist
|
||||
|
@ -134,6 +143,7 @@ describe('<FileTreeRoot/>', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{ _id: '456def', name: 'main.tex' },
|
||||
{ _id: '789ghi', name: 'other.tex' },
|
||||
|
@ -144,11 +154,6 @@ describe('<FileTreeRoot/>', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
hasWritePermissions={false}
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
|
@ -156,7 +161,14 @@ describe('<FileTreeRoot/>', function () {
|
|||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
isConnected
|
||||
/>
|
||||
/>,
|
||||
{
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
rootDocId: '456def',
|
||||
features: {},
|
||||
permissionsLevel: 'readOnly',
|
||||
}
|
||||
)
|
||||
sinon.assert.calledOnce(onSelect)
|
||||
sinon.assert.calledWithMatch(onSelect, [
|
||||
|
@ -187,6 +199,7 @@ describe('<FileTreeRoot/>', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{ _id: '456def', name: 'main.tex' },
|
||||
{ _id: '789ghi', name: 'other.tex' },
|
||||
|
@ -197,11 +210,6 @@ describe('<FileTreeRoot/>', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
hasWritePermissions={false}
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
|
@ -209,7 +217,14 @@ describe('<FileTreeRoot/>', function () {
|
|||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
isConnected
|
||||
/>
|
||||
/>,
|
||||
{
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
rootDocId: '456def',
|
||||
features: {},
|
||||
permissionsLevel: 'owner',
|
||||
}
|
||||
)
|
||||
|
||||
screen.getByRole('treeitem', { name: 'main.tex', selected: true })
|
||||
|
@ -231,6 +246,7 @@ describe('<FileTreeRoot/>', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{ _id: '456def', name: 'main.tex' },
|
||||
{ _id: '789ghi', name: 'other.tex' },
|
||||
|
@ -241,11 +257,6 @@ describe('<FileTreeRoot/>', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
hasWritePermissions
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
|
@ -253,7 +264,14 @@ describe('<FileTreeRoot/>', function () {
|
|||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
isConnected
|
||||
/>
|
||||
/>,
|
||||
{
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
rootDocId: '456def',
|
||||
features: {},
|
||||
permissionsLevel: 'owner',
|
||||
}
|
||||
)
|
||||
|
||||
const main = screen.getByRole('treeitem', {
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('<FileTreeToolbar/>', function () {
|
|||
|
||||
it('read-only', function () {
|
||||
renderWithContext(<FileTreeToolbar />, {
|
||||
contextProps: { hasWritePermissions: false },
|
||||
contextProps: { permissionsLevel: 'readOnly' },
|
||||
})
|
||||
|
||||
expect(screen.queryByRole('button')).to.not.exist
|
||||
|
@ -31,9 +31,10 @@ describe('<FileTreeToolbar/>', function () {
|
|||
renderWithContext(<FileTreeToolbar />, {
|
||||
contextProps: {
|
||||
rootDocId: '456def',
|
||||
rootFolder: [
|
||||
projectRootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
|
|
|
@ -22,6 +22,7 @@ describe('FileTree Context Menu Flow', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
|
@ -29,19 +30,19 @@ describe('FileTree Context Menu Flow', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
setStartedFreeTrial={() => null}
|
||||
rootDocId="456def"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
isConnected
|
||||
/>
|
||||
/>,
|
||||
{
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
rootDocId: '456def',
|
||||
}
|
||||
)
|
||||
const treeitem = screen.getByRole('button', { name: 'main.tex' })
|
||||
|
||||
|
@ -56,6 +57,7 @@ describe('FileTree Context Menu Flow', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
|
@ -63,19 +65,20 @@ describe('FileTree Context Menu Flow', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions={false}
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
setStartedFreeTrial={() => null}
|
||||
rootDocId="456def"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
isConnected
|
||||
/>
|
||||
/>,
|
||||
{
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
rootDocId: '456def',
|
||||
permissionsLevel: 'readOnly',
|
||||
}
|
||||
)
|
||||
const treeitem = screen.getByRole('button', { name: 'main.tex' })
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ describe('FileTree Create Folder Flow', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
|
@ -37,10 +38,6 @@ describe('FileTree Create Folder Flow', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
|
@ -49,7 +46,11 @@ describe('FileTree Create Folder Flow', function () {
|
|||
onInit={onInit}
|
||||
isConnected
|
||||
/>,
|
||||
{ socket: new MockedSocket() }
|
||||
{
|
||||
socket: new MockedSocket(),
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
}
|
||||
)
|
||||
|
||||
const newFolderName = 'Foo Bar In Root'
|
||||
|
@ -83,6 +84,7 @@ describe('FileTree Create Folder Flow', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [],
|
||||
folders: [
|
||||
{
|
||||
|
@ -98,20 +100,20 @@ describe('FileTree Create Folder Flow', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
setStartedFreeTrial={() => null}
|
||||
rootDocId="789ghi"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
isConnected
|
||||
/>,
|
||||
{ socket: new MockedSocket() }
|
||||
{
|
||||
socket: new MockedSocket(),
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
rootDocId: '789ghi',
|
||||
}
|
||||
)
|
||||
|
||||
const expandButton = screen.getByRole('button', { name: 'Expand' })
|
||||
|
@ -154,6 +156,7 @@ describe('FileTree Create Folder Flow', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [],
|
||||
folders: [
|
||||
{
|
||||
|
@ -169,20 +172,20 @@ describe('FileTree Create Folder Flow', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
setStartedFreeTrial={() => null}
|
||||
rootDocId="456def"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
isConnected
|
||||
/>,
|
||||
{ socket: new MockedSocket() }
|
||||
{
|
||||
socket: new MockedSocket(),
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
rootDocId: '456def',
|
||||
}
|
||||
)
|
||||
|
||||
const newFolderName = 'Foo Bar In thefolder'
|
||||
|
@ -222,6 +225,7 @@ describe('FileTree Create Folder Flow', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'existingFile' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
|
@ -229,20 +233,20 @@ describe('FileTree Create Folder Flow', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
setStartedFreeTrial={() => null}
|
||||
rootDocId="456def"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
isConnected
|
||||
/>,
|
||||
{ socket: new MockedSocket() }
|
||||
{
|
||||
socket: new MockedSocket(),
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
rootDocId: '456def',
|
||||
}
|
||||
)
|
||||
|
||||
let newFolderName = 'existingFile'
|
||||
|
|
|
@ -26,6 +26,7 @@ describe('FileTree Delete Entity Flow', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
|
@ -33,10 +34,6 @@ describe('FileTree Delete Entity Flow', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
|
@ -45,7 +42,11 @@ describe('FileTree Delete Entity Flow', function () {
|
|||
onInit={onInit}
|
||||
isConnected
|
||||
/>,
|
||||
{ socket: new MockedSocket() }
|
||||
{
|
||||
socket: new MockedSocket(),
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
}
|
||||
)
|
||||
|
||||
const treeitem = screen.getByRole('treeitem', { name: 'main.tex' })
|
||||
|
@ -136,6 +137,8 @@ describe('FileTree Delete Entity Flow', function () {
|
|||
beforeEach(function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [
|
||||
{
|
||||
|
@ -151,10 +154,6 @@ describe('FileTree Delete Entity Flow', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
|
@ -163,7 +162,11 @@ describe('FileTree Delete Entity Flow', function () {
|
|||
onInit={onInit}
|
||||
isConnected
|
||||
/>,
|
||||
{ socket: new MockedSocket() }
|
||||
{
|
||||
socket: new MockedSocket(),
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
}
|
||||
)
|
||||
|
||||
const expandButton = screen.queryByRole('button', { name: 'Expand' })
|
||||
|
@ -201,6 +204,7 @@ describe('FileTree Delete Entity Flow', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [{ _id: '789ghi', name: 'my.bib' }],
|
||||
|
@ -209,10 +213,6 @@ describe('FileTree Delete Entity Flow', function () {
|
|||
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
|
@ -221,7 +221,11 @@ describe('FileTree Delete Entity Flow', function () {
|
|||
onInit={onInit}
|
||||
isConnected
|
||||
/>,
|
||||
{ socket: new MockedSocket() }
|
||||
{
|
||||
socket: new MockedSocket(),
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
}
|
||||
)
|
||||
|
||||
// select two files
|
||||
|
|
|
@ -30,6 +30,7 @@ describe('FileTree Rename Entity Flow', function () {
|
|||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'a.tex' }],
|
||||
folders: [
|
||||
{
|
||||
|
@ -48,10 +49,6 @@ describe('FileTree Rename Entity Flow', function () {
|
|||
]
|
||||
renderWithEditorContext(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
userHasFeature={() => true}
|
||||
refProviders={{}}
|
||||
reindexReferences={() => null}
|
||||
setRefProviderEnabled={() => null}
|
||||
|
@ -60,7 +57,11 @@ describe('FileTree Rename Entity Flow', function () {
|
|||
onInit={onInit}
|
||||
isConnected
|
||||
/>,
|
||||
{ socket: new MockedSocket() }
|
||||
{
|
||||
socket: new MockedSocket(),
|
||||
projectRootFolder: rootFolder,
|
||||
projectId: '123abc',
|
||||
}
|
||||
)
|
||||
onSelect.reset()
|
||||
})
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { render } from '@testing-library/react'
|
||||
import FileTreeContext from '../../../../../frontend/js/features/file-tree/components/file-tree-context'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
|
||||
export default (children, options = {}) => {
|
||||
let { contextProps = {}, ...renderOptions } = options
|
||||
contextProps = {
|
||||
projectId: '123abc',
|
||||
rootFolder: [
|
||||
projectRootFolder: [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
],
|
||||
hasWritePermissions: true,
|
||||
userHasFeature: () => true,
|
||||
refProviders: {},
|
||||
reindexReferences: () => {
|
||||
console.log('reindex references')
|
||||
|
@ -27,8 +27,25 @@ export default (children, options = {}) => {
|
|||
onSelect: () => {},
|
||||
...contextProps,
|
||||
}
|
||||
return render(
|
||||
<FileTreeContext {...contextProps}>{children}</FileTreeContext>,
|
||||
const {
|
||||
refProviders,
|
||||
reindexReferences,
|
||||
setRefProviderEnabled,
|
||||
setStartedFreeTrial,
|
||||
onSelect,
|
||||
...editorContextProps
|
||||
} = contextProps
|
||||
return renderWithEditorContext(
|
||||
<FileTreeContext
|
||||
refProviders={refProviders}
|
||||
reindexReferences={reindexReferences}
|
||||
setRefProviderEnabled={setRefProviderEnabled}
|
||||
setStartedFreeTrial={setStartedFreeTrial}
|
||||
onSelect={onSelect}
|
||||
>
|
||||
{children}
|
||||
</FileTreeContext>,
|
||||
editorContextProps,
|
||||
renderOptions
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ export const PROJECT_NAME = 'project-name'
|
|||
export function EditorProviders({
|
||||
user = { id: '123abd', email: 'testuser@example.com' },
|
||||
projectId = PROJECT_ID,
|
||||
rootDocId = '_root_doc_id',
|
||||
socket = {
|
||||
on: sinon.stub(),
|
||||
removeListener: sinon.stub(),
|
||||
|
@ -30,8 +31,21 @@ export function EditorProviders({
|
|||
isRestrictedTokenMember = false,
|
||||
clsiServerId = '1234',
|
||||
scope,
|
||||
features = {
|
||||
referencesSearch: true,
|
||||
},
|
||||
permissionsLevel = 'owner',
|
||||
children,
|
||||
rootFolder,
|
||||
projectRootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
],
|
||||
ui = { view: null, pdfLayout: 'flat', chatOpen: true },
|
||||
fileTreeManager = {
|
||||
findEntityById: () => null,
|
||||
|
@ -59,10 +73,9 @@ export function EditorProviders({
|
|||
_id: '124abd',
|
||||
email: 'owner@example.com',
|
||||
},
|
||||
features: {
|
||||
referencesSearch: true,
|
||||
},
|
||||
rootDoc_id: '_root_doc_id',
|
||||
features,
|
||||
rootDoc_id: rootDocId,
|
||||
rootFolder: projectRootFolder,
|
||||
},
|
||||
rootFolder: rootFolder || {
|
||||
children: [],
|
||||
|
@ -74,6 +87,7 @@ export function EditorProviders({
|
|||
},
|
||||
$applyAsync: sinon.stub(),
|
||||
toggleHistory: sinon.stub(),
|
||||
permissionsLevel,
|
||||
...scope,
|
||||
}
|
||||
|
||||
|
@ -113,12 +127,19 @@ export function EditorProviders({
|
|||
)
|
||||
}
|
||||
|
||||
export function renderWithEditorContext(component, contextProps) {
|
||||
export function renderWithEditorContext(
|
||||
component,
|
||||
contextProps,
|
||||
renderOptions = {}
|
||||
) {
|
||||
const EditorProvidersWrapper = ({ children }) => (
|
||||
<EditorProviders {...contextProps}>{children}</EditorProviders>
|
||||
)
|
||||
|
||||
return render(component, { wrapper: EditorProvidersWrapper })
|
||||
return render(component, {
|
||||
wrapper: EditorProvidersWrapper,
|
||||
...renderOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export function renderHookWithEditorContext(hook, contextProps) {
|
||||
|
|
Loading…
Reference in a new issue