Skip to content

Commit

Permalink
Cancel Service Order (#19117)
Browse files Browse the repository at this point in the history
Co-authored-by: Kar <[email protected]>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Bose <[email protected]>
  • Loading branch information
4 people authored Aug 21, 2024
1 parent fc480fb commit 7957c4c
Show file tree
Hide file tree
Showing 43 changed files with 1,437 additions and 16 deletions.
3 changes: 2 additions & 1 deletion integration-libs/s4-service/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

@import './styles/index';

$s4-service-components-allowlist: cx-service-details !default;
$s4-service-components-allowlist: cx-service-details
cx-cancel-service-order-headline cx-cancel-service-order !default;

$skipComponentStyles: () !default;

Expand Down
17 changes: 17 additions & 0 deletions integration-libs/s4-service/assets/translations/en/s4-service.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@
"emptyServiceDetailsCard": "None",
"unknownError": "An unknown error occurred. Please contact support."
},
"cancelService": {
"heading": "The following items will be included in the cancellation request",
"cancelReason": "Reason for cancelling service",
"optional": "(optional)",
"action": "Cancel Service",
"back": "Back",
"Submit": "Submit Request",
"cancelServiceSuccess": "The service has been cancelled sucessfully",
"unknownError": "An unknown error occurred. Please contact support.",
"ServiceTime": "Service Time",
"ServiceLocation": "Service Location",
"ServiceDate": "Service Date",
"SubmitRequest": "Submit Request",
"serviceNotRcancelable": "Service is not cancelable",
"charactersLeft": "characters left: {{count}}",
"Services": "Services"
},
"rescheduleService": {
"actionButtonLabel": "Reschedule Service",
"headerLabel": "Services",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ export const s4ServiceTranslations: TranslationResources = {
};

export const s4ServiceTranslationChunksConfig: TranslationChunksConfig = {
s4Service: ['serviceOrderCheckout', 'rescheduleService'],
s4Service: ['serviceOrderCheckout', 'rescheduleService', 'cancelService'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<p
class="cx-checkout-title d-none d-lg-block d-xl-block text-center"
[attr.aria-label]="'cancelService.heading' | cxTranslate"
>
{{ 'cancelService.heading' | cxTranslate }}
</p>

<ng-container *ngIf="order$ | async as order">
<div class="mt-3 mb-3 service-table" *ngIf="order.entries?.length">
<h3 class="service-header">{{ 'cancelService.Services' | cxTranslate }}</h3>
<div class="m-4">
<table class="table table-bordered border-0">
<thead class="thead-padding">
<tr>
<th>{{ 'cancelService.ServiceDate' | cxTranslate }}</th>
<th>{{ 'cancelService.ServiceTime' | cxTranslate }}</th>
<th>{{ 'cancelService.ServiceLocation' | cxTranslate }}</th>
</tr>
</thead>
<tbody class="tbody-padding">
<tr class="service-row">
<td>{{ order.servicedAt | date: 'MM/dd/yyyy' }}</td>
<td>{{ order.servicedAt | date: 'HH:mm' }}</td>
<td>{{ order.deliveryAddress?.town }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<ng-container *ngIf="order.entries?.length">
<ng-template
[cxOutlet]="CartOutlets.CART_ITEM_LIST"
[cxOutletContext]="{
items: order.entries,
readonly: true
}"
>
</ng-template>
</ng-container>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { of } from 'rxjs';
import { CancelServiceOrderHeadlineComponent } from './cancel-service-order-headline.component';
import { OrderDetailsService } from '@spartacus/order/components';
import { I18nTestingModule } from '@spartacus/core';
import { Pipe, PipeTransform } from '@angular/core';

// Mock data
const mockOrder = {
servicedAt: '2024-07-29T10:00:00Z',
deliveryAddress: { town: 'Test Town' },
entries: [{ product: { productTypes: 'SERVICE' } }],
};
@Pipe({
name: 'cxUrl',
})
class MockUrlPipe implements PipeTransform {
transform() {}
}

describe('CancelServiceOrderHeadlineComponent', () => {
let component: CancelServiceOrderHeadlineComponent;
let fixture: ComponentFixture<CancelServiceOrderHeadlineComponent>;
let orderDetailsService: jasmine.SpyObj<OrderDetailsService>;

beforeEach(waitForAsync(() => {
const orderDetailsServiceSpy = jasmine.createSpyObj('OrderDetailsService', [
'getOrderDetails',
]);
orderDetailsServiceSpy.getOrderDetails.and.returnValue(of(mockOrder));

TestBed.configureTestingModule({
imports: [I18nTestingModule],
declarations: [CancelServiceOrderHeadlineComponent, MockUrlPipe],
providers: [
{ provide: OrderDetailsService, useValue: orderDetailsServiceSpy },
],
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(CancelServiceOrderHeadlineComponent);
component = fixture.componentInstance;
orderDetailsService = TestBed.inject(
OrderDetailsService
) as jasmine.SpyObj<OrderDetailsService>;
fixture.detectChanges();
});

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

it('should call getOrderDetails from OrderDetailsService and set order$', () => {
component.order$.subscribe((order) => {
expect(order).toEqual({
...mockOrder,
entries: mockOrder.entries.filter(
(entry) => entry.product && entry.product.productTypes === 'SERVICE'
),
});
});
expect(orderDetailsService.getOrderDetails).toHaveBeenCalled();
});

it('should display correct service details in the table', () => {
// Convert the ISO string to a date object
const mockOrderDate = new Date(mockOrder.servicedAt);

// Format date and time according to the format used in the component
const expectedDate = mockOrderDate.toLocaleDateString('en-US', {
month: '2-digit',
day: '2-digit',
year: 'numeric',
});
const expectedTime = mockOrderDate.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: false,
});

fixture.detectChanges();
const dateCell = fixture.debugElement
.query(By.css('.service-row td:nth-child(1)'))
.nativeElement.textContent.trim();
const timeCell = fixture.debugElement
.query(By.css('.service-row td:nth-child(2)'))
.nativeElement.textContent.trim();
const locationCell = fixture.debugElement
.query(By.css('.service-row td:nth-child(3)'))
.nativeElement.textContent.trim();

expect(dateCell).toBe(expectedDate);
expect(timeCell).toBe(expectedTime);
expect(locationCell).toBe(mockOrder.deliveryAddress.town);
});

it('should render the correct aria-label attribute for the heading', () => {
const headingElement = fixture.debugElement.query(
By.css('.cx-checkout-title')
).nativeElement;
expect(headingElement.getAttribute('aria-label')).toBe(
'cancelService.heading'
);
});

it('should render the correct content in the cx-checkout-title', () => {
const titleElement = fixture.debugElement.query(
By.css('.cx-checkout-title')
).nativeElement;
expect(titleElement.textContent).toContain('cancelService.heading');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2024 SAP Spartacus team <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { CartOutlets } from '@spartacus/cart/base/root';
import { OrderDetailsService } from '@spartacus/order/components';
import { map } from 'rxjs/operators';

@Component({
selector: 'cx-cancel-service-order-headline',
templateUrl: './cancel-service-order-headline.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CancelServiceOrderHeadlineComponent {
protected orderDetailsService = inject(OrderDetailsService);
order$ = this.orderDetailsService.getOrderDetails().pipe(
map((order) => ({
...order,
entries: (order.entries || []).filter(
(entry) => entry.product && entry.product.productTypes === 'SERVICE'
),
}))
);

readonly CartOutlets = CartOutlets;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2024 SAP Spartacus team <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CancelServiceOrderHeadlineComponent } from './cancel-service-order-headline.component';
import { RouterModule } from '@angular/router';
import {
provideDefaultConfig,
CmsConfig,
AuthGuard,
I18nModule,
UrlModule,
} from '@spartacus/core';
import {
CardModule,
IconModule,
OutletModule,
PromotionsModule,
} from '@spartacus/storefront';

@NgModule({
declarations: [CancelServiceOrderHeadlineComponent],
imports: [
CommonModule,
RouterModule,
I18nModule,
CardModule,
UrlModule,
PromotionsModule,
IconModule,
OutletModule,
],
providers: [
provideDefaultConfig(<CmsConfig>{
cmsComponents: {
CancelServiceOrderHeadline: {
component: CancelServiceOrderHeadlineComponent,
guards: [AuthGuard],
},
},
}),
],
exports: [CancelServiceOrderHeadlineComponent],
})
export class CancelServiceOrderHeadlineModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<form [formGroup]="form" (ngSubmit)="cancelServiceOrder()">
<div class="form-group">
<label for="reasonTextarea"
>{{ 'cancelService.cancelReason' | cxTranslate }}
<span class="optional">{{
'cancelService.optional' | cxTranslate
}}</span></label
>
<textarea
id="reasonTextarea"
class="form-control"
formControlName="cancelReason"
rows="5"
(input)="updateCharacterLeft()"
></textarea>
<div class="characters-left">
<span>{{
'cancelService.charactersLeft' | cxTranslate: { count: characterLeft }
}}</span>
</div>
</div>

<div class="row mt-5">
<div class="col-xs-12 col-md-6 col-lg-6">
<button
type="button"
class="btn btn-secondary btn-block back-button"
*ngIf="order$ | async as order"
[routerLink]="
{
cxRoute: 'orderDetails',
params: order
} | cxUrl
"
>
{{ 'common.back' | cxTranslate }}
</button>
</div>
<div class="col-xs-12 col-md-6 col-lg-6">
<button type="submit" class="btn btn-primary btn-block">
{{ 'cancelService.SubmitRequest' | cxTranslate }}
</button>
</div>
</div>
</form>
Loading

0 comments on commit 7957c4c

Please sign in to comment.