Skip to content

Commit d02163f

Browse files
committed
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
1 parent c245f63 commit d02163f

18 files changed

Lines changed: 402 additions & 9 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

‎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: 2 additions & 0 deletions
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', '');
@@ -70,6 +71,7 @@ public function wayfAction(Request $request)
7071
showRequestAccess: $displayUnconnectedIdpsWayf,
7172
requestId: 'bogus-request-id',
7273
serviceProvider: TestEntitySeeder::buildSp(),
74+
rememberChoicePerIdp: $rememberChoicePerIdp,
7375
);
7476

7577
return new Response($output);

‎tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {
55
matchSelector,
66
noResultSectionSelector,
77
remainingIdpSelector,
8+
rememberChoicePerIdpClass,
9+
rememberChoiceTooltipToggleSelector,
10+
rememberChoiceTooltipValueSelector,
811
searchFieldSelector,
912
searchResetSelector,
1013
searchSubmitSelector,
@@ -202,6 +205,30 @@ context('WAYF behaviour not tied to mouse / keyboard navigation', () => {
202205
});
203206
});
204207

208+
describe('Should show the per-SP remember my choice option with a tooltip', () => {
209+
it('Renders the per-SP checkbox with a tooltip toggle', () => {
210+
cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=true');
211+
cy.get(`.${rememberChoicePerIdpClass}`).should('exist');
212+
cy.get(rememberChoiceTooltipToggleSelector).should('exist');
213+
});
214+
215+
it('Hides the tooltip content until the toggle is activated', () => {
216+
cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=true');
217+
cy.get(rememberChoiceTooltipValueSelector).should('not.be.visible');
218+
cy.get(rememberChoiceTooltipToggleSelector).click({force: true});
219+
cy.get(rememberChoiceTooltipValueSelector).should('be.visible');
220+
cy.get(rememberChoiceTooltipToggleSelector).click({force: true});
221+
cy.get(rememberChoiceTooltipValueSelector).should('not.be.visible');
222+
});
223+
224+
it('Does not show the tooltip toggle for the global (non per-SP) variant', () => {
225+
cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=false');
226+
cy.get(`.${rememberChoicePerIdpClass}`).should('not.exist');
227+
cy.get(rememberChoiceTooltipToggleSelector).should('not.exist');
228+
cy.onPage('Remember my choice');
229+
});
230+
});
231+
205232
describe('Preferred IdPs section heading', () => {
206233
it('Should show the preferred IdPs section with the correct heading when preferred IdPs are configured', () => {
207234
cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?preferredIdpEntityIds%5B%5D=https%3A%2F%2Fexample.com%2FentityId%2F1');
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 Tests\OpenConext\EngineBlockBundle\Service;
22+
23+
use Mockery as m;
24+
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
25+
use OpenConext\EngineBlockBundle\Service\RememberChoiceDurationFormatter;
26+
use PHPUnit\Framework\Attributes\DataProvider;
27+
use PHPUnit\Framework\TestCase;
28+
use Symfony\Contracts\Translation\TranslatorInterface;
29+
30+
class RememberChoiceDurationFormatterTest extends TestCase
31+
{
32+
use MockeryPHPUnitIntegration;
33+
34+
#[DataProvider('lifetimeProvider')]
35+
public function testFormat(int $lifetimeInSeconds, string $expectedKey, array $expectedParameters): void
36+
{
37+
$translator = m::mock(TranslatorInterface::class);
38+
$translator->shouldReceive('trans')
39+
->once()
40+
->with($expectedKey, $expectedParameters)
41+
->andReturn('formatted-duration');
42+
43+
$formatter = new RememberChoiceDurationFormatter($translator, $lifetimeInSeconds);
44+
45+
$this->assertSame('formatted-duration', $formatter->format());
46+
}
47+
48+
public static function lifetimeProvider(): array
49+
{
50+
return [
51+
'ninety days (default lifetime)' => [7776000, 'remember_choice_duration_days', ['%count%' => 90]],
52+
'exactly one day' => [86400, 'remember_choice_duration_days', ['%count%' => 1]],
53+
'one second under a day' => [86399, 'remember_choice_duration_minutes', ['%count%' => 1439]],
54+
'forty-five minutes' => [2700, 'remember_choice_duration_minutes', ['%count%' => 45]],
55+
'zero seconds' => [0, 'remember_choice_duration_minutes', ['%count%' => 0]],
56+
];
57+
}
58+
}

0 commit comments

Comments
 (0)