Merge pull request #3548 from overleaf/ta-file-tree-init-fix

[ReactFileTree] Fix Initial State When Selected File Doesn't Exist

GitOrigin-RevId: 92ee8573203e66abf26c9b3afab2fccd90ec8c2e
This commit is contained in:
Timothée Alby 2021-01-08 11:03:10 +01:00 committed by Copybot
parent 759fbe8587
commit d121b81896
5 changed files with 78 additions and 8 deletions

View file

@ -82,13 +82,26 @@ export function FileTreeSelectableProvider({
rootDocId rootDocId
) )
const { fileTreeData } = useFileTreeMutable()
const [selectedEntityIds, dispatch] = useReducer( const [selectedEntityIds, dispatch] = useReducer(
hasWritePermissions hasWritePermissions
? fileTreeSelectableReadWriteReducer ? fileTreeSelectableReadWriteReducer
: fileTreeSelectableReadOnlyReducer, : fileTreeSelectableReadOnlyReducer,
initialSelectedEntityId ? new Set([initialSelectedEntityId]) : new Set() null,
() => {
if (!initialSelectedEntityId) return new Set()
// the entity with id=initialSelectedEntityId might not exist in the tree
// anymore. This checks that it exists before initialising the reducer
// with the id.
if (findInTree(fileTreeData, initialSelectedEntityId))
return new Set([initialSelectedEntityId])
// the entity doesn't exist anymore; don't select any files
return new Set()
}
) )
const { fileTreeData } = useFileTreeMutable()
const [selectedEntityParentIds, setSelectedEntityParentIds] = useState( const [selectedEntityParentIds, setSelectedEntityParentIds] = useState(
new Set() new Set()

View file

@ -65,15 +65,15 @@ describe('<FileTreeFolderList/>', function() {
{ _id: '3', name: '3.tex' } { _id: '3', name: '3.tex' }
] ]
renderWithContext( renderWithContext(
<FileTreeFolderList folders={[]} docs={docs} files={[]} />, <FileTreeFolderList folders={[]} docs={docs} files={[]} />
{ contextProps: { rootDocId: '1' } }
) )
const treeitem1 = screen.getByRole('treeitem', { name: '1.tex' }) const treeitem1 = screen.getByRole('treeitem', { name: '1.tex' })
const treeitem2 = screen.getByRole('treeitem', { name: '2.tex' }) const treeitem2 = screen.getByRole('treeitem', { name: '2.tex' })
const treeitem3 = screen.getByRole('treeitem', { name: '3.tex' }) const treeitem3 = screen.getByRole('treeitem', { name: '3.tex' })
// item 1 i selected by default // click item 1: it gets selected
fireEvent.click(treeitem1)
screen.getByRole('treeitem', { name: '1.tex', selected: true }) screen.getByRole('treeitem', { name: '1.tex', selected: true })
screen.getByRole('treeitem', { name: '2.tex', selected: false }) screen.getByRole('treeitem', { name: '2.tex', selected: false })
screen.getByRole('treeitem', { name: '3.tex', selected: false }) screen.getByRole('treeitem', { name: '3.tex', selected: false })

View file

@ -69,7 +69,19 @@ describe('<FileTreeitemInner />', function() {
it('starts rename on menu item click', function() { it('starts rename on menu item click', function() {
renderWithContext( renderWithContext(
<FileTreeitemInner id="123abc" name="bar.tex" isSelected />, <FileTreeitemInner id="123abc" name="bar.tex" isSelected />,
{ contextProps: { rootDocId: '123abc' } } {
contextProps: {
rootDocId: '123abc',
rootFolder: [
{
_id: 'root-folder-id',
docs: [{ _id: '123abc', name: 'bar.tex' }],
folders: [],
fileRefs: []
}
]
}
}
) )
const renameButton = screen.getByRole('menuitem', { name: 'Rename' }) const renameButton = screen.getByRole('menuitem', { name: 'Rename' })

View file

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import sinon from 'sinon' import sinon from 'sinon'
import { screen, render, fireEvent } from '@testing-library/react' import { screen, render, fireEvent, waitFor } from '@testing-library/react'
import fetchMock from 'fetch-mock' import fetchMock from 'fetch-mock'
import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root' import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
@ -18,6 +18,7 @@ describe('<FileTreeRoot/>', function() {
fetchMock.restore() fetchMock.restore()
onSelect.reset() onSelect.reset()
onInit.reset() onInit.reset()
global.localStorage.clear()
}) })
it('renders', function() { it('renders', function() {
@ -45,6 +46,40 @@ describe('<FileTreeRoot/>', function() {
screen.getByRole('treeitem', { name: 'main.tex', selected: true }) screen.getByRole('treeitem', { name: 'main.tex', selected: true })
}) })
it('renders with invalid selected doc in local storage', async function() {
global.localStorage.setItem(
'doc.open_id.123abc',
JSON.stringify('not-a-valid-id')
)
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}
/>
)
// as a proxy to check that the invalid entity ha 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.
const treeitemFile = screen.getByRole('treeitem', { name: 'main.tex' })
fireEvent.click(treeitemFile, { ctrlKey: true })
const deleteButton = screen.getByRole('menuitem', { name: 'Delete' })
fireEvent.click(deleteButton)
await waitFor(() => screen.getByRole('button', { name: 'Cancel' }))
})
it('fire onSelect', function() { it('fire onSelect', function() {
const rootFolder = [ const rootFolder = [
{ {

View file

@ -30,7 +30,17 @@ describe('<FileTreeToolbar/>', function() {
it('with one selected file', function() { it('with one selected file', function() {
renderWithContext(<FileTreeToolbar />, { renderWithContext(<FileTreeToolbar />, {
contextProps: { rootDocId: '123abc' } contextProps: {
rootDocId: '456def',
rootFolder: [
{
_id: 'root-folder-id',
docs: [{ _id: '456def', name: 'main.tex' }],
folders: [],
fileRefs: []
}
]
}
}) })
screen.getByRole('button', { name: 'New File' }) screen.getByRole('button', { name: 'New File' })