Skip to content

Commit 552692e

Browse files
authored
Redesign remember-my-choice checkbox in WAYF (#2095)
* 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 * 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. * 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. * Remove explanatory class docblock restating what the test renders * 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 #2095 * Let rememberChoicePerIdp render the mock WAYF checkbox on its own # Why is this change needed? Prior to this change, the mock WAYF page only showed the remember my choice checkbox in per-SP mode when both rememberChoiceFeature and rememberChoicePerIdp were passed. In production only one mode can be enabled, so the per-SP mode never has the global flag set. The parameter was also missing from the testing docs. # How does it address the issue? This change makes rememberChoicePerIdp imply the feature, like in production, and documents both modes and example links in docs/testing.md and docs/js_testing.md. * Split the WAYF remember-my-choice template per mode # If applied, this commit will... Render the global and the per-SP remember-my-choice checkbox from two separate templates, selected by a thin if/elseif in rememberChoice.html.twig. # Why is this change needed? Prior to this change, one template held both variants and nested the per-SP checkbox inside the rememberChoiceFeature flag. The two modes are mutually exclusive, so the per-SP variant should not depend on the flag of the global variant, and the nesting hid that either/or relationship. # How does it address the issue? This change moves each variant into its own partial (rememberChoiceGlobal and rememberChoicePerIdp). The per-SP variant is rendered whenever rememberChoicePerIdp is set, the global variant only otherwise. The markup of both variants is unchanged. The render test now also covers the per-SP variant without the global flag and the case where both are disabled. # Provide links to any relevant tickets, articles or other resources Review feedback on the remember-my-choice checkbox pull request. * Fix double aria-pressed flip on the WAYF tooltip toggle Prior to this change, the per-SP remember-choice tooltip checkbox had aria-pressed flipped twice per label click on the WAYF. mouseBehaviour() attached changeAriaPressed to every input[aria-pressed], and toggleTooltipPressedStates() flips it as well. The attribute therefore ended at its original value while aria-expanded changed, giving screen readers a wrong pressed state. This change skips tooltip toggles in the generic aria-pressed handler of mouseBehaviour(), leaving toggleTooltipPressedStates() as the only owner, as on the consent page. The per-SP tooltip tests move to their own spec so they run regardless of the it.only in wayf.general.spec.js, and now assert aria-pressed in both states.
1 parent b6d8cad commit 552692e

25 files changed

Lines changed: 459 additions & 18 deletions

File tree

‎config/services/services.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ services:
44

55
OpenConext\EngineBlock\Service\Wayf\IdpSplitter:
66

7+
OpenConext\EngineBlockBundle\Service\RememberChoiceDurationFormatter:
8+
autowire: true
9+
arguments:
10+
$lifetimeInSeconds: '%wayf.remember_choice_per_idp_lifetime%'
11+
712
OpenConext\EngineBlockBundle\Service\WayfViewModelFactory:
813
arguments:
914
$wayfExtension: '@OpenConext\EngineBlockBundle\Twig\Extensions\Extension\Wayf'
15+
$durationFormatter: '@OpenConext\EngineBlockBundle\Service\RememberChoiceDurationFormatter'
1016

1117
OpenConext\EngineBlockBundle\Service\WayfRenderer:
1218
autowire: true

‎docs/js_testing.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ parameters can be used to manipulate the behaviour of the wayf that is rendered.
4242
| **Query parameter** | **Default value** | **Explanation** |
4343
|---|----|----|
4444
| displayUnconnectedIdpsWayf | (bool) false | Type: boolean. Display unconnected IdPs on the WAYF. |
45-
| rememberChoiceFeature | (bool) false | Type: boolean. Display the remember choice feature. |
45+
| rememberChoiceFeature | (bool) false | Type: boolean. Display the remember choice feature in global mode. |
46+
| rememberChoicePerIdp | (bool) false | Type: boolean. Display the remember choice feature in per-SP mode. Implies rememberChoiceFeature. |
4647
| cutoffPointForShowingUnfilteredIdps | (int) 100 | Type: integer. The number of IdPs to display on the WAYF before cutting them off. |
4748
| showIdpBanner | (bool) true | Type: boolean. Show the EduId (default IdP) banner on the WAYF or not |
4849
| defaultIdpEntityId | (string) null | Type: string. The entityId of the default IdP (EduId) |

‎docs/testing.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ The functional-testing route renders the WAYF page with synthetic IdP data, cont
2020
| `showIdPBanner` | bool | `true` | Whether to show the default IdP banner |
2121
| `displayUnconnectedIdpsWayf` | bool | `false` | Show unconnected IdPs with a "Request access" button |
2222
| `backLink` | bool | `false` | Show "Return to service provider" back link |
23-
| `rememberChoiceFeature` | bool | `false` | Show "Remember my choice" checkbox |
23+
| `rememberChoiceFeature` | bool | `false` | Show "Remember my choice" checkbox (global mode: the choice is stored in the `rememberchoice` cookie) |
24+
| `rememberChoicePerIdp` | bool | `false` | Show "Remember my choice" checkbox in per-SP mode (the choice is stored in the `rememberedidps` cookie). Implies `rememberChoiceFeature`, like in production where only one of the two modes can be enabled |
2425
| `cutoffPointForShowingUnfilteredIdps` | int | `100` | Hide the IdP list until the user searches when list length exceeds this value |
2526

2627
#### Baseline
@@ -38,7 +39,8 @@ The functional-testing route renders the WAYF page with synthetic IdP data, cont
3839

3940
#### UI features
4041
- [Back link](https://engine.dev.openconext.local/functional-testing/wayf?backLink=true)
41-
- [Remember my choice](https://engine.dev.openconext.local/functional-testing/wayf?rememberChoiceFeature=true)
42+
- [Remember my choice (global mode)](https://engine.dev.openconext.local/functional-testing/wayf?rememberChoiceFeature=true)
43+
- [Remember my choice (per-SP mode)](https://engine.dev.openconext.local/functional-testing/wayf?rememberChoicePerIdp=true)
4244
- [Default IdP banner](https://engine.dev.openconext.local/functional-testing/wayf?defaultIdpEntityId=https%3A%2F%2Fexample.com%2FentityId%2F3&showIdPBanner=true&addDiscoveries=false)
4345

4446
#### Preferred IdPs

‎languages/messages.en.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@
8686

8787
// Remove cookies
8888
'remember_choice' => 'Remember my choice',
89+
'remember_choice_per_idp' => 'Remember my choice for this service for %duration%',
90+
'remember_choice_duration_days' => '%count% days',
91+
'remember_choice_duration_minutes' => '%count% minutes',
92+
'remember_choice_tooltip_screenreader' => 'Why do we remember your choice?',
93+
'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. <strong>Please note</strong>: do you sometimes use a different account to log in? If so, please disable this option. You can reverse this setting later at <a href="http://profile.surfconext.nl/">profile.surfconext.nl</a>.',
8994
'cookie_removal_header' => 'Remove cookies',
9095
'cookie_remove_button' => 'Remove',
9196
'cookie_remove_all_button' => 'Remove all',

‎languages/messages.nl.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@
8585

8686
// Remove cookies
8787
'remember_choice' => 'Onthoud mijn keuze',
88+
'remember_choice_per_idp' => 'Onthoud mijn keuze voor deze dienst voor %duration%',
89+
'remember_choice_duration_days' => '%count% dagen',
90+
'remember_choice_duration_minutes' => '%count% minuten',
91+
'remember_choice_tooltip_screenreader' => 'Waarom onthouden we jouw keuze?',
92+
'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. <strong>Let op</strong>: gebruik je soms een ander account om in te loggen? Zet de optie dan uit. Je kunt de keuze later weer terugdraaien op <a href="http://profile.surfconext.nl/">profile.surfconext.nl</a>.',
8893
'cookie_removal_header' => 'Cookies verwijderen',
8994
'cookie_remove_button' => 'Verwijderen',
9095
'cookie_remove_all_button' => 'Alles verwijderen',

‎languages/messages.pt.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@
8282

8383
// Remove cookies
8484
'remember_choice' => 'Relembrar a minha escolha',
85+
'remember_choice_per_idp' => 'Relembrar a minha escolha para este serviço durante %duration%',
86+
'remember_choice_duration_days' => '%count% dias',
87+
'remember_choice_duration_minutes' => '%count% minutos',
88+
'remember_choice_tooltip_screenreader' => 'Porque é que relembramos a sua escolha?',
89+
'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 <a href="http://profile.surfconext.nl/">profile.surfconext.nl</a>.',
8590
'cookie_removal_header' => 'Remover cookies',
8691
'cookie_remove_button' => 'Remover',
8792
'cookie_remove_all_button' => 'Remover todos',
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/**
4+
* Copyright 2026 SURFnet B.V.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
declare(strict_types=1);
20+
21+
namespace OpenConext\EngineBlockBundle\Service;
22+
23+
use Symfony\Contracts\Translation\TranslatorInterface;
24+
25+
final class RememberChoiceDurationFormatter
26+
{
27+
private const SECONDS_PER_MINUTE = 60;
28+
private const SECONDS_PER_DAY = 86400;
29+
30+
public function __construct(
31+
private readonly TranslatorInterface $translator,
32+
private readonly int $lifetimeInSeconds,
33+
) {
34+
}
35+
36+
public function format(): string
37+
{
38+
if ($this->lifetimeInSeconds < self::SECONDS_PER_DAY) {
39+
$minutes = intdiv($this->lifetimeInSeconds, self::SECONDS_PER_MINUTE);
40+
return $this->translator->trans('remember_choice_duration_minutes', ['%count%' => $minutes]);
41+
}
42+
43+
$days = intdiv($this->lifetimeInSeconds, self::SECONDS_PER_DAY);
44+
return $this->translator->trans('remember_choice_duration_days', ['%count%' => $days]);
45+
}
46+
}

‎src/OpenConext/EngineBlockBundle/Service/WayfViewModelFactory.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class WayfViewModelFactory
2828
{
2929
public function __construct(
3030
private readonly Wayf $wayfExtension,
31+
private readonly RememberChoiceDurationFormatter $durationFormatter,
3132
) {
3233
}
3334

@@ -70,6 +71,7 @@ public function create(
7071
regularIdpList: $regularIdpList,
7172
preferredIdpList: $preferredIdpList,
7273
rememberChoicePerIdp: $rememberChoicePerIdp,
74+
rememberChoiceDuration: $rememberChoicePerIdp ? $this->durationFormatter->format() : '',
7375
);
7476
}
7577
}

‎src/OpenConext/EngineBlockBundle/ViewModel/WayfViewModel.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public function __construct(
5353
/** @var WayfIdp[] */
5454
public array $preferredIdpList,
5555
public bool $rememberChoicePerIdp = false,
56+
public string $rememberChoiceDuration = '',
5657
) {
5758
}
5859

‎src/OpenConext/EngineBlockFunctionalTestingBundle/Controllers/WayfController.php‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public function wayfAction(Request $request)
4444
$displayUnconnectedIdpsWayf = $request->query->getBoolean('displayUnconnectedIdpsWayf');
4545
$addDiscoveries = $request->query->getBoolean('addDiscoveries', true);
4646
$rememberChoiceFeature = $request->query->getBoolean('rememberChoiceFeature');
47+
$rememberChoicePerIdp = $request->query->getBoolean('rememberChoicePerIdp');
4748
$cutoffPointForShowingUnfilteredIdps = $request->query->getInt('cutoffPointForShowingUnfilteredIdps', 100);
4849
$showIdPBanner = $request->query->getBoolean('showIdPBanner', true);
4950
$defaultIdpEntityId = $request->query->get('defaultIdpEntityId', '');
@@ -66,10 +67,11 @@ public function wayfAction(Request $request)
6667
shouldDisplayBanner: $showIdPBanner,
6768
backLink: $backLink,
6869
cutoffPoint: $cutoffPointForShowingUnfilteredIdps,
69-
rememberChoice: $rememberChoiceFeature,
70+
rememberChoice: $rememberChoiceFeature || $rememberChoicePerIdp,
7071
showRequestAccess: $displayUnconnectedIdpsWayf,
7172
requestId: 'bogus-request-id',
7273
serviceProvider: TestEntitySeeder::buildSp(),
74+
rememberChoicePerIdp: $rememberChoicePerIdp,
7375
);
7476

7577
return new Response($output);

0 commit comments

Comments
 (0)