'use strict'; /* * Identifier-first login (#258). * * The password box does not exist until an address has been submitted. That is what lets the * organization lookup happen BEFORE a credential is offered, so someone whose company requires its * own identity provider is never shown a password box that is going to be refused. * * Verified in a real browser as well (password hidden -> submit -> visible + focused -> edit the * address -> hidden again); these assertions stop the wiring being removed silently. */ const { test } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const LOGIN = fs.readFileSync(path.join(__dirname, '..', '..', 'frontend', 'js', 'views', 'login.js'), 'utf8'); test('password visibility depends on BOTH identification and SSO-only', () => { assert.match(LOGIN, /const showPassword = identified && !ssoOnlyDomain;/, 'the two drivers must be combined in one place so they cannot disagree'); }); test('the primary button advances before it signs in', () => { assert.match(LOGIN, /if \(identified && !ssoOnlyDomain\) return doLogin\(\);\s*\n\s*identify\(\);/, 'the button must identify first and only sign in once an address is known'); assert.match(LOGIN, /btn\.textContent = identified && !ssoOnlyDomain \? t\('auth\.sign_in'\) : t\('auth\.next'\)/); }); test('editing the address returns to the identifier step', () => { assert.match(LOGIN, /if \(!identified\) return;\s*\n\s*identified = false;/, 'a corrected address must get a fresh answer, not the previous domain\'s'); }); test('the per-keystroke lookup is gone', () => { assert.doesNotMatch(LOGIN, /ssoLookupTimer/, 'the debounced lookup answered for half-typed domains and burned a 10/min budget'); assert.match(LOGIN, /async function identify\(\)[\s\S]{0,400}await lookupOrgSso\(email\)/, 'the lookup now runs on submit'); }); test('instance-wide providers are never hidden', () => { // Deliberate: they are the operator's, offered to everyone, and the server refuses them for an // SSO-only organization anyway. Hiding them made the page change shape while typing. assert.doesNotMatch(LOGIN, /getElementById\('instanceProviders'\)[\s\S]{0,120}style\.display/, 'nothing may hide #instanceProviders'); }); test('first-run setup skips identifier-first', () => { assert.match(LOGIN, /if \(isSetup\) identified = true;/, 'creating the first admin needs both fields at once'); }); test('the initial state is applied after its declarations (temporal dead zone)', () => { const decl = LOGIN.indexOf('let identified = false;'); const call = LOGIN.lastIndexOf('\n applyFormState();'); assert.ok(decl !== -1 && call !== -1, 'both the declaration and the init call must exist'); assert.ok(call > decl, 'applyFormState() must be called AFTER the let declarations — earlier throws on the TDZ, which ' + 'on this page means a login form that never renders'); });