mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-08-26 17:12:03 +00:00
195 lines
6.4 KiB
TypeScript
195 lines
6.4 KiB
TypeScript
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
|
// 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.
|
|
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
|
|
|
import {
|
|
APP_COMMAND,
|
|
APP_COMMAND_CHANNEL,
|
|
APP_COMMAND_HANDLED_CHANNEL,
|
|
APP_SHELL_NOT_READY_CHANNEL,
|
|
APP_SHELL_READY_CHANNEL,
|
|
APP_SHELL_READY_PROBE_CHANNEL,
|
|
} from '@/shared/appCommands';
|
|
import { NATIVE_MENU_LOCALE_CHANNEL } from '@/shared/nativeMenu';
|
|
import {
|
|
WINDOW_CLOSE_REQUEST_CHANNEL,
|
|
WINDOW_CLOSE_RESPONSE_CHANNEL,
|
|
} from '@/shared/windowClose';
|
|
import { beforeAll, describe, expect, it, vi } from 'vitest';
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
exposeInMainWorld: vi.fn(),
|
|
invoke: vi.fn(),
|
|
off: vi.fn(),
|
|
on: vi.fn(),
|
|
removeAllListeners: vi.fn(),
|
|
send: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('electron', () => ({
|
|
contextBridge: { exposeInMainWorld: mocks.exposeInMainWorld },
|
|
ipcRenderer: {
|
|
invoke: mocks.invoke,
|
|
off: mocks.off,
|
|
on: mocks.on,
|
|
removeAllListeners: mocks.removeAllListeners,
|
|
send: mocks.send,
|
|
},
|
|
webUtils: { getPathForFile: vi.fn() },
|
|
}));
|
|
|
|
type ExposedElectronAPI = {
|
|
onAppCommand: (callback: (command: string) => void) => () => void;
|
|
onCloseRequest: (
|
|
callback: (request: { intent: 'close-window' | 'quit-app' }) => void
|
|
) => () => void;
|
|
respondToCloseRequest: (response: {
|
|
intent: 'close-window' | 'quit-app';
|
|
action: 'acknowledge' | 'confirm' | 'cancel';
|
|
}) => void;
|
|
setNativeMenuLocale: (locale: 'en-US' | 'zh-Hans') => void;
|
|
};
|
|
|
|
let electronAPI: ExposedElectronAPI;
|
|
|
|
beforeAll(async () => {
|
|
await import('../../../../electron/preload/index');
|
|
const exposure = mocks.exposeInMainWorld.mock.calls.find(
|
|
([name]) => name === 'electronAPI'
|
|
);
|
|
electronAPI = exposure?.[1] as ExposedElectronAPI;
|
|
});
|
|
|
|
describe('preload app shell bridge', () => {
|
|
it('re-announces READY when the first announcement preceded did-finish-load', async () => {
|
|
mocks.send.mockClear();
|
|
const unsubscribeCommand = electronAPI.onAppCommand(vi.fn());
|
|
const unsubscribeClose = electronAPI.onCloseRequest(vi.fn());
|
|
const probeListener = mocks.on.mock.calls.find(
|
|
([channel]) => channel === APP_SHELL_READY_PROBE_CHANNEL
|
|
)?.[1];
|
|
const initialReady = mocks.send.mock.calls.find(
|
|
([channel]) => channel === APP_SHELL_READY_CHANNEL
|
|
)?.[1] as { epoch: string } | undefined;
|
|
|
|
expect(initialReady?.epoch).toEqual(expect.any(String));
|
|
expect(probeListener).toEqual(expect.any(Function));
|
|
|
|
// Main rejects the first READY until did-finish-load, then sends this
|
|
// probe. The renderer must repeat READY for the same document epoch.
|
|
probeListener({});
|
|
const readyMessages = mocks.send.mock.calls.filter(
|
|
([channel]) => channel === APP_SHELL_READY_CHANNEL
|
|
);
|
|
expect(readyMessages).toEqual([
|
|
[APP_SHELL_READY_CHANNEL, initialReady],
|
|
[APP_SHELL_READY_CHANNEL, initialReady],
|
|
]);
|
|
|
|
unsubscribeCommand();
|
|
unsubscribeClose();
|
|
await Promise.resolve();
|
|
});
|
|
|
|
it('handshakes after both listeners, filters epochs, and sends handled receipts', async () => {
|
|
mocks.send.mockClear();
|
|
const callback = vi.fn();
|
|
const closeCallback = vi.fn();
|
|
const unsubscribeCommand = electronAPI.onAppCommand(callback);
|
|
const unsubscribeClose = electronAPI.onCloseRequest(closeCallback);
|
|
const commandListener = mocks.on.mock.calls
|
|
.filter(([channel]) => channel === APP_COMMAND_CHANNEL)
|
|
.at(-1)?.[1];
|
|
const ready = mocks.send.mock.calls.find(
|
|
([channel]) => channel === APP_SHELL_READY_CHANNEL
|
|
)?.[1] as { epoch: string } | undefined;
|
|
|
|
expect(ready?.epoch).toEqual(expect.any(String));
|
|
expect(commandListener).toEqual(expect.any(Function));
|
|
const request = {
|
|
commandId: APP_COMMAND.newProject,
|
|
requestId: 'request-1',
|
|
epoch: ready!.epoch,
|
|
};
|
|
commandListener({}, request);
|
|
commandListener({}, { ...request, epoch: 'stale-epoch' });
|
|
commandListener({}, APP_COMMAND.newProject);
|
|
|
|
expect(callback).toHaveBeenCalledOnce();
|
|
expect(callback).toHaveBeenCalledWith(APP_COMMAND.newProject);
|
|
expect(mocks.send).toHaveBeenCalledWith(
|
|
APP_COMMAND_HANDLED_CHANNEL,
|
|
request
|
|
);
|
|
|
|
unsubscribeCommand();
|
|
const unsubscribeReplacement = electronAPI.onAppCommand(vi.fn());
|
|
await Promise.resolve();
|
|
expect(mocks.send).not.toHaveBeenCalledWith(
|
|
APP_SHELL_NOT_READY_CHANNEL,
|
|
expect.anything()
|
|
);
|
|
|
|
unsubscribeReplacement();
|
|
unsubscribeClose();
|
|
await Promise.resolve();
|
|
expect(mocks.off).toHaveBeenCalledWith(
|
|
APP_COMMAND_CHANNEL,
|
|
commandListener
|
|
);
|
|
expect(mocks.send).toHaveBeenCalledWith(APP_SHELL_NOT_READY_CHANNEL, {
|
|
epoch: ready!.epoch,
|
|
});
|
|
});
|
|
|
|
it('sends typed close responses on the shared response channel', () => {
|
|
const response = { intent: 'quit-app', action: 'cancel' } as const;
|
|
|
|
electronAPI.respondToCloseRequest(response);
|
|
|
|
expect(mocks.send).toHaveBeenCalledWith(
|
|
WINDOW_CLOSE_RESPONSE_CHANNEL,
|
|
response
|
|
);
|
|
});
|
|
|
|
it('sends native-menu locale updates on their dedicated channel', () => {
|
|
electronAPI.setNativeMenuLocale('zh-Hans');
|
|
|
|
expect(mocks.send).toHaveBeenCalledWith(
|
|
NATIVE_MENU_LOCALE_CHANNEL,
|
|
'zh-Hans'
|
|
);
|
|
});
|
|
|
|
it('filters close requests and removes the exact listener on cleanup', () => {
|
|
const callback = vi.fn();
|
|
const unsubscribe = electronAPI.onCloseRequest(callback);
|
|
const listener = mocks.on.mock.calls
|
|
.filter(([channel]) => channel === WINDOW_CLOSE_REQUEST_CHANNEL)
|
|
.at(-1)?.[1];
|
|
|
|
expect(listener).toEqual(expect.any(Function));
|
|
listener({}, { intent: 'quit-app' });
|
|
listener({}, { intent: 'not-a-close-intent' });
|
|
|
|
expect(callback).toHaveBeenCalledOnce();
|
|
expect(callback).toHaveBeenCalledWith({ intent: 'quit-app' });
|
|
|
|
unsubscribe();
|
|
expect(mocks.off).toHaveBeenCalledWith(
|
|
WINDOW_CLOSE_REQUEST_CHANNEL,
|
|
listener
|
|
);
|
|
});
|
|
});
|