mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 01:16:15 -08:00
The custom minimize/maximize/close controls stayed hidden forever after leaving HTML-element (video player) fullscreen on Windows: window state was polled at event time, and isFullScreen() can still report the pre-transition value while 'leave-full-screen' fires, leaving a stale push with no later event to correct it. The same polling on the companion flag cleared isMaximized during fullscreen transitions and stuck the maximize/restore glyph on the wrong icon. attachWindowStateEvents now seeds the state once at window creation and each event patches only the flag it names, sending a copy per push. The enter/leave-html-full-screen variants are wired too. Regression coverage: app-window-state.spec.ts (9 cases, 6 of which fail against the old implementation) and an Electron E2E case that toggles HTML element fullscreen and asserts the controls come back.
190 lines
6.6 KiB
TypeScript
190 lines
6.6 KiB
TypeScript
import {
|
|
closeElectronApp,
|
|
expect,
|
|
launchElectronApp,
|
|
test,
|
|
} from './electron-test-fixtures';
|
|
|
|
// Custom window controls are only rendered on Windows/Linux; macOS keeps
|
|
// the native traffic lights.
|
|
//
|
|
// Linux CI runs Electron under xvfb, which has no window manager, so
|
|
// maximize/minimize never take effect there — those tests are skipped and
|
|
// rely on Windows CI plus local Linux/macOS runs for coverage.
|
|
const isWindowManagerlessCi =
|
|
process.platform === 'linux' && !!process.env['CI'];
|
|
|
|
test.describe('Custom window controls', () => {
|
|
test.skip(
|
|
process.platform === 'darwin',
|
|
'macOS uses native traffic lights instead of custom controls'
|
|
);
|
|
|
|
test('@electron renders the window control buttons', async ({
|
|
dataDir,
|
|
}) => {
|
|
const app = await launchElectronApp(dataDir);
|
|
|
|
try {
|
|
await expect(
|
|
app.mainWindow.getByTestId('window-minimize')
|
|
).toBeVisible();
|
|
await expect(
|
|
app.mainWindow.getByTestId('window-maximize')
|
|
).toBeVisible();
|
|
await expect(
|
|
app.mainWindow.getByTestId('window-close')
|
|
).toBeVisible();
|
|
await expect(
|
|
app.mainWindow.getByTestId('window-maximize-glyph')
|
|
).toBeVisible();
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
}
|
|
});
|
|
|
|
test('@electron maximize button toggles the native window state', async ({
|
|
dataDir,
|
|
}) => {
|
|
test.skip(
|
|
isWindowManagerlessCi,
|
|
'xvfb on Linux CI has no window manager'
|
|
);
|
|
const app = await launchElectronApp(dataDir);
|
|
|
|
try {
|
|
const isMaximized = () =>
|
|
app.electronApp.evaluate(({ BrowserWindow }) => {
|
|
const mainWindow = BrowserWindow.getAllWindows()[0];
|
|
return mainWindow ? mainWindow.isMaximized() : false;
|
|
});
|
|
|
|
await app.mainWindow.getByTestId('window-maximize').click();
|
|
await expect.poll(isMaximized, { timeout: 10_000 }).toBe(true);
|
|
await expect(
|
|
app.mainWindow.getByTestId('window-restore-glyph')
|
|
).toBeVisible();
|
|
|
|
await app.mainWindow.getByTestId('window-maximize').click();
|
|
await expect.poll(isMaximized, { timeout: 10_000 }).toBe(false);
|
|
await expect(
|
|
app.mainWindow.getByTestId('window-maximize-glyph')
|
|
).toBeVisible();
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
}
|
|
});
|
|
|
|
test('@electron reflects window state changes triggered from the main process', async ({
|
|
dataDir,
|
|
}) => {
|
|
test.skip(
|
|
isWindowManagerlessCi,
|
|
'xvfb on Linux CI has no window manager'
|
|
);
|
|
const app = await launchElectronApp(dataDir);
|
|
|
|
try {
|
|
await app.electronApp.evaluate(({ BrowserWindow }) => {
|
|
BrowserWindow.getAllWindows()[0]?.maximize();
|
|
});
|
|
await expect(
|
|
app.mainWindow.getByTestId('window-restore-glyph')
|
|
).toBeVisible({ timeout: 10_000 });
|
|
|
|
await app.electronApp.evaluate(({ BrowserWindow }) => {
|
|
BrowserWindow.getAllWindows()[0]?.unmaximize();
|
|
});
|
|
await expect(
|
|
app.mainWindow.getByTestId('window-maximize-glyph')
|
|
).toBeVisible({ timeout: 10_000 });
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
}
|
|
});
|
|
|
|
test('@electron controls reappear after leaving HTML element fullscreen', async ({
|
|
dataDir,
|
|
}) => {
|
|
test.skip(
|
|
isWindowManagerlessCi,
|
|
'xvfb on Linux CI has no window manager'
|
|
);
|
|
const app = await launchElectronApp(dataDir);
|
|
|
|
try {
|
|
const controls = app.mainWindow.locator('app-window-controls');
|
|
await expect(controls).not.toHaveClass(/is-hidden/);
|
|
|
|
// The video players fullscreen their player root through the
|
|
// HTML element-fullscreen API. That API needs transient user
|
|
// activation, so trigger it from a real click on a temporary
|
|
// overlay instead of calling it directly from evaluate().
|
|
await app.mainWindow.evaluate(() => {
|
|
const overlay = document.createElement('div');
|
|
overlay.id = 'e2e-fullscreen-trigger';
|
|
overlay.style.cssText =
|
|
'position: fixed; inset: 0; z-index: 2147483647;';
|
|
overlay.addEventListener('click', () => {
|
|
overlay.remove();
|
|
void document.documentElement.requestFullscreen();
|
|
});
|
|
document.body.append(overlay);
|
|
});
|
|
await app.mainWindow.locator('#e2e-fullscreen-trigger').click();
|
|
|
|
const isHtmlFullScreen = () =>
|
|
app.mainWindow.evaluate(
|
|
() => document.fullscreenElement !== null
|
|
);
|
|
|
|
await expect.poll(isHtmlFullScreen, { timeout: 10_000 }).toBe(
|
|
true
|
|
);
|
|
await expect(controls).toHaveClass(/is-hidden/, {
|
|
timeout: 10_000,
|
|
});
|
|
|
|
await app.mainWindow.evaluate(() => document.exitFullscreen());
|
|
await expect.poll(isHtmlFullScreen, { timeout: 10_000 }).toBe(
|
|
false
|
|
);
|
|
|
|
// Regression: the exit push must not carry a stale
|
|
// isFullScreen=true read mid-transition — the controls have to
|
|
// come back once fullscreen is left.
|
|
await expect(controls).not.toHaveClass(/is-hidden/, {
|
|
timeout: 10_000,
|
|
});
|
|
await expect(
|
|
app.mainWindow.getByTestId('window-minimize')
|
|
).toBeVisible();
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
}
|
|
});
|
|
|
|
test('@electron minimize button minimizes the window', async ({
|
|
dataDir,
|
|
}) => {
|
|
test.skip(
|
|
isWindowManagerlessCi,
|
|
'xvfb on Linux CI has no window manager'
|
|
);
|
|
const app = await launchElectronApp(dataDir);
|
|
|
|
try {
|
|
const isMinimized = () =>
|
|
app.electronApp.evaluate(({ BrowserWindow }) => {
|
|
const mainWindow = BrowserWindow.getAllWindows()[0];
|
|
return mainWindow ? mainWindow.isMinimized() : false;
|
|
});
|
|
|
|
await app.mainWindow.getByTestId('window-minimize').click();
|
|
await expect.poll(isMinimized, { timeout: 10_000 }).toBe(true);
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
}
|
|
});
|
|
});
|