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.
This commit is contained in:
David Lechner
2022-04-04 18:59:13 -05:00
parent 05731e50fc
commit c6cf0608f4
9 changed files with 103 additions and 96 deletions
+8 -8
View File
@@ -31,7 +31,7 @@ const testFile: FileMetadata = {
describe('archive button', () => {
it('should be enabled if there are files', () => {
const [explorer, dispatch] = testRender(<Explorer />, {
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(<Explorer />, {
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(<Explorer />, {
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(<Explorer />, {
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(<Explorer />, {
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(<Explorer />, {
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(<Explorer />, {
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(<Explorer />, {
fileStorage: { files: [testFile] },
explorer: { files: [testFile] },
});
const treeItem = explorer.getByRole('treeitem', { name: 'test.file' });
+2 -2
View File
@@ -132,7 +132,7 @@ type HeaderProps = {
const Header: React.VoidFunctionComponent<HeaderProps> = ({ i18n }) => {
const [isNewFileWizardOpen, setIsNewFileWizardOpen] = useState(false);
const dispatch = useDispatch();
const files = useSelector((s) => s.fileStorage.files);
const files = useSelector((s) => s.explorer.files);
return (
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
@@ -281,7 +281,7 @@ type FileTreeProps = {
const FileTree: React.VoidFunctionComponent<FileTreeProps> = ({ i18n }) => {
const [focusedItem, setFocusedItem] = useState<TreeItemIndex>();
const files = useSelector((s) => s.fileStorage.files);
const files = useSelector((s) => s.explorer.files);
const liveDescriptors = useLiveDescriptors(i18n);
const rootItemIndex = 'root';
+1 -1
View File
@@ -41,7 +41,7 @@ const NewFileWizard: React.VoidFunctionComponent<NewFileWizardProps> = ({
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,
+58
View File
@@ -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<typeof reducers>;
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);
});
+29 -2
View File
@@ -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<readonly FileMetadata[]> = (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 });
@@ -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,
+1 -1
View File
@@ -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,
+1 -53
View File
@@ -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<typeof reducers>;
@@ -17,7 +10,6 @@ type State = ReturnType<typeof reducers>;
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);
});
+2 -28
View File
@@ -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<boolean> = (state = false, action) => {
if (fileStorageDidInitialize.matches(action)) {
@@ -18,24 +12,4 @@ const isInitialized: Reducer<boolean> = (state = false, action) => {
return state;
};
const files: Reducer<readonly FileMetadata[]> = (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 });