diff --git a/test/web_interface/test_calendar_oauth_endpoints.py b/test/web_interface/test_calendar_oauth_endpoints.py index e7208cb8..c170fa8b 100644 --- a/test/web_interface/test_calendar_oauth_endpoints.py +++ b/test/web_interface/test_calendar_oauth_endpoints.py @@ -81,6 +81,35 @@ class TestTheRoutesExistAtAll: assert "'/plugins/calendar/list-calendars'" in source assert "'/plugins/calendar/authenticate'" in source + def test_the_oauth_widget_is_dispatched_not_rendered_as_a_text_box(self): + # The string branch of the config template dispatches on an allow-list + # of widget names; anything missing from it silently falls through to a + # plain . That produced two boxes on the calendar + # page -- the widget's own, and a stray one for the same field -- and + # no way to tell which to paste into. + template = (Path(project_root) + / 'web_interface/templates/v3/partials/plugin_config.html' + ).read_text(encoding='utf-8') + allow_list_line = [ln for ln in template.splitlines() + if "str_widget in [" in ln] + assert allow_list_line, "the string widget allow-list moved" + assert "'google-oauth'" in allow_list_line[0], allow_list_line[0] + + def test_the_widget_script_is_served(self): + base = (Path(project_root) / 'web_interface/templates/v3/base.html' + ).read_text(encoding='utf-8') + assert 'widgets/google-oauth.js' in base + + def test_the_failed_page_is_called_out_loudly(self): + # The loopback redirect lands on a browser error page at exactly the + # moment the user has to act. In small grey text it gets missed and the + # flow reads as broken while it is working. + widget = (Path(project_root) + / 'web_interface/static/v3/js/widgets/google-oauth.js' + ).read_text(encoding='utf-8') + assert 'expected' in widget.lower() + assert 'amber' in widget, "the warning is not visually distinguished" + class TestItSaysWhatIsWrong: def test_listing_without_a_token_asks_for_step_2(self, client): diff --git a/web_interface/static/v3/js/widgets/google-oauth.js b/web_interface/static/v3/js/widgets/google-oauth.js index e288adb7..44088b2f 100644 --- a/web_interface/static/v3/js/widgets/google-oauth.js +++ b/web_interface/static/v3/js/widgets/google-oauth.js @@ -66,12 +66,28 @@ link.className = 'text-blue-400 underline text-sm break-all'; link.textContent = 'Open the Google consent screen'; - const hint = document.createElement('p'); - hint.className = 'text-xs text-gray-400 mt-2'; - hint.textContent = - 'After approving, your browser will try to open a page that fails ' - + 'to load. That is expected. Copy its full address from the bar ' - + 'and paste it below.'; + // Deliberately loud. After consent the browser is redirected to a + // loopback address nothing is listening on, so it lands on a + // browser error page -- which reads as a failure at exactly the + // moment the user has to act on it. Said quietly in grey it gets + // missed, and the flow looks broken when it is working. + const hint = document.createElement('div'); + hint.className = + 'mt-3 p-3 rounded-md border border-amber-500/60 bg-amber-500/10'; + hint.innerHTML = + '

' + + ' ' + + 'The next page will fail to load. That is expected.

' + + '

' + + 'After you approve access, Google sends your browser to ' + + '127.0.0.1, where nothing is running \u2014 so you will see ' + + '"This site can\u2019t be reached" or similar. Nothing has gone wrong. ' + + 'Copy the entire address out of the address bar ' + + '(it contains ?code=...) and paste it in the box below.

'; + + const codeLabel = document.createElement('label'); + codeLabel.className = 'block text-xs text-gray-300 mt-3'; + codeLabel.textContent = 'Paste the address from that failed page here:'; const codeInput = document.createElement('input'); codeInput.type = 'text'; @@ -150,6 +166,7 @@ step2.appendChild(link); step2.appendChild(hint); + step2.appendChild(codeLabel); step2.appendChild(codeInput); step2.appendChild(finishBtn); diff --git a/web_interface/templates/v3/partials/plugin_config.html b/web_interface/templates/v3/partials/plugin_config.html index 41651fb8..f789fd5b 100644 --- a/web_interface/templates/v3/partials/plugin_config.html +++ b/web_interface/templates/v3/partials/plugin_config.html @@ -815,7 +815,7 @@ Changes in the file manager save immediately — no need to click Save Configuration.

- {% elif str_widget in ['text-input', 'textarea', 'select-dropdown', 'toggle-switch', 'radio-group', 'date-picker', 'time-picker', 'slider', 'color-picker', 'email-input', 'url-input', 'password-input', 'font-selector', 'file-upload-single', 'plugin-file-manager'] %} + {% elif str_widget in ['text-input', 'textarea', 'select-dropdown', 'toggle-switch', 'radio-group', 'date-picker', 'time-picker', 'slider', 'color-picker', 'email-input', 'url-input', 'password-input', 'font-selector', 'file-upload-single', 'plugin-file-manager', 'google-oauth'] %} {# Render widget container #}