Skip to content

Commit

Permalink
Merge branch 'develop' into opps-coupon
Browse files Browse the repository at this point in the history
  • Loading branch information
anjana-bl authored Apr 28, 2024
2 parents 4db37f4 + dc3b5d8 commit cf760a4
Show file tree
Hide file tree
Showing 53 changed files with 1,137 additions and 464 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import {
ChangeDetectorRef,
Component,
ComponentRef,
HostListener,
Input,
OnDestroy,
OnInit,
Optional,
inject,
} from '@angular/core';
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import {
Expand All @@ -24,8 +26,9 @@ import {
import {
CmsAddToCartComponent,
EventService,
isNotNullable,
FeatureConfigService,
Product,
isNotNullable,
} from '@spartacus/core';
import {
CmsComponentData,
Expand Down Expand Up @@ -74,6 +77,29 @@ export class AddToCartComponent implements OnInit, OnDestroy {

iconTypes = ICON_TYPE;

@Optional() featureConfigService = inject(FeatureConfigService, {
optional: true,
});

/**
* We disable the dialog launch on quantity input,
* as it causes an unexpected change of context.
* The expectation is only for the quantity to get updated in the Qty field.
*/
@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
// TODO: (CXSPA-6034) Remove Feature flag next major release
if (!this.featureConfigService?.isEnabled('a11yQuantityOrderTabbing')) {
return;
}
const eventTarget = event.target as HTMLElement;
const isQuantityInput =
eventTarget.ariaLabel === 'Quantity' && eventTarget.tagName === 'INPUT';
if (event.key === 'Enter' && isQuantityInput) {
event.preventDefault();
}
}

constructor(
protected currentProductService: CurrentProductService,
protected cd: ChangeDetectorRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '@spartacus/cart/base/root';
import { ICON_TYPE } from '@spartacus/storefront';
import { CartItemContextSource } from './model/cart-item-context-source.model';
import { useFeatureStyles } from '@spartacus/core';

@Component({
selector: 'cx-cart-item',
Expand Down Expand Up @@ -42,7 +43,9 @@ export class CartItemComponent implements OnChanges {
iconTypes = ICON_TYPE;
readonly CartOutlets = CartOutlets;

constructor(protected cartItemContextSource: CartItemContextSource) {}
constructor(protected cartItemContextSource: CartItemContextSource) {
useFeatureStyles('a11yCartItemsLinksStyles');
}

ngOnChanges(changes?: SimpleChanges) {
if (changes?.compact) {
Expand Down
6 changes: 6 additions & 0 deletions feature-libs/cart/base/styles/components/_cart-item-list.scss
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@
text-decoration: none;
color: var(--cx-color-text);
font-weight: var(--cx-font-weight-bold);

// TODO: (CXSPA-6903) - Remove feature flag next major release
@include forFeature('a11yCartItemsLinksStyles') {
text-decoration: underline;
color: var(--cx-color-primary);
}
}

.cx-total {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { UntypedFormControl } from '@angular/forms';
import { OrderEntry } from '@spartacus/cart/base/root';
import { QuickOrderFacade } from '@spartacus/cart/quick-order/root';
import { useFeatureStyles } from '@spartacus/core';
import { Subscription } from 'rxjs';

@Component({
Expand Down Expand Up @@ -48,7 +49,9 @@ export class QuickOrderItemComponent implements OnInit, OnDestroy {
constructor(
protected cd: ChangeDetectorRef,
protected quickOrderService: QuickOrderFacade
) {}
) {
useFeatureStyles('a11yCartItemsLinksStyles');
}

ngOnInit(): void {
this.subscription.add(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
SimpleChanges,
} from '@angular/core';
import { OrderEntry } from '@spartacus/cart/base/root';
import { Product } from '@spartacus/core';
import { Product, useFeatureStyles } from '@spartacus/core';
import {
ProductListItemContext,
ProductListItemContextSource,
Expand Down Expand Up @@ -42,7 +42,9 @@ export class WishListItemComponent implements OnChanges {

constructor(
protected productListItemContextSource: ProductListItemContextSource
) {}
) {
useFeatureStyles('a11yCartItemsLinksStyles');
}

ngOnChanges(changes?: SimpleChanges): void {
if (changes?.cartEntry) {
Expand Down
4 changes: 2 additions & 2 deletions feature-libs/order/assets/translations/en/order.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
"deliveryStatus_PICKUP_COMPLETE": "Pickup Complete",
"deliveryStatus_DELIVERY_COMPLETED": "Delivery Complete",
"deliveryStatus_PAYMENT_NOT_CAPTURED": "Payment Issue",
"deliveryStatus_IN_PROCESS": "In Process",
"deliveryStatus_READY": "In Process",
"deliveryStatus_IN_PROCESS": "Order Processing",
"deliveryStatus_READY": "Order Processing",
"deliveryStatus_DELIVERY_REJECTED": "Delivery Rejected",
"deliveryStatus_SHIPPED": "Shipped",
"deliveryStatus_TAX_NOT_COMMITTED": "Tax Issue",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<cx-view
[viewTitle]="i18nRoot + '.title' | cxTranslate: { item: item$ | async }"
class="card"
role="tabpanel"
>
<div class="header">
<div class="title-bar">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ <h3 *cxFeature="'!a11yOrganizationListHeadingOrder'">
[i18nRoot]="domainType"
[currentItem]="{ property: key, value: currentKey$ | async }"
(launch)="launchItem($event)"
[cxFocus]="{ trap: trapFocus.both }"
role="tree"
>
</cx-table>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
<a
*ngIf="hasItem"
[id]="model.uid"
class="hide-focus-border"
[routerLink]="{ cxRoute: route, params: routeModel } | cxUrl"
[tabIndex]="tabIndex"
(keydown)="onKeydown($event)"
[attr.aria-haspopup]="isSwitchable"
[attr.data-depth]="model.depthLevel"
role="treeitem"
>
<button
*ngIf="isSwitchable"
Expand All @@ -11,6 +15,7 @@
[attr.aria-expanded]="expanded"
[attr.aria-label]="'orgUnit.tree.explore' | cxTranslate"
(click)="toggleItem($event)"
tabindex="-1"
>
<cx-icon [type]="expanded ? 'CARET_DOWN' : 'CARET_RIGHT'"></cx-icon>
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { I18nTestingModule } from '@spartacus/core';
import {
FeatureConfigService,
I18nTestingModule,
RoutingService,
} from '@spartacus/core';
import { ToggleLinkCellComponent } from '@spartacus/organization/administration/components';
import { IconModule, OutletContextData } from '@spartacus/storefront';
import { UrlTestingModule } from 'projects/core/src/routing/configurable-routes/url-translation/testing/url-testing.module';
import { Subject } from 'rxjs';
import { UnitTreeService } from '../../services/unit-tree.service';
import createSpy = jasmine.createSpy;

Expand All @@ -23,6 +33,14 @@ class MockUnitTreeService implements Partial<UnitTreeService> {
toggle = createSpy('toggle');
}

class MockRoutingService implements Partial<RoutingService> {
go = () => Promise.resolve(true);
}

class MockFeatureConfigService {
isEnabled = () => true;
}

describe('ToggleLinkCellComponent', () => {
let component: ToggleLinkCellComponent;
let unitTreeService: UnitTreeService;
Expand All @@ -46,6 +64,14 @@ describe('ToggleLinkCellComponent', () => {
provide: UnitTreeService,
useClass: MockUnitTreeService,
},
{
provide: RoutingService,
useClass: MockRoutingService,
},
{
provide: FeatureConfigService,
useClass: MockFeatureConfigService,
},
],
}).compileComponents();
});
Expand All @@ -61,10 +87,6 @@ describe('ToggleLinkCellComponent', () => {
expect(component).toBeTruthy();
});

it('should return 0 for tabIndex', () => {
expect(component.tabIndex).toEqual(0);
});

it('should render tabindex = 0 by default', () => {
const el: HTMLElement = fixture.debugElement.query(By.css('a')).nativeNode;
expect(el.innerText).toEqual('my name (1)');
Expand All @@ -78,4 +100,145 @@ describe('ToggleLinkCellComponent', () => {
el.click();
expect(unitTreeService.toggle).toHaveBeenCalledWith(mockContext);
});

describe('a11y', () => {
const mockElement1 = document.createElement('a');
const mockElement2 = document.createElement('a');
const mockSiblingElements = [mockElement1, mockElement2];
const mockSpaceEvent = new KeyboardEvent('keydown', { key: ' ' });
const mockArrowDownEvent = new KeyboardEvent('keydown', {
key: 'ArrowDown',
});
const mockArrowUpEvent = new KeyboardEvent('keydown', { key: 'ArrowUp' });
const mockArrowRightEvent = new KeyboardEvent('keydown', {
key: 'ArrowRight',
});
const mockArrowLeftEvent = new KeyboardEvent('keydown', {
key: 'ArrowLeft',
});

it('should enable keyboard controls', () => {
const mockTableElement = {
querySelectorAll: jasmine
.createSpy('querySelectorAll')
.and.returnValue(mockSiblingElements),
};
component['elementRef'] = {
nativeElement: {
closest: jasmine
.createSpy('closest')
.and.returnValue(mockTableElement),
},
};
spyOn(component, 'onSpace').and.stub();
spyOn(component, 'onArrowDown').and.stub();
spyOn(component, 'onArrowUp').and.stub();
spyOn(component, 'onArrowRight').and.stub();
spyOn(component, 'onArrowLeft').and.stub();

component.onKeydown(mockSpaceEvent);
expect(component.onSpace).toHaveBeenCalled();
component.onKeydown(mockArrowDownEvent);
expect(component.onArrowDown).toHaveBeenCalled();
component.onKeydown(mockArrowUpEvent);
expect(component.onArrowUp).toHaveBeenCalled();
component.onKeydown(mockArrowRightEvent);
expect(component.onArrowRight).toHaveBeenCalled();
component.onKeydown(mockArrowLeftEvent);
expect(component.onArrowLeft).toHaveBeenCalled();
});

it('should make active item the only focusable item and navigate', () => {
Object.defineProperty(mockSpaceEvent, 'target', {
value: mockElement1,
});
spyOn(mockSpaceEvent, 'preventDefault');
spyOn(component, 'restoreFocus');

component.onSpace(mockSpaceEvent, mockSiblingElements);

expect(mockSpaceEvent.preventDefault).toHaveBeenCalled();
expect(mockElement1.tabIndex).toEqual(0);
expect(mockElement2.tabIndex).toEqual(-1);
fixture.whenStable().then(() => {
expect(component.restoreFocus).toHaveBeenCalled();
});
});

it('should focus next link on ArrowDown', () => {
const currentSelectedIndex = 0;
spyOn(mockArrowDownEvent, 'preventDefault');
spyOn(mockElement2, 'focus');

component.onArrowDown(
mockArrowDownEvent,
currentSelectedIndex,
mockSiblingElements
);

expect(mockArrowDownEvent.preventDefault).toHaveBeenCalled();
expect(mockElement2.focus).toHaveBeenCalled();
});

it('should focus previous element on ArrowUp', () => {
const currentSelectedIndex = 1;
spyOn(mockArrowUpEvent, 'preventDefault');
spyOn(mockElement1, 'focus');

component.onArrowUp(
mockArrowUpEvent,
currentSelectedIndex,
mockSiblingElements
);

expect(mockArrowUpEvent.preventDefault).toHaveBeenCalled();
expect(mockElement1.focus).toHaveBeenCalled();
});

it('should expand option on ArrowRight', () => {
Object.defineProperty(component, 'expanded', {
writable: true,
value: false,
});
spyOn(component, 'toggleItem');
spyOn(component, 'restoreFocus');

component.onArrowRight(mockArrowRightEvent);

expect(component.toggleItem).toHaveBeenCalledWith(mockArrowRightEvent);
expect(component.restoreFocus).toHaveBeenCalled();
});

it('should collapse option on ArrowLeft', () => {
Object.defineProperty(component, 'expanded', {
writable: true,
value: true,
});
spyOn(component, 'toggleItem');
spyOn(component, 'restoreFocus');

component.onArrowLeft(mockArrowLeftEvent);

expect(component.toggleItem).toHaveBeenCalledWith(mockArrowLeftEvent);
expect(component.restoreFocus).toHaveBeenCalled();
});

it('should restore focus after tree toggle', fakeAsync(() => {
const mockElement = document.createElement('a');
mockElement.id = 'mockElement';
document.body.appendChild(mockElement);
spyOnProperty(document, 'activeElement').and.returnValue(mockElement);
const treeToggle$ = new Subject<void>();
component['unitTreeService'] = {
treeToggle$: treeToggle$.asObservable(),
} as any;
spyOn(mockElement, 'focus');

component.restoreFocus();
treeToggle$.next();
tick();

expect(mockElement.focus).toHaveBeenCalled();
}));
});
});
Loading

0 comments on commit cf760a4

Please sign in to comment.