diff --git a/static/js/settings.js b/static/js/settings.js index 3819c83fa..540acff00 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -5790,29 +5790,30 @@ export function close() { window.history.replaceState(null, '', clean); const success = sp.has('email_oauth_success'); const errMsg = sp.get('email_oauth_error') || ''; - // Open settings → integrations after the app has initialised. - function _tryOpen() { - if (window.settingsModule && typeof window.settingsModule.open === 'function') { - window.settingsModule.open('integrations'); - // Brief toast-style banner. - const banner = document.createElement('div'); - banner.textContent = success - ? '✓ Google account connected — email is ready' - : `Google OAuth failed: ${errMsg || 'unknown error'}`; - Object.assign(banner.style, { - position: 'fixed', bottom: '24px', left: '50%', transform: 'translateX(-50%)', - background: success ? 'var(--accent, #50fa7b)' : 'var(--red, #ff5555)', - color: '#000', padding: '8px 18px', borderRadius: '6px', fontSize: '12px', - fontWeight: '600', zIndex: '99999', pointerEvents: 'none', - boxShadow: '0 2px 12px rgba(0,0,0,0.3)', - }); - document.body.appendChild(banner); - setTimeout(() => banner.remove(), 4000); - } else { - setTimeout(_tryOpen, 100); - } + // Open settings → integrations once the document is ready. This module owns + // the open() API, so it does not need to wait for a window-level alias. + function _showResult() { + open('integrations'); + // Brief toast-style banner. + const banner = document.createElement('div'); + banner.textContent = success + ? 'Google account connected — email is ready' + : `Google OAuth failed: ${errMsg || 'unknown error'}`; + Object.assign(banner.style, { + position: 'fixed', bottom: '24px', left: '50%', transform: 'translateX(-50%)', + background: success ? 'var(--accent, #50fa7b)' : 'var(--red, #ff5555)', + color: '#000', padding: '8px 18px', borderRadius: '6px', fontSize: '12px', + fontWeight: '600', zIndex: '99999', pointerEvents: 'none', + boxShadow: '0 2px 12px rgba(0,0,0,0.3)', + }); + document.body.appendChild(banner); + setTimeout(() => banner.remove(), 4000); + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', _showResult, { once: true }); + } else { + _showResult(); } - _tryOpen(); })(); const settingsModule = { open, close, initIntegrations, initUnifiedIntegrations, syncAdminVisibility, refreshAiModelEndpoints }; diff --git a/tests/test_email_oauth_settings_redirect.py b/tests/test_email_oauth_settings_redirect.py new file mode 100644 index 000000000..f7d588132 --- /dev/null +++ b/tests/test_email_oauth_settings_redirect.py @@ -0,0 +1,19 @@ +"""Regression coverage for the settings UI after Google OAuth redirects.""" + +from pathlib import Path + + +_REPO = Path(__file__).resolve().parents[1] + + +def test_oauth_redirect_uses_the_module_local_settings_api(): + source = (_REPO / "static" / "js" / "settings.js").read_text(encoding="utf-8") + handler = source[ + source.index("(function _handleOauthRedirect"): + source.index("const settingsModule =") + ] + + assert "open('integrations');" in handler + assert "window.settingsModule" not in handler + assert "window.__odysseusAppStarted" not in handler + assert "document.addEventListener('DOMContentLoaded', _showResult, { once: true })" in handler