From c6cf0608f453846bcc1680a8df681633b3b2b174 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 4 Apr 2022 18:59:13 -0500 Subject: [PATCH] explorer: move files state from fileStorage This state should be exclusivly used to for displaying files in the explorer, so the state should be in the explorer branch of the reducer tree. --- src/explorer/Explorer.test.tsx | 16 ++--- src/explorer/Explorer.tsx | 4 +- src/explorer/newFileWizard/NewFileWizard.tsx | 2 +- src/explorer/reducers.test.ts | 58 +++++++++++++++++++ src/explorer/reducers.ts | 31 +++++++++- .../renameFileDialog/RenameFileDialog.tsx | 2 +- src/explorer/sagas.ts | 2 +- src/fileStorage/reducers.test.ts | 54 +---------------- src/fileStorage/reducers.ts | 30 +--------- 9 files changed, 103 insertions(+), 96 deletions(-) create mode 100644 src/explorer/reducers.test.ts diff --git a/src/explorer/Explorer.test.tsx b/src/explorer/Explorer.test.tsx index b96e88a7..c94c6b4d 100644 --- a/src/explorer/Explorer.test.tsx +++ b/src/explorer/Explorer.test.tsx @@ -31,7 +31,7 @@ const testFile: FileMetadata = { describe('archive button', () => { it('should be enabled if there are files', () => { const [explorer, dispatch] = testRender(, { - fileStorage: { files: [testFile] }, + explorer: { files: [testFile] }, }); const button = explorer.getByTitle('Backup all files'); @@ -43,7 +43,7 @@ describe('archive button', () => { it('should be disabled if there are no files', () => { const [explorer, dispatch] = testRender(, { - fileStorage: { files: [] }, + explorer: { files: [] }, }); const button = explorer.getByTitle('Backup all files'); @@ -84,7 +84,7 @@ describe('new file button', () => { describe('tree item', () => { it('should dispatch action when button is clicked', async () => { const [explorer, dispatch] = testRender(, { - fileStorage: { files: [testFile] }, + explorer: { files: [testFile] }, }); expect( @@ -102,7 +102,7 @@ describe('tree item', () => { it('should dispatch action when key is pressed', async () => { const [explorer, dispatch] = testRender(, { - fileStorage: { files: [testFile] }, + explorer: { files: [testFile] }, }); expect( @@ -119,7 +119,7 @@ describe('tree item', () => { it('should dispatch delete action when button is clicked', async () => { const [explorer, dispatch] = testRender(, { - fileStorage: { files: [testFile] }, + explorer: { files: [testFile] }, }); // NB: this button is intentionally not accessible (by role) since @@ -133,7 +133,7 @@ describe('tree item', () => { it('should dispatch delete action when key is pressed', async () => { const [explorer, dispatch] = testRender(, { - fileStorage: { files: [testFile] }, + explorer: { files: [testFile] }, }); const treeItem = explorer.getByRole('treeitem', { name: 'test.file' }); @@ -146,7 +146,7 @@ describe('tree item', () => { it('should dispatch export action when button is clicked', async () => { const [explorer, dispatch] = testRender(, { - fileStorage: { files: [testFile] }, + explorer: { files: [testFile] }, }); // NB: this button is intentionally not accessible (by role) since @@ -160,7 +160,7 @@ describe('tree item', () => { it('should dispatch export action when key is pressed', async () => { const [explorer, dispatch] = testRender(, { - fileStorage: { files: [testFile] }, + explorer: { files: [testFile] }, }); const treeItem = explorer.getByRole('treeitem', { name: 'test.file' }); diff --git a/src/explorer/Explorer.tsx b/src/explorer/Explorer.tsx index 29379582..59a15701 100644 --- a/src/explorer/Explorer.tsx +++ b/src/explorer/Explorer.tsx @@ -132,7 +132,7 @@ type HeaderProps = { const Header: React.VoidFunctionComponent = ({ i18n }) => { const [isNewFileWizardOpen, setIsNewFileWizardOpen] = useState(false); const dispatch = useDispatch(); - const files = useSelector((s) => s.fileStorage.files); + const files = useSelector((s) => s.explorer.files); return (
@@ -281,7 +281,7 @@ type FileTreeProps = { const FileTree: React.VoidFunctionComponent = ({ i18n }) => { const [focusedItem, setFocusedItem] = useState(); - const files = useSelector((s) => s.fileStorage.files); + const files = useSelector((s) => s.explorer.files); const liveDescriptors = useLiveDescriptors(i18n); const rootItemIndex = 'root'; diff --git a/src/explorer/newFileWizard/NewFileWizard.tsx b/src/explorer/newFileWizard/NewFileWizard.tsx index b6d3e1df..d09b5bd0 100644 --- a/src/explorer/newFileWizard/NewFileWizard.tsx +++ b/src/explorer/newFileWizard/NewFileWizard.tsx @@ -41,7 +41,7 @@ const NewFileWizard: React.VoidFunctionComponent = ({ const dispatch = useDispatch(); const [fileName, setFileName] = useState(''); - const files = useSelector((s) => s.fileStorage.files); + const files = useSelector((s) => s.explorer.files); const fileNameValidation = validateFileName( fileName, pythonFileExtension, diff --git a/src/explorer/reducers.test.ts b/src/explorer/reducers.test.ts new file mode 100644 index 00000000..f65e2c8c --- /dev/null +++ b/src/explorer/reducers.test.ts @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2022 The Pybricks Authors + +import { uuid } from '../../test'; +import { + FileMetadata, + fileStorageDidAddItem, + fileStorageDidChangeItem, + fileStorageDidInitialize, + fileStorageDidRemoveItem, +} from '../fileStorage/actions'; +import reducers from './reducers'; + +type State = ReturnType; + +test('files', () => { + const testFile: FileMetadata = { + uuid: uuid(0), + path: 'test.file', + sha256: '', + }; + + const modifiedFile: FileMetadata = { ...testFile, path: 'modified.file' }; + + expect(testFile).not.toEqual(modifiedFile); + + // initialization populates file list + expect( + reducers( + { files: [] as readonly FileMetadata[] } as State, + fileStorageDidInitialize([testFile]), + ).files, + ).toEqual([testFile]); + + // adding appends an item + expect( + reducers( + { files: [] as readonly FileMetadata[] } as State, + fileStorageDidAddItem(testFile), + ).files, + ).toEqual([testFile]); + + // changing replaces an item + expect( + reducers( + { files: [testFile] as readonly FileMetadata[] } as State, + fileStorageDidChangeItem(testFile, modifiedFile), + ).files, + ).toEqual([modifiedFile]); + + // removing deletes an item + expect( + reducers( + { files: [testFile] as readonly FileMetadata[] } as State, + fileStorageDidRemoveItem(testFile), + ).files, + ).not.toContain(testFile); +}); diff --git a/src/explorer/reducers.ts b/src/explorer/reducers.ts index 03f36dfd..d820af8c 100644 --- a/src/explorer/reducers.ts +++ b/src/explorer/reducers.ts @@ -1,8 +1,35 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 The Pybricks Authors -import { combineReducers } from 'redux'; +import { Reducer, combineReducers } from 'redux'; +import { + FileMetadata, + fileStorageDidAddItem, + fileStorageDidChangeItem, + fileStorageDidInitialize, + fileStorageDidRemoveItem, +} from '../fileStorage/actions'; import renameFileDialog from './renameFileDialog/reducers'; -export default combineReducers({ renameFileDialog }); +const files: Reducer = (state = [], action) => { + if (fileStorageDidInitialize.matches(action)) { + return [...action.files]; + } + + if (fileStorageDidAddItem.matches(action)) { + return [...state, action.file]; + } + + if (fileStorageDidChangeItem.matches(action)) { + return [...state].map((f) => (f.uuid === action.file.uuid ? action.file : f)); + } + + if (fileStorageDidRemoveItem.matches(action)) { + return [...state].filter((value) => value.uuid !== action.file.uuid); + } + + return state; +}; + +export default combineReducers({ files, renameFileDialog }); diff --git a/src/explorer/renameFileDialog/RenameFileDialog.tsx b/src/explorer/renameFileDialog/RenameFileDialog.tsx index acf42cee..34ba7672 100644 --- a/src/explorer/renameFileDialog/RenameFileDialog.tsx +++ b/src/explorer/renameFileDialog/RenameFileDialog.tsx @@ -24,7 +24,7 @@ const RenameFileDialog: React.VFC = () => { const [baseName, extension] = oldName.split(/(\.\w+)$/); const [newName, setNewName] = useState(baseName); - const files = useSelector((s) => s.fileStorage.files); + const files = useSelector((s) => s.explorer.files); const result = validateFileName( newName, extension, diff --git a/src/explorer/sagas.ts b/src/explorer/sagas.ts index 5bcac7e9..bcfc7e47 100644 --- a/src/explorer/sagas.ts +++ b/src/explorer/sagas.ts @@ -120,7 +120,7 @@ function* handleExplorerImportFiles(): Generator { const text = yield* call(() => file.text()); const [baseName] = file.name.split(pythonFileExtensionRegex); - const existingFiles = yield* select((s: RootState) => s.fileStorage.files); + const existingFiles = yield* select((s: RootState) => s.explorer.files); const result = validateFileName( baseName, diff --git a/src/fileStorage/reducers.test.ts b/src/fileStorage/reducers.test.ts index 8ea27596..27278f46 100644 --- a/src/fileStorage/reducers.test.ts +++ b/src/fileStorage/reducers.test.ts @@ -2,14 +2,7 @@ // Copyright (c) 2022 The Pybricks Authors import { AnyAction } from 'redux'; -import { uuid } from '../../test'; -import { - FileMetadata, - fileStorageDidAddItem, - fileStorageDidChangeItem, - fileStorageDidInitialize, - fileStorageDidRemoveItem, -} from './actions'; +import { fileStorageDidInitialize } from './actions'; import reducers from './reducers'; type State = ReturnType; @@ -17,7 +10,6 @@ type State = ReturnType; test('initial state', () => { expect(reducers(undefined, {} as AnyAction)).toMatchInlineSnapshot(` Object { - "files": Array [], "isInitialized": false, } `); @@ -29,47 +21,3 @@ test('isInitialized', () => { .isInitialized, ).toBeTruthy(); }); - -test('fileNames', () => { - const testFile: FileMetadata = { - uuid: uuid(0), - path: 'test.file', - sha256: '', - }; - - const modifiedFile: FileMetadata = { ...testFile, path: 'modified.file' }; - - expect(testFile).not.toEqual(modifiedFile); - - // initialization populates file list - expect( - reducers( - { files: [] as readonly FileMetadata[] } as State, - fileStorageDidInitialize([testFile]), - ).files, - ).toEqual([testFile]); - - // adding appends an item - expect( - reducers( - { files: [] as readonly FileMetadata[] } as State, - fileStorageDidAddItem(testFile), - ).files, - ).toEqual([testFile]); - - // changing replaces an item - expect( - reducers( - { files: [testFile] as readonly FileMetadata[] } as State, - fileStorageDidChangeItem(testFile, modifiedFile), - ).files, - ).toEqual([modifiedFile]); - - // removing deletes an item - expect( - reducers( - { files: [testFile] as readonly FileMetadata[] } as State, - fileStorageDidRemoveItem(testFile), - ).files, - ).not.toContain(testFile); -}); diff --git a/src/fileStorage/reducers.ts b/src/fileStorage/reducers.ts index 723be334..84676062 100644 --- a/src/fileStorage/reducers.ts +++ b/src/fileStorage/reducers.ts @@ -2,13 +2,7 @@ // Copyright (c) 2022 The Pybricks Authors import { Reducer, combineReducers } from 'redux'; -import { - FileMetadata, - fileStorageDidAddItem, - fileStorageDidChangeItem, - fileStorageDidInitialize, - fileStorageDidRemoveItem, -} from './actions'; +import { fileStorageDidInitialize } from './actions'; const isInitialized: Reducer = (state = false, action) => { if (fileStorageDidInitialize.matches(action)) { @@ -18,24 +12,4 @@ const isInitialized: Reducer = (state = false, action) => { return state; }; -const files: Reducer = (state = [], action) => { - if (fileStorageDidInitialize.matches(action)) { - return [...action.files]; - } - - if (fileStorageDidAddItem.matches(action)) { - return [...state, action.file]; - } - - if (fileStorageDidChangeItem.matches(action)) { - return [...state].map((f) => (f.uuid === action.file.uuid ? action.file : f)); - } - - if (fileStorageDidRemoveItem.matches(action)) { - return [...state].filter((value) => value.uuid !== action.file.uuid); - } - - return state; -}; - -export default combineReducers({ isInitialized, files }); +export default combineReducers({ isInitialized });