Skip to content

Commit

Permalink
Feat: As an assisted service agent, I want to view list of coupons pa…
Browse files Browse the repository at this point in the history
…rticularly for customer support (#17703)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
i53577 and github-actions[bot] authored Aug 9, 2023
1 parent ef8200e commit 4370f73
Show file tree
Hide file tree
Showing 24 changed files with 1,040 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const customer360 = {
alertErrorMessage:
'The information cannot be be loaded. Please try again later or contact your system administrator.',
errorMessageHeader: 'Oops! Something didn\x27t',
applyActionAlter:
"The action couldn't be completed. Please try again later.",
header: {
title: 'Customer Profile',
subTitle: '{{name}} Customer 360\xB0 View',
Expand Down Expand Up @@ -88,6 +90,13 @@ export const customer360 = {
header: 'Support Tickets',
emptyDescription: 'There are currently no support tickets',
},
coupons: {
headerText: 'Coupons',
emptyDescription: 'There are currently no coupons',
applyButtonText: 'Apply to Cart',
applied: 'Coupon Applied',
removeButtonText: 'Remove',
},
maps: {
storeClosed: 'Close',
storesFound: '{{ initial }} - {{ end }} from {{ total }} stores found',
Expand All @@ -96,7 +105,7 @@ export const customer360 = {
profileTab: 'Profile',
activityTab: 'Activity',
feedbackTab: 'Feedback',
promotionsTab: 'Promotion',
promotionsTab: 'Promotions',
mapsTab: 'Maps',
aria: {
activeCartCode: 'Active Cart {{code}}',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<div class="cx-asm-customer-promotion-listing-heading" *ngIf="headerText">
<h4 class="cx-asm-customer-promotion-listing-heading-text">
{{ headerText }}
</h4>
</div>
<div class="message-container">
<cx-message
*ngIf="showAlert"
[text]="'customer360.alertErrorMessage' | cxTranslate"
[type]="globalMessageType.MSG_TYPE_ERROR"
(closeMessage)="removeAlert.emit()"
></cx-message>
<cx-message
*ngIf="showAlertForApplyAction"
[text]="'customer360.applyActionAlter' | cxTranslate"
[type]="globalMessageType.MSG_TYPE_ERROR"
(closeMessage)="removeAlertForApplyAction.emit()"
></cx-message>
</div>
<table class="cx-asm-customer-promotion-listing">
<ng-container *ngFor="let entry of entries">
<tr class="cx-asm-customer-promotion-listing-row">
<td>
<tr class="cx-asm-customer-promotion-listing-subheader" tabindex="-1">
{{
entry.code
}}
</tr>
<tr class="cx-asm-customer-promotion-listing-description">
{{
entry.name
}}
</tr>
</td>
<td>
<ng-container *ngIf="!entry.applied">
<button
*ngIf="showApplyButton"
class="cx-asm-customer-promotion-listing-apply-button"
(click)="apply.emit(entry)"
>
{{ applyButtonText }}
</button>
</ng-container>
<ng-container *ngIf="entry.applied">
<tr class="cx-asm-customer-promotion-listing-action">
<td>
<cx-icon class="success" type="SUCCESS"></cx-icon>
</td>
<td class="cx-asm-customer-promotion-listing-applied">
{{ applied }}
</td>
<td
*ngIf="showRemoveButton"
class="cx-asm-customer-promotion-listing-action-separator"
>
|
</td>
<td *ngIf="showRemoveButton">
<button
class="cx-asm-customer-promotion-listing-remove-button"
(click)="remove.emit(entry)"
>
{{ removeButtonText }}
</button>
</td>
</tr>
</ng-container>
</td>
</tr>
</ng-container>
</table>
<hr
class="cx-asm-customer-promotion-listing-separator"
aria-hidden="true"
*ngIf="!showAlert"
/>
<div
class="cx-asm-customer-promotion-listing-empty"
*ngIf="entries?.length === 0 && !showAlert"
>
{{ emptyStateText }}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { Component, DebugElement, Input } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AsmCustomerPromotionListingComponent } from './asm-customer-promotion-listing.component';
import { I18nTestingModule } from '@spartacus/core';
import { By } from '@angular/platform-browser';
import { PromotionListEntry } from './asm-customer-promotion-listing.model';

describe('AsmCustomerPromotionListingComponent', () => {
const mockEntries: Array<PromotionListEntry> = [
{
code: 'COUPON_1',
name: 'NAME OF COUPON_1',
applied: true,
},
{
code: 'COUPON_2',
name: 'NAME OF COUPON_2',
applied: false,
},
{
code: 'COUPON_3',
name: 'NAME OF COUPON_3',
applied: false,
},
];

const mockEmptyText = 'empty list';
const mockHeaderText = 'Header Text';

@Component({
selector: 'cx-test-host',
template: `
<cx-asm-customer-promotion-listing
[emptyStateText]="emptyStateText"
[headerText]="headerText"
[entries]="entries"
[showAlert]="showAlert"
[showAlertForApplyAction]="showAlertForApplyAction"
(apply)="applyCouponToCustomer($event)"
(remove)="removeCouponToCustomer($event)"
(removeAlert)="closeErrorAlert()"
(removeAlertForApplyAction)="closeErrorAlertForApplyAction()"
[applyButtonText]="applyButtonText"
[applied]="applied"
[removeButtonText]="removeButtonText"
[showRemoveButton]="true"
[showApplyButton]="true"
>
</cx-asm-customer-promotion-listing>
`,
})
class TestHostComponent {
@Input() headerText: string;
@Input() emptyStateText: string;
@Input() applyButtonText: string;
@Input() applied: string;
@Input() removeButtonText: string;
@Input() entries: Array<PromotionListEntry> | null;
@Input() showAlert: boolean | null;
@Input() showAlertForApplyAction: boolean | null;
@Input() showRemoveButton: boolean;
@Input() showApplyButton: boolean;
apply = void {};
remove = void {};
removeAlert = void {};
removeAlertForApplyAction = void {};
}

let component: AsmCustomerPromotionListingComponent;
let fixture: ComponentFixture<TestHostComponent>;
let testHost: TestHostComponent;
let el: DebugElement;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [I18nTestingModule],
declarations: [TestHostComponent, AsmCustomerPromotionListingComponent],
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(TestHostComponent);
testHost = fixture.componentInstance;
component = fixture.debugElement.query(
By.directive(AsmCustomerPromotionListingComponent)
).componentInstance;
el = fixture.debugElement;
});

it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});

it('should display header text', () => {
testHost.headerText = mockHeaderText;
fixture.detectChanges();

const header = el.query(
By.css('.cx-asm-customer-promotion-listing-heading-text')
);
expect(header.nativeElement.innerText).toBe(mockHeaderText);
});

it('should display entries list', () => {
testHost.entries = mockEntries;
fixture.detectChanges();

const entriesList = el.query(By.css('.cx-asm-customer-promotion-listing'));
expect(entriesList).toBeTruthy();

const listTableBody = el.query(By.css('table'));

const rows = listTableBody.queryAll(
By.css('.cx-asm-customer-promotion-listing-row')
);
expect(rows.length).toBe(mockEntries.length);
});

it('should display empty message when entries is empty', () => {
testHost.emptyStateText = mockEmptyText;
testHost.entries = [];
fixture.detectChanges();

const emptyMessage = el.query(
By.css('.cx-asm-customer-promotion-listing-empty')
);

expect(emptyMessage).toBeTruthy();
});

it('should show meaasge when entries loaded failed', () => {
testHost.showAlert = true;
fixture.detectChanges();
expect(el.query(By.css('cx-message'))).not.toBeNull();
});

it('should show meaasge when action perform failed', () => {
testHost.showAlertForApplyAction = true;
fixture.detectChanges();
expect(el.query(By.css('cx-message'))).not.toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2023 SAP Spartacus team <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { GlobalMessageType } from '@spartacus/core';
import { PromotionListEntry } from './asm-customer-promotion-listing.model';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'cx-asm-customer-promotion-listing',
templateUrl: './asm-customer-promotion-listing.component.html',
})
export class AsmCustomerPromotionListingComponent {
@Input() headerText: string;
@Input() emptyStateText: string;
@Input() applyButtonText: string;
@Input() applied: string;
@Input() removeButtonText: string;
@Input() entries: Array<PromotionListEntry> | null;
@Input() showAlert: boolean | null;
@Input() showAlertForApplyAction: boolean | null;
@Input() showRemoveButton: boolean;
@Input() showApplyButton: boolean;
@Output() apply = new EventEmitter<PromotionListEntry>();
@Output() remove = new EventEmitter<PromotionListEntry>();
@Output() removeAlert = new EventEmitter();
@Output() removeAlertForApplyAction = new EventEmitter();
globalMessageType = GlobalMessageType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2023 SAP Spartacus team <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

export interface PromotionListEntry {
[key: string]: string | boolean | undefined;
}

export interface GeneralEntry extends PromotionListEntry {
applied: boolean;
code: string;
name?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2023 SAP Spartacus team <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ArgsModule } from '@spartacus/asm/core';
import { I18nModule } from '@spartacus/core';
import {
StarRatingModule,
IconModule,
MessageComponentModule,
} from '@spartacus/storefront';
import { AsmCustomerPromotionListingComponent } from './asm-customer-promotion-listing.component';

@NgModule({
declarations: [AsmCustomerPromotionListingComponent],
exports: [AsmCustomerPromotionListingComponent],
imports: [
CommonModule,
I18nModule,
ArgsModule,
StarRatingModule,
MessageComponentModule,
IconModule,
],
})
export class AsmCustomerPromotionListingModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ArgsModule } from '@spartacus/asm/core';
import { I18nModule, provideDefaultConfig } from '@spartacus/core';
import {
FeaturesConfigModule,
I18nModule,
provideDefaultConfig,
} from '@spartacus/core';
import {
IconModule,
KeyboardFocusModule,
Expand Down Expand Up @@ -36,6 +40,8 @@ import {
AsmCustomerProfileComponent,
AsmCustomerSavedCartComponent,
} from './sections/components';
import { AsmCustomerCouponComponent } from './sections/asm-customer-coupon/asm-customer-coupon.component';
import { AsmCustomerCouponComponentModule } from './sections/asm-customer-coupon/asm-customer-coupon.module';

@NgModule({
imports: [
Expand All @@ -56,6 +62,8 @@ import {
AsmCustomerMapComponentModule,
AsmCustomerProductReviewsComponentModule,
AsmCustomerSupportTicketsComponentModule,
AsmCustomerCouponComponentModule,
FeaturesConfigModule,
],
declarations: [Customer360Component, AsmCustomerSectionComponent],
exports: [Customer360Component],
Expand Down Expand Up @@ -89,6 +97,9 @@ import {
AsmCustomer360MapComponent: {
component: AsmCustomerMapComponent,
},
AsmCustomer360CouponComponent: {
component: AsmCustomerCouponComponent,
},
},
}),
],
Expand Down
Loading

0 comments on commit 4370f73

Please sign in to comment.