[MM-39888][MM-39960][MM-39961] Added tests for common/tabs, common/config, added coverage tool (#1857)

* [MM-39888] Unit tests for common/config

* [MM-39960][MM-39961] Added tests for common/tabs, added coverage tool

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
Devin Binnie
2021-11-10 09:51:56 -05:00
committed by GitHub
parent 38270fcfe8
commit 113d87fe04
8 changed files with 640 additions and 5 deletions

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@ release/
npm-debug.log*
build/
coverage/
dist/
test-results.xml

View File

@@ -44,8 +44,7 @@
"test:e2e:build": "webpack-cli --bail --config webpack.config.test.js",
"test:e2e:run": "electron-mocha -r @babel/register --reporter mocha-circleci-reporter dist/tests/e2e_bundle.js",
"test:unit": "jest",
"test:unit:build": "cross-env NODE_ENV=test webpack-cli --bail --config webpack.config.test.js",
"test:unit:run": "cross-env NODE_ENV=test mocha --reporter mocha-circleci-reporter dist/tests/test_bundle.js",
"test:coverage": "jest --coverage",
"package:all": "cross-env NODE_ENV=production npm-run-all check-build-config package:windows package:mac package:mac-universal package:linux",
"package:windows": "cross-env NODE_ENV=production npm-run-all check-build-config build-prod && electron-builder --win --x64 --ia32 --publish=never",
"package:mac": "cross-env NODE_ENV=production npm-run-all check-build-config build-prod && electron-builder --mac --x64 --arm64 --publish=never",

View File

@@ -0,0 +1,72 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import RegistryConfig from 'common/config/RegistryConfig';
jest.mock('winreg-utf8', () => {
return jest.fn().mockImplementation(({hive, key}) => {
return {
values: (fn) => {
if (hive === 'correct-hive') {
fn(null, [
{
name: `${key}-name-1`,
value: `${key}-value-1`,
},
{
name: `${key}-name-2`,
value: `${key}-value-2`,
},
]);
} else if (hive === 'really-bad-hive') {
throw new Error('This is an error');
} else {
fn('Error', []);
}
},
};
});
});
jest.mock('electron-log', () => ({
error: jest.fn(),
}));
describe('common/config/RegistryConfig', () => {
describe('getRegistryEntryValues', () => {
it('should return correct values', () => {
const registryConfig = new RegistryConfig();
expect(registryConfig.getRegistryEntryValues('correct-hive', 'correct-key')).resolves.toStrictEqual([
{
name: 'correct-key-name-1',
value: 'correct-key-value-1',
},
{
name: 'correct-key-name-2',
value: 'correct-key-value-2',
},
]);
});
it('should return correct value by name', () => {
const registryConfig = new RegistryConfig();
expect(registryConfig.getRegistryEntryValues('correct-hive', 'correct-key', 'correct-key-name-1')).resolves.toBe('correct-key-value-1');
});
it('should return undefined with wrong name', () => {
const registryConfig = new RegistryConfig();
expect(registryConfig.getRegistryEntryValues('correct-hive', 'correct-key', 'wrong-key-name-1')).resolves.toBe(undefined);
});
it('should return undefined with bad hive', () => {
const registryConfig = new RegistryConfig();
expect(registryConfig.getRegistryEntryValues('bad-hive', 'correct-key')).resolves.toBe(undefined);
});
it('should call reject when an error occurs', () => {
const registryConfig = new RegistryConfig();
expect(registryConfig.getRegistryEntryValues('really-bad-hive', 'correct-key')).rejects.toThrow(new Error('This is an error'));
});
});
});

View File

@@ -33,6 +33,7 @@ const defaultPreferences: ConfigV3 = {
autostart: true,
spellCheckerLocales: [],
darkMode: false,
lastActiveTeam: 0,
downloadLocation: getDefaultDownloadLocation(),
};

View File

@@ -0,0 +1,386 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import Config from 'common/config';
const configPath = '/fake/config/path';
jest.mock('electron', () => ({
app: {
name: 'Mattermost',
},
nativeTheme: {
shouldUseDarkColors: false,
},
}));
jest.mock('main/Validator', () => ({
validateV0ConfigData: (configData) => (configData.version === 0 ? configData : null),
validateV1ConfigData: (configData) => (configData.version === 1 ? configData : null),
validateV2ConfigData: (configData) => (configData.version === 2 ? configData : null),
validateV3ConfigData: (configData) => (configData.version === 3 ? configData : null),
}));
jest.mock('electron-log', () => ({
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
}));
jest.mock('common/tabs/TabView', () => ({
getDefaultTeamWithTabsFromTeam: (value) => ({
...value,
tabs: [
{
name: 'tab1',
},
{
name: 'tab2',
},
],
}),
}));
const buildTeam = {
name: 'build-team-1',
order: 0,
url: 'http://build-team-1.com',
};
const buildTeamWithTabs = {
...buildTeam,
tabs: [
{
name: 'tab1',
},
{
name: 'tab2',
},
],
};
const registryTeam = {
name: 'registry-team-1',
order: 0,
url: 'http://registry-team-1.com',
};
const team = {
name: 'team-1',
order: 0,
url: 'http://team-1.com',
tabs: [
{
name: 'tab1',
},
{
name: 'tab2',
},
],
};
jest.mock('common/config/upgradePreferences', () => {
return jest.fn().mockImplementation((configData) => {
return {...configData, version: 10};
});
});
jest.mock('common/config/buildConfig', () => {
return {
defaultTeams: [buildTeam],
};
});
jest.mock('common/config/RegistryConfig', () => {
return jest.fn();
});
describe('common/config', () => {
it('should load buildConfig', () => {
const config = new Config(configPath);
expect(config.predefinedTeams).toContainEqual(buildTeamWithTabs);
});
describe('loadRegistry', () => {
it('should load the registry items and reload the config', () => {
const config = new Config(configPath);
config.reload = jest.fn();
config.loadRegistry({teams: [registryTeam]});
expect(config.reload).toHaveBeenCalled();
expect(config.predefinedTeams).toContainEqual({
...registryTeam,
tabs: [
{
name: 'tab1',
},
{
name: 'tab2',
},
],
});
});
});
describe('reload', () => {
it('should emit update and synchronize events', () => {
const config = new Config(configPath);
config.loadDefaultConfigData = jest.fn();
config.loadBuildConfigData = jest.fn();
config.loadLocalConfigFile = jest.fn();
config.checkForConfigUpdates = jest.fn();
config.regenerateCombinedConfigData = jest.fn().mockImplementation(() => {
config.combinedData = {test: 'test'};
});
config.emit = jest.fn();
config.reload();
expect(config.emit).toHaveBeenNthCalledWith(1, 'update', {test: 'test'});
expect(config.emit).toHaveBeenNthCalledWith(2, 'synchronize');
});
});
describe('set', () => {
it('should set an arbitrary value and save to local config data', () => {
const config = new Config(configPath);
config.localConfigData = {};
config.regenerateCombinedConfigData = jest.fn().mockImplementation(() => {
config.combinedData = {...config.localConfigData};
});
config.saveLocalConfigData = jest.fn();
config.set('setting', 'test_value_1');
expect(config.combinedData.setting).toBe('test_value_1');
expect(config.regenerateCombinedConfigData).toHaveBeenCalled();
expect(config.saveLocalConfigData).toHaveBeenCalled();
});
it('should set teams without including predefined', () => {
const config = new Config(configPath);
config.localConfigData = {};
config.regenerateCombinedConfigData = jest.fn().mockImplementation(() => {
config.combinedData = {...config.localConfigData};
});
config.saveLocalConfigData = jest.fn();
config.set('teams', [{...buildTeamWithTabs, name: 'build-team-2'}, team]);
expect(config.localConfigData.teams).not.toContainEqual({...buildTeamWithTabs, name: 'build-team-2'});
expect(config.localConfigData.teams).toContainEqual(team);
expect(config.predefinedTeams).toContainEqual({...buildTeamWithTabs, name: 'build-team-2'});
});
});
describe('saveLocalConfigData', () => {
it('should emit update and synchronize events on save', () => {
const config = new Config(configPath);
config.localConfigData = {test: 'test'};
config.combinedData = {...config.localConfigData};
config.writeFile = jest.fn().mockImplementation((configFilePath, data, callback) => {
callback();
});
config.emit = jest.fn();
config.saveLocalConfigData();
expect(config.emit).toHaveBeenNthCalledWith(1, 'update', {test: 'test'});
expect(config.emit).toHaveBeenNthCalledWith(2, 'synchronize');
});
it('should emit error when fs.writeSync throws an error', () => {
const config = new Config(configPath);
config.localConfigData = {test: 'test'};
config.combinedData = {...config.localConfigData};
config.writeFile = jest.fn().mockImplementation((configFilePath, data, callback) => {
callback({message: 'Error message'});
});
config.emit = jest.fn();
config.saveLocalConfigData();
expect(config.emit).toHaveBeenNthCalledWith(1, 'error', {message: 'Error message'});
});
it('should emit error when writeFile throws an error', () => {
const config = new Config(configPath);
config.localConfigData = {test: 'test'};
config.combinedData = {...config.localConfigData};
config.writeFile = jest.fn().mockImplementation(() => {
throw new Error('Error message');
});
config.emit = jest.fn();
config.saveLocalConfigData();
expect(config.emit).toHaveBeenNthCalledWith(1, 'error', new Error('Error message'));
});
it('should retry when file is locked', () => {
const testFunc = jest.fn();
const config = new Config(configPath);
config.localConfigData = {test: 'test'};
config.combinedData = {...config.localConfigData};
config.writeFile = jest.fn().mockImplementation((configFilePath, data, callback) => {
config.saveLocalConfigData = testFunc;
callback({code: 'EBUSY'});
});
config.emit = jest.fn();
config.saveLocalConfigData();
expect(testFunc).toHaveBeenCalled();
});
});
describe('loadLocalConfigFile', () => {
it('should use defaults if readFileSync fails', () => {
const config = new Config(configPath);
config.defaultConfigData = {test: 'test'};
config.combinedData = {...config.localConfigData};
config.readFileSync = jest.fn().mockImplementation(() => {
throw new Error('Error message');
});
config.writeFileSync = jest.fn();
const configData = config.loadLocalConfigFile();
expect(configData).toStrictEqual({test: 'test'});
});
it('should use defaults if validation fails', () => {
const config = new Config(configPath);
config.defaultConfigData = {test: 'test'};
config.combinedData = {...config.localConfigData};
config.readFileSync = jest.fn().mockImplementation(() => {
return {version: -1};
});
config.writeFileSync = jest.fn();
const configData = config.loadLocalConfigFile();
expect(configData).toStrictEqual({test: 'test'});
});
it('should return config data if valid', () => {
const config = new Config(configPath);
config.readFileSync = jest.fn().mockImplementation(() => {
return {version: 3};
});
config.writeFileSync = jest.fn();
const configData = config.loadLocalConfigFile();
expect(configData).toStrictEqual({version: 3});
});
});
describe('checkForConfigUpdates', () => {
it('should upgrade to latest version', () => {
const config = new Config(configPath);
config.defaultConfigData = {version: 10};
config.writeFileSync = jest.fn();
const configData = config.checkForConfigUpdates({version: 5, setting: 'true'});
expect(configData).toStrictEqual({version: 10, setting: 'true'});
});
});
describe('regenerateCombinedConfigData', () => {
it('should combine config from all sources', () => {
const config = new Config(configPath);
config.predefinedTeams = [];
config.useNativeWindow = false;
config.defaultConfigData = {defaultSetting: 'default', otherDefaultSetting: 'default'};
config.localConfigData = {otherDefaultSetting: 'local', localSetting: 'local', otherLocalSetting: 'local'};
config.buildConfigData = {otherLocalSetting: 'build', buildSetting: 'build', otherBuildSetting: 'build'};
config.registryConfigData = {otherBuildSetting: 'registry', registrySetting: 'registry'};
config.regenerateCombinedConfigData();
config.combinedData.darkMode = false;
expect(config.combinedData).toStrictEqual({
teams: [],
registryTeams: [],
appName: 'Mattermost',
useNativeWindow: false,
darkMode: false,
otherBuildSetting: 'registry',
registrySetting: 'registry',
otherLocalSetting: 'build',
buildSetting: 'build',
otherDefaultSetting: 'local',
localSetting: 'local',
defaultSetting: 'default',
});
});
it('should combine teams from all sources and filter duplicates', () => {
const config = new Config(configPath);
config.defaultConfigData = {};
config.localConfigData = {};
config.buildConfigData = {enableServerManagement: true};
config.registryConfigData = {};
config.predefinedTeams = [team, team];
config.useNativeWindow = false;
config.localConfigData = {teams: [
team,
{
...team,
name: 'local-team-2',
url: 'http://local-team-2.com',
},
{
...team,
name: 'local-team-1',
order: 1,
url: 'http://local-team-1.com',
},
]};
config.regenerateCombinedConfigData();
config.combinedData.darkMode = false;
expect(config.combinedData).toStrictEqual({
teams: [
team,
{
...team,
name: 'local-team-2',
order: 1,
url: 'http://local-team-2.com',
},
{
...team,
name: 'local-team-1',
order: 2,
url: 'http://local-team-1.com',
},
],
registryTeams: [],
appName: 'Mattermost',
useNativeWindow: false,
darkMode: false,
enableServerManagement: true,
});
});
it('should not include local teams if enableServerManagement is false', () => {
const config = new Config(configPath);
config.defaultConfigData = {};
config.localConfigData = {};
config.buildConfigData = {enableServerManagement: false};
config.registryConfigData = {};
config.predefinedTeams = [team, team];
config.useNativeWindow = false;
config.localConfigData = {teams: [
team,
{
...team,
name: 'local-team-1',
order: 1,
url: 'http://local-team-1.com',
},
]};
config.regenerateCombinedConfigData();
config.combinedData.darkMode = false;
expect(config.combinedData).toStrictEqual({
teams: [team],
registryTeams: [],
appName: 'Mattermost',
useNativeWindow: false,
darkMode: false,
enableServerManagement: false,
});
});
});
});

View File

@@ -0,0 +1,141 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {upgradeV0toV1, upgradeV1toV2, upgradeV2toV3} from 'common/config/upgradePreferences';
import pastDefaultPreferences from 'common/config/pastDefaultPreferences';
jest.mock('common/tabs/TabView', () => ({
getDefaultTeamWithTabsFromTeam: (value) => ({
...value,
tabs: [
{
name: 'tab1',
},
{
name: 'tab2',
},
],
}),
}));
describe('common/config/upgradePreferences', () => {
describe('upgradeV0toV1', () => {
it('should upgrade from v0', () => {
const config = {url: 'http://server-1.com'};
expect(upgradeV0toV1(config)).toStrictEqual({
...pastDefaultPreferences[1],
version: 1,
teams: [
{
name: 'Primary team',
url: config.url,
},
],
});
});
});
describe('upgradeV1toV2', () => {
it('should upgrade from v1', () => {
const config = {
version: 1,
teams: [{
name: 'Primary team',
url: 'http://server-1.com',
}, {
name: 'Secondary team',
url: 'http://server-2.com',
}],
showTrayIcon: true,
trayIconTheme: 'dark',
minimizeToTray: true,
notifications: {
flashWindow: 2,
bounceIcon: true,
bounceIconType: 'informational',
},
showUnreadBadge: false,
useSpellChecker: false,
enableHardwareAcceleration: false,
autostart: false,
spellCheckerLocale: 'en-CA',
};
expect(upgradeV1toV2(config)).toStrictEqual({
...pastDefaultPreferences[2],
...config,
version: 2,
teams: [{
name: 'Primary team',
url: 'http://server-1.com',
order: 0,
}, {
name: 'Secondary team',
url: 'http://server-2.com',
order: 1,
}],
});
});
});
describe('upgradeV2toV3', () => {
it('should upgrade from v2', () => {
const config = {
version: 2,
teams: [{
name: 'Primary team',
url: 'http://server-1.com',
order: 0,
}, {
name: 'Secondary team',
url: 'http://server-2.com',
order: 1,
}],
showTrayIcon: true,
trayIconTheme: 'dark',
minimizeToTray: true,
notifications: {
flashWindow: 2,
bounceIcon: true,
bounceIconType: 'informational',
},
showUnreadBadge: false,
useSpellChecker: false,
enableHardwareAcceleration: false,
autostart: false,
spellCheckerLocale: 'en-CA',
darkMode: true,
downloadLocation: '/some/folder/name',
};
expect(upgradeV2toV3(config)).toStrictEqual({
...pastDefaultPreferences[3],
...config,
version: 3,
teams: [{
name: 'Primary team',
url: 'http://server-1.com',
order: 0,
tabs: [
{
name: 'tab1',
},
{
name: 'tab2',
},
],
lastActiveTab: 0,
}, {
name: 'Secondary team',
url: 'http://server-2.com',
order: 1,
tabs: [
{
name: 'tab1',
},
{
name: 'tab2',
},
],
lastActiveTab: 0,
}],
});
});
});
});

View File

@@ -12,7 +12,7 @@ function deepCopy<T>(object: T): T {
return JSON.parse(JSON.stringify(object));
}
function upgradeV0toV1(configV0: ConfigV0) {
export function upgradeV0toV1(configV0: ConfigV0) {
const config = deepCopy(pastDefaultPreferences[1]);
config.teams.push({
name: 'Primary team',
@@ -21,7 +21,7 @@ function upgradeV0toV1(configV0: ConfigV0) {
return config;
}
function upgradeV1toV2(configV1: ConfigV1) {
export function upgradeV1toV2(configV1: ConfigV1) {
const config: ConfigV2 = Object.assign({}, deepCopy<ConfigV2>(pastDefaultPreferences[2]), configV1);
config.version = 2;
config.teams = configV1.teams.map((value, index) => {
@@ -33,7 +33,7 @@ function upgradeV1toV2(configV1: ConfigV1) {
return config;
}
function upgradeV2toV3(configV2: ConfigV2) {
export function upgradeV2toV3(configV2: ConfigV2) {
const config: ConfigV3 = Object.assign({}, deepCopy<ConfigV3>(pastDefaultPreferences[3]), configV2);
config.version = 3;
config.teams = configV2.teams.map((value) => {

View File

@@ -0,0 +1,35 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {MattermostServer} from 'common/servers/MattermostServer';
import * as TabView from 'common/tabs/TabView';
describe('common/tabs/TabView', () => {
describe('getServerView', () => {
it('should return correct URL on messaging tab', () => {
const server = new MattermostServer('server-1', 'http://server-1.com');
const tab = {name: TabView.TAB_MESSAGING};
expect(TabView.getServerView(server, tab).url).toBe(server.url);
});
it('should return correct URL on playbooks tab', () => {
const server = new MattermostServer('server-1', 'http://server-1.com');
const tab = {name: TabView.TAB_PLAYBOOKS};
expect(TabView.getServerView(server, tab).url.toString()).toBe(`${server.url}playbooks`);
});
it('should return correct URL on boards tab', () => {
const server = new MattermostServer('server-1', 'http://server-1.com');
const tab = {name: TabView.TAB_FOCALBOARD};
expect(TabView.getServerView(server, tab).url.toString()).toBe(`${server.url}boards`);
});
it('should throw error on bad tab name', () => {
const server = new MattermostServer('server-1', 'http://server-1.com');
const tab = {name: 'not a real tab name'};
expect(() => {
TabView.getServerView(server, tab);
}).toThrow(Error);
});
});
});