Merge pull request #3232 from overleaf/ta-file-tree-react
React File Tree GitOrigin-RevId: fb3141ba8cd9ca0d68e87edb74764a360144c8fe
This commit is contained in:
@@ -29,13 +29,11 @@ export function tearDownMathJaxStubs() {
|
||||
|
||||
export function stubChatStore({ user }) {
|
||||
window._ide = { socket: { on: sinon.stub(), off: sinon.stub() } }
|
||||
window.dispatchEvent = sinon.stub()
|
||||
window.user = user
|
||||
resetChatStore()
|
||||
}
|
||||
|
||||
export function tearDownChatStore() {
|
||||
delete window._ide
|
||||
delete window.dispatchEvent
|
||||
delete window.user
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ describe('ChatStore', function() {
|
||||
window._ide = { socket }
|
||||
mockSocketMessage = message => socket.on.getCall(0).args[1](message)
|
||||
|
||||
window.dispatchEvent = sinon.stub()
|
||||
|
||||
store = new ChatStore()
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import { screen, fireEvent } from '@testing-library/react'
|
||||
import renderWithContext from '../helpers/render-with-context'
|
||||
|
||||
import FileTreeDoc from '../../../../../frontend/js/features/file-tree/components/file-tree-doc'
|
||||
|
||||
describe('<FileTreeDoc/>', function() {
|
||||
it('renders unselected', function() {
|
||||
const { container } = renderWithContext(
|
||||
<FileTreeDoc name="foo.tex" id="123abc" isLinkedFile={false} />
|
||||
)
|
||||
|
||||
screen.getByRole('treeitem', { selected: false })
|
||||
expect(container.querySelector('i.linked-file-highlight')).to.not.exist
|
||||
})
|
||||
|
||||
it('renders selected', function() {
|
||||
renderWithContext(
|
||||
<FileTreeDoc name="foo.tex" id="123abc" isLinkedFile={false} />
|
||||
)
|
||||
|
||||
const treeitem = screen.getByRole('treeitem', { selected: false })
|
||||
fireEvent.click(treeitem)
|
||||
|
||||
screen.getByRole('treeitem', { selected: true })
|
||||
screen.getByRole('menuitem', { name: 'Rename' })
|
||||
screen.getByRole('menuitem', { name: 'Delete' })
|
||||
screen.getByRole('menuitem', { name: 'New File' })
|
||||
screen.getByRole('menuitem', { name: 'New Folder' })
|
||||
screen.getByRole('menuitem', { name: 'Upload' })
|
||||
})
|
||||
|
||||
it('renders as linked file', function() {
|
||||
const { container } = renderWithContext(
|
||||
<FileTreeDoc name="foo.tex" id="123abc" isLinkedFile />
|
||||
)
|
||||
|
||||
screen.getByRole('treeitem')
|
||||
expect(container.querySelector('i.linked-file-highlight')).to.exist
|
||||
})
|
||||
|
||||
it('selects', function() {
|
||||
renderWithContext(<FileTreeDoc name="foo.tex" id="123abc" expanded />)
|
||||
|
||||
const treeitem = screen.getByRole('treeitem', { selected: false })
|
||||
fireEvent.click(treeitem)
|
||||
|
||||
screen.getByRole('treeitem', { selected: true })
|
||||
})
|
||||
|
||||
it('multi-selects', function() {
|
||||
renderWithContext(<FileTreeDoc name="foo.tex" id="123abc" expanded />)
|
||||
|
||||
const treeitem = screen.getByRole('treeitem')
|
||||
|
||||
fireEvent.click(treeitem, { ctrlKey: true })
|
||||
screen.getByRole('treeitem', { selected: true })
|
||||
})
|
||||
})
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import { screen, fireEvent } from '@testing-library/react'
|
||||
import renderWithContext from '../helpers/render-with-context'
|
||||
|
||||
import FileTreeFolderList from '../../../../../frontend/js/features/file-tree/components/file-tree-folder-list'
|
||||
|
||||
describe('<FileTreeFolderList/>', function() {
|
||||
it('renders empty', function() {
|
||||
renderWithContext(<FileTreeFolderList folders={[]} docs={[]} files={[]} />)
|
||||
|
||||
screen.queryByRole('tree')
|
||||
expect(screen.queryByRole('treeitem')).to.not.exist
|
||||
})
|
||||
|
||||
it('renders docs, files and folders', function() {
|
||||
const aFolder = {
|
||||
_id: '456def',
|
||||
name: 'A Folder',
|
||||
folders: [],
|
||||
docs: [],
|
||||
fileRefs: []
|
||||
}
|
||||
const aDoc = { _id: '789ghi', name: 'doc.tex', linkedFileData: {} }
|
||||
const aFile = { _id: '987jkl', name: 'file.bib', linkedFileData: {} }
|
||||
renderWithContext(
|
||||
<FileTreeFolderList folders={[aFolder]} docs={[aDoc]} files={[aFile]} />
|
||||
)
|
||||
|
||||
screen.queryByRole('tree')
|
||||
screen.queryByRole('treeitem', { name: 'A Folder' })
|
||||
screen.queryByRole('treeitem', { name: 'doc.tex' })
|
||||
screen.queryByRole('treeitem', { name: 'file.bib' })
|
||||
})
|
||||
|
||||
describe('selection and multi-selection', function() {
|
||||
it('without write permissions', function() {
|
||||
const docs = [{ _id: '1', name: '1.tex' }, { _id: '2', name: '2.tex' }]
|
||||
renderWithContext(
|
||||
<FileTreeFolderList folders={[]} docs={docs} files={[]} />,
|
||||
{ contextProps: { hasWritePermissions: false } }
|
||||
)
|
||||
|
||||
const treeitem1 = screen.getByRole('treeitem', { name: '1.tex' })
|
||||
const treeitem2 = screen.getByRole('treeitem', { name: '2.tex' })
|
||||
|
||||
// click on item 1: it gets selected
|
||||
fireEvent.click(treeitem1)
|
||||
screen.getByRole('treeitem', { name: '1.tex', selected: true })
|
||||
screen.getByRole('treeitem', { name: '2.tex', selected: false })
|
||||
|
||||
// meta-click on item 2: no changes
|
||||
fireEvent.click(treeitem2, { ctrlKey: true })
|
||||
screen.getByRole('treeitem', { name: '1.tex', selected: true })
|
||||
screen.getByRole('treeitem', { name: '2.tex', selected: false })
|
||||
})
|
||||
|
||||
it('with write permissions', function() {
|
||||
const docs = [
|
||||
{ _id: '1', name: '1.tex' },
|
||||
{ _id: '2', name: '2.tex' },
|
||||
{ _id: '3', name: '3.tex' }
|
||||
]
|
||||
renderWithContext(
|
||||
<FileTreeFolderList folders={[]} docs={docs} files={[]} />,
|
||||
{ contextProps: { initialSelectedEntityId: '1' } }
|
||||
)
|
||||
|
||||
const treeitem1 = screen.getByRole('treeitem', { name: '1.tex' })
|
||||
const treeitem2 = screen.getByRole('treeitem', { name: '2.tex' })
|
||||
const treeitem3 = screen.getByRole('treeitem', { name: '3.tex' })
|
||||
|
||||
// item 1 i selected by default
|
||||
screen.getByRole('treeitem', { name: '1.tex', selected: true })
|
||||
screen.getByRole('treeitem', { name: '2.tex', selected: false })
|
||||
screen.getByRole('treeitem', { name: '3.tex', selected: false })
|
||||
|
||||
// click on item 2: it gets selected and item 1 is not selected anymore
|
||||
fireEvent.click(treeitem2)
|
||||
screen.getByRole('treeitem', { name: '1.tex', selected: false })
|
||||
screen.getByRole('treeitem', { name: '2.tex', selected: true })
|
||||
screen.getByRole('treeitem', { name: '3.tex', selected: false })
|
||||
|
||||
// meta-click on item 3: it gets selected and item 2 as well
|
||||
fireEvent.click(treeitem3, { ctrlKey: true })
|
||||
screen.getByRole('treeitem', { name: '1.tex', selected: false })
|
||||
screen.getByRole('treeitem', { name: '2.tex', selected: true })
|
||||
screen.getByRole('treeitem', { name: '3.tex', selected: true })
|
||||
|
||||
// meta-click on item 1: add to selection
|
||||
fireEvent.click(treeitem1, { ctrlKey: true })
|
||||
screen.getByRole('treeitem', { name: '1.tex', selected: true })
|
||||
screen.getByRole('treeitem', { name: '2.tex', selected: true })
|
||||
screen.getByRole('treeitem', { name: '3.tex', selected: true })
|
||||
|
||||
// meta-click on item 1: remove from selection
|
||||
fireEvent.click(treeitem1, { ctrlKey: true })
|
||||
screen.getByRole('treeitem', { name: '1.tex', selected: false })
|
||||
screen.getByRole('treeitem', { name: '2.tex', selected: true })
|
||||
screen.getByRole('treeitem', { name: '3.tex', selected: true })
|
||||
|
||||
// meta-click on item 3: remove from selection
|
||||
fireEvent.click(treeitem3, { ctrlKey: true })
|
||||
screen.getByRole('treeitem', { name: '1.tex', selected: false })
|
||||
screen.getByRole('treeitem', { name: '2.tex', selected: true })
|
||||
screen.getByRole('treeitem', { name: '3.tex', selected: false })
|
||||
|
||||
// meta-click on item 2: cannot unselect
|
||||
fireEvent.click(treeitem2, { ctrlKey: true })
|
||||
screen.getByRole('treeitem', { name: '1.tex', selected: false })
|
||||
screen.getByRole('treeitem', { name: '2.tex', selected: true })
|
||||
screen.getByRole('treeitem', { name: '3.tex', selected: false })
|
||||
|
||||
// meta-click on item 3: add back to selection
|
||||
fireEvent.click(treeitem3, { ctrlKey: true })
|
||||
screen.getByRole('treeitem', { name: '1.tex', selected: false })
|
||||
screen.getByRole('treeitem', { name: '2.tex', selected: true })
|
||||
screen.getByRole('treeitem', { name: '3.tex', selected: true })
|
||||
|
||||
// click on item 3: unselect other items
|
||||
fireEvent.click(treeitem3)
|
||||
screen.getByRole('treeitem', { name: '1.tex', selected: false })
|
||||
screen.getByRole('treeitem', { name: '2.tex', selected: false })
|
||||
screen.getByRole('treeitem', { name: '3.tex', selected: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,66 @@
|
||||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import { screen, fireEvent } from '@testing-library/react'
|
||||
import renderWithContext from '../helpers/render-with-context'
|
||||
|
||||
import FileTreeFolder from '../../../../../frontend/js/features/file-tree/components/file-tree-folder'
|
||||
|
||||
describe('<FileTreeFolder/>', function() {
|
||||
it('renders unselected', function() {
|
||||
renderWithContext(
|
||||
<FileTreeFolder
|
||||
name="foo"
|
||||
id="123abc"
|
||||
folders={[]}
|
||||
docs={[]}
|
||||
files={[]}
|
||||
/>
|
||||
)
|
||||
|
||||
screen.getByRole('treeitem', { selected: false })
|
||||
expect(screen.queryByRole('tree')).to.not.exist
|
||||
})
|
||||
|
||||
it('renders selected', function() {
|
||||
renderWithContext(
|
||||
<FileTreeFolder
|
||||
name="foo"
|
||||
id="123abc"
|
||||
folders={[]}
|
||||
docs={[]}
|
||||
files={[]}
|
||||
/>
|
||||
)
|
||||
|
||||
const treeitem = screen.getByRole('treeitem', { selected: false })
|
||||
fireEvent.click(treeitem)
|
||||
|
||||
screen.getByRole('treeitem', { selected: true })
|
||||
screen.getByRole('menuitem', { name: 'Rename' })
|
||||
screen.getByRole('menuitem', { name: 'Delete' })
|
||||
screen.getByRole('menuitem', { name: 'New File' })
|
||||
screen.getByRole('menuitem', { name: 'New Folder' })
|
||||
screen.getByRole('menuitem', { name: 'Upload' })
|
||||
|
||||
screen.getByRole('treeitem', { selected: true })
|
||||
expect(screen.queryByRole('tree')).to.not.exist
|
||||
})
|
||||
|
||||
it('expands', function() {
|
||||
renderWithContext(
|
||||
<FileTreeFolder
|
||||
name="foo"
|
||||
id="123abc"
|
||||
folders={[]}
|
||||
docs={[]}
|
||||
files={[]}
|
||||
/>
|
||||
)
|
||||
|
||||
screen.getByRole('treeitem')
|
||||
const expandButton = screen.getByRole('button', { name: 'Expand' })
|
||||
|
||||
fireEvent.click(expandButton)
|
||||
screen.getByRole('tree')
|
||||
})
|
||||
})
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import sinon from 'sinon'
|
||||
import { screen, fireEvent } from '@testing-library/react'
|
||||
import renderWithContext from '../../helpers/render-with-context'
|
||||
|
||||
import FileTreeitemInner from '../../../../../../frontend/js/features/file-tree/components/file-tree-item/file-tree-item-inner'
|
||||
|
||||
describe('<FileTreeitemInner />', function() {
|
||||
const setContextMenuCoords = sinon.stub()
|
||||
|
||||
beforeEach(function() {
|
||||
global.requestAnimationFrame = sinon.stub()
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
setContextMenuCoords.reset()
|
||||
delete global.requestAnimationFrame
|
||||
})
|
||||
|
||||
describe('menu', function() {
|
||||
it('does not display if file is not selected', function() {
|
||||
renderWithContext(
|
||||
<FileTreeitemInner id="123abc" name="bar.tex" isSelected={false} />,
|
||||
{}
|
||||
)
|
||||
|
||||
expect(screen.queryByRole('menu', { visible: false })).to.not.exist
|
||||
})
|
||||
})
|
||||
|
||||
describe('context menu', function() {
|
||||
it('does not display without write permissions', function() {
|
||||
renderWithContext(
|
||||
<FileTreeitemInner id="123abc" name="bar.tex" isSelected />,
|
||||
{ contextProps: { hasWritePermissions: false } }
|
||||
)
|
||||
|
||||
expect(screen.queryByRole('menu', { visible: false })).to.not.exist
|
||||
})
|
||||
|
||||
it('open / close', function() {
|
||||
const { container } = renderWithContext(
|
||||
<FileTreeitemInner id="123abc" name="bar.tex" isSelected />
|
||||
)
|
||||
|
||||
const entityElement = container.querySelector('div.entity')
|
||||
|
||||
screen.getByRole('menu', { visible: false })
|
||||
|
||||
fireEvent.contextMenu(entityElement)
|
||||
screen.getByRole('menu', { visible: true })
|
||||
|
||||
fireEvent.contextMenu(entityElement)
|
||||
screen.getByRole('menu', { visible: false })
|
||||
})
|
||||
})
|
||||
|
||||
describe('name', function() {
|
||||
it('renders name', function() {
|
||||
renderWithContext(
|
||||
<FileTreeitemInner id="123abc" name="bar.tex" isSelected />
|
||||
)
|
||||
|
||||
screen.getByRole('button', { name: 'bar.tex' })
|
||||
expect(screen.queryByRole('textbox')).to.not.exist
|
||||
})
|
||||
|
||||
it('starts rename on menu item click', function() {
|
||||
renderWithContext(
|
||||
<FileTreeitemInner id="123abc" name="bar.tex" isSelected />,
|
||||
{ contextProps: { initialSelectedEntityId: '123abc' } }
|
||||
)
|
||||
|
||||
const renameButton = screen.getByRole('menuitem', { name: 'Rename' })
|
||||
fireEvent.click(renameButton)
|
||||
expect(screen.queryByRole('button', { name: 'bar.tex' })).to.not.exist
|
||||
screen.getByRole('textbox')
|
||||
})
|
||||
})
|
||||
})
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import React from 'react'
|
||||
import sinon from 'sinon'
|
||||
import { screen, fireEvent } from '@testing-library/react'
|
||||
import renderWithContext from '../../helpers/render-with-context'
|
||||
|
||||
import FileTreeitemMenu from '../../../../../../frontend/js/features/file-tree/components/file-tree-item/file-tree-item-menu'
|
||||
|
||||
describe('<FileTreeitemMenu />', function() {
|
||||
const setContextMenuCoords = sinon.stub()
|
||||
|
||||
afterEach(function() {
|
||||
setContextMenuCoords.reset()
|
||||
})
|
||||
|
||||
it('renders dropdown', function() {
|
||||
renderWithContext(
|
||||
<FileTreeitemMenu
|
||||
id="123abc"
|
||||
setContextMenuCoords={setContextMenuCoords}
|
||||
/>
|
||||
)
|
||||
|
||||
screen.getByRole('button', { name: 'Menu' })
|
||||
screen.getByRole('menu')
|
||||
})
|
||||
|
||||
it('open / close', function() {
|
||||
renderWithContext(
|
||||
<FileTreeitemMenu
|
||||
id="123abc"
|
||||
setContextMenuCoords={setContextMenuCoords}
|
||||
/>
|
||||
)
|
||||
|
||||
const toggleButton = screen.getByRole('button', { name: 'Menu' })
|
||||
|
||||
screen.getByRole('menu', { visible: false })
|
||||
|
||||
fireEvent.click(toggleButton)
|
||||
screen.getByRole('menu', { visible: true })
|
||||
|
||||
fireEvent.click(toggleButton)
|
||||
screen.getByRole('menu', { visible: false })
|
||||
})
|
||||
})
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import sinon from 'sinon'
|
||||
import { screen, fireEvent } from '@testing-library/react'
|
||||
import renderWithContext from '../../helpers/render-with-context'
|
||||
|
||||
import FileTreeItemName from '../../../../../../frontend/js/features/file-tree/components/file-tree-item/file-tree-item-name'
|
||||
|
||||
describe('<FileTreeItemName />', function() {
|
||||
beforeEach(function() {
|
||||
global.requestAnimationFrame = sinon.stub()
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
delete global.requestAnimationFrame
|
||||
})
|
||||
|
||||
it('renders name as button', function() {
|
||||
renderWithContext(<FileTreeItemName name="foo.tex" isSelected />)
|
||||
|
||||
screen.getByRole('button', { name: 'foo.tex' })
|
||||
expect(screen.queryByRole('textbox')).to.not.exist
|
||||
})
|
||||
|
||||
it("doesn't start renaming on unselected component", function() {
|
||||
renderWithContext(<FileTreeItemName name="foo.tex" isSelected={false} />)
|
||||
|
||||
const button = screen.queryByRole('button')
|
||||
fireEvent.click(button)
|
||||
fireEvent.click(button)
|
||||
fireEvent.doubleClick(button)
|
||||
expect(screen.queryByRole('textbox')).to.not.exist
|
||||
})
|
||||
|
||||
it('start renaming on double-click', function() {
|
||||
renderWithContext(<FileTreeItemName name="foo.tex" isSelected />)
|
||||
|
||||
const button = screen.queryByRole('button')
|
||||
fireEvent.click(button)
|
||||
fireEvent.click(button)
|
||||
fireEvent.doubleClick(button)
|
||||
screen.getByRole('textbox')
|
||||
expect(screen.queryByRole('button')).to.not.exist
|
||||
expect(global.requestAnimationFrame).to.be.calledOnce
|
||||
})
|
||||
|
||||
it('cannot start renaming in read-only', function() {
|
||||
renderWithContext(<FileTreeItemName name="foo.tex" isSelected />, {
|
||||
contextProps: { hasWritePermissions: false }
|
||||
})
|
||||
|
||||
const button = screen.queryByRole('button')
|
||||
fireEvent.click(button)
|
||||
fireEvent.click(button)
|
||||
fireEvent.doubleClick(button)
|
||||
|
||||
expect(screen.queryByRole('textbox')).to.not.exist
|
||||
})
|
||||
|
||||
describe('stop renaming', function() {
|
||||
beforeEach(function() {
|
||||
renderWithContext(<FileTreeItemName name="foo.tex" isSelected />)
|
||||
|
||||
const button = screen.getByRole('button')
|
||||
fireEvent.click(button)
|
||||
fireEvent.click(button)
|
||||
fireEvent.doubleClick(button)
|
||||
|
||||
const input = screen.getByRole('textbox')
|
||||
fireEvent.change(input, { target: { value: 'bar.tex' } })
|
||||
})
|
||||
|
||||
it('on Escape', function() {
|
||||
const input = screen.getByRole('textbox')
|
||||
fireEvent.keyDown(input, { key: 'Escape' })
|
||||
|
||||
screen.getByRole('button', { name: 'foo.tex' })
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,123 @@
|
||||
import React from 'react'
|
||||
import sinon from 'sinon'
|
||||
import { screen, render, fireEvent } from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
|
||||
import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
|
||||
|
||||
describe('<FileTreeRoot/>', function() {
|
||||
const onSelect = sinon.stub()
|
||||
const onInit = sinon.stub()
|
||||
|
||||
beforeEach(function() {
|
||||
global.requestAnimationFrame = sinon.stub()
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
delete global.requestAnimationFrame
|
||||
fetchMock.restore()
|
||||
onSelect.reset()
|
||||
onInit.reset()
|
||||
})
|
||||
|
||||
it('renders', function() {
|
||||
const rootFolder = [
|
||||
{
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: []
|
||||
}
|
||||
]
|
||||
render(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId={'123abc'}
|
||||
hasWritePermissions={false}
|
||||
rootDocId="456def"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
/>
|
||||
)
|
||||
|
||||
screen.queryByRole('tree')
|
||||
screen.getByRole('treeitem')
|
||||
screen.getByRole('treeitem', { name: 'main.tex', selected: true })
|
||||
})
|
||||
|
||||
it('fire onSelect', function() {
|
||||
const rootFolder = [
|
||||
{
|
||||
docs: [
|
||||
{ _id: '456def', name: 'main.tex' },
|
||||
{ _id: '789ghi', name: 'other.tex' }
|
||||
],
|
||||
folders: [],
|
||||
fileRefs: []
|
||||
}
|
||||
]
|
||||
render(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
hasWritePermissions={false}
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
/>
|
||||
)
|
||||
sinon.assert.calledOnce(onSelect)
|
||||
sinon.assert.calledWithMatch(onSelect, [
|
||||
sinon.match({
|
||||
entity: {
|
||||
_id: '456def',
|
||||
name: 'main.tex'
|
||||
}
|
||||
})
|
||||
])
|
||||
onSelect.reset()
|
||||
|
||||
screen.queryByRole('tree')
|
||||
const treeitem = screen.getByRole('treeitem', { name: 'other.tex' })
|
||||
fireEvent.click(treeitem)
|
||||
sinon.assert.calledOnce(onSelect)
|
||||
sinon.assert.calledWithMatch(onSelect, [
|
||||
sinon.match({
|
||||
entity: {
|
||||
_id: '789ghi',
|
||||
name: 'other.tex'
|
||||
}
|
||||
})
|
||||
])
|
||||
})
|
||||
|
||||
it('listen to editor.openDoc', function() {
|
||||
const rootFolder = [
|
||||
{
|
||||
docs: [
|
||||
{ _id: '456def', name: 'main.tex' },
|
||||
{ _id: '789ghi', name: 'other.tex' }
|
||||
],
|
||||
folders: [],
|
||||
fileRefs: []
|
||||
}
|
||||
]
|
||||
render(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
rootDocId="456def"
|
||||
hasWritePermissions={false}
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
/>
|
||||
)
|
||||
|
||||
screen.getByRole('treeitem', { name: 'main.tex', selected: true })
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('editor.openDoc', { detail: '789ghi' })
|
||||
)
|
||||
screen.getByRole('treeitem', { name: 'main.tex', selected: false })
|
||||
screen.getByRole('treeitem', { name: 'other.tex', selected: true })
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,35 @@
|
||||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import { screen } from '@testing-library/react'
|
||||
import renderWithContext from '../helpers/render-with-context'
|
||||
|
||||
import FileTreeToolbar from '../../../../../frontend/js/features/file-tree/components/file-tree-toolbar'
|
||||
|
||||
describe('<FileTreeToolbar/>', function() {
|
||||
it('without selected files', function() {
|
||||
renderWithContext(<FileTreeToolbar />)
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'New File' })).to.not.exist
|
||||
expect(screen.queryByRole('button', { name: 'New Folder' })).to.not.exist
|
||||
expect(screen.queryByRole('button', { name: 'Upload' })).to.not.exist
|
||||
expect(screen.queryByRole('button', { name: 'Rename' })).to.not.exist
|
||||
expect(screen.queryByRole('button', { name: 'Delete' })).to.not.exist
|
||||
})
|
||||
|
||||
it('read-only', function() {
|
||||
renderWithContext(<FileTreeToolbar />, {
|
||||
contextProps: { hasWritePermissions: false }
|
||||
})
|
||||
|
||||
expect(screen.queryByRole('button')).to.not.exist
|
||||
})
|
||||
|
||||
it('with one selected file', function() {
|
||||
renderWithContext(<FileTreeToolbar />, {
|
||||
contextProps: { initialSelectedEntityId: '123abc' }
|
||||
})
|
||||
|
||||
screen.getByRole('button', { name: 'Rename' })
|
||||
screen.getByRole('button', { name: 'Delete' })
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import sinon from 'sinon'
|
||||
import { screen, render, fireEvent } from '@testing-library/react'
|
||||
|
||||
import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
|
||||
|
||||
describe('FileTree Context Menu Flow', function() {
|
||||
const onSelect = sinon.stub()
|
||||
const onInit = sinon.stub()
|
||||
|
||||
it('opens on contextMenu event', async function() {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: []
|
||||
}
|
||||
]
|
||||
render(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
rootDocId="456def"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
/>
|
||||
)
|
||||
const treeitem = screen.getByRole('button', { name: 'main.tex' })
|
||||
|
||||
expect(screen.getAllByRole('menu').length).to.equal(1) // toolbar
|
||||
|
||||
fireEvent.contextMenu(treeitem)
|
||||
|
||||
expect(screen.getAllByRole('menu').length).to.equal(2) // toolbar + menu
|
||||
})
|
||||
|
||||
it("doesn't open in read only mode", async function() {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: []
|
||||
}
|
||||
]
|
||||
render(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions={false}
|
||||
rootDocId="456def"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
/>
|
||||
)
|
||||
const treeitem = screen.getByRole('button', { name: 'main.tex' })
|
||||
|
||||
fireEvent.contextMenu(treeitem)
|
||||
|
||||
expect(screen.queryByRole('menu')).to.not.exist
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,231 @@
|
||||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import sinon from 'sinon'
|
||||
import { screen, render, fireEvent } from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import MockedSocket from 'socket.io-mock'
|
||||
|
||||
import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
|
||||
|
||||
describe('FileTree Create Folder Flow', function() {
|
||||
const onSelect = sinon.stub()
|
||||
const onInit = sinon.stub()
|
||||
|
||||
beforeEach(function() {
|
||||
global.requestAnimationFrame = sinon.stub()
|
||||
window._ide = {
|
||||
socket: new MockedSocket()
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
delete global.requestAnimationFrame
|
||||
fetchMock.restore()
|
||||
onSelect.reset()
|
||||
onInit.reset()
|
||||
delete window._ide
|
||||
})
|
||||
|
||||
it('add to root', async function() {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: []
|
||||
}
|
||||
]
|
||||
render(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
rootDocId="456def"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
/>
|
||||
)
|
||||
|
||||
const newFolderName = 'Foo Bar In Root'
|
||||
const matcher = /\/project\/\w+\/folder/
|
||||
const response = {
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
docs: [],
|
||||
_id: fakeId(),
|
||||
name: newFolderName
|
||||
}
|
||||
fetchMock.post(matcher, response)
|
||||
|
||||
fireCreateFolder(newFolderName)
|
||||
|
||||
const lastCallBody = JSON.parse(fetchMock.lastCall(matcher)[1].body)
|
||||
expect(lastCallBody.name).to.equal(newFolderName)
|
||||
expect(lastCallBody.parent_folder_id).to.equal('root-folder-id')
|
||||
|
||||
window._ide.socket.socketClient.emit('reciveNewFolder', 'root-folder-id', {
|
||||
_id: fakeId(),
|
||||
name: newFolderName,
|
||||
docs: [],
|
||||
fileRefs: [],
|
||||
folders: []
|
||||
})
|
||||
await screen.findByRole('treeitem', { name: newFolderName })
|
||||
})
|
||||
|
||||
it('add to folder from folder', async function() {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
docs: [],
|
||||
folders: [
|
||||
{
|
||||
_id: '789ghi',
|
||||
name: 'thefolder',
|
||||
docs: [],
|
||||
fileRefs: [],
|
||||
folders: []
|
||||
}
|
||||
],
|
||||
fileRefs: []
|
||||
}
|
||||
]
|
||||
render(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
rootDocId="789ghi"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
/>
|
||||
)
|
||||
|
||||
const expandButton = screen.getByRole('button', { name: 'Expand' })
|
||||
fireEvent.click(expandButton)
|
||||
|
||||
const newFolderName = 'Foo Bar In thefolder'
|
||||
const matcher = /\/project\/\w+\/folder/
|
||||
const response = {
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
docs: [],
|
||||
_id: fakeId(),
|
||||
name: newFolderName
|
||||
}
|
||||
fetchMock.post(matcher, response)
|
||||
|
||||
fireCreateFolder(newFolderName)
|
||||
|
||||
const lastCallBody = JSON.parse(fetchMock.lastCall(matcher)[1].body)
|
||||
expect(lastCallBody.name).to.equal(newFolderName)
|
||||
expect(lastCallBody.parent_folder_id).to.equal('789ghi')
|
||||
|
||||
window._ide.socket.socketClient.emit('reciveNewFolder', '789ghi', {
|
||||
_id: fakeId(),
|
||||
name: newFolderName,
|
||||
docs: [],
|
||||
fileRefs: [],
|
||||
folders: []
|
||||
})
|
||||
|
||||
// find the created folder
|
||||
await screen.findByRole('treeitem', { name: newFolderName })
|
||||
|
||||
// collapse the parent folder; created folder should not be rendered anymore
|
||||
fireEvent.click(expandButton)
|
||||
expect(screen.queryByRole('treeitem', { name: newFolderName })).to.not.exist
|
||||
})
|
||||
|
||||
it('add to folder from child', async function() {
|
||||
const rootFolder = [
|
||||
{
|
||||
_id: 'root-folder-id',
|
||||
docs: [],
|
||||
folders: [
|
||||
{
|
||||
_id: '789ghi',
|
||||
name: 'thefolder',
|
||||
docs: [],
|
||||
fileRefs: [{ _id: '456def', name: 'sub.tex' }],
|
||||
folders: []
|
||||
}
|
||||
],
|
||||
fileRefs: []
|
||||
}
|
||||
]
|
||||
render(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId="123abc"
|
||||
hasWritePermissions
|
||||
rootDocId="456def"
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
/>
|
||||
)
|
||||
|
||||
const expandButton = screen.getByRole('button', { name: 'Expand' })
|
||||
fireEvent.click(expandButton)
|
||||
|
||||
const newFolderName = 'Foo Bar In thefolder'
|
||||
const matcher = /\/project\/\w+\/folder/
|
||||
const response = {
|
||||
folders: [],
|
||||
fileRefs: [],
|
||||
docs: [],
|
||||
_id: fakeId(),
|
||||
name: newFolderName
|
||||
}
|
||||
fetchMock.post(matcher, response)
|
||||
|
||||
fireCreateFolder(newFolderName)
|
||||
|
||||
const lastCallBody = JSON.parse(fetchMock.lastCall(matcher)[1].body)
|
||||
expect(lastCallBody.name).to.equal(newFolderName)
|
||||
expect(lastCallBody.parent_folder_id).to.equal('789ghi')
|
||||
|
||||
window._ide.socket.socketClient.emit('reciveNewFolder', '789ghi', {
|
||||
_id: fakeId(),
|
||||
name: newFolderName,
|
||||
docs: [],
|
||||
fileRefs: [],
|
||||
folders: []
|
||||
})
|
||||
|
||||
// find the created folder
|
||||
await screen.findByRole('treeitem', { name: newFolderName })
|
||||
|
||||
// collapse the parent folder; created folder should not be rendered anymore
|
||||
fireEvent.click(expandButton)
|
||||
expect(screen.queryByRole('treeitem', { name: newFolderName })).to.not.exist
|
||||
})
|
||||
|
||||
function fireCreateFolder(name) {
|
||||
const createFolderButton = screen.getByRole('button', {
|
||||
name: 'New Folder'
|
||||
})
|
||||
fireEvent.click(createFolderButton)
|
||||
|
||||
const input = screen.getByRole('textbox', {
|
||||
hidden: true // FIXME: modal should not be hidden but it has the aria-hidden label due to a react-bootstrap bug
|
||||
})
|
||||
fireEvent.change(input, { target: { value: name } })
|
||||
|
||||
const modalCreateButton = getModalCreateButton()
|
||||
fireEvent.click(modalCreateButton)
|
||||
}
|
||||
|
||||
function fakeId() {
|
||||
return Math.random()
|
||||
.toString(16)
|
||||
.replace(/0\./, 'random-test-id-')
|
||||
}
|
||||
|
||||
function getModalCreateButton() {
|
||||
return screen.getAllByRole('button', {
|
||||
name: 'Create',
|
||||
hidden: true // FIXME: modal should not be hidden but it has the aria-hidden label due to a react-bootstrap bug
|
||||
})[0] // the first matched button is the toolbar button
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,188 @@
|
||||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import sinon from 'sinon'
|
||||
import { screen, render, fireEvent, waitFor } from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import MockedSocket from 'socket.io-mock'
|
||||
|
||||
import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
|
||||
|
||||
describe('FileTree Delete Entity Flow', function() {
|
||||
const onSelect = sinon.stub()
|
||||
const onInit = sinon.stub()
|
||||
|
||||
beforeEach(function() {
|
||||
window._ide = {
|
||||
socket: new MockedSocket()
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
fetchMock.restore()
|
||||
onSelect.reset()
|
||||
onInit.reset()
|
||||
delete window._ide
|
||||
})
|
||||
|
||||
describe('single entity', function() {
|
||||
beforeEach(function() {
|
||||
const rootFolder = [
|
||||
{
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: []
|
||||
}
|
||||
]
|
||||
render(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId={'123abc'}
|
||||
hasWritePermissions
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
/>
|
||||
)
|
||||
|
||||
const treeitem = screen.getByRole('treeitem', { name: 'main.tex' })
|
||||
fireEvent.click(treeitem)
|
||||
|
||||
const deleteButton = screen.getByRole('menuitem', { name: 'Delete' })
|
||||
fireEvent.click(deleteButton)
|
||||
})
|
||||
|
||||
it('removes item', async function() {
|
||||
const fetchMatcher = /\/project\/\w+\/doc\/\w+/
|
||||
fetchMock.delete(fetchMatcher, 204)
|
||||
|
||||
const modalDeleteButton = getModalDeleteButton()
|
||||
fireEvent.click(modalDeleteButton)
|
||||
|
||||
window._ide.socket.socketClient.emit('removeEntity', '456def')
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByRole('treeitem', {
|
||||
name: 'main.tex',
|
||||
hidden: true // treeitem might be hidden behind the modal
|
||||
})
|
||||
).to.not.exist
|
||||
|
||||
expect(
|
||||
screen.queryByRole('treeitem', {
|
||||
name: 'main.tex'
|
||||
})
|
||||
).to.not.exist
|
||||
})
|
||||
|
||||
const [lastFetchPath] = fetchMock.lastCall(fetchMatcher)
|
||||
expect(lastFetchPath).to.equal('/project/123abc/doc/456def')
|
||||
})
|
||||
|
||||
it('continues delete on 404s', async function() {
|
||||
fetchMock.delete(/\/project\/\w+\/doc\/\w+/, 404)
|
||||
|
||||
const modalDeleteButton = getModalDeleteButton()
|
||||
fireEvent.click(modalDeleteButton)
|
||||
|
||||
window._ide.socket.socketClient.emit('removeEntity', '456def')
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByRole('treeitem', {
|
||||
name: 'main.tex',
|
||||
hidden: true // treeitem might be hidden behind the modal
|
||||
})
|
||||
).to.not.exist
|
||||
|
||||
expect(
|
||||
screen.queryByRole('treeitem', {
|
||||
name: 'main.tex'
|
||||
})
|
||||
).to.not.exist
|
||||
})
|
||||
})
|
||||
|
||||
it('aborts delete on error', async function() {
|
||||
const fetchMatcher = /\/project\/\w+\/doc\/\w+/
|
||||
fetchMock.delete(fetchMatcher, 500)
|
||||
|
||||
const modalDeleteButton = getModalDeleteButton()
|
||||
fireEvent.click(modalDeleteButton)
|
||||
|
||||
// The modal should still be open, but the file should not be deleted
|
||||
await screen.findByRole('treeitem', { name: 'main.tex', hidden: true })
|
||||
})
|
||||
})
|
||||
|
||||
describe('multiple entities', function() {
|
||||
beforeEach(function() {
|
||||
const rootFolder = [
|
||||
{
|
||||
docs: [{ _id: '456def', name: 'main.tex' }],
|
||||
folders: [],
|
||||
fileRefs: [{ _id: '789ghi', name: 'my.bib' }]
|
||||
}
|
||||
]
|
||||
render(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId={'123abc'}
|
||||
hasWritePermissions
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
/>
|
||||
)
|
||||
|
||||
const treeitemDoc = screen.getByRole('treeitem', { name: 'main.tex' })
|
||||
fireEvent.click(treeitemDoc)
|
||||
const treeitemFile = screen.getByRole('treeitem', { name: 'my.bib' })
|
||||
fireEvent.click(treeitemFile, { ctrlKey: true })
|
||||
|
||||
const deleteButton = screen.getAllByRole('menuitem', {
|
||||
name: 'Delete'
|
||||
})[0]
|
||||
fireEvent.click(deleteButton)
|
||||
})
|
||||
|
||||
it('removes all items', async function() {
|
||||
const fetchMatcher = /\/project\/\w+\/(doc|file)\/\w+/
|
||||
fetchMock.delete(fetchMatcher, 204)
|
||||
|
||||
const modalDeleteButton = getModalDeleteButton()
|
||||
fireEvent.click(modalDeleteButton)
|
||||
|
||||
window._ide.socket.socketClient.emit('removeEntity', '456def')
|
||||
window._ide.socket.socketClient.emit('removeEntity', '789ghi')
|
||||
|
||||
await waitFor(() => {
|
||||
for (const name of ['main.tex', 'my.bib']) {
|
||||
expect(
|
||||
screen.queryByRole('treeitem', {
|
||||
name,
|
||||
hidden: true // treeitem might be hidden behind the modal
|
||||
})
|
||||
).to.not.exist
|
||||
|
||||
expect(
|
||||
screen.queryByRole('treeitem', {
|
||||
name
|
||||
})
|
||||
).to.not.exist
|
||||
}
|
||||
})
|
||||
|
||||
const [firstFetchPath, secondFetchPath] = fetchMock
|
||||
.calls()
|
||||
.map(([url]) => url)
|
||||
expect(firstFetchPath).to.equal('/project/123abc/doc/456def')
|
||||
expect(secondFetchPath).to.equal('/project/123abc/file/789ghi')
|
||||
})
|
||||
})
|
||||
|
||||
function getModalDeleteButton() {
|
||||
return screen.getAllByRole('button', {
|
||||
name: 'Delete',
|
||||
hidden: true // FIXME: modal should not be hidden but it has the aria-hidden label due to a react-bootstrap bug
|
||||
})[1] // the first matched button is the toolbar button
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,140 @@
|
||||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import sinon from 'sinon'
|
||||
import { screen, render, fireEvent } from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import MockedSocket from 'socket.io-mock'
|
||||
|
||||
import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
|
||||
|
||||
describe('FileTree Rename Entity Flow', function() {
|
||||
const onSelect = sinon.stub()
|
||||
const onInit = sinon.stub()
|
||||
|
||||
beforeEach(function() {
|
||||
window._ide = {
|
||||
socket: new MockedSocket()
|
||||
}
|
||||
global.requestAnimationFrame = sinon.stub()
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
delete global.requestAnimationFrame
|
||||
fetchMock.restore()
|
||||
onSelect.reset()
|
||||
onInit.reset()
|
||||
delete window._ide
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
const rootFolder = [
|
||||
{
|
||||
docs: [{ _id: '456def', name: 'a.tex' }],
|
||||
folders: [
|
||||
{
|
||||
_id: '987jkl',
|
||||
name: 'folder',
|
||||
docs: [],
|
||||
fileRefs: [{ _id: '789ghi', name: 'c.tex' }],
|
||||
folders: []
|
||||
}
|
||||
],
|
||||
fileRefs: []
|
||||
}
|
||||
]
|
||||
render(
|
||||
<FileTreeRoot
|
||||
rootFolder={rootFolder}
|
||||
projectId={'123abc'}
|
||||
hasWritePermissions
|
||||
onSelect={onSelect}
|
||||
onInit={onInit}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
it('renames doc', function() {
|
||||
const fetchMatcher = /\/project\/\w+\/doc\/\w+\/rename/
|
||||
fetchMock.post(fetchMatcher, 204)
|
||||
|
||||
const input = initItemRename('a.tex')
|
||||
fireEvent.change(input, { target: { value: 'b.tex' } })
|
||||
fireEvent.keyDown(input, { key: 'Enter' })
|
||||
|
||||
screen.getByRole('treeitem', { name: 'b.tex' })
|
||||
|
||||
const lastFetchBody = getLastFetchBody(fetchMatcher)
|
||||
expect(lastFetchBody.name).to.equal('b.tex')
|
||||
})
|
||||
|
||||
it('renames folder', function() {
|
||||
const fetchMatcher = /\/project\/\w+\/folder\/\w+\/rename/
|
||||
fetchMock.post(fetchMatcher, 204)
|
||||
|
||||
const input = initItemRename('folder')
|
||||
fireEvent.change(input, { target: { value: 'new folder name' } })
|
||||
fireEvent.keyDown(input, { key: 'Enter' })
|
||||
|
||||
screen.getByRole('treeitem', { name: 'new folder name' })
|
||||
|
||||
const lastFetchBody = getLastFetchBody(fetchMatcher)
|
||||
expect(lastFetchBody.name).to.equal('new folder name')
|
||||
})
|
||||
|
||||
it('renames file in subfolder', function() {
|
||||
const fetchMatcher = /\/project\/\w+\/file\/\w+\/rename/
|
||||
fetchMock.post(fetchMatcher, 204)
|
||||
|
||||
const expandButton = screen.getByRole('button', { name: 'Expand' })
|
||||
fireEvent.click(expandButton)
|
||||
|
||||
const input = initItemRename('c.tex')
|
||||
fireEvent.change(input, { target: { value: 'd.tex' } })
|
||||
fireEvent.keyDown(input, { key: 'Enter' })
|
||||
|
||||
screen.getByRole('treeitem', { name: 'folder' })
|
||||
screen.getByRole('treeitem', { name: 'd.tex' })
|
||||
|
||||
const lastFetchBody = getLastFetchBody(fetchMatcher)
|
||||
expect(lastFetchBody.name).to.equal('d.tex')
|
||||
})
|
||||
|
||||
it('reverts rename on error', async function() {
|
||||
const fetchMatcher = /\/project\/\w+\/doc\/\w+\/rename/
|
||||
fetchMock.post(fetchMatcher, 500)
|
||||
|
||||
const input = initItemRename('a.tex')
|
||||
fireEvent.change(input, { target: { value: 'b.tex' } })
|
||||
fireEvent.keyDown(input, { key: 'Enter' })
|
||||
|
||||
screen.getByRole('treeitem', { name: 'b.tex' })
|
||||
})
|
||||
|
||||
describe('via socket event', function() {
|
||||
it('renames doc', function() {
|
||||
screen.getByRole('treeitem', { name: 'a.tex' })
|
||||
|
||||
window._ide.socket.socketClient.emit(
|
||||
'reciveEntityRename',
|
||||
'456def',
|
||||
'socket.tex'
|
||||
)
|
||||
|
||||
screen.getByRole('treeitem', { name: 'socket.tex' })
|
||||
})
|
||||
})
|
||||
|
||||
function initItemRename(treeitemName) {
|
||||
const treeitem = screen.getByRole('treeitem', { name: treeitemName })
|
||||
fireEvent.click(treeitem)
|
||||
|
||||
const renameButton = screen.getByRole('menuitem', { name: 'Rename' })
|
||||
fireEvent.click(renameButton)
|
||||
|
||||
return screen.getByRole('textbox')
|
||||
}
|
||||
function getLastFetchBody(matcher) {
|
||||
const [, { body }] = fetchMock.lastCall(matcher)
|
||||
return JSON.parse(body)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import { render } from '@testing-library/react'
|
||||
import FileTreeContext from '../../../../../frontend/js/features/file-tree/components/file-tree-context'
|
||||
|
||||
export default (children, options = {}) => {
|
||||
let { contextProps = {}, ...renderOptions } = options
|
||||
contextProps = {
|
||||
projectId: '123abc',
|
||||
rootFolder: [{}],
|
||||
hasWritePermissions: true,
|
||||
onSelect: () => {},
|
||||
...contextProps
|
||||
}
|
||||
return render(
|
||||
<FileTreeContext {...contextProps}>{children}</FileTreeContext>,
|
||||
renderOptions
|
||||
)
|
||||
}
|
||||
@@ -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('file')
|
||||
expect(iconTypeFromName('main.png')).to.equal('image')
|
||||
expect(iconTypeFromName('main.csv')).to.equal('table')
|
||||
expect(iconTypeFromName('main.py')).to.equal('file-text')
|
||||
expect(iconTypeFromName('main.bib')).to.equal('book')
|
||||
})
|
||||
|
||||
it('handles missing extensions', function() {
|
||||
expect(iconTypeFromName('main')).to.equal('file')
|
||||
})
|
||||
|
||||
it('lowercases extension', function() {
|
||||
expect(iconTypeFromName('ZOTERO.BIB')).to.equal('book')
|
||||
})
|
||||
})
|
||||
@@ -43,4 +43,16 @@ describe('<Icon />', function() {
|
||||
)
|
||||
expect(element).to.exist
|
||||
})
|
||||
|
||||
it('renders children', function() {
|
||||
const { container } = render(
|
||||
<Icon type="angle-down">
|
||||
<Icon type="angle-up" />
|
||||
</Icon>
|
||||
)
|
||||
const element = container.querySelector(
|
||||
'i.fa.fa-angle-down > i.fa.fa-angle-up'
|
||||
)
|
||||
expect(element).to.exist
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user