first commit
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import FileTreeCreateNameInput from '../../../../../../frontend/js/features/file-tree/components/file-tree-create/file-tree-create-name-input'
|
||||
import FileTreeCreateNameProvider from '../../../../../../frontend/js/features/file-tree/contexts/file-tree-create-name'
|
||||
|
||||
describe('<FileTreeCreateNameInput/>', function () {
|
||||
it('renders an empty input', function () {
|
||||
cy.mount(
|
||||
<FileTreeCreateNameProvider>
|
||||
<FileTreeCreateNameInput inFlight={false} />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
|
||||
cy.findByLabelText('File Name')
|
||||
cy.findByPlaceholderText('File Name')
|
||||
})
|
||||
|
||||
it('renders a custom label and placeholder', function () {
|
||||
cy.mount(
|
||||
<FileTreeCreateNameProvider>
|
||||
<FileTreeCreateNameInput
|
||||
label="File name in this project"
|
||||
placeholder="Enter a file name…"
|
||||
inFlight={false}
|
||||
/>
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
|
||||
cy.findByLabelText('File name in this project')
|
||||
cy.findByPlaceholderText('Enter a file name…')
|
||||
})
|
||||
|
||||
it('uses an initial name', function () {
|
||||
cy.mount(
|
||||
<FileTreeCreateNameProvider initialName="test.tex">
|
||||
<FileTreeCreateNameInput inFlight={false} />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
|
||||
cy.findByLabelText('File Name').should('have.value', 'test.tex')
|
||||
})
|
||||
|
||||
it('focuses the name', function () {
|
||||
cy.spy(window, 'requestAnimationFrame').as('requestAnimationFrame')
|
||||
|
||||
cy.mount(
|
||||
<FileTreeCreateNameProvider initialName="test.tex">
|
||||
<FileTreeCreateNameInput focusName inFlight={false} />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
|
||||
cy.findByLabelText('File Name').as('input')
|
||||
|
||||
cy.get('@input').should('have.value', 'test.tex')
|
||||
|
||||
cy.get('@requestAnimationFrame').should('have.been.calledOnce')
|
||||
|
||||
// https://github.com/jsdom/jsdom/issues/2995
|
||||
// "window.getSelection doesn't work with selection of <input> element"
|
||||
// const selection = window.getSelection().toString()
|
||||
// expect(selection).to.equal('test')
|
||||
|
||||
// wait for the selection to update
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(100)
|
||||
|
||||
cy.get<HTMLInputElement>('@input').then(element => {
|
||||
expect(element.get(0).selectionStart).to.equal(0)
|
||||
expect(element.get(0).selectionEnd).to.equal(4)
|
||||
})
|
||||
})
|
||||
|
||||
it('disables the input when in flight', function () {
|
||||
cy.mount(
|
||||
<FileTreeCreateNameProvider initialName="test.tex">
|
||||
<FileTreeCreateNameInput inFlight={false} />
|
||||
</FileTreeCreateNameProvider>
|
||||
).then(({ rerender }) => {
|
||||
cy.findByLabelText('File Name').should('not.be.disabled')
|
||||
rerender(
|
||||
<FileTreeCreateNameProvider initialName="test.tex">
|
||||
<FileTreeCreateNameInput inFlight />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
cy.findByLabelText('File Name').should('be.disabled')
|
||||
rerender(
|
||||
<FileTreeCreateNameProvider initialName="test.tex">
|
||||
<FileTreeCreateNameInput inFlight={false} />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
cy.findByLabelText('File Name').should('not.be.disabled')
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1,497 @@
|
||||
import { useEffect } from 'react'
|
||||
import FileTreeModalCreateFile from '../../../../../../frontend/js/features/file-tree/components/modals/file-tree-modal-create-file'
|
||||
import { useFileTreeActionable } from '../../../../../../frontend/js/features/file-tree/contexts/file-tree-actionable'
|
||||
import { useFileTreeData } from '../../../../../../frontend/js/shared/context/file-tree-data-context'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
import { FileTreeProvider } from '../../helpers/file-tree-provider'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
describe('<FileTreeModalCreateFile/>', function () {
|
||||
it('handles invalid file names', function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<OpenWithMode mode="doc" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByLabelText('File Name').as('input')
|
||||
cy.findByRole('button', { name: 'Create' }).as('submit')
|
||||
|
||||
cy.get('@input').should('have.value', 'name.tex')
|
||||
cy.get('@submit').should('not.be.disabled')
|
||||
cy.findByRole('alert').should('not.exist')
|
||||
|
||||
cy.get('@input').clear()
|
||||
cy.get('@submit').should('be.disabled')
|
||||
cy.findByRole('alert').should('contain.text', 'File name is empty')
|
||||
|
||||
cy.get('@input').type('test.tex')
|
||||
cy.get('@submit').should('not.be.disabled')
|
||||
cy.findByRole('alert').should('not.exist')
|
||||
|
||||
cy.get('@input').type('oops/i/did/it/again')
|
||||
cy.get('@submit').should('be.disabled')
|
||||
cy.findByRole('alert').should('contain.text', 'contains invalid characters')
|
||||
})
|
||||
|
||||
it('displays an error when the file limit is reached', function () {
|
||||
getMeta('ol-ExposedSettings').maxEntitiesPerProject = 10
|
||||
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: Array.from({ length: 10 }, (_, index) => ({
|
||||
_id: `entity-${index}`,
|
||||
})),
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders rootFolder={rootFolder as any}>
|
||||
<FileTreeProvider>
|
||||
<OpenWithMode mode="doc" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('alert')
|
||||
.invoke('text')
|
||||
.should('match', /This project has reached the \d+ file limit/)
|
||||
})
|
||||
|
||||
it('displays a warning when the file limit is nearly reached', function () {
|
||||
getMeta('ol-ExposedSettings').maxEntitiesPerProject = 10
|
||||
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: Array.from({ length: 9 }, (_, index) => ({
|
||||
_id: `entity-${index}`,
|
||||
})),
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders rootFolder={rootFolder as any}>
|
||||
<FileTreeProvider>
|
||||
<OpenWithMode mode="doc" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByText(/This project is approaching the file limit \(\d+\/\d+\)/)
|
||||
})
|
||||
|
||||
it('counts files in nested folders', function () {
|
||||
getMeta('ol-ExposedSettings').maxEntitiesPerProject = 10
|
||||
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: 'doc-1' }],
|
||||
fileRefs: [],
|
||||
folders: [
|
||||
{
|
||||
docs: [{ _id: 'doc-2' }],
|
||||
fileRefs: [],
|
||||
folders: [
|
||||
{
|
||||
docs: [
|
||||
{ _id: 'doc-3' },
|
||||
{ _id: 'doc-4' },
|
||||
{ _id: 'doc-5' },
|
||||
{ _id: 'doc-6' },
|
||||
{ _id: 'doc-7' },
|
||||
],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders rootFolder={rootFolder as any}>
|
||||
<FileTreeProvider>
|
||||
<OpenWithMode mode="doc" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByText(/This project is approaching the file limit \(\d+\/\d+\)/)
|
||||
})
|
||||
|
||||
it('counts folders toward the limit', function () {
|
||||
getMeta('ol-ExposedSettings').maxEntitiesPerProject = 10
|
||||
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: 'doc-1' }],
|
||||
fileRefs: [],
|
||||
folders: [
|
||||
{ docs: [], fileRefs: [], folders: [] },
|
||||
{ docs: [], fileRefs: [], folders: [] },
|
||||
{ docs: [], fileRefs: [], folders: [] },
|
||||
{ docs: [], fileRefs: [], folders: [] },
|
||||
{ docs: [], fileRefs: [], folders: [] },
|
||||
{ docs: [], fileRefs: [], folders: [] },
|
||||
{ docs: [], fileRefs: [], folders: [] },
|
||||
{ docs: [], fileRefs: [], folders: [] },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders rootFolder={rootFolder as any}>
|
||||
<FileTreeProvider>
|
||||
<OpenWithMode mode="doc" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByText(/This project is approaching the file limit \(\d+\/\d+\)/)
|
||||
})
|
||||
|
||||
it('creates a new file when the form is submitted', function () {
|
||||
cy.intercept('post', '/project/*/doc', {
|
||||
statusCode: 204,
|
||||
}).as('createDoc')
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<OpenWithMode mode="doc" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByLabelText('File Name').type('test')
|
||||
cy.findByRole('button', { name: 'Create' }).click()
|
||||
|
||||
cy.wait('@createDoc')
|
||||
|
||||
cy.get('@createDoc').its('request.body').should('deep.equal', {
|
||||
parent_folder_id: 'root-folder-id',
|
||||
name: 'test.tex',
|
||||
})
|
||||
})
|
||||
|
||||
it('imports a new file from a project', function () {
|
||||
getMeta('ol-ExposedSettings').hasLinkedProjectFileFeature = true
|
||||
getMeta('ol-ExposedSettings').hasLinkedProjectOutputFileFeature = true
|
||||
|
||||
cy.intercept('/user/projects', {
|
||||
body: {
|
||||
projects: [
|
||||
{
|
||||
_id: 'test-project',
|
||||
name: 'This Project',
|
||||
},
|
||||
{
|
||||
_id: 'project-1',
|
||||
name: 'Project One',
|
||||
},
|
||||
{
|
||||
_id: 'project-2',
|
||||
name: 'Project Two',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
cy.intercept('/project/*/entities', {
|
||||
body: {
|
||||
entities: [
|
||||
{
|
||||
path: '/foo.tex',
|
||||
},
|
||||
{
|
||||
path: '/bar.tex',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
cy.intercept('post', '/project/*/compile', {
|
||||
body: {
|
||||
status: 'success',
|
||||
outputFiles: [
|
||||
{
|
||||
build: 'test',
|
||||
path: 'baz.jpg',
|
||||
},
|
||||
{
|
||||
build: 'test',
|
||||
path: 'ball.jpg',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
cy.intercept('post', '/project/*/linked_file', {
|
||||
statusCode: 204,
|
||||
}).as('createLinkedFile')
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<OpenWithMode mode="project" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
// initial state, no project selected
|
||||
cy.findByLabelText('Select a Project').should('not.be.disabled')
|
||||
|
||||
// the submit button should be disabled
|
||||
cy.findByRole('button', { name: 'Create' }).should('be.disabled')
|
||||
|
||||
// the source file selector should be disabled
|
||||
cy.findByLabelText('Select a File').should('be.disabled')
|
||||
cy.findByLabelText('Select an Output File').should('not.exist')
|
||||
// TODO: check for options length, excluding current project
|
||||
|
||||
// select a project
|
||||
cy.findByLabelText('Select a Project').select('project-2')
|
||||
|
||||
// wait for the source file selector to be enabled
|
||||
cy.findByLabelText('Select a File').should('not.be.disabled')
|
||||
cy.findByLabelText('Select an Output File').should('not.exist')
|
||||
cy.findByRole('button', { name: 'Create' }).should('be.disabled')
|
||||
|
||||
// TODO: check for fileInput options length, excluding current project
|
||||
|
||||
// click on the button to toggle between source and output files
|
||||
cy.findByRole('button', {
|
||||
// NOTE: When changing the label, update the other tests with this label as well.
|
||||
name: 'select from output files',
|
||||
}).click()
|
||||
|
||||
// wait for the output file selector to be enabled
|
||||
cy.findByLabelText('Select an Output File').should('not.be.disabled')
|
||||
cy.findByLabelText('Select a File').should('not.exist')
|
||||
cy.findByRole('button', { name: 'Create' }).should('be.disabled')
|
||||
|
||||
// TODO: check for entityInput options length, excluding current project
|
||||
cy.findByLabelText('Select an Output File').select('ball.jpg')
|
||||
cy.findByRole('button', { name: 'Create' }).should('not.be.disabled')
|
||||
cy.findByRole('button', { name: 'Create' }).click()
|
||||
|
||||
cy.get('@createLinkedFile')
|
||||
.its('request.body')
|
||||
.should('deep.equal', {
|
||||
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',
|
||||
build_id: 'test',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the output files feature is not available', function () {
|
||||
beforeEach(function () {
|
||||
getMeta('ol-ExposedSettings').hasLinkedProjectFileFeature = true
|
||||
getMeta('ol-ExposedSettings').hasLinkedProjectOutputFileFeature = false
|
||||
})
|
||||
|
||||
it('should not show the import from output file mode', function () {
|
||||
cy.intercept('/user/projects', {
|
||||
body: {
|
||||
projects: [
|
||||
{
|
||||
_id: 'test-project',
|
||||
name: 'This Project',
|
||||
},
|
||||
{
|
||||
_id: 'project-1',
|
||||
name: 'Project One',
|
||||
},
|
||||
{
|
||||
_id: 'project-2',
|
||||
name: 'Project Two',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<OpenWithMode mode="project" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByLabelText('Select a File')
|
||||
|
||||
cy.findByRole('button', {
|
||||
name: 'select from output files',
|
||||
}).should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
it('import from a URL when the form is submitted', function () {
|
||||
cy.intercept('/project/*/linked_file', {
|
||||
statusCode: 204,
|
||||
}).as('createLinkedFile')
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<OpenWithMode mode="url" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByLabelText('URL to fetch the file from').type(
|
||||
'https://example.com/example.tex'
|
||||
)
|
||||
cy.findByLabelText('File Name In This Project').should(
|
||||
'have.value',
|
||||
'example.tex'
|
||||
)
|
||||
|
||||
// check that the name can still be edited manually
|
||||
cy.findByLabelText('File Name In This Project').clear()
|
||||
cy.findByLabelText('File Name In This Project').type('test.tex')
|
||||
cy.findByLabelText('File Name In This Project').should(
|
||||
'have.value',
|
||||
'test.tex'
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Create' }).click()
|
||||
|
||||
cy.get('@createLinkedFile')
|
||||
.its('request.body')
|
||||
.should('deep.equal', {
|
||||
name: 'test.tex',
|
||||
provider: 'url',
|
||||
parent_folder_id: 'root-folder-id',
|
||||
data: { url: 'https://example.com/example.tex' },
|
||||
})
|
||||
})
|
||||
|
||||
it('uploads a dropped file', function () {
|
||||
cy.intercept('post', '/project/*/upload?folder_id=root-folder-id', {
|
||||
statusCode: 204,
|
||||
}).as('uploadFile')
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<OpenWithMode mode="upload" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
// the submit button should not be present
|
||||
cy.findByRole('button', { name: 'Create' }).should('not.exist')
|
||||
|
||||
cy.get('input[type=file]')
|
||||
.eq(0)
|
||||
.selectFile(
|
||||
{
|
||||
contents: Cypress.Buffer.from('test'),
|
||||
fileName: 'test.tex',
|
||||
mimeType: 'text/plain',
|
||||
lastModified: Date.now(),
|
||||
},
|
||||
{
|
||||
action: 'drag-drop',
|
||||
force: true, // invisible element
|
||||
}
|
||||
)
|
||||
|
||||
cy.wait('@uploadFile')
|
||||
})
|
||||
|
||||
it('uploads a pasted file', function () {
|
||||
cy.intercept('post', '/project/*/upload?folder_id=root-folder-id', {
|
||||
statusCode: 204,
|
||||
}).as('uploadFile')
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<OpenWithMode mode="upload" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
// the submit button should not be present
|
||||
cy.findByRole('button', { name: 'Create' }).should('not.exist')
|
||||
|
||||
cy.wrap(null).then(() => {
|
||||
const clipboardData = new DataTransfer()
|
||||
clipboardData.items.add(
|
||||
new File(['test'], 'test.tex', { type: 'text/plain' })
|
||||
)
|
||||
cy.findByLabelText('Uppy Dashboard').trigger('paste', { clipboardData })
|
||||
})
|
||||
|
||||
cy.wait('@uploadFile')
|
||||
})
|
||||
|
||||
it('displays upload errors', function () {
|
||||
cy.intercept('post', '/project/*/upload?folder_id=root-folder-id', {
|
||||
statusCode: 422,
|
||||
body: { success: false, error: 'invalid_filename' },
|
||||
}).as('uploadFile')
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<OpenWithMode mode="upload" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
// the submit button should not be present
|
||||
cy.findByRole('button', { name: 'Create' }).should('not.exist')
|
||||
|
||||
cy.wrap(null).then(() => {
|
||||
const clipboardData = new DataTransfer()
|
||||
clipboardData.items.add(
|
||||
new File(['test'], 'tes!t.tex', { type: 'text/plain' })
|
||||
)
|
||||
cy.findByLabelText('Uppy Dashboard').trigger('paste', { clipboardData })
|
||||
})
|
||||
|
||||
cy.wait('@uploadFile')
|
||||
|
||||
cy.findByText(
|
||||
`Upload failed: check that the file name doesn’t contain special characters, trailing/leading whitespace or more than 150 characters`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
function OpenWithMode({ mode }: { mode: string }) {
|
||||
const { newFileCreateMode, startCreatingFile } = useFileTreeActionable()
|
||||
|
||||
const { fileCount } = useFileTreeData()
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => startCreatingFile(mode), [])
|
||||
|
||||
if (!fileCount || !newFileCreateMode) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <FileTreeModalCreateFile />
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
import FileTreeDoc from '../../../../../frontend/js/features/file-tree/components/file-tree-doc'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import { FileTreeProvider } from '../helpers/file-tree-provider'
|
||||
|
||||
describe('<FileTreeDoc/>', function () {
|
||||
it('renders unselected', function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeDoc name="foo.tex" id="123abc" />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('treeitem', { selected: false })
|
||||
cy.get('.linked-file-highlight').should('not.exist')
|
||||
})
|
||||
|
||||
it('renders selected', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders rootFolder={rootFolder as any}>
|
||||
<FileTreeProvider>
|
||||
<FileTreeDoc name="foo.tex" id="123abc" />,
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('treeitem', { selected: false }).click()
|
||||
cy.findByRole('treeitem', { selected: true })
|
||||
})
|
||||
|
||||
it('renders as linked file', function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeDoc name="foo.tex" id="123abc" isLinkedFile />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('treeitem')
|
||||
cy.get('.linked-file-highlight')
|
||||
})
|
||||
|
||||
it('multi-selects', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders rootFolder={rootFolder as any}>
|
||||
<FileTreeProvider>
|
||||
<FileTreeDoc name="foo.tex" id="123abc" />,
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('treeitem').click({ ctrlKey: true, cmdKey: true })
|
||||
cy.findByRole('treeitem', { selected: true })
|
||||
})
|
||||
})
|
@@ -0,0 +1,197 @@
|
||||
import FileTreeFolderList from '../../../../../frontend/js/features/file-tree/components/file-tree-folder-list'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import { FileTreeProvider } from '../helpers/file-tree-provider'
|
||||
|
||||
describe('<FileTreeFolderList/>', function () {
|
||||
it('renders empty', function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeFolderList folders={[]} docs={[]} files={[]} />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('tree')
|
||||
cy.findByRole('treeitem').should('not.exist')
|
||||
})
|
||||
|
||||
it('renders docs, files and folders', function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeFolderList
|
||||
folders={[
|
||||
{
|
||||
_id: '456def',
|
||||
name: 'A Folder',
|
||||
folders: [],
|
||||
docs: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]}
|
||||
docs={[{ _id: '789ghi', name: 'doc.tex' }]}
|
||||
files={[
|
||||
{
|
||||
_id: '987jkl',
|
||||
name: 'file.bib',
|
||||
hash: 'some hash',
|
||||
linkedFileData: {},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('tree')
|
||||
cy.findByRole('treeitem', { name: 'A Folder' })
|
||||
cy.findByRole('treeitem', { name: 'doc.tex' })
|
||||
cy.findByRole('treeitem', { name: 'file.bib' })
|
||||
})
|
||||
|
||||
describe('selection and multi-selection', function () {
|
||||
it('without write permissions', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '1' }, { _id: '2' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
permissionsLevel="readOnly"
|
||||
>
|
||||
<FileTreeProvider>
|
||||
<FileTreeFolderList
|
||||
folders={[]}
|
||||
docs={[
|
||||
{ _id: '1', name: '1.tex' },
|
||||
{ _id: '2', name: '2.tex' },
|
||||
]}
|
||||
files={[]}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
// click on item 1: it gets selected
|
||||
cy.findByRole('treeitem', { name: '1.tex' }).click()
|
||||
cy.findByRole('treeitem', { name: '1.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: '2.tex', selected: false })
|
||||
|
||||
// meta-click on item 2: no changes
|
||||
cy.findByRole('treeitem', { name: '2.tex' }).click({
|
||||
ctrlKey: true,
|
||||
cmdKey: true,
|
||||
})
|
||||
cy.findByRole('treeitem', { name: '1.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: '2.tex', selected: false })
|
||||
})
|
||||
|
||||
it('with write permissions', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '1' }, { _id: '2' }, { _id: '3' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders rootFolder={rootFolder as any}>
|
||||
<FileTreeProvider>
|
||||
<FileTreeFolderList
|
||||
folders={[]}
|
||||
docs={[
|
||||
{ _id: '1', name: '1.tex' },
|
||||
{ _id: '2', name: '2.tex' },
|
||||
{ _id: '3', name: '3.tex' },
|
||||
]}
|
||||
files={[]}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
// click item 1: it gets selected
|
||||
cy.findByRole('treeitem', { name: '1.tex' }).click()
|
||||
cy.findByRole('treeitem', { name: '1.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: '2.tex', selected: false })
|
||||
cy.findByRole('treeitem', { name: '3.tex', selected: false })
|
||||
|
||||
// click on item 2: it gets selected and item 1 is not selected anymore
|
||||
cy.findByRole('treeitem', { name: '2.tex' }).click()
|
||||
cy.findByRole('treeitem', { name: '1.tex', selected: false })
|
||||
cy.findByRole('treeitem', { name: '2.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: '3.tex', selected: false })
|
||||
|
||||
// meta-click on item 3: it gets selected and item 2 as well
|
||||
cy.findByRole('treeitem', { name: '3.tex' }).click({
|
||||
ctrlKey: true,
|
||||
cmdKey: true,
|
||||
})
|
||||
cy.findByRole('treeitem', { name: '1.tex', selected: false })
|
||||
cy.findByRole('treeitem', { name: '2.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: '3.tex', selected: true })
|
||||
|
||||
// meta-click on item 1: add to selection
|
||||
cy.findByRole('treeitem', { name: '1.tex' }).click({
|
||||
ctrlKey: true,
|
||||
cmdKey: true,
|
||||
})
|
||||
cy.findByRole('treeitem', { name: '1.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: '2.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: '3.tex', selected: true })
|
||||
|
||||
// meta-click on item 1: remove from selection
|
||||
cy.findByRole('treeitem', { name: '1.tex' }).click({
|
||||
ctrlKey: true,
|
||||
cmdKey: true,
|
||||
})
|
||||
cy.findByRole('treeitem', { name: '1.tex', selected: false })
|
||||
cy.findByRole('treeitem', { name: '2.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: '3.tex', selected: true })
|
||||
|
||||
// meta-click on item 3: remove from selection
|
||||
cy.findByRole('treeitem', { name: '3.tex' }).click({
|
||||
ctrlKey: true,
|
||||
cmdKey: true,
|
||||
})
|
||||
cy.findByRole('treeitem', { name: '1.tex', selected: false })
|
||||
cy.findByRole('treeitem', { name: '2.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: '3.tex', selected: false })
|
||||
|
||||
// meta-click on item 2: cannot unselect
|
||||
cy.findByRole('treeitem', { name: '2.tex' }).click({
|
||||
ctrlKey: true,
|
||||
cmdKey: true,
|
||||
})
|
||||
cy.findByRole('treeitem', { name: '1.tex', selected: false })
|
||||
cy.findByRole('treeitem', { name: '2.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: '3.tex', selected: false })
|
||||
|
||||
// meta-click on item 3: add back to selection
|
||||
cy.findByRole('treeitem', { name: '3.tex' }).click({
|
||||
ctrlKey: true,
|
||||
cmdKey: true,
|
||||
})
|
||||
cy.findByRole('treeitem', { name: '1.tex', selected: false })
|
||||
cy.findByRole('treeitem', { name: '2.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: '3.tex', selected: true })
|
||||
|
||||
// click on item 3: unselect other items
|
||||
cy.findByRole('treeitem', { name: '3.tex' }).click()
|
||||
cy.findByRole('treeitem', { name: '1.tex', selected: false })
|
||||
cy.findByRole('treeitem', { name: '2.tex', selected: false })
|
||||
cy.findByRole('treeitem', { name: '3.tex', selected: true })
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1,134 @@
|
||||
import FileTreeFolder from '../../../../../frontend/js/features/file-tree/components/file-tree-folder'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import { FileTreeProvider } from '../helpers/file-tree-provider'
|
||||
import { getContainerEl } from 'cypress/react'
|
||||
import ReactDom from 'react-dom'
|
||||
|
||||
describe('<FileTreeFolder/>', function () {
|
||||
it('renders unselected', function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeFolder
|
||||
name="foo"
|
||||
id="123abc"
|
||||
folders={[]}
|
||||
docs={[]}
|
||||
files={[]}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('treeitem', { selected: false })
|
||||
cy.findByRole('tree').should('not.exist')
|
||||
})
|
||||
|
||||
it('renders selected', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders rootFolder={rootFolder as any}>
|
||||
<FileTreeProvider>
|
||||
<FileTreeFolder
|
||||
name="foo"
|
||||
id="123abc"
|
||||
folders={[]}
|
||||
docs={[]}
|
||||
files={[]}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('treeitem', { selected: false }).click()
|
||||
cy.findByRole('treeitem', { selected: true })
|
||||
cy.findByRole('tree').should('not.exist')
|
||||
})
|
||||
|
||||
it('expands', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders rootFolder={rootFolder as any}>
|
||||
<FileTreeProvider>
|
||||
<FileTreeFolder
|
||||
name="foo"
|
||||
id="123abc"
|
||||
folders={[]}
|
||||
docs={[]}
|
||||
files={[]}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('treeitem')
|
||||
cy.findByRole('button', { name: 'Expand' }).click()
|
||||
cy.findByRole('tree')
|
||||
})
|
||||
|
||||
it('saves the expanded state for the next render', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders rootFolder={rootFolder as any}>
|
||||
<FileTreeProvider>
|
||||
<FileTreeFolder
|
||||
name="foo"
|
||||
id="123abc"
|
||||
folders={[]}
|
||||
docs={[]}
|
||||
files={[]}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('tree').should('not.exist')
|
||||
cy.findByRole('button', { name: 'Expand' }).click()
|
||||
cy.findByRole('tree')
|
||||
|
||||
cy.then(() => ReactDom.unmountComponentAtNode(getContainerEl()))
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeFolder
|
||||
name="foo"
|
||||
id="123abc"
|
||||
folders={[]}
|
||||
docs={[]}
|
||||
files={[]}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('tree')
|
||||
})
|
||||
})
|
@@ -0,0 +1,124 @@
|
||||
import FileTreeitemInner from '../../../../../../frontend/js/features/file-tree/components/file-tree-item/file-tree-item-inner'
|
||||
import FileTreeContextMenu from '../../../../../../frontend/js/features/file-tree/components/file-tree-context-menu'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
import { FileTreeProvider } from '../../helpers/file-tree-provider'
|
||||
|
||||
describe('<FileTreeitemInner />', function () {
|
||||
describe('menu', function () {
|
||||
it('does not display if file is not selected', function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeitemInner
|
||||
id="123abc"
|
||||
name="bar.tex"
|
||||
isSelected={false}
|
||||
type="doc"
|
||||
/>
|
||||
,
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('menu', { hidden: true }).should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
describe('context menu', function () {
|
||||
it('does not display without write permissions', function () {
|
||||
cy.mount(
|
||||
<EditorProviders permissionsLevel="readOnly">
|
||||
<FileTreeProvider>
|
||||
<FileTreeitemInner
|
||||
id="123abc"
|
||||
name="bar.tex"
|
||||
isSelected
|
||||
type="doc"
|
||||
/>
|
||||
<FileTreeContextMenu />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get('div.entity').trigger('contextmenu')
|
||||
cy.findByRole('menu', { hidden: true }).should('not.exist')
|
||||
})
|
||||
|
||||
it('open / close', function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeitemInner
|
||||
id="123abc"
|
||||
name="bar.tex"
|
||||
isSelected
|
||||
type="doc"
|
||||
/>
|
||||
<FileTreeContextMenu />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('menu', { hidden: true }).should('not.exist')
|
||||
|
||||
// open the context menu
|
||||
cy.get('div.entity').trigger('contextmenu')
|
||||
cy.findByRole('menu')
|
||||
|
||||
// close the context menu
|
||||
cy.get('div.entity').click()
|
||||
cy.findByRole('menu').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
describe('name', function () {
|
||||
it('renders name', function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeitemInner
|
||||
id="123abc"
|
||||
name="bar.tex"
|
||||
isSelected
|
||||
type="doc"
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'bar.tex' })
|
||||
cy.findByRole('textbox').should('not.exist')
|
||||
})
|
||||
|
||||
it('starts rename on menu item click', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc', name: 'bar.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders rootDocId="123abc" rootFolder={rootFolder as any}>
|
||||
<FileTreeProvider>
|
||||
<FileTreeitemInner
|
||||
id="123abc"
|
||||
name="bar.tex"
|
||||
isSelected
|
||||
type="doc"
|
||||
/>
|
||||
<FileTreeContextMenu />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Open bar.tex action menu' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Rename' }).click()
|
||||
cy.findByRole('button', { name: 'bar.tex' }).should('not.exist')
|
||||
cy.findByRole('textbox')
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1,108 @@
|
||||
import FileTreeItemName from '../../../../../../frontend/js/features/file-tree/components/file-tree-item/file-tree-item-name'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
import { FileTreeProvider } from '../../helpers/file-tree-provider'
|
||||
|
||||
describe('<FileTreeItemName />', function () {
|
||||
it('renders name as button', function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeItemName
|
||||
name="foo.tex"
|
||||
isSelected
|
||||
setIsDraggable={cy.stub()}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'foo.tex' })
|
||||
cy.findByRole('textbox').should('not.exist')
|
||||
})
|
||||
|
||||
it("doesn't start renaming on unselected component", function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeItemName
|
||||
name="foo.tex"
|
||||
isSelected={false}
|
||||
setIsDraggable={cy.stub()}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button').click()
|
||||
cy.findByRole('button').click()
|
||||
cy.findByRole('button').dblclick()
|
||||
cy.findByRole('textbox').should('not.exist')
|
||||
})
|
||||
|
||||
it('start renaming on double-click', function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeItemName
|
||||
name="foo.tex"
|
||||
isSelected
|
||||
setIsDraggable={cy.stub().as('setIsDraggable')}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button').click()
|
||||
cy.findByRole('button').click()
|
||||
cy.findByRole('button').dblclick()
|
||||
cy.findByRole('textbox')
|
||||
cy.findByRole('button').should('not.exist')
|
||||
cy.get('@setIsDraggable').should('have.been.calledWith', false)
|
||||
})
|
||||
|
||||
it('cannot start renaming in read-only', function () {
|
||||
cy.mount(
|
||||
<EditorProviders permissionsLevel="readOnly">
|
||||
<FileTreeProvider>
|
||||
<FileTreeItemName
|
||||
name="foo.tex"
|
||||
isSelected
|
||||
setIsDraggable={cy.stub()}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button').click()
|
||||
cy.findByRole('button').click()
|
||||
cy.findByRole('button').dblclick()
|
||||
|
||||
cy.findByRole('textbox').should('not.exist')
|
||||
})
|
||||
|
||||
describe('stop renaming', function () {
|
||||
it('on Escape', function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<FileTreeProvider>
|
||||
<FileTreeItemName
|
||||
name="foo.tex"
|
||||
isSelected
|
||||
setIsDraggable={cy.stub().as('setIsDraggable')}
|
||||
/>
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button').click()
|
||||
cy.findByRole('button').click()
|
||||
cy.findByRole('button').dblclick()
|
||||
|
||||
cy.findByRole('textbox').clear()
|
||||
cy.findByRole('textbox').type('bar.tex{esc}')
|
||||
|
||||
cy.findByRole('button', { name: 'foo.tex' })
|
||||
cy.get('@setIsDraggable').should('have.been.calledWith', true)
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1,354 @@
|
||||
import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import { SocketIOMock } from '@/ide/connection/SocketIoShim'
|
||||
|
||||
describe('<FileTreeRoot/>', function () {
|
||||
beforeEach(function () {
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-user', { id: 'user1' })
|
||||
})
|
||||
})
|
||||
|
||||
it('renders', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
features={{} as any}
|
||||
permissionsLevel="owner"
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('tree')
|
||||
cy.findByRole('treeitem')
|
||||
cy.findByRole('treeitem', { name: 'main.tex', selected: true })
|
||||
cy.get('.disconnected-overlay').should('not.exist')
|
||||
})
|
||||
|
||||
it('renders with invalid selected doc in local storage', function () {
|
||||
global.localStorage.setItem(
|
||||
'doc.open_id.123abc',
|
||||
JSON.stringify('not-a-valid-id')
|
||||
)
|
||||
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<div style={{ width: 400 }}>
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
features={{} as any}
|
||||
permissionsLevel="owner"
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
</div>
|
||||
)
|
||||
|
||||
// as a proxy to check that the invalid entity has not been select we start
|
||||
// a delete and ensure the modal is displayed (the cancel button can be
|
||||
// selected) This is needed to make sure the test fail.
|
||||
cy.findByRole('treeitem', { name: 'main.tex' }).click({
|
||||
ctrlKey: true,
|
||||
cmdKey: true,
|
||||
})
|
||||
cy.findByRole('button', { name: 'Open main.tex action menu' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Delete' }).click()
|
||||
cy.findByRole('button', { name: 'Cancel' })
|
||||
})
|
||||
|
||||
it('renders disconnected overlay', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
features={{} as any}
|
||||
permissionsLevel="owner"
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected={false}
|
||||
/>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get('.disconnected-overlay')
|
||||
})
|
||||
|
||||
it('fire onSelect', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{ _id: '456def', name: 'main.tex' },
|
||||
{ _id: '789ghi', name: 'other.tex' },
|
||||
],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
features={{} as any}
|
||||
permissionsLevel="readOnly"
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub().as('onSelect')}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get('@onSelect').should('have.been.calledOnceWith', [
|
||||
Cypress.sinon.match({
|
||||
entity: Cypress.sinon.match({ _id: '456def', name: 'main.tex' }),
|
||||
}),
|
||||
])
|
||||
cy.findByRole('tree')
|
||||
cy.findByRole('treeitem', { name: 'other.tex' }).click()
|
||||
cy.get('@onSelect').should('have.been.calledWith', [
|
||||
Cypress.sinon.match({
|
||||
entity: Cypress.sinon.match({ _id: '789ghi', name: 'other.tex' }),
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('only shows a menu button when a single item is selected', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{ _id: '456def', name: 'main.tex' },
|
||||
{ _id: '789ghi', name: 'other.tex' },
|
||||
],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
features={{} as any}
|
||||
permissionsLevel="owner"
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('treeitem', { name: 'main.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: 'other.tex', selected: false })
|
||||
|
||||
// single item selected: menu button is visible
|
||||
cy.findAllByRole('button', { name: 'Open main.tex action menu' }).should(
|
||||
'have.length',
|
||||
1
|
||||
)
|
||||
|
||||
// select the other item
|
||||
cy.findByRole('treeitem', { name: 'other.tex' }).click()
|
||||
|
||||
cy.findByRole('treeitem', { name: 'main.tex', selected: false })
|
||||
cy.findByRole('treeitem', { name: 'other.tex', selected: true })
|
||||
|
||||
// single item selected: menu button is visible
|
||||
cy.findAllByRole('button', { name: 'Open other.tex action menu' }).should(
|
||||
'have.length',
|
||||
1
|
||||
)
|
||||
|
||||
// multi-select the main item
|
||||
cy.findByRole('treeitem', { name: 'main.tex' }).click({
|
||||
ctrlKey: true,
|
||||
cmdKey: true,
|
||||
})
|
||||
|
||||
cy.findByRole('treeitem', { name: 'main.tex', selected: true })
|
||||
cy.findByRole('treeitem', { name: 'other.tex', selected: true })
|
||||
|
||||
// multiple items selected: no menu button is visible
|
||||
cy.findAllByRole('button', { name: 'Open main.tex action menu' }).should(
|
||||
'have.length',
|
||||
0
|
||||
)
|
||||
})
|
||||
|
||||
describe('when deselecting files', function () {
|
||||
let socket: SocketIOMock
|
||||
beforeEach(function () {
|
||||
socket = new SocketIOMock()
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '123abc', name: 'main.tex' }],
|
||||
folders: [
|
||||
{
|
||||
_id: '789ghi',
|
||||
name: 'thefolder',
|
||||
docs: [{ _id: '456def', name: 'sub.tex' }],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
features={{} as any}
|
||||
permissionsLevel="owner"
|
||||
socket={socket}
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
// select the sub file
|
||||
cy.findByRole('treeitem', { name: 'sub.tex' }).click()
|
||||
cy.findByRole('treeitem', { name: 'sub.tex' }).should(
|
||||
'have.attr',
|
||||
'aria-selected',
|
||||
'true'
|
||||
)
|
||||
|
||||
// click on empty area (after giving it extra height below the tree)
|
||||
cy.findByTestId('file-tree-inner')
|
||||
.invoke('attr', 'style', 'height: 400px')
|
||||
.click()
|
||||
})
|
||||
|
||||
it('removes the selected indicator', function () {
|
||||
cy.findByRole('treeitem', { selected: true }).should('not.exist')
|
||||
})
|
||||
|
||||
it('disables the "rename" and "delete" buttons', function () {
|
||||
cy.findByRole('button', { name: 'Rename' }).should('not.exist')
|
||||
cy.findByRole('button', { name: 'Delete' }).should('not.exist')
|
||||
})
|
||||
|
||||
it('creates new file in the root folder', function () {
|
||||
cy.intercept('project/*/doc', { statusCode: 200 })
|
||||
|
||||
cy.findByRole('button', { name: /new file/i }).click()
|
||||
cy.findByRole('button', { name: /create/i }).click()
|
||||
|
||||
cy.then(() => {
|
||||
socket.emitToClient('reciveNewDoc', 'root-folder-id', {
|
||||
_id: '12345',
|
||||
name: 'abcdef.tex',
|
||||
docs: [],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
})
|
||||
})
|
||||
|
||||
cy.findByRole('treeitem', { name: 'abcdef.tex' }).then($itemEl => {
|
||||
cy.findByTestId('file-tree-list-root').then($rootEl => {
|
||||
expect($itemEl.get(0).parentNode?.parentNode).to.equal($rootEl.get(0))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('starts a new selection', function () {
|
||||
cy.findByRole('treeitem', { name: 'sub.tex' }).should(
|
||||
'have.attr',
|
||||
'aria-selected',
|
||||
'false'
|
||||
)
|
||||
|
||||
cy.findByRole('treeitem', { name: 'main.tex' }).click({
|
||||
ctrlKey: true,
|
||||
cmdKey: true,
|
||||
})
|
||||
|
||||
cy.findByRole('treeitem', { name: 'main.tex' }).should(
|
||||
'have.attr',
|
||||
'aria-selected',
|
||||
'true'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1,59 @@
|
||||
import FileTreeToolbar from '../../../../../frontend/js/features/file-tree/components/file-tree-toolbar'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import { FileTreeProvider } from '../helpers/file-tree-provider'
|
||||
|
||||
describe('<FileTreeToolbar/>', function () {
|
||||
it('without selected files', function () {
|
||||
cy.mount(
|
||||
<EditorProviders rootDocId="">
|
||||
<FileTreeProvider>
|
||||
<FileTreeToolbar />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findAllByRole('button', { name: 'New file' })
|
||||
cy.findAllByRole('button', { name: 'New folder' })
|
||||
cy.findAllByRole('button', { name: 'Upload' })
|
||||
cy.findAllByRole('button', { name: 'Rename' }).should('not.exist')
|
||||
cy.findAllByRole('button', { name: 'Delete' }).should('not.exist')
|
||||
})
|
||||
|
||||
it('read-only', function () {
|
||||
cy.mount(
|
||||
<EditorProviders permissionsLevel="readOnly">
|
||||
<FileTreeProvider>
|
||||
<FileTreeToolbar />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findAllByRole('button').should('not.exist')
|
||||
})
|
||||
|
||||
it('with one selected file', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders rootDocId="456def" rootFolder={rootFolder as any}>
|
||||
<FileTreeProvider>
|
||||
<FileTreeToolbar />
|
||||
</FileTreeProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findAllByRole('button', { name: 'New file' })
|
||||
cy.findAllByRole('button', { name: 'New folder' })
|
||||
cy.findAllByRole('button', { name: 'Upload' })
|
||||
cy.findAllByRole('button', { name: 'Rename' })
|
||||
cy.findAllByRole('button', { name: 'Delete' })
|
||||
})
|
||||
})
|
@@ -0,0 +1,114 @@
|
||||
import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
|
||||
describe('FileTree Context Menu Flow', function () {
|
||||
beforeEach(function () {
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-user', { id: 'user1' })
|
||||
})
|
||||
})
|
||||
|
||||
it('opens on contextMenu event', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('menu').should('not.exist')
|
||||
cy.findByRole('button', { name: 'main.tex' }).trigger('contextmenu')
|
||||
cy.findByRole('menu')
|
||||
})
|
||||
|
||||
it('closes when a new selection is started', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{ _id: '456def', name: 'main.tex' },
|
||||
{ _id: '456def', name: 'foo.tex' },
|
||||
],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('menu').should('not.exist')
|
||||
cy.findByRole('button', { name: 'main.tex' }).trigger('contextmenu')
|
||||
cy.findByRole('menu')
|
||||
cy.findAllByRole('button', { name: 'foo.tex' }).click()
|
||||
cy.findByRole('menu').should('not.exist')
|
||||
})
|
||||
|
||||
it("doesn't open in read only mode", function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
permissionsLevel="readOnly"
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findAllByRole('button', { name: 'main.tex' }).trigger('contextmenu')
|
||||
cy.findByRole('menu').should('not.exist')
|
||||
})
|
||||
})
|
@@ -0,0 +1,281 @@
|
||||
import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import { SocketIOMock } from '@/ide/connection/SocketIoShim'
|
||||
|
||||
describe('FileTree Create Folder Flow', function () {
|
||||
let socket: SocketIOMock
|
||||
beforeEach(function () {
|
||||
socket = new SocketIOMock()
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-user', { id: 'user1' })
|
||||
})
|
||||
})
|
||||
|
||||
it('add to root when no files are selected', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
socket={socket}
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const name = 'Foo Bar In Root'
|
||||
|
||||
cy.intercept('post', '/project/*/folder', {
|
||||
body: {
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
docs: [],
|
||||
_id: fakeId(),
|
||||
name,
|
||||
},
|
||||
}).as('createFolder')
|
||||
|
||||
createFolder(name)
|
||||
|
||||
cy.get('@createFolder').its('request.body').should('deep.equal', {
|
||||
parent_folder_id: 'root-folder-id',
|
||||
name,
|
||||
})
|
||||
|
||||
cy.then(() => {
|
||||
socket.emitToClient('reciveNewFolder', 'root-folder-id', {
|
||||
_id: fakeId(),
|
||||
name,
|
||||
docs: [],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
})
|
||||
})
|
||||
|
||||
cy.findByRole('treeitem', { name })
|
||||
})
|
||||
|
||||
it('add to folder from folder', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [],
|
||||
folders: [
|
||||
{
|
||||
_id: '789ghi',
|
||||
name: 'thefolder',
|
||||
docs: [],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
},
|
||||
],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
rootDocId="789ghi"
|
||||
socket={socket}
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Expand' }).click()
|
||||
|
||||
const name = 'Foo Bar In thefolder'
|
||||
|
||||
cy.intercept('post', '/project/*/folder', {
|
||||
body: {
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
docs: [],
|
||||
_id: fakeId(),
|
||||
name,
|
||||
},
|
||||
}).as('createFolder')
|
||||
|
||||
createFolder(name)
|
||||
|
||||
cy.get('@createFolder').its('request.body').should('deep.equal', {
|
||||
parent_folder_id: '789ghi',
|
||||
name,
|
||||
})
|
||||
|
||||
cy.then(() => {
|
||||
socket.emitToClient('reciveNewFolder', '789ghi', {
|
||||
_id: fakeId(),
|
||||
name,
|
||||
docs: [],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
})
|
||||
})
|
||||
|
||||
// find the created folder
|
||||
cy.findByRole('treeitem', { name })
|
||||
|
||||
// collapse the parent folder; created folder should not be rendered anymore
|
||||
cy.findByRole('button', { name: 'Collapse' }).click()
|
||||
cy.findByRole('treeitem', { name }).should('not.exist')
|
||||
})
|
||||
|
||||
it('add to folder from child', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [],
|
||||
folders: [
|
||||
{
|
||||
_id: '789ghi',
|
||||
name: 'thefolder',
|
||||
docs: [],
|
||||
fileRefs: [{ _id: '456def', name: 'sub.tex' }],
|
||||
folders: [],
|
||||
},
|
||||
],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
socket={socket}
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const name = 'Foo Bar In thefolder'
|
||||
|
||||
cy.intercept('post', '/project/*/folder', {
|
||||
body: {
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
docs: [],
|
||||
_id: fakeId(),
|
||||
name,
|
||||
},
|
||||
}).as('createFolder')
|
||||
|
||||
createFolder(name)
|
||||
|
||||
cy.get('@createFolder').its('request.body').should('deep.equal', {
|
||||
parent_folder_id: '789ghi',
|
||||
name,
|
||||
})
|
||||
|
||||
cy.then(() => {
|
||||
socket.emitToClient('reciveNewFolder', '789ghi', {
|
||||
_id: fakeId(),
|
||||
name,
|
||||
docs: [],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
})
|
||||
})
|
||||
|
||||
// find the created folder
|
||||
cy.findByRole('treeitem', { name })
|
||||
|
||||
// collapse the parent folder; created folder should not be rendered anymore
|
||||
cy.findByRole('button', { name: 'Collapse' }).click()
|
||||
cy.findByRole('treeitem', { name }).should('not.exist')
|
||||
})
|
||||
|
||||
it('prevents adding duplicate or invalid names', function () {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'existingFile' }],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
socket={socket}
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const name = 'existingFile'
|
||||
|
||||
cy.intercept('post', '/project/*/folder', cy.spy().as('createFolder'))
|
||||
|
||||
createFolder(name)
|
||||
|
||||
cy.get('@createFolder').should('not.have.been.called')
|
||||
|
||||
cy.findByRole('alert', {
|
||||
name: 'A file or folder with this name already exists',
|
||||
})
|
||||
|
||||
cy.findByRole('textbox').type('in/valid ')
|
||||
|
||||
cy.findByRole('alert', {
|
||||
name: 'File name is empty or contains invalid characters',
|
||||
})
|
||||
})
|
||||
|
||||
function createFolder(name: string) {
|
||||
cy.findByRole('button', { name: 'New folder' }).click()
|
||||
cy.findByRole('textbox').type(name)
|
||||
cy.findByRole('button', { name: 'Create' }).click()
|
||||
}
|
||||
|
||||
function fakeId() {
|
||||
return Math.random().toString(16).replace(/0\./, 'random-test-id-')
|
||||
}
|
||||
})
|
@@ -0,0 +1,291 @@
|
||||
import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import { SocketIOMock } from '@/ide/connection/SocketIoShim'
|
||||
|
||||
describe('FileTree Delete Entity Flow', function () {
|
||||
beforeEach(function () {
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-user', { id: 'user1' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('single entity', function () {
|
||||
let socket: SocketIOMock
|
||||
beforeEach(function () {
|
||||
socket = new SocketIOMock()
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{ _id: '123abc', name: 'foo.tex' },
|
||||
{ _id: '456def', name: 'main.tex' },
|
||||
],
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<div style={{ width: 400 }}>
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
socket={socket}
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
</div>
|
||||
)
|
||||
|
||||
cy.findByRole('treeitem', { name: 'main.tex' }).click()
|
||||
cy.findByRole('button', { name: 'Open main.tex action menu' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Delete' }).click()
|
||||
})
|
||||
|
||||
it('removes item', function () {
|
||||
cy.intercept('delete', '/project/*/doc/*', { statusCode: 204 }).as(
|
||||
'deleteDoc'
|
||||
)
|
||||
|
||||
cy.findByRole('dialog').within(() => {
|
||||
// check that the confirmation modal is open
|
||||
cy.findByText(
|
||||
'Are you sure you want to permanently delete the following files?'
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Delete' }).click()
|
||||
})
|
||||
|
||||
cy.wait('@deleteDoc')
|
||||
|
||||
cy.then(() => {
|
||||
socket.emitToClient('removeEntity', '456def')
|
||||
})
|
||||
|
||||
cy.findByRole('treeitem', {
|
||||
name: 'main.tex',
|
||||
hidden: true, // treeitem might be hidden behind the modal
|
||||
}).should('not.exist')
|
||||
|
||||
cy.findByRole('treeitem', {
|
||||
name: 'main.tex',
|
||||
}).should('not.exist')
|
||||
|
||||
// check that the confirmation modal is closed
|
||||
cy.findByText(
|
||||
'Are you sure you want to permanently delete the following files?'
|
||||
).should('not.exist')
|
||||
|
||||
cy.get('@deleteDoc.all').should('have.length', 1)
|
||||
})
|
||||
|
||||
it('continues delete on 404s', function () {
|
||||
cy.intercept('delete', '/project/*/doc/*', { statusCode: 404 }).as(
|
||||
'deleteDoc'
|
||||
)
|
||||
|
||||
cy.findByRole('dialog').within(() => {
|
||||
// check that the confirmation modal is open
|
||||
cy.findByText(
|
||||
'Are you sure you want to permanently delete the following files?'
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Delete' }).click()
|
||||
})
|
||||
|
||||
cy.then(() => {
|
||||
socket.emitToClient('removeEntity', '456def')
|
||||
})
|
||||
|
||||
cy.findByRole('treeitem', {
|
||||
name: 'main.tex',
|
||||
hidden: true, // treeitem might be hidden behind the modal
|
||||
}).should('not.exist')
|
||||
|
||||
cy.findByRole('treeitem', {
|
||||
name: 'main.tex',
|
||||
}).should('not.exist')
|
||||
|
||||
// check that the confirmation modal is closed
|
||||
// is not, the 404 probably triggered a bug
|
||||
cy.findByText(
|
||||
'Are you sure you want to permanently delete the following files?'
|
||||
).should('not.exist')
|
||||
})
|
||||
|
||||
it('aborts delete on error', function () {
|
||||
cy.intercept('delete', '/project/*/doc/*', { statusCode: 500 }).as(
|
||||
'deleteDoc'
|
||||
)
|
||||
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.findByRole('button', { name: 'Delete' }).click()
|
||||
})
|
||||
|
||||
// The modal should still be open, but the file should not be deleted
|
||||
cy.findByRole('treeitem', { name: 'main.tex', hidden: true })
|
||||
})
|
||||
})
|
||||
|
||||
describe('folders', function () {
|
||||
let socket: SocketIOMock
|
||||
beforeEach(function () {
|
||||
socket = new SocketIOMock()
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [
|
||||
{
|
||||
_id: '123abc',
|
||||
name: 'folder',
|
||||
docs: [],
|
||||
folders: [],
|
||||
fileRefs: [{ _id: '789ghi', name: 'my.bib' }],
|
||||
},
|
||||
],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<div style={{ width: 400 }}>
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
socket={socket}
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
</div>
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Expand' }).click()
|
||||
cy.findByRole('treeitem', { name: 'main.tex' }).click()
|
||||
cy.findByRole('treeitem', { name: 'my.bib' }).click({
|
||||
ctrlKey: true,
|
||||
cmdKey: true,
|
||||
})
|
||||
|
||||
cy.then(() => {
|
||||
socket.emitToClient('removeEntity', '123abc')
|
||||
})
|
||||
})
|
||||
|
||||
it('removes the folder', function () {
|
||||
cy.findByRole('treeitem', { name: 'folder' }).should('not.exist')
|
||||
})
|
||||
|
||||
it('leaves the main file selected', function () {
|
||||
cy.findByRole('treeitem', { name: 'main.tex', selected: true })
|
||||
})
|
||||
|
||||
it('unselect the child entity', function () {
|
||||
// as a proxy to check that the child entity has been unselect we start
|
||||
// a delete and ensure the modal is displayed (the cancel button can be
|
||||
// selected) This is needed to make sure the test fail.
|
||||
cy.findByRole('button', { name: 'Open main.tex action menu' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Delete' }).click()
|
||||
cy.findByRole('button', { name: 'Cancel' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('multiple entities', function () {
|
||||
let socket: SocketIOMock
|
||||
beforeEach(function () {
|
||||
socket = new SocketIOMock()
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [{ _id: '789ghi', name: 'my.bib' }],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<div style={{ width: 400 }}>
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
socket={socket}
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub()}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
</div>
|
||||
)
|
||||
|
||||
// select two files
|
||||
cy.findByRole('treeitem', { name: 'main.tex' }).click()
|
||||
cy.findByRole('treeitem', { name: 'my.bib' }).click({
|
||||
ctrlKey: true,
|
||||
cmdKey: true,
|
||||
})
|
||||
|
||||
// open the context menu
|
||||
cy.findByRole('button', { name: 'my.bib' }).trigger('contextmenu')
|
||||
|
||||
// make sure the menu has opened, with only a "Delete" item (as multiple files are selected)
|
||||
cy.findByRole('menu')
|
||||
cy.findAllByRole('menuitem').should('have.length', 1)
|
||||
|
||||
// select the Delete menu item
|
||||
cy.findByRole('menuitem', { name: 'Delete' }).click()
|
||||
})
|
||||
|
||||
it('removes all items and reindexes references after deleting .bib file', function () {
|
||||
cy.intercept('delete', '/project/123abc/doc/456def', {
|
||||
statusCode: 204,
|
||||
}).as('deleteDoc')
|
||||
|
||||
cy.intercept('delete', '/project/123abc/file/789ghi', {
|
||||
statusCode: 204,
|
||||
}).as('deleteFile')
|
||||
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.findByRole('button', { name: 'Delete' }).click()
|
||||
})
|
||||
|
||||
cy.then(() => {
|
||||
socket.emitToClient('removeEntity', '456def')
|
||||
socket.emitToClient('removeEntity', '789ghi')
|
||||
})
|
||||
|
||||
for (const name of ['main.tex', 'my.bib']) {
|
||||
for (const hidden of [true, false]) {
|
||||
cy.findByRole('treeitem', { name, hidden }).should('not.exist')
|
||||
}
|
||||
}
|
||||
|
||||
// check that the confirmation modal is closed
|
||||
cy.findByText('Are you sure').should('not.exist')
|
||||
|
||||
cy.get('@deleteDoc.all').should('have.length', 1)
|
||||
cy.get('@deleteFile.all').should('have.length', 1)
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1,164 @@
|
||||
import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import { SocketIOMock } from '@/ide/connection/SocketIoShim'
|
||||
|
||||
describe('FileTree Rename Entity Flow', function () {
|
||||
beforeEach(function () {
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-user', { id: 'user1' })
|
||||
})
|
||||
})
|
||||
|
||||
let socket: SocketIOMock
|
||||
beforeEach(function () {
|
||||
socket = new SocketIOMock()
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [{ _id: '456def', name: 'a.tex' }],
|
||||
folders: [
|
||||
{
|
||||
_id: '987jkl',
|
||||
name: 'folder',
|
||||
docs: [],
|
||||
fileRefs: [
|
||||
{ _id: '789ghi', name: 'c.tex' },
|
||||
{ _id: '981gkp', name: 'e.tex' },
|
||||
],
|
||||
folders: [],
|
||||
},
|
||||
],
|
||||
fileRefs: [],
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<div style={{ width: 400 }}>
|
||||
<EditorProviders
|
||||
rootFolder={rootFolder as any}
|
||||
projectId="123abc"
|
||||
socket={socket}
|
||||
>
|
||||
<FileTreeRoot
|
||||
refProviders={{}}
|
||||
setRefProviderEnabled={cy.stub()}
|
||||
setStartedFreeTrial={cy.stub()}
|
||||
onSelect={cy.stub().as('onSelect')}
|
||||
onInit={cy.stub()}
|
||||
isConnected
|
||||
/>
|
||||
</EditorProviders>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
it('renames doc', function () {
|
||||
cy.intercept('/project/*/doc/*/rename', { statusCode: 204 }).as('renameDoc')
|
||||
|
||||
renameItem('a.tex', 'b.tex')
|
||||
|
||||
cy.findByRole('treeitem', { name: 'b.tex' })
|
||||
|
||||
cy.get('@renameDoc').its('request.body').should('deep.equal', {
|
||||
name: 'b.tex',
|
||||
})
|
||||
})
|
||||
|
||||
it('renames folder', function () {
|
||||
cy.intercept('/project/*/folder/*/rename', { statusCode: 204 }).as(
|
||||
'renameFolder'
|
||||
)
|
||||
|
||||
renameItem('folder', 'new folder name')
|
||||
|
||||
cy.findByRole('treeitem', { name: 'new folder name' })
|
||||
|
||||
cy.get('@renameFolder').its('request.body').should('deep.equal', {
|
||||
name: 'new folder name',
|
||||
})
|
||||
})
|
||||
|
||||
it('renames file in subfolder', function () {
|
||||
cy.intercept('/project/*/file/*/rename', { statusCode: 204 }).as(
|
||||
'renameFile'
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Expand' }).click()
|
||||
|
||||
renameItem('c.tex', 'd.tex')
|
||||
|
||||
cy.findByRole('treeitem', { name: 'folder' })
|
||||
cy.findByRole('treeitem', { name: 'd.tex' })
|
||||
|
||||
cy.get('@renameFile').its('request.body').should('deep.equal', {
|
||||
name: 'd.tex',
|
||||
})
|
||||
})
|
||||
|
||||
it('reverts rename on error', function () {
|
||||
cy.intercept('/project/*/doc/*/rename', { statusCode: 500 })
|
||||
|
||||
renameItem('a.tex', 'b.tex')
|
||||
|
||||
cy.findByRole('treeitem', { name: 'a.tex' })
|
||||
})
|
||||
|
||||
it('shows error modal on invalid filename', function () {
|
||||
renameItem('a.tex', '///')
|
||||
|
||||
cy.findByRole('alert', {
|
||||
name: 'File name is empty or contains invalid characters',
|
||||
hidden: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('shows error modal on duplicate filename', function () {
|
||||
renameItem('a.tex', 'folder')
|
||||
|
||||
cy.findByRole('alert', {
|
||||
name: 'A file or folder with this name already exists',
|
||||
hidden: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('shows error modal on duplicate filename in subfolder', function () {
|
||||
cy.findByRole('button', { name: 'Expand' }).click()
|
||||
|
||||
renameItem('c.tex', 'e.tex')
|
||||
|
||||
cy.findByRole('alert', {
|
||||
name: 'A file or folder with this name already exists',
|
||||
hidden: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('shows error modal on blocked filename', function () {
|
||||
renameItem('a.tex', 'prototype')
|
||||
|
||||
cy.findByRole('alert', {
|
||||
name: 'This file name is blocked.',
|
||||
hidden: true,
|
||||
})
|
||||
})
|
||||
|
||||
describe('via socket event', function () {
|
||||
it('renames doc', function () {
|
||||
cy.findByRole('treeitem', { name: 'a.tex' })
|
||||
|
||||
cy.then(() => {
|
||||
socket.emitToClient('reciveEntityRename', '456def', 'socket.tex')
|
||||
})
|
||||
|
||||
cy.findByRole('treeitem', { name: 'socket.tex' })
|
||||
})
|
||||
})
|
||||
|
||||
function renameItem(from: string, to: string) {
|
||||
cy.findByRole('treeitem', { name: from }).click()
|
||||
cy.findByRole('button', { name: `Open ${from} action menu` }).click()
|
||||
cy.findByRole('menuitem', { name: 'Rename' }).click()
|
||||
cy.findByRole('textbox').clear()
|
||||
cy.findByRole('textbox').type(to + '{enter}')
|
||||
}
|
||||
})
|
@@ -0,0 +1,34 @@
|
||||
import { ComponentProps, FC, useRef, useState } from 'react'
|
||||
import FileTreeContext from '@/features/file-tree/components/file-tree-context'
|
||||
|
||||
export const FileTreeProvider: FC<{
|
||||
refProviders?: Record<string, boolean>
|
||||
}> = ({ children, refProviders = {} }) => {
|
||||
const [fileTreeContainer, setFileTreeContainer] =
|
||||
useState<HTMLDivElement | null>(null)
|
||||
|
||||
const propsRef =
|
||||
useRef<Omit<ComponentProps<typeof FileTreeContext>, 'refProviders'>>()
|
||||
|
||||
if (propsRef.current === undefined) {
|
||||
propsRef.current = {
|
||||
setRefProviderEnabled: cy.stub().as('setRefProviderEnabled'),
|
||||
setStartedFreeTrial: cy.stub().as('setStartedFreeTrial'),
|
||||
onSelect: cy.stub(),
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={setFileTreeContainer}>
|
||||
{fileTreeContainer && (
|
||||
<FileTreeContext
|
||||
refProviders={refProviders}
|
||||
fileTreeContainer={fileTreeContainer}
|
||||
{...propsRef.current}
|
||||
>
|
||||
<>{children}</>
|
||||
</FileTreeContext>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
import { expect } from 'chai'
|
||||
|
||||
import iconTypeFromName from '../../../../../frontend/js/features/file-tree/util/icon-type-from-name'
|
||||
|
||||
describe('iconTypeFromName', function () {
|
||||
it('returns correct icon type', function () {
|
||||
expect(iconTypeFromName('main.tex')).to.equal('description')
|
||||
expect(iconTypeFromName('main.png')).to.equal('image')
|
||||
expect(iconTypeFromName('main.csv')).to.equal('table_chart')
|
||||
expect(iconTypeFromName('main.py')).to.equal('code')
|
||||
expect(iconTypeFromName('main.bib')).to.equal('menu_book')
|
||||
})
|
||||
|
||||
it('handles missing extensions', function () {
|
||||
expect(iconTypeFromName('main')).to.equal('description')
|
||||
})
|
||||
|
||||
it('lowercases extension', function () {
|
||||
expect(iconTypeFromName('ZOTERO.BIB')).to.equal('menu_book')
|
||||
})
|
||||
})
|
162
services/web/test/frontend/features/file-tree/util/path.test.ts
Normal file
162
services/web/test/frontend/features/file-tree/util/path.test.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { expect } from 'chai'
|
||||
import { Folder } from '../../../../../types/folder'
|
||||
import { docId } from '../../source-editor/helpers/mock-doc'
|
||||
import {
|
||||
findEntityByPath,
|
||||
pathInFolder,
|
||||
previewByPath,
|
||||
} from '@/features/file-tree/util/path'
|
||||
|
||||
describe('Path utils', function () {
|
||||
let rootFolder: Folder
|
||||
|
||||
beforeEach(function () {
|
||||
rootFolder = {
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{
|
||||
_id: docId,
|
||||
name: 'main.tex',
|
||||
},
|
||||
],
|
||||
folders: [
|
||||
{
|
||||
_id: 'test-folder-id',
|
||||
name: 'test-folder',
|
||||
docs: [
|
||||
{
|
||||
_id: 'test-doc-in-folder',
|
||||
name: 'example.tex',
|
||||
},
|
||||
],
|
||||
fileRefs: [
|
||||
{
|
||||
_id: 'test-file-in-folder',
|
||||
name: 'example.png',
|
||||
hash: '42',
|
||||
},
|
||||
],
|
||||
folders: [
|
||||
{
|
||||
_id: 'test-subfolder-id',
|
||||
name: 'test-subfolder',
|
||||
docs: [
|
||||
{
|
||||
_id: 'test-doc-in-subfolder',
|
||||
name: 'nested-example.tex',
|
||||
},
|
||||
],
|
||||
fileRefs: [
|
||||
{
|
||||
_id: 'test-file-in-subfolder',
|
||||
name: 'nested-example.png',
|
||||
hash: '43',
|
||||
},
|
||||
],
|
||||
folders: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
fileRefs: [
|
||||
{
|
||||
_id: 'test-image-file',
|
||||
name: 'frog.jpg',
|
||||
hash: '21',
|
||||
},
|
||||
{
|
||||
_id: 'uppercase-extension-image-file',
|
||||
name: 'frog.JPG',
|
||||
hash: '22',
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
describe('pathInFolder', function () {
|
||||
it('gets null path for non-existent entity', function () {
|
||||
const retrieved = pathInFolder(rootFolder, 'non-existent.tex')
|
||||
expect(retrieved).to.be.null
|
||||
})
|
||||
|
||||
it('gets correct path for document in the root', function () {
|
||||
const retrieved = pathInFolder(rootFolder, docId)
|
||||
expect(retrieved).to.equal('main.tex')
|
||||
})
|
||||
|
||||
it('gets correct path for document in a folder', function () {
|
||||
const retrieved = pathInFolder(rootFolder, 'test-doc-in-folder')
|
||||
expect(retrieved).to.equal('test-folder/example.tex')
|
||||
})
|
||||
|
||||
it('gets correct path for document in a nested folder', function () {
|
||||
const retrieved = pathInFolder(rootFolder, 'test-doc-in-subfolder')
|
||||
expect(retrieved).to.equal(
|
||||
'test-folder/test-subfolder/nested-example.tex'
|
||||
)
|
||||
})
|
||||
|
||||
it('gets correct path for file in a nested folder', function () {
|
||||
const retrieved = pathInFolder(rootFolder, 'test-file-in-subfolder')
|
||||
expect(retrieved).to.equal(
|
||||
'test-folder/test-subfolder/nested-example.png'
|
||||
)
|
||||
})
|
||||
|
||||
it('gets correct path for file in a nested folder relative to folder', function () {
|
||||
const retrieved = pathInFolder(
|
||||
rootFolder.folders[0],
|
||||
'test-file-in-subfolder'
|
||||
)
|
||||
expect(retrieved).to.equal('test-subfolder/nested-example.png')
|
||||
})
|
||||
})
|
||||
|
||||
describe('findEntityByPath', function () {
|
||||
it('returns null for a non-existent path', function () {
|
||||
const retrieved = findEntityByPath(rootFolder, 'not-a-real-document.tex')
|
||||
expect(retrieved).to.be.null
|
||||
})
|
||||
|
||||
it('finds a document in the root', function () {
|
||||
const retrieved = findEntityByPath(rootFolder, 'main.tex')
|
||||
expect(retrieved?.entity._id).to.equal(docId)
|
||||
})
|
||||
|
||||
it('finds a document in a folder', function () {
|
||||
const retrieved = findEntityByPath(rootFolder, 'test-folder/example.tex')
|
||||
expect(retrieved?.entity._id).to.equal('test-doc-in-folder')
|
||||
})
|
||||
|
||||
it('finds a document in a nested folder', function () {
|
||||
const retrieved = findEntityByPath(
|
||||
rootFolder,
|
||||
'test-folder/test-subfolder/nested-example.tex'
|
||||
)
|
||||
expect(retrieved?.entity._id).to.equal('test-doc-in-subfolder')
|
||||
})
|
||||
|
||||
it('finds a file in a nested folder', function () {
|
||||
const retrieved = findEntityByPath(
|
||||
rootFolder,
|
||||
'test-folder/test-subfolder/nested-example.png'
|
||||
)
|
||||
expect(retrieved?.entity._id).to.equal('test-file-in-subfolder')
|
||||
})
|
||||
})
|
||||
|
||||
describe('previewByPath', function () {
|
||||
it('returns extension without preceding dot', function () {
|
||||
const preview = previewByPath(
|
||||
rootFolder,
|
||||
'test-project-id',
|
||||
'test-folder/example.png'
|
||||
)
|
||||
expect(preview).to.deep.equal({
|
||||
url: '/project/test-project-id/blob/42?fallback=test-file-in-folder',
|
||||
extension: 'png',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user