From 41fc961661feb537ce3bc4498b4197bbe8eb1104 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 10 Mar 2022 01:01:13 -0600 Subject: [PATCH] fileStorage: implement delete action --- src/explorer/Explorer.tsx | 3 +- src/fileStorage/actions.ts | 6 +++ src/fileStorage/sagas.test.ts | 81 ++++++++++++++++------------------- src/fileStorage/sagas.ts | 14 ++++++ 4 files changed, 60 insertions(+), 44 deletions(-) diff --git a/src/explorer/Explorer.tsx b/src/explorer/Explorer.tsx index ca26d5a7..7fbe87c1 100644 --- a/src/explorer/Explorer.tsx +++ b/src/explorer/Explorer.tsx @@ -16,6 +16,7 @@ import React, { forwardRef, useImperativeHandle, useMemo, useState } from 'react import { useDispatch } from 'react-redux'; import { fileStorageArchiveAllFiles, + fileStorageDeleteFile, fileStorageExportFile, } from '../fileStorage/actions'; import { useSelector } from '../reducers'; @@ -92,7 +93,7 @@ const FileActionButtonGroup = forwardRef< icon="trash" toolTipId={ExplorerStringId.TreeItemDeleteTooltip} toolTipReplacements={{ fileName: props.fileName }} - onClick={() => alert('not implemented')} + onClick={() => dispatch(fileStorageDeleteFile(props.fileName))} /> ); diff --git a/src/fileStorage/actions.ts b/src/fileStorage/actions.ts index bb03a0fa..975556e2 100644 --- a/src/fileStorage/actions.ts +++ b/src/fileStorage/actions.ts @@ -78,6 +78,12 @@ export const fileStorageDidFailToWriteFile = createAction( }), ); +/** Request to delete a file from storage. */ +export const fileStorageDeleteFile = createAction((fileName: string) => ({ + type: 'fileStorage.action.deleteFile', + fileName, +})); + /** * Request to export (download) a file. * @param fileName The name of the file. diff --git a/src/fileStorage/sagas.test.ts b/src/fileStorage/sagas.test.ts index aa469e2d..63bce2e9 100644 --- a/src/fileStorage/sagas.test.ts +++ b/src/fileStorage/sagas.test.ts @@ -6,6 +6,7 @@ import { mock } from 'jest-mock-extended'; import { AsyncSaga } from '../../test'; import { fileStorageArchiveAllFiles, + fileStorageDeleteFile, fileStorageDidArchiveAllFiles, fileStorageDidChangeItem, fileStorageDidExportFile, @@ -14,6 +15,7 @@ import { fileStorageDidFailToReadFile, fileStorageDidInitialize, fileStorageDidReadFile, + fileStorageDidRemoveItem, fileStorageDidWriteFile, fileStorageExportFile, fileStorageReadFile, @@ -29,6 +31,29 @@ beforeEach(() => { localStorage.clear(); }); +/** + * helper function that writes test file to storage for later use in a test + * @param saga The saga. + * @returns The test file name and test file contents. + */ +async function setUpTestFile(saga: AsyncSaga): Promise<[string, string]> { + const testFileName = 'test.file'; + const testFileContents = 'test file contents'; + + const action0 = await saga.take(); + expect(action0).toEqual(fileStorageDidInitialize([])); + + saga.put(fileStorageWriteFile(testFileName, testFileContents)); + + const action1 = await saga.take(); + expect(action1).toEqual(fileStorageDidWriteFile(testFileName)); + + const action2 = await saga.take(); + expect(action2).toEqual(fileStorageDidChangeItem(testFileName)); + + return [testFileName, testFileContents]; +} + it('should migrate old program from local storage during initialization', async () => { const oldProgramKey = 'program'; const oldProgramContents = '# test program'; @@ -94,30 +119,20 @@ it('should dispatch fail action if file does not exist', async () => { await saga.end(); }); +it('should delete files', async () => { + const saga = new AsyncSaga(fileStorage); + + const [testFileName] = await setUpTestFile(saga); + + saga.put(fileStorageDeleteFile(testFileName)); + + const action = await saga.take(); + expect(action).toEqual(fileStorageDidRemoveItem(testFileName)); + + await saga.end(); +}); + describe('export', () => { - /** - * helper function that writes test file to storage for later use in a test - * @param saga The saga. - * @returns The test file name and test file contents. - */ - async function setUpTestFile(saga: AsyncSaga): Promise<[string, string]> { - const testFileName = 'test.file'; - const testFileContents = 'test file contents'; - - const action0 = await saga.take(); - expect(action0).toEqual(fileStorageDidInitialize([])); - - saga.put(fileStorageWriteFile(testFileName, testFileContents)); - - const action1 = await saga.take(); - expect(action1).toEqual(fileStorageDidWriteFile(testFileName)); - - const action2 = await saga.take(); - expect(action2).toEqual(fileStorageDidChangeItem(testFileName)); - - return [testFileName, testFileContents]; - } - it('should fail if file does not exist', async () => { const testFileName = 'test.file'; @@ -236,26 +251,6 @@ describe('export', () => { }); describe('archive', () => { - /** - * helper function that writes test file to storage for later use in a test - * @param saga The saga. - */ - async function setUpTestFile(saga: AsyncSaga): Promise { - const testFileName = 'test.file'; - const testFileContents = 'test file contents'; - - const action0 = await saga.take(); - expect(action0).toEqual(fileStorageDidInitialize([])); - - saga.put(fileStorageWriteFile(testFileName, testFileContents)); - - const action1 = await saga.take(); - expect(action1).toEqual(fileStorageDidWriteFile(testFileName)); - - const action2 = await saga.take(); - expect(action2).toEqual(fileStorageDidChangeItem(testFileName)); - } - it('should archive file with web file system api', async () => { const saga = new AsyncSaga(fileStorage); diff --git a/src/fileStorage/sagas.ts b/src/fileStorage/sagas.ts index 08369c78..60f4dee2 100644 --- a/src/fileStorage/sagas.ts +++ b/src/fileStorage/sagas.ts @@ -12,6 +12,7 @@ import { EditorType } from '../editor/Editor'; import { ensureError, timestamp } from '../utils'; import { fileStorageArchiveAllFiles, + fileStorageDeleteFile, fileStorageDidArchiveAllFiles, fileStorageDidChangeItem, fileStorageDidExportFile, @@ -146,6 +147,18 @@ function* handleExportFile( yield* put(fileStorageDidExportFile(action.fileName)); } +/** + * Deletes a file from storage. + * @param files The localForage instance. + * @param action The action that triggered this saga. + */ +function* handleDeleteFile( + files: LocalForage, + action: ReturnType, +) { + yield* call(() => files.removeItem(action.fileName)); +} + function* handleArchiveAllFiles(files: LocalForage): Generator { try { const zip = new JSZip(); @@ -242,6 +255,7 @@ function* initialize(): Generator { yield* takeEvery(localForageChannel, handleFileStorageDidChange); yield* takeEvery(fileStorageReadFile, handleReadFile, files); yield* takeEvery(fileStorageWriteFile, handleWriteFile, files); + yield* takeEvery(fileStorageDeleteFile, handleDeleteFile, files); yield* takeEvery(fileStorageExportFile, handleExportFile, files); yield* takeEvery(fileStorageArchiveAllFiles, handleArchiveAllFiles, files);