641 lines
22 KiB
JavaScript
641 lines
22 KiB
JavaScript
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
/* eslint-disable max-lines */
|
|
'use strict';
|
|
|
|
import {dialog} from 'electron';
|
|
|
|
import {BROWSER_HISTORY_PUSH, LOAD_SUCCESS, SET_ACTIVE_VIEW} from 'common/communication';
|
|
import {TAB_MESSAGING} from 'common/views/View';
|
|
import ServerManager from 'common/servers/serverManager';
|
|
import urlUtils from 'common/utils/url';
|
|
|
|
import MainWindow from 'main/windows/mainWindow';
|
|
|
|
import {MattermostBrowserView} from './MattermostBrowserView';
|
|
import {ViewManager} from './viewManager';
|
|
import LoadingScreen from './loadingScreen';
|
|
|
|
jest.mock('electron', () => ({
|
|
app: {
|
|
getAppPath: () => '/path/to/app',
|
|
},
|
|
dialog: {
|
|
showErrorBox: jest.fn(),
|
|
},
|
|
ipcMain: {
|
|
emit: jest.fn(),
|
|
on: jest.fn(),
|
|
handle: jest.fn(),
|
|
},
|
|
}));
|
|
jest.mock('common/views/View', () => ({
|
|
getViewName: jest.fn((a, b) => `${a}-${b}`),
|
|
TAB_MESSAGING: 'view',
|
|
}));
|
|
|
|
jest.mock('common/servers/MattermostServer', () => ({
|
|
MattermostServer: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('common/utils/url', () => ({
|
|
isTeamUrl: jest.fn(),
|
|
isAdminUrl: jest.fn(),
|
|
cleanPathName: jest.fn(),
|
|
parseURL: (url) => {
|
|
try {
|
|
return new URL(url);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
},
|
|
getFormattedPathName: (pathname) => (pathname.length ? pathname : '/'),
|
|
equalUrlsIgnoringSubpath: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('main/i18nManager', () => ({
|
|
localizeMessage: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('main/server/serverInfo', () => ({
|
|
ServerInfo: jest.fn(),
|
|
}));
|
|
jest.mock('main/views/loadingScreen', () => ({
|
|
show: jest.fn(),
|
|
fade: jest.fn(),
|
|
}));
|
|
jest.mock('main/windows/mainWindow', () => ({
|
|
get: jest.fn(),
|
|
on: jest.fn(),
|
|
}));
|
|
jest.mock('common/servers/serverManager', () => ({
|
|
getCurrentServer: jest.fn(),
|
|
getOrderedTabsForServer: jest.fn(),
|
|
getAllServers: jest.fn(),
|
|
hasServers: jest.fn(),
|
|
getLastActiveServer: jest.fn(),
|
|
getLastActiveTabForServer: jest.fn(),
|
|
updateLastActive: jest.fn(),
|
|
lookupViewByURL: jest.fn(),
|
|
getRemoteInfo: jest.fn(),
|
|
on: jest.fn(),
|
|
getServerLog: () => ({
|
|
error: jest.fn(),
|
|
warn: jest.fn(),
|
|
info: jest.fn(),
|
|
verbose: jest.fn(),
|
|
debug: jest.fn(),
|
|
silly: jest.fn(),
|
|
}),
|
|
getViewLog: () => ({
|
|
error: jest.fn(),
|
|
warn: jest.fn(),
|
|
info: jest.fn(),
|
|
verbose: jest.fn(),
|
|
debug: jest.fn(),
|
|
silly: jest.fn(),
|
|
}),
|
|
}));
|
|
|
|
jest.mock('./MattermostBrowserView', () => ({
|
|
MattermostBrowserView: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('./modalManager', () => ({
|
|
showModal: jest.fn(),
|
|
isModalDisplayed: jest.fn(),
|
|
}));
|
|
jest.mock('./webContentEvents', () => ({}));
|
|
jest.mock('common/appState', () => ({}));
|
|
|
|
describe('main/views/viewManager', () => {
|
|
describe('loadView', () => {
|
|
const viewManager = new ViewManager();
|
|
const onceFn = jest.fn();
|
|
const loadFn = jest.fn();
|
|
const destroyFn = jest.fn();
|
|
|
|
beforeEach(() => {
|
|
viewManager.showById = jest.fn();
|
|
MainWindow.get.mockReturnValue({});
|
|
MattermostBrowserView.mockImplementation((view) => ({
|
|
on: jest.fn(),
|
|
load: loadFn,
|
|
once: onceFn,
|
|
destroy: destroyFn,
|
|
id: view.id,
|
|
}));
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
viewManager.closedViews = new Map();
|
|
viewManager.views = new Map();
|
|
});
|
|
|
|
it('should add closed views to closedViews', () => {
|
|
viewManager.loadView({id: 'server1'}, {id: 'view1', isOpen: false});
|
|
expect(viewManager.closedViews.has('view1')).toBe(true);
|
|
});
|
|
|
|
it('should remove from remove from closedViews when the view is open', () => {
|
|
viewManager.closedViews.set('view1', {});
|
|
expect(viewManager.closedViews.has('view1')).toBe(true);
|
|
viewManager.loadView({id: 'server1'}, {id: 'view1', isOpen: true});
|
|
expect(viewManager.closedViews.has('view1')).toBe(false);
|
|
});
|
|
|
|
it('should add view to views map and add listeners', () => {
|
|
viewManager.loadView({id: 'server1'}, {id: 'view1', isOpen: true}, 'http://server-1.com/subpath');
|
|
expect(viewManager.views.has('view1')).toBe(true);
|
|
expect(onceFn).toHaveBeenCalledWith(LOAD_SUCCESS, viewManager.activateView);
|
|
expect(loadFn).toHaveBeenCalledWith('http://server-1.com/subpath');
|
|
});
|
|
});
|
|
|
|
describe('handleReloadConfiguration', () => {
|
|
const viewManager = new ViewManager();
|
|
|
|
beforeEach(() => {
|
|
viewManager.loadView = jest.fn();
|
|
viewManager.showById = jest.fn();
|
|
viewManager.showInitial = jest.fn();
|
|
viewManager.focus = jest.fn();
|
|
MainWindow.get.mockReturnValue({
|
|
webContents: {
|
|
send: jest.fn(),
|
|
},
|
|
});
|
|
|
|
const onceFn = jest.fn();
|
|
const loadFn = jest.fn();
|
|
const destroyFn = jest.fn();
|
|
MattermostBrowserView.mockImplementation((view) => ({
|
|
on: jest.fn(),
|
|
load: loadFn,
|
|
once: onceFn,
|
|
destroy: destroyFn,
|
|
id: view.id,
|
|
updateServerInfo: jest.fn(),
|
|
view,
|
|
}));
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
delete viewManager.currentView;
|
|
viewManager.closedViews = new Map();
|
|
viewManager.views = new Map();
|
|
});
|
|
|
|
it('should recycle existing views', () => {
|
|
const makeSpy = jest.spyOn(viewManager, 'makeView');
|
|
const view = new MattermostBrowserView({
|
|
id: 'view1',
|
|
server: {
|
|
id: 'server1',
|
|
},
|
|
});
|
|
viewManager.views.set('view1', view);
|
|
ServerManager.getAllServers.mockReturnValue([{
|
|
id: 'server1',
|
|
url: new URL('http://server1.com'),
|
|
}]);
|
|
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
|
{
|
|
id: 'view1',
|
|
isOpen: true,
|
|
},
|
|
]);
|
|
viewManager.handleReloadConfiguration();
|
|
expect(viewManager.views.get('view1')).toBe(view);
|
|
expect(makeSpy).not.toHaveBeenCalled();
|
|
makeSpy.mockRestore();
|
|
});
|
|
|
|
it('should close views that arent open', () => {
|
|
ServerManager.getAllServers.mockReturnValue([{
|
|
id: 'server1',
|
|
url: new URL('http://server1.com'),
|
|
}]);
|
|
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
|
{
|
|
id: 'view1',
|
|
isOpen: false,
|
|
},
|
|
]);
|
|
viewManager.handleReloadConfiguration();
|
|
expect(viewManager.closedViews.has('view1')).toBe(true);
|
|
});
|
|
|
|
it('should create new views for new views', () => {
|
|
const makeSpy = jest.spyOn(viewManager, 'makeView');
|
|
ServerManager.getAllServers.mockReturnValue([{
|
|
id: 'server1',
|
|
name: 'server1',
|
|
url: new URL('http://server1.com'),
|
|
}]);
|
|
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
|
{
|
|
id: 'view1',
|
|
name: 'view1',
|
|
isOpen: true,
|
|
url: new URL('http://server1.com/view'),
|
|
},
|
|
]);
|
|
viewManager.handleReloadConfiguration();
|
|
expect(makeSpy).toHaveBeenCalledWith(
|
|
{
|
|
id: 'server1',
|
|
name: 'server1',
|
|
url: new URL('http://server1.com'),
|
|
},
|
|
{
|
|
id: 'view1',
|
|
name: 'view1',
|
|
isOpen: true,
|
|
url: new URL('http://server1.com/view'),
|
|
},
|
|
);
|
|
makeSpy.mockRestore();
|
|
});
|
|
|
|
it('should set focus to current view on reload', () => {
|
|
const view = {
|
|
id: 'view1',
|
|
view: {
|
|
server: {
|
|
id: 'server-1',
|
|
},
|
|
id: 'view1',
|
|
url: new URL('http://server1.com'),
|
|
},
|
|
destroy: jest.fn(),
|
|
updateServerInfo: jest.fn(),
|
|
focus: jest.fn(),
|
|
};
|
|
viewManager.currentView = 'view1';
|
|
viewManager.views.set('view1', view);
|
|
ServerManager.getAllServers.mockReturnValue([{
|
|
id: 'server1',
|
|
url: new URL('http://server1.com'),
|
|
}]);
|
|
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
|
{
|
|
id: 'view1',
|
|
isOpen: true,
|
|
},
|
|
]);
|
|
viewManager.handleReloadConfiguration();
|
|
expect(view.focus).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should show initial if currentView has been removed', () => {
|
|
const view = {
|
|
id: 'view1',
|
|
view: {
|
|
id: 'view1',
|
|
url: new URL('http://server1.com'),
|
|
},
|
|
destroy: jest.fn(),
|
|
updateServerInfo: jest.fn(),
|
|
};
|
|
viewManager.currentView = 'view1';
|
|
viewManager.views.set('view1', view);
|
|
ServerManager.getAllServers.mockReturnValue([{
|
|
id: 'server2',
|
|
url: new URL('http://server2.com'),
|
|
}]);
|
|
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
|
{
|
|
id: 'view1',
|
|
isOpen: false,
|
|
},
|
|
]);
|
|
viewManager.handleReloadConfiguration();
|
|
expect(viewManager.showInitial).toBeCalled();
|
|
});
|
|
|
|
it('should remove unused views', () => {
|
|
const view = {
|
|
name: 'view1',
|
|
view: {
|
|
name: 'view1',
|
|
url: new URL('http://server1.com'),
|
|
},
|
|
destroy: jest.fn(),
|
|
};
|
|
viewManager.views.set('view1', view);
|
|
ServerManager.getAllServers.mockReturnValue([{
|
|
id: 'server2',
|
|
url: new URL('http://server2.com'),
|
|
}]);
|
|
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
|
{
|
|
id: 'view1',
|
|
isOpen: false,
|
|
},
|
|
]);
|
|
viewManager.handleReloadConfiguration();
|
|
expect(view.destroy).toBeCalled();
|
|
expect(viewManager.showInitial).toBeCalled();
|
|
});
|
|
});
|
|
|
|
describe('showInitial', () => {
|
|
const viewManager = new ViewManager();
|
|
const window = {webContents: {send: jest.fn()}};
|
|
|
|
beforeEach(() => {
|
|
viewManager.showById = jest.fn();
|
|
MainWindow.get.mockReturnValue(window);
|
|
ServerManager.hasServers.mockReturnValue(true);
|
|
ServerManager.getCurrentServer.mockReturnValue({id: 'server-0'});
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
});
|
|
|
|
it('should show last active view and server', () => {
|
|
ServerManager.getLastActiveServer.mockReturnValue({id: 'server-1'});
|
|
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-1'});
|
|
viewManager.showInitial();
|
|
expect(viewManager.showById).toHaveBeenCalledWith('view-1');
|
|
});
|
|
|
|
it('should open new server modal when no servers exist', () => {
|
|
ServerManager.hasServers.mockReturnValue(false);
|
|
viewManager.showInitial();
|
|
expect(window.webContents.send).toHaveBeenCalledWith(SET_ACTIVE_VIEW);
|
|
});
|
|
});
|
|
|
|
describe('handleBrowserHistoryPush', () => {
|
|
const viewManager = new ViewManager();
|
|
viewManager.handleBrowserHistoryButton = jest.fn();
|
|
viewManager.showById = jest.fn();
|
|
const servers = [
|
|
{
|
|
name: 'server-1',
|
|
url: 'http://server-1.com',
|
|
order: 0,
|
|
tabs: [
|
|
{
|
|
name: 'view-messaging',
|
|
order: 0,
|
|
isOpen: true,
|
|
},
|
|
{
|
|
name: 'other_type_1',
|
|
order: 2,
|
|
isOpen: true,
|
|
},
|
|
{
|
|
name: 'other_type_2',
|
|
order: 1,
|
|
isOpen: false,
|
|
},
|
|
],
|
|
},
|
|
];
|
|
const view1 = {
|
|
id: 'server-1_view-messaging',
|
|
isLoggedIn: true,
|
|
view: {
|
|
type: TAB_MESSAGING,
|
|
server: {
|
|
url: 'http://server-1.com',
|
|
},
|
|
},
|
|
sendToRenderer: jest.fn(),
|
|
};
|
|
const view2 = {
|
|
...view1,
|
|
id: 'server-1_other_type_1',
|
|
view: {
|
|
...view1.view,
|
|
type: 'other_type_1',
|
|
},
|
|
};
|
|
const view3 = {
|
|
...view1,
|
|
id: 'server-1_other_type_2',
|
|
view: {
|
|
...view1.view,
|
|
type: 'other_type_2',
|
|
},
|
|
};
|
|
const views = new Map([
|
|
['server-1_view-messaging', view1],
|
|
['server-1_other_type_1', view2],
|
|
]);
|
|
const closedViews = new Map([
|
|
['server-1_other_type_2', view3],
|
|
]);
|
|
viewManager.getView = (viewId) => views.get(viewId);
|
|
viewManager.isViewClosed = (viewId) => closedViews.has(viewId);
|
|
viewManager.openClosedView = jest.fn();
|
|
|
|
beforeEach(() => {
|
|
ServerManager.getAllServers.mockReturnValue(servers);
|
|
ServerManager.getCurrentServer.mockReturnValue(servers[0]);
|
|
urlUtils.cleanPathName.mockImplementation((base, path) => path);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
});
|
|
|
|
it('should open closed view if pushing to it', () => {
|
|
viewManager.openClosedView.mockImplementation((name) => {
|
|
const view = closedViews.get(name);
|
|
closedViews.delete(name);
|
|
views.set(name, view);
|
|
});
|
|
ServerManager.lookupViewByURL.mockReturnValue({id: 'server-1_other_type_2'});
|
|
viewManager.handleBrowserHistoryPush(null, 'server-1_view-messaging', '/other_type_2/subpath');
|
|
expect(viewManager.openClosedView).toBeCalledWith('server-1_other_type_2', 'http://server-1.com/other_type_2/subpath');
|
|
});
|
|
|
|
it('should open redirect view if different from current view', () => {
|
|
ServerManager.lookupViewByURL.mockReturnValue({id: 'server-1_other_type_1'});
|
|
viewManager.handleBrowserHistoryPush(null, 'server-1_view-messaging', '/other_type_1/subpath');
|
|
expect(viewManager.showById).toBeCalledWith('server-1_other_type_1');
|
|
});
|
|
|
|
it('should ignore redirects to "/" to Messages from other views', () => {
|
|
ServerManager.lookupViewByURL.mockReturnValue({id: 'server-1_view-messaging'});
|
|
viewManager.handleBrowserHistoryPush(null, 'server-1_other_type_1', '/');
|
|
expect(view1.sendToRenderer).not.toBeCalled();
|
|
});
|
|
});
|
|
|
|
describe('showById', () => {
|
|
const viewManager = new ViewManager({});
|
|
const baseView = {
|
|
isReady: jest.fn(),
|
|
isErrored: jest.fn(),
|
|
show: jest.fn(),
|
|
hide: jest.fn(),
|
|
needsLoadingScreen: jest.fn(),
|
|
window: {
|
|
webContents: {
|
|
send: jest.fn(),
|
|
},
|
|
},
|
|
view: {
|
|
server: {
|
|
name: 'server-1',
|
|
},
|
|
type: 'view-1',
|
|
},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
viewManager.getCurrentView = jest.fn();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
viewManager.views = new Map();
|
|
delete viewManager.currentView;
|
|
});
|
|
|
|
it('should do nothing when view is already visible or if view doesnt exist', () => {
|
|
const view = {
|
|
...baseView,
|
|
isVisible: true,
|
|
};
|
|
viewManager.views.set('server1-view1', view);
|
|
|
|
viewManager.showById('server1-view1');
|
|
expect(viewManager.currentView).toBeUndefined();
|
|
expect(view.isReady).not.toBeCalled();
|
|
expect(view.show).not.toBeCalled();
|
|
|
|
viewManager.showById('some-view-name');
|
|
expect(viewManager.currentView).toBeUndefined();
|
|
expect(view.isReady).not.toBeCalled();
|
|
expect(view.show).not.toBeCalled();
|
|
});
|
|
|
|
it('should hide current view when new view is shown', () => {
|
|
const oldView = {
|
|
...baseView,
|
|
isVisible: true,
|
|
};
|
|
const newView = {
|
|
...baseView,
|
|
isVisible: false,
|
|
};
|
|
viewManager.getCurrentView.mockImplementation(() => oldView);
|
|
viewManager.views.set('oldView', oldView);
|
|
viewManager.views.set('newView', newView);
|
|
viewManager.currentView = 'oldView';
|
|
viewManager.showById('newView');
|
|
expect(oldView.hide).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not show the view when it is in error state', () => {
|
|
const view = {...baseView};
|
|
view.isErrored.mockReturnValue(true);
|
|
viewManager.views.set('view1', view);
|
|
viewManager.showById('view1');
|
|
expect(view.show).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should show loading screen when the view needs it', () => {
|
|
const view = {...baseView};
|
|
view.isErrored.mockReturnValue(false);
|
|
view.needsLoadingScreen.mockImplementation(() => true);
|
|
viewManager.views.set('view1', view);
|
|
viewManager.showById('view1');
|
|
expect(LoadingScreen.show).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should show the view when not errored', () => {
|
|
const view = {...baseView};
|
|
view.needsLoadingScreen.mockImplementation(() => false);
|
|
view.isErrored.mockReturnValue(false);
|
|
viewManager.views.set('view1', view);
|
|
viewManager.showById('view1');
|
|
expect(viewManager.currentView).toBe('view1');
|
|
expect(view.show).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('handleDeepLink', () => {
|
|
const viewManager = new ViewManager({});
|
|
const baseView = {
|
|
resetLoadingStatus: jest.fn(),
|
|
load: jest.fn(),
|
|
once: jest.fn(),
|
|
isReady: jest.fn(),
|
|
sendToRenderer: jest.fn(),
|
|
serverInfo: {
|
|
remoteInfo: {
|
|
serverVersion: '1.0.0',
|
|
},
|
|
},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
viewManager.openClosedView = jest.fn();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
viewManager.views = new Map();
|
|
viewManager.closedViews = new Map();
|
|
});
|
|
|
|
it('should load URL into matching view', () => {
|
|
ServerManager.lookupViewByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
|
const view = {...baseView};
|
|
viewManager.views.set('view1', view);
|
|
viewManager.handleDeepLink('mattermost://server-1.com/deep/link?thing=yes');
|
|
expect(view.load).toHaveBeenCalledWith('http://server-1.com/deep/link?thing=yes');
|
|
});
|
|
|
|
it('should send the URL to the view if its already loaded on a 6.0 server', () => {
|
|
ServerManager.lookupViewByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
|
ServerManager.getRemoteInfo.mockReturnValue({serverVersion: '6.0.0'});
|
|
const view = {
|
|
...baseView,
|
|
view: {
|
|
server: {
|
|
url: new URL('http://server-1.com'),
|
|
},
|
|
},
|
|
};
|
|
view.isReady.mockImplementation(() => true);
|
|
viewManager.views.set('view1', view);
|
|
viewManager.handleDeepLink('mattermost://server-1.com/deep/link?thing=yes');
|
|
expect(view.sendToRenderer).toHaveBeenCalledWith(BROWSER_HISTORY_PUSH, '/deep/link?thing=yes');
|
|
});
|
|
|
|
it('should throw error if view is missing', () => {
|
|
ServerManager.lookupViewByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
|
const view = {...baseView};
|
|
viewManager.handleDeepLink('mattermost://server-1.com/deep/link?thing=yes');
|
|
expect(view.load).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should throw dialog when cannot find the view', () => {
|
|
const view = {...baseView};
|
|
viewManager.handleDeepLink('mattermost://server-1.com/deep/link?thing=yes');
|
|
expect(view.load).not.toHaveBeenCalled();
|
|
expect(dialog.showErrorBox).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should reopen closed view if called upon', () => {
|
|
ServerManager.lookupViewByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
|
viewManager.closedViews.set('view1', {});
|
|
viewManager.handleDeepLink('mattermost://server-1.com/deep/link?thing=yes');
|
|
expect(viewManager.openClosedView).toHaveBeenCalledWith('view1', 'http://server-1.com/deep/link?thing=yes');
|
|
});
|
|
});
|
|
});
|