overleaf/services/web/test/frontend/features/source-editor/components/figure-modal.spec.tsx
Alf Eaton 612c7c28b0 Merge pull request #16511 from overleaf/ae-upgrade-uppy
Upgrade Uppy to v3

GitOrigin-RevId: ca3e366a20ac651a98aafe12bf319b1968ac6ec1
2024-01-19 09:05:05 +00:00

418 lines
14 KiB
TypeScript

import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { EditorProviders } from '../../../helpers/editor-providers'
import { mockScope, rootFolderId } from '../helpers/mock-scope'
import { FC } from 'react'
import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
const Container: FC = ({ children }) => (
<div style={{ width: 1500, height: 785 }}>{children}</div>
)
const clickToolbarButton = (text: string) => {
cy.findByLabelText(text).click()
cy.findByLabelText(text).trigger('mouseout')
}
const chooseFileFromComputer = () => {
cy.get('@file-input').selectFile(
{
fileName: 'frog.jpg',
contents: Cypress.Buffer.from('image-data'),
mimeType: 'image/jpg',
},
{
force: true,
}
)
}
const matchUrl = (urlToMatch: RegExp | string) =>
Cypress.sinon.match(req => {
if (!req || typeof req.url !== 'string') {
return false
}
if (typeof urlToMatch === 'string') {
return req.url.endsWith(urlToMatch)
}
return Boolean(req.url.match(urlToMatch))
})
describe('<FigureModal />', function () {
// TODO: rewrite these tests to be in source mode when toolbar is added there
// TODO: Write tests for width toggle, when we can match on source code
beforeEach(function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptMathJax()
cy.interceptEvents()
cy.interceptSpelling()
const content = ''
const scope = mockScope(content)
scope.editor.showVisual = true
const FileTreePathProvider: FC = ({ children }) => (
<FileTreePathContext.Provider
value={{
dirname: cy.stub(),
findEntityByPath: cy.stub(),
pathInFolder: cy.stub(),
previewByPath: cy
.stub()
.as('previewByPath')
.returns({ url: 'frog.jpg', extension: 'jpg' }),
}}
>
{children}
</FileTreePathContext.Provider>
)
cy.mount(
<Container>
<EditorProviders scope={scope} providers={{ FileTreePathProvider }}>
<CodemirrorEditor />
</EditorProviders>
</Container>
)
})
describe('Upload from computer source', function () {
beforeEach(function () {
cy.interceptFileUpload()
clickToolbarButton('Insert Figure')
cy.findByRole('menu').within(() => {
cy.findByText('Upload from computer').click()
})
cy.findByLabelText('Uppy Dashboard')
.get('.uppy-Dashboard-input:first')
.as('file-input')
})
it('Shows file name and size when selecting file', function () {
chooseFileFromComputer()
cy.findByLabelText('File name').should('have.text', 'frog.jpg')
cy.findByLabelText('File size').should('have.text', '10 B')
})
it('Uploads file when clicking insert', function () {
chooseFileFromComputer()
cy.get('@uploadRequest').should('not.have.been.called')
cy.findByText('Insert figure').click()
cy.get('@uploadRequest').should(
'have.been.calledWith',
matchUrl(`/project/test-project/upload?folder_id=${rootFolderId}`)
)
cy.get('.cm-content').should(
'have.text',
'\\begin{figure} \\centering Enter Caption 🏷fig:enter-label\\end{figure}'
)
})
it('Enables insert button when choosing file', function () {
cy.findByText('Insert figure').should('be.disabled')
chooseFileFromComputer()
cy.findByText('Insert figure').should('be.enabled')
})
})
describe('Upload from project files source', function () {
beforeEach(function () {
clickToolbarButton('Insert Figure')
cy.findByRole('menu').within(() => {
cy.findByText('From project files').click()
})
})
it('Lists files from project', function () {
cy.findByText('Select image from project files').click()
cy.findByRole('listbox')
.children()
.should('have.length', 2)
.should('contain.text', 'frog.jpg')
.should('contain.text', 'unicorn.png')
.should('contain.text', 'figures/')
})
it('Enables insert button when choosing file', function () {
cy.findByText('Insert figure').should('be.disabled')
cy.findByText('Select image from project files').click()
cy.findByRole('listbox').within(() => {
cy.findByText('frog.jpg').click()
})
cy.findByText('Insert figure').should('be.enabled')
})
it('Inserts file when pressing insert button', function () {
cy.findByText('Select image from project files').click()
cy.findByRole('listbox').within(() => {
cy.findByText('frog.jpg').click()
})
cy.findByText('Insert figure').click()
cy.get('.cm-content').should(
'have.text',
'\\begin{figure} \\centering Enter Caption 🏷fig:enter-label\\end{figure}'
)
})
})
describe('From another project source', function () {
beforeEach(function () {
cy.interceptProjectListing()
cy.interceptCompile()
cy.interceptLinkedFile()
clickToolbarButton('Insert Figure')
cy.findByRole('menu').within(() => {
cy.findByText('From another project').click()
})
cy.findByText('Select a project').parent().as('project-dropdown')
cy.findByText('Select a file').parent().as('file-dropdown')
})
it('List projects and files in projects', function () {
cy.findByText('Insert figure').should('be.disabled')
cy.get('@file-dropdown').should('have.class', 'disabled')
cy.get('@project-dropdown').click()
cy.findByRole('listbox').as('project-select')
cy.get('@project-select').children().should('have.length', 2)
cy.get('@project-select').within(() => {
cy.findByText('My first project').click()
})
cy.get('@file-dropdown').should('not.have.class', 'disabled')
cy.get('@file-dropdown').click()
cy.findByRole('listbox').as('file-select')
cy.get('@file-select').children().should('have.length', 2)
cy.get('@file-select').should('contain.text', 'frog.jpg')
cy.get('@file-select').should('contain.text', 'figures/unicorn.png')
cy.get('@file-select').within(() => {
cy.findByText('frog.jpg').click()
})
cy.findByText('Insert figure').should('be.enabled')
})
it('Enables insert button when choosing file', function () {
cy.findByText('Insert figure').should('be.disabled')
cy.get('@project-dropdown').click()
cy.findByRole('listbox').within(() => {
cy.findByText('My first project').click()
})
cy.get('@file-dropdown').click()
cy.findByRole('listbox').within(() => {
cy.findByText('frog.jpg').click()
})
cy.findByText('Insert figure').should('be.enabled')
})
it('Creates linked file when pressing insert', function () {
cy.get('@project-dropdown').click()
cy.findByRole('listbox').within(() => {
cy.findByText('My first project').click()
})
cy.get('@file-dropdown').click()
cy.findByRole('listbox').within(() => {
cy.findByText('frog.jpg').click()
})
cy.findByText('Insert figure').click()
cy.get('@linked-file-request').should('have.been.calledWithMatch', {
body: {
provider: 'project_file',
data: {
source_entity_path: '/frog.jpg',
source_project_id: 'fake-project-1',
},
},
})
cy.get('.cm-content').should(
'have.text',
'\\begin{figure} \\centering Enter Caption 🏷fig:enter-label\\end{figure}'
)
})
it('Creates linked output file when pressing insert', function () {
cy.get('@project-dropdown').click()
cy.findByRole('listbox').within(() => {
cy.findByText('My first project').click()
})
cy.findByText('select from output files').click()
cy.findByText('Select an output file').parent().as('output-file-dropdown')
cy.get('@output-file-dropdown').click()
cy.findByRole('listbox').within(() => {
cy.findByText('output.pdf').click()
})
cy.findByText('Insert figure').click()
cy.get('@linked-file-request').should('have.been.calledWithMatch', {
body: {
provider: 'project_output_file',
data: {
source_output_file_path: 'output.pdf',
source_project_id: 'fake-project-1',
},
},
})
cy.get('.cm-content').should(
'have.text',
'\\begin{figure} \\centering Enter Caption 🏷fig:enter-label\\end{figure}'
)
})
})
describe('From URL source', function () {
beforeEach(function () {
cy.interceptLinkedFile()
clickToolbarButton('Insert Figure')
cy.findByRole('menu').within(() => {
cy.findByText('From URL').click()
})
cy.findByLabelText('File name in this project').as(
'relocated-file-name-input'
)
cy.findByLabelText('Image URL').as('image-url-input')
cy.get('[data-cy="include-label-option"]').as('include-label-checkbox')
cy.get('[data-cy="include-caption-option"]').as(
'include-caption-checkbox'
)
})
it('Auto fills name based on url', function () {
cy.get('@image-url-input').type('https://my-fake-website.com/frog.jpg')
cy.get('@relocated-file-name-input').should('have.value', 'frog.jpg')
cy.get('@relocated-file-name-input').type('pig')
cy.get('@relocated-file-name-input').should('have.value', 'pig.jpg')
})
it('Enables insert button when name and url is available', function () {
cy.findByText('Insert figure').should('be.disabled')
cy.get('@image-url-input').type('https://my-fake-website.com/frog.jpg')
cy.findByText('Insert figure').should('be.enabled')
})
it('Adds linked file when pressing insert', function () {
cy.get('@image-url-input').type('https://my-fake-website.com/frog.jpg')
cy.findByText('Insert figure').click()
cy.get('@linked-file-request').should('have.been.calledWithMatch', {
body: {
provider: 'url',
data: {
url: 'https://my-fake-website.com/frog.jpg',
},
},
})
cy.get('.cm-content').should(
'have.text',
'\\begin{figure} \\centering Enter Caption 🏷fig:enter-label\\end{figure}'
)
})
})
describe('Editing existing figure', function () {
it('Parses existing label and caption', function () {
cy.get('.cm-content').type(
`\\begin{{}figure}
\\centering
\\includegraphics[width=0.5\\linewidth]{{}frog.jpg}
\\caption{{}My caption}
\\label{{}fig:my-label}
\\end{{}figure}`,
{ delay: 0 }
)
cy.get('[aria-label="Edit figure"]').click()
cy.get('[data-cy="include-caption-option"]').should('be.checked')
cy.get('[data-cy="include-label-option"]').should('be.checked')
})
it('Parses existing width', function () {
cy.get('.cm-content').type(
`\\begin{{}figure}
\\centering
\\includegraphics[width=0.75\\linewidth]{{}frog.jpg}
\\caption{{}My caption}
\\label{{}fig:my-label}
\\end{{}figure}`,
{ delay: 0 }
)
cy.get('[aria-label="Edit figure"]').click()
cy.get('[value="0.75"]').should('be.checked')
})
it('Removes existing label when unchecked', function () {
cy.get('.cm-content').type(
`\\begin{{}figure}
\\centering
\\includegraphics[width=0.75\\linewidth]{{}frog.jpg}
\\label{{}fig:my-label}
\\end{{}figure}`,
{ delay: 0 }
)
cy.get('[aria-label="Edit figure"]').click()
cy.get('[data-cy="include-label-option"]').click()
cy.get('[data-cy="include-label-option"]').should('not.be.checked')
cy.findByText('Done').click()
cy.get('.cm-content').should(
'have.text',
'\\begin{figure}\\centering\\end{figure}'
)
})
it('Removes existing caption when unchecked', function () {
cy.get('.cm-content').type(
`\\begin{{}figure}
\\centering
\\includegraphics[width=0.75\\linewidth]{{}frog.jpg}
\\caption{{}My caption}
\\label{{}fig:my-label}
\\end{{}figure}`,
{ delay: 0 }
)
cy.get('[aria-label="Edit figure"]').click()
cy.get('[data-cy="include-caption-option"]').click()
cy.get('[data-cy="include-caption-option"]').should('not.be.checked')
cy.findByText('Done').click()
cy.get('.cm-content').should(
'have.text',
'\\begin{figure}\\centering🏷fig:my-label\\end{figure}'
)
})
it('Preserves other content when removing figure', function () {
cy.get('.cm-content').type(
`text above
\\begin{{}figure}
\\centering
\\includegraphics[width=0.75\\linewidth]{{}frog.jpg}
\\caption{{}My caption}
\\label{{}fig:my-label}
\\end{{}figure}
text below`,
{ delay: 0 }
)
cy.get('[aria-label="Edit figure"]').click()
cy.get('[aria-label="Remove or replace figure"]').click()
cy.findByText('Delete figure').click()
cy.get('.cm-content').should('have.text', 'text abovetext below')
})
it('Opens figure modal on pasting image', function () {
cy.fixture<Uint8Array>('images/gradient.png').then(gradientBuffer => {
const gradientFile = new File([gradientBuffer], 'gradient.png', {
type: 'image/png',
})
const clipboardData = new DataTransfer()
clipboardData.items.add(gradientFile)
cy.wrap(clipboardData.files).should('have.length', 1)
cy.get('.cm-content').trigger('paste', { clipboardData })
cy.findByText('Upload from computer').should('be.visible')
cy.findByLabelText('File name in this project').should(
'have.value',
'gradient.png'
)
})
})
// TODO: Add tests for replacing image when we can match on image path
// TODO: Add tests for changing image size when we can match on figure width
})
})