Skip to content
Merged
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
38 changes: 23 additions & 15 deletions apps/desktop/e2e/workhub-layout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,31 @@ test('WorkHub uses its coordination model and shared attachment composer', async
// would be covered by this WebContentsView and never receive native clicks.
await workhub.getByRole('button', { name: '展开任务工作栏', exact: true }).click();
await expect(page.locator('.maka-session-workbar[data-placement="right"]')).toBeVisible();
// A real native menu must coexist with the live sibling WebContentsView.
// DOM tests cannot detect replacing that view with a frozen screenshot.
// The menu interaction must coexist with the live sibling WebContentsView.
// DOM tests cannot detect replacing that view with a frozen screenshot, so
// the WorkHub view's native visibility is asserted below. The popup itself
// is stubbed instead of really opened: on Linux the native teardown races
// the scripted close and crashes the main process (#5995), and every
// assertion here is Maka state — aria-expanded, the dock backdrop, the
// view's visibility — none of which needs a real GTK menu.
await app.evaluate(({ Menu }) => {
const original = Menu.prototype.popup;
Menu.prototype.popup = function (options) {
const probe = globalThis as unknown as { workbarMenu: Electron.Menu; workbarMenuOpen: boolean };
probe.workbarMenu = this;
probe.workbarMenuOpen = true;
// Track the popup's own lifetime in the main process: Linux may
// auto-dismiss it (e.g. after a resize) at any moment.
// Restoring immediately keeps the later task-action menu on the real

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The later task-action menu ("任务操作") in this spec is a DOM role=menuitem menu and never calls Menu.popup. The workbar add-panel is the only popupNativeMenu caller. Restoring the prototype is still good hygiene, but this comment (and the PR body) overstate the remaining coverage: after this change no e2e opens a real native popup.

// native popup; only this scripted one is simulated.
Menu.prototype.popup = original;
// The popup contract is that options.callback fires once the menu is
// closed; that callback resolves the renderer's request and drops
// aria-expanded. Driving it from the menu's own lifecycle event lets
// the test close the menu without ever touching native closePopup.
this.once('menu-will-close', () => {
probe.workbarMenuOpen = false;
(options as { callback?: () => void } | undefined)?.callback?.();
});
Menu.prototype.popup = original;
return original.call(this, options);
return undefined;
};
});
const addPanel = page.getByRole('button', { name: '添加面板', exact: true });
Expand All @@ -142,16 +152,14 @@ test('WorkHub uses its coordination model and shared attachment composer', async
'webContents' in child && (child as Electron.WebContentsView).webContents.getURL().includes('surface=workhub')));
return container?.getVisible();
})).toBe(true);
// A menu that already auto-dismissed (Linux closes popups after window
// resizes) must not be closed again: closePopup on a dead popup crashes the
// main process. Decide inside the main process, in the same task as the
// close, so the dismissal cannot land between the check and the call;
// checking the renderer's aria-expanded first left exactly that window.
await mainWindow.evaluate((window) => {
// Close through the menu's own lifecycle event. The stubbed popup never
// opened a native menu, so there is nothing for closePopup to close — and
// closePopup on a popup GTK already tore down crashes the main process
// (#5995). Decide inside the main process, in the same task as the emit,
// so the close cannot land before the state behind the assertions above.
await mainWindow.evaluate(() => {
const probe = globalThis as unknown as { workbarMenu: Electron.Menu; workbarMenuOpen: boolean };
// Close on the same owner passed to popup(). The no-window overload takes
// Electron's close-all MenuRunner path on Linux, even for this single menu.
if (probe.workbarMenuOpen) probe.workbarMenu.closePopup(window);
if (probe.workbarMenuOpen) probe.workbarMenu.emit('menu-will-close');
});
await expect(addPanel).not.toHaveAttribute('aria-expanded', 'true');
await workhub.getByRole('button', { name: '收起任务工作栏', exact: true }).click();
Expand Down