Skip to content

Commit

Permalink
refactor: move menu creation to a dedicated file
Browse files Browse the repository at this point in the history
also add tests prior to add new entries for zoom in / zoom out

Signed-off-by: Florent Benoit <[email protected]>
  • Loading branch information
benoitf committed Sep 6, 2024
1 parent abad430 commit ea6e6e7
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 27 deletions.
103 changes: 103 additions & 0 deletions packages/main/src/application-menu-builder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**********************************************************************
* Copyright (C) 2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

/* eslint-disable @typescript-eslint/no-explicit-any */

import { Menu } from 'electron';
import { aboutMenuItem } from 'electron-util/main';
import { beforeEach, expect, test, vi } from 'vitest';

import { ApplicationMenuBuilder } from './application-menu-builder.js';

vi.mock('electron', async () => {
class MyCustomWindow {
static readonly singleton = new MyCustomWindow();

loadURL(): void {}
setBounds(): void {}

on(): void {}

show(): void {}
focus(): void {}
isMinimized(): boolean {
return false;
}
isDestroyed(): boolean {
return false;
}

static getAllWindows(): unknown[] {
return [MyCustomWindow.singleton];
}
}

return {
BrowserWindow: MyCustomWindow,
Menu: {
buildFromTemplate: vi.fn(),
getApplicationMenu: vi.fn(),
setApplicationMenu: vi.fn(),
},
};
});

vi.mock('electron-util/main', async () => {
return {
aboutMenuItem: vi.fn(),
};
});

let applicationMenuBuilder: ApplicationMenuBuilder;

beforeEach(() => {
vi.resetAllMocks();
applicationMenuBuilder = new ApplicationMenuBuilder();
});

test('check about menu is added', () => {
vi.mocked(Menu.buildFromTemplate).mockImplementation(a => {
return a as unknown as Menu;
});

// set a menu
vi.mocked(Menu.getApplicationMenu).mockReturnValue({
items: [
{
role: 'help',
submenu: {
items: [],
},
},
],
} as unknown as Menu);

// mock aboutMenuItem
vi.mocked(aboutMenuItem).mockReturnValue({
label: 'About',
});

const menu = applicationMenuBuilder.build();
expect(menu).toBeDefined();

const items = menu?.items[0]?.submenu as unknown as any[];
expect(items).toBeDefined();

// expect to have the about menu
expect(items[1]?.label).toBe('About');
});
43 changes: 43 additions & 0 deletions packages/main/src/application-menu-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**********************************************************************
* Copyright (C) 2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

import { Menu } from 'electron';
import { aboutMenuItem } from 'electron-util/main';

export class ApplicationMenuBuilder {
build(): Electron.Menu | undefined {
const menu = Menu.getApplicationMenu(); // get default menu
if (!menu) {
return undefined;
}
// Add help/about menu entry
menu.items.forEach(i => {
// add the About entry only in the help menu
if (i.role === 'help' && i.submenu) {
const aboutMenuSubItem = aboutMenuItem({});
aboutMenuSubItem.label = 'About';

// create new submenu
// also add a separator before the About entry
const newSubMenu = Menu.buildFromTemplate([...i.submenu.items, { type: 'separator' }, aboutMenuSubItem]);
i.submenu = newSubMenu;
}
});
return menu;
}
}
38 changes: 37 additions & 1 deletion packages/main/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
***********************************************************************/

import type { App } from 'electron';
import { app, BrowserWindow, Tray } from 'electron';
import { app, BrowserWindow, Menu, Tray } from 'electron';
import { afterEach, assert, beforeEach, expect, test, vi } from 'vitest';

import {
Expand All @@ -26,6 +26,8 @@ import {
mainWindowDeferred,
sanitizeProtocolForExtension,
} from './index.js';
import type { ConfigurationRegistry } from './plugin/configuration-registry.js';
import type { Emitter } from './plugin/events/emitter.js';
import { PluginSystem } from './plugin/index.js';
import { Deferred } from './plugin/util/deferred.js';
import * as util from './util.js';
Expand Down Expand Up @@ -58,6 +60,10 @@ vi.mock('electron-util/main', async () => {
};
});

const configurationRegistryMock = {
registerConfigurations: vi.fn(),
} as unknown as ConfigurationRegistry;

const fakeWindow = {
isDestroyed: vi.fn(),
webContents: {
Expand Down Expand Up @@ -223,6 +229,36 @@ test('app-ready event with activate event', async () => {
// expect show and focus have been called
expect(spyShow).toHaveBeenCalled();
expect(spyFocus).toHaveBeenCalled();

// capture the pluginSystem.initExtensions call
const initExtensionsCalls = vi.mocked(initMock).mock.calls;
expect(initExtensionsCalls).toHaveLength(1);

// grab onDidConfigurationRegistry parameter
const _onDidConfigurationRegistry = initExtensionsCalls?.[0]?.[0];
// call the onDidConfigurationRegistry
expect(_onDidConfigurationRegistry).toBeDefined();

// cast as Emitter
const onDidConfigurationRegistry = _onDidConfigurationRegistry as Emitter<ConfigurationRegistry>;

// create a Menu
const menu = {
items: [],
} as unknown as Menu;

vi.mocked(Menu.getApplicationMenu).mockReturnValue(menu);

onDidConfigurationRegistry.fire(configurationRegistryMock);

// check we've called Menu.getApplicationMenu
await vi.waitFor(() => expect(vi.mocked(Menu.getApplicationMenu)).toHaveBeenCalled());

// and Menu.buildFromTemplate
expect(vi.mocked(Menu.buildFromTemplate)).toHaveBeenCalled();

// and Menu.setApplicationMenu
expect(vi.mocked(Menu.setApplicationMenu)).toHaveBeenCalled();
});

test('should send the URL to open when mainWindow is created', async () => {
Expand Down
11 changes: 10 additions & 1 deletion packages/main/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ import './security-restrictions';
import dns from 'node:dns';

import type { BrowserWindow } from 'electron';
import { app, ipcMain, Tray } from 'electron';
import { app, ipcMain, Menu, Tray } from 'electron';

import { createNewWindow, restoreWindow } from '/@/mainWindow.js';

import { ApplicationMenuBuilder } from './application-menu-builder.js';
import type { ConfigurationRegistry } from './plugin/configuration-registry.js';
import type { Event } from './plugin/events/emitter.js';
import { Emitter } from './plugin/events/emitter.js';
Expand Down Expand Up @@ -249,6 +250,14 @@ app.whenReady().then(
.then(browserWindow => {
const windowHandler = new WindowHandler(configurationRegistry, browserWindow);
windowHandler.init();
// sets the menu
const applicationMenuBuilder = new ApplicationMenuBuilder();
const menu = applicationMenuBuilder.build();

if (menu) {
Menu.setApplicationMenu(menu);
}

// send window Handler
ipcMain.emit('window-handler', '', windowHandler);
})
Expand Down
26 changes: 1 addition & 25 deletions packages/main/src/mainWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ import { join } from 'node:path';
import { URL } from 'node:url';

import type { BrowserWindowConstructorOptions, Rectangle } from 'electron';
import { app, autoUpdater, BrowserWindow, ipcMain, Menu, nativeTheme, screen } from 'electron';
import { app, autoUpdater, BrowserWindow, ipcMain, nativeTheme, screen } from 'electron';
import contextMenu from 'electron-context-menu';
import { aboutMenuItem } from 'electron-util/main';

import { NavigationItemsMenuBuilder } from './navigation-items-menu-builder.js';
import { OpenDevTools } from './open-dev-tools.js';
Expand Down Expand Up @@ -239,29 +238,6 @@ async function createWindow(): Promise<BrowserWindow> {
},
});

// Add help/about menu entry
const menu = Menu.getApplicationMenu(); // get default menu
if (menu) {
// build a new menu based on default one but adding about entry in the help menu
const newmenu = Menu.buildFromTemplate(
menu.items.map(i => {
// add the About entry only in the help menu
if (i.role === 'help' && i.submenu) {
const aboutMenuSubItem = aboutMenuItem({});
aboutMenuSubItem.label = 'About';

// create new submenu
// also add a separator before the About entry
const newSubMenu = Menu.buildFromTemplate([...i.submenu.items, { type: 'separator' }, aboutMenuSubItem]);
return { ...i, submenu: newSubMenu };
}
return i;
}),
);

Menu.setApplicationMenu(newmenu);
}

/**
* URL for main window.
* Vite dev server for development.
Expand Down

0 comments on commit ea6e6e7

Please sign in to comment.