[MM-14740] Consolidate configuration to support integration of MSI/GPO (#959)

* config logic consolidation

* filter out duplicate servers

* build default teams and GPO teams are not editable

* tweaks

* tweak config architecture to support tests

- config needs to load in each process (main and renderer) and then synchronize with each other
- finished saving ui functionality

* add esdoc comments to new config module

* remove old config-related files

* revert eslint comment

* don’t filter teams, duplicates are allowed

* some code review tweaks

* Remove unecessary deepCopy

* tweak for tests

* Skip test for now

Can’t seem to get this test to work, even though what is being tested works fine in the actual app.

* fix for failing test

click of ‘light’ option wasn’t triggering an update as it is selected by default, so flipped the order to first select ‘dark’ and then ‘light’
This commit is contained in:
Dean Whillier 2019-04-11 07:58:30 -04:00 committed by William Gathoye
parent b7b88c4fbb
commit 4137d0ea23
15 changed files with 618 additions and 422 deletions

View file

@ -53,7 +53,7 @@
"react/jsx-filename-extension": [
1,
{
"extensions": [".js"]
"extensions": [".js", ".jsx"]
}
],
"react/prop-types": [

View file

@ -30,7 +30,6 @@
{
"files": [
"webpack.config.renderer.js",
"test/specs/settings_test.js",
"test/specs/spellchecker_test.js",
"test/specs/app_test.js",
"test/specs/security_test.js",
@ -51,7 +50,6 @@
"LICENSE.txt",
"src/utils/util.js",
"src/main.js",
"src/browser/config/AppConfig.js",
"src/browser/js/contextMenu.js",
"src/browser/updater.jsx",
"src/browser/js/notification.js",
@ -78,13 +76,13 @@
"src/browser/settings.jsx",
"src/browser/index.jsx",
"src/common/deepmerge.js",
"src/common/config/index.js",
"src/common/config/buildConfig.js",
"src/common/config/pastDefaultPreferences.js",
"src/common/config/upgradePreferences.js",
"src/common/osVersion.js",
"src/common/config/defaultPreferences.js",
"src/common/JsonFileManager.js",
"src/common/settings.js",
"src/main/certificateStore.js",
"src/main/mainWindow.js",
"src/main/allowProtocolDialog.js",

View file

@ -6,120 +6,141 @@
/* eslint-disable react/no-set-state */
import React from 'react';
import PropTypes from 'prop-types';
import {Button, Checkbox, Col, FormGroup, Grid, HelpBlock, Navbar, Radio, Row} from 'react-bootstrap';
import {ipcRenderer, remote} from 'electron';
import {debounce} from 'underscore';
import buildConfig from '../../common/config/buildConfig';
import settings from '../../common/settings';
import Config from '../../common/config';
import TeamList from './TeamList.jsx';
import AutoSaveIndicator from './AutoSaveIndicator.jsx';
const CONFIG_TYPE_SERVERS = 'servers';
const CONFIG_TYPE_APP_OPTIONS = 'appOptions';
const config = new Config(remote.app.getPath('userData') + '/config.json');
function backToIndex(index) {
const target = typeof index === 'undefined' ? 0 : index;
const indexURL = remote.getGlobal('isDev') ? 'http://localhost:8080/browser/index.html' : `file://${remote.app.getAppPath()}/browser/index.html`;
remote.getCurrentWindow().loadURL(`${indexURL}?index=${target}`);
}
const CONFIG_TYPE_SERVERS = 'servers';
const CONFIG_TYPE_APP_OPTIONS = 'appOptions';
export default class SettingsPage extends React.Component {
constructor(props) {
super(props);
let initialState;
try {
initialState = settings.readFileSync(this.props.configFile);
} catch (e) {
initialState = settings.loadDefault();
}
initialState.showAddTeamForm = false;
initialState.trayWasVisible = remote.getCurrentWindow().trayWasVisible;
if (initialState.teams.length === 0) {
initialState.showAddTeamForm = true;
}
initialState.savingState = {
appOptions: AutoSaveIndicator.SAVING_STATE_DONE,
servers: AutoSaveIndicator.SAVING_STATE_DONE,
};
this.state = initialState;
this.startSaveConfig = this.startSaveConfig.bind(this);
this.didSaveConfig = this.didSaveConfig.bind(this);
this.handleTeamsChange = this.handleTeamsChange.bind(this);
this.saveConfig = this.saveConfig.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.handleChangeShowTrayIcon = this.handleChangeShowTrayIcon.bind(this);
this.handleChangeTrayIconTheme = this.handleChangeTrayIconTheme.bind(this);
this.handleChangeAutoStart = this.handleChangeAutoStart.bind(this);
this.handleChangeMinimizeToTray = this.handleChangeMinimizeToTray.bind(this);
this.toggleShowTeamForm = this.toggleShowTeamForm.bind(this);
this.setShowTeamFormVisibility = this.setShowTeamFormVisibility.bind(this);
this.handleFlashWindow = this.handleFlashWindow.bind(this);
this.handleBounceIcon = this.handleBounceIcon.bind(this);
this.handleBounceIconType = this.handleBounceIconType.bind(this);
this.handleShowUnreadBadge = this.handleShowUnreadBadge.bind(this);
this.handleChangeUseSpellChecker = this.handleChangeUseSpellChecker.bind(this);
this.handleChangeEnableHardwareAcceleration = this.handleChangeEnableHardwareAcceleration.bind(this);
this.updateTeam = this.updateTeam.bind(this);
this.addServer = this.addServer.bind(this);
this.state = this.convertConfigDataToState(config.data);
this.trayIconThemeRef = React.createRef();
this.saveQueue = [];
}
componentDidMount() {
config.on('update', (configData) => {
this.updateSaveState();
this.setState(this.convertConfigDataToState(configData, this.state));
});
config.on('error', (error) => {
console.log('Config saving error: ', error);
const savingState = Object.assign({}, this.state.savingState);
Object.entries(savingState).forEach(([configType, currentState]) => {
if (currentState !== AutoSaveIndicator.SAVING_STATE_DONE) {
savingState[configType] = AutoSaveIndicator.SAVING_STATE_ERROR;
this.setState({savingState});
}
});
});
// when the config object changes here in the renderer process, tell the main process to reload its config object to get the changes
config.on('synchronize', () => {
ipcRenderer.send('reload-config');
});
// listen for any config reload requests from the main process to reload configuration changes here in the renderer process
ipcRenderer.on('reload-config', () => {
config.reload();
});
ipcRenderer.on('add-server', () => {
this.setState({
showAddTeamForm: true,
});
});
ipcRenderer.on('switch-tab', (event, key) => {
backToIndex(key);
});
}
startSaveConfig(configType) {
if (!this.startSaveConfigImpl) {
this.startSaveConfigImpl = {};
}
if (!this.startSaveConfigImpl[configType]) {
this.startSaveConfigImpl[configType] = debounce(() => {
this.saveConfig((err) => {
if (err) {
console.error(err);
}
const savingState = Object.assign({}, this.state.savingState);
savingState[configType] = err ? AutoSaveIndicator.SAVING_STATE_ERROR : AutoSaveIndicator.SAVING_STATE_SAVED;
this.setState({savingState});
this.didSaveConfig(configType);
});
}, 500);
convertConfigDataToState = (configData, currentState = {}) => {
const newState = Object.assign({}, configData);
newState.showAddTeamForm = currentState.showAddTeamForm || false;
newState.trayWasVisible = currentState.trayWasVisible || remote.getCurrentWindow().trayWasVisible;
if (newState.teams.length === 0 && currentState.firstRun !== false) {
newState.firstRun = false;
newState.showAddTeamForm = true;
}
newState.savingState = currentState.savingState || {
appOptions: AutoSaveIndicator.SAVING_STATE_DONE,
servers: AutoSaveIndicator.SAVING_STATE_DONE,
};
return newState;
}
saveSetting = (configType, {key, data}) => {
this.saveQueue.push({
configType,
key,
data,
});
this.updateSaveState();
this.processSaveQueue();
}
processSaveQueue = debounce(() => {
config.setMultiple(this.saveQueue.splice(0, this.saveQueue.length));
}, 500);
updateSaveState = () => {
let queuedUpdateCounts = {
[CONFIG_TYPE_SERVERS]: 0,
[CONFIG_TYPE_APP_OPTIONS]: 0,
};
queuedUpdateCounts = this.saveQueue.reduce((updateCounts, {configType}) => {
updateCounts[configType]++;
return updateCounts;
}, queuedUpdateCounts);
const savingState = Object.assign({}, this.state.savingState);
savingState[configType] = AutoSaveIndicator.SAVING_STATE_SAVING;
Object.entries(queuedUpdateCounts).forEach(([configType, count]) => {
if (count > 0) {
savingState[configType] = AutoSaveIndicator.SAVING_STATE_SAVING;
} else if (count === 0 && savingState[configType] === AutoSaveIndicator.SAVING_STATE_SAVING) {
savingState[configType] = AutoSaveIndicator.SAVING_STATE_SAVED;
this.resetSaveState(configType);
}
});
this.setState({savingState});
this.startSaveConfigImpl[configType]();
}
didSaveConfig(configType) {
if (!this.didSaveConfigImpl) {
this.didSaveConfigImpl = {};
resetSaveState = debounce((configType) => {
if (this.state.savingState[configType] !== AutoSaveIndicator.SAVING_STATE_SAVING) {
const savingState = Object.assign({}, this.state.savingState);
savingState[configType] = AutoSaveIndicator.SAVING_STATE_DONE;
this.setState({savingState});
}
if (!this.didSaveConfigImpl[configType]) {
this.didSaveConfigImpl[configType] = debounce(() => {
const savingState = Object.assign({}, this.state.savingState);
savingState[configType] = AutoSaveIndicator.SAVING_STATE_DONE;
this.setState({savingState});
}, 2000);
}
this.didSaveConfigImpl[configType]();
}
}, 2000);
handleTeamsChange(teams) {
handleTeamsChange = (teams) => {
setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams});
this.setState({
showAddTeamForm: false,
teams,
@ -127,45 +148,15 @@ export default class SettingsPage extends React.Component {
if (teams.length === 0) {
this.setState({showAddTeamForm: true});
}
setImmediate(this.startSaveConfig, CONFIG_TYPE_SERVERS);
}
saveConfig(callback) {
const config = {
teams: this.state.teams,
showTrayIcon: this.state.showTrayIcon,
trayIconTheme: this.state.trayIconTheme,
version: settings.version,
minimizeToTray: this.state.minimizeToTray,
notifications: {
flashWindow: this.state.notifications.flashWindow,
bounceIcon: this.state.notifications.bounceIcon,
bounceIconType: this.state.notifications.bounceIconType,
},
showUnreadBadge: this.state.showUnreadBadge,
useSpellChecker: this.state.useSpellChecker,
spellCheckerLocale: this.state.spellCheckerLocale,
enableHardwareAcceleration: this.state.enableHardwareAcceleration,
autostart: this.state.autostart,
};
settings.writeFile(this.props.configFile, config, (err) => {
if (err) {
callback(err);
return;
}
ipcRenderer.send('update-menu', config);
ipcRenderer.send('update-config');
callback();
});
}
handleCancel() {
handleCancel = () => {
backToIndex();
}
handleChangeShowTrayIcon() {
handleChangeShowTrayIcon = () => {
const shouldShowTrayIcon = !this.refs.showTrayIcon.props.checked;
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showTrayIcon', data: shouldShowTrayIcon});
this.setState({
showTrayIcon: shouldShowTrayIcon,
});
@ -175,113 +166,129 @@ export default class SettingsPage extends React.Component {
minimizeToTray: false,
});
}
setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS);
}
handleChangeTrayIconTheme() {
handleChangeTrayIconTheme = (theme) => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'trayIconTheme', data: theme});
this.setState({
trayIconTheme: this.trayIconThemeRef.current.value,
trayIconTheme: theme,
});
setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS);
}
handleChangeAutoStart() {
handleChangeAutoStart = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'autostart', data: !this.refs.autostart.props.checked});
this.setState({
autostart: !this.refs.autostart.props.checked,
});
setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS);
}
handleChangeMinimizeToTray() {
handleChangeMinimizeToTray = () => {
const shouldMinimizeToTray = this.state.showTrayIcon && !this.refs.minimizeToTray.props.checked;
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'minimizeToTray', data: shouldMinimizeToTray});
this.setState({
minimizeToTray: shouldMinimizeToTray,
});
setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS);
}
toggleShowTeamForm() {
toggleShowTeamForm = () => {
this.setState({
showAddTeamForm: !this.state.showAddTeamForm,
});
document.activeElement.blur();
}
setShowTeamFormVisibility(val) {
setShowTeamFormVisibility = (val) => {
this.setState({
showAddTeamForm: val,
});
}
handleFlashWindow() {
handleFlashWindow = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {
key: 'notifications',
data: {
...this.state.notifications,
flashWindow: this.refs.flashWindow.props.checked ? 0 : 2,
},
});
this.setState({
notifications: {
...this.state.notifications,
flashWindow: this.refs.flashWindow.props.checked ? 0 : 2,
},
});
setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS);
}
handleBounceIcon() {
handleBounceIcon = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {
key: 'notifications',
data: {
...this.state.notifications,
bounceIcon: !this.refs.bounceIcon.props.checked,
},
});
this.setState({
notifications: {
...this.state.notifications,
bounceIcon: !this.refs.bounceIcon.props.checked,
},
});
setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS);
}
handleBounceIconType(event) {
handleBounceIconType = (event) => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {
key: 'notifications',
data: {
...this.state.notifications,
bounceIconType: event.target.value,
},
});
this.setState({
notifications: {
...this.state.notifications,
bounceIconType: event.target.value,
},
});
setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS);
}
handleShowUnreadBadge() {
handleShowUnreadBadge = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showUnreadBadge', data: !this.refs.showUnreadBadge.props.checked});
this.setState({
showUnreadBadge: !this.refs.showUnreadBadge.props.checked,
});
setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS);
}
handleChangeUseSpellChecker() {
handleChangeUseSpellChecker = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'useSpellChecker', data: !this.refs.useSpellChecker.props.checked});
this.setState({
useSpellChecker: !this.refs.useSpellChecker.props.checked,
});
setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS);
}
handleChangeEnableHardwareAcceleration() {
handleChangeEnableHardwareAcceleration = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'enableHardwareAcceleration', data: !this.refs.enableHardwareAcceleration.props.checked});
this.setState({
enableHardwareAcceleration: !this.refs.enableHardwareAcceleration.props.checked,
});
setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS);
}
updateTeam(index, newData) {
const teams = this.state.teams;
updateTeam = (index, newData) => {
const teams = this.state.localTeams;
teams[index] = newData;
setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams});
this.setState({
teams,
});
setImmediate(this.startSaveConfig, CONFIG_TYPE_SERVERS);
}
addServer(team) {
const teams = this.state.teams;
addServer = (team) => {
const teams = this.state.localTeams;
teams.push(team);
setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams});
this.setState({
teams,
});
setImmediate(this.startSaveConfig, CONFIG_TYPE_SERVERS);
}
render() {
@ -324,7 +331,7 @@ export default class SettingsPage extends React.Component {
<Row>
<Col md={12}>
<TeamList
teams={this.state.teams}
teams={this.state.localTeams}
showAddTeamForm={this.state.showAddTeamForm}
toggleAddTeamForm={this.toggleShowTeamForm}
setAddTeamFormVisibility={this.setShowTeamFormVisibility}
@ -333,7 +340,7 @@ export default class SettingsPage extends React.Component {
addServer={this.addServer}
allowTeamEdit={this.state.enableTeamModification}
onTeamClick={(index) => {
backToIndex(index + buildConfig.defaultTeams.length);
backToIndex(index + this.state.buildTeams.length + this.state.GPOTeams.length);
}}
/>
</Col>
@ -372,7 +379,7 @@ export default class SettingsPage extends React.Component {
);
let srvMgmt;
if (this.props.enableServerManagement === true) {
if (this.state.enableServerManagement === true) {
srvMgmt = (
<div>
{serversRow}
@ -393,7 +400,8 @@ export default class SettingsPage extends React.Component {
ref='autostart'
checked={this.state.autostart}
onChange={this.handleChangeAutoStart}
>{'Start app on login'}
>
{'Start app on login'}
<HelpBlock>
{'If enabled, the app starts automatically when you log in to your machine.'}
{' '}
@ -426,7 +434,8 @@ export default class SettingsPage extends React.Component {
ref='showUnreadBadge'
checked={this.state.showUnreadBadge}
onChange={this.handleShowUnreadBadge}
>{`Show red badge on ${TASKBAR} icon to indicate unread messages`}
>
{`Show red badge on ${TASKBAR} icon to indicate unread messages`}
<HelpBlock>
{`Regardless of this setting, mentions are always indicated with a red badge and item count on the ${TASKBAR} icon.`}
</HelpBlock>
@ -441,7 +450,8 @@ export default class SettingsPage extends React.Component {
ref='flashWindow'
checked={this.state.notifications.flashWindow === 2}
onChange={this.handleFlashWindow}
>{'Flash app window and taskbar icon when a new message is received'}
>
{'Flash app window and taskbar icon when a new message is received'}
<HelpBlock>
{'If enabled, app window and taskbar icon flash for a few seconds when a new message is received.'}
</HelpBlock>
@ -459,7 +469,8 @@ export default class SettingsPage extends React.Component {
checked={this.state.notifications.bounceIcon}
onChange={this.handleBounceIcon}
style={{marginRight: '10px'}}
>{'Bounce the Dock icon'}
>
{'Bounce the Dock icon'}
</Checkbox>
<Radio
inline={true}
@ -471,7 +482,9 @@ export default class SettingsPage extends React.Component {
this.state.notifications.bounceIconType === 'informational'
}
onChange={this.handleBounceIconType}
>{'once'}</Radio>
>
{'once'}
</Radio>
{' '}
<Radio
inline={true}
@ -480,7 +493,9 @@ export default class SettingsPage extends React.Component {
disabled={!this.state.notifications.bounceIcon}
defaultChecked={this.state.notifications.bounceIconType === 'critical'}
onChange={this.handleBounceIconType}
>{'until I open the app'}</Radio>
>
{'until I open the app'}
</Radio>
<HelpBlock
style={{marginLeft: '20px'}}
>
@ -498,14 +513,14 @@ export default class SettingsPage extends React.Component {
ref='showTrayIcon'
checked={this.state.showTrayIcon}
onChange={this.handleChangeShowTrayIcon}
>{process.platform === 'darwin' ?
`Show ${remote.app.getName()} icon in the menu bar` :
'Show icon in the notification area'}
>
{process.platform === 'darwin' ? `Show ${remote.app.getName()} icon in the menu bar` : 'Show icon in the notification area'}
<HelpBlock>
{'Setting takes effect after restarting the app.'}
</HelpBlock>
</Checkbox>);
}
if (process.platform === 'linux') {
options.push(
<FormGroup
@ -519,21 +534,17 @@ export default class SettingsPage extends React.Component {
name='trayIconTheme'
value='light'
defaultChecked={this.state.trayIconTheme === 'light' || this.state.trayIconTheme === ''}
onChange={() => {
this.setState({trayIconTheme: 'light'});
setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS);
}}
>{'Light'}</Radio>
onChange={(event) => this.handleChangeTrayIconTheme('light', event)}
>
{'Light'}
</Radio>
{' '}
<Radio
inline={true}
name='trayIconTheme'
value='dark'
defaultChecked={this.state.trayIconTheme === 'dark'}
onChange={() => {
this.setState({trayIconTheme: 'dark'});
setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS);
}}
onChange={(event) => this.handleChangeTrayIconTheme('dark', event)}
>{'Dark'}</Radio>
</FormGroup>
);
@ -607,7 +618,7 @@ export default class SettingsPage extends React.Component {
bsStyle='link'
style={settingsPage.close}
onClick={this.handleCancel}
disabled={settings.mergeDefaultTeams(this.state.teams).length === 0}
disabled={this.state.localTeams.length === 0}
>
<span>{'×'}</span>
</Button>
@ -625,9 +636,4 @@ export default class SettingsPage extends React.Component {
}
}
SettingsPage.propTypes = {
configFile: PropTypes.string,
enableServerManagement: PropTypes.bool,
};
/* eslint-enable react/no-set-state */

View file

@ -1,26 +0,0 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {remote} from 'electron';
import settings from '../../common/settings';
class AppConfig {
constructor(file) {
this.fileName = file;
try {
this.data = settings.readFileSync(file);
} catch (e) {
this.data = {
teams: [],
};
}
}
set(key, value) {
this.data[key] = value;
settings.writeFileSync(this.fileName, this.data);
}
}
export default new AppConfig(remote.app.getPath('userData') + '/config.json');

View file

@ -1,7 +1,6 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
'use strict';
import './css/index.css';
@ -15,15 +14,16 @@ import React from 'react';
import ReactDOM from 'react-dom';
import {remote, ipcRenderer} from 'electron';
import buildConfig from '../common/config/buildConfig';
import settings from '../common/settings';
import utils from '../utils/util';
import Config from '../common/config';
import MainPage from './components/MainPage.jsx';
import AppConfig from './config/AppConfig.js';
import {createDataURL as createBadgeDataURL} from './js/badge';
const teams = settings.mergeDefaultTeams(AppConfig.data.teams);
const config = new Config(remote.app.getPath('userData') + '/config.json');
const teams = config.teams;
remote.getCurrentWindow().removeAllListeners('focus');
@ -31,6 +31,30 @@ if (teams.length === 0) {
remote.getCurrentWindow().loadFile('browser/settings.html');
}
const permissionRequestQueue = [];
const requestingPermission = new Array(teams.length);
const parsedURL = url.parse(window.location.href, true);
const initialIndex = parsedURL.query.index ? parseInt(parsedURL.query.index, 10) : 0;
let deeplinkingUrl = null;
if (!parsedURL.query.index || parsedURL.query.index === null) {
deeplinkingUrl = remote.getCurrentWindow().deeplinkingUrl;
}
config.on('update', (configData) => {
teams.splice(0, teams.length, ...configData.teams);
requestingPermission.length = teams.length;
});
config.on('synchronize', () => {
ipcRenderer.send('reload-config');
});
ipcRenderer.on('reload-config', () => {
config.reload();
});
function showBadgeWindows(sessionExpired, unreadCount, mentionCount) {
function sendBadge(dataURL, description) {
// window.setOverlayIcon() does't work with NativeImage across remote boundaries.
@ -50,7 +74,7 @@ function showBadgeWindows(sessionExpired, unreadCount, mentionCount) {
} else if (mentionCount > 0) {
const dataURL = createBadgeDataURL(mentionCount.toString());
sendBadge(dataURL, 'You have unread mentions (' + mentionCount + ')');
} else if (unreadCount > 0 && AppConfig.data.showUnreadBadge) {
} else if (unreadCount > 0 && config.showUnreadBadge) {
const dataURL = createBadgeDataURL('•');
sendBadge(dataURL, 'You have unread channels (' + unreadCount + ')');
} else {
@ -63,7 +87,7 @@ function showBadgeOSX(sessionExpired, unreadCount, mentionCount) {
remote.app.dock.setBadge('•');
} else if (mentionCount > 0) {
remote.app.dock.setBadge(mentionCount.toString());
} else if (unreadCount > 0 && AppConfig.data.showUnreadBadge) {
} else if (unreadCount > 0 && config.showUnreadBadge) {
remote.app.dock.setBadge('•');
} else {
remote.app.dock.setBadge('');
@ -106,15 +130,8 @@ function showBadge(sessionExpired, unreadCount, mentionCount) {
}
}
const permissionRequestQueue = [];
const requestingPermission = new Array(AppConfig.data.teams.length);
function teamConfigChange(updatedTeams) {
AppConfig.set('teams', updatedTeams.slice(buildConfig.defaultTeams.length));
teams.splice(0, teams.length, ...updatedTeams);
requestingPermission.length = teams.length;
ipcRenderer.send('update-menu', AppConfig.data);
ipcRenderer.send('update-config');
config.set('teams', updatedTeams);
}
function feedPermissionRequest() {
@ -152,6 +169,10 @@ function handleClickPermissionDialog(index, status) {
feedPermissionRequest();
}
function handleSelectSpellCheckerLocale(locale) {
config.set('spellCheckerLocale', locale);
}
ipcRenderer.on('request-permission', (event, origin, permission) => {
if (permissionRequestQueue.length >= 100) {
return;
@ -160,31 +181,16 @@ ipcRenderer.on('request-permission', (event, origin, permission) => {
feedPermissionRequest();
});
function handleSelectSpellCheckerLocale(locale) {
console.log(locale);
AppConfig.set('spellCheckerLocale', locale);
ipcRenderer.send('update-config');
ipcRenderer.send('update-dict');
}
const parsedURL = url.parse(window.location.href, true);
const initialIndex = parsedURL.query.index ? parseInt(parsedURL.query.index, 10) : 0;
let deeplinkingUrl = null;
if (!parsedURL.query.index || parsedURL.query.index === null) {
deeplinkingUrl = remote.getCurrentWindow().deeplinkingUrl;
}
ReactDOM.render(
<MainPage
teams={teams}
initialIndex={initialIndex}
onBadgeChange={showBadge}
onTeamConfigChange={teamConfigChange}
useSpellChecker={AppConfig.data.useSpellChecker}
useSpellChecker={config.useSpellChecker}
onSelectSpellCheckerLocale={handleSelectSpellCheckerLocale}
deeplinkingUrl={deeplinkingUrl}
showAddServerButton={buildConfig.enableServerManagement}
showAddServerButton={config.enableServerManagement}
requestingPermission={requestingPermission}
onClickPermissionDialog={handleClickPermissionDialog}
/>,

View file

@ -1,7 +1,6 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
'use strict';
import {remote} from 'electron';
window.eval = global.eval = () => { // eslint-disable-line no-multi-assign, no-eval
@ -11,20 +10,13 @@ window.eval = global.eval = () => { // eslint-disable-line no-multi-assign, no-e
import React from 'react';
import ReactDOM from 'react-dom';
import buildConfig from '../common/config/buildConfig';
import SettingsPage from './components/SettingsPage.jsx';
import contextMenu from './js/contextMenu';
const configFile = remote.app.getPath('userData') + '/config.json';
contextMenu.setup(remote.getCurrentWindow());
ReactDOM.render(
<SettingsPage
configFile={configFile}
enableServerManagement={buildConfig.enableServerManagement}
/>,
<SettingsPage/>,
document.getElementById('content')
);

354
src/common/config/index.js Normal file
View file

@ -0,0 +1,354 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import fs from 'fs';
import path from 'path';
import {EventEmitter} from 'events';
import defaultPreferences from './defaultPreferences';
import upgradeConfigData from './upgradePreferences';
import buildConfig from './buildConfig';
/**
* Handles loading and merging all sources of configuration as well as saving user provided config
*/
export default class Config extends EventEmitter {
constructor(configFilePath) {
super();
this.configFilePath = configFilePath;
this.reload();
}
/**
* Reload all sources of config data
*
* @param {boolean} synchronize determines whether or not to emit a synchronize event once config has been reloaded
* @emits {update} emitted once all data has been loaded and merged
* @emits {synchronize} emitted when requested by a call to method; used to notify other config instances of changes
*/
reload(synchronize = false) {
this.defaultConfigData = this.loadDefaultConfigData();
this.buildConfigData = this.loadBuildConfigData();
this.localConfigData = this.loadLocalConfigFile();
this.localConfigData = this.checkForConfigUpdates(this.localConfigData);
this.GPOConfigData = this.loadGPOConfigData();
this.regenerateCombinedConfigData();
this.emit('update', this.combinedData);
if (synchronize) {
this.emit('synchronize');
}
}
/**
* Used to save a single config property
*
* @param {string} key name of config property to be saved
* @param {*} data value to save for provided key
*/
set(key, data) {
if (key) {
this.localConfigData[key] = data;
this.regenerateCombinedConfigData();
this.saveLocalConfigData();
}
}
/**
* Used to save an array of config properties in one go
*
* @param {array} properties an array of config properties to save
*/
setMultiple(properties = []) {
if (properties.length) {
properties.forEach(({key, data}) => {
if (key) {
this.localConfigData[key] = data;
}
});
this.regenerateCombinedConfigData();
this.saveLocalConfigData();
}
}
/**
* Used to replace the existing config data with new config data
*
* @param {object} configData a new, config data object to completely replace the existing config data
*/
replace(configData) {
const newConfigData = configData;
this.localConfigData = Object.assign({}, this.localConfigData, newConfigData);
this.regenerateCombinedConfigData();
this.saveLocalConfigData();
}
/**
* Used to save the current set of local config data to disk
*
* @emits {update} emitted once all data has been saved
* @emits {synchronize} emitted once all data has been saved; used to notify other config instances of changes
* @emits {error} emitted if saving local config data to file fails
*/
saveLocalConfigData() {
try {
this.writeFile(this.configFilePath, this.localConfigData, (error) => {
if (error) {
throw new Error(error);
}
this.emit('update', this.combinedData);
this.emit('synchronize');
});
} catch (error) {
this.emit('error', error);
}
}
// getters for accessing the various config data inputs
get data() {
return this.combinedData;
}
get localData() {
return this.localConfigData;
}
get defaultData() {
return this.defaultConfigData;
}
get buildData() {
return this.buildConfigData;
}
get GPOData() {
return this.GPOConfigData;
}
// convenience getters
get version() {
return this.combinedData.version;
}
get teams() {
return this.combinedData.teams;
}
get localTeams() {
return this.localConfigData.teams;
}
get predefinedTeams() {
return [...this.buildConfigData.defaultTeams, ...this.GPOConfigData.teams];
}
get enableHardwareAcceleration() {
return this.combinedData.enableHardwareAcceleration;
}
get enableServerManagement() {
return this.combinedData.enableServerManagement;
}
get enableAutoUpdater() {
return this.combinedData.enableAutoUpdater;
}
get autostart() {
return this.combinedData.autostart;
}
get notifications() {
return this.combinedData.notifications;
}
get showUnreadBadge() {
return this.combinedData.showUnreadBadge;
}
get useSpellChecker() {
return this.combinedData.useSpellChecker;
}
get spellCheckerLocale() {
return this.combinedData.spellCheckerLocale;
}
get showTrayIcon() {
return this.combinedData.showTrayIcon;
}
get trayIconTheme() {
return this.combinedData.trayIconTheme;
}
get helpLink() {
return this.combinedData.helpLink;
}
// initialization/processing methods
/**
* Returns a copy of the app's default config data
*/
loadDefaultConfigData() {
return this.copy(defaultPreferences);
}
/**
* Returns a copy of the app's build config data
*/
loadBuildConfigData() {
return this.copy(buildConfig);
}
/**
* Loads and returns locally stored config data from the filesystem or returns app defaults if no file is found
*/
loadLocalConfigFile() {
let configData = {};
try {
configData = this.readFileSync(this.configFilePath);
} catch (e) {
console.log('Failed to load configuration file from the filesystem. Using defaults.');
configData = this.copy(this.defaultConfigData);
// add default team to teams if one exists and there arent currently any teams
if (!configData.teams.length && this.defaultConfigData.defaultTeam) {
configData.teams.push(this.defaultConfigData.defaultTeam);
}
delete configData.defaultTeam;
this.writeFileSync(this.configFilePath, configData);
}
return configData;
}
/**
* Loads and returns config data defined in GPO for Windows
*/
loadGPOConfigData() {
const configData = {
teams: [],
enableServerManagement: true,
enableAutoUpdater: true,
};
if (process.platform === 'win32') {
//
// TODO: GPO data needs to be retrieved here and merged into the local `configData` variable for return
//
}
return configData;
}
/**
* Determines if locally stored data needs to be updated and upgrades as needed
*
* @param {*} data locally stored data
*/
checkForConfigUpdates(data) {
let configData = data;
try {
if (configData.version !== this.defaultConfigData.version) {
configData = upgradeConfigData(configData);
this.writeFileSync(this.configFilePath, configData);
console.log(`Configuration updated to version ${this.defaultConfigData.version} successfully.`);
}
} catch (error) {
console.log(`Failed to update configuration to version ${this.defaultConfigData.version}.`);
}
return configData;
}
/**
* Properly combines all sources of data into a single, manageable set of all config data
*/
regenerateCombinedConfigData() {
// combine all config data in the correct order
this.combinedData = Object.assign({}, this.defaultConfigData, this.localConfigData, this.buildConfigData, this.GPOConfigData);
// remove unecessary data pulled from default and build config
delete this.combinedData.defaultTeam;
delete this.combinedData.defaultTeams;
// IMPORTANT: properly combine teams from all sources
const combinedTeams = [];
// - start by adding default teams from buildConfig, if any
if (this.buildConfigData.defaultTeams && this.buildConfigData.defaultTeams.length) {
combinedTeams.push(...this.buildConfigData.defaultTeams);
}
// - add GPO defined teams, if any
if (this.GPOConfigData.teams && this.GPOConfigData.teams.length) {
combinedTeams.push(...this.GPOConfigData.teams);
}
// - add locally defined teams only if server management is enabled
if (this.enableServerManagement) {
combinedTeams.push(...this.localConfigData.teams);
}
this.combinedData.teams = combinedTeams;
this.combinedData.localTeams = this.localConfigData.teams;
this.combinedData.buildTeams = this.buildConfigData.defaultTeams;
this.combinedData.GPOTeams = this.GPOConfigData.teams;
}
/**
* Returns the provided list of teams with duplicates filtered out
*
* @param {array} teams array of teams to check for duplicates
*/
filterOutDuplicateTeams(teams) {
let newTeams = teams;
const uniqueURLs = new Set();
newTeams = newTeams.filter((team) => {
return uniqueURLs.has(team.url) ? false : uniqueURLs.add(team.url);
});
return newTeams;
}
/**
* Returns the provided array fo teams with existing teams filtered out
* @param {array} teams array of teams to check for already defined teams
*/
filterOutPredefinedTeams(teams) {
let newTeams = teams;
// filter out predefined teams
newTeams = newTeams.filter((newTeam) => {
return this.predefinedTeams.findIndex((existingTeam) => newTeam.url === existingTeam.url) === -1; // eslint-disable-line max-nested-callbacks
});
return newTeams;
}
// helper functions
readFileSync(filePath) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
writeFile(filePath, configData, callback) {
if (configData.version !== this.defaultConfigData.version) {
throw new Error('version ' + configData.version + ' is not equal to ' + this.defaultConfigData.version);
}
const json = JSON.stringify(configData, null, ' ');
fs.writeFile(filePath, json, 'utf8', callback);
}
writeFileSync(filePath, config) {
if (config.version !== this.defaultConfigData.version) {
throw new Error('version ' + config.version + ' is not equal to ' + this.defaultConfigData.version);
}
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
const json = JSON.stringify(config, null, ' ');
fs.writeFileSync(filePath, json, 'utf8');
}
merge(base, target) {
return Object.assign({}, base, target);
}
copy(data) {
return Object.assign({}, data);
}
}

View file

@ -1,78 +0,0 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
'use strict';
import fs from 'fs';
import path from 'path';
import buildConfig from './config/buildConfig';
function merge(base, target) {
return Object.assign({}, base, target);
}
import defaultPreferences from './config/defaultPreferences';
import upgradePreferences from './config/upgradePreferences';
function loadDefault() {
return JSON.parse(JSON.stringify(defaultPreferences));
}
function hasBuildConfigDefaultTeams(config) {
return config.defaultTeams.length > 0;
}
function upgrade(config) {
return upgradePreferences(config);
}
export default {
version: defaultPreferences.version,
upgrade,
readFileSync(configFile) {
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
if (config.version === defaultPreferences.version) {
const defaultConfig = loadDefault();
return merge(defaultConfig, config);
}
return config;
},
writeFile(configFile, config, callback) {
if (config.version !== defaultPreferences.version) {
throw new Error('version ' + config.version + ' is not equal to ' + defaultPreferences.version);
}
const data = JSON.stringify(config, null, ' ');
fs.writeFile(configFile, data, 'utf8', callback);
},
writeFileSync(configFile, config) {
if (config.version !== defaultPreferences.version) {
throw new Error('version ' + config.version + ' is not equal to ' + defaultPreferences.version);
}
const dir = path.dirname(configFile);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
const data = JSON.stringify(config, null, ' ');
fs.writeFileSync(configFile, data, 'utf8');
},
loadDefault,
mergeDefaultTeams(teams) {
const newTeams = [];
if (hasBuildConfigDefaultTeams(buildConfig)) {
newTeams.push(...JSON.parse(JSON.stringify(buildConfig.defaultTeams)));
}
if (buildConfig.enableServerManagement) {
newTeams.push(...JSON.parse(JSON.stringify(teams)));
}
return newTeams;
},
};

View file

@ -1,7 +1,6 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
'use strict';
import os from 'os';
import path from 'path';
@ -29,7 +28,6 @@ import AutoLauncher from './main/AutoLauncher';
import CriticalErrorHandler from './main/CriticalErrorHandler';
import upgradeAutoLaunch from './main/autoLaunch';
import autoUpdater from './main/autoUpdater';
import buildConfig from './common/config/buildConfig';
const criticalErrorHandler = new CriticalErrorHandler();
@ -39,7 +37,7 @@ global.willAppQuit = false;
app.setAppUserModelId('com.squirrel.mattermost.Mattermost'); // Use explicit AppUserModelID
import settings from './common/settings';
import Config from './common/config';
import CertificateStore from './main/certificateStore';
const certificateStore = CertificateStore.load(path.resolve(app.getPath('userData'), 'certificate.json'));
import createMainWindow from './main/mainWindow';
@ -65,6 +63,7 @@ let deeplinkingUrl = null;
let scheme = null;
let appState = null;
let permissionManager = null;
let config = null;
const argv = parseArgv(process.argv.slice(1));
const hideOnStartup = shouldBeHiddenOnStartup(argv);
@ -75,31 +74,14 @@ if (argv['data-dir']) {
global.isDev = isDev && !argv.disableDevMode;
let config = {};
try {
const configFile = app.getPath('userData') + '/config.json';
config = settings.readFileSync(configFile);
if (config.version !== settings.version) {
config = settings.upgrade(config);
settings.writeFileSync(configFile, config);
}
} catch (e) {
config = settings.loadDefault();
console.log('Failed to read or upgrade config.json', e);
if (!config.teams.length && config.defaultTeam) {
config.teams.push(config.defaultTeam);
config = new Config(app.getPath('userData') + '/config.json');
const configFile = app.getPath('userData') + '/config.json';
settings.writeFileSync(configFile, config);
}
}
// can only call this before the app is ready
if (config.enableHardwareAcceleration === false) {
app.disableHardwareAcceleration();
}
ipcMain.on('update-config', () => {
const configFile = app.getPath('userData') + '/config.json';
config = settings.readFileSync(configFile);
config.on('update', (configData) => {
if (process.platform === 'win32' || process.platform === 'linux') {
const appLauncher = new AutoLauncher();
const autoStartTask = config.autostart ? appLauncher.enable() : appLauncher.disable();
@ -109,9 +91,26 @@ ipcMain.on('update-config', () => {
console.log('error:', err);
});
}
const trustedURLs = settings.mergeDefaultTeams(config.teams).map((team) => team.url);
permissionManager.setTrustedURLs(trustedURLs);
ipcMain.emit('update-dict', true, config.spellCheckerLocale);
if (permissionManager) {
const trustedURLs = config.teams.map((team) => team.url);
permissionManager.setTrustedURLs(trustedURLs);
ipcMain.emit('update-dict', true, config.spellCheckerLocale);
}
ipcMain.emit('update-menu', true, configData);
});
// when the config object changes here in the main process, tell the renderer process to reload any initialized config objects to get the changes
config.on('synchronize', () => {
if (mainWindow) {
mainWindow.webContents.send('reload-config');
}
});
// listen for any config reload requests from the renderer process to reload configuration changes here in the main process
ipcMain.on('reload-config', () => {
config.reload();
});
// Only for OS X
@ -419,9 +418,7 @@ app.on('ready', () => {
}
if (!config.spellCheckerLocale) {
config.spellCheckerLocale = SpellChecker.getSpellCheckerLocale(app.getLocale());
const configFile = app.getPath('userData') + '/config.json';
settings.writeFileSync(configFile, config);
config.set('spellCheckerLocale', SpellChecker.getSpellCheckerLocale(app.getLocale()));
}
const appStateJson = path.join(app.getPath('userData'), 'app-state.json');
@ -455,7 +452,7 @@ app.on('ready', () => {
initCookieManager(session.defaultSession);
mainWindow = createMainWindow(config, {
mainWindow = createMainWindow(config.data, {
hideOnStartup,
linuxAppIcon: path.join(assetsDir, 'appicon.png'),
deeplinkingUrl,
@ -618,7 +615,7 @@ app.on('ready', () => {
}
}
});
ipcMain.emit('update-menu', true, config);
ipcMain.emit('update-menu', true, config.data);
ipcMain.on('update-dict', () => {
if (config.useSpellChecker) {
@ -665,11 +662,11 @@ app.on('ready', () => {
ipcMain.emit('update-dict');
const permissionFile = path.join(app.getPath('userData'), 'permission.json');
const trustedURLs = settings.mergeDefaultTeams(config.teams).map((team) => team.url);
const trustedURLs = config.teams.map((team) => team.url);
permissionManager = new PermissionManager(permissionFile, trustedURLs);
session.defaultSession.setPermissionRequestHandler(permissionRequestHandler(mainWindow, permissionManager));
if (buildConfig.enableAutoUpdater) {
if (config.enableAutoUpdater) {
const updaterConfig = autoUpdater.loadConfig();
autoUpdater.initialize(appState, mainWindow, updaterConfig.isNotifyOnly());
ipcMain.on('check-for-updates', autoUpdater.checkForUpdates);
@ -694,7 +691,7 @@ app.on('web-contents-created', (dc, contents) => {
});
contents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);
const trustedURLs = settings.mergeDefaultTeams(config.teams).map((team) => new URL(team.url)); //eslint-disable-line max-nested-callbacks
const trustedURLs = config.teams.map((team) => new URL(team.url)); //eslint-disable-line max-nested-callbacks
let trusted = false;
for (const url of trustedURLs) {

View file

@ -5,9 +5,6 @@
import {app, dialog, ipcMain, Menu, shell} from 'electron';
import settings from '../../common/settings';
import buildConfig from '../../common/config/buildConfig';
function createTemplate(mainWindow, config, isDev) {
const settingsURL = isDev ? 'http://localhost:8080/browser/settings.html' : `file://${app.getAppPath()}/browser/settings.html`;
@ -42,7 +39,7 @@ function createTemplate(mainWindow, config, isDev) {
},
}];
if (buildConfig.enableServerManagement === true) {
if (config.enableServerManagement === true) {
platformAppMenu.push({
label: 'Sign in to Another Server',
click() {
@ -189,7 +186,7 @@ function createTemplate(mainWindow, config, isDev) {
}],
});
const teams = settings.mergeDefaultTeams(config.teams);
const teams = config.teams;
const windowMenu = {
label: '&Window',
submenu: [{
@ -223,11 +220,11 @@ function createTemplate(mainWindow, config, isDev) {
};
template.push(windowMenu);
const submenu = [];
if (buildConfig.helpLink) {
if (config.helpLink) {
submenu.push({
label: 'Learn More...',
click() {
shell.openExternal(buildConfig.helpLink);
shell.openExternal(config.helpLink);
},
});
submenu.push(separatorItem);
@ -236,7 +233,7 @@ function createTemplate(mainWindow, config, isDev) {
label: `Version ${app.getVersion()}`,
enabled: false,
});
if (buildConfig.enableAutoUpdater) {
if (config.enableAutoUpdater) {
submenu.push({
label: 'Check for Updates...',
click() {

View file

@ -5,11 +5,9 @@
import {app, Menu} from 'electron';
import settings from '../../common/settings';
function createTemplate(mainWindow, config, isDev) {
const settingsURL = isDev ? 'http://localhost:8080/browser/settings.html' : `file://${app.getAppPath()}/browser/settings.html`;
const teams = settings.mergeDefaultTeams(config.teams);
const teams = config.teams;
const template = [
...teams.slice(0, 9).map((team, i) => {
return {

11
src/package-lock.json generated
View file

@ -171,10 +171,6 @@
"resolved": "https://registry.npmjs.org/cross-unzip/-/cross-unzip-0.0.2.tgz",
"integrity": "sha1-UYO8R6CVWb78+YzEZXlkmZNZNy8="
},
"damerau-levenshtein": {
"version": "git://github.com/cbaatz/damerau-levenshtein.git#8d7440a51ae9dc6912e44385115c7cb1da4e8ebc",
"from": "git://github.com/cbaatz/damerau-levenshtein.git"
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@ -642,9 +638,14 @@
"integrity": "sha512-RGgpBBn4p65nOhZdBHDt84Remz3QvmoQDppKJX0tYS53SbuUJwm7zG1swT5O770zPvN6wZaZlc8ie/jwlZbeHA==",
"requires": {
"binarysearch": "^0.2.4",
"damerau-levenshtein": "git://github.com/cbaatz/damerau-levenshtein.git",
"strip-bom": "^2.0.0",
"unzip-stream": "^0.3.0"
},
"dependencies": {
"damerau-levenshtein": {
"version": "git://github.com/cbaatz/damerau-levenshtein.git#8d7440a51ae9dc6912e44385115c7cb1da4e8ebc",
"from": "git://github.com/cbaatz/damerau-levenshtein.git#8d7440a51ae9dc6912e44385115c7cb1da4e8ebc"
}
}
},
"sort-keys": {

View file

@ -82,7 +82,8 @@ describe('application', function desc() {
});
it('should upgrade v0 config file', async () => {
const settings = require('../../src/common/settings').default;
const Config = require('../../src/common/config').default;
const config = new Config(env.configFilePath);
fs.writeFileSync(env.configFilePath, JSON.stringify({
url: env.mattermostURL,
}));
@ -92,8 +93,8 @@ describe('application', function desc() {
url.should.match(/\/index.html$/);
const str = fs.readFileSync(env.configFilePath, 'utf8');
const config = JSON.parse(str);
config.version.should.equal(settings.version);
const localConfigData = JSON.parse(str);
localConfigData.version.should.equal(config.defaultData.version);
});
it.skip('should be stopped when the app instance already exists', (done) => {

View file

@ -216,18 +216,18 @@ describe('browser/settings.html', function desc() {
await this.app.client.
loadSettingsPage().
click('#inputShowTrayIcon').
click('input[value="light"]').
pause(700); // wait auto-save
const config0 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8'));
config0.trayIconTheme.should.equal('light');
await this.app.client.
click('input[value="dark"]').
pause(700); // wait auto-save
const config0 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8'));
config0.trayIconTheme.should.equal('dark');
await this.app.client.
click('input[value="light"]').
pause(700); // wait auto-save
const config1 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8'));
config1.trayIconTheme.should.equal('dark');
config1.trayIconTheme.should.equal('light');
});
});
});

View file

@ -1,50 +0,0 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import settings from '../../src/common/settings';
import buildConfig from '../../src/common/config/buildConfig';
import defaultPreferences from '../../src/common/config/defaultPreferences';
import pastDefaultPreferences from '../../src/common/config/pastDefaultPreferences';
describe('common/settings.js', () => {
it('should upgrade v0 config file', () => {
const v0Config = {
url: 'https://example.com/team',
};
const config = settings.upgrade(v0Config);
config.teams.length.should.equal(1);
config.teams[0].url.should.equal(v0Config.url);
config.version.should.equal(settings.version);
});
it('should merge teams with buildConfig.defaultTeams', () => {
const teams = [
{
name: 'test',
url: 'https://example.com',
},
];
const mergedTeams = settings.mergeDefaultTeams(teams);
mergedTeams.should.deep.equal([
{
name: 'test',
url: 'https://example.com',
},
...buildConfig.defaultTeams,
]);
});
});
describe('common/config/', () => {
it('pastDefaultPreferences should have each past version of defaultPreferences', () => {
for (let version = 0; version <= defaultPreferences.version; version++) {
pastDefaultPreferences[`${version}`].should.exist;
}
});
it('defaultPreferences equal to one of pastDefaultPreferences', () => {
const pastPreferences = pastDefaultPreferences[`${defaultPreferences.version}`];
pastPreferences.should.deep.equal(defaultPreferences);
});
});