Skip to content

Commit

Permalink
fix: (CXSPA-9080) - Hide 'Consent Management' button when cookies ban…
Browse files Browse the repository at this point in the history
…ner is visible and ensure clear visible focus for accessibility (#19728)
  • Loading branch information
petarmarkov9449 authored Dec 16, 2024
1 parent b5bec0f commit 1d9957a
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 22 deletions.
3 changes: 2 additions & 1 deletion projects/assets/src/translations/en/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"title": "This website uses cookies",
"description": "We use cookies/browser's storage to personalize the content and improve user experience.",
"allowAll": "Allow All",
"viewDetails": "View Details"
"viewDetails": "View Details",
"consentManagement": "Consent Management"
}
},
"checkoutLogin": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,15 @@ export interface FeatureTogglesInterface {
*/
a11yAddPaddingToCarouselPanel?: boolean;

/**
* Hides the 'Consent Management' button from the tab order when the cookies banner is visible.
* Ensures the button is re-enabled and part of the tab order once consent is given and the banner disappears.
* Renames the button from "View Details" to "Consent Management" after consent is given.
* Ensures the button is centered in the `AnonymousConsentOpenDialogComponent` and has clear, four-sided visible focus when navigated via keyboard.
* Affects: AnonymousConsentOpenDialogComponent, AnonymousConsentManagementBannerComponent
*/
a11yHideConsentButtonWhenBannerVisible?: boolean;

/**
* In OCC cart requests, it puts parameters of a cart name and cart description
* into a request body, instead of query params.
Expand Down Expand Up @@ -985,6 +994,7 @@ export const defaultFeatureToggles: Required<FeatureTogglesInterface> = {
a11ySearchboxAssistiveMessage: false,
a11yDifferentiateFocusedAndSelected: false,
a11yAddPaddingToCarouselPanel: false,
a11yHideConsentButtonWhenBannerVisible: false,
occCartNameAndDescriptionInHttpRequestBody: false,
cmsBottomHeaderSlotUsingFlexStyles: false,
useSiteThemeService: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ if (environment.cpq) {
a11ySearchboxAssistiveMessage: true,
a11yDifferentiateFocusedAndSelected: true,
a11yAddPaddingToCarouselPanel: true,
a11yHideConsentButtonWhenBannerVisible: true,
cmsBottomHeaderSlotUsingFlexStyles: true,
useSiteThemeService: false,
enableConsecutiveCharactersPasswordRequirement: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { NgModule } from '@angular/core';
import {
CmsConfig,
DeferLoadingStrategy,
FeaturesConfigModule,
I18nModule,
provideDefaultConfig,
} from '@spartacus/core';
Expand All @@ -18,7 +19,12 @@ import { defaultAnonymousConsentLayoutConfig } from './default-anonymous-consent
import { AnonymousConsentOpenDialogComponent } from './open-dialog/anonymous-consent-open-dialog.component';

@NgModule({
imports: [CommonModule, I18nModule, KeyboardFocusModule],
imports: [
CommonModule,
I18nModule,
KeyboardFocusModule,
FeaturesConfigModule,
],
providers: [
provideDefaultConfig(defaultAnonymousConsentLayoutConfig),
provideDefaultConfig(<CmsConfig>{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
class="anonymous-consent-banner"
>
<div class="container">
<div class="row">
<div class="row" *cxFeature="'!a11yHideConsentButtonWhenBannerVisible'">
<div class="col-lg-7 col-xs-12">
<div class="cx-banner-title">
{{ 'anonymousConsents.banner.title' | cxTranslate }}
Expand All @@ -23,6 +23,25 @@
</button>
</div>
</div>
<div class="row" *cxFeature="'a11yHideConsentButtonWhenBannerVisible'">
<div class="col-xl-7 col-lg-6 col-xs-12">
<div class="cx-banner-title">
{{ 'anonymousConsents.banner.title' | cxTranslate }}
</div>
<div class="cx-banner-description">
{{ 'anonymousConsents.banner.description' | cxTranslate }}
</div>
</div>

<div class="col-xl-5 col-lg-6 col-xs-12 cx-banner-buttons">
<button class="btn btn-secondary" (click)="viewDetails()">
{{ 'anonymousConsents.banner.consentManagement' | cxTranslate }}
</button>
<button class="btn btn-primary" (click)="allowAll()">
{{ 'anonymousConsents.banner.allowAll' | cxTranslate }}
</button>
</div>
</div>
</div>
</div>
</ng-container>
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
<button #open class="btn btn-link" (click)="openDialog()">
{{ 'anonymousConsents.dialog.title' | cxTranslate }}
</button>
<ng-container *cxFeature="'!a11yHideConsentButtonWhenBannerVisible'">
<button #open class="btn btn-link" (click)="openDialog()">
{{ 'anonymousConsents.dialog.title' | cxTranslate }}
</button>
</ng-container>

<ng-container *cxFeature="'a11yHideConsentButtonWhenBannerVisible'">
<button
*ngIf="!(bannerVisible$ | async)"
#open
class="btn btn-link"
(click)="openDialog()"
>
{{ 'anonymousConsents.dialog.title' | cxTranslate }}
</button>
</ng-container>
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
import { ElementRef, ViewContainerRef } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { I18nTestingModule } from '@spartacus/core';
import { LaunchDialogService, LAUNCH_CALLER } from '@spartacus/storefront';
import { EMPTY } from 'rxjs';
import {
AnonymousConsentsService,
ConsentTemplate,
I18nTestingModule,
} from '@spartacus/core';
import { LAUNCH_CALLER, LaunchDialogService } from '@spartacus/storefront';
import { MockFeatureDirective } from 'projects/storefrontlib/shared/test/mock-feature-directive';
import { EMPTY, Observable, of } from 'rxjs';
import { AnonymousConsentOpenDialogComponent } from './anonymous-consent-open-dialog.component';

class MockAnonymousConsentsService {
isBannerVisible(): Observable<boolean> {
return EMPTY;
}
giveAllConsents(): Observable<ConsentTemplate[]> {
return EMPTY;
}
getTemplatesUpdated(): Observable<boolean> {
return EMPTY;
}
toggleBannerDismissed(_dismissed: boolean): void {}
}
class MockLaunchDialogService implements Partial<LaunchDialogService> {
openDialog(
_caller: LAUNCH_CALLER,
Expand All @@ -23,8 +40,12 @@ describe('AnonymousConsentOpenDialogComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [I18nTestingModule],
declarations: [AnonymousConsentOpenDialogComponent],
declarations: [AnonymousConsentOpenDialogComponent, MockFeatureDirective],
providers: [
{
provide: AnonymousConsentsService,
useClass: MockAnonymousConsentsService,
},
{
provide: LaunchDialogService,
useClass: MockLaunchDialogService,
Expand All @@ -45,15 +66,35 @@ describe('AnonymousConsentOpenDialogComponent', () => {
});

describe('openDialog', () => {
it('should call modalService.open', () => {
spyOn(launchDialogService, 'openDialog');
component.openDialog();

expect(launchDialogService.openDialog).toHaveBeenCalledWith(
LAUNCH_CALLER.ANONYMOUS_CONSENT,
component.openElement,
component['vcr']
);
it('should not show the button if the banner is visible', () => {
component.bannerVisible$ = of(true);
fixture.detectChanges();

fixture.whenStable().then(() => {
const button =
fixture.debugElement.nativeElement.querySelector('button');
expect(button).toBeNull();
});
});

it('should show the button and open the dialog if the banner is not visible', waitForAsync(() => {
component.bannerVisible$ = of(false);
fixture.detectChanges();

fixture.whenStable().then(() => {
const button =
fixture.debugElement.nativeElement.querySelector('button');
expect(button).not.toBeNull();

spyOn(launchDialogService, 'openDialog');
button.click();

expect(launchDialogService.openDialog).toHaveBeenCalledWith(
LAUNCH_CALLER.ANONYMOUS_CONSENT,
component.openElement,
component['vcr']
);
});
}));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,28 @@ import {
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { LaunchDialogService } from '../../../layout/launch-dialog/services/launch-dialog.service';
import { LAUNCH_CALLER } from '../../../layout/launch-dialog/config/launch-config';
import { AnonymousConsentsService, useFeatureStyles } from '@spartacus/core';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { LAUNCH_CALLER } from '../../../layout/launch-dialog/config/launch-config';
import { LaunchDialogService } from '../../../layout/launch-dialog/services/launch-dialog.service';

@Component({
selector: 'cx-anonymous-consent-open-dialog',
templateUrl: './anonymous-consent-open-dialog.component.html',
})
export class AnonymousConsentOpenDialogComponent {
@ViewChild('open') openElement: ElementRef;
bannerVisible$: Observable<boolean> =
this.anonymousConsentsService.isBannerVisible();

constructor(
protected vcr: ViewContainerRef,
protected anonymousConsentsService: AnonymousConsentsService,
protected launchDialogService: LaunchDialogService
) {}
) {
useFeatureStyles('a11yHideConsentButtonWhenBannerVisible');
}

openDialog(): void {
const dialog = this.launchDialogService.openDialog(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@
justify-content: center;
margin: 0 3vw 3vw 3vw;

@include forFeature('a11yHideConsentButtonWhenBannerVisible') {
margin: 0;
}

@include media-breakpoint-down(sm) {
justify-content: flex-start;
}
.btn-link {
@include forFeature('a11yHideConsentButtonWhenBannerVisible') {
margin: 1.5vw 3vw;
}
padding: 0;
color: var(--cx-color-inverse);
font-size: 0.875rem;
Expand Down

0 comments on commit 1d9957a

Please sign in to comment.