From f7243e70d68cde29c61cf0ccf6b1a3650371b273 Mon Sep 17 00:00:00 2001 From: scar-day <93832519+scar-day@users.noreply.github.com> Date: Sun, 1 Nov 2026 23:30:47 +0400 Subject: [PATCH 1/2] Auth: Fixed remember-device label wrapping --- scripts/signin-browser.md | 14 +++++ scripts/signin-browser.mjs | 102 ++++++++++++++++++++++++++++++++++++ styles/components/signin.ts | 1 + 3 files changed, 117 insertions(+) create mode 100644 scripts/signin-browser.md create mode 100644 scripts/signin-browser.mjs diff --git a/scripts/signin-browser.md b/scripts/signin-browser.md new file mode 100644 index 00000000..8fad09f4 --- /dev/null +++ b/scripts/signin-browser.md @@ -0,0 +1,14 @@ +# Sign-in layout browser regression + +Build the theme, then run the regression with Chromium, DejaVu Sans, and the unmodified `index.css` from a Forgejo +17.0.1 installation: + +```sh +bun bundle +FORGEJO_CSS=/path/to/forgejo/assets/css/index.css node scripts/signin-browser.mjs +``` + +Set `CHROMIUM` to the browser executable if it is not named `chromium`. The test renders the sign-in template's +remember-device control with Forgejo's stylesheet and the built theme. It checks line wrapping and checkbox interaction +at desktop and phone widths, including a longer translation and an unrelated inline label. It does not contact a running +Forgejo or submit a form. diff --git a/scripts/signin-browser.mjs b/scripts/signin-browser.mjs new file mode 100644 index 00000000..ca79e99c --- /dev/null +++ b/scripts/signin-browser.mjs @@ -0,0 +1,102 @@ +import { spawnSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; + +const browser = process.env.CHROMIUM || "chromium"; +const baseCss = process.env.FORGEJO_CSS; +if (!baseCss) throw new Error("Set FORGEJO_CSS to the index.css from Forgejo 17.0.1"); + +const theme = readFileSync(resolve("dist/theme-github-dark.css"), "utf8"); +const forgejo = readFileSync(resolve(baseCss), "utf8"); +const font = spawnSync("fc-match", ["-f", "%{family}", "DejaVu Sans"], { encoding: "utf8" }); +if (font.status !== 0 || !font.stdout.includes("DejaVu Sans")) throw new Error("DejaVu Sans is required for this test"); +const formCss = readFileSync(resolve("components/common/form/form.css"), "utf8"); +const template = readFileSync(resolve("templates/user/auth/signin_inner.tmpl"), "utf8"); +const markup = template.match(/
[\s\S]*?<\/div>/)?.[0]; +if (!markup) throw new Error("Remember-device markup was not found in the sign-in template"); + +const cases = [ + { name: "Russian desktop", text: "Запомнить это устройство", width: 1440, form: 352, lines: 1 }, + { name: "Russian phone", text: "Запомнить это устройство", width: 320, form: 288, lines: 1 }, + { + name: "Long translation desktop", + text: "Remember this device for future sign-ins on this computer", + width: 1440, + form: 352, + lines: 2, + multiline: true, + }, + { + name: "Long translation phone", + text: "Remember this device for future sign-ins on this computer", + width: 320, + form: 200, + lines: 2, + multiline: true, + }, +]; + +const directory = mkdtempSync(join(tmpdir(), "signin-browser-")); +let failures = 0; +try { + for (const test of cases) { + const remember = markup.replace('{{ctx.Locale.Tr "auth.remember_me"}}', test.text); + const html = ` + +
${remember}
`; + const wrapper = ` + `; + const file = join(directory, "fixture.html"); + writeFileSync(file, wrapper); + const result = spawnSync( + browser, + [ + "--headless", + "--disable-gpu", + "--disable-dev-shm-usage", + `--user-data-dir=${directory}/profile`, + `--window-size=${Math.max(500, test.width)},800`, + "--virtual-time-budget=5000", + "--dump-dom", + `file://${file}`, + ], + { encoding: "utf8", maxBuffer: 5_000_000 } + ); + if (result.status !== 0) throw new Error(`Chromium failed: ${result.stderr}`); + const encoded = result.stdout.match(/data-result="([^"]+)"/)?.[1]; + if (!encoded) throw new Error("Chromium did not report layout results"); + const actual = JSON.parse(encoded.replaceAll(""", '"').replaceAll("&", "&")); + const pass = + actual.viewport === test.width && + (test.multiline ? actual.lines >= test.lines : actual.lines === test.lines) && + actual.checked && + actual.text === test.text && + actual.right <= actual.fieldRight + 1 && + actual.otherWidth === (test.width < 768 ? test.form : 200); + console.log(`${pass ? "PASS" : "FAIL"} ${test.name}: ${JSON.stringify(actual)}`); + if (!pass) failures++; + } +} finally { + rmSync(directory, { recursive: true, force: true }); +} +if (failures) process.exitCode = 1; diff --git a/styles/components/signin.ts b/styles/components/signin.ts index aa8f59dd..59ea685f 100644 --- a/styles/components/signin.ts +++ b/styles/components/signin.ts @@ -127,6 +127,7 @@ const signIn = css` .signin-remember { margin: 0 0 16px; label { + width: 100%; display: inline-flex; align-items: center; gap: 8px; From f84918f3640ba6cf3b1042d6e8530f250105947f Mon Sep 17 00:00:00 2001 From: scar-day <93832519+scar-day@users.noreply.github.com> Date: Sun, 1 Nov 2026 23:35:02 +0400 Subject: [PATCH 2/2] Remove standalone sign-in test files --- scripts/signin-browser.md | 14 ----- scripts/signin-browser.mjs | 102 ------------------------------------- 2 files changed, 116 deletions(-) delete mode 100644 scripts/signin-browser.md delete mode 100644 scripts/signin-browser.mjs diff --git a/scripts/signin-browser.md b/scripts/signin-browser.md deleted file mode 100644 index 8fad09f4..00000000 --- a/scripts/signin-browser.md +++ /dev/null @@ -1,14 +0,0 @@ -# Sign-in layout browser regression - -Build the theme, then run the regression with Chromium, DejaVu Sans, and the unmodified `index.css` from a Forgejo -17.0.1 installation: - -```sh -bun bundle -FORGEJO_CSS=/path/to/forgejo/assets/css/index.css node scripts/signin-browser.mjs -``` - -Set `CHROMIUM` to the browser executable if it is not named `chromium`. The test renders the sign-in template's -remember-device control with Forgejo's stylesheet and the built theme. It checks line wrapping and checkbox interaction -at desktop and phone widths, including a longer translation and an unrelated inline label. It does not contact a running -Forgejo or submit a form. diff --git a/scripts/signin-browser.mjs b/scripts/signin-browser.mjs deleted file mode 100644 index ca79e99c..00000000 --- a/scripts/signin-browser.mjs +++ /dev/null @@ -1,102 +0,0 @@ -import { spawnSync } from "node:child_process"; -import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join, resolve } from "node:path"; - -const browser = process.env.CHROMIUM || "chromium"; -const baseCss = process.env.FORGEJO_CSS; -if (!baseCss) throw new Error("Set FORGEJO_CSS to the index.css from Forgejo 17.0.1"); - -const theme = readFileSync(resolve("dist/theme-github-dark.css"), "utf8"); -const forgejo = readFileSync(resolve(baseCss), "utf8"); -const font = spawnSync("fc-match", ["-f", "%{family}", "DejaVu Sans"], { encoding: "utf8" }); -if (font.status !== 0 || !font.stdout.includes("DejaVu Sans")) throw new Error("DejaVu Sans is required for this test"); -const formCss = readFileSync(resolve("components/common/form/form.css"), "utf8"); -const template = readFileSync(resolve("templates/user/auth/signin_inner.tmpl"), "utf8"); -const markup = template.match(/
[\s\S]*?<\/div>/)?.[0]; -if (!markup) throw new Error("Remember-device markup was not found in the sign-in template"); - -const cases = [ - { name: "Russian desktop", text: "Запомнить это устройство", width: 1440, form: 352, lines: 1 }, - { name: "Russian phone", text: "Запомнить это устройство", width: 320, form: 288, lines: 1 }, - { - name: "Long translation desktop", - text: "Remember this device for future sign-ins on this computer", - width: 1440, - form: 352, - lines: 2, - multiline: true, - }, - { - name: "Long translation phone", - text: "Remember this device for future sign-ins on this computer", - width: 320, - form: 200, - lines: 2, - multiline: true, - }, -]; - -const directory = mkdtempSync(join(tmpdir(), "signin-browser-")); -let failures = 0; -try { - for (const test of cases) { - const remember = markup.replace('{{ctx.Locale.Tr "auth.remember_me"}}', test.text); - const html = ` - - `; - const wrapper = ` - `; - const file = join(directory, "fixture.html"); - writeFileSync(file, wrapper); - const result = spawnSync( - browser, - [ - "--headless", - "--disable-gpu", - "--disable-dev-shm-usage", - `--user-data-dir=${directory}/profile`, - `--window-size=${Math.max(500, test.width)},800`, - "--virtual-time-budget=5000", - "--dump-dom", - `file://${file}`, - ], - { encoding: "utf8", maxBuffer: 5_000_000 } - ); - if (result.status !== 0) throw new Error(`Chromium failed: ${result.stderr}`); - const encoded = result.stdout.match(/data-result="([^"]+)"/)?.[1]; - if (!encoded) throw new Error("Chromium did not report layout results"); - const actual = JSON.parse(encoded.replaceAll(""", '"').replaceAll("&", "&")); - const pass = - actual.viewport === test.width && - (test.multiline ? actual.lines >= test.lines : actual.lines === test.lines) && - actual.checked && - actual.text === test.text && - actual.right <= actual.fieldRight + 1 && - actual.otherWidth === (test.width < 768 ? test.form : 200); - console.log(`${pass ? "PASS" : "FAIL"} ${test.name}: ${JSON.stringify(actual)}`); - if (!pass) failures++; - } -} finally { - rmSync(directory, { recursive: true, force: true }); -} -if (failures) process.exitCode = 1;