mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-31 13:42:50 +00:00
* fix(app): preserve adaptive theme events while hidden Read the current scheme through fresh matchMedia lists so hidden runtime reads do not consume the pending events on listener-owned lists. Carry Dan Sabin's fix from #7172 into the current combined Unistyles patch, and cover both transition directions with a real Electron regression in the existing desktop browser suite. Co-authored-by: Dan Sabin <dan@freethinking.it> * test(desktop): run adaptive theme regression on Linux Electron retains --no-sandbox in argv, so the positional lookup used the script filename as the artifact directory. Read the final argument so the same real Electron regression exercises Linux as well as macOS. Co-authored-by: Nurşat Bülbül <85260051+Nurshot@users.noreply.github.com> Co-authored-by: Dan Sabin <dan@freethinking.it> --------- Co-authored-by: Nurshot <85260051+Nurshot@users.noreply.github.com> Co-authored-by: Dan Sabin <dan@freethinking.it> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
159 lines
5.9 KiB
JavaScript
159 lines
5.9 KiB
JavaScript
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { execFile } from "node:child_process";
|
|
import { promisify } from "node:util";
|
|
import electronPath from "electron";
|
|
import { build } from "esbuild";
|
|
|
|
export async function runAdaptiveThemeRegression(artifactDir) {
|
|
const { outputFiles } = await build({
|
|
stdin: {
|
|
resolveDir: fileURLToPath(new URL("../../app", import.meta.url)),
|
|
contents: `
|
|
import { createElement } from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
import { StyleSheet, UnistylesRuntime, withUnistyles } from "react-native-unistyles";
|
|
StyleSheet.configure({
|
|
themes: {
|
|
dark: { foreground: "#fafafa", background: "#181b1a" },
|
|
light: { foreground: "#1a1a1e", background: "#ffffff" },
|
|
},
|
|
settings: { adaptiveThemes: true },
|
|
});
|
|
const Message = withUnistyles(({ color }) =>
|
|
createElement("p", { style: { color } }, "Theme-reactive message"));
|
|
createRoot(document.getElementById("root")).render(createElement(Message, {
|
|
style: { margin: 0 },
|
|
uniProps: theme => ({ color: theme.foreground }),
|
|
}));
|
|
window.readThemeForTest = () => UnistylesRuntime.themeName;
|
|
`,
|
|
},
|
|
bundle: true,
|
|
write: false,
|
|
format: "iife",
|
|
platform: "browser",
|
|
alias: { "react-native": "react-native-web" },
|
|
define: { __DEV__: "false" },
|
|
resolveExtensions: [".web.js", ".js", ".json"],
|
|
});
|
|
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "paseo-adaptive-theme-"));
|
|
try {
|
|
await fs.writeFile(path.join(directory, "renderer.js"), outputFiles[0].text);
|
|
await fs.writeFile(path.join(directory, "main.cjs"), `(${electronRegression.toString()})()`);
|
|
// A separate process avoids Playwright's focus emulation, which keeps hidden pages visible.
|
|
const env = { ...process.env };
|
|
delete env.ELECTRON_RUN_AS_NODE;
|
|
const args = [
|
|
...(process.platform === "linux" ? ["--no-sandbox"] : []),
|
|
path.join(directory, "main.cjs"),
|
|
artifactDir,
|
|
];
|
|
const command = process.platform === "linux" ? "xvfb-run" : electronPath;
|
|
const commandArgs =
|
|
process.platform === "linux"
|
|
? ["-a", "--server-args=-screen 0 1280x800x24", electronPath, ...args]
|
|
: args;
|
|
await promisify(execFile)(command, commandArgs, {
|
|
env,
|
|
timeout: 30_000,
|
|
});
|
|
return JSON.parse(await fs.readFile(path.join(artifactDir, "adaptive-theme.json"), "utf8"));
|
|
} finally {
|
|
await fs.rm(directory, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
async function electronRegression() {
|
|
const { app, BrowserWindow, nativeTheme } = require("electron");
|
|
const assert = require("node:assert/strict");
|
|
const files = require("node:fs");
|
|
const paths = require("node:path");
|
|
const { setTimeout: delay } = require("node:timers/promises");
|
|
app.setPath("userData", paths.join(__dirname, "profile"));
|
|
app.on("window-all-closed", () => {});
|
|
// Electron keeps Linux's --no-sandbox in argv; the artifact directory is always last.
|
|
const artifactDir = process.argv.at(-1);
|
|
let window;
|
|
const observations = [];
|
|
const colors = {
|
|
dark: { foreground: "rgb(250, 250, 250)", background: "rgb(24, 27, 26)" },
|
|
light: { foreground: "rgb(26, 26, 30)", background: "rgb(255, 255, 255)" },
|
|
};
|
|
async function read(expression) {
|
|
return window.webContents.executeJavaScript(expression);
|
|
}
|
|
async function expectValue(expression, expected) {
|
|
let actual;
|
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
actual = await read(expression);
|
|
if (actual === expected) return;
|
|
await delay(50);
|
|
}
|
|
assert.equal(actual, expected, expression);
|
|
}
|
|
try {
|
|
await app.whenReady();
|
|
if (process.platform === "darwin") {
|
|
app.setActivationPolicy("accessory");
|
|
app.dock.hide();
|
|
}
|
|
for (const [from, to] of [
|
|
["dark", "light"],
|
|
["light", "dark"],
|
|
]) {
|
|
nativeTheme.themeSource = from;
|
|
window = new BrowserWindow({ width: 600, height: 200, show: false, skipTaskbar: true });
|
|
await window.loadURL(
|
|
"data:text/html," +
|
|
encodeURIComponent(`<!doctype html>
|
|
<style>body { background: var(--background); } p { font: 20px system-ui; }</style>
|
|
<div id="root"></div>`),
|
|
);
|
|
await read(files.readFileSync(paths.join(__dirname, "renderer.js"), "utf8"));
|
|
window.showInactive();
|
|
const foreground =
|
|
"document.querySelector('p') && getComputedStyle(document.querySelector('p')).color";
|
|
const background = "getComputedStyle(document.body).backgroundColor";
|
|
await expectValue(foreground, colors[from].foreground);
|
|
await expectValue(background, colors[from].background);
|
|
window.hide();
|
|
await expectValue("document.visibilityState", "hidden");
|
|
nativeTheme.themeSource = to;
|
|
await expectValue(`matchMedia('(prefers-color-scheme: ${to})').matches`, true);
|
|
// Reading the public runtime while hidden must not consume the pending theme notification.
|
|
assert.equal(await read("window.readThemeForTest()"), to);
|
|
window.showInactive();
|
|
await expectValue("document.visibilityState", "visible");
|
|
await expectValue(background, colors[to].background);
|
|
try {
|
|
await expectValue(foreground, colors[to].foreground);
|
|
} finally {
|
|
files.writeFileSync(
|
|
paths.join(artifactDir, `theme-${from}-to-${to}.png`),
|
|
(await window.webContents.capturePage()).toPNG(),
|
|
);
|
|
}
|
|
observations.push({
|
|
from,
|
|
to,
|
|
foreground: await read(foreground),
|
|
background: await read(background),
|
|
});
|
|
window.destroy();
|
|
window = undefined;
|
|
}
|
|
files.writeFileSync(
|
|
paths.join(artifactDir, "adaptive-theme.json"),
|
|
JSON.stringify(observations, null, 2),
|
|
);
|
|
app.exit(0);
|
|
} catch (error) {
|
|
console.error(error);
|
|
window?.destroy();
|
|
app.exit(1);
|
|
}
|
|
}
|