From 7eab373ef4f0e4189ffb9dc5bf736b43f33fc237 Mon Sep 17 00:00:00 2001 From: Michael Hutchison Date: Sat, 10 Apr 2021 17:08:23 +1000 Subject: [PATCH] Display a success or error message once `git-graph.clearAvatarCache` has executed. --- src/avatarManager.ts | 3 ++- src/commands.ts | 10 +++++++- src/extensionState.ts | 17 ++++++++---- tests/avatarManager.test.ts | 29 ++++++++++++++++++--- tests/commands.test.ts | 50 +++++++++++++++++++++++++++++++++--- tests/extensionState.test.ts | 47 +++++++++++++++++++++++---------- 6 files changed, 128 insertions(+), 28 deletions(-) diff --git a/src/avatarManager.ts b/src/avatarManager.ts index 0572bfac..27fd7fc0 100644 --- a/src/avatarManager.ts +++ b/src/avatarManager.ts @@ -134,10 +134,11 @@ export class AvatarManager extends Disposable { /** * Remove all avatars from the cache. + * @returns A Thenable resolving to the ErrorInfo that resulted from executing this method. */ public clearCache() { this.avatars = {}; - this.extensionState.clearAvatarCache(); + return this.extensionState.clearAvatarCache(); } /** diff --git a/src/commands.ts b/src/commands.ts index e102b418..3275f5ee 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -183,7 +183,15 @@ export class CommandManager extends Disposable { * The method run when the `git-graph.clearAvatarCache` command is invoked. */ private clearAvatarCache() { - this.avatarManager.clearCache(); + this.avatarManager.clearCache().then((errorInfo) => { + if (errorInfo === null) { + showInformationMessage('The Avatar Cache was successfully cleared.'); + } else { + showErrorMessage(errorInfo); + } + }, () => { + showErrorMessage('An unexpected error occurred while running the command "Clear Avatar Cache".'); + }); } /** diff --git a/src/extensionState.ts b/src/extensionState.ts index 1b39346d..286ab1ba 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -297,14 +297,19 @@ export class ExtensionState extends Disposable { /** * Clear all avatars from the cache of avatars known to Git Graph. + * @returns A Thenable resolving to the ErrorInfo that resulted from executing this method. */ public clearAvatarCache() { - this.updateGlobalState(AVATAR_CACHE, {}); - fs.readdir(this.globalStoragePath + AVATAR_STORAGE_FOLDER, (err, files) => { - if (err) return; - for (let i = 0; i < files.length; i++) { - fs.unlink(this.globalStoragePath + AVATAR_STORAGE_FOLDER + '/' + files[i], () => { }); + return this.updateGlobalState(AVATAR_CACHE, {}).then((errorInfo) => { + if (errorInfo === null) { + fs.readdir(this.globalStoragePath + AVATAR_STORAGE_FOLDER, (err, files) => { + if (err) return; + for (let i = 0; i < files.length; i++) { + fs.unlink(this.globalStoragePath + AVATAR_STORAGE_FOLDER + '/' + files[i], () => { }); + } + }); } + return errorInfo; }); } @@ -434,6 +439,7 @@ export class ExtensionState extends Disposable { * Update the Git Graph Global State with a new pair. * @param key The key. * @param value The value. + * @returns A Thenable resolving to the ErrorInfo that resulted from updating the Global State. */ private updateGlobalState(key: string, value: any): Thenable { return this.globalState.update(key, value).then( @@ -446,6 +452,7 @@ export class ExtensionState extends Disposable { * Update the Git Graph Workspace State with a new pair. * @param key The key. * @param value The value. + * @returns A Thenable resolving to the ErrorInfo that resulted from updating the Workspace State. */ private updateWorkspaceState(key: string, value: any): Thenable { return this.workspaceState.update(key, value).then( diff --git a/tests/avatarManager.test.ts b/tests/avatarManager.test.ts index d41332e2..7b451cae 100644 --- a/tests/avatarManager.test.ts +++ b/tests/avatarManager.test.ts @@ -1627,13 +1627,36 @@ describe('AvatarManager', () => { }); describe('clearCache', () => { - it('Should clear the cache of avatars', () => { + let spyOnClearAvatarCache: jest.SpyInstance; + beforeAll(() => { + spyOnClearAvatarCache = jest.spyOn(extensionState, 'clearAvatarCache'); + }); + + it('Should clear the cache of avatars', async () => { + // Setup + spyOnClearAvatarCache.mockResolvedValueOnce(null); + + // Run + const result = await avatarManager.clearCache(); + + // Assert + expect(result).toBeNull(); + expect(avatarManager['avatars']).toStrictEqual({}); + expect(spyOnClearAvatarCache).toHaveBeenCalledTimes(1); + }); + + it('Should return the error message returned by ExtensionState.clearAvatarCache', async () => { + // Setup + const errorMessage = 'Visual Studio Code was unable to save the Git Graph Global State Memento.'; + spyOnClearAvatarCache.mockResolvedValueOnce(errorMessage); + // Run - avatarManager.clearCache(); + const result = await avatarManager.clearCache(); // Assert + expect(result).toBe(errorMessage); expect(avatarManager['avatars']).toStrictEqual({}); - expect(extensionState.clearAvatarCache).toHaveBeenCalledTimes(1); + expect(spyOnClearAvatarCache).toHaveBeenCalledTimes(1); }); }); }); diff --git a/tests/commands.test.ts b/tests/commands.test.ts index f30b9f02..bc64920c 100644 --- a/tests/commands.test.ts +++ b/tests/commands.test.ts @@ -518,16 +518,58 @@ describe('CommandManager', () => { }); describe('git-graph.clearAvatarCache', () => { - it('Should clear the avatar cache', () => { + let spyOnClearCache: jest.SpyInstance; + beforeAll(() => { + spyOnClearCache = jest.spyOn(avatarManager, 'clearCache'); + }); + + it('Should clear the avatar cache, and display a success message', async () => { + // Setup + spyOnClearCache.mockResolvedValueOnce(null); + vscode.window.showInformationMessage.mockResolvedValueOnce(null); + + // Run + vscode.commands.executeCommand('git-graph.clearAvatarCache'); + + // Assert + await waitForExpect(() => { + expect(spyOnLog).toHaveBeenCalledWith('Command Invoked: git-graph.clearAvatarCache'); + expect(spyOnClearCache).toBeCalledTimes(1); + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith('The Avatar Cache was successfully cleared.'); + }); + }); + + it('Should display the error message returned by AvatarManager.clearCache', async () => { // Setup - const spyOnClearCache = jest.spyOn(avatarManager, 'clearCache'); + const errorMessage = 'Visual Studio Code was unable to save the Git Graph Global State Memento.'; + spyOnClearCache.mockResolvedValueOnce(errorMessage); + vscode.window.showErrorMessage.mockResolvedValueOnce(null); + + // Run + vscode.commands.executeCommand('git-graph.clearAvatarCache'); + + // Assert + await waitForExpect(() => { + expect(spyOnLog).toHaveBeenCalledWith('Command Invoked: git-graph.clearAvatarCache'); + expect(spyOnClearCache).toBeCalledTimes(1); + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith(errorMessage); + }); + }); + + it('Should display an error message when AvatarManager.clearCache rejects', async () => { + // Setup + spyOnClearCache.mockRejectedValueOnce(null); + vscode.window.showErrorMessage.mockResolvedValueOnce(null); // Run vscode.commands.executeCommand('git-graph.clearAvatarCache'); // Assert - expect(spyOnLog).toHaveBeenCalledWith('Command Invoked: git-graph.clearAvatarCache'); - expect(spyOnClearCache).toBeCalledTimes(1); + await waitForExpect(() => { + expect(spyOnLog).toHaveBeenCalledWith('Command Invoked: git-graph.clearAvatarCache'); + expect(spyOnClearCache).toBeCalledTimes(1); + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith('An unexpected error occurred while running the command "Clear Avatar Cache".'); + }); }); }); diff --git a/tests/extensionState.test.ts b/tests/extensionState.test.ts index b749f7d5..e670c61e 100644 --- a/tests/extensionState.test.ts +++ b/tests/extensionState.test.ts @@ -863,41 +863,60 @@ describe('ExtensionState', () => { }); describe('clearAvatarCache', () => { - it('Should clear all avatars from the cache and delete all avatars that are currently stored on the file system', () => { + let spyOnReaddir: jest.SpyInstance, spyOnUnlink: jest.SpyInstance; + beforeAll(() => { + spyOnReaddir = jest.spyOn(fs, 'readdir'); + spyOnUnlink = jest.spyOn(fs, 'unlink'); + }); + + it('Should clear all avatars from the cache and delete all avatars that are currently stored on the file system', async () => { + // Setup extensionContext.globalState.update.mockResolvedValueOnce(null); - const spyOnReaddir = jest.spyOn(fs, 'readdir'); spyOnReaddir.mockImplementationOnce((_, callback) => callback(null, ['file1.jpg', 'file2.jpg'])); - const spyOnUnlink = jest.spyOn(fs, 'unlink'); - spyOnUnlink.mockImplementation((_, callback) => callback(null)); + spyOnUnlink.mockImplementationOnce((_, callback) => callback(null)); + spyOnUnlink.mockImplementationOnce((_, callback) => callback(null)); // Run - extensionState.clearAvatarCache(); + const result = await extensionState.clearAvatarCache(); // Assert + expect(result).toBeNull(); expect(extensionContext.globalState.update).toHaveBeenCalledWith('avatarCache', {}); expect(spyOnReaddir).toHaveBeenCalledTimes(1); - expect(spyOnReaddir.mock.calls[0][0]).toBe('/path/to/globalStorage/avatars'); + expect(spyOnReaddir).toHaveBeenNthCalledWith(1, '/path/to/globalStorage/avatars', expect.anything()); expect(spyOnUnlink).toHaveBeenCalledTimes(2); - expect(spyOnUnlink.mock.calls[0][0]).toBe('/path/to/globalStorage/avatars/file1.jpg'); - expect(spyOnUnlink.mock.calls[1][0]).toBe('/path/to/globalStorage/avatars/file2.jpg'); + expect(spyOnUnlink).toHaveBeenNthCalledWith(1, '/path/to/globalStorage/avatars/file1.jpg', expect.anything()); + expect(spyOnUnlink).toHaveBeenNthCalledWith(2, '/path/to/globalStorage/avatars/file2.jpg', expect.anything()); }); - it('Should skip deleting avatars on the file system if they could not be listed from the file system', () => { + it('Should skip deleting avatars on the file system if they could not be listed from the file system', async () => { + // Setup extensionContext.globalState.update.mockResolvedValueOnce(null); - const spyOnReaddir = jest.spyOn(fs, 'readdir'); spyOnReaddir.mockImplementationOnce((_, callback) => callback(new Error(), ['file1.jpg', 'file2.jpg'])); - const spyOnUnlink = jest.spyOn(fs, 'unlink'); - spyOnUnlink.mockImplementation((_, callback) => callback(null)); // Run - extensionState.clearAvatarCache(); + const result = await extensionState.clearAvatarCache(); // Assert + expect(result).toBeNull(); expect(extensionContext.globalState.update).toHaveBeenCalledWith('avatarCache', {}); expect(spyOnReaddir).toHaveBeenCalledTimes(1); - expect(spyOnReaddir.mock.calls[0][0]).toBe('/path/to/globalStorage/avatars'); + expect(spyOnReaddir).toHaveBeenNthCalledWith(1, '/path/to/globalStorage/avatars', expect.anything()); expect(spyOnUnlink).toHaveBeenCalledTimes(0); }); + + it('Shouldn\'t delete avatars on the file system if globalState.update rejects, and return the error message', async () => { + // Setup + extensionContext.globalState.update.mockRejectedValueOnce(null); + + // Run + const result = await extensionState.clearAvatarCache(); + + // Assert + expect(result).toBe('Visual Studio Code was unable to save the Git Graph Global State Memento.'); + expect(extensionContext.globalState.update).toHaveBeenCalledWith('avatarCache', {}); + expect(spyOnReaddir).not.toHaveBeenCalled(); + }); }); describe('startCodeReview', () => {