From 93707340842b05a6e2b2ebeed01f0784c5f2ae5b Mon Sep 17 00:00:00 2001 From: William Mantly Date: Mon, 27 Jul 2026 23:36:34 -0400 Subject: [PATCH] Add regression test: no native alert()/confirm()/prompt() Native confirm() blocks all further browser events on the page (found live, mid browser-automation testing, on sso-manager-node's equivalent secret-rotate flow -- it froze the tab). Every call site in this app was already removed in favor of app.messages.action/confirm/toast and app.modal.open; this static check (scans views/ and public/js|lib/js for bare alert(/confirm(/prompt() calls) keeps a regression from shipping unnoticed. Co-Authored-By: Claude Sonnet 5 --- nodejs/package.json | 6 +-- nodejs/test/unit/no_native_dialogs.test.js | 44 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 nodejs/test/unit/no_native_dialogs.test.js diff --git a/nodejs/package.json b/nodejs/package.json index 1c099bd..7f16d1e 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -10,10 +10,10 @@ "scripts": { "start": "node ./bin/www", "dev": "npx nodemon --ignore public/ ./bin/www", - "test": "node --test test/unit/callback_queue.test.js test/unit/host_lookup.test.js test/unit/wildcard_matchany.test.js test/unit/roles.test.js test/unit/oidc.test.js test/unit/safe_redirect.test.js test/unit/host_features.test.js test/unit/dynamic_record.test.js test/unit/hostname_validate.test.js test/unit/password_policy.test.js test/unit/basicauth.test.js test/unit/host_sso.test.js test/unit/unix_socket.test.js test/integration/dns_provider.test.js", - "test:unit": "node --test test/unit/callback_queue.test.js test/unit/host_lookup.test.js test/unit/wildcard_matchany.test.js test/unit/roles.test.js test/unit/oidc.test.js test/unit/safe_redirect.test.js test/unit/host_features.test.js test/unit/dynamic_record.test.js test/unit/hostname_validate.test.js test/unit/password_policy.test.js test/unit/basicauth.test.js test/unit/host_sso.test.js test/unit/unix_socket.test.js", + "test": "node --test test/unit/callback_queue.test.js test/unit/host_lookup.test.js test/unit/wildcard_matchany.test.js test/unit/roles.test.js test/unit/oidc.test.js test/unit/safe_redirect.test.js test/unit/host_features.test.js test/unit/dynamic_record.test.js test/unit/hostname_validate.test.js test/unit/password_policy.test.js test/unit/basicauth.test.js test/unit/host_sso.test.js test/unit/unix_socket.test.js test/unit/no_native_dialogs.test.js test/integration/dns_provider.test.js", + "test:unit": "node --test test/unit/callback_queue.test.js test/unit/host_lookup.test.js test/unit/wildcard_matchany.test.js test/unit/roles.test.js test/unit/oidc.test.js test/unit/safe_redirect.test.js test/unit/host_features.test.js test/unit/dynamic_record.test.js test/unit/hostname_validate.test.js test/unit/password_policy.test.js test/unit/basicauth.test.js test/unit/host_sso.test.js test/unit/unix_socket.test.js test/unit/no_native_dialogs.test.js", "test:integration": "node --test test/integration/dns_provider.test.js", - "test:watch": "node --test --watch test/unit/callback_queue.test.js test/unit/host_lookup.test.js test/unit/wildcard_matchany.test.js test/unit/roles.test.js test/unit/oidc.test.js test/unit/safe_redirect.test.js test/unit/host_features.test.js test/unit/dynamic_record.test.js test/unit/hostname_validate.test.js test/unit/password_policy.test.js test/unit/basicauth.test.js test/unit/host_sso.test.js test/unit/unix_socket.test.js test/integration/dns_provider.test.js" + "test:watch": "node --test --watch test/unit/callback_queue.test.js test/unit/host_lookup.test.js test/unit/wildcard_matchany.test.js test/unit/roles.test.js test/unit/oidc.test.js test/unit/safe_redirect.test.js test/unit/host_features.test.js test/unit/dynamic_record.test.js test/unit/hostname_validate.test.js test/unit/password_policy.test.js test/unit/basicauth.test.js test/unit/host_sso.test.js test/unit/unix_socket.test.js test/unit/no_native_dialogs.test.js test/integration/dns_provider.test.js" }, "engines": { "node": ">=18.0.0" diff --git a/nodejs/test/unit/no_native_dialogs.test.js b/nodejs/test/unit/no_native_dialogs.test.js new file mode 100644 index 0000000..58ed323 --- /dev/null +++ b/nodejs/test/unit/no_native_dialogs.test.js @@ -0,0 +1,44 @@ +'use strict'; + +// Regression guard: native alert()/confirm()/prompt() calls block all further +// browser events on the page (found live, mid browser-automation testing, on +// sso-manager-node's equivalent secret-rotate flow) and are visually +// inconsistent with the rest of the UI. Every call site was removed in favor +// of app.messages.action/confirm/toast and app.modal.open; this test keeps +// it that way. + +const { test } = require('node:test'); +const assert = require('node:assert'); +const fs = require('fs'); +const path = require('path'); + +const ROOTS = ['views', 'public/js', 'public/lib/js'].map((d) => path.join(__dirname, '..', '..', d)); + +const NATIVE_DIALOG_RE = /(^|[^.\w$])(alert|confirm|prompt)\s*\(/g; + +function walk(dir) { + let files = []; + if (!fs.existsSync(dir)) return files; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) files = files.concat(walk(full)); + else if (/\.(ejs|js)$/.test(entry.name)) files.push(full); + } + return files; +} + +test('no view or client-side script calls native alert()/confirm()/prompt()', () => { + const offenders = []; + for (const root of ROOTS) { + for (const file of walk(root)) { + const src = fs.readFileSync(file, 'utf8'); + let m; + NATIVE_DIALOG_RE.lastIndex = 0; + while ((m = NATIVE_DIALOG_RE.exec(src))) { + const line = src.slice(0, m.index).split('\n').length; + offenders.push(`${path.relative(path.join(__dirname, '..', '..'), file)}:${line} — ${m[2]}(`); + } + } + } + assert.deepStrictEqual(offenders, []); +});