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 });