Skip to content

Commit

Permalink
Merge pull request #36 from xsolla/PAYMENTS-16646-phone-control
Browse files Browse the repository at this point in the history
feat(PAYMENTS-16646): add phone control
  • Loading branch information
Aleksey-Kornienko-xsolla authored Oct 20, 2023
2 parents 3ca2da1 + c417065 commit c3c53db
Show file tree
Hide file tree
Showing 15 changed files with 125 additions and 52 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ Using SDK components is straightforward: you only need to paste the HTML tag of
| **Component** | **Selector** | **Status** |
| --------------------- | ------------ | ---------- |
| Text Component | psdk-text ||
| Phone Component | | 🕑 |
| Phone Component | psdk-phone | |
| Card Number Component || 🕑 |

![SDK secure componentscheme](./readme_images/secure_component_scheme.png 'SDK secure componentscheme')
Expand Down
1 change: 1 addition & 0 deletions src/core/web-components/web-component-tag-name.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export enum WebComponentTagName {
LegalComponent = 'psdk-legal',
PaymentFormComponent = 'psdk-payment-form',
StatusComponent = 'psdk-status',
PhoneComponent = 'psdk-phone',
}
2 changes: 2 additions & 0 deletions src/core/web-components/web-components.map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FinanceDetailsComponent } from '../../features/headless-checkout/web-co
import { PriceTextComponent } from '../../features/headless-checkout/web-components/finance-details/price-text/price-text.component';
import { WebComponentTagName } from './web-component-tag-name.enum';
import { PaymentFormComponent } from '../../features/headless-checkout/web-components/payment-form/payment-form.component';
import { PhoneComponent } from '../../features/headless-checkout/web-components/phone-component/phone.component';

export const webComponents: {
[key in WebComponentTagName]: CustomElementConstructor;
Expand All @@ -19,4 +20,5 @@ export const webComponents: {
[WebComponentTagName.LegalComponent]: LegalComponent,
[WebComponentTagName.StatusComponent]: StatusComponent,
[WebComponentTagName.PaymentFormComponent]: PaymentFormComponent,
[WebComponentTagName.PhoneComponent]: PhoneComponent,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum';

export const formControlsTags = [
WebComponentTagName.TextComponent,
WebComponentTagName.PhoneComponent,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum';
import { webComponentsFieldsNamesMap } from './web-components-fields-names.map';

export const getWebComponentByFieldName = (
name: string
): WebComponentTagName => {
const webComponent: undefined | WebComponentTagName =
webComponentsFieldsNamesMap[name];

return webComponent ?? WebComponentTagName.TextComponent;
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const getTextInputElements = (names: string[]): NodeListOf<Element> => {
const mockContainer = document.createElement('div');
for (const name of names) {
const mockElement = document.createElement(
WebComponentTagName.TextComponent,
WebComponentTagName.TextComponent
);
mockElement.setAttribute('name', name);
mockContainer.appendChild(mockElement);
Expand All @@ -34,14 +34,14 @@ describe('PaymentFormFieldsManager', () => {
it('Should create missed fields and append them into body', () => {
const textInputElements = getTextInputElements([]);
spyOn(windowService.document, 'querySelectorAll').and.returnValue(
textInputElements,
textInputElements
);
const missedFields = ['card'];
const mockBody = {
appendChild: noopStub,
} as unknown as HTMLElement;
spyOn(windowService.document, 'querySelector').and.returnValue(
mockBody as unknown as Element,
mockBody as unknown as Element
);
const spy = spyOn(mockBody, 'appendChild');
paymentFormFieldsManager.createMissedFields(missedFields, mockBody);
Expand All @@ -54,7 +54,7 @@ describe('PaymentFormFieldsManager', () => {
remove: noopStub,
};
spyOn(windowService.document, 'querySelector').and.returnValue(
mockElement as unknown as Element,
mockElement as unknown as Element
);
const spy = spyOn(mockElement, 'remove');
paymentFormFieldsManager.removeExtraFields(extraFields);
Expand All @@ -67,7 +67,7 @@ describe('PaymentFormFieldsManager', () => {
element.removeAttribute('name');
});
spyOn(windowService.document, 'querySelectorAll').and.returnValue(
textInputElements,
textInputElements
);
const spy = spyOn(textInputElements[0], 'remove');
paymentFormFieldsManager.removeEmptyNameFields();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { injectable } from 'tsyringe';
import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum';
import { getWebComponentByFieldName } from './get-web-component-by-field-name.function';

@injectable()
export class PaymentFormFieldsService {
Expand All @@ -13,7 +14,7 @@ export class PaymentFormFieldsService {

for (const name of missedFieldsNames) {
const formElement = this.window.document.createElement(
WebComponentTagName.TextComponent
getWebComponentByFieldName(name)
);
formElement.setAttribute('name', name);
documentFragment.append(formElement);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { PostMessagesClient } from '../../../../core/post-messages-client/post-m

function createComponent(): void {
const element = document.createElement(
WebComponentTagName.PaymentFormComponent,
WebComponentTagName.PaymentFormComponent
);
element.setAttribute('id', 'test');
(document.getElementById('container')! as HTMLElement).appendChild(element);
Expand All @@ -29,7 +29,7 @@ const getTextInputElements = (names: string[]): NodeListOf<Element> => {
const mockContainer = document.createElement('div');
for (const name of names) {
const mockElement = document.createElement(
WebComponentTagName.TextComponent,
WebComponentTagName.TextComponent
);
mockElement.setAttribute('name', name);
mockContainer.appendChild(mockElement);
Expand All @@ -47,7 +47,7 @@ describe('PaymentFormComponent', () => {

window.customElements.define(
WebComponentTagName.PaymentFormComponent,
PaymentFormComponent,
PaymentFormComponent
);

beforeEach(() => {
Expand Down Expand Up @@ -109,7 +109,7 @@ describe('PaymentFormComponent', () => {
it('Should create component', () => {
createComponent();
expect(
document.querySelector(WebComponentTagName.PaymentFormComponent),
document.querySelector(WebComponentTagName.PaymentFormComponent)
).toBeDefined();
});

Expand Down Expand Up @@ -139,7 +139,7 @@ describe('PaymentFormComponent', () => {

const textInputElements = getTextInputElements(['zip']);
spyOn(windowService.document, 'querySelectorAll').and.returnValue(
textInputElements,
textInputElements
);

const spy = spyOn(paymentFormFieldsManager, 'createMissedFields');
Expand All @@ -164,12 +164,12 @@ describe('PaymentFormComponent', () => {

const textInputElements = getTextInputElements(['zip', 'zip']);
spyOn(windowService.document, 'querySelectorAll').and.returnValue(
textInputElements,
textInputElements
);

const spy = spyOn(paymentFormFieldsManager, 'removeExtraFields');
createComponent();

expect(spy).toHaveBeenCalledWith(['zip']);
expect(spy).toHaveBeenCalledWith(['zip', 'zip', 'zip']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getMissedFieldsNames } from './get-missed-fields-names.function';
import { getInvalidFieldsNames } from './get-invalid-fields-names.function';
import { PaymentFormFieldsService } from './payment-form-fields.service';
import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum';
import { formControlsTags } from './form-controls-tags.list';

export class PaymentFormComponent extends WebComponentAbstract {
private readonly headlessCheckout: HeadlessCheckout;
Expand Down Expand Up @@ -90,12 +91,21 @@ export class PaymentFormComponent extends WebComponentAbstract {
}

private getExistsControlsNames(): Array<string | null> {
const formInputs = this.window.document.querySelectorAll(
WebComponentTagName.TextComponent
);
return Array.from(formInputs).map((formInput) =>
formInput.getAttribute('name')
);
const existsControlsNames: Array<string | null> = [];

formControlsTags.forEach((tag: WebComponentTagName) => {
const formInputs = this.window.document.querySelectorAll(tag);

const controlsNames = Array.from(formInputs).map((formInput) =>
formInput.getAttribute('name')
);

controlsNames.forEach((name: string | null) =>
existsControlsNames.push(name)
);
});

return existsControlsNames;
}

private logMissedFields(missedFieldsNames: string[]): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { WebComponentTagName } from '../../../../core/web-components/web-component-tag-name.enum';

export const webComponentsFieldsNamesMap: { [k: string]: WebComponentTagName } =
{
phone: WebComponentTagName.PhoneComponent,
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { HeadlessCheckout } from '../../headless-checkout';

function createComponent(): void {
const element = document.createElement(
WebComponentTagName.PaymentMethodsComponent,
WebComponentTagName.PaymentMethodsComponent
);
element.setAttribute('id', 'test');
(document.getElementById('container')! as HTMLElement).appendChild(element);
Expand Down Expand Up @@ -40,7 +40,7 @@ describe('PaymentMethodsComponent', () => {

window.customElements.define(
WebComponentTagName.PaymentMethodsComponent,
PaymentMethodsComponent,
PaymentMethodsComponent
);

beforeEach(() => {
Expand Down Expand Up @@ -75,29 +75,29 @@ describe('PaymentMethodsComponent', () => {
it('Should create component', () => {
createComponent();
expect(
document.querySelector(WebComponentTagName.PaymentMethodsComponent),
document.querySelector(WebComponentTagName.PaymentMethodsComponent)
).toBeDefined();
});

it('Should load payment methods', () => {
const spy = spyOn(headlessCheckout, 'getRegularMethods').and.returnValue(
Promise.resolve([]),
Promise.resolve([])
);
spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue(
true,
true
);
createComponent();
expect(spy).toHaveBeenCalled();
});

it('Should load payment methods after init', () => {
const spy = spyOn(headlessCheckout, 'getRegularMethods').and.returnValue(
Promise.resolve([]),
Promise.resolve([])
);
const appWasInitSpy = spyOnProperty(
headlessCheckoutSpy,
'appWasInit',
'get',
'get'
);
const listenAppInitSpy = spyOn(headlessCheckoutSpy, 'listenAppInit');
listenAppInitSpy.and.callFake((callback: () => void) => {
Expand All @@ -111,21 +111,21 @@ describe('PaymentMethodsComponent', () => {

it('Should not load payment methods', () => {
const spy = spyOn(headlessCheckout, 'getRegularMethods').and.returnValue(
Promise.resolve([]),
Promise.resolve([])
);
spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue(
false,
false
);
createComponent();
expect(spy).not.toHaveBeenCalled();
});

it('Should load payment methods twice if change country', () => {
const spy = spyOn(headlessCheckout, 'getRegularMethods').and.returnValue(
Promise.resolve([]),
Promise.resolve([])
);
spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue(
true,
true
);
createComponent();
document
Expand All @@ -136,10 +136,10 @@ describe('PaymentMethodsComponent', () => {

it('Should draw 2 payment methods', async () => {
spyOn(headlessCheckout, 'getRegularMethods').and.returnValue(
Promise.resolve([mockVisiblePaymentMethod, mockVisiblePaymentMethod]),
Promise.resolve([mockVisiblePaymentMethod, mockVisiblePaymentMethod])
);
spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue(
true,
true
);
createComponent();

Expand All @@ -150,10 +150,10 @@ describe('PaymentMethodsComponent', () => {

it('Should draw 1 payment methods', async () => {
spyOn(headlessCheckout, 'getRegularMethods').and.returnValue(
Promise.resolve([mockNotVisiblePaymentMethod, mockVisiblePaymentMethod]),
Promise.resolve([mockNotVisiblePaymentMethod, mockVisiblePaymentMethod])
);
spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue(
true,
true
);
createComponent();

Expand All @@ -166,10 +166,10 @@ describe('PaymentMethodsComponent', () => {
Promise.resolve([
mockNotVisiblePaymentMethod,
mockNotVisiblePaymentMethod,
]),
])
);
spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue(
true,
true
);
createComponent();

Expand All @@ -179,10 +179,10 @@ describe('PaymentMethodsComponent', () => {

it('Should dispatch custom event', async () => {
spyOn(headlessCheckout, 'getRegularMethods').and.returnValue(
Promise.resolve([mockVisiblePaymentMethod]),
Promise.resolve([mockVisiblePaymentMethod])
);
spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue(
true,
true
);
createComponent();

Expand All @@ -200,10 +200,10 @@ describe('PaymentMethodsComponent', () => {

it('Should search methods', async () => {
spyOn(headlessCheckout, 'getRegularMethods').and.returnValue(
Promise.resolve([mockVisiblePaymentMethod, mockQiwiPaymentMethod]),
Promise.resolve([mockVisiblePaymentMethod, mockQiwiPaymentMethod])
);
spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue(
true,
true
);
createComponent();

Expand All @@ -218,10 +218,10 @@ describe('PaymentMethodsComponent', () => {

it('Should draw no methods', async () => {
spyOn(headlessCheckout, 'getRegularMethods').and.returnValue(
Promise.resolve([mockVisiblePaymentMethod, mockQiwiPaymentMethod]),
Promise.resolve([mockVisiblePaymentMethod, mockQiwiPaymentMethod])
);
spyOnProperty(headlessCheckoutSpy, 'appWasInit', 'get').and.returnValue(
true,
true
);
createComponent();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum PhoneComponentAttributes {
name = 'name',
showFlags = 'showflags',
}
Loading

0 comments on commit c3c53db

Please sign in to comment.