test: add src/native/tray.test.ts
This commit is contained in:
parent
347944463d
commit
15e36b8ea6
|
|
@ -0,0 +1,325 @@
|
||||||
|
/// <reference types="jest" />
|
||||||
|
|
||||||
|
import { Menu, Tray, nativeImage } from "electron";
|
||||||
|
|
||||||
|
import { initTray, updateTrayMenu } from "./tray";
|
||||||
|
import { mainWindow, quitApp } from "./window";
|
||||||
|
|
||||||
|
// Mock electron
|
||||||
|
jest.mock("electron", () => ({
|
||||||
|
Menu: {
|
||||||
|
buildFromTemplate: jest.fn((template: unknown[]) => template),
|
||||||
|
},
|
||||||
|
Tray: jest.fn().mockImplementation(() => ({
|
||||||
|
setToolTip: jest.fn(),
|
||||||
|
setImage: jest.fn(),
|
||||||
|
on: jest.fn(),
|
||||||
|
setContextMenu: jest.fn(),
|
||||||
|
})),
|
||||||
|
nativeImage: {
|
||||||
|
createFromDataURL: jest.fn().mockReturnValue({
|
||||||
|
resize: jest.fn().mockReturnValue({
|
||||||
|
setTemplateImage: jest.fn(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock the asset import
|
||||||
|
jest.mock("../../assets/desktop/icon.png?asset", () => "mock-icon-path", {
|
||||||
|
virtual: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock package.json
|
||||||
|
jest.mock("../../package.json", () => ({
|
||||||
|
version: "1.1.11",
|
||||||
|
}), { virtual: true });
|
||||||
|
|
||||||
|
// Mock window
|
||||||
|
jest.mock("./window", () => ({
|
||||||
|
mainWindow: {
|
||||||
|
show: jest.fn(),
|
||||||
|
focus: jest.fn(),
|
||||||
|
isVisible: jest.fn().mockReturnValue(false),
|
||||||
|
hide: jest.fn(),
|
||||||
|
},
|
||||||
|
quitApp: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("tray", () => {
|
||||||
|
let mockTray: Record<string, jest.Mock>;
|
||||||
|
let capturedClickHandler: Function;
|
||||||
|
let capturedMenu: unknown[];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
capturedClickHandler = jest.fn();
|
||||||
|
capturedMenu = [];
|
||||||
|
|
||||||
|
mockTray = {
|
||||||
|
setToolTip: jest.fn(),
|
||||||
|
setImage: jest.fn(),
|
||||||
|
on: jest.fn((event: string, handler: Function) => {
|
||||||
|
if (event === "click") {
|
||||||
|
capturedClickHandler = handler;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
setContextMenu: jest.fn((menu: unknown[]) => {
|
||||||
|
capturedMenu = menu;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
(Tray as unknown as jest.Mock).mockImplementation(() => mockTray);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("initTray", () => {
|
||||||
|
it("should create a tray icon from the asset", () => {
|
||||||
|
initTray();
|
||||||
|
|
||||||
|
expect(nativeImage.createFromDataURL).toHaveBeenCalledWith(
|
||||||
|
"mock-icon-path",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resize the tray icon to 20x20", () => {
|
||||||
|
const mockResize = jest.fn().mockReturnValue({
|
||||||
|
setTemplateImage: jest.fn(),
|
||||||
|
});
|
||||||
|
(nativeImage.createFromDataURL as jest.Mock).mockReturnValue({
|
||||||
|
resize: mockResize,
|
||||||
|
});
|
||||||
|
|
||||||
|
initTray();
|
||||||
|
|
||||||
|
expect(mockResize).toHaveBeenCalledWith({ width: 20, height: 20 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the resized icon as a template image", () => {
|
||||||
|
const mockSetTemplateImage = jest.fn();
|
||||||
|
const mockResized = {
|
||||||
|
resize: jest.fn().mockReturnValue({
|
||||||
|
setTemplateImage: mockSetTemplateImage,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
(nativeImage.createFromDataURL as jest.Mock).mockReturnValue(mockResized);
|
||||||
|
|
||||||
|
initTray();
|
||||||
|
|
||||||
|
expect(mockSetTemplateImage).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a new Tray instance with the resized icon", () => {
|
||||||
|
initTray();
|
||||||
|
|
||||||
|
expect(Tray).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the tray tooltip", () => {
|
||||||
|
initTray();
|
||||||
|
|
||||||
|
expect(mockTray.setToolTip).toHaveBeenCalledWith("Stoat for Desktop");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the tray image", () => {
|
||||||
|
initTray();
|
||||||
|
|
||||||
|
expect(mockTray.setImage).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call updateTrayMenu during initialization", () => {
|
||||||
|
initTray();
|
||||||
|
|
||||||
|
expect(mockTray.setContextMenu).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should register a click handler on the tray", () => {
|
||||||
|
initTray();
|
||||||
|
|
||||||
|
expect(mockTray.on).toHaveBeenCalledWith("click", expect.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show and focus main window when tray is clicked", () => {
|
||||||
|
initTray();
|
||||||
|
|
||||||
|
capturedClickHandler();
|
||||||
|
|
||||||
|
expect(mainWindow.show).toHaveBeenCalled();
|
||||||
|
expect(mainWindow.focus).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("updateTrayMenu", () => {
|
||||||
|
it("should build a context menu from a template", () => {
|
||||||
|
initTray();
|
||||||
|
updateTrayMenu();
|
||||||
|
|
||||||
|
expect(Menu.buildFromTemplate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the context menu on the tray", () => {
|
||||||
|
initTray();
|
||||||
|
updateTrayMenu();
|
||||||
|
|
||||||
|
expect(mockTray.setContextMenu).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include a disabled label item for the app name", () => {
|
||||||
|
initTray();
|
||||||
|
updateTrayMenu();
|
||||||
|
|
||||||
|
const appNameItem = capturedMenu.find(
|
||||||
|
(item: { label: string }) => item.label === "Stoat for Desktop",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(appNameItem).toBeDefined();
|
||||||
|
expect(appNameItem.enabled).toBe(false);
|
||||||
|
expect(appNameItem.type).toBe("normal");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include a version submenu with the current version", () => {
|
||||||
|
initTray();
|
||||||
|
updateTrayMenu();
|
||||||
|
|
||||||
|
const versionItem = capturedMenu.find(
|
||||||
|
(item: { label: string }) => item.label === "Version",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(versionItem).toBeDefined();
|
||||||
|
expect(versionItem.type).toBe("submenu");
|
||||||
|
expect(versionItem.submenu).toEqual([
|
||||||
|
{
|
||||||
|
label: "1.1.11",
|
||||||
|
type: "normal",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include a separator", () => {
|
||||||
|
initTray();
|
||||||
|
updateTrayMenu();
|
||||||
|
|
||||||
|
const separatorItem = capturedMenu.find(
|
||||||
|
(item: { type: string }) => item.type === "separator",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(separatorItem).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show 'Show App' when window is not visible", () => {
|
||||||
|
(mainWindow.isVisible as jest.Mock).mockReturnValue(false);
|
||||||
|
initTray();
|
||||||
|
updateTrayMenu();
|
||||||
|
|
||||||
|
const toggleItem = capturedMenu.find(
|
||||||
|
(item: { label: string }) =>
|
||||||
|
item.label === "Show App" || item.label === "Hide App",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(toggleItem.label).toBe("Show App");
|
||||||
|
expect(toggleItem.type).toBe("normal");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show 'Hide App' when window is visible", () => {
|
||||||
|
(mainWindow.isVisible as jest.Mock).mockReturnValue(true);
|
||||||
|
initTray();
|
||||||
|
updateTrayMenu();
|
||||||
|
|
||||||
|
const toggleItem = capturedMenu.find(
|
||||||
|
(item: { label: string }) =>
|
||||||
|
item.label === "Show App" || item.label === "Hide App",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(toggleItem.label).toBe("Hide App");
|
||||||
|
expect(toggleItem.type).toBe("normal");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should hide the app when 'Hide App' menu item is clicked", () => {
|
||||||
|
(mainWindow.isVisible as jest.Mock).mockReturnValue(true);
|
||||||
|
initTray();
|
||||||
|
updateTrayMenu();
|
||||||
|
|
||||||
|
const toggleItem = capturedMenu.find(
|
||||||
|
(item: { label: string }) => item.label === "Hide App",
|
||||||
|
);
|
||||||
|
|
||||||
|
toggleItem.click();
|
||||||
|
|
||||||
|
expect(mainWindow.hide).toHaveBeenCalled();
|
||||||
|
expect(mainWindow.show).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show the app when 'Show App' menu item is clicked", () => {
|
||||||
|
(mainWindow.isVisible as jest.Mock).mockReturnValue(false);
|
||||||
|
initTray();
|
||||||
|
updateTrayMenu();
|
||||||
|
|
||||||
|
const toggleItem = capturedMenu.find(
|
||||||
|
(item: { label: string }) => item.label === "Show App",
|
||||||
|
);
|
||||||
|
|
||||||
|
toggleItem.click();
|
||||||
|
|
||||||
|
expect(mainWindow.show).toHaveBeenCalled();
|
||||||
|
expect(mainWindow.hide).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include a 'Quit App' menu item", () => {
|
||||||
|
initTray();
|
||||||
|
updateTrayMenu();
|
||||||
|
|
||||||
|
const quitItem = capturedMenu.find(
|
||||||
|
(item: { label: string }) => item.label === "Quit App",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(quitItem).toBeDefined();
|
||||||
|
expect(quitItem.type).toBe("normal");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call quitApp when 'Quit App' menu item is clicked", () => {
|
||||||
|
initTray();
|
||||||
|
updateTrayMenu();
|
||||||
|
|
||||||
|
const quitItem = capturedMenu.find(
|
||||||
|
(item: { label: string }) => item.label === "Quit App",
|
||||||
|
);
|
||||||
|
|
||||||
|
quitItem.click();
|
||||||
|
|
||||||
|
expect(quitApp).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the correct menu structure with all items in order", () => {
|
||||||
|
initTray();
|
||||||
|
updateTrayMenu();
|
||||||
|
|
||||||
|
expect(capturedMenu).toHaveLength(5);
|
||||||
|
expect(capturedMenu[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
label: "Stoat for Desktop",
|
||||||
|
type: "normal",
|
||||||
|
enabled: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(capturedMenu[1]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
label: "Version",
|
||||||
|
type: "submenu",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(capturedMenu[2]).toEqual({ type: "separator" });
|
||||||
|
expect(capturedMenu[3]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
type: "normal",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(capturedMenu[4]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
label: "Quit App",
|
||||||
|
type: "normal",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue