| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 |
- 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: '1234-5678',
- path: 'baz.jpg',
- },
- {
- build: '1234-5678',
- 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: '1234-5678',
- },
- })
- })
- 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('imports from a pasted URL and removes a trailing dot', function () {
- cy.intercept('/project/*/linked_file', {
- statusCode: 204,
- }).as('createLinkedFile')
- cy.mount(
- <EditorProviders>
- <FileTreeProvider>
- <OpenWithMode mode="url" />
- </FileTreeProvider>
- </EditorProviders>
- )
- cy.wrap(null).then(() => {
- const clipboardData = new DataTransfer()
- clipboardData.setData('text/plain', 'https://example.com/example.tex.')
- cy.findByLabelText('URL to fetch the file from').trigger('paste', {
- clipboardData,
- })
- })
- cy.findByLabelText('URL to fetch the file from').should(
- 'have.value',
- 'https://example.com/example.tex'
- )
- cy.findByLabelText('File Name In This Project').should(
- 'have.value',
- 'example.tex'
- )
- cy.findByLabelText('File Name In This Project').clear()
- cy.findByLabelText('File Name In This Project').type('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('imports from a pasted URL without a trailing dot and keeps it unchanged', function () {
- cy.intercept('/project/*/linked_file', {
- statusCode: 204,
- }).as('createLinkedFile')
- cy.mount(
- <EditorProviders>
- <FileTreeProvider>
- <OpenWithMode mode="url" />
- </FileTreeProvider>
- </EditorProviders>
- )
- cy.wrap(null).then(() => {
- const clipboardData = new DataTransfer()
- clipboardData.setData('text/plain', 'https://example.com/example.tex')
- cy.findByLabelText('URL to fetch the file from').trigger('paste', {
- clipboardData,
- })
- })
- cy.findByLabelText('URL to fetch the file from').should(
- 'have.value',
- 'https://example.com/example.tex'
- )
- cy.findByLabelText('File Name In This Project').should(
- 'have.value',
- 'example.tex'
- )
- cy.findByLabelText('File Name In This Project').clear()
- cy.findByLabelText('File Name In This Project').type('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.findByRole('button', { name: 'Select files' }).should('be.focused')
- cy.wrap(null).then(() => {
- const clipboardData = new DataTransfer()
- clipboardData.items.add(
- new File(['test'], 'test.tex', { type: 'text/plain' })
- )
- cy.focused().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 />
- }
|