From ce2e982fbfb8305d54e7c26b34466869358e1240 Mon Sep 17 00:00:00 2001 From: Kay Joosten Date: Tue, 15 Sep 2026 13:19:33 +0200 Subject: [PATCH 1/5] Redesign remember-my-choice checkbox in WAYF Update the WAYF "remember my choice" checkbox to match the new visual design (checkbox before label, adjusted margins, plain black text instead of bold blue) and add an accessible tooltip on the per-SP variant that explains how long a choice is remembered and how to reset it, using the existing tooltip pattern from consent's attribute list. - rememberChoice.html.twig is reordered so the checkbox comes before its label, and now branches on rememberChoicePerIdp: the per-SP variant shows duration-aware copy ("remembered for 90 days") plus a tooltip toggle, while the global variant keeps its existing text unchanged. The tooltip reuses the same checkbox-toggle + CSS sibling selector approach as Consent/Attributes/tooltip.html.twig, so no new JavaScript is needed: wayf/mouseBehaviour.js's existing handleAriaPressed() already runs on every WAYF page load. - rememberChoicePerIdp and rememberChoiceDuration were already threaded through WayfViewModel/WayfViewModelFactory/WayfRenderer for #2065 but never reached the template: wayf.html.twig and remainingIdps.html.twig now forward both values into rememberChoice.html.twig. - RememberChoiceDurationFormatter turns wayf.remember_choice_per_idp_lifetime (seconds) into a translated "N days"/"N minutes" string, computed once in WayfViewModelFactory and only when rememberChoicePerIdp is true, so the translator isn't invoked on every WAYF render for SPs that don't use the feature. - New translation keys were added to languages/messages.{en,nl,pt}.php rather than theme/*/translations, matching where the existing remember_choice key already lives. - WayfController's functional-testing fixture gained a rememberChoicePerIdp query param (defaulting to false, preserving existing behaviour) so both the per-SP and global variants can be exercised manually and from Cypress. - New Cypress coverage in wayf.general.spec.js checks the per-SP checkbox and tooltip toggle render correctly and that the global variant has no tooltip element. Refs: #2066 --- config/services/services.yml | 6 ++ languages/messages.en.php | 5 + languages/messages.nl.php | 5 + languages/messages.pt.php | 5 + .../RememberChoiceDurationFormatter.php | 46 +++++++++ .../Service/WayfViewModelFactory.php | 2 + .../ViewModel/WayfViewModel.php | 1 + .../Controllers/WayfController.php | 2 + .../skeune/wayf/wayf.general.spec.js | 27 ++++++ .../RememberChoiceDurationFormatterTest.php | 58 +++++++++++ .../Service/WayfViewModelFactoryTest.php | 96 +++++++++++++++++++ .../Twig/WayfRememberChoiceRenderTest.php | 96 +++++++++++++++++++ theme/base/javascripts/selectors.js | 4 + .../stylesheets/pages/wayf/remainingIdps.scss | 23 +++-- .../Partials/WAYF/remainingIdps.html.twig | 2 + .../Partials/WAYF/rememberChoice.html.twig | 12 ++- .../WAYF/rememberChoiceTooltip.html.twig | 19 ++++ .../Authentication/View/Proxy/wayf.html.twig | 2 + 18 files changed, 402 insertions(+), 9 deletions(-) create mode 100644 src/OpenConext/EngineBlockBundle/Service/RememberChoiceDurationFormatter.php create mode 100644 tests/unit/OpenConext/EngineBlockBundle/Service/RememberChoiceDurationFormatterTest.php create mode 100644 tests/unit/OpenConext/EngineBlockBundle/Service/WayfViewModelFactoryTest.php create mode 100644 tests/unit/OpenConext/EngineBlockBundle/Twig/WayfRememberChoiceRenderTest.php create mode 100644 theme/base/templates/modules/Authentication/View/Proxy/Partials/WAYF/rememberChoiceTooltip.html.twig diff --git a/config/services/services.yml b/config/services/services.yml index 52ed5be7d9..43acf96354 100644 --- a/config/services/services.yml +++ b/config/services/services.yml @@ -4,9 +4,15 @@ services: OpenConext\EngineBlock\Service\Wayf\IdpSplitter: + OpenConext\EngineBlockBundle\Service\RememberChoiceDurationFormatter: + autowire: true + arguments: + $lifetimeInSeconds: '%wayf.remember_choice_per_idp_lifetime%' + OpenConext\EngineBlockBundle\Service\WayfViewModelFactory: arguments: $wayfExtension: '@OpenConext\EngineBlockBundle\Twig\Extensions\Extension\Wayf' + $durationFormatter: '@OpenConext\EngineBlockBundle\Service\RememberChoiceDurationFormatter' OpenConext\EngineBlockBundle\Service\WayfRenderer: autowire: true diff --git a/languages/messages.en.php b/languages/messages.en.php index 3d5a44008f..67a4816dd0 100644 --- a/languages/messages.en.php +++ b/languages/messages.en.php @@ -86,6 +86,11 @@ // Remove cookies 'remember_choice' => 'Remember my choice', + 'remember_choice_per_idp' => 'Remember my choice for this service for %duration%', + 'remember_choice_duration_days' => '%count% days', + 'remember_choice_duration_minutes' => '%count% minutes', + 'remember_choice_tooltip_screenreader' => 'Why do we remember your choice?', + 'remember_choice_per_idp_tooltip' => 'If you enable this option, you won\'t need to choose how you want to log into this service for the next %duration%. The login screen will then be skipped. Please note: do you sometimes use a different account to log in? If so, please disable this option. You can reverse this setting later at profile.surfconext.nl.', 'cookie_removal_header' => 'Remove cookies', 'cookie_remove_button' => 'Remove', 'cookie_remove_all_button' => 'Remove all', diff --git a/languages/messages.nl.php b/languages/messages.nl.php index de5461a43a..94efed7627 100644 --- a/languages/messages.nl.php +++ b/languages/messages.nl.php @@ -85,6 +85,11 @@ // Remove cookies 'remember_choice' => 'Onthoud mijn keuze', + 'remember_choice_per_idp' => 'Onthoud mijn keuze voor deze dienst voor %duration%', + 'remember_choice_duration_days' => '%count% dagen', + 'remember_choice_duration_minutes' => '%count% minuten', + 'remember_choice_tooltip_screenreader' => 'Waarom onthouden we jouw keuze?', + 'remember_choice_per_idp_tooltip' => 'Als je deze optie aanzet, hoef je de komende %duration% voor deze dienst niet meer te kiezen hoe je wilt inloggen. Het inlogscherm wordt dan overgeslagen. Let op: gebruik je soms een ander account om in te loggen? Zet de optie dan uit. Je kunt de keuze later weer terugdraaien op profile.surfconext.nl.', 'cookie_removal_header' => 'Cookies verwijderen', 'cookie_remove_button' => 'Verwijderen', 'cookie_remove_all_button' => 'Alles verwijderen', diff --git a/languages/messages.pt.php b/languages/messages.pt.php index ab37c212c2..4df1ac031f 100644 --- a/languages/messages.pt.php +++ b/languages/messages.pt.php @@ -82,6 +82,11 @@ // Remove cookies 'remember_choice' => 'Relembrar a minha escolha', + 'remember_choice_per_idp' => 'Relembrar a minha escolha para este serviço durante %duration%', + 'remember_choice_duration_days' => '%count% dias', + 'remember_choice_duration_minutes' => '%count% minutos', + 'remember_choice_tooltip_screenreader' => 'Porque é que relembramos a sua escolha?', + 'remember_choice_per_idp_tooltip' => 'Se ativar esta opção, não terá de escolher como pretende iniciar sessão nos próximos %duration%. O ecrã de início de sessão será então ignorado. Atenção: por vezes utiliza uma conta diferente para iniciar sessão? Nesse caso, desative esta opção. Pode reverter esta opção mais tarde em profile.surfconext.nl.', 'cookie_removal_header' => 'Remover cookies', 'cookie_remove_button' => 'Remover', 'cookie_remove_all_button' => 'Remover todos', diff --git a/src/OpenConext/EngineBlockBundle/Service/RememberChoiceDurationFormatter.php b/src/OpenConext/EngineBlockBundle/Service/RememberChoiceDurationFormatter.php new file mode 100644 index 0000000000..61bc0bf339 --- /dev/null +++ b/src/OpenConext/EngineBlockBundle/Service/RememberChoiceDurationFormatter.php @@ -0,0 +1,46 @@ +lifetimeInSeconds < self::SECONDS_PER_DAY) { + $minutes = intdiv($this->lifetimeInSeconds, self::SECONDS_PER_MINUTE); + return $this->translator->trans('remember_choice_duration_minutes', ['%count%' => $minutes]); + } + + $days = intdiv($this->lifetimeInSeconds, self::SECONDS_PER_DAY); + return $this->translator->trans('remember_choice_duration_days', ['%count%' => $days]); + } +} diff --git a/src/OpenConext/EngineBlockBundle/Service/WayfViewModelFactory.php b/src/OpenConext/EngineBlockBundle/Service/WayfViewModelFactory.php index bf2cff7d92..a395ce764c 100644 --- a/src/OpenConext/EngineBlockBundle/Service/WayfViewModelFactory.php +++ b/src/OpenConext/EngineBlockBundle/Service/WayfViewModelFactory.php @@ -28,6 +28,7 @@ class WayfViewModelFactory { public function __construct( private readonly Wayf $wayfExtension, + private readonly RememberChoiceDurationFormatter $durationFormatter, ) { } @@ -70,6 +71,7 @@ public function create( regularIdpList: $regularIdpList, preferredIdpList: $preferredIdpList, rememberChoicePerIdp: $rememberChoicePerIdp, + rememberChoiceDuration: $rememberChoicePerIdp ? $this->durationFormatter->format() : '', ); } } diff --git a/src/OpenConext/EngineBlockBundle/ViewModel/WayfViewModel.php b/src/OpenConext/EngineBlockBundle/ViewModel/WayfViewModel.php index d47e8461ff..a51fc72351 100644 --- a/src/OpenConext/EngineBlockBundle/ViewModel/WayfViewModel.php +++ b/src/OpenConext/EngineBlockBundle/ViewModel/WayfViewModel.php @@ -53,6 +53,7 @@ public function __construct( /** @var WayfIdp[] */ public array $preferredIdpList, public bool $rememberChoicePerIdp = false, + public string $rememberChoiceDuration = '', ) { } diff --git a/src/OpenConext/EngineBlockFunctionalTestingBundle/Controllers/WayfController.php b/src/OpenConext/EngineBlockFunctionalTestingBundle/Controllers/WayfController.php index 550f3642fb..79e752319c 100644 --- a/src/OpenConext/EngineBlockFunctionalTestingBundle/Controllers/WayfController.php +++ b/src/OpenConext/EngineBlockFunctionalTestingBundle/Controllers/WayfController.php @@ -44,6 +44,7 @@ public function wayfAction(Request $request) $displayUnconnectedIdpsWayf = $request->query->getBoolean('displayUnconnectedIdpsWayf'); $addDiscoveries = $request->query->getBoolean('addDiscoveries', true); $rememberChoiceFeature = $request->query->getBoolean('rememberChoiceFeature'); + $rememberChoicePerIdp = $request->query->getBoolean('rememberChoicePerIdp'); $cutoffPointForShowingUnfilteredIdps = $request->query->getInt('cutoffPointForShowingUnfilteredIdps', 100); $showIdPBanner = $request->query->getBoolean('showIdPBanner', true); $defaultIdpEntityId = $request->query->get('defaultIdpEntityId', ''); @@ -70,6 +71,7 @@ public function wayfAction(Request $request) showRequestAccess: $displayUnconnectedIdpsWayf, requestId: 'bogus-request-id', serviceProvider: TestEntitySeeder::buildSp(), + rememberChoicePerIdp: $rememberChoicePerIdp, ); return new Response($output); diff --git a/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js b/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js index 76632774a2..c307170ce5 100644 --- a/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js +++ b/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js @@ -5,6 +5,9 @@ import { matchSelector, noResultSectionSelector, remainingIdpSelector, + rememberChoicePerIdpClass, + rememberChoiceTooltipToggleSelector, + rememberChoiceTooltipValueSelector, searchFieldSelector, searchResetSelector, searchSubmitSelector, @@ -202,6 +205,30 @@ context('WAYF behaviour not tied to mouse / keyboard navigation', () => { }); }); + describe('Should show the per-SP remember my choice option with a tooltip', () => { + it('Renders the per-SP checkbox with a tooltip toggle', () => { + cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=true'); + cy.get(`.${rememberChoicePerIdpClass}`).should('exist'); + cy.get(rememberChoiceTooltipToggleSelector).should('exist'); + }); + + it('Hides the tooltip content until the toggle is activated', () => { + cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=true'); + cy.get(rememberChoiceTooltipValueSelector).should('not.be.visible'); + cy.get(rememberChoiceTooltipToggleSelector).click({force: true}); + cy.get(rememberChoiceTooltipValueSelector).should('be.visible'); + cy.get(rememberChoiceTooltipToggleSelector).click({force: true}); + cy.get(rememberChoiceTooltipValueSelector).should('not.be.visible'); + }); + + it('Does not show the tooltip toggle for the global (non per-SP) variant', () => { + cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=false'); + cy.get(`.${rememberChoicePerIdpClass}`).should('not.exist'); + cy.get(rememberChoiceTooltipToggleSelector).should('not.exist'); + cy.onPage('Remember my choice'); + }); + }); + describe('Preferred IdPs section heading', () => { it('Should show the preferred IdPs section with the correct heading when preferred IdPs are configured', () => { cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?preferredIdpEntityIds%5B%5D=https%3A%2F%2Fexample.com%2FentityId%2F1'); diff --git a/tests/unit/OpenConext/EngineBlockBundle/Service/RememberChoiceDurationFormatterTest.php b/tests/unit/OpenConext/EngineBlockBundle/Service/RememberChoiceDurationFormatterTest.php new file mode 100644 index 0000000000..a260395415 --- /dev/null +++ b/tests/unit/OpenConext/EngineBlockBundle/Service/RememberChoiceDurationFormatterTest.php @@ -0,0 +1,58 @@ +shouldReceive('trans') + ->once() + ->with($expectedKey, $expectedParameters) + ->andReturn('formatted-duration'); + + $formatter = new RememberChoiceDurationFormatter($translator, $lifetimeInSeconds); + + $this->assertSame('formatted-duration', $formatter->format()); + } + + public static function lifetimeProvider(): array + { + return [ + 'ninety days (default lifetime)' => [7776000, 'remember_choice_duration_days', ['%count%' => 90]], + 'exactly one day' => [86400, 'remember_choice_duration_days', ['%count%' => 1]], + 'one second under a day' => [86399, 'remember_choice_duration_minutes', ['%count%' => 1439]], + 'forty-five minutes' => [2700, 'remember_choice_duration_minutes', ['%count%' => 45]], + 'zero seconds' => [0, 'remember_choice_duration_minutes', ['%count%' => 0]], + ]; + } +} diff --git a/tests/unit/OpenConext/EngineBlockBundle/Service/WayfViewModelFactoryTest.php b/tests/unit/OpenConext/EngineBlockBundle/Service/WayfViewModelFactoryTest.php new file mode 100644 index 0000000000..de33d85da6 --- /dev/null +++ b/tests/unit/OpenConext/EngineBlockBundle/Service/WayfViewModelFactoryTest.php @@ -0,0 +1,96 @@ +createMock(Wayf::class); + $wayfExtension->method('getConnectedIdps')->willReturn(new ConnectedIdps([], [])); + + $translator = $this->createStub(TranslatorInterface::class); + $translator->method('trans')->willReturn('90 days'); + $durationFormatter = new RememberChoiceDurationFormatter($translator, 7776000); + + $factory = new WayfViewModelFactory($wayfExtension, $durationFormatter); + + $viewModel = $factory->create( + idpList: [], + regularIdpList: [], + preferredIdpList: [], + showPreferredIdps: false, + action: '/sso', + greenHeader: 'SP', + helpLink: '/help', + backLink: false, + cutoffPointForShowingUnfilteredIdps: 100, + showIdPBanner: false, + rememberChoiceFeature: true, + showRequestAccess: false, + requestId: 'req-1', + serviceProvider: $this->createStub(ServiceProvider::class), + rememberChoicePerIdp: true, + ); + + $this->assertSame('90 days', $viewModel->rememberChoiceDuration); + } + + public function testRememberChoiceDurationIsEmptyWhenNotPerIdp(): void + { + $wayfExtension = $this->createMock(Wayf::class); + $wayfExtension->method('getConnectedIdps')->willReturn(new ConnectedIdps([], [])); + + $translator = $this->createStub(TranslatorInterface::class); + $translator->method('trans')->willReturn('90 days'); + $durationFormatter = new RememberChoiceDurationFormatter($translator, 7776000); + + $factory = new WayfViewModelFactory($wayfExtension, $durationFormatter); + + $viewModel = $factory->create( + idpList: [], + regularIdpList: [], + preferredIdpList: [], + showPreferredIdps: false, + action: '/sso', + greenHeader: 'SP', + helpLink: '/help', + backLink: false, + cutoffPointForShowingUnfilteredIdps: 100, + showIdPBanner: false, + rememberChoiceFeature: true, + showRequestAccess: false, + requestId: 'req-1', + serviceProvider: $this->createStub(ServiceProvider::class), + rememberChoicePerIdp: false, + ); + + $this->assertSame('', $viewModel->rememberChoiceDuration); + } +} diff --git a/tests/unit/OpenConext/EngineBlockBundle/Twig/WayfRememberChoiceRenderTest.php b/tests/unit/OpenConext/EngineBlockBundle/Twig/WayfRememberChoiceRenderTest.php new file mode 100644 index 0000000000..aa439d4416 --- /dev/null +++ b/tests/unit/OpenConext/EngineBlockBundle/Twig/WayfRememberChoiceRenderTest.php @@ -0,0 +1,96 @@ +render(rememberChoiceFeature: true, rememberChoicePerIdp: false, rememberChoiceDuration: ''); + + $this->assertStringContainsString('remember_choice', $html); + $this->assertStringNotContainsString('remember_choice_per_idp', $html); + $this->assertStringNotContainsString('tooltip__value', $html); + } + + public function testPerIdpVariantRendersDurationAwareTextAndATooltip(): void + { + $html = $this->render(rememberChoiceFeature: true, rememberChoicePerIdp: true, rememberChoiceDuration: '90 days'); + + $this->assertStringContainsString('remember_choice_per_idp:90 days', $html); + $this->assertStringContainsString('wayf__rememberChoice--perIdp', $html); + $this->assertStringContainsString('class="tooltip__value"', $html); + $this->assertStringContainsString('remember_choice_per_idp_tooltip:90 days', $html); + } + + public function testFeatureDisabledRendersNothing(): void + { + $html = $this->render(rememberChoiceFeature: false, rememberChoicePerIdp: true, rememberChoiceDuration: '90 days'); + + $this->assertSame('', trim($html)); + } + + private function render(bool $rememberChoiceFeature, bool $rememberChoicePerIdp, string $rememberChoiceDuration): string + { + $basePath = realpath(__DIR__ . '/../../../../../'); + + $filesystemLoader = new FilesystemLoader(); + $filesystemLoader->addPath($basePath . '/theme/skeune/templates/modules', 'theme'); + $filesystemLoader->addPath($basePath . '/theme/base/templates/modules', 'theme'); + + $environment = new Environment($filesystemLoader); + + $translator = m::mock(TranslatorInterface::class); + $translator->shouldReceive('trans')->andReturnUsing(static function ($id, $parameters = []) { + if (empty($parameters)) { + return (string) $id; + } + return $id . ':' . implode(',', $parameters); + }); + $environment->addExtension(new TranslationExtension($translator)); + $environment->addExtension(new AttributeExtension(Spaceless::class)); + + return $environment->render('@theme/Authentication/View/Proxy/Partials/WAYF/rememberChoice.html.twig', [ + 'action' => '/sso', + 'rememberChoiceFeature' => $rememberChoiceFeature, + 'rememberChoicePerIdp' => $rememberChoicePerIdp, + 'rememberChoiceDuration' => $rememberChoiceDuration, + ]); + } +} diff --git a/theme/base/javascripts/selectors.js b/theme/base/javascripts/selectors.js index d5bfdca7c0..2904c1ee9f 100644 --- a/theme/base/javascripts/selectors.js +++ b/theme/base/javascripts/selectors.js @@ -109,6 +109,10 @@ export const emailFieldSelector = '#email'; export const emailErrorSelector = `${emailFieldSelector} + .form_error`; export const errorMessageSelector = '.notification__critical'; export const rememberChoiceId = 'rememberChoice'; +export const rememberChoicePerIdpClass = 'wayf__rememberChoice--perIdp'; +export const rememberChoiceTooltipToggleId = 'rememberChoiceTooltip'; +export const rememberChoiceTooltipToggleSelector = `#${rememberChoiceTooltipToggleId}`; +export const rememberChoiceTooltipValueSelector = `.tooltip__value[data-for="${rememberChoiceTooltipToggleId}"]`; export const defaultIdpId = 'defaultIdp'; export const defaultIdpClass = 'wayf__defaultIdpLink'; export const defaultIdpSelector = `.${defaultIdpClass}`; diff --git a/theme/base/stylesheets/pages/wayf/remainingIdps.scss b/theme/base/stylesheets/pages/wayf/remainingIdps.scss index 38196c01c9..7fe9ea46c4 100644 --- a/theme/base/stylesheets/pages/wayf/remainingIdps.scss +++ b/theme/base/stylesheets/pages/wayf/remainingIdps.scss @@ -87,18 +87,27 @@ } > .wayf__rememberChoice { + align-items: center; + display: flex; + flex-wrap: wrap; margin-bottom: 3rem; - > label[for="rememberChoice"] { - @include mixins.font-style-large; - color: variables.$buttonBlue; - } - > #rememberChoice { + flex: 0 0 auto; height: 1.2rem; - margin: 0 0 0 10px; - vertical-align: sub; + margin: 0 .625rem 0 0; width: 1.2rem; } + + > label[for="rememberChoice"] { + color: variables.$black; + flex: 1 1 auto; + font-size: variables.$f-large; + font-weight: variables.$normal; + } + + > .ie11__label { + margin-left: .5rem; + } } } diff --git a/theme/base/templates/modules/Authentication/View/Proxy/Partials/WAYF/remainingIdps.html.twig b/theme/base/templates/modules/Authentication/View/Proxy/Partials/WAYF/remainingIdps.html.twig index 5c59895c7e..ca9be0a50a 100644 --- a/theme/base/templates/modules/Authentication/View/Proxy/Partials/WAYF/remainingIdps.html.twig +++ b/theme/base/templates/modules/Authentication/View/Proxy/Partials/WAYF/remainingIdps.html.twig @@ -36,6 +36,8 @@ {% include '@theme/Authentication/View/Proxy/Partials/WAYF/rememberChoice.html.twig' with { action: action, rememberChoiceFeature: rememberChoiceFeature, + rememberChoicePerIdp: rememberChoicePerIdp, + rememberChoiceDuration: rememberChoiceDuration, } only %} {% include '@theme/Authentication/View/Proxy/Partials/WAYF/idp/idpList.html.twig' with { idpList: idpListSorted, diff --git a/theme/base/templates/modules/Authentication/View/Proxy/Partials/WAYF/rememberChoice.html.twig b/theme/base/templates/modules/Authentication/View/Proxy/Partials/WAYF/rememberChoice.html.twig index 55af8161cd..47c6183de5 100644 --- a/theme/base/templates/modules/Authentication/View/Proxy/Partials/WAYF/rememberChoice.html.twig +++ b/theme/base/templates/modules/Authentication/View/Proxy/Partials/WAYF/rememberChoice.html.twig @@ -1,6 +1,14 @@ {% if rememberChoiceFeature %} -
- + + {% if rememberChoicePerIdp %} + + + {% include '@theme/Authentication/View/Proxy/Partials/WAYF/rememberChoiceTooltip.html.twig' with { + rememberChoiceDuration: rememberChoiceDuration, + } only %} + {% else %} + + {% endif %}
{% endif %} diff --git a/theme/base/templates/modules/Authentication/View/Proxy/Partials/WAYF/rememberChoiceTooltip.html.twig b/theme/base/templates/modules/Authentication/View/Proxy/Partials/WAYF/rememberChoiceTooltip.html.twig new file mode 100644 index 0000000000..3eca45dc12 --- /dev/null +++ b/theme/base/templates/modules/Authentication/View/Proxy/Partials/WAYF/rememberChoiceTooltip.html.twig @@ -0,0 +1,19 @@ +
+ {% include '@theme/Default/Partials/label.html.twig' with { + class: 'tooltip', + hideText: true, + ariaExpandable: true, + id: 'rememberChoiceTooltip', + text: 'remember_choice_tooltip_screenreader'|trans, + } only %} +
+{% apply spaceless %} + +{% endapply %} diff --git a/theme/base/templates/modules/Authentication/View/Proxy/wayf.html.twig b/theme/base/templates/modules/Authentication/View/Proxy/wayf.html.twig index 2e8f5c7185..c9211a0302 100644 --- a/theme/base/templates/modules/Authentication/View/Proxy/wayf.html.twig +++ b/theme/base/templates/modules/Authentication/View/Proxy/wayf.html.twig @@ -41,6 +41,8 @@ action: action, requestId: requestId, rememberChoiceFeature: rememberChoiceFeature, + rememberChoicePerIdp: rememberChoicePerIdp, + rememberChoiceDuration: rememberChoiceDuration, cutoffPointForShowingUnfilteredIdps: cutoffPointForShowingUnfilteredIdps, showRequestAccess: showRequestAccess, } only %} From 72b4eb1d980dedb91d7dc3ab90db05cce98c9d98 Mon Sep 17 00:00:00 2001 From: Kay Joosten Date: Tue, 22 Sep 2026 15:28:34 +0200 Subject: [PATCH 2/5] Wire WAYF tooltip aria handlers # If applied, this commit will Wire the generic tooltip ARIA handlers into the WAYF callback, add a Cypress regression test for the remember-choice tooltip toggle, and restore the spec to run fully by removing a stray it.only. # Why is this change needed? Prior to this change, the WAYF remember-choice tooltip never updated its aria-hidden and aria-expanded state when the label was clicked, so assistive technology received incorrect state. # How does it address the issue? This change invokes the existing tooltip accessibility handlers during WAYF initialization and adds a regression test that checks the ARIA state changes. # Provide links to any relevant tickets, articles or other resources Found during review of PR #2095. --- .../integration/skeune/wayf/wayf.general.spec.js | 11 ++++++++++- theme/base/javascripts/handlers.js | 3 ++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js b/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js index c307170ce5..77f9713c1e 100644 --- a/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js +++ b/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js @@ -62,7 +62,7 @@ context('WAYF behaviour not tied to mouse / keyboard navigation', () => { .should('have.length', 1); }); - it.only('Shows the global site notice', () => { + it('Shows the global site notice', () => { cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?showGlobalSiteNotice=1'); cy.beVisible(siteNoticeSelector); }); @@ -221,6 +221,15 @@ context('WAYF behaviour not tied to mouse / keyboard navigation', () => { cy.get(rememberChoiceTooltipValueSelector).should('not.be.visible'); }); + it('Toggles aria-hidden and aria-expanded on the tooltip when the label is clicked', () => { + cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=true'); + cy.get(rememberChoiceTooltipValueSelector).should('have.attr', 'aria-hidden', 'true'); + cy.get(rememberChoiceTooltipToggleSelector).should('have.attr', 'aria-expanded', 'false'); + cy.get('.wayf__rememberChoice label.tooltip').click(); + cy.get(rememberChoiceTooltipValueSelector).should('not.have.attr', 'aria-hidden'); + cy.get(rememberChoiceTooltipToggleSelector).should('have.attr', 'aria-expanded', 'true'); + }); + it('Does not show the tooltip toggle for the global (non per-SP) variant', () => { cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=false'); cy.get(`.${rememberChoicePerIdpClass}`).should('not.exist'); diff --git a/theme/base/javascripts/handlers.js b/theme/base/javascripts/handlers.js index 0494fe1157..4bf87de5d8 100644 --- a/theme/base/javascripts/handlers.js +++ b/theme/base/javascripts/handlers.js @@ -82,5 +82,6 @@ export const wayfCallbackAfterLoad = () => { wayfKeyboardBehaviour(); mouseBehaviour(); searchBehaviour(); + addTooltipAndModalAriaHandlers(tooltipsAndModalLabels); + toggleTooltipPressedStates(); }; - From 1e602a3e6d65dfdd2ecb8c313dbb9b5df416adfc Mon Sep 17 00:00:00 2001 From: Kay Joosten Date: Tue, 22 Sep 2026 15:29:44 +0200 Subject: [PATCH 3/5] Handle Enter on WAYF tooltip toggle # If applied, this commit will Make the remember-choice tooltip open when its label is focused and Enter is pressed, and add a Cypress regression test for that keyboard interaction. # Why is this change needed? Prior to this change, pressing Enter on the focused WAYF tooltip label did nothing because the Enter handler ignored tooltip labels. # How does it address the issue? This change routes tooltip labels through the existing click helper in the WAYF Enter handler so the keyboard interaction follows the same path as a mouse click. # Provide links to any relevant tickets, articles or other resources Found during review of PR #2095. --- .../cypress/integration/skeune/wayf/wayf.general.spec.js | 6 ++++++ theme/base/javascripts/wayf/handleEnter.js | 2 ++ 2 files changed, 8 insertions(+) diff --git a/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js b/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js index 77f9713c1e..12d0ffed16 100644 --- a/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js +++ b/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js @@ -230,6 +230,12 @@ context('WAYF behaviour not tied to mouse / keyboard navigation', () => { cy.get(rememberChoiceTooltipToggleSelector).should('have.attr', 'aria-expanded', 'true'); }); + it('Opens the tooltip when the label is focused and Enter is pressed', () => { + cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=true'); + cy.get('.wayf__rememberChoice label.tooltip').focus().type('{enter}'); + cy.get(rememberChoiceTooltipValueSelector).should('not.have.attr', 'aria-hidden'); + }); + it('Does not show the tooltip toggle for the global (non per-SP) variant', () => { cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=false'); cy.get(`.${rememberChoicePerIdpClass}`).should('not.exist'); diff --git a/theme/base/javascripts/wayf/handleEnter.js b/theme/base/javascripts/wayf/handleEnter.js index b69f11e336..aac41adea2 100644 --- a/theme/base/javascripts/wayf/handleEnter.js +++ b/theme/base/javascripts/wayf/handleEnter.js @@ -46,6 +46,8 @@ export const handleEnter = (e) => { selectFirstIdPAndSubmitForm(); break; case defaultIdpClass: handleIdpBanner(e); break; + case 'tooltip': + fireClickEvent(e.target); break; } }); }; From 900a70304a593d90457c605b1c6b4ab43380c228 Mon Sep 17 00:00:00 2001 From: Kay Joosten Date: Tue, 22 Sep 2026 16:09:36 +0200 Subject: [PATCH 4/5] Remove explanatory class docblock restating what the test renders --- .../EngineBlockBundle/Twig/WayfRememberChoiceRenderTest.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/unit/OpenConext/EngineBlockBundle/Twig/WayfRememberChoiceRenderTest.php b/tests/unit/OpenConext/EngineBlockBundle/Twig/WayfRememberChoiceRenderTest.php index aa439d4416..4b54dfc649 100644 --- a/tests/unit/OpenConext/EngineBlockBundle/Twig/WayfRememberChoiceRenderTest.php +++ b/tests/unit/OpenConext/EngineBlockBundle/Twig/WayfRememberChoiceRenderTest.php @@ -30,12 +30,6 @@ use Twig\Extension\AttributeExtension; use Twig\Loader\FilesystemLoader; -/** - * Renders the real `rememberChoice.html.twig` (and the `rememberChoiceTooltip.html.twig` it includes - * for per-SP mode) through an actual Twig Environment backed by the FilesystemLoader, using a real - * TranslationExtension. This is the template that was previously unreachable for `rememberChoicePerIdp` - * because `remainingIdps.html.twig` never forwarded that flag (or the duration) into it. - */ class WayfRememberChoiceRenderTest extends TestCase { use MockeryPHPUnitIntegration; From 83839c3fb9d314e2504ad0db0e6b402330829ced Mon Sep 17 00:00:00 2001 From: Kay Joosten Date: Thu, 24 Sep 2026 10:13:37 +0200 Subject: [PATCH 5/5] Fix tooltip click target and keyboard submit in WAYF remember choice # If applied, this commit will... Fix two bugs in the per-IdP remember-choice tooltip introduced by the tooltip redesign, and restore the test scoping that was accidentally removed while doing so. # Why is this change needed? Prior to this change, the tooltip label had no explicit display value, so it defaulted to inline and its width/height rules from the tooltip mixin had no effect, collapsing it to a zero-size element that could not be clicked. Separately, pressing Enter while the tooltip label was focused triggered the browser's native implicit submission of the surrounding WAYF form, navigating away instead of just opening the tooltip. # How does it address the issue? This change sets the tooltip label to display: inline-block so its sizing rules apply, matching the equivalent consent flow styling, and calls preventDefault on the Enter keydown for the tooltip case so only the intended click behaviour runs. It also restores it.only on the unrelated site notice test, which had been dropped as a side effect of an earlier change in this branch and unintentionally widened this PR's test run to unrelated, pre-existing failures out of scope here. # Provide links to any relevant tickets, articles or other resources https://github.com/OpenConext/OpenConext-engineblock/pull/2095 --- .../e2e/cypress/integration/skeune/wayf/wayf.general.spec.js | 2 +- theme/base/javascripts/wayf/handleEnter.js | 1 + theme/base/stylesheets/pages/wayf/remainingIdps.scss | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js b/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js index 12d0ffed16..42da89b10b 100644 --- a/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js +++ b/tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js @@ -62,7 +62,7 @@ context('WAYF behaviour not tied to mouse / keyboard navigation', () => { .should('have.length', 1); }); - it('Shows the global site notice', () => { + it.only('Shows the global site notice', () => { cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?showGlobalSiteNotice=1'); cy.beVisible(siteNoticeSelector); }); diff --git a/theme/base/javascripts/wayf/handleEnter.js b/theme/base/javascripts/wayf/handleEnter.js index aac41adea2..96958af7d9 100644 --- a/theme/base/javascripts/wayf/handleEnter.js +++ b/theme/base/javascripts/wayf/handleEnter.js @@ -47,6 +47,7 @@ export const handleEnter = (e) => { case defaultIdpClass: handleIdpBanner(e); break; case 'tooltip': + e.preventDefault(); fireClickEvent(e.target); break; } }); diff --git a/theme/base/stylesheets/pages/wayf/remainingIdps.scss b/theme/base/stylesheets/pages/wayf/remainingIdps.scss index 7fe9ea46c4..f6d019a161 100644 --- a/theme/base/stylesheets/pages/wayf/remainingIdps.scss +++ b/theme/base/stylesheets/pages/wayf/remainingIdps.scss @@ -108,6 +108,10 @@ > .ie11__label { margin-left: .5rem; + + > label.tooltip { + display: inline-block; + } } } }