Skip to content

Commit

Permalink
feat: Add pickup in store support for GooglePay
Browse files Browse the repository at this point in the history
Closes: CXSPA-5537
  • Loading branch information
Matejk00 authored Mar 18, 2024
1 parent 03864d7 commit 0742c24
Show file tree
Hide file tree
Showing 19 changed files with 391 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import {
AuthGuard,
CmsConfig,
I18nModule,
provideDefaultConfig,
} from '@spartacus/core';
import { CmsConfig, I18nModule, provideDefaultConfig } from '@spartacus/core';
import { QuoteCartGuardComponent } from './quote-cart-guard.component';
import { QuoteCartGuard } from './quote-cart.guard';

Expand All @@ -22,7 +17,7 @@ import { QuoteCartGuard } from './quote-cart.guard';
cmsComponents: {
QuoteCartGuardComponent: {
component: QuoteCartGuardComponent,
guards: [AuthGuard, QuoteCartGuard],
guards: [QuoteCartGuard],
},
},
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<button
*ngIf="isLoggedIn$ | async"
#element
(click)="goToQuoteDetails()"
type="button"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Pipe, PipeTransform } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { I18nTestingModule, RoutingService } from '@spartacus/core';
import {
AuthService,
I18nTestingModule,
RoutingService,
} from '@spartacus/core';
import { Quote, QuoteFacade } from '@spartacus/quote/root';
import { of } from 'rxjs';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { createEmptyQuote } from '../../core/testing/quote-test-utils';
import { QuoteRequestButtonComponent } from './quote-request-button.component';
import createSpy = jasmine.createSpy;
Expand All @@ -22,6 +26,13 @@ const mockCreatedQuote: Quote = {
class MockQuoteFacade implements Partial<QuoteFacade> {
createQuote = createSpy().and.returnValue(of(mockCreatedQuote));
}

const loggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
class MockAuthService implements Partial<AuthService> {
isUserLoggedIn(): Observable<boolean> {
return loggedIn.asObservable();
}
}
describe('QuoteRequestButtonComponent', () => {
let fixture: ComponentFixture<QuoteRequestButtonComponent>;
let component: QuoteRequestButtonComponent;
Expand All @@ -37,6 +48,10 @@ describe('QuoteRequestButtonComponent', () => {
provide: QuoteFacade,
useClass: MockQuoteFacade,
},
{
provide: AuthService,
useClass: MockAuthService,
},
{ provide: RoutingService, useValue: mockRoutingService },
],
}).compileComponents();
Expand All @@ -47,7 +62,6 @@ describe('QuoteRequestButtonComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(QuoteRequestButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
Expand All @@ -61,4 +75,14 @@ describe('QuoteRequestButtonComponent', () => {
params: { quoteId: quoteCode },
});
});
it('should render button in case user is logged in', () => {
loggedIn.next(true);
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('button')).not.toBe(null);
});
it('should not render button in case user is anonymous', () => {
loggedIn.next(false);
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('button')).toBe(null);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
inject,
OnDestroy,
} from '@angular/core';
import { RoutingService } from '@spartacus/core';
import { AuthService, RoutingService } from '@spartacus/core';
import { QuoteFacade } from '@spartacus/quote/root';
import { Subscription } from 'rxjs';
import { tap } from 'rxjs/operators';
Expand All @@ -23,9 +23,17 @@ import { tap } from 'rxjs/operators';
export class QuoteRequestButtonComponent implements OnDestroy {
protected quoteFacade = inject(QuoteFacade);
protected routingService = inject(RoutingService);
protected authService = inject(AuthService);

protected subscription = new Subscription();

/**
* Quote handling requires a logged-in user. We cannot enforce that via an authGuard here
* because otheriwise the entire cart page would need an authenticated user. So we check on
* the view and don't render the button if the user is not logged in.
*/
isLoggedIn$ = this.authService.isUserLoggedIn();

/**
* Creates a new quote and triggers the navigation according to route 'quoteDetails',
* in order to land on the quote details page.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import {
AuthGuard,
CmsConfig,
I18nModule,
provideDefaultConfig,
} from '@spartacus/core';
import { CmsConfig, I18nModule, provideDefaultConfig } from '@spartacus/core';
import { QuoteRequestButtonComponent } from './quote-request-button.component';

@NgModule({
Expand All @@ -23,7 +18,6 @@ import { QuoteRequestButtonComponent } from './quote-request-button.component';
cmsComponents: {
QuoteRequestComponent: {
component: QuoteRequestButtonComponent,
guards: [AuthGuard],
},
},
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ describe('ApplePayComponent', () => {
]);
mockCartHandlerService = jasmine.createSpyObj('OpfCartHandlerService', [
'checkStableCart',
'blockMiniCartComponentUpdate',
]);
mockApplePaySessionFactory = jasmine.createSpyObj(
'ApplePaySessionFactory',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,24 @@ describe('OpfGooglePayService', () => {
'loadOriginalCart',
'removeProductFromOriginalCart',
'loadCartAfterSingleProductTransaction',
'blockMiniCartComponentUpdate',
]);
mockPaymentFacade = jasmine.createSpyObj('OpfPaymentFacade', [
'submitPayment',
]);
mockQuickBuyService = jasmine.createSpyObj('OpfQuickBuyService', [
'getQuickBuyLocationContext',
'getQuickBuyProviderConfig',
'getQuickBuyDeliveryType',
]);

const googlePayApiMock = {
payments: {
api: {
PaymentsClient: jasmine.createSpy('PaymentsClient').and.returnValue({
loadPaymentData: jasmine
.createSpy()
.and.returnValue(Promise.resolve({})),
isReadyToPay: jasmine
.createSpy()
.and.returnValue(Promise.resolve({ result: true })),
Expand All @@ -99,6 +104,7 @@ describe('OpfGooglePayService', () => {
});

service = TestBed.inject(OpfGooglePayService);
service['updateGooglePaymentClient']();
});

describe('getClient', () => {
Expand Down Expand Up @@ -479,72 +485,50 @@ describe('OpfGooglePayService', () => {
});

describe('initTransaction', () => {
beforeEach(() => {
const mockGooglePaymentClient = jasmine.createSpyObj('PaymentsClient', [
'loadPaymentData',
'isReadyToPay',
]);
service['googlePaymentClient'] = mockGooglePaymentClient;
service['googlePaymentClient'].loadPaymentData = () =>
Promise.resolve({} as google.payments.api.PaymentData);
});

it('should initiate a transaction process for single product', (done) => {
spyOn(service, 'handleActiveCartTransaction').and.callThrough();
spyOn(service, 'handleSingleProductTransaction').and.callThrough();
spyOn(
service['googlePaymentClient'],
'loadPaymentData'
).and.callThrough();
it('should initialize transaction for single product context', (done: DoneFn) => {
spyOn(service, 'handleSingleProductTransaction').and.returnValue(
of(null)
);
spyOn(service, 'handleActiveCartTransaction').and.returnValue(of(null));

mockQuickBuyService.getQuickBuyLocationContext.and.returnValue(
of(OpfQuickBuyLocation.PRODUCT)
);
const mockProduct = { code: 'productCode', price: { value: 100 } };
const counter = 1;
mockCurrentProductService.getProduct.and.returnValue(of(mockProduct));
mockItemCounterService.getCounter.and.returnValue(counter);
mockCartHandlerService.deleteCurrentCart.and.returnValue(of(true));
mockCartHandlerService.addProductToCart.and.returnValue(of(true));
mockCartHandlerService.getCurrentCart.and.returnValue(of({}));
mockCartHandlerService.loadCartAfterSingleProductTransaction.and.returnValue();

service.initTransaction();

expect(service['transactionDetails'].context).toBe(
OpfQuickBuyLocation.PRODUCT
);

setTimeout(() => {
expect(service.handleActiveCartTransaction).not.toHaveBeenCalled();
expect(service.handleSingleProductTransaction).toHaveBeenCalled();
expect(mockCurrentProductService.getProduct).toHaveBeenCalled();
expect(mockCartHandlerService.deleteCurrentCart).not.toHaveBeenCalled();
expect(mockCartHandlerService.addProductToCart).toHaveBeenCalledWith(
mockProduct.code,
counter
);
expect(
service['googlePaymentClient'].loadPaymentData
).toHaveBeenCalled();
expect(service.handleActiveCartTransaction).not.toHaveBeenCalled();
done();
}, 0);
});

it('should initiate a transaction process for active cart', () => {
spyOn(service, 'handleActiveCartTransaction').and.callThrough();
spyOn(service, 'handleSingleProductTransaction').and.callThrough();
spyOn(
service['googlePaymentClient'],
'loadPaymentData'
).and.callThrough();
it('should initialize transaction for active cart context', (done: DoneFn) => {
spyOn(service, 'handleSingleProductTransaction').and.returnValue(
of(null)
);
spyOn(service, 'handleActiveCartTransaction').and.returnValue(of(null));

mockQuickBuyService.getQuickBuyLocationContext.and.returnValue(
of(OpfQuickBuyLocation.CART)
);
mockCartHandlerService.getCurrentCart.and.returnValue(of({}));

service.initTransaction();

expect(mockCartHandlerService.getCurrentCart).toHaveBeenCalled();
expect(service.handleActiveCartTransaction).toHaveBeenCalled();
expect(service.handleSingleProductTransaction).not.toHaveBeenCalled();
expect(service['googlePaymentClient'].loadPaymentData).toHaveBeenCalled();
expect(service['transactionDetails'].context).toBe(
OpfQuickBuyLocation.CART
);

setTimeout(() => {
expect(service.handleActiveCartTransaction).toHaveBeenCalled();
expect(service.handleSingleProductTransaction).not.toHaveBeenCalled();
done();
}, 0);
});
});

Expand Down
Loading

0 comments on commit 0742c24

Please sign in to comment.