Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into release-2211.35.0-1
Browse files Browse the repository at this point in the history
  • Loading branch information
rmch91 committed Jan 27, 2025
2 parents d0cd5ec + 79ee677 commit 06f8a08
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,12 @@ export interface FeatureTogglesInterface {
*/
a11yAddPaddingToCarouselPanel?: boolean;

/**
* Removes invalid aria-level usage on button elements and ensures buttons have a proper accessible name via aria-label or aria-labelledby.
* Affects: NavigationUIComponent
*/
a11yNavigationButtonsAriaFixes?: boolean;

/**
* Restores the focus to the card once a option has been selected and the checkout has updated.
* Affects: CheckoutPaymentMethodComponent, CheckoutDeliveryAddressComponent
Expand Down Expand Up @@ -1114,6 +1120,7 @@ export const defaultFeatureToggles: Required<FeatureTogglesInterface> = {
a11yQuickOrderSearchBoxRefocusOnClose: false,
a11yKeyboardFocusInSearchBox: false,
a11yAddPaddingToCarouselPanel: false,
a11yNavigationButtonsAriaFixes: false,
a11yFocusOnCardAfterSelecting: false,
a11ySearchableDropdownFirstElementFocus: false,
a11yHideConsentButtonWhenBannerVisible: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,50 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { loginUser, signOut } from '../../../helpers/checkout-flow';
import {
addCheapProductToCartAndBeginCheckoutForSignedInCustomer,
goToCheapProductDetailsPage,
loginUser,
signOut,
} from '../../../helpers/checkout-flow';
import {
addProductToCart,
cheapProduct,
checkoutDeliveryMode,
checkoutPaymentDetails,
checkoutShippingAddress,
my_user,
orderConfirmation,
reviewAndPlaceOrder,
my_user,
} from '../../../helpers/estimated-delivery-date';

describe('estimated delivery date', () => {
it('should see estimated delivery date in cart and order pages', () => {
cy.visit('/apparel-uk-spa/en/GBP/login');
loginUser(my_user);
cy.wait(3000);
cy.visit('/apparel-uk-spa/en/GBP/product/M_CR_1015');
cy.visit('/apparel-uk-spa/en/GBP/product/M_CR_1016');
cy.wait(4000);
cy.get('cx-add-to-cart')
.findByText(/Add To Cart/i)
.click();
cy.wait(4000);
addCheapProductToCartAndBeginCheckoutForSignedInCustomer(cheapProduct);
cy.findByText(/proceed to checkout/i).click();
cy.wait(8000);
checkoutShippingAddress();
checkoutDeliveryMode();
//going back to PDP and adding a product again to show Estimated delivery date in cart
goToCheapProductDetailsPage(cheapProduct);
addProductToCart(cheapProduct);
//going back to cart to show Estimated delivery date in cart
cy.visit('/apparel-uk-spa/en/GBP/cart');
cy.wait(4000);
cy.get('cx-estimated-delivery-date').should('exist');
cy.findByText(/proceed to checkout/i).click();
cy.wait(8000);
checkoutShippingAddress();
checkoutDeliveryMode();
checkoutPaymentDetails();
reviewAndPlaceOrder();
orderConfirmation();
});
it('should see estimated delivery date in order history', () => {
cy.visit('apparel-uk-spa/en/GBP/my-account/order/');
cy.get('.cx-list').should('have.length', 1);
cy.get('cx-order-history-code').click();
//For this test to run successfully ensure a order is already present.
cy.visit('/apparel-uk-spa/en/GBP/login');
loginUser(my_user);
cy.visit('apparel-uk-spa/en/GBP/my-account/orders/');
cy.wait(6000);
cy.get('.cx-order-history-code').click({ multiple: true });
cy.contains('Estimated delivery date');
signOut();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { waitForPage, addCheapProductToCart } from './checkout-flow';
import { SampleProduct } from '../sample-data/checkout-flow';

export const cheapProduct: SampleProduct = {
name: 'Coney Flare',
code: 'M_CR_1015',
name: 'Frozen Peas',
code: 'M_CR_1016',
};

export const my_user = {
Expand All @@ -19,60 +18,45 @@ export const my_user = {
};

export function checkoutShippingAddress() {
const deliveryModePage = waitForPage(
'/checkout/delivery-mode',
'getDeliveryModePage'
);
cy.get('cx-delivery-address').within(() => {
cy.findByText('Selected');
cy.findByText('Continue').click();
});
cy.wait(`@${deliveryModePage}`).its('response.statusCode').should('eq', 200);
}

export function checkoutDeliveryMode() {
const PaymentDetailsPage = waitForPage(
'/checkout/payment-details',
'getPaymentDetailsPage'
);
cy.get('[formcontrolname="deliveryModeId"]').eq(0).click();
cy.get('cx-delivery-mode').within(() => {
cy.wait(3000);
cy.findByText('Continue').click();
});
cy.wait(`@${PaymentDetailsPage}`)
.its('response.statusCode')
.should('eq', 200);
}

export function checkoutPaymentDetails() {
const ReviewOrderPage = waitForPage(
'/checkout/review-order',
'getReviewOrderPage'
);
cy.get('cx-payment-method').within(() => {
cy.get('cx-card')
.eq(0)
.within(() => {
cy.findByText('Use this payment').click();
cy.wait(3000);
cy.findByText('OMSA Customer');
cy.findByText('5105105105105100');
cy.findByText('Expires: 08/2030');
cy.findByText('Use this payment', { timeout: 10000 })
.should(Cypress._.noop) // No-op to avoid failures if not found
.then(($button) => {
if ($button.length > 0) {
cy.wrap($button).click();
}
});
});
cy.findByText('Continue').click();
});
cy.wait(`@${ReviewOrderPage}`).its('response.statusCode').should('eq', 200);
}

export function reviewAndPlaceOrder() {
const ConfirmOrderPage = waitForPage(
'/order-confirmation',
'getOrderConfirmationPage'
);
cy.contains('Estimated delivery date');
cy.contains('Estimated delivery date').should('exist');
cy.get('cx-place-order').within(() => {
cy.get('[formcontrolname="termsAndConditions"]').check();
cy.findByText('Place Order').click();
});
cy.wait(`@${ConfirmOrderPage}`).its('response.statusCode').should('eq', 200);
}

export function orderConfirmation() {
Expand All @@ -82,17 +66,3 @@ export function orderConfirmation() {
cy.get('cx-order-confirmation-thank-you-message');
cy.contains('Estimated delivery date');
}

export function addProductToCart(sampleProduct: SampleProduct = cheapProduct) {
addCheapProductToCart(sampleProduct);

const deliveryAddressPage = waitForPage(
'/checkout/delivery-address',
'getDeliveryAddressPage'
);
cy.contains('Estimated delivery date');
cy.findByText(/proceed to checkout/i).click();
cy.wait(`@${deliveryAddressPage}`)
.its('response.statusCode')
.should('eq', 200);
}
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ if (environment.cpq) {
a11yQuickOrderSearchBoxRefocusOnClose: true,
a11yKeyboardFocusInSearchBox: true,
a11yAddPaddingToCarouselPanel: true,
a11yNavigationButtonsAriaFixes: true,
a11yFocusOnCardAfterSelecting: true,
a11ySearchableDropdownFirstElementFocus: true,
a11yHideConsentButtonWhenBannerVisible: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,29 +105,49 @@
</button>
</ng-container>
<ng-container *cxFeature="'a11yNavigationUiKeyboardControls'">
<button
aria-level="4"
[attr.role]="(isDesktop$ | async) && depth ? 'heading' : 'button'"
[attr.aria-haspopup]="true"
[attr.aria-expanded]="false"
[attr.aria-controls]="node.title"
[attr.aria-label]="node.title"
[attr.title]="
'navigation.menuButonTitle' | cxTranslate: { title: node.title }
"
(click)="toggleOpen($any($event))"
(mouseenter)="onMouseEnter($event)"
(keydown.space)="onSpace($any($event))"
(keydown.enter)="onSpace($any($event))"
(keydown.esc)="back()"
(keydown.arrowDown)="focusOnNode($any($event))"
(focus)="depth || reinitializeMenu()"
>
<ng-container *ngIf="!node.url">
{{ node.title }}
</ng-container>
<cx-icon [type]="iconType.CARET_DOWN"></cx-icon>
</button>
<ng-container *cxFeature="'!a11yNavigationButtonsAriaFixes'">
<button
aria-level="4"
[attr.role]="(isDesktop$ | async) && depth ? 'heading' : 'button'"
[attr.aria-haspopup]="true"
[attr.aria-expanded]="false"
[attr.aria-controls]="node.title"
[attr.aria-describedby]="'greeting'"
(click)="toggleOpen($any($event))"
(mouseenter)="onMouseEnter($event)"
(keydown.space)="onSpace($any($event))"
(keydown.enter)="onSpace($any($event))"
(keydown.esc)="back()"
(keydown.arrowDown)="focusOnNode($any($event))"
(focus)="depth || reinitializeMenu()"
>
<ng-container *ngIf="!node.url">
{{ node.title }}
</ng-container>
<cx-icon [type]="iconType.CARET_DOWN"></cx-icon>
</button>
</ng-container>
<ng-container *cxFeature="'a11yNavigationButtonsAriaFixes'">
<button
[attr.role]="(isDesktop$ | async) && depth ? 'heading' : 'button'"
[attr.aria-haspopup]="true"
[attr.aria-expanded]="false"
[attr.aria-label]="getAriaLabelAndControl(node)"
[attr.aria-controls]="getAriaLabelAndControl(node)"
(click)="toggleOpen($any($event))"
(mouseenter)="onMouseEnter($event)"
(keydown.space)="onSpace($any($event))"
(keydown.enter)="onSpace($any($event))"
(keydown.esc)="back()"
(keydown.arrowDown)="focusOnNode($any($event))"
(focus)="depth || reinitializeMenu()"
>
<ng-container *ngIf="!node.url">
{{ node.title }}
</ng-container>
<cx-icon [type]="iconType.CARET_DOWN"></cx-icon>
</button>
</ng-container>
</ng-container>
</ng-container>
<ng-template #title>
Expand All @@ -144,7 +164,7 @@

<!-- we add a wrapper to allow for better layout handling in CSS -->
<div
[id]="node.title"
[id]="getSanitizedTitle(node.title)"
class="wrapper"
*ngIf="node.children && node.children.length > 0"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,10 @@ describe('Navigation UI Component', () => {
.query(By.css('nav > ul > li:nth-child(2) > button'))
.nativeElement.click();
element
.query(By.css('button[aria-controls="Child 1"]'))
.query(By.css('button[aria-controls="child-1"]'))
.nativeElement.click();
element
.query(By.css('button[aria-controls="Sub child 1"]'))
.query(By.css('button[aria-controls="sub-child-1"]'))
.nativeElement.click();

expect(element.queryAll(By.css('li.is-open:not(.back)')).length).toBe(1);
Expand Down Expand Up @@ -365,10 +365,10 @@ describe('Navigation UI Component', () => {
it('should apply role="heading" to nested dropdown trigger button while on desktop', () => {
fixture.detectChanges();
const nestedTriggerButton = fixture.debugElement.query(
By.css('button[aria-controls="Child 1"]')
By.css('button[aria-controls="child-1"]')
).nativeElement;
const rootTriggerButton = fixture.debugElement.query(
By.css('button[aria-controls="Root 1"]')
By.css('button[aria-controls="root-1"]')
).nativeElement;

expect(nestedTriggerButton.getAttribute('role')).toEqual('heading');
Expand All @@ -385,7 +385,7 @@ describe('Navigation UI Component', () => {
const spy = spyOn(navigationComponent, 'toggleOpen');
const spaceEvent = new KeyboardEvent('keydown', { code: 'Space' });
const dropDownButton = element.query(
By.css('button[aria-controls="Sub child 1"]')
By.css('button[aria-controls="sub-child-1"]')
).nativeElement;
Object.defineProperty(spaceEvent, 'target', { value: dropDownButton });

Expand All @@ -399,7 +399,7 @@ describe('Navigation UI Component', () => {
const spy = spyOn(firstChild.nativeElement, 'focus');
const spaceEvent = new KeyboardEvent('keydown', { code: 'Space' });
const dropDownButton = element.query(
By.css('button[aria-controls="Sub child 1"]')
By.css('button[aria-controls="sub-child-1"]')
).nativeElement;
Object.defineProperty(spaceEvent, 'target', { value: dropDownButton });

Expand All @@ -420,7 +420,7 @@ describe('Navigation UI Component', () => {
});
const spaceEvent = new KeyboardEvent('keydown', { code: 'Space' });
const dropDownButton = element.query(
By.css('button[aria-controls="Sub child 1"]')
By.css('button[aria-controls="sub-child-1"]')
).nativeElement;
Object.defineProperty(spaceEvent, 'target', { value: dropDownButton });
Object.defineProperty(arrowDownEvent, 'target', {
Expand Down Expand Up @@ -471,22 +471,21 @@ describe('Navigation UI Component', () => {
const childNode = rootNode?.children?.[0];
const rootTitle = rootNode?.title;
const childTitle = childNode?.title;
const sanitizedRootTitle =
navigationComponent.getSanitizedTitle(rootTitle);
const sanitizedChildTitle =
navigationComponent.getSanitizedTitle(childTitle);

fixture.detectChanges();
const nestedTriggerButton = fixture.debugElement.query(
By.css(`button[aria-label="${childTitle}"]`)
By.css(`button[aria-label="${sanitizedRootTitle}"]`)
).nativeElement;
const rootTriggerButton = fixture.debugElement.query(
By.css(`button[aria-label="${rootTitle}"]`)
By.css(`button[aria-label="${sanitizedChildTitle}"]`)
).nativeElement;

expect(nestedTriggerButton).toBeDefined();
expect(rootTriggerButton).toBeDefined();
expect(rootTriggerButton.getAttribute('title')).toEqual(
`navigation.menuButonTitle title:${rootTitle}`
);
expect(nestedTriggerButton.getAttribute('title')).toEqual(
`navigation.menuButonTitle title:${childTitle}`
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -397,4 +397,18 @@ export class NavigationUIComponent implements OnInit, OnDestroy {
}
return depth > 0 && !node?.children ? -1 : 0;
}

/**
* // Replace spaces with hyphens and convert to lowercase
*/
getSanitizedTitle(title: string | undefined): string | null {
return title ? title.replace(/\s+/g, '-').toLowerCase() : null;
}

/**
* Returns the value for the `aria-control` and the `aria-label` attribute of a button.
*/
getAriaLabelAndControl(node: NavigationNode): string | null {
return this.getSanitizedTitle(node.title) || null;
}
}
Loading

0 comments on commit 06f8a08

Please sign in to comment.