// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import fs from 'fs'; import path from 'path'; import {shell} from 'electron'; import {getDoNotDisturb as getDarwinDoNotDisturb} from 'macos-notification-state'; import Config from 'common/config'; import {APP_UPDATE_KEY} from 'common/constants'; import {DownloadsManager} from 'main/downloadsManager'; const downloadLocationMock = '/path/to/downloads'; const locationMock = '/some/dir/file.txt'; const locationMock1 = '/downloads/file1.txt'; jest.mock('electron', () => { class NotificationMock { static isSupported = jest.fn(); static didConstruct = jest.fn(); constructor() { NotificationMock.didConstruct(); } on = jest.fn(); show = jest.fn(); click = jest.fn(); close = jest.fn(); } return { app: { getAppPath: jest.fn(), }, BrowserView: jest.fn().mockImplementation(() => ({ webContents: { loadURL: jest.fn(), focus: jest.fn(), send: jest.fn(), }, setBounds: jest.fn(), })), ipcMain: { emit: jest.fn(), handle: jest.fn(), on: jest.fn(), removeHandler: jest.fn(), removeListener: jest.fn(), }, Menu: { getApplicationMenu: () => ({ getMenuItemById: jest.fn(), }), }, Notification: NotificationMock, session: { defaultSession: { on: jest.fn(), }, }, shell: { showItemInFolder: jest.fn(), openPath: jest.fn(), }, }; }); jest.mock('path', () => { const original = jest.requireActual('path'); return { ...original, resolve: jest.fn(), parse: jest.fn(), }; }); jest.mock('fs', () => ({ existsSync: jest.fn().mockReturnValue(false), readFileSync: jest.fn().mockImplementation((text) => text), writeFile: jest.fn(), })); jest.mock('macos-notification-state', () => ({ getDoNotDisturb: jest.fn(), })); jest.mock('main/notifications', () => ({})); jest.mock('main/windows/mainWindow', () => ({ sendToRenderer: jest.fn(), })); jest.mock('main/views/viewManager', () => ({})); jest.mock('common/config', () => { const original = jest.requireActual('common/config'); return { ...original, canUpgrade: true, downloadLocation: downloadLocationMock, }; }); const downloadsJson = { 'file1.txt': { addedAt: 1662545584346, filename: 'file1.txt', mimeType: 'text/plain', location: '/downloads/file1.txt', progress: 100, receivedBytes: 5425, state: 'completed', totalBytes: 5425, type: 'file', }, 'file2.txt': { addedAt: 1662545588346, filename: 'file2.txt', mimeType: 'text/plain', location: '/downloads/file2.txt', progress: 100, receivedBytes: 5425, state: 'cancelled', totalBytes: 5425, type: 'file', }, }; const nowSeconds = Date.now() / 1000; const item = { getFilename: () => 'file.txt', getMimeType: () => 'text/plain', getReceivedBytes: () => 2121, getStartTime: () => nowSeconds, getTotalBytes: () => 4242, getSavePath: () => locationMock, getURL: () => 'http://some-url.com/some-text.txt', hasUserGesture: jest.fn().mockReturnValue(true), setSavePath: jest.fn(), on: jest.fn(), setSaveDialogOptions: jest.fn(), once: jest.fn(), location: locationMock, }; const item1 = { ...item, getFilename: () => 'file1.txt', getSavePath: () => locationMock1, location: locationMock1, }; describe('main/downloadsManager', () => { beforeEach(() => { getDarwinDoNotDisturb.mockReturnValue(false); }); it('should be initialized', () => { expect(new DownloadsManager({})).toHaveProperty('downloads', {}); }); it('should mark "completed" files that were deleted as "deleted"', () => { expect(new DownloadsManager(JSON.stringify(downloadsJson))).toHaveProperty('downloads', {...downloadsJson, 'file1.txt': {...downloadsJson['file1.txt'], state: 'deleted'}}); }); it('should handle a new download', () => { const dl = new DownloadsManager({}); path.parse.mockImplementation(() => ({base: 'file.txt'})); dl.willDownloadURLs.set('http://some-url.com/some-text.txt', {filePath: locationMock}); dl.handleNewDownload({preventDefault: jest.fn()}, item, {id: 0, getURL: jest.fn(), downloadURL: jest.fn()}); expect(dl).toHaveProperty('downloads', {'file.txt': { addedAt: nowSeconds * 1000, filename: 'file.txt', mimeType: 'text/plain', location: '/some/dir/file.txt', progress: 50, receivedBytes: 2121, state: 'progressing', totalBytes: 4242, type: 'file', }}); }); it('should monitor network to retrieve the file size of downloading items', () => { const dl = new DownloadsManager({}); const details = { responseHeaders: { 'content-encoding': ['gzip'], 'x-uncompressed-content-length': ['4242'], 'content-disposition': ['attachment; filename="file.txt"; foobar'], }, }; dl.webRequestOnHeadersReceivedHandler(details, jest.fn()); expect(dl.fileSizes.get('file.txt')).toBe('4242'); }); it('should clear the downloads list', () => { const dl = new DownloadsManager(JSON.stringify(downloadsJson)); dl.clearDownloadsDropDown(); expect(dl).toHaveProperty('downloads', {}); }); it('should open downloads folder if file deleted', () => { const dl = new DownloadsManager(JSON.stringify(downloadsJson)); path.parse.mockImplementation(() => ({base: 'file1.txt'})); dl.showFileInFolder(item1); expect(shell.openPath).toHaveBeenCalledWith(downloadLocationMock); }); it('should show the file in the downloads folder', () => { const dl = new DownloadsManager(JSON.stringify(downloadsJson)); fs.existsSync.mockReturnValueOnce(true); path.parse.mockImplementation(() => ({base: 'file1.txt'})); dl.showFileInFolder(item1); expect(shell.showItemInFolder).toHaveBeenCalledWith(locationMock1); }); it('MM-48483 - should remove an invalid file from the list on startup', () => { const dl = new DownloadsManager(JSON.stringify({ ...downloadsJson, 'invalid_file1.txt': undefined, 'invalid_file2.txt': {}, 'invalid_file3.txt': {invalidProperty: 'something'}, 'invalid_file4.txt': { state: 'completed', type: 'file', }, 'invalid_file5.txt': { filename: 'invalid_file5.txt', type: 'file', }, 'invalid_file6.txt': { filename: 'invalid_file5.txt', state: 'completed', }, })); expect(Object.keys(dl.downloads).includes('invalid_file1.txt')).toBe(false); expect(Object.keys(dl.downloads).includes('invalid_file2.txt')).toBe(false); expect(Object.keys(dl.downloads).includes('invalid_file3.txt')).toBe(false); expect(Object.keys(dl.downloads).includes('invalid_file4.txt')).toBe(false); expect(Object.keys(dl.downloads).includes('invalid_file5.txt')).toBe(false); expect(Object.keys(dl.downloads).includes('invalid_file6.txt')).toBe(false); }); it('should remove updates if they are disabled', () => { const file = JSON.stringify({ ...downloadsJson, [APP_UPDATE_KEY]: { type: 'update', progress: 0, location: '', addedAt: 0, receivedBytes: 0, totalBytes: 0, state: 'available', filename: '1.2.3', }, }); const dl = new DownloadsManager(file); expect(dl.hasUpdate()).toBe(true); Config.canUpgrade = false; dl.init(); expect(dl.hasUpdate()).toBe(false); }); });