mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2025-12-06 10:41:02 -08:00
* feat: new config for tidgi mini window * chore: upgrade electron-forge * fix: use 汉语 和 漢語 * feat: shortcut to open mini window * test: TidGiMenubarWindow * feat: allow updateWindowProperties on the fly * fix: wrong icon path * fix: log not showing error message and stack * refactor: directly log error when using logger.error * feat: shortcut to open window * fix: menubar not closed * test: e2e for menubar * test: keyboard shortcut * test: wiki web content, and refactor to files * test: update command * Update Testing.md * test: menubar settings about menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId * test: simplify menubar test and cleanup test config * fix: view missing when execute several test all together * refactor: use hook to cleanup menubar setting * refactor: I clear test ai settings to before hook * Add option to show title bar on menubar window Introduces a new preference 'showMenubarWindowTitleBar' allowing users to toggle the title bar visibility on the menubar window. Updates related services, interfaces, and UI components to support this feature, and adds corresponding localization strings for English and Chinese. * refactor: tidgiminiwindow * refactor: preference keys to right order * Refactor window dimension checks to use constants Replaces hardcoded window dimensions with values from windowDimension and WindowNames constants for improved maintainability and consistency in window identification and checks. * I cleanup test wiki * Update defaultPreferences.ts * test: mini window workspace switch * fix: image broken by ai, and lint * fix: can't switch to mini window * refactor: useless todo * Update index.ts * refactor: reuse serialize-error * Update index.ts * Update testKeyboardShortcuts.ts * refactor: dup logic * Update ui.ts * fix: electron-ipc-cat
207 lines
7.4 KiB
TypeScript
207 lines
7.4 KiB
TypeScript
import { When } from '@cucumber/cucumber';
|
|
import type { ElectronApplication } from 'playwright';
|
|
import type { ApplicationWorld } from './application';
|
|
import { checkWindowDimension, checkWindowName } from './application';
|
|
|
|
// Constants for retry logic
|
|
const MAX_ATTEMPTS = 10;
|
|
const RETRY_INTERVAL_MS = 1000;
|
|
|
|
// Helper function to get browser view info from Electron window
|
|
async function getBrowserViewInfo(
|
|
app: ElectronApplication,
|
|
dimensions: { width: number; height: number },
|
|
): Promise<{ view?: { x: number; y: number; width: number; height: number }; windowContent?: { width: number; height: number }; hasView: boolean }> {
|
|
return app.evaluate(async ({ BrowserWindow }, dimensions: { width: number; height: number }) => {
|
|
const windows = BrowserWindow.getAllWindows();
|
|
|
|
// Find the target window by dimensions
|
|
const targetWindow = windows.find(win => {
|
|
const bounds = win.getBounds();
|
|
return bounds.width === dimensions.width && bounds.height === dimensions.height;
|
|
});
|
|
|
|
if (!targetWindow) {
|
|
return { hasView: false };
|
|
}
|
|
|
|
// Get all child views (WebContentsView instances) attached to this specific window
|
|
if (targetWindow.contentView && 'children' in targetWindow.contentView) {
|
|
const views = targetWindow.contentView.children || [];
|
|
|
|
for (const view of views) {
|
|
// Type guard to check if view is a WebContentsView
|
|
if (view && view.constructor.name === 'WebContentsView') {
|
|
const webContentsView = view as unknown as { getBounds: () => { x: number; y: number; width: number; height: number } };
|
|
const viewBounds = webContentsView.getBounds();
|
|
const windowContentBounds = targetWindow.getContentBounds();
|
|
|
|
return {
|
|
view: viewBounds,
|
|
windowContent: windowContentBounds,
|
|
hasView: true,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
return { hasView: false };
|
|
}, dimensions);
|
|
}
|
|
|
|
When('I confirm the {string} window exists', async function(this: ApplicationWorld, windowType: string) {
|
|
if (!this.app) {
|
|
throw new Error('Application is not launched');
|
|
}
|
|
|
|
const success = await this.waitForWindowCondition(
|
|
windowType,
|
|
(window) => window !== undefined && !window.isClosed(),
|
|
MAX_ATTEMPTS,
|
|
RETRY_INTERVAL_MS,
|
|
);
|
|
|
|
if (!success) {
|
|
throw new Error(`${windowType} window was not found or is closed`);
|
|
}
|
|
});
|
|
|
|
When('I confirm the {string} window visible', async function(this: ApplicationWorld, windowType: string) {
|
|
if (!this.app) {
|
|
throw new Error('Application is not launched');
|
|
}
|
|
|
|
const success = await this.waitForWindowCondition(
|
|
windowType,
|
|
(window, isVisible) => window !== undefined && !window.isClosed() && isVisible,
|
|
MAX_ATTEMPTS,
|
|
RETRY_INTERVAL_MS,
|
|
);
|
|
|
|
if (!success) {
|
|
throw new Error(`${windowType} window was not visible after ${MAX_ATTEMPTS} attempts`);
|
|
}
|
|
});
|
|
|
|
When('I confirm the {string} window not visible', async function(this: ApplicationWorld, windowType: string) {
|
|
if (!this.app) {
|
|
throw new Error('Application is not launched');
|
|
}
|
|
|
|
const success = await this.waitForWindowCondition(
|
|
windowType,
|
|
(window, isVisible) => window !== undefined && !window.isClosed() && !isVisible,
|
|
MAX_ATTEMPTS,
|
|
RETRY_INTERVAL_MS,
|
|
);
|
|
|
|
if (!success) {
|
|
throw new Error(`${windowType} window was visible or not found after ${MAX_ATTEMPTS} attempts`);
|
|
}
|
|
});
|
|
|
|
When('I confirm the {string} window does not exist', async function(this: ApplicationWorld, windowType: string) {
|
|
if (!this.app) {
|
|
throw new Error('Application is not launched');
|
|
}
|
|
|
|
const success = await this.waitForWindowCondition(
|
|
windowType,
|
|
(window) => window === undefined,
|
|
MAX_ATTEMPTS,
|
|
RETRY_INTERVAL_MS,
|
|
);
|
|
|
|
if (!success) {
|
|
throw new Error(`${windowType} window still exists after ${MAX_ATTEMPTS} attempts`);
|
|
}
|
|
});
|
|
|
|
When('I confirm the {string} window browser view is positioned within visible window bounds', async function(this: ApplicationWorld, windowType: string) {
|
|
if (!this.app) {
|
|
throw new Error('Application is not available');
|
|
}
|
|
|
|
const targetWindow = await this.findWindowByType(windowType);
|
|
if (!targetWindow || targetWindow.isClosed()) {
|
|
throw new Error(`Window "${windowType}" is not available or has been closed`);
|
|
}
|
|
|
|
// Get the window dimensions to identify it - must match a defined WindowNames
|
|
const windowName = checkWindowName(windowType);
|
|
const windowDimensions = checkWindowDimension(windowName);
|
|
|
|
// Get browser view bounds for the specific window type
|
|
const viewInfo = await getBrowserViewInfo(this.app, windowDimensions);
|
|
|
|
if (!viewInfo.hasView || !viewInfo.view || !viewInfo.windowContent) {
|
|
throw new Error(`No browser view found in "${windowType}" window`);
|
|
}
|
|
|
|
// Check if browser view is within window content bounds
|
|
// View coordinates are relative to the window, so we check if they're within the content area
|
|
const viewRight = viewInfo.view.x + viewInfo.view.width;
|
|
const viewBottom = viewInfo.view.y + viewInfo.view.height;
|
|
const contentWidth = viewInfo.windowContent.width;
|
|
const contentHeight = viewInfo.windowContent.height;
|
|
|
|
const isWithinBounds = viewInfo.view.x >= 0 &&
|
|
viewInfo.view.y >= 0 &&
|
|
viewRight <= contentWidth &&
|
|
viewBottom <= contentHeight &&
|
|
viewInfo.view.width > 0 &&
|
|
viewInfo.view.height > 0;
|
|
|
|
if (!isWithinBounds) {
|
|
throw new Error(
|
|
`Browser view is not positioned within visible window bounds.\n` +
|
|
`View: {x: ${viewInfo.view.x}, y: ${viewInfo.view.y}, width: ${viewInfo.view.width}, height: ${viewInfo.view.height}}, ` +
|
|
`Window content: {width: ${contentWidth}, height: ${contentHeight}}`,
|
|
);
|
|
}
|
|
});
|
|
|
|
When('I confirm the {string} window browser view is not positioned within visible window bounds', async function(this: ApplicationWorld, windowType: string) {
|
|
if (!this.app) {
|
|
throw new Error('Application is not available');
|
|
}
|
|
|
|
const targetWindow = await this.findWindowByType(windowType);
|
|
if (!targetWindow || targetWindow.isClosed()) {
|
|
throw new Error(`Window "${windowType}" is not available or has been closed`);
|
|
}
|
|
|
|
// Get the window dimensions to identify it - must match a defined WindowNames
|
|
const windowName = checkWindowName(windowType);
|
|
const windowDimensions = checkWindowDimension(windowName);
|
|
|
|
// Get browser view bounds for the specific window type
|
|
const viewInfo = await getBrowserViewInfo(this.app, windowDimensions);
|
|
|
|
if (!viewInfo.hasView || !viewInfo.view || !viewInfo.windowContent) {
|
|
// No view found is acceptable for this check - means it's definitely not visible
|
|
return;
|
|
}
|
|
|
|
// Check if browser view is OUTSIDE window content bounds
|
|
// View coordinates are relative to the window, so we check if they're outside the content area
|
|
const viewRight = viewInfo.view.x + viewInfo.view.width;
|
|
const viewBottom = viewInfo.view.y + viewInfo.view.height;
|
|
const contentWidth = viewInfo.windowContent.width;
|
|
const contentHeight = viewInfo.windowContent.height;
|
|
|
|
const isWithinBounds = viewInfo.view.x >= 0 &&
|
|
viewInfo.view.y >= 0 &&
|
|
viewRight <= contentWidth &&
|
|
viewBottom <= contentHeight &&
|
|
viewInfo.view.width > 0 &&
|
|
viewInfo.view.height > 0;
|
|
|
|
if (isWithinBounds) {
|
|
throw new Error(
|
|
`Browser view IS positioned within visible window bounds, but expected it to be outside.\n` +
|
|
`View: {x: ${viewInfo.view.x}, y: ${viewInfo.view.y}, width: ${viewInfo.view.width}, height: ${viewInfo.view.height}}, ` +
|
|
`Window content: {width: ${contentWidth}, height: ${contentHeight}}`,
|
|
);
|
|
}
|
|
});
|