Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ start minimized option #393

Merged
merged 2 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/__tests__/electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const mockBrowserWindowInstance = () => {
setBounds: jest.fn(),
setBrowserView: jest.fn(),
setFullScreen: jest.fn(),
show: jest.fn(),
showInactive: jest.fn(),
webContents: {
loadedUrl: '',
browserWindowInstance: () => instance,
Expand Down
12 changes: 6 additions & 6 deletions src/chrome-tabs/__tests__/chrome-tabs.browser.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('ChromeTabs in Browser test suite', () => {
{id: 13373, favicon: 'https://13373.png', url: 'https://13373.com'}
];
});
test('addTabs, should set tabs', async () => {
test('addTabs, should set tabs without restoring the main window / activating any tab', async () => {
// When
mockIpcRenderer.events.addTabs({}, tabs);
// Then
Expand All @@ -62,7 +62,7 @@ describe('ChromeTabs in Browser test suite', () => {
expect($addedTabs[1].hasAttribute('active')).toBe(false);
expect($addedTabs[2].querySelector('.chrome-tab-favicon').style.backgroundImage)
.toBe('url(https://13373.png)');
expect(mockIpcRenderer.send).toHaveBeenCalledWith('activateTab', {id: 1337});
expect(mockIpcRenderer.send).toHaveBeenCalledWith('activateTab', {id: 1337, restoreWindow: false});
});
test('activateTabInContainer, should change active tab', async () => {
// Given
Expand Down Expand Up @@ -136,7 +136,7 @@ describe('ChromeTabs in Browser test suite', () => {
// When
fireEvent.click($chromeTabs.querySelector('.chrome-tab[data-tab-id="313373"]'));
// Then
expect(mockIpcRenderer.send).toHaveBeenNthCalledWith(3, 'activateTab', {id: 313373});
expect(mockIpcRenderer.send).toHaveBeenNthCalledWith(3, 'activateTab', {id: 313373, restoreWindow: true});
});
test('click, on active tab, should do nothing', () => {
// When
Expand All @@ -153,9 +153,9 @@ describe('ChromeTabs in Browser test suite', () => {
// Then
expect(event.preventDefault).toHaveBeenCalledTimes(1);
});
test('dragStart, should activate tab and set initial drag values', () => {
test('dragStart, on inactive tab, should activate tab and set initial drag values', () => {
// Given
const $tab = $chromeTabs.querySelector('.chrome-tab[data-tab-id="1337"]');
const $tab = $chromeTabs.querySelector('.chrome-tab[data-tab-id="313373"]');
const event = new MouseEvent('dragstart', {
clientX: 100, clientY: 0});
Object.defineProperty(event, 'dataTransfer', {value: {
Expand All @@ -164,7 +164,7 @@ describe('ChromeTabs in Browser test suite', () => {
// When
fireEvent($tab, event);
// Then
expect(mockIpcRenderer.send).toHaveBeenCalledWith('activateTab', {id: 1337});
expect(mockIpcRenderer.send).toHaveBeenCalledWith('activateTab', {id: 313373, restoreWindow: true});
expect(event.dataTransfer.setDragImage).toHaveBeenCalledTimes(1);
});
test('drag, same position, should keep positions moving current tab left', async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/chrome-tabs/chrome-tabs.browser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const BackgroundSvg = () => html`
const Tab = ({dispatch, numberOfTabs, idx, id, active, offsetX = 0, title, url, width, ...rest}) => {
const tabClick = () => {
if (active !== true) {
sendActivateTab(id);
sendActivateTab({id, restoreWindow: true});
activateTab({dispatch})(null, {tabId: id});
}
};
Expand Down
6 changes: 4 additions & 2 deletions src/chrome-tabs/chrome-tabs.reducer.browser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
*/
/* eslint-disable no-undef */
import {APP_EVENTS} from '../components/index.mjs';
export const sendActivateTab = id => ipcRenderer.send(APP_EVENTS.activateTab, {id});
export const sendActivateTab = ({id, restoreWindow = true}) => {
ipcRenderer.send(APP_EVENTS.activateTab, {id, restoreWindow});
};
const sendReorderTabs = tabs =>
ipcRenderer.send(APP_EVENTS.tabReorder, {tabIds: tabs.map(({id}) => id)});

Expand Down Expand Up @@ -79,7 +81,7 @@ export const addTabs = ({dispatch}) => (_event, tabs) => {
dispatch({type: ACTIONS.SET_TABS, payload: tabs});
const activeTabMeta = tabs.find(({active}) => active === true);
if (tabs.length > 0 && activeTabMeta) {
sendActivateTab(activeTabMeta.id);
sendActivateTab({id: activeTabMeta.id, restoreWindow: false});
}
};
export const moveTab = ({dispatch}) => ({id, idx, offsetX}) =>
Expand Down
1 change: 1 addition & 0 deletions src/components/icon.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Icon.inbox = '\ue156';
Icon.info = '\ue88e';
Icon.lock = '\ue88d';
Icon.lockOpen = '\ue898';
Icon.minimize = '\ue931';
Icon.more = '\ue619';
Icon.moreVert = '\ue5d4';
Icon.notifications = '\ue7f4';
Expand Down
59 changes: 59 additions & 0 deletions src/main/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,46 @@ describe('Main module test suite', () => {
}));
});
});
describe('startMinimized', () => {
test('Main window should always start not shown', () => {
// When
main.init();
// Then
expect(electron.BrowserWindow).toHaveBeenCalledWith(expect.objectContaining({
show: false, paintWhenInitiallyHidden: false
}));
});
describe('=true', () => {
beforeEach(() => {
mockSettings.startMinimized = true;
main.init();
});
test('should call showInactive', () => {
expect(mockBrowserWindow.showInactive).toHaveBeenCalledTimes(1);
});
test('should call minimize after showInactive', () => {
expect(mockBrowserWindow.minimize).toHaveBeenCalledAfter(mockBrowserWindow.showInactive);
});
test('should not call show', () => {
expect(mockBrowserWindow.show).not.toHaveBeenCalled();
});
});
describe('=false', () => {
beforeEach(() => {
mockSettings.startMinimized = false;
main.init();
});
test('should call show', () => {
expect(mockBrowserWindow.show).toHaveBeenCalledTimes(1);
});
test('should not call showInactive', () => {
expect(mockBrowserWindow.showInactive).not.toHaveBeenCalled();
});
test('should not call minimize', () => {
expect(mockBrowserWindow.minimize).not.toHaveBeenCalled();
});
});
});
test('fixUserDataLocation, should set a location in lower-case (Electron <14 compatible)', () => {
// Given
electron.app.getPath.mockImplementation(() => 'ImMixed-Case/WithSome\\Separator$');
Expand Down Expand Up @@ -148,6 +188,23 @@ describe('Main module test suite', () => {
expect(settingsModule.updateSettings).toHaveBeenCalledWith({width: 13, height: 37});
});
});
describe('restore (required for windows when starting minimized)', () => {
let mockAppMenu;
beforeEach(() => {
mockAppMenu = {
setBounds: jest.fn()
};
jest.spyOn(appMenuModule, 'newAppMenu').mockImplementation(() => mockAppMenu);
main.init();
});
test('should set app-menu bounds', () => {
// When
mockBrowserWindow.listeners.restore({sender: mockBrowserWindow});
jest.runAllTimers();
// Then
expect(mockAppMenu.setBounds).toHaveBeenCalledWith({x: 0, y: 0, width: 10, height: 34});
});
});
describe('resize', () => {
test('#78: should be run in separate setTimeout timer function to resize properly in Linux', () => {
// When
Expand Down Expand Up @@ -320,6 +377,7 @@ describe('Main module test suite', () => {
});
test('notificationClick, should restore window and activate tab', () => {
// Given
mockSettings.startMinimized = true;
mockBrowserWindow.restore = jest.fn();
mockBrowserWindow.show = jest.fn();
jest.spyOn(tabManagerModule, 'getTab').mockImplementation();
Expand All @@ -330,6 +388,7 @@ describe('Main module test suite', () => {
expect(mockBrowserView.webContents.send).toHaveBeenCalledWith('activateTabInContainer', {tabId: 'validId'});
expect(mockBrowserWindow.restore).toHaveBeenCalledTimes(1);
expect(mockBrowserWindow.show).toHaveBeenCalledTimes(1);
expect(mockBrowserWindow.show).toHaveBeenCalledAfter(mockBrowserWindow.restore);
expect(tabManagerModule.getTab).toHaveBeenCalledWith('validId');
});
test('handleReload', () => {
Expand Down
29 changes: 20 additions & 9 deletions src/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const resetMainWindow = () => {
}
};

const activateTab = tabId => {
const activateTab = ({tabId, restoreWindow = true}) => {
const activeTab = tabManager.getTab(tabId);
if (activeTab) {
const {width, height} = mainWindow.getContentBounds();
Expand All @@ -64,7 +64,9 @@ const activateTab = tabId => {
tabContainer.setBounds({x: 0, y: 0, width, height: TABS_CONTAINER_HEIGHT});
activeTab.setBounds({x: 0, y: TABS_CONTAINER_HEIGHT, width, height: height - TABS_CONTAINER_HEIGHT});
tabManager.setActiveTab(tabId);
activeTab.webContents.focus();
if (restoreWindow) {
activeTab.webContents.focus();
}
}
};

Expand Down Expand Up @@ -99,7 +101,7 @@ const handleTabTraverse = getTabIdFunction => () => {
}
const tabId = getTabIdFunction();
tabContainer.webContents.send(APP_EVENTS.activateTabInContainer, {tabId});
activateTab(tabId);
activateTab({tabId});
};

const handleTabSwitchToPosition = tabPosition => {
Expand Down Expand Up @@ -145,14 +147,16 @@ const initTabListener = () => {
eventBus.emit(APP_EVENTS.settingsOpenDialog);
}
});
eventBus.on(APP_EVENTS.activateTab, (_event, data) => activateTab(data.id));
eventBus.on(APP_EVENTS.activateTab, (_event, data) => {
activateTab({tabId: data.id, restoreWindow: data.restoreWindow});
});
eventBus.on(APP_EVENTS.canNotify, (event, tabId) => {
event.returnValue = tabManager.canNotify(tabId);
});
eventBus.on(APP_EVENTS.notificationClick, (_event, {tabId}) => {
tabContainer.webContents.send(APP_EVENTS.activateTabInContainer, {tabId});
eventBus.emit(APP_EVENTS.restore);
activateTab(tabId);
activateTab({tabId});
});
eventBus.on(APP_EVENTS.reload, handleTabReload);
eventBus.on(APP_EVENTS.tabReorder, handleTabReorder);
Expand All @@ -176,7 +180,7 @@ const appMenuClose = () => {
return;
}
mainWindow.removeBrowserView(appMenu);
activateTab(tabManager.getActiveTab());
activateTab({tabId: tabManager.getActiveTab()});
};

const fullscreenToggle = () => {
Expand All @@ -188,7 +192,7 @@ const closeDialog = () => {
return;
}
const dialogView = mainWindow.getBrowserView();
activateTab(tabManager.getActiveTab());
activateTab({tabId: tabManager.getActiveTab()});
dialogView.webContents.destroy();
};

Expand Down Expand Up @@ -253,15 +257,22 @@ const browserVersionsReady = () => {
const init = () => {
fixUserDataLocation();
loadDictionaries();
const {width = 800, height = 600, theme} = loadSettings();
const {width = 800, height = 600, startMinimized, theme} = loadSettings();
nativeTheme.themeSource = theme;
mainWindow = new BrowserWindow({
width, height, resizable: true, maximizable: true,
icon: path.resolve(__dirname, '..', 'assets', getPlatform() === 'linux' ? 'icon.png' : 'icon.ico'),
show: false, paintWhenInitiallyHidden: false,
webPreferences
});
if (startMinimized) {
mainWindow.showInactive();
mainWindow.minimize();
} else {
mainWindow.show();
}
mainWindow.removeMenu();
['resize', 'maximize']
['resize', 'maximize', 'restore']
.forEach(event => mainWindow.on(event, handleMainWindowResize));
mainWindow.on('close', handleWindowClose);
initTabListener();
Expand Down
6 changes: 5 additions & 1 deletion src/settings/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ describe('Settings module test suite', () => {
'{\n "tabs": [],\n' +
' "useNativeSpellChecker": false,\n' +
' "enabledDictionaries": [\n "en"\n ],\n' +
' "theme": "system",\n "trayEnabled": false,\n' +
' "theme": "system",\n' +
' "trayEnabled": false,\n' +
' "startMinimized": false,\n' +
' "closeButtonBehavior": "quit"\n' +
'}');
});
Expand All @@ -140,6 +142,7 @@ describe('Settings module test suite', () => {
' "enabledDictionaries": [\n "en"\n ],\n' +
' "theme": "system",\n' +
' "trayEnabled": false,\n' +
' "startMinimized": false,\n' +
' "closeButtonBehavior": "quit",\n' +
' "activeTab": 1337,\n' +
' "otherSetting": "1337"\n' +
Expand All @@ -157,6 +160,7 @@ describe('Settings module test suite', () => {
' "enabledDictionaries": [\n "en"\n ],\n' +
' "theme": "system",\n' +
' "trayEnabled": false,\n' +
' "startMinimized": false,\n' +
' "closeButtonBehavior": "quit",\n' +
' "activeTab": 1337\n}');
});
Expand Down
3 changes: 2 additions & 1 deletion src/settings/__tests__/settings.browser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export const ipcRenderer = () => {
{id: '2', url: 'https://initial-tab-2.com', disabled: true, disableNotifications: true}
],
theme: 'dark',
trayEnabled: true
trayEnabled: true,
startMinimized: false
};
return {
mockDictionariesAvailableNative,
Expand Down
3 changes: 2 additions & 1 deletion src/settings/__tests__/settings.browser.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ describe('Settings in Browser test suite', () => {
enabledDictionaries: ['en'],
disableNotificationsGlobally: false,
theme: 'dark',
trayEnabled: true
trayEnabled: true,
startMinimized: false
});
});
test('Cancel should send close dialog event', () => {
Expand Down
24 changes: 24 additions & 0 deletions src/settings/__tests__/settings.other.browser.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,30 @@ describe('Settings (Other) in Browser test suite', () => {
await waitFor(() => expect($traySwitch.checked).toBe(true));
});
});
describe('Toggle Start Minimized click', () => {
let $startMinimizedSwitch;
beforeEach(() => {
// In tests, start minimized is disabled by default
$startMinimizedSwitch = document.querySelector('.settings__start-minimized .switch input');
});
test('when start minimized enabled, should uncheck input', async () => {
// Given
fireEvent.click($startMinimizedSwitch);
await waitFor(() => expect($startMinimizedSwitch.checked).toBe(true));
// When
fireEvent.click($startMinimizedSwitch);
// Then
await waitFor(() => expect($startMinimizedSwitch.checked).toBe(false));
});
test('when start minimized disabled, should check input', async () => {
// Given
expect($startMinimizedSwitch.checked).toBe(false);
// When
fireEvent.click($startMinimizedSwitch);
// Then
await waitFor(() => expect($startMinimizedSwitch.checked).toBe(true));
});
});
test('ElectronIM version is visible', async () => {
const $electronimVersion = await findByTestId(document, 'settings-electronim-version');
expect($electronimVersion.textContent).toBe('ElectronIM version 0.0.0');
Expand Down
1 change: 1 addition & 0 deletions src/settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const DEFAULT_SETTINGS = {
enabledDictionaries: ['en'],
theme: 'system',
trayEnabled: false,
startMinimized: false,
closeButtonBehavior: CLOSE_BUTTON_BEHAVIORS.quit
};

Expand Down
2 changes: 2 additions & 0 deletions src/settings/settings.browser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const Settings = ({initialState}) => {
disableNotificationsGlobally: state.disableNotificationsGlobally,
theme: state.theme,
trayEnabled: state.trayEnabled,
startMinimized: state.startMinimized,
closeButtonBehavior: state.closeButtonBehavior
});
const cancel = () => ipcRenderer.send(APP_EVENTS.closeDialog);
Expand Down Expand Up @@ -96,6 +97,7 @@ Promise.all([
disableNotificationsGlobally: currentSettings.disableNotificationsGlobally,
theme: currentSettings.theme,
trayEnabled: currentSettings.trayEnabled,
startMinimized: currentSettings.startMinimized,
closeButtonBehavior: currentSettings.closeButtonBehavior
};
render(html`<${Settings} initialState=${initialState} />`, settingsRoot());
Expand Down
Loading
Loading