mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-05 06:55:17 -05:00
509 lines
14 KiB
TypeScript
509 lines
14 KiB
TypeScript
|
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'
|
|||
|
|
|||
|
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 () {
|
|||
|
cy.window().then(win => {
|
|||
|
win.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 () {
|
|||
|
cy.window().then(win => {
|
|||
|
win.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 () {
|
|||
|
cy.window().then(win => {
|
|||
|
win.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 () {
|
|||
|
cy.window().then(win => {
|
|||
|
win.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 () {
|
|||
|
cy.window().then(win => {
|
|||
|
win.ExposedSettings.hasLinkedProjectFileFeature = true
|
|||
|
win.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 () {
|
|||
|
cy.window().then(win => {
|
|||
|
win.ExposedSettings.hasLinkedProjectFileFeature = true
|
|||
|
win.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 />
|
|||
|
}
|